@octavio.bot/review 0.1.3 → 0.1.5

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 (3) hide show
  1. package/README.md +32 -0
  2. package/dist/index.mjs +401 -127
  3. package/package.json +6 -3
package/README.md CHANGED
@@ -17,6 +17,19 @@ No GitHub review comments are created or updated.
17
17
  bunx --bun @octavio.bot/review@latest review --owner acme --repo web --pr 123 --workdir .
18
18
  ```
19
19
 
20
+ Initialize Octavio in your repository:
21
+
22
+ ```bash
23
+ bunx --bun @octavio.bot/review@latest init --workdir .
24
+ ```
25
+
26
+ This scaffolds:
27
+
28
+ - `.octavio/review.config.json`
29
+ - `.github/workflows/review-check.yml`
30
+
31
+ The generated workflow defaults to `OPENCODE_MODEL=opencode/minimax-m2.5-free` and sets `OPENCODE_API_KEY` to an empty fallback (`''`). If you move to a non-free model, set repository secret `OPENCODE_API_KEY`.
32
+
20
33
  CLI binary name: `octavio-review`.
21
34
 
22
35
  ## OpenCode Detection and Install
@@ -63,6 +76,12 @@ bun install
63
76
  bun run review-bot --owner acme --repo web --pr 123 --instructions-profile balanced --workdir .
64
77
  ```
65
78
 
79
+ Build and packaging notes:
80
+
81
+ - `bun run review-cli:build` builds the CLI bundle and syncs prompt markdown into `apps/review-bot-cli/prompts/`
82
+ - `bun run sync` from repo root runs the CLI `sync` task through Turborepo and refreshes generated prompts
83
+ - Prompt source-of-truth lives in `packages/prompts/prompts/*.md`; the app-level `prompts/` directory is generated for publishing
84
+
66
85
  Optional flags:
67
86
 
68
87
  - `--report-output path/to/review.md`
@@ -72,6 +91,11 @@ Optional flags:
72
91
  - `--artifact-execution agent|host`
73
92
  - `--install-opencode`
74
93
 
94
+ Init flags:
95
+
96
+ - `--workdir path/to/repo`
97
+ - `--force` (overwrite existing scaffolded files)
98
+
75
99
  ## Instruction Profiles
76
100
 
77
101
  Instruction resolution order:
@@ -111,9 +135,17 @@ Default artifact schema writes these files into `artifacts/`:
111
135
  - Review workflow: `.github/workflows/review-check.yml`
112
136
  - Runs profile matrix (`balanced`, `styling`, `security`) with `max-parallel: 1`
113
137
  - Uses `bunx --bun @octavio.bot/review@latest`
138
+ - Defaults to `OPENCODE_MODEL=opencode/minimax-m2.5-free`
139
+ - Uses `OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY || '' }}`
114
140
  - Uploads `review.md`, `confidence.json`, and `result.json`
115
141
  - CI workflow: `.github/workflows/ci.yml`
116
142
  - Includes smoke test: `bunx --bun @octavio.bot/review@latest doctor`
117
143
  - Publish workflow: `.github/workflows/publish-review.yml`
118
144
  - Manual only (`workflow_dispatch`)
119
145
  - Publishes from `apps/review-bot-cli` using npm trusted publishing (OIDC)
146
+
147
+ ## Troubleshooting
148
+
149
+ - If runs fail after changing models, set repository secret `OPENCODE_API_KEY`.
150
+ - If OpenCode is unreachable, verify `OPENCODE_HOSTNAME` and `OPENCODE_PORT`.
151
+ - If GitHub API requests fail, verify `GITHUB_TOKEN` permissions.
package/dist/index.mjs CHANGED
@@ -1694,6 +1694,17 @@ class CodeReviewWorkflow {
1694
1694
  }
1695
1695
  }
1696
1696
 
1697
+ // ../../packages/prompts/src/index.ts
1698
+ var PROMPT_PROFILES = ["balanced", "styling", "security"];
1699
+ var PROMPT_PATHS = {
1700
+ balanced: "../prompts/code-review.md",
1701
+ security: "../prompts/security-review.md",
1702
+ styling: "../prompts/styling-review.md"
1703
+ };
1704
+ var DEFAULT_PROMPT_PROFILE = "balanced";
1705
+ var isPromptProfile = (value) => PROMPT_PROFILES.some((profile) => profile === value);
1706
+ var resolvePromptPath = (profile) => Bun.fileURLToPath(new URL(PROMPT_PATHS[profile], import.meta.url));
1707
+
1697
1708
  // ../../node_modules/.bun/zod@4.3.6/node_modules/zod/v4/classic/external.js
1698
1709
  var exports_external = {};
1699
1710
  __export(exports_external, {
@@ -15235,7 +15246,7 @@ var envSchema = exports_external.object({
15235
15246
  });
15236
15247
  var POLICY_RULE_REGEX = /^(any|new):(low|medium|high|critical)$/u;
15237
15248
  var artifactExecutionSchema = exports_external.enum(["host", "agent"]);
15238
- var instructionsPromptSchema = exports_external.enum(["balanced", "styling", "security"]);
15249
+ var instructionsPromptSchema = exports_external.enum(PROMPT_PROFILES);
15239
15250
  var artifactSchemaConfigSchema = exports_external.object({
15240
15251
  artifactDir: exports_external.string().min(1).optional(),
15241
15252
  confidenceFile: exports_external.string().min(1).optional(),
@@ -15658,40 +15669,379 @@ class OpenCodeReportRunner {
15658
15669
  }
15659
15670
  }
15660
15671
 
15661
- // ../../packages/prompts/src/index.ts
15662
- var PROMPT_PATHS = {
15663
- balanced: "../prompts/code-review.md",
15664
- security: "../prompts/security-review.md",
15665
- styling: "../prompts/styling-review.md"
15672
+ // src/init.ts
15673
+ var DEFAULT_REVIEW_CONFIG = {
15674
+ defaultProfile: "balanced",
15675
+ profiles: {
15676
+ balanced: {
15677
+ artifactExecution: "agent",
15678
+ artifactSchema: {
15679
+ artifactDir: "artifacts",
15680
+ confidenceFile: "confidence.json",
15681
+ maxAttempts: 2,
15682
+ reviewFile: "review.md"
15683
+ },
15684
+ instructionsPrompt: "balanced",
15685
+ policy: {
15686
+ failOn: ["new:high", "new:critical"]
15687
+ }
15688
+ },
15689
+ security: {
15690
+ artifactExecution: "agent",
15691
+ artifactSchema: {
15692
+ artifactDir: "artifacts",
15693
+ confidenceFile: "confidence.json",
15694
+ maxAttempts: 2,
15695
+ reviewFile: "review.md"
15696
+ },
15697
+ instructionsPrompt: "security",
15698
+ policy: {
15699
+ failOn: ["new:medium", "new:high", "new:critical"]
15700
+ }
15701
+ },
15702
+ styling: {
15703
+ artifactExecution: "agent",
15704
+ artifactSchema: {
15705
+ artifactDir: "artifacts",
15706
+ confidenceFile: "confidence.json",
15707
+ maxAttempts: 2,
15708
+ reviewFile: "review.md"
15709
+ },
15710
+ instructionsPrompt: "styling",
15711
+ policy: {
15712
+ failOn: ["new:high", "new:critical"]
15713
+ }
15714
+ }
15715
+ }
15666
15716
  };
15667
- var DEFAULT_PROMPT_PROFILE = "balanced";
15668
- var isPromptProfile = (value) => Object.hasOwn(PROMPT_PATHS, value);
15669
- var resolvePromptPath = (profile) => Bun.fileURLToPath(new URL(PROMPT_PATHS[profile], import.meta.url));
15717
+ var DEFAULT_REVIEW_WORKFLOW = `name: review-check
15670
15718
 
15671
- // src/index.ts
15672
- var REPORT_LOG_MAX_CHARS = 8000;
15719
+ on:
15720
+ pull_request:
15721
+ types: [opened, synchronize, reopened, ready_for_review]
15722
+
15723
+ concurrency:
15724
+ group: review-check-pr-\${{ github.event.pull_request.number }}
15725
+ cancel-in-progress: true
15726
+
15727
+ permissions:
15728
+ actions: read
15729
+ contents: read
15730
+ pull-requests: read
15731
+
15732
+ jobs:
15733
+ review:
15734
+ name: review (\${{ matrix.profile }})
15735
+ runs-on: ubuntu-latest
15736
+ timeout-minutes: 10
15737
+ strategy:
15738
+ fail-fast: false
15739
+ max-parallel: 1
15740
+ matrix:
15741
+ profile: [balanced, styling, security]
15742
+ steps:
15743
+ - name: Checkout
15744
+ uses: actions/checkout@v4
15745
+
15746
+ - name: Setup Bun
15747
+ uses: oven-sh/setup-bun@v2
15748
+ with:
15749
+ bun-version: latest
15750
+
15751
+ - name: Run review check
15752
+ timeout-minutes: 8
15753
+ env:
15754
+ GITHUB_TOKEN: \${{ github.token }}
15755
+ OPENCODE_HOSTNAME: \${{ vars.OPENCODE_HOSTNAME || '127.0.0.1' }}
15756
+ OCTAVIO_INSTRUCTIONS_PROFILE: \${{ matrix.profile }}
15757
+ OPENCODE_MODEL: opencode/minimax-m2.5-free
15758
+ OPENCODE_PORT: \${{ vars.OPENCODE_PORT || '4096' }}
15759
+ OPENCODE_API_KEY: \${{ secrets.OPENCODE_API_KEY || '' }}
15760
+ run: |
15761
+ mkdir -p artifacts
15762
+ PROFILE_ARGS=""
15763
+ if [ -n "$OCTAVIO_INSTRUCTIONS_PROFILE" ]; then
15764
+ PROFILE_ARGS="--instructions-profile $OCTAVIO_INSTRUCTIONS_PROFILE"
15765
+ fi
15766
+ bunx --bun @octavio.bot/review@latest review \\
15767
+ --owner "\${{ github.repository_owner }}" \\
15768
+ --repo "\${{ github.event.repository.name }}" \\
15769
+ --pr "\${{ github.event.pull_request.number }}" \\
15770
+ $PROFILE_ARGS \\
15771
+ --install-opencode \\
15772
+ --workdir . \\
15773
+ --report-output artifacts/review.md \\
15774
+ --findings-output artifacts/confidence.json \\
15775
+ --result-output artifacts/result.json
15776
+
15777
+ - name: Publish job summary
15778
+ if: always()
15779
+ env:
15780
+ OCTAVIO_INSTRUCTIONS_PROFILE: \${{ matrix.profile }}
15781
+ run: |
15782
+ if [ -f artifacts/result.json ]; then
15783
+ bun -e 'const result = await Bun.file("artifacts/result.json").json();
15784
+ const lines = [
15785
+ "## Octavio Review",
15786
+ "",
15787
+ "- Profile: " + (process.env.OCTAVIO_INSTRUCTIONS_PROFILE || "balanced"),
15788
+ "- Blocking findings: " + (result.hasBlockingFindings ? "yes" : "no"),
15789
+ "- Policy source: " + result.policy.source,
15790
+ "- Policy rules: " + (result.policy.failOnRules.join(", ") || "none"),
15791
+ "- Matched rules: " + (result.policy.matchedRules.join(", ") || "none"),
15792
+ "",
15793
+ "### Summary",
15794
+ result.summary,
15795
+ ];
15796
+ await Bun.write(Bun.stdout, lines.join("\\n") + "\\n");' >> "$GITHUB_STEP_SUMMARY"
15797
+ else
15798
+ printf '## Octavio Review
15799
+
15800
+ Run failed before report generation.
15801
+
15802
+ Troubleshooting:
15803
+ - Confirm workflow env uses OPENCODE_MODEL=opencode/minimax-m2.5-free.
15804
+ - If you switch to a non-free model, set repository secret OPENCODE_API_KEY.
15805
+ - Check run logs for OpenCode connection/auth/model errors.
15806
+ ' >> "$GITHUB_STEP_SUMMARY"
15807
+ fi
15808
+
15809
+ - name: Upload report artifact
15810
+ if: always()
15811
+ uses: actions/upload-artifact@v4
15812
+ with:
15813
+ name: octavio-review-report-pr-\${{ github.event.pull_request.number }}-\${{ matrix.profile }}
15814
+ path: artifacts/review.md
15815
+ if-no-files-found: ignore
15816
+
15817
+ - name: Upload confidence artifact
15818
+ if: always()
15819
+ uses: actions/upload-artifact@v4
15820
+ with:
15821
+ name: octavio-confidence-pr-\${{ github.event.pull_request.number }}-\${{ matrix.profile }}
15822
+ path: artifacts/confidence.json
15823
+ if-no-files-found: ignore
15824
+
15825
+ - name: Upload result artifact
15826
+ if: always()
15827
+ uses: actions/upload-artifact@v4
15828
+ with:
15829
+ name: octavio-result-pr-\${{ github.event.pull_request.number }}-\${{ matrix.profile }}
15830
+ path: artifacts/result.json
15831
+ if-no-files-found: ignore
15832
+ `;
15833
+ var scaffoldPlans = () => [
15834
+ {
15835
+ contents: `${JSON.stringify(DEFAULT_REVIEW_CONFIG, null, 2)}
15836
+ `,
15837
+ relativePath: ".octavio/review.config.json"
15838
+ },
15839
+ {
15840
+ contents: DEFAULT_REVIEW_WORKFLOW,
15841
+ relativePath: ".github/workflows/review-check.yml"
15842
+ }
15843
+ ];
15844
+ var scaffoldReviewSetup = async (workspaceDirectory, force) => {
15845
+ const results = [];
15846
+ for (const plan of scaffoldPlans()) {
15847
+ const absolutePath = resolvePathFromWorkspace(workspaceDirectory, plan.relativePath);
15848
+ const file2 = Bun.file(absolutePath);
15849
+ const exists = await file2.exists();
15850
+ if (exists && !force) {
15851
+ results.push({
15852
+ relativePath: plan.relativePath,
15853
+ status: "skipped"
15854
+ });
15855
+ continue;
15856
+ }
15857
+ await Bun.write(absolutePath, plan.contents);
15858
+ results.push({
15859
+ relativePath: plan.relativePath,
15860
+ status: exists ? "overwritten" : "created"
15861
+ });
15862
+ }
15863
+ return results;
15864
+ };
15865
+
15866
+ // src/opencode.ts
15673
15867
  var OPEN_CODE_INSTALL_COMMAND = "curl -fsSL https://opencode.ai/install | bash";
15674
15868
  var OPEN_CODE_INSTALL_COMMAND_NO_PATH = "curl -fsSL https://opencode.ai/install | bash -s -- --no-modify-path";
15869
+ var ensureBinaryDirectoryOnPath = (binaryPath) => {
15870
+ const directoryEnd = binaryPath.lastIndexOf("/");
15871
+ if (directoryEnd <= 0) {
15872
+ return;
15873
+ }
15874
+ const directory = binaryPath.slice(0, directoryEnd);
15875
+ const currentPath = process.env.PATH ?? "";
15876
+ const pathEntries = currentPath.split(":").filter((entry) => entry.length > 0);
15877
+ if (pathEntries.includes(directory)) {
15878
+ return;
15879
+ }
15880
+ process.env.PATH = currentPath.length > 0 ? `${directory}:${currentPath}` : directory;
15881
+ process.stdout.write(`Added ${directory} to PATH for this process.
15882
+ `);
15883
+ };
15884
+ var knownOpenCodePaths = () => {
15885
+ const home = process.env.HOME;
15886
+ return [
15887
+ Bun.which("opencode") ?? "",
15888
+ home ? `${home}/.opencode/bin/opencode` : "",
15889
+ home ? `${home}/.local/bin/opencode` : ""
15890
+ ].filter((value, index, values) => value.length > 0 && values.indexOf(value) === index);
15891
+ };
15892
+ var readProcessOutput = async (stream) => stream ? await new Response(stream).text() : "";
15893
+ var getOpenCodeVersion = async (binaryPath) => {
15894
+ const subprocess = Bun.spawn([binaryPath, "--version"], {
15895
+ stderr: "pipe",
15896
+ stdout: "pipe"
15897
+ });
15898
+ const [exitCode, stderr, stdout] = await Promise.all([
15899
+ subprocess.exited,
15900
+ readProcessOutput(subprocess.stderr),
15901
+ readProcessOutput(subprocess.stdout)
15902
+ ]);
15903
+ if (exitCode !== 0) {
15904
+ const details = stderr.trim();
15905
+ return details.length > 0 ? details : null;
15906
+ }
15907
+ const version2 = stdout.trim();
15908
+ return version2.length > 0 ? version2 : null;
15909
+ };
15910
+ var detectOpenCode = async () => {
15911
+ for (const candidatePath of knownOpenCodePaths()) {
15912
+ const file2 = Bun.file(candidatePath);
15913
+ if (!await file2.exists()) {
15914
+ continue;
15915
+ }
15916
+ const version2 = await getOpenCodeVersion(candidatePath);
15917
+ return {
15918
+ path: candidatePath,
15919
+ version: version2 ?? "unknown"
15920
+ };
15921
+ }
15922
+ return null;
15923
+ };
15924
+ var runOpenCodeInstall = async () => {
15925
+ process.stdout.write(`OpenCode was not found. Installing now...
15926
+ `);
15927
+ process.stdout.write(`Install command: ${OPEN_CODE_INSTALL_COMMAND_NO_PATH}
15928
+ `);
15929
+ const subprocess = Bun.spawn(["bash", "-lc", OPEN_CODE_INSTALL_COMMAND_NO_PATH], {
15930
+ stderr: "inherit",
15931
+ stdout: "inherit"
15932
+ });
15933
+ const exitCode = await subprocess.exited;
15934
+ if (exitCode !== 0) {
15935
+ throw new Error([
15936
+ "Failed to auto-install OpenCode.",
15937
+ `Run this manually and retry: ${OPEN_CODE_INSTALL_COMMAND}`
15938
+ ].join(`
15939
+ `));
15940
+ }
15941
+ };
15942
+ var ensureOpenCodeInstalled = async (forceInstall) => {
15943
+ const detectedBeforeInstall = await detectOpenCode();
15944
+ if (detectedBeforeInstall) {
15945
+ ensureBinaryDirectoryOnPath(detectedBeforeInstall.path);
15946
+ process.stdout.write(`OpenCode detected at ${detectedBeforeInstall.path} (${detectedBeforeInstall.version}).
15947
+ `);
15948
+ return detectedBeforeInstall;
15949
+ }
15950
+ const shouldAutoInstall = forceInstall || process.env.GITHUB_ACTIONS === "true";
15951
+ if (!shouldAutoInstall) {
15952
+ throw new Error([
15953
+ "OpenCode CLI is required but was not found.",
15954
+ `Install it with: ${OPEN_CODE_INSTALL_COMMAND}`,
15955
+ "Then rerun this command, or pass --install-opencode to auto-install."
15956
+ ].join(`
15957
+ `));
15958
+ }
15959
+ await runOpenCodeInstall();
15960
+ const detectedAfterInstall = await detectOpenCode();
15961
+ if (!detectedAfterInstall) {
15962
+ throw new Error([
15963
+ "OpenCode install completed but the binary was still not detected.",
15964
+ `Try opening a new shell or run manually: ${OPEN_CODE_INSTALL_COMMAND}`
15965
+ ].join(`
15966
+ `));
15967
+ }
15968
+ process.stdout.write(`OpenCode installed at ${detectedAfterInstall.path} (${detectedAfterInstall.version}).
15969
+ `);
15970
+ ensureBinaryDirectoryOnPath(detectedAfterInstall.path);
15971
+ return detectedAfterInstall;
15972
+ };
15973
+ var runDoctor = async () => {
15974
+ const detected = await detectOpenCode();
15975
+ process.stdout.write(`Octavio doctor
15976
+ `);
15977
+ process.stdout.write(`- bun: ${Bun.version}
15978
+ `);
15979
+ if (detected) {
15980
+ process.stdout.write(`- opencode: installed (${detected.version})
15981
+ `);
15982
+ process.stdout.write(`- opencode-path: ${detected.path}
15983
+ `);
15984
+ } else {
15985
+ process.stdout.write(`- opencode: missing
15986
+ `);
15987
+ process.stdout.write(`- install: ${OPEN_CODE_INSTALL_COMMAND}
15988
+ `);
15989
+ }
15990
+ };
15991
+
15992
+ // src/troubleshooting.ts
15993
+ var troubleshootingGuidance = (message) => {
15994
+ const lower = message.toLowerCase();
15995
+ const guidance = new Set;
15996
+ if (lower.includes("github_token is required")) {
15997
+ guidance.add("Set GITHUB_TOKEN before running the review command.");
15998
+ }
15999
+ if (lower.includes("github api request failed (401") || lower.includes("github api request failed (403")) {
16000
+ guidance.add("Check GITHUB_TOKEN permissions: repository contents read and pull requests read.");
16001
+ }
16002
+ if (lower.includes("econnrefused") || lower.includes("enotfound") || lower.includes("connect")) {
16003
+ guidance.add("Verify OpenCode connectivity (OPENCODE_HOSTNAME and OPENCODE_PORT).");
16004
+ }
16005
+ if (lower.includes("opencode_api_key") || lower.includes("api key") || lower.includes("unauthorized")) {
16006
+ guidance.add("If you are not using a free model, set OPENCODE_API_KEY (repository secret in GitHub Actions, env var locally).");
16007
+ }
16008
+ if (lower.includes("model") && (lower.includes("not found") || lower.includes("unsupported") || lower.includes("unavailable"))) {
16009
+ guidance.add("Use OPENCODE_MODEL=opencode/minimax-m2.5-free, or provide OPENCODE_API_KEY for paid/private models.");
16010
+ }
16011
+ if (lower.includes("429") || lower.includes("rate limit")) {
16012
+ guidance.add("You hit provider limits. Retry later or use a model/account with higher quota.");
16013
+ }
16014
+ return [...guidance];
16015
+ };
16016
+
16017
+ // src/index.ts
16018
+ var REPORT_LOG_MAX_CHARS = 8000;
15675
16019
  var DEFAULT_ARTIFACT_DIR = "artifacts";
15676
16020
  var DEFAULT_REVIEW_FILE = "review.md";
15677
16021
  var DEFAULT_CONFIDENCE_FILE = "confidence.json";
15678
16022
  var DEFAULT_ARTIFACT_MAX_ATTEMPTS = 2;
15679
16023
  var DEFAULT_ARTIFACT_EXECUTION = "agent";
16024
+ var INSTRUCTIONS_PROFILE_LIST = PROMPT_PROFILES.join("|");
15680
16025
  var usage = () => [
15681
16026
  "Usage:",
15682
16027
  " octavio-review review --owner <owner> --repo <repo> --pr <number> [options]",
16028
+ " octavio-review init [options]",
15683
16029
  " octavio-review doctor",
15684
16030
  " octavio-review install-opencode",
15685
16031
  "",
15686
16032
  "Options for review:",
15687
16033
  " --instructions <path>",
15688
- " --instructions-profile <balanced|styling|security>",
16034
+ ` --instructions-profile <${INSTRUCTIONS_PROFILE_LIST}>`,
15689
16035
  " --artifact-execution <agent|host>",
15690
16036
  " --workdir <path>",
15691
16037
  " --report-output <path>",
15692
16038
  " --findings-output <path>",
15693
16039
  " --result-output <path>",
15694
- " --install-opencode (force auto-install if missing)"
16040
+ " --install-opencode (force auto-install if missing)",
16041
+ "",
16042
+ "Options for init:",
16043
+ " --workdir <path>",
16044
+ " --force (overwrite existing files)"
15695
16045
  ].join(`
15696
16046
  `);
15697
16047
  var parseArtifactExecution = (rawValue) => {
@@ -15767,6 +16117,18 @@ ${usage()}`);
15767
16117
  workspaceDirectory
15768
16118
  };
15769
16119
  };
16120
+ var parseInitInput = (argv) => {
16121
+ const { flags, positional } = parseArgs(argv);
16122
+ if (positional.length > 0) {
16123
+ throw new Error(`Unexpected positional arguments for init: ${positional.join(", ")}
16124
+
16125
+ ${usage()}`);
16126
+ }
16127
+ return {
16128
+ force: hasBooleanFlag(flags, "force"),
16129
+ workspaceDirectory: resolveWorkspaceDirectory(getStringFlag(flags, "workdir") ?? process.cwd())
16130
+ };
16131
+ };
15770
16132
  var resolveInstructions = (cliInput, config2) => {
15771
16133
  const selectedProfileName = cliInput.instructionsProfile ?? config2?.defaultProfile;
15772
16134
  const selectedProfile = selectedProfileName ? config2?.profiles[selectedProfileName] : undefined;
@@ -15777,7 +16139,7 @@ var resolveInstructions = (cliInput, config2) => {
15777
16139
  if (selectedProfile) {
15778
16140
  const profilePrompt = selectedProfile.instructionsPrompt;
15779
16141
  if (!isPromptProfile(profilePrompt)) {
15780
- throw new Error(`Unsupported instructionsPrompt '${profilePrompt}'. Supported values: balanced, styling, security.`);
16142
+ throw new Error(`Unsupported instructionsPrompt '${profilePrompt}'. Supported values: ${PROMPT_PROFILES.join(", ")}.`);
15781
16143
  }
15782
16144
  resolvedPath = resolvePromptPath(profilePrompt);
15783
16145
  }
@@ -15798,127 +16160,26 @@ var defaultResultPath = (pullNumber) => {
15798
16160
  };
15799
16161
  var truncateForLogs = (value) => value.length > REPORT_LOG_MAX_CHARS ? `${value.slice(0, REPORT_LOG_MAX_CHARS)}
15800
16162
  ...truncated...` : value;
15801
- var ensureBinaryDirectoryOnPath = (binaryPath) => {
15802
- const directoryEnd = binaryPath.lastIndexOf("/");
15803
- if (directoryEnd <= 0) {
15804
- return;
15805
- }
15806
- const directory = binaryPath.slice(0, directoryEnd);
15807
- const currentPath = process.env.PATH ?? "";
15808
- const pathEntries = currentPath.split(":").filter((entry) => entry.length > 0);
15809
- if (pathEntries.includes(directory)) {
15810
- return;
15811
- }
15812
- process.env.PATH = currentPath.length > 0 ? `${directory}:${currentPath}` : directory;
15813
- process.stdout.write(`Added ${directory} to PATH for this process.
15814
- `);
15815
- };
15816
- var knownOpenCodePaths = () => {
15817
- const home = process.env.HOME;
15818
- return [
15819
- Bun.which("opencode") ?? "",
15820
- home ? `${home}/.opencode/bin/opencode` : "",
15821
- home ? `${home}/.local/bin/opencode` : ""
15822
- ].filter((value, index, values) => value.length > 0 && values.indexOf(value) === index);
15823
- };
15824
- var readProcessOutput = async (stream) => stream ? await new Response(stream).text() : "";
15825
- var getOpenCodeVersion = async (binaryPath) => {
15826
- const subprocess = Bun.spawn([binaryPath, "--version"], {
15827
- stderr: "pipe",
15828
- stdout: "pipe"
15829
- });
15830
- const [exitCode, stderr, stdout] = await Promise.all([
15831
- subprocess.exited,
15832
- readProcessOutput(subprocess.stderr),
15833
- readProcessOutput(subprocess.stdout)
15834
- ]);
15835
- if (exitCode !== 0) {
15836
- const details = stderr.trim();
15837
- return details.length > 0 ? details : null;
15838
- }
15839
- const version2 = stdout.trim();
15840
- return version2.length > 0 ? version2 : null;
15841
- };
15842
- var detectOpenCode = async () => {
15843
- for (const candidatePath of knownOpenCodePaths()) {
15844
- const file2 = Bun.file(candidatePath);
15845
- if (!await file2.exists()) {
15846
- continue;
15847
- }
15848
- const version2 = await getOpenCodeVersion(candidatePath);
15849
- return {
15850
- path: candidatePath,
15851
- version: version2 ?? "unknown"
15852
- };
15853
- }
15854
- return null;
15855
- };
15856
- var runOpenCodeInstall = async () => {
15857
- process.stdout.write(`OpenCode was not found. Installing now...
16163
+ var runInit = async (argv) => {
16164
+ const input = parseInitInput(argv);
16165
+ process.stdout.write(`Initializing Octavio review in ${input.workspaceDirectory}...
15858
16166
  `);
15859
- process.stdout.write(`Install command: ${OPEN_CODE_INSTALL_COMMAND_NO_PATH}
16167
+ const results = await scaffoldReviewSetup(input.workspaceDirectory, input.force);
16168
+ for (const result of results) {
16169
+ process.stdout.write(`- ${result.status}: ${result.relativePath}
15860
16170
  `);
15861
- const subprocess = Bun.spawn(["bash", "-lc", OPEN_CODE_INSTALL_COMMAND_NO_PATH], {
15862
- stderr: "inherit",
15863
- stdout: "inherit"
15864
- });
15865
- const exitCode = await subprocess.exited;
15866
- if (exitCode !== 0) {
15867
- throw new Error([
15868
- "Failed to auto-install OpenCode.",
15869
- `Run this manually and retry: ${OPEN_CODE_INSTALL_COMMAND}`
15870
- ].join(`
15871
- `));
15872
16171
  }
15873
- };
15874
- var ensureOpenCodeInstalled = async (forceInstall) => {
15875
- const detectedBeforeInstall = await detectOpenCode();
15876
- if (detectedBeforeInstall) {
15877
- ensureBinaryDirectoryOnPath(detectedBeforeInstall.path);
15878
- process.stdout.write(`OpenCode detected at ${detectedBeforeInstall.path} (${detectedBeforeInstall.version}).
16172
+ const skippedCount = results.filter((result) => result.status === "skipped").length;
16173
+ if (skippedCount > 0 && !input.force) {
16174
+ process.stdout.write(`Some files already existed and were skipped. Re-run with --force to overwrite.
15879
16175
  `);
15880
- return detectedBeforeInstall;
15881
16176
  }
15882
- const shouldAutoInstall = forceInstall || process.env.GITHUB_ACTIONS === "true";
15883
- if (!shouldAutoInstall) {
15884
- throw new Error([
15885
- "OpenCode CLI is required but was not found.",
15886
- `Install it with: ${OPEN_CODE_INSTALL_COMMAND}`,
15887
- "Then rerun this command, or pass --install-opencode to auto-install."
15888
- ].join(`
15889
- `));
15890
- }
15891
- await runOpenCodeInstall();
15892
- const detectedAfterInstall = await detectOpenCode();
15893
- if (!detectedAfterInstall) {
15894
- throw new Error([
15895
- "OpenCode install completed but the binary was still not detected.",
15896
- `Try opening a new shell or run manually: ${OPEN_CODE_INSTALL_COMMAND}`
15897
- ].join(`
15898
- `));
15899
- }
15900
- process.stdout.write(`OpenCode installed at ${detectedAfterInstall.path} (${detectedAfterInstall.version}).
15901
- `);
15902
- ensureBinaryDirectoryOnPath(detectedAfterInstall.path);
15903
- return detectedAfterInstall;
15904
- };
15905
- var runDoctor = async () => {
15906
- const detected = await detectOpenCode();
15907
- process.stdout.write(`Octavio doctor
15908
- `);
15909
- process.stdout.write(`- bun: ${Bun.version}
15910
- `);
15911
- if (detected) {
15912
- process.stdout.write(`- opencode: installed (${detected.version})
16177
+ process.stdout.write(`Next steps:
15913
16178
  `);
15914
- process.stdout.write(`- opencode-path: ${detected.path}
16179
+ process.stdout.write(`1. Commit .octavio/review.config.json and .github/workflows/review-check.yml.
15915
16180
  `);
15916
- } else {
15917
- process.stdout.write(`- opencode: missing
16181
+ process.stdout.write(`2. Optional: set OPENCODE_API_KEY if you switch away from the default free model.
15918
16182
  `);
15919
- process.stdout.write(`- install: ${OPEN_CODE_INSTALL_COMMAND}
15920
- `);
15921
- }
15922
16183
  };
15923
16184
  var runReview = async (argv) => {
15924
16185
  const cliInput = parseReviewInput(argv);
@@ -16011,6 +16272,10 @@ var run = async () => {
16011
16272
  await runDoctor();
16012
16273
  return;
16013
16274
  }
16275
+ if (firstArg === "init") {
16276
+ await runInit(args.slice(1));
16277
+ return;
16278
+ }
16014
16279
  if (firstArg === "install-opencode") {
16015
16280
  await ensureOpenCodeInstalled(true);
16016
16281
  return;
@@ -16033,6 +16298,15 @@ try {
16033
16298
  const message = error48 instanceof Error ? error48.message : String(error48);
16034
16299
  process.stderr.write(`octavio-review failed: ${message}
16035
16300
  `);
16301
+ const guidance = troubleshootingGuidance(message);
16302
+ if (guidance.length > 0) {
16303
+ process.stderr.write(`Troubleshooting:
16304
+ `);
16305
+ for (const line of guidance) {
16306
+ process.stderr.write(`- ${line}
16307
+ `);
16308
+ }
16309
+ }
16036
16310
  process.exitCode = 1;
16037
16311
  }
16038
16312
  await Bun.sleep(0);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@octavio.bot/review",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "private": false,
5
5
  "description": "CLI for OpenCode-powered pull request review checks",
6
6
  "homepage": "https://github.com/rustydotwtf/octavio.bot/tree/main/apps/review-bot-cli",
@@ -13,7 +13,7 @@
13
13
  "directory": "apps/review-bot-cli"
14
14
  },
15
15
  "bin": {
16
- "octavio-review": "./dist/index.mjs"
16
+ "octavio-review": "dist/index.mjs"
17
17
  },
18
18
  "files": [
19
19
  "dist",
@@ -24,7 +24,10 @@
24
24
  "access": "public"
25
25
  },
26
26
  "scripts": {
27
- "build": "bun build ./src/index.ts --outfile ./dist/index.mjs --target bun --banner '#!/usr/bin/env bun' && chmod +x ./dist/index.mjs && rm -rf ./prompts && mkdir -p ./prompts && cp ../../packages/prompts/prompts/*.md ./prompts/",
27
+ "build:bin": "bun build ./src/index.ts --outfile ./dist/index.mjs --target bun --banner '#!/usr/bin/env bun' && chmod +x ./dist/index.mjs",
28
+ "sync:prompts": "[ -d ../../packages/prompts/prompts ] || (echo 'Missing prompts source: ../../packages/prompts/prompts' && exit 1) && rm -rf ./prompts && mkdir -p ./prompts && cp ../../packages/prompts/prompts/*.md ./prompts/",
29
+ "sync": "bun run sync:prompts",
30
+ "build": "bun run build:bin && bun run sync:prompts",
28
31
  "prepack": "bun run build"
29
32
  },
30
33
  "dependencies": {