jinzd-ai-cli 0.4.215 → 0.4.216

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +2 -0
  2. package/dist/{batch-74H5SA7P.js → batch-C6HCXZIG.js} +2 -2
  3. package/dist/{chunk-ZOF5NFKW.js → chunk-6HLKCSB3.js} +1 -1
  4. package/dist/{chunk-X6OXS7KU.js → chunk-77HDCGTN.js} +1 -1
  5. package/dist/{chunk-7JES2NWR.js → chunk-7YWS3JLU.js} +1 -1
  6. package/dist/{ci-3ALK2XJN.js → chunk-GY3C3C2Y.js} +40 -46
  7. package/dist/{chunk-RS4WBI73.js → chunk-OLN7VUZA.js} +1 -1
  8. package/dist/{chunk-KL7UBVSQ.js → chunk-PNTLA3MN.js} +2 -2
  9. package/dist/{chunk-6C3JYNM6.js → chunk-TCOC4AUI.js} +3 -3
  10. package/dist/chunk-TJ6GGN4B.js +105 -0
  11. package/dist/{chunk-3TSHNZKI.js → chunk-VW7Y27WW.js} +1 -1
  12. package/dist/{chunk-2224JGA6.js → chunk-WAI3WPV2.js} +1 -1
  13. package/dist/ci-6ZTFO3LX.js +19 -0
  14. package/dist/ci-format-CLZ6QJRL.js +18 -0
  15. package/dist/{constants-IN2HXJK7.js → constants-APSORFOH.js} +1 -1
  16. package/dist/{doctor-cli-7XL4TCVT.js → doctor-cli-GUIX4X5F.js} +4 -4
  17. package/dist/electron-server.js +2 -2
  18. package/dist/{hub-VAOG5EY6.js → hub-ZGHQWNWE.js} +1 -1
  19. package/dist/index.js +58 -22
  20. package/dist/pr-JOL3IAGV.js +267 -0
  21. package/dist/{run-tests-J2JQ57R3.js → run-tests-4PKSIVK5.js} +1 -1
  22. package/dist/{run-tests-QJD43AML.js → run-tests-ZRK4TQUN.js} +2 -2
  23. package/dist/{server-VGPZOISQ.js → server-FXUF5P64.js} +7 -7
  24. package/dist/{server-WDLV3W4F.js → server-O3XHT56X.js} +4 -4
  25. package/dist/{task-orchestrator-QFOCO3N7.js → task-orchestrator-TUMDTOAX.js} +4 -4
  26. package/dist/{usage-IEB476NE.js → usage-X7MJX4YD.js} +2 -2
  27. package/package.json +1 -1
package/README.md CHANGED
@@ -47,6 +47,8 @@
47
47
  - **Headless Mode** — `ai-cli -p "prompt"` for CI/CD pipelines and scripting
48
48
  - **44 REPL Commands** — Session management, checkpointing, code review, security review/scan, rewind, scaffolding, cross-session history search, chat-memory recall, smart model routing (`/route`), and more
49
49
  - **GitHub Actions CI/CD** — Automated testing on Node 20/22 + npm publish on release tags
50
+ - **PR Review CLI** *(v0.4.216+)* — `aicli pr review|security-review|summarize` reviews `main...HEAD`, custom `--base/--head`, or GitHub PR URLs; optional `--agents security,bugs,tests,maintainability`; never posts comments unless `--post` is explicit
51
+ - **CI Review Artifacts** *(v0.4.216+)* — `aicli ci` emits Markdown, JSON, or SARIF and gates on security-high, test-failure, and lint-failure findings in read-only review mode
50
52
  - **Cross-Platform** — Windows, macOS, Linux
51
53
 
52
54
  ## Installation
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ConfigManager
4
- } from "./chunk-2224JGA6.js";
4
+ } from "./chunk-WAI3WPV2.js";
5
5
  import "./chunk-TZQHYZKT.js";
6
- import "./chunk-7JES2NWR.js";
6
+ import "./chunk-7YWS3JLU.js";
7
7
  import {
8
8
  atomicWriteFileSync
9
9
  } from "./chunk-IW3Q7AE5.js";
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  CONFIG_DIR_NAME,
4
4
  VERSION
5
- } from "./chunk-7JES2NWR.js";
5
+ } from "./chunk-7YWS3JLU.js";
6
6
 
7
7
  // src/diagnostics/crash-log.ts
8
8
  import {
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  CONFIG_DIR_NAME
4
- } from "./chunk-7JES2NWR.js";
4
+ } from "./chunk-7YWS3JLU.js";
5
5
  import {
6
6
  atomicWriteFileSync
7
7
  } from "./chunk-IW3Q7AE5.js";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/core/constants.ts
4
- var VERSION = "0.4.215";
4
+ var VERSION = "0.4.216";
5
5
  var APP_NAME = "ai-cli";
6
6
  var CONFIG_DIR_NAME = ".aicli";
7
7
  var CONFIG_FILE_NAME = "config.json";
@@ -1,4 +1,8 @@
1
1
  #!/usr/bin/env node
2
+ import {
3
+ evaluateCiGate,
4
+ normalizeCiThresholds
5
+ } from "./chunk-TJ6GGN4B.js";
2
6
  import {
3
7
  buildReviewPrompt,
4
8
  buildSecurityReviewPrompt,
@@ -7,19 +11,30 @@ import {
7
11
  import {
8
12
  ProviderRegistry
9
13
  } from "./chunk-QMXC327F.js";
10
- import "./chunk-XPBEJB27.js";
11
14
  import {
12
15
  ConfigManager
13
- } from "./chunk-2224JGA6.js";
14
- import "./chunk-TZQHYZKT.js";
16
+ } from "./chunk-WAI3WPV2.js";
15
17
  import {
16
18
  VERSION
17
- } from "./chunk-7JES2NWR.js";
18
- import "./chunk-IW3Q7AE5.js";
19
+ } from "./chunk-7YWS3JLU.js";
19
20
 
20
21
  // src/cli/ci.ts
21
22
  import { execFileSync } from "child_process";
22
23
  var CI_COMMENT_MARKER = "<!-- aicli-ci-review -->";
24
+ function zeroSeverity() {
25
+ return { critical: 0, high: 0, warning: 0, info: 0 };
26
+ }
27
+ function earlyResult(exitCode, markdown, thresholds) {
28
+ const gate = evaluateCiGate(markdown, thresholds);
29
+ return {
30
+ exitCode: exitCode === 0 && gate.failed ? 1 : exitCode,
31
+ markdown,
32
+ posted: false,
33
+ severity: zeroSeverity(),
34
+ gate,
35
+ meta: { mode: "read-only", thresholds }
36
+ };
37
+ }
23
38
  function fetchDiff(opts) {
24
39
  if (opts.diffOverride != null) return opts.diffOverride;
25
40
  if (opts.pr != null) {
@@ -48,9 +63,7 @@ function fetchDiff(opts) {
48
63
  throw new Error("aicli ci: must supply --pr <num> or --base <ref> to determine the diff source");
49
64
  }
50
65
  function validateGitRef(ref) {
51
- if (!/^[A-Za-z0-9._/\-]+$/.test(ref)) {
52
- throw new Error(`unsafe ref: ${ref}`);
53
- }
66
+ if (!/^[A-Za-z0-9._/\-]+$/.test(ref)) throw new Error(`unsafe ref: ${ref}`);
54
67
  }
55
68
  function buildGitContextStr(opts) {
56
69
  const parts = [];
@@ -62,6 +75,7 @@ function buildGitContextStr(opts) {
62
75
  } catch {
63
76
  }
64
77
  parts.push(`Reviewer: aicli v${VERSION}`);
78
+ parts.push("Mode: read-only CI review (no tools executed)");
65
79
  return parts.join(" | ");
66
80
  }
67
81
  function countSeverity(md) {
@@ -118,9 +132,7 @@ ${CI_COMMENT_MARKER}`;
118
132
  function gqlIdToRest(graphqlId) {
119
133
  if (/^\d+$/.test(graphqlId)) return graphqlId;
120
134
  const query = `query($id:ID!){ node(id:$id){ ... on IssueComment { databaseId } } }`;
121
- const out = execFileSync("gh", ["api", "graphql", "-f", `query=${query}`, "-F", `id=${graphqlId}`], {
122
- encoding: "utf-8"
123
- });
135
+ const out = execFileSync("gh", ["api", "graphql", "-f", `query=${query}`, "-F", `id=${graphqlId}`], { encoding: "utf-8" });
124
136
  const parsed = JSON.parse(out);
125
137
  const id = parsed.data?.node?.databaseId;
126
138
  if (!id) throw new Error(`could not resolve comment id ${graphqlId}`);
@@ -128,25 +140,14 @@ function gqlIdToRest(graphqlId) {
128
140
  }
129
141
  async function runCi(opts) {
130
142
  const maxDiff = opts.maxDiffChars ?? 3e4;
143
+ const thresholds = normalizeCiThresholds(opts.thresholds);
131
144
  let diff;
132
145
  try {
133
146
  diff = fetchDiff(opts).trim();
134
147
  } catch (err) {
135
- return {
136
- exitCode: 2,
137
- markdown: `\u274C ${err.message}`,
138
- posted: false,
139
- severity: { critical: 0, high: 0, warning: 0, info: 0 }
140
- };
141
- }
142
- if (!diff) {
143
- return {
144
- exitCode: 0,
145
- markdown: "\u2705 No changes to review.",
146
- posted: false,
147
- severity: { critical: 0, high: 0, warning: 0, info: 0 }
148
- };
148
+ return earlyResult(2, `\u274C ${err.message}`, thresholds);
149
149
  }
150
+ if (!diff) return earlyResult(0, "\u2705 No changes to review.", thresholds);
150
151
  const { diff: trimmedDiff, truncated } = truncateDiff(diff, maxDiff);
151
152
  const gitCtx = buildGitContextStr(opts);
152
153
  const config = new ConfigManager();
@@ -162,18 +163,14 @@ async function runCi(opts) {
162
163
  );
163
164
  const providerId = opts.provider ?? config.getDefaultProvider();
164
165
  if (!registry.has(providerId)) {
165
- return {
166
- exitCode: 2,
167
- markdown: `\u274C Provider '${providerId}' is not configured. Set the right AICLI_API_KEY_* env var or pass --provider.`,
168
- posted: false,
169
- severity: { critical: 0, high: 0, warning: 0, info: 0 }
170
- };
166
+ return earlyResult(2, `\u274C Provider '${providerId}' is not configured. Set the right AICLI_API_KEY_* env var or pass --provider.`, thresholds);
171
167
  }
172
168
  const providerInfo = registry.get(providerId).info;
173
169
  const modelId = opts.model ?? config.get("defaultModels")[providerId] ?? providerInfo.defaultModel;
170
+ const meta = { provider: providerId, model: modelId, mode: "read-only", thresholds };
174
171
  const sections = [];
175
- sections.push(`## \u{1F916} aicli code review`);
176
- sections.push(`*Provider: \`${providerId}\` \xB7 Model: \`${modelId}\` \xB7 aicli v${VERSION}*`);
172
+ sections.push("## \u{1F916} aicli code review");
173
+ sections.push(`*Provider: \`${providerId}\` \xB7 Model: \`${modelId}\` \xB7 Mode: \`read-only CI review\` \xB7 aicli v${VERSION}*`);
177
174
  sections.push("");
178
175
  try {
179
176
  if (!opts.skipCode) {
@@ -195,14 +192,15 @@ async function runCi(opts) {
195
192
  exitCode: 2,
196
193
  markdown: `\u274C Review call failed: ${err.message}`,
197
194
  posted: false,
198
- severity: { critical: 0, high: 0, warning: 0, info: 0 }
195
+ severity: zeroSeverity(),
196
+ gate: evaluateCiGate("", thresholds),
197
+ meta
199
198
  };
200
199
  }
201
- if (truncated) {
202
- sections.push(`> \u26A0 Diff was truncated to ${maxDiff} chars (original: ${diff.length}). Consider splitting large PRs.`);
203
- }
200
+ if (truncated) sections.push(`> \u26A0 Diff was truncated to ${maxDiff} chars (original: ${diff.length}). Consider splitting large PRs.`);
204
201
  const markdown = sections.join("\n");
205
202
  const severity = countSeverity(markdown);
203
+ const gate = evaluateCiGate(markdown, thresholds);
206
204
  let posted = false;
207
205
  let updatedCommentId;
208
206
  if (opts.post && opts.pr != null && !opts.dryRun) {
@@ -219,19 +217,15 @@ async function runCi(opts) {
219
217
 
220
218
  \u274C Failed to post comment: ${err.message}`,
221
219
  posted: false,
222
- severity
220
+ severity,
221
+ gate,
222
+ meta
223
223
  };
224
224
  }
225
225
  }
226
- const hasBlocker = severity.critical > 0 || severity.high > 0;
227
- return {
228
- exitCode: hasBlocker ? 1 : 0,
229
- markdown,
230
- posted,
231
- updatedCommentId,
232
- severity
233
- };
226
+ return { exitCode: gate.failed ? 1 : 0, markdown, posted, updatedCommentId, severity, gate, meta };
234
227
  }
228
+
235
229
  export {
236
230
  CI_COMMENT_MARKER,
237
231
  countSeverity,
@@ -6,7 +6,7 @@ import { platform } from "os";
6
6
  import chalk from "chalk";
7
7
 
8
8
  // src/core/constants.ts
9
- var VERSION = "0.4.215";
9
+ var VERSION = "0.4.216";
10
10
  var APP_NAME = "ai-cli";
11
11
  var CONFIG_DIR_NAME = ".aicli";
12
12
  var CONFIG_FILE_NAME = "config.json";
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  truncateForPersist
4
- } from "./chunk-6C3JYNM6.js";
4
+ } from "./chunk-TCOC4AUI.js";
5
5
  import {
6
6
  APP_NAME,
7
7
  CONFIG_DIR_NAME,
@@ -13,7 +13,7 @@ import {
13
13
  MCP_PROTOCOL_VERSION,
14
14
  MCP_TOOL_PREFIX,
15
15
  VERSION
16
- } from "./chunk-7JES2NWR.js";
16
+ } from "./chunk-7YWS3JLU.js";
17
17
  import {
18
18
  atomicWriteFileSync
19
19
  } from "./chunk-IW3Q7AE5.js";
@@ -5,10 +5,10 @@ import {
5
5
  } from "./chunk-T2NL5ZIA.js";
6
6
  import {
7
7
  runTestsTool
8
- } from "./chunk-3TSHNZKI.js";
8
+ } from "./chunk-VW7Y27WW.js";
9
9
  import {
10
10
  runTool
11
- } from "./chunk-X6OXS7KU.js";
11
+ } from "./chunk-77HDCGTN.js";
12
12
  import {
13
13
  getDangerLevel,
14
14
  isFileWriteTool,
@@ -26,7 +26,7 @@ import {
26
26
  SUBAGENT_ALLOWED_TOOLS,
27
27
  SUBAGENT_DEFAULT_MAX_ROUNDS,
28
28
  SUBAGENT_MAX_ROUNDS_LIMIT
29
- } from "./chunk-7JES2NWR.js";
29
+ } from "./chunk-7YWS3JLU.js";
30
30
  import {
31
31
  fileCheckpoints
32
32
  } from "./chunk-4BKXL7SM.js";
@@ -0,0 +1,105 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ VERSION
4
+ } from "./chunk-7YWS3JLU.js";
5
+
6
+ // src/cli/ci-format.ts
7
+ var DEFAULT_CI_THRESHOLDS = {
8
+ securityHigh: true,
9
+ testFailure: true,
10
+ lintFailure: true
11
+ };
12
+ function normalizeCiThresholds(input) {
13
+ return {
14
+ securityHigh: input?.securityHigh ?? DEFAULT_CI_THRESHOLDS.securityHigh,
15
+ testFailure: input?.testFailure ?? DEFAULT_CI_THRESHOLDS.testFailure,
16
+ lintFailure: input?.lintFailure ?? DEFAULT_CI_THRESHOLDS.lintFailure
17
+ };
18
+ }
19
+ function extractSection(markdown, heading) {
20
+ const start = markdown.indexOf(heading);
21
+ if (start < 0) return "";
22
+ const rest = markdown.slice(start + heading.length);
23
+ const next = rest.search(/\n###\s+/);
24
+ return next >= 0 ? rest.slice(0, next) : rest;
25
+ }
26
+ function detectCiGateSignals(markdown) {
27
+ const security = extractSection(markdown, "### Security Review");
28
+ const securityHigh = (security.match(/(?:🔴\s*CRITICAL|🔴\s*Critical|🟠\s*HIGH|🟠\s*High)/g) ?? []).length;
29
+ const testFailure = (markdown.match(/(?:test(?:s|ing)?\s+(?:failed|failure|failures)|failing\s+test|npm\s+test\s+failed|pytest\s+failed|vitest\s+failed)/gi) ?? []).length;
30
+ const lintFailure = (markdown.match(/(?:lint\s+(?:failed|failure|failures)|eslint\s+failed|ruff\s+failed|biome\s+failed|typecheck\s+failed|tsc\s+failed)/gi) ?? []).length;
31
+ return { securityHigh, testFailure, lintFailure };
32
+ }
33
+ function evaluateCiGate(markdown, thresholds) {
34
+ const normalized = normalizeCiThresholds(thresholds);
35
+ const signals = detectCiGateSignals(markdown);
36
+ const reasons = [];
37
+ if (normalized.securityHigh && signals.securityHigh > 0) reasons.push(`security-high:${signals.securityHigh}`);
38
+ if (normalized.testFailure && signals.testFailure > 0) reasons.push(`test-failure:${signals.testFailure}`);
39
+ if (normalized.lintFailure && signals.lintFailure > 0) reasons.push(`lint-failure:${signals.lintFailure}`);
40
+ return { thresholds: normalized, signals, failed: reasons.length > 0, reasons };
41
+ }
42
+ function sarifLevel(line) {
43
+ if (/🔴|🟠|CRITICAL|HIGH/i.test(line)) return "error";
44
+ if (/🟡|WARNING|MEDIUM/i.test(line)) return "warning";
45
+ return "note";
46
+ }
47
+ function stripMarkdown(line) {
48
+ return line.replace(/^[\s>*-]+/, "").replace(/[`*_]/g, "").trim();
49
+ }
50
+ function formatCiSarif(result) {
51
+ const lines = result.markdown.split(/\r?\n/);
52
+ const findingLines = lines.map((line, idx) => ({ line, idx })).filter(({ line }) => /🔴|🟠|🟡|🔵|CRITICAL|HIGH|MEDIUM|LOW|WARNING|INFO/i.test(line));
53
+ const results = findingLines.map(({ line, idx }, n) => ({
54
+ ruleId: "aicli-review-finding",
55
+ level: sarifLevel(line),
56
+ message: { text: stripMarkdown(line) || line.trim() },
57
+ locations: [{
58
+ physicalLocation: {
59
+ artifactLocation: { uri: "aicli-review.md" },
60
+ region: { startLine: idx + 1 }
61
+ }
62
+ }],
63
+ partialFingerprints: { aicliLine: String(n + 1) }
64
+ }));
65
+ return JSON.stringify({
66
+ version: "2.1.0",
67
+ $schema: "https://json.schemastore.org/sarif-2.1.0.json",
68
+ runs: [{
69
+ tool: {
70
+ driver: {
71
+ name: "aicli",
72
+ informationUri: "https://github.com/jinzhengdong/ai-cli",
73
+ version: VERSION,
74
+ rules: [{
75
+ id: "aicli-review-finding",
76
+ name: "aicli review finding",
77
+ shortDescription: { text: "Finding emitted by aicli CI review" },
78
+ helpUri: "https://github.com/jinzhengdong/ai-cli"
79
+ }]
80
+ }
81
+ },
82
+ invocations: [{ executionSuccessful: !result.gate.failed }],
83
+ results,
84
+ properties: {
85
+ severity: result.severity,
86
+ gate: result.gate,
87
+ posted: result.posted
88
+ }
89
+ }]
90
+ }, null, 2);
91
+ }
92
+ function formatCiResult(result, format) {
93
+ if (format === "json") return JSON.stringify(result, null, 2);
94
+ if (format === "sarif") return formatCiSarif(result);
95
+ return result.markdown;
96
+ }
97
+
98
+ export {
99
+ DEFAULT_CI_THRESHOLDS,
100
+ normalizeCiThresholds,
101
+ detectCiGateSignals,
102
+ evaluateCiGate,
103
+ formatCiSarif,
104
+ formatCiResult
105
+ };
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  TEST_TIMEOUT
4
- } from "./chunk-7JES2NWR.js";
4
+ } from "./chunk-7YWS3JLU.js";
5
5
 
6
6
  // src/tools/builtin/run-tests.ts
7
7
  import { execSync, spawnSync } from "child_process";
@@ -8,7 +8,7 @@ import {
8
8
  CONFIG_FILE_NAME,
9
9
  HISTORY_DIR_NAME,
10
10
  PLUGINS_DIR_NAME
11
- } from "./chunk-7JES2NWR.js";
11
+ } from "./chunk-7YWS3JLU.js";
12
12
  import {
13
13
  atomicWriteFileSync
14
14
  } from "./chunk-IW3Q7AE5.js";
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ CI_COMMENT_MARKER,
4
+ countSeverity,
5
+ runCi
6
+ } from "./chunk-GY3C3C2Y.js";
7
+ import "./chunk-TJ6GGN4B.js";
8
+ import "./chunk-HLWUDRBO.js";
9
+ import "./chunk-QMXC327F.js";
10
+ import "./chunk-XPBEJB27.js";
11
+ import "./chunk-WAI3WPV2.js";
12
+ import "./chunk-TZQHYZKT.js";
13
+ import "./chunk-7YWS3JLU.js";
14
+ import "./chunk-IW3Q7AE5.js";
15
+ export {
16
+ CI_COMMENT_MARKER,
17
+ countSeverity,
18
+ runCi
19
+ };
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ DEFAULT_CI_THRESHOLDS,
4
+ detectCiGateSignals,
5
+ evaluateCiGate,
6
+ formatCiResult,
7
+ formatCiSarif,
8
+ normalizeCiThresholds
9
+ } from "./chunk-TJ6GGN4B.js";
10
+ import "./chunk-7YWS3JLU.js";
11
+ export {
12
+ DEFAULT_CI_THRESHOLDS,
13
+ detectCiGateSignals,
14
+ evaluateCiGate,
15
+ formatCiResult,
16
+ formatCiSarif,
17
+ normalizeCiThresholds
18
+ };
@@ -37,7 +37,7 @@ import {
37
37
  TEST_TIMEOUT,
38
38
  VERSION,
39
39
  buildUserIdentityPrompt
40
- } from "./chunk-7JES2NWR.js";
40
+ } from "./chunk-7YWS3JLU.js";
41
41
  export {
42
42
  AGENTIC_BEHAVIOR_GUIDELINE,
43
43
  APP_NAME,
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  getConfigDirUsage,
4
4
  listRecentCrashes
5
- } from "./chunk-ZOF5NFKW.js";
5
+ } from "./chunk-6HLKCSB3.js";
6
6
  import {
7
7
  ProviderRegistry
8
8
  } from "./chunk-QMXC327F.js";
@@ -11,17 +11,17 @@ import {
11
11
  getTopFailingTools,
12
12
  getTopUsedTools,
13
13
  resetStats
14
- } from "./chunk-X6OXS7KU.js";
14
+ } from "./chunk-77HDCGTN.js";
15
15
  import "./chunk-XPBEJB27.js";
16
16
  import {
17
17
  ConfigManager
18
- } from "./chunk-2224JGA6.js";
18
+ } from "./chunk-WAI3WPV2.js";
19
19
  import "./chunk-TZQHYZKT.js";
20
20
  import {
21
21
  DEV_STATE_FILE_NAME,
22
22
  MEMORY_FILE_NAME,
23
23
  VERSION
24
- } from "./chunk-7JES2NWR.js";
24
+ } from "./chunk-7YWS3JLU.js";
25
25
  import "./chunk-IW3Q7AE5.js";
26
26
 
27
27
  // src/diagnostics/doctor-cli.ts
@@ -37,7 +37,7 @@ import {
37
37
  VERSION,
38
38
  buildUserIdentityPrompt,
39
39
  runTestsTool
40
- } from "./chunk-RS4WBI73.js";
40
+ } from "./chunk-OLN7VUZA.js";
41
41
  import {
42
42
  hasSemanticIndex,
43
43
  semanticSearch
@@ -16070,7 +16070,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
16070
16070
  case "test": {
16071
16071
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
16072
16072
  try {
16073
- const { executeTests } = await import("./run-tests-J2JQ57R3.js");
16073
+ const { executeTests } = await import("./run-tests-4PKSIVK5.js");
16074
16074
  const argStr = args.join(" ").trim();
16075
16075
  let testArgs = {};
16076
16076
  if (argStr) {
@@ -154,7 +154,7 @@ ${content}`);
154
154
  }
155
155
  }
156
156
  async function runTaskMode(config, providers, configManager, topic) {
157
- const { TaskOrchestrator } = await import("./task-orchestrator-QFOCO3N7.js");
157
+ const { TaskOrchestrator } = await import("./task-orchestrator-TUMDTOAX.js");
158
158
  const orchestrator = new TaskOrchestrator(config, providers, configManager);
159
159
  let interrupted = false;
160
160
  const onSigint = () => {
package/dist/index.js CHANGED
@@ -15,7 +15,7 @@ import {
15
15
  scanProject,
16
16
  sessionHasMeaningfulContent,
17
17
  setupProxy
18
- } from "./chunk-KL7UBVSQ.js";
18
+ } from "./chunk-PNTLA3MN.js";
19
19
  import {
20
20
  buildReviewPrompt,
21
21
  buildSecurityReviewPrompt
@@ -47,10 +47,10 @@ import {
47
47
  trustHook,
48
48
  undoStack,
49
49
  untrustHook
50
- } from "./chunk-6C3JYNM6.js";
50
+ } from "./chunk-TCOC4AUI.js";
51
51
  import "./chunk-T2NL5ZIA.js";
52
52
  import "./chunk-BXP6YZ2P.js";
53
- import "./chunk-3TSHNZKI.js";
53
+ import "./chunk-VW7Y27WW.js";
54
54
  import {
55
55
  SessionManager,
56
56
  getContentText
@@ -67,7 +67,7 @@ import {
67
67
  getConfigDirUsage,
68
68
  listRecentCrashes,
69
69
  writeCrashLog
70
- } from "./chunk-ZOF5NFKW.js";
70
+ } from "./chunk-6HLKCSB3.js";
71
71
  import {
72
72
  ProviderRegistry
73
73
  } from "./chunk-QMXC327F.js";
@@ -76,7 +76,7 @@ import {
76
76
  getTopFailingTools,
77
77
  getTopUsedTools,
78
78
  installFlushOnExit
79
- } from "./chunk-X6OXS7KU.js";
79
+ } from "./chunk-77HDCGTN.js";
80
80
  import {
81
81
  CONTENT_ONLY_STREAM_REMINDER,
82
82
  TEE_FINAL_USER_NUDGE,
@@ -98,7 +98,7 @@ import {
98
98
  } from "./chunk-XPBEJB27.js";
99
99
  import {
100
100
  ConfigManager
101
- } from "./chunk-2224JGA6.js";
101
+ } from "./chunk-WAI3WPV2.js";
102
102
  import {
103
103
  AuthError,
104
104
  ProviderError,
@@ -124,7 +124,7 @@ import {
124
124
  SKILLS_DIR_NAME,
125
125
  VERSION,
126
126
  buildUserIdentityPrompt
127
- } from "./chunk-7JES2NWR.js";
127
+ } from "./chunk-7YWS3JLU.js";
128
128
  import {
129
129
  formatGitContextForPrompt,
130
130
  getGitContext,
@@ -1661,7 +1661,7 @@ No tools match "${filter}".
1661
1661
  const { join: join5 } = await import("path");
1662
1662
  const { existsSync: existsSync5 } = await import("fs");
1663
1663
  const { getGitRoot: getGitRoot2 } = await import("./git-context-EXOEHQSF.js");
1664
- const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-IN2HXJK7.js");
1664
+ const { MCP_PROJECT_CONFIG_NAME: MCP_PROJECT_CONFIG_NAME2 } = await import("./constants-APSORFOH.js");
1665
1665
  const { approveProject, hashMcpFile } = await import("./project-trust-NKYHL3VZ.js");
1666
1666
  const cwd = process.cwd();
1667
1667
  const projectRoot = getGitRoot2(cwd) ?? cwd;
@@ -2720,7 +2720,7 @@ ${hint}` : "")
2720
2720
  usage: "/test [command|filter]",
2721
2721
  async execute(args, ctx) {
2722
2722
  try {
2723
- const { executeTests } = await import("./run-tests-QJD43AML.js");
2723
+ const { executeTests } = await import("./run-tests-ZRK4TQUN.js");
2724
2724
  const argStr = args.join(" ").trim();
2725
2725
  let testArgs = {};
2726
2726
  if (argStr) {
@@ -7207,7 +7207,7 @@ program.command("web").description("Start Web UI server with browser-based chat
7207
7207
  console.error("Error: Invalid port number. Must be between 1 and 65535.");
7208
7208
  process.exit(1);
7209
7209
  }
7210
- const { startWebServer } = await import("./server-VGPZOISQ.js");
7210
+ const { startWebServer } = await import("./server-FXUF5P64.js");
7211
7211
  await startWebServer({ port, host: options.host });
7212
7212
  });
7213
7213
  program.command("user [action] [username]").description("Manage Web UI users (list | create <name> | delete <name> | reset-password <name> | logout-all <name> | migrate <name>)").action(async (action, username) => {
@@ -7374,16 +7374,16 @@ program.command("sessions").description("List recent conversation sessions").opt
7374
7374
  console.log(footer + "\n");
7375
7375
  });
7376
7376
  program.command("usage").description("Show token + cost usage grouped by provider/model (cross-session)").option("--days <n>", "Only the last N days (inclusive of today)").option("--month <ym>", "Only a specific month, format YYYY-MM (e.g. 2026-06)").option("--json", "Output as JSON (for scripting)").action(async (options) => {
7377
- const { runUsageCli } = await import("./usage-IEB476NE.js");
7377
+ const { runUsageCli } = await import("./usage-X7MJX4YD.js");
7378
7378
  await runUsageCli(options);
7379
7379
  });
7380
7380
  program.command("doctor").description("Health check: API keys, config, MCP, recent crashes, tool usage, disk usage").option("--json", "Output as JSON (for scripting)").option("--reset-stats", "Reset accumulated tool usage statistics").action(async (options) => {
7381
- const { runDoctorCli } = await import("./doctor-cli-7XL4TCVT.js");
7381
+ const { runDoctorCli } = await import("./doctor-cli-GUIX4X5F.js");
7382
7382
  await runDoctorCli({ json: !!options.json, resetStats: !!options.resetStats });
7383
7383
  });
7384
7384
  program.command("batch <action> [arg] [arg2]").description("Anthropic Message Batches: submit | list | status <id> | results <id> [out] | cancel <id>").option("--dry-run", "Parse and validate input without submitting (submit only)").action(async (action, arg, arg2, options) => {
7385
7385
  try {
7386
- const batch = await import("./batch-74H5SA7P.js");
7386
+ const batch = await import("./batch-C6HCXZIG.js");
7387
7387
  switch (action) {
7388
7388
  case "submit":
7389
7389
  if (!arg) {
@@ -7426,7 +7426,7 @@ program.command("batch <action> [arg] [arg2]").description("Anthropic Message Ba
7426
7426
  }
7427
7427
  });
7428
7428
  program.command("mcp-serve").description("Start an MCP server over STDIO, exposing aicli's built-in tools to Claude Desktop / Cursor / other MCP clients").option("--allow-destructive", "Allow bash / run_interactive / task_create (always destructive in MCP mode)").option("--allow-outside-cwd", "Allow tool path arguments to escape the sandbox root \u2014 disabled by default").option("--tools <list>", "Comma-separated whitelist of tools to expose (default: all eligible tools)").option("--cwd <path>", "Working directory AND sandbox root (default: current directory)").action(async (options) => {
7429
- const { startMcpServer } = await import("./server-WDLV3W4F.js");
7429
+ const { startMcpServer } = await import("./server-O3XHT56X.js");
7430
7430
  await startMcpServer({
7431
7431
  allowDestructive: !!options.allowDestructive,
7432
7432
  allowOutsideCwd: !!options.allowOutsideCwd,
@@ -7434,8 +7434,39 @@ program.command("mcp-serve").description("Start an MCP server over STDIO, exposi
7434
7434
  cwd: options.cwd
7435
7435
  });
7436
7436
  });
7437
- program.command("ci").description("Headless PR review (code + security) \u2014 reads git/gh diff, optionally posts to PR. Designed for GitHub Actions.").option("--pr <num>", "PR number; diff fetched via `gh pr diff <num>`", (v) => parseInt(v, 10)).option("--base <ref>", "Base ref for `git diff <ref>...HEAD` (ignored when --pr set)").option("--post", "Post review as a PR comment (requires gh CLI + GH_TOKEN, needs --pr)").option("--no-update", "Always create a new comment instead of updating the previous aicli review").option("--skip-code", "Skip the code review section").option("--skip-security", "Skip the security review section").option("--detailed", "Use the detailed code-review prompt").option("--max-diff <n>", "Max diff chars sent to the model (default 30000)", (v) => parseInt(v, 10)).option("--provider <id>", "Override provider (default: config.defaultProvider)").option("--model <id>", "Override model").option("--dry-run", "Print result to stdout instead of posting (overrides --post)").action(async (options) => {
7438
- const { runCi } = await import("./ci-3ALK2XJN.js");
7437
+ program.command("pr <action> [target]").description("PR review helpers: review | security-review | summarize. Defaults to git diff main...HEAD.").option("--base <ref>", "Base ref for git diff <ref>...HEAD (ignored when --pr set)").option("--head <ref>", "Head ref for local git diff (default: HEAD)").option("--post", "Post/update a PR comment; only when target is a GitHub PR number or URL").option("--no-update", "Always create a new PR comment instead of updating the previous aicli review").option("--detailed", "Use the detailed code-review prompt").option("--agents <list>", "Comma-separated review lenses: security,bugs,tests,maintainability").option("--max-diff <n>", "Max diff chars sent to the model (default 30000)", (v) => parseInt(v, 10)).option("--provider <id>", "Override provider (default: config.defaultProvider)").option("--model <id>", "Override model").option("--dry-run", "Print result to stdout instead of posting (overrides --post)").action(async (action, target, options) => {
7438
+ if (!["review", "security-review", "summarize"].includes(action)) {
7439
+ console.error(`Unknown pr action: ${action}. Use review | security-review | summarize.`);
7440
+ process.exit(1);
7441
+ }
7442
+ const { runPrCli } = await import("./pr-JOL3IAGV.js");
7443
+ const result = await runPrCli({
7444
+ action,
7445
+ target,
7446
+ base: options.base,
7447
+ head: options.head,
7448
+ post: !!options.post,
7449
+ update: options.update !== false,
7450
+ detailed: !!options.detailed,
7451
+ agents: options.agents,
7452
+ maxDiffChars: options.maxDiff,
7453
+ provider: options.provider,
7454
+ model: options.model,
7455
+ dryRun: !!options.dryRun
7456
+ });
7457
+ process.stdout.write(result.markdown + "\n");
7458
+ if (result.posted) process.stderr.write("\n\u2705 Posted PR review comment\n");
7459
+ process.exit(result.exitCode);
7460
+ });
7461
+ program.command("ci").description("Headless PR review (code + security) \u2014 reads git/gh diff, optionally posts to PR. Designed for GitHub Actions.").option("--pr <num>", "PR number or URL; diff fetched via gh pr diff <value>", (v) => /^#?\d+$/.test(v) ? parseInt(v.replace(/^#/, ""), 10) : v).option("--base <ref>", "Base ref for git diff <ref>...HEAD (ignored when --pr set)").option("--post", "Post review as a PR comment (requires gh CLI + GH_TOKEN, needs --pr)").option("--no-update", "Always create a new comment instead of updating the previous aicli review").option("--skip-code", "Skip the code review section").option("--skip-security", "Skip the security review section").option("--detailed", "Use the detailed code-review prompt").option("--max-diff <n>", "Max diff chars sent to the model (default 30000)", (v) => parseInt(v, 10)).option("--provider <id>", "Override provider (default: config.defaultProvider)").option("--model <id>", "Override model").option("--dry-run", "Print result to stdout instead of posting (overrides --post)").option("--json", "Output a structured JSON report").option("--markdown", "Output Markdown report (default)").option("--sarif", "Output SARIF 2.1.0 for code scanning artifacts").option("--fail-on-security-high", "Fail when the security review contains HIGH/CRITICAL findings (default)").option("--no-fail-on-security-high", "Do not fail on security HIGH/CRITICAL findings").option("--fail-on-test-failure", "Fail when the report mentions test failures (default)").option("--no-fail-on-test-failure", "Do not fail on test failure findings").option("--fail-on-lint-failure", "Fail when the report mentions lint/typecheck failures (default)").option("--no-fail-on-lint-failure", "Do not fail on lint/typecheck failure findings").action(async (options) => {
7462
+ const { runCi } = await import("./ci-6ZTFO3LX.js");
7463
+ const { formatCiResult } = await import("./ci-format-CLZ6QJRL.js");
7464
+ const formats = [options.json, options.markdown, options.sarif].filter(Boolean).length;
7465
+ if (formats > 1) {
7466
+ console.error("Choose only one output format: --json, --markdown, or --sarif.");
7467
+ process.exit(2);
7468
+ }
7469
+ const outputFormat = options.json ? "json" : options.sarif ? "sarif" : "markdown";
7439
7470
  const result = await runCi({
7440
7471
  pr: options.pr,
7441
7472
  base: options.base,
@@ -7447,13 +7478,16 @@ program.command("ci").description("Headless PR review (code + security) \u2014 r
7447
7478
  maxDiffChars: options.maxDiff,
7448
7479
  provider: options.provider,
7449
7480
  model: options.model,
7450
- dryRun: !!options.dryRun
7481
+ dryRun: !!options.dryRun,
7482
+ thresholds: {
7483
+ securityHigh: options.failOnSecurityHigh !== false,
7484
+ testFailure: options.failOnTestFailure !== false,
7485
+ lintFailure: options.failOnLintFailure !== false
7486
+ }
7451
7487
  });
7452
- process.stdout.write(result.markdown + "\n");
7488
+ process.stdout.write(formatCiResult({ version: VERSION, ...result }, outputFormat) + "\n");
7453
7489
  if (result.posted) {
7454
- process.stderr.write(`
7455
- \u2705 Posted review to PR #${options.pr}${result.updatedCommentId ? " (updated existing comment)" : ""}
7456
- `);
7490
+ process.stderr.write("\n\u2705 Posted review to PR #" + options.pr + (result.updatedCommentId ? " (updated existing comment)" : "") + "\n");
7457
7491
  }
7458
7492
  process.exit(result.exitCode);
7459
7493
  });
@@ -7484,6 +7518,8 @@ program.command("help").description("Show a comprehensive guide to all aicli fea
7484
7518
  ` ${G}aicli usage${R} Token + cost usage by provider/model (--days/--month/--json)`,
7485
7519
  ` ${G}aicli user <action>${R} User management (list/create/delete)`,
7486
7520
  ` ${G}aicli batch <action>${R} Anthropic Batches API (50% off, 24h): submit/list/status/results/cancel`,
7521
+ ` ${G}aicli pr <action>${R} Local PR review/summarize commands`,
7522
+ ` ${G}aicli ci [options]${R} Headless PR review for GitHub Actions`,
7487
7523
  ` ${G}aicli mcp-serve${R} MCP server over STDIO (expose tools to Claude Desktop / Cursor)`,
7488
7524
  "",
7489
7525
  `${B}${C} \u25A0 STARTUP OPTIONS${R}`,
@@ -7581,7 +7617,7 @@ program.command("hub [topic]").description("Start multi-agent hub (discuss / bra
7581
7617
  }),
7582
7618
  config.get("customProviders")
7583
7619
  );
7584
- const { startHub } = await import("./hub-VAOG5EY6.js");
7620
+ const { startHub } = await import("./hub-ZGHQWNWE.js");
7585
7621
  await startHub(
7586
7622
  {
7587
7623
  topic: topic ?? "",
@@ -0,0 +1,267 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ countSeverity,
4
+ runCi
5
+ } from "./chunk-GY3C3C2Y.js";
6
+ import "./chunk-TJ6GGN4B.js";
7
+ import {
8
+ buildReviewPrompt,
9
+ buildSecurityReviewPrompt,
10
+ truncateDiff
11
+ } from "./chunk-HLWUDRBO.js";
12
+ import {
13
+ ProviderRegistry
14
+ } from "./chunk-QMXC327F.js";
15
+ import "./chunk-XPBEJB27.js";
16
+ import {
17
+ ConfigManager
18
+ } from "./chunk-WAI3WPV2.js";
19
+ import "./chunk-TZQHYZKT.js";
20
+ import {
21
+ VERSION
22
+ } from "./chunk-7YWS3JLU.js";
23
+ import "./chunk-IW3Q7AE5.js";
24
+
25
+ // src/cli/pr.ts
26
+ import { execFileSync } from "child_process";
27
+ var ALLOWED_AGENTS = /* @__PURE__ */ new Set(["security", "bugs", "tests", "maintainability"]);
28
+ function parsePrTarget(input) {
29
+ if (!input) return {};
30
+ const s = input.trim();
31
+ if (/^https:\/\/github\.com\/[^/]+\/[^/]+\/pull\/\d+(?:[/?#].*)?$/.test(s)) return { pr: s };
32
+ if (/^#?\d+$/.test(s)) return { pr: parseInt(s.replace(/^#/, ""), 10) };
33
+ const range = s.match(/^([^\s.][^\s]*?)\.\.\.([^\s]+)$/);
34
+ if (range) return { base: range[1], head: range[2] };
35
+ return { base: s, head: "HEAD" };
36
+ }
37
+ function parseAgentList(input) {
38
+ if (!input) return [];
39
+ const agents = input.split(",").map((s) => s.trim()).filter(Boolean);
40
+ const invalid = agents.filter((a) => !ALLOWED_AGENTS.has(a));
41
+ if (invalid.length) throw new Error(`unknown PR review agent(s): ${invalid.join(", ")}. Allowed: ${Array.from(ALLOWED_AGENTS).join(", ")}`);
42
+ return [...new Set(agents)];
43
+ }
44
+ function validateGitRef(ref) {
45
+ if (!/^[A-Za-z0-9._/\-]+$/.test(ref)) throw new Error(`unsafe ref: ${ref}`);
46
+ }
47
+ function hasGitHubToken() {
48
+ return !!(process.env.GH_TOKEN || process.env.GITHUB_TOKEN);
49
+ }
50
+ function fetchPrMetadata(pr) {
51
+ if (!hasGitHubToken()) return null;
52
+ try {
53
+ const out = execFileSync("gh", ["pr", "view", String(pr), "--json", "number,url,title,author,state,baseRefName,headRefName"], {
54
+ encoding: "utf-8",
55
+ maxBuffer: 5 * 1024 * 1024,
56
+ timeout: 3e4
57
+ });
58
+ const parsed = JSON.parse(out);
59
+ return {
60
+ number: parsed.number,
61
+ url: parsed.url,
62
+ title: parsed.title,
63
+ state: parsed.state,
64
+ baseRefName: parsed.baseRefName,
65
+ headRefName: parsed.headRefName,
66
+ author: parsed.author?.login
67
+ };
68
+ } catch {
69
+ return null;
70
+ }
71
+ }
72
+ function fetchDiffSource(opts) {
73
+ const target = parsePrTarget(opts.target);
74
+ if (opts.diffOverride != null) {
75
+ const isPr = target.pr != null;
76
+ return {
77
+ kind: isPr ? "github-pr" : "git",
78
+ label: isPr ? `PR ${String(target.pr)}` : opts.base ? `${opts.base}...${opts.head || "HEAD"}` : "injected diff",
79
+ diff: opts.diffOverride,
80
+ metadata: opts.metadataOverride ?? null
81
+ };
82
+ }
83
+ const pr = target.pr;
84
+ const base = opts.base ?? target.base ?? "main";
85
+ const head = opts.head ?? target.head ?? "HEAD";
86
+ if (pr != null) {
87
+ try {
88
+ const diff = execFileSync("gh", ["pr", "diff", String(pr)], {
89
+ encoding: "utf-8",
90
+ maxBuffer: 50 * 1024 * 1024,
91
+ timeout: 6e4
92
+ });
93
+ return { kind: "github-pr", label: `PR ${String(pr)}`, diff, metadata: fetchPrMetadata(pr) };
94
+ } catch (err) {
95
+ const msg = err.stderr?.toString() ?? err.message;
96
+ throw new Error(`gh pr diff ${String(pr)} failed: ${msg.trim() || "unknown error"}. For local review without GitHub auth, use --base/--head instead.`);
97
+ }
98
+ }
99
+ validateGitRef(base);
100
+ validateGitRef(head);
101
+ try {
102
+ const diff = execFileSync("git", ["diff", `${base}...${head}`], {
103
+ encoding: "utf-8",
104
+ maxBuffer: 50 * 1024 * 1024,
105
+ timeout: 3e4
106
+ });
107
+ return { kind: "git", label: `${base}...${head}`, diff, metadata: null };
108
+ } catch (err) {
109
+ throw new Error(`git diff ${base}...${head} failed: ${err.message}`);
110
+ }
111
+ }
112
+ function metadataMarkdown(meta) {
113
+ if (!meta) return "";
114
+ const parts = [
115
+ meta.number != null ? `#${meta.number}` : void 0,
116
+ meta.title,
117
+ meta.state,
118
+ meta.author ? `by @${meta.author}` : void 0,
119
+ meta.baseRefName && meta.headRefName ? `${meta.baseRefName} \u2190 ${meta.headRefName}` : void 0,
120
+ meta.url
121
+ ].filter(Boolean);
122
+ return parts.length ? `
123
+ PR metadata: ${parts.join(" \xB7 ")}` : "";
124
+ }
125
+ function buildGitContext(src) {
126
+ const parts = [`Source: ${src.label}`, `Reviewer: aicli v${VERSION}`];
127
+ if (src.metadata?.number != null) parts.push(`PR: #${src.metadata.number}`);
128
+ if (src.metadata?.title) parts.push(`Title: ${src.metadata.title}`);
129
+ if (src.metadata?.baseRefName && src.metadata?.headRefName) parts.push(`Base/Head: ${src.metadata.baseRefName}...${src.metadata.headRefName}`);
130
+ return parts.join(" | ");
131
+ }
132
+ function buildSummaryPrompt(diff, gitContext) {
133
+ return `# Pull Request Summary Request
134
+
135
+ Summarize this pull request for maintainers. Focus on what changed, why it matters, risk areas, tests implied by the diff, and release-note worthy behavior.
136
+
137
+ ## Git Context
138
+ ${gitContext}
139
+
140
+ ## Code Changes (diff)
141
+ \`\`\`diff
142
+ ${diff}
143
+ \`\`\`
144
+
145
+ ## Output Format
146
+ 1. **Summary**: 2-4 bullets
147
+ 2. **Key Files**: important files and why they changed
148
+ 3. **Risk / Review Focus**: areas reviewers should inspect
149
+ 4. **Testing Notes**: tests present or missing
150
+ 5. **Suggested PR Description**: concise markdown description`;
151
+ }
152
+ function buildAgentPrompt(agent, diff, gitContext) {
153
+ const focus = {
154
+ security: "security vulnerabilities, secret handling, authz/authn, injection, unsafe network/file behavior",
155
+ bugs: "logic bugs, edge cases, race conditions, incorrect assumptions, regressions",
156
+ tests: "missing tests, weak assertions, CI breakage risk, testability gaps",
157
+ maintainability: "complexity, naming, duplication, API shape, future maintenance risk"
158
+ };
159
+ return `# PR Review Agent: ${agent}
160
+
161
+ Review the diff only from this lens: ${focus[agent]}. Be concise and cite file paths/lines when possible.
162
+
163
+ ## Git Context
164
+ ${gitContext}
165
+
166
+ ## Diff
167
+ \`\`\`diff
168
+ ${diff}
169
+ \`\`\`
170
+
171
+ ## Output Format
172
+ - Findings first, ordered by severity
173
+ - If no material findings, say so clearly
174
+ - Keep recommendations actionable`;
175
+ }
176
+ async function getProvider(opts) {
177
+ const config = new ConfigManager();
178
+ const registry = new ProviderRegistry();
179
+ await registry.initialize(
180
+ (id) => config.getApiKey(id),
181
+ (id) => ({ baseUrl: config.get("customBaseUrls")[id], timeout: config.get("timeouts")[id], proxy: config.get("proxy") }),
182
+ config.get("customProviders")
183
+ );
184
+ const providerId = opts.provider ?? config.getDefaultProvider();
185
+ if (!registry.has(providerId)) throw new Error(`Provider '${providerId}' is not configured. Set the right AICLI_API_KEY_* env var or pass --provider.`);
186
+ const info = registry.get(providerId).info;
187
+ return { registry, providerId, modelId: opts.model ?? config.get("defaultModels")[providerId] ?? info.defaultModel };
188
+ }
189
+ async function runPrompt(registry, providerId, modelId, prompt) {
190
+ const provider = registry.get(providerId);
191
+ const resp = await provider.chat({
192
+ messages: [{ role: "user", content: prompt, timestamp: /* @__PURE__ */ new Date() }],
193
+ model: modelId,
194
+ stream: false,
195
+ temperature: 0.3,
196
+ maxTokens: 8192
197
+ });
198
+ return resp.content?.trim() ?? "";
199
+ }
200
+ async function runPrCli(opts) {
201
+ let source;
202
+ try {
203
+ source = fetchDiffSource(opts);
204
+ } catch (err) {
205
+ return { exitCode: 2, markdown: `\u274C ${err.message}`, posted: false, severity: { critical: 0, high: 0, warning: 0, info: 0 } };
206
+ }
207
+ const diff = source.diff.trim();
208
+ if (!diff) return { exitCode: 0, markdown: `\u2705 No changes to review (${source.label}).`, posted: false, severity: { critical: 0, high: 0, warning: 0, info: 0 } };
209
+ const parsedTarget = parsePrTarget(opts.target);
210
+ const requestedAgents = parseAgentList(opts.agents);
211
+ if (opts.post && source.kind !== "github-pr") {
212
+ return { exitCode: 2, markdown: "\u274C --post requires a GitHub PR number or URL target.", posted: false, severity: { critical: 0, high: 0, warning: 0, info: 0 } };
213
+ }
214
+ if (opts.post && opts.action === "summarize") {
215
+ return { exitCode: 2, markdown: "\u274C --post is currently supported for pr review and pr security-review only.", posted: false, severity: { critical: 0, high: 0, warning: 0, info: 0 } };
216
+ }
217
+ if (opts.post && requestedAgents.length > 0) {
218
+ return { exitCode: 2, markdown: "\u274C --post is currently not supported together with --agents; run without --agents or post the Markdown manually.", posted: false, severity: { critical: 0, high: 0, warning: 0, info: 0 } };
219
+ }
220
+ if ((opts.action === "review" || opts.action === "security-review") && requestedAgents.length === 0) {
221
+ const ci = await runCi({
222
+ pr: source.kind === "github-pr" ? parsedTarget.pr : void 0,
223
+ diffOverride: diff,
224
+ base: source.kind === "git" ? source.label.split("...")[0] : void 0,
225
+ post: !!opts.post,
226
+ update: opts.update !== false,
227
+ skipCode: opts.action === "security-review",
228
+ skipSecurity: opts.action === "review",
229
+ detailed: !!opts.detailed,
230
+ maxDiffChars: opts.maxDiffChars,
231
+ provider: opts.provider,
232
+ model: opts.model,
233
+ dryRun: !!opts.dryRun
234
+ });
235
+ return { exitCode: ci.exitCode, markdown: ci.markdown + metadataMarkdown(source.metadata), posted: ci.posted, severity: ci.severity };
236
+ }
237
+ const maxDiff = opts.maxDiffChars ?? 3e4;
238
+ const { diff: trimmedDiff, truncated } = truncateDiff(diff, maxDiff);
239
+ const gitContext = buildGitContext(source);
240
+ let providerBundle;
241
+ try {
242
+ providerBundle = await getProvider(opts);
243
+ } catch (err) {
244
+ return { exitCode: 2, markdown: `\u274C ${err.message}`, posted: false, severity: { critical: 0, high: 0, warning: 0, info: 0 } };
245
+ }
246
+ const sections = [`## \u{1F916} aicli pr ${opts.action}`, `*Provider: \`${providerBundle.providerId}\` \xB7 Model: \`${providerBundle.modelId}\` \xB7 Source: \`${source.label}\` \xB7 aicli v${VERSION}*`, ""];
247
+ if (source.metadata) sections.push(`> ${metadataMarkdown(source.metadata).trim()}`, "");
248
+ try {
249
+ const prompt = opts.action === "security-review" ? buildSecurityReviewPrompt(trimmedDiff, gitContext) : opts.action === "summarize" ? buildSummaryPrompt(trimmedDiff, gitContext) : buildReviewPrompt(trimmedDiff, gitContext, !!opts.detailed);
250
+ sections.push(await runPrompt(providerBundle.registry, providerBundle.providerId, providerBundle.modelId, prompt), "");
251
+ if (requestedAgents.length) {
252
+ const outputs = await Promise.all(requestedAgents.map(async (agent) => ({ agent, output: await runPrompt(providerBundle.registry, providerBundle.providerId, providerBundle.modelId, buildAgentPrompt(agent, trimmedDiff, gitContext)) })));
253
+ sections.push("### Agent Lenses", "");
254
+ for (const item of outputs) sections.push(`#### ${item.agent}`, item.output, "");
255
+ }
256
+ } catch (err) {
257
+ return { exitCode: 2, markdown: `\u274C PR review call failed: ${err.message}`, posted: false, severity: { critical: 0, high: 0, warning: 0, info: 0 } };
258
+ }
259
+ if (truncated) sections.push(`> \u26A0 Diff was truncated to ${maxDiff} chars (original: ${diff.length}). Consider splitting large PRs.`);
260
+ const markdown = sections.join("\n");
261
+ return { exitCode: 0, markdown, posted: false, severity: countSeverity(markdown) };
262
+ }
263
+ export {
264
+ parseAgentList,
265
+ parsePrTarget,
266
+ runPrCli
267
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  executeTests,
3
3
  runTestsTool
4
- } from "./chunk-RS4WBI73.js";
4
+ } from "./chunk-OLN7VUZA.js";
5
5
  export {
6
6
  executeTests,
7
7
  runTestsTool
@@ -2,8 +2,8 @@
2
2
  import {
3
3
  executeTests,
4
4
  runTestsTool
5
- } from "./chunk-3TSHNZKI.js";
6
- import "./chunk-7JES2NWR.js";
5
+ } from "./chunk-VW7Y27WW.js";
6
+ import "./chunk-7YWS3JLU.js";
7
7
  export {
8
8
  executeTests,
9
9
  runTestsTool
@@ -24,7 +24,7 @@ import {
24
24
  scanDirTree,
25
25
  scanProject,
26
26
  setupProxy
27
- } from "./chunk-KL7UBVSQ.js";
27
+ } from "./chunk-PNTLA3MN.js";
28
28
  import {
29
29
  buildReviewPrompt,
30
30
  buildSecurityReviewPrompt
@@ -61,10 +61,10 @@ import {
61
61
  spawnAgentContext,
62
62
  truncateOutput,
63
63
  undoStack
64
- } from "./chunk-6C3JYNM6.js";
64
+ } from "./chunk-TCOC4AUI.js";
65
65
  import "./chunk-T2NL5ZIA.js";
66
66
  import "./chunk-BXP6YZ2P.js";
67
- import "./chunk-3TSHNZKI.js";
67
+ import "./chunk-VW7Y27WW.js";
68
68
  import {
69
69
  SessionManager,
70
70
  getContentText
@@ -78,7 +78,7 @@ import {
78
78
  } from "./chunk-QMXC327F.js";
79
79
  import {
80
80
  runTool
81
- } from "./chunk-X6OXS7KU.js";
81
+ } from "./chunk-77HDCGTN.js";
82
82
  import {
83
83
  CONTENT_ONLY_STREAM_REMINDER,
84
84
  TEE_FINAL_USER_NUDGE,
@@ -97,7 +97,7 @@ import {
97
97
  } from "./chunk-XPBEJB27.js";
98
98
  import {
99
99
  ConfigManager
100
- } from "./chunk-2224JGA6.js";
100
+ } from "./chunk-WAI3WPV2.js";
101
101
  import "./chunk-TZQHYZKT.js";
102
102
  import {
103
103
  AGENTIC_BEHAVIOR_GUIDELINE,
@@ -116,7 +116,7 @@ import {
116
116
  SKILLS_DIR_NAME,
117
117
  VERSION,
118
118
  buildUserIdentityPrompt
119
- } from "./chunk-7JES2NWR.js";
119
+ } from "./chunk-7YWS3JLU.js";
120
120
  import {
121
121
  formatGitContextForPrompt,
122
122
  getGitContext,
@@ -2755,7 +2755,7 @@ ${undoResults.map((r) => ` \u2022 ${r}`).join("\n")}` });
2755
2755
  case "test": {
2756
2756
  this.send({ type: "info", message: "\u{1F9EA} Running tests..." });
2757
2757
  try {
2758
- const { executeTests } = await import("./run-tests-QJD43AML.js");
2758
+ const { executeTests } = await import("./run-tests-ZRK4TQUN.js");
2759
2759
  const argStr = args.join(" ").trim();
2760
2760
  let testArgs = {};
2761
2761
  if (argStr) {
@@ -1,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  ToolRegistry
4
- } from "./chunk-6C3JYNM6.js";
4
+ } from "./chunk-TCOC4AUI.js";
5
5
  import "./chunk-T2NL5ZIA.js";
6
6
  import "./chunk-BXP6YZ2P.js";
7
- import "./chunk-3TSHNZKI.js";
7
+ import "./chunk-VW7Y27WW.js";
8
8
  import {
9
9
  runTool
10
- } from "./chunk-X6OXS7KU.js";
10
+ } from "./chunk-77HDCGTN.js";
11
11
  import {
12
12
  getDangerLevel,
13
13
  schemaToJsonSchema
@@ -15,7 +15,7 @@ import {
15
15
  import "./chunk-TZQHYZKT.js";
16
16
  import {
17
17
  VERSION
18
- } from "./chunk-7JES2NWR.js";
18
+ } from "./chunk-7YWS3JLU.js";
19
19
  import "./chunk-4BKXL7SM.js";
20
20
  import "./chunk-TB4W4Y4T.js";
21
21
  import "./chunk-KHYD3WXE.js";
@@ -3,13 +3,13 @@ import {
3
3
  ToolRegistry,
4
4
  googleSearchContext,
5
5
  truncateOutput
6
- } from "./chunk-6C3JYNM6.js";
6
+ } from "./chunk-TCOC4AUI.js";
7
7
  import "./chunk-T2NL5ZIA.js";
8
8
  import "./chunk-BXP6YZ2P.js";
9
- import "./chunk-3TSHNZKI.js";
9
+ import "./chunk-VW7Y27WW.js";
10
10
  import {
11
11
  runTool
12
- } from "./chunk-X6OXS7KU.js";
12
+ } from "./chunk-77HDCGTN.js";
13
13
  import {
14
14
  getDangerLevel,
15
15
  runLeanAgentLoop
@@ -17,7 +17,7 @@ import {
17
17
  import "./chunk-TZQHYZKT.js";
18
18
  import {
19
19
  SUBAGENT_ALLOWED_TOOLS
20
- } from "./chunk-7JES2NWR.js";
20
+ } from "./chunk-7YWS3JLU.js";
21
21
  import "./chunk-4BKXL7SM.js";
22
22
  import "./chunk-TB4W4Y4T.js";
23
23
  import "./chunk-KHYD3WXE.js";
@@ -8,9 +8,9 @@ import {
8
8
  } from "./chunk-E44DTERW.js";
9
9
  import {
10
10
  ConfigManager
11
- } from "./chunk-2224JGA6.js";
11
+ } from "./chunk-WAI3WPV2.js";
12
12
  import "./chunk-TZQHYZKT.js";
13
- import "./chunk-7JES2NWR.js";
13
+ import "./chunk-7YWS3JLU.js";
14
14
  import "./chunk-IW3Q7AE5.js";
15
15
 
16
16
  // src/cli/usage.ts
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jinzd-ai-cli",
3
- "version": "0.4.215",
3
+ "version": "0.4.216",
4
4
  "description": "Cross-platform REPL-style AI CLI with multi-provider support",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",