repopilot 0.10.0 → 0.11.0

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
@@ -15,9 +15,10 @@ and framework health checks. It gives developers useful terminal reports, CI gat
15
15
  SARIF for GitHub Code Scanning, and Markdown briefs that can be pasted into Claude
16
16
  Code, ChatGPT, Cursor, or another coding assistant.
17
17
 
18
- Version `0.10.0` is an adoption and stability release: it focuses on cleaner
19
- rollout diagnostics, reproducible audit receipts, CI artifacts, and 0.x command
20
- compatibility while keeping all scans local.
18
+ Version `0.11.0` is a signal quality and CI polish release: it reduces
19
+ low-value self-audit findings, adds first-party Action parity for priority/rule
20
+ filters, and tightens the `doctor -> scan/review -> ai context -> CI gate` loop
21
+ while keeping all scans local.
21
22
 
22
23
  ## Why RepoPilot?
23
24
 
@@ -133,8 +134,10 @@ Once you want CI integration, baseline tracking, or tighter configuration:
133
134
  ```bash
134
135
  repopilot init # generate repopilot.toml
135
136
  repopilot doctor . # check CI / baseline readiness
137
+ repopilot scan . --min-priority p2 --rule security.secret-candidate
138
+ repopilot ai context . --focus security # prepare local AI context
136
139
  repopilot baseline create . # accept existing findings as known debt
137
- repopilot review . --base origin/main --fail-on new-high # CI gate on new findings only
140
+ repopilot review . --base origin/main --fail-on-priority p1 # CI gate on high-priority risk
138
141
  ```
139
142
 
140
143
  Save a shareable report:
@@ -217,7 +220,7 @@ RepoPilot is a local-first safety layer for AI-assisted and vibe-coded changes.
217
220
  repopilot ai context . --focus security # get an LLM brief for Claude Code
218
221
  repopilot ai plan . --focus security # get a prioritized hardening plan
219
222
  repopilot ai prompt . --budget 8k # generate a paste-ready remediation prompt
220
- repopilot review . --base origin/main --baseline .repopilot/baseline.json --fail-on new-high
223
+ repopilot review . --base origin/main --baseline .repopilot/baseline.json --fail-on-priority p1
221
224
  repopilot scan . --workspace --min-severity medium --format markdown --output repopilot-report.md
222
225
  ```
223
226
 
@@ -381,7 +384,7 @@ If your installed RepoPilot version does not support `--output`, redirect stdout
381
384
  repopilot scan . --format sarif > repopilot.sarif
382
385
  ```
383
386
 
384
- Use JSON when custom scripts need to parse RepoPilot results. Console, Markdown, and HTML reports include the RepoPilot version, risk summary, top rules, and grouped findings for human review. Use SARIF for CI and code scanning integrations, including GitHub Code Scanning.
387
+ Use JSON when custom scripts need to parse RepoPilot results. Console, Markdown, and HTML reports include the RepoPilot version, risk summary, top risk clusters, top rules, and grouped findings for human review. Use SARIF for CI and code scanning integrations, including GitHub Code Scanning.
385
388
  Use `--receipt <path>` with `scan` when CI or release processes need compact evidence of the exact RepoPilot version, git state, scan scope, finding counts, language counts, and health score.
386
389
 
387
390
  See [docs/integrations/github-code-scanning.md](docs/integrations/github-code-scanning.md) for a copy-paste GitHub Actions workflow, required permissions, and local validation commands.
@@ -403,7 +406,7 @@ Use `--fail-on new-high` to fail CI only when new high or critical findings are
403
406
  When `--fail-on new-*` is used without `--baseline`, RepoPilot treats all current findings as new. For baseline-based adoption, commit an accepted baseline and scan against it in CI.
404
407
 
405
408
  To upload RepoPilot findings to GitHub Code Scanning, generate SARIF and use `github/codeql-action/upload-sarif`. The workflow must include `security-events: write`.
406
- The first-party action can also run `command: ai-context`, `command: ai-plan`, or `command: ai-prompt`; those commands emit Markdown and do not produce SARIF. Prefer typed action inputs such as `path`, `config`, `baseline`, `fail-on`, `focus`, `budget`, `output`, and `receipt`; `args` remains available for advanced flags.
409
+ The first-party action can also run `command: doctor`, `command: ai-context`, `command: ai-plan`, or `command: ai-prompt`; AI commands emit Markdown and do not produce SARIF. Prefer typed action inputs such as `path`, `config`, `baseline`, `fail-on`, `fail-on-priority`, `min-priority`, `rule`, `timing`, `focus`, `budget`, `output`, and `receipt`; `args` remains available for advanced flags.
407
410
 
408
411
  ```yaml
409
412
  name: RepoPilot
@@ -454,7 +457,6 @@ These are planned ideas, not current features:
454
457
 
455
458
  - Change Risk Map
456
459
  - Better architecture drift detection
457
- - Optional per-platform npm binary packages for faster installs
458
460
 
459
461
  ## Documentation
460
462
 
@@ -465,12 +467,15 @@ These are planned ideas, not current features:
465
467
  | [docs/security.md](docs/security.md) | Local-first trust model, installer security, and vulnerability reporting |
466
468
  | [docs/configuration.md](docs/configuration.md) | `repopilot.toml`, presets, ignore files, and baseline adoption |
467
469
  | [docs/language-support.md](docs/language-support.md) | Supported language/framework tiers, rule families, and limitations |
470
+ | [docs/risk-engine.md](docs/risk-engine.md) | Risk scoring, priority buckets, signals, and calibration policy |
468
471
  | [docs/cli.md](docs/cli.md) | Complete CLI reference — all commands, flags, and exit codes |
469
472
  | [docs/commands.md](docs/commands.md) | Task-oriented command guide and common patterns |
470
473
  | [docs/rulesets.md](docs/rulesets.md) | Implemented audit rules, categories, and severity levels |
471
474
  | [docs/react-native.md](docs/react-native.md) | React Native and Expo detection, findings, and limitations |
472
475
  | [docs/integrations/github-code-scanning.md](docs/integrations/github-code-scanning.md) | GitHub Code Scanning SARIF workflow |
473
476
  | [docs/release.md](docs/release.md) | Manual release process |
477
+ | [docs/release-announcement-0.11.md](docs/release-announcement-0.11.md) | 0.11.0 launch post and post-publish checks |
478
+ | [docs/release-checklist-0.11.md](docs/release-checklist-0.11.md) | 0.11.0 release readiness checklist |
474
479
  | [docs/release-checklist-0.10.md](docs/release-checklist-0.10.md) | 0.10.0 release readiness checklist |
475
480
  | [docs/distribution.md](docs/distribution.md) | Distribution channels |
476
481
  | [docs/github-ruleset.md](docs/github-ruleset.md) | GitHub branch ruleset configuration |
package/bin/repopilot.js CHANGED
@@ -4,29 +4,141 @@ const fs = require("node:fs");
4
4
  const path = require("node:path");
5
5
  const { spawn } = require("node:child_process");
6
6
 
7
- function binaryName() {
8
- return process.platform === "win32" ? "repopilot.exe" : "repopilot";
7
+ const SUPPORTED_TARGETS = [
8
+ { platform: "darwin", arch: "arm64", packageName: "@repopilot/darwin-arm64", binary: "repopilot" },
9
+ { platform: "darwin", arch: "x64", packageName: "@repopilot/darwin-x64", binary: "repopilot" },
10
+ {
11
+ platform: "linux",
12
+ arch: "arm64",
13
+ libc: "glibc",
14
+ packageName: "@repopilot/linux-arm64-gnu",
15
+ binary: "repopilot",
16
+ },
17
+ {
18
+ platform: "linux",
19
+ arch: "x64",
20
+ libc: "glibc",
21
+ packageName: "@repopilot/linux-x64-gnu",
22
+ binary: "repopilot",
23
+ },
24
+ { platform: "win32", arch: "x64", packageName: "@repopilot/win32-x64-msvc", binary: "repopilot.exe" },
25
+ ];
26
+
27
+ class UnsupportedPlatformError extends Error {
28
+ constructor(platform, arch, libc) {
29
+ const runtime = libc ? `${platform}/${arch}/${libc}` : `${platform}/${arch}`;
30
+ super(`Unsupported npm platform: ${runtime}`);
31
+ this.name = "UnsupportedPlatformError";
32
+ this.code = "ERR_REPOPILOT_UNSUPPORTED_PLATFORM";
33
+ this.platform = platform;
34
+ this.arch = arch;
35
+ this.libc = libc;
36
+ }
37
+ }
38
+
39
+ class MissingOptionalPackageError extends Error {
40
+ constructor(packageName, cause) {
41
+ super(`Missing optional native package: ${packageName}`);
42
+ this.name = "MissingOptionalPackageError";
43
+ this.code = "ERR_REPOPILOT_MISSING_OPTIONAL_PACKAGE";
44
+ this.packageName = packageName;
45
+ this.cause = cause;
46
+ }
47
+ }
48
+
49
+ function detectLibc(platform = process.platform) {
50
+ if (platform !== "linux") {
51
+ return undefined;
52
+ }
53
+
54
+ const report = typeof process.report?.getReport === "function" ? process.report.getReport() : undefined;
55
+ if (report?.header?.glibcVersionRuntime) {
56
+ return "glibc";
57
+ }
58
+ return "musl";
59
+ }
60
+
61
+ function platformPackage(platform = process.platform, arch = process.arch, libc = detectLibc(platform)) {
62
+ const match = SUPPORTED_TARGETS.find((target) => {
63
+ if (target.platform !== platform || target.arch !== arch) {
64
+ return false;
65
+ }
66
+ return target.platform !== "linux" || target.libc === libc;
67
+ });
68
+
69
+ if (!match) {
70
+ throw new UnsupportedPlatformError(platform, arch, platform === "linux" ? libc : undefined);
71
+ }
72
+ return match;
9
73
  }
10
74
 
11
- function bundledBinaryPath() {
12
- return path.join(__dirname, "..", "vendor", binaryName());
75
+ function packageRoot(packageName, resolver = require.resolve) {
76
+ try {
77
+ return path.dirname(resolver(`${packageName}/package.json`));
78
+ } catch (error) {
79
+ if (error && error.code === "MODULE_NOT_FOUND") {
80
+ throw new MissingOptionalPackageError(packageName, error);
81
+ }
82
+ throw error;
83
+ }
13
84
  }
14
85
 
15
- function resolveBinaryPath() {
16
- if (process.env.REPOPILOT_BINARY_PATH) {
17
- return process.env.REPOPILOT_BINARY_PATH;
86
+ function resolveBinaryPath(options = {}) {
87
+ const env = options.env ?? process.env;
88
+ if (env.REPOPILOT_BINARY_PATH) {
89
+ return env.REPOPILOT_BINARY_PATH;
18
90
  }
19
- return bundledBinaryPath();
91
+
92
+ const target = platformPackage(options.platform, options.arch, options.libc);
93
+ return path.join(packageRoot(target.packageName, options.resolver), "bin", target.binary);
94
+ }
95
+
96
+ function supportedTargetsText() {
97
+ return SUPPORTED_TARGETS.map((target) => {
98
+ const runtime = target.libc
99
+ ? `${target.platform}/${target.arch}/${target.libc}`
100
+ : `${target.platform}/${target.arch}`;
101
+ return `${runtime} (${target.packageName})`;
102
+ }).join(", ");
103
+ }
104
+
105
+ function formatResolveError(error) {
106
+ if (error.code === "ERR_REPOPILOT_UNSUPPORTED_PLATFORM") {
107
+ return [
108
+ "RepoPilot does not provide an npm native package for this platform.",
109
+ `Detected: ${error.libc ? `${error.platform}/${error.arch}/${error.libc}` : `${error.platform}/${error.arch}`}`,
110
+ `Supported npm targets: ${supportedTargetsText()}`,
111
+ "Install with cargo, Homebrew, GitHub Releases, or set REPOPILOT_BINARY_PATH to an existing repopilot binary.",
112
+ ].join("\n");
113
+ }
114
+
115
+ if (error.code === "ERR_REPOPILOT_MISSING_OPTIONAL_PACKAGE") {
116
+ return [
117
+ "RepoPilot optional native package was not installed.",
118
+ `Expected npm package: ${error.packageName}`,
119
+ "Reinstall without omitting optional dependencies, for example: npm install -g repopilot",
120
+ "If your environment blocks optional dependencies, install with cargo or set REPOPILOT_BINARY_PATH to an existing repopilot binary.",
121
+ ].join("\n");
122
+ }
123
+
124
+ return `Failed to resolve RepoPilot binary: ${error.message}`;
20
125
  }
21
126
 
22
127
  function main() {
23
- const binary = resolveBinaryPath();
128
+ let binary;
129
+ try {
130
+ binary = resolveBinaryPath();
131
+ } catch (error) {
132
+ console.error(formatResolveError(error));
133
+ process.exit(1);
134
+ }
135
+
24
136
  if (!fs.existsSync(binary)) {
25
137
  console.error(
26
138
  [
27
139
  "RepoPilot binary was not found.",
28
140
  `Expected: ${binary}`,
29
- "Run npm install again, install with cargo, or set REPOPILOT_BINARY_PATH to an existing repopilot binary.",
141
+ "Reinstall repopilot, install with cargo, or set REPOPILOT_BINARY_PATH to an existing repopilot binary.",
30
142
  ].join("\n")
31
143
  );
32
144
  process.exit(1);
@@ -55,7 +167,12 @@ if (require.main === module) {
55
167
  }
56
168
 
57
169
  module.exports = {
58
- binaryName,
59
- bundledBinaryPath,
170
+ SUPPORTED_TARGETS,
171
+ MissingOptionalPackageError,
172
+ UnsupportedPlatformError,
173
+ detectLibc,
174
+ formatResolveError,
175
+ platformPackage,
60
176
  resolveBinaryPath,
177
+ supportedTargetsText,
61
178
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "repopilot",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Local-first CLI for repository audit, architecture risk detection, baseline tracking, and CI-friendly code review.",
5
5
  "license": "MIT OR Apache-2.0",
6
6
  "repository": {
@@ -15,7 +15,6 @@
15
15
  "repopilot": "bin/repopilot.js"
16
16
  },
17
17
  "scripts": {
18
- "postinstall": "node scripts/install.js",
19
18
  "test": "npm run test:npm",
20
19
  "test:npm": "node --test tests/npm_wrapper.test.js",
21
20
  "release:check": "bash scripts/verify-release.sh",
@@ -23,10 +22,16 @@
23
22
  },
24
23
  "files": [
25
24
  "bin",
26
- "scripts",
27
25
  "README.md",
28
26
  "LICENSE"
29
27
  ],
28
+ "optionalDependencies": {
29
+ "@repopilot/darwin-arm64": "0.11.0",
30
+ "@repopilot/darwin-x64": "0.11.0",
31
+ "@repopilot/linux-arm64-gnu": "0.11.0",
32
+ "@repopilot/linux-x64-gnu": "0.11.0",
33
+ "@repopilot/win32-x64-msvc": "0.11.0"
34
+ },
30
35
  "keywords": [
31
36
  "cli",
32
37
  "audit",
@@ -1,141 +0,0 @@
1
- #!/usr/bin/env node
2
-
3
- const crypto = require("node:crypto");
4
- const fs = require("node:fs");
5
- const https = require("node:https");
6
- const os = require("node:os");
7
- const path = require("node:path");
8
- const { spawnSync } = require("node:child_process");
9
-
10
- const ROOT = path.join(__dirname, "..");
11
- const VENDOR_DIR = path.join(ROOT, "vendor");
12
- const PACKAGE_JSON = require(path.join(ROOT, "package.json"));
13
-
14
- function targetTriple(platform = process.platform, arch = process.arch) {
15
- if (platform === "darwin" && arch === "x64") return "x86_64-apple-darwin";
16
- if (platform === "darwin" && arch === "arm64") return "aarch64-apple-darwin";
17
- if (platform === "linux" && arch === "x64") return "x86_64-unknown-linux-gnu";
18
- if (platform === "linux" && arch === "arm64") return "aarch64-unknown-linux-gnu";
19
- if (platform === "win32" && arch === "x64") return "x86_64-pc-windows-msvc";
20
- throw new Error(`Unsupported platform: ${platform}/${arch}`);
21
- }
22
-
23
- function archiveExt(target) {
24
- return target.endsWith("windows-msvc") ? "zip" : "tar.gz";
25
- }
26
-
27
- function assetName(version, target) {
28
- return `repopilot-v${version}-${target}.${archiveExt(target)}`;
29
- }
30
-
31
- function releaseUrl(version, file) {
32
- return `https://github.com/MykytaStel/repopilot/releases/download/v${version}/${file}`;
33
- }
34
-
35
- function download(url, destination) {
36
- return new Promise((resolve, reject) => {
37
- const file = fs.createWriteStream(destination);
38
- https
39
- .get(url, (response) => {
40
- if (response.statusCode >= 300 && response.statusCode < 400 && response.headers.location) {
41
- file.close();
42
- fs.rmSync(destination, { force: true });
43
- download(response.headers.location, destination).then(resolve, reject);
44
- return;
45
- }
46
- if (response.statusCode !== 200) {
47
- file.close();
48
- fs.rmSync(destination, { force: true });
49
- reject(new Error(`Download failed (${response.statusCode}): ${url}`));
50
- return;
51
- }
52
- response.pipe(file);
53
- file.on("finish", () => file.close(resolve));
54
- })
55
- .on("error", (error) => {
56
- file.close();
57
- fs.rmSync(destination, { force: true });
58
- reject(error);
59
- });
60
- });
61
- }
62
-
63
- function sha256(file) {
64
- const hash = crypto.createHash("sha256");
65
- hash.update(fs.readFileSync(file));
66
- return hash.digest("hex");
67
- }
68
-
69
- function expectedChecksum(checksumText) {
70
- const first = checksumText.trim().split(/\s+/)[0];
71
- if (!/^[a-fA-F0-9]{64}$/.test(first)) {
72
- throw new Error("Invalid checksum file format");
73
- }
74
- return first.toLowerCase();
75
- }
76
-
77
- function verifyChecksum(archivePath, checksumText) {
78
- const expected = expectedChecksum(checksumText);
79
- const actual = sha256(archivePath);
80
- if (actual !== expected) {
81
- throw new Error(`Checksum mismatch for ${path.basename(archivePath)}: expected ${expected}, got ${actual}`);
82
- }
83
- }
84
-
85
- function extractArchive(archivePath, destination, target) {
86
- fs.rmSync(destination, { recursive: true, force: true });
87
- fs.mkdirSync(destination, { recursive: true });
88
-
89
- const result = target.endsWith("windows-msvc")
90
- ? spawnSync("powershell", ["-NoProfile", "-Command", `Expand-Archive -Force '${archivePath}' '${destination}'`], { stdio: "inherit" })
91
- : spawnSync("tar", ["-xzf", archivePath, "-C", destination], { stdio: "inherit" });
92
-
93
- if (result.status !== 0) {
94
- throw new Error(`Failed to extract ${archivePath}`);
95
- }
96
- }
97
-
98
- async function install() {
99
- if (process.env.REPOPILOT_SKIP_DOWNLOAD === "1" || process.env.REPOPILOT_SKIP_DOWNLOAD === "true") {
100
- console.log("Skipping RepoPilot binary download because REPOPILOT_SKIP_DOWNLOAD is set.");
101
- return;
102
- }
103
-
104
- const target = targetTriple();
105
- const version = PACKAGE_JSON.version;
106
- const archive = assetName(version, target);
107
- const tmp = fs.mkdtempSync(path.join(os.tmpdir(), "repopilot-"));
108
- const archivePath = path.join(tmp, archive);
109
- const checksumPath = `${archivePath}.sha256`;
110
- const extractDir = path.join(tmp, "extract");
111
- const binaryName = target.endsWith("windows-msvc") ? "repopilot.exe" : "repopilot";
112
-
113
- try {
114
- await download(releaseUrl(version, archive), archivePath);
115
- await download(releaseUrl(version, `${archive}.sha256`), checksumPath);
116
- verifyChecksum(archivePath, fs.readFileSync(checksumPath, "utf8"));
117
- extractArchive(archivePath, extractDir, target);
118
-
119
- fs.mkdirSync(VENDOR_DIR, { recursive: true });
120
- fs.copyFileSync(path.join(extractDir, binaryName), path.join(VENDOR_DIR, binaryName));
121
- fs.chmodSync(path.join(VENDOR_DIR, binaryName), 0o755);
122
- } finally {
123
- fs.rmSync(tmp, { recursive: true, force: true });
124
- }
125
- }
126
-
127
- if (require.main === module) {
128
- install().catch((error) => {
129
- console.error(`Failed to install RepoPilot binary: ${error.message}`);
130
- console.error("Set REPOPILOT_SKIP_DOWNLOAD=1 to skip download or REPOPILOT_BINARY_PATH to use an existing binary.");
131
- process.exit(1);
132
- });
133
- }
134
-
135
- module.exports = {
136
- archiveExt,
137
- assetName,
138
- expectedChecksum,
139
- targetTriple,
140
- verifyChecksum,
141
- };
@@ -1,266 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
-
6
- BINARY=""
7
- REPO_PATH="$ROOT_DIR"
8
- TMP_DIR=""
9
- TMP_CREATED=0
10
- KEEP_TMP=0
11
- RUN_LEGACY=1
12
-
13
- usage() {
14
- cat <<'EOF'
15
- Usage: scripts/smoke-product.sh [OPTIONS]
16
-
17
- Run product-readiness smoke checks against RepoPilot.
18
-
19
- Options:
20
- --binary PATH Use an already built repopilot binary.
21
- --repo PATH Repository or project path to smoke test. Defaults to this repo.
22
- --tmp-dir PATH Directory for generated smoke artifacts. Defaults to mktemp.
23
- --keep-tmp Keep generated smoke artifacts when using a temporary directory.
24
- --skip-legacy Skip hidden 0.x compatibility command checks.
25
- -h, --help Show this help message.
26
-
27
- Examples:
28
- scripts/smoke-product.sh
29
- scripts/smoke-product.sh --binary ./target/release/repopilot
30
- scripts/smoke-product.sh --repo /path/to/project --keep-tmp
31
- EOF
32
- }
33
-
34
- while [[ $# -gt 0 ]]; do
35
- case "$1" in
36
- --binary)
37
- BINARY="${2:-}"
38
- shift 2
39
- ;;
40
- --repo)
41
- REPO_PATH="${2:-}"
42
- shift 2
43
- ;;
44
- --tmp-dir)
45
- TMP_DIR="${2:-}"
46
- shift 2
47
- ;;
48
- --keep-tmp)
49
- KEEP_TMP=1
50
- shift
51
- ;;
52
- --skip-legacy)
53
- RUN_LEGACY=0
54
- shift
55
- ;;
56
- -h|--help)
57
- usage
58
- exit 0
59
- ;;
60
- *)
61
- echo "Unknown argument: $1" >&2
62
- usage >&2
63
- exit 2
64
- ;;
65
- esac
66
- done
67
-
68
- if [[ -z "$REPO_PATH" ]]; then
69
- echo "--repo cannot be empty" >&2
70
- exit 2
71
- fi
72
-
73
- REPO_PATH="$(cd "$REPO_PATH" && pwd)"
74
-
75
- if [[ -z "$BINARY" ]]; then
76
- if [[ ! -x "$ROOT_DIR/target/debug/repopilot" ]]; then
77
- echo "==> Building debug binary for product smoke checks" >&2
78
- cargo build --manifest-path "$ROOT_DIR/Cargo.toml"
79
- fi
80
- BINARY="$ROOT_DIR/target/debug/repopilot"
81
- else
82
- case "$BINARY" in
83
- /*) ;;
84
- *)
85
- BINARY="$(cd "$(dirname "$BINARY")" && pwd)/$(basename "$BINARY")"
86
- ;;
87
- esac
88
- fi
89
-
90
- if [[ ! -x "$BINARY" ]]; then
91
- echo "RepoPilot binary is not executable: $BINARY" >&2
92
- exit 3
93
- fi
94
-
95
- if [[ -z "$TMP_DIR" ]]; then
96
- TMP_DIR="$(mktemp -d)"
97
- TMP_CREATED=1
98
- else
99
- rm -rf "$TMP_DIR"
100
- mkdir -p "$TMP_DIR"
101
- TMP_DIR="$(cd "$TMP_DIR" && pwd)"
102
- fi
103
-
104
- cleanup() {
105
- if [[ "$TMP_CREATED" -eq 1 && "$KEEP_TMP" -eq 0 ]]; then
106
- rm -rf "$TMP_DIR"
107
- fi
108
- }
109
- trap cleanup EXIT
110
-
111
- run_repopilot() {
112
- echo "==> repopilot $*" >&2
113
- (
114
- cd "$REPO_PATH"
115
- "$BINARY" "$@"
116
- )
117
- }
118
-
119
- assert_non_empty() {
120
- local file="$1"
121
- if [[ ! -s "$file" ]]; then
122
- echo "Expected non-empty file: $file" >&2
123
- exit 3
124
- fi
125
- }
126
-
127
- assert_contains() {
128
- local file="$1"
129
- local expected="$2"
130
-
131
- if ! grep -Fq "$expected" "$file"; then
132
- echo "Expected '$file' to contain: $expected" >&2
133
- echo "---- file content ----" >&2
134
- cat "$file" >&2 || true
135
- echo "----------------------" >&2
136
- exit 3
137
- fi
138
- }
139
-
140
- validate_json_if_possible() {
141
- local file="$1"
142
-
143
- if command -v python3 >/dev/null 2>&1; then
144
- python3 -m json.tool "$file" >/dev/null
145
- fi
146
- }
147
-
148
- find_explain_file() {
149
- local preferred="$REPO_PATH/src/main.rs"
150
-
151
- if [[ -f "$preferred" ]]; then
152
- printf '%s\n' "$preferred"
153
- return 0
154
- fi
155
-
156
- local discovered=""
157
- while IFS= read -r candidate; do
158
- discovered="$candidate"
159
- break
160
- done < <(
161
- find "$REPO_PATH" \
162
- -path "$REPO_PATH/.git" -prune -o \
163
- -path "$REPO_PATH/target" -prune -o \
164
- -path "$REPO_PATH/node_modules" -prune -o \
165
- -type f \( \
166
- -name '*.rs' -o \
167
- -name '*.ts' -o \
168
- -name '*.tsx' -o \
169
- -name '*.js' -o \
170
- -name '*.jsx' -o \
171
- -name '*.py' -o \
172
- -name '*.go' \
173
- \) \
174
- -print
175
- )
176
-
177
- if [[ -n "$discovered" && -f "$discovered" ]]; then
178
- printf '%s\n' "$discovered"
179
- return 0
180
- fi
181
-
182
- printf '%s\n' "$REPO_PATH/Cargo.toml"
183
- }
184
-
185
- EXPLAIN_FILE="$(find_explain_file)"
186
-
187
- echo "==> RepoPilot product smoke suite"
188
- echo "Binary: $BINARY"
189
- echo "Repo: $REPO_PATH"
190
- echo "Tmp: $TMP_DIR"
191
- echo
192
-
193
- run_repopilot --version > "$TMP_DIR/version.txt"
194
- assert_non_empty "$TMP_DIR/version.txt"
195
-
196
- run_repopilot --help > "$TMP_DIR/help.txt"
197
- assert_non_empty "$TMP_DIR/help.txt"
198
- assert_contains "$TMP_DIR/help.txt" "scan"
199
- assert_contains "$TMP_DIR/help.txt" "review"
200
- assert_contains "$TMP_DIR/help.txt" "ai"
201
- assert_contains "$TMP_DIR/help.txt" "inspect"
202
-
203
- run_repopilot init --force --path "$TMP_DIR/repopilot.toml"
204
- assert_non_empty "$TMP_DIR/repopilot.toml"
205
-
206
- run_repopilot doctor . --format markdown --output "$TMP_DIR/doctor.md"
207
- assert_non_empty "$TMP_DIR/doctor.md"
208
-
209
- run_repopilot scan . --format json --output "$TMP_DIR/scan.json" --receipt "$TMP_DIR/receipt.json"
210
- assert_non_empty "$TMP_DIR/scan.json"
211
- validate_json_if_possible "$TMP_DIR/scan.json"
212
- assert_non_empty "$TMP_DIR/receipt.json"
213
- validate_json_if_possible "$TMP_DIR/receipt.json"
214
-
215
- run_repopilot scan . --format markdown --output "$TMP_DIR/scan.md"
216
- assert_non_empty "$TMP_DIR/scan.md"
217
-
218
- if git -C "$REPO_PATH" rev-parse --is-inside-work-tree >/dev/null 2>&1; then
219
- run_repopilot review . --format markdown --output "$TMP_DIR/review.md"
220
- assert_non_empty "$TMP_DIR/review.md"
221
- else
222
- echo "==> Skipping review smoke check because repo path is not inside a Git worktree" >&2
223
- fi
224
-
225
- run_repopilot ai context . --focus security --budget 2k --output "$TMP_DIR/ai-context.md"
226
- assert_non_empty "$TMP_DIR/ai-context.md"
227
-
228
- run_repopilot ai plan . --focus all --budget 2k --output "$TMP_DIR/ai-plan.md"
229
- assert_non_empty "$TMP_DIR/ai-plan.md"
230
-
231
- run_repopilot ai prompt . --focus quality --budget 2k --output "$TMP_DIR/ai-prompt.md"
232
- assert_non_empty "$TMP_DIR/ai-prompt.md"
233
-
234
- run_repopilot inspect knowledge --format json --output "$TMP_DIR/inspect-knowledge.json"
235
- assert_non_empty "$TMP_DIR/inspect-knowledge.json"
236
- validate_json_if_possible "$TMP_DIR/inspect-knowledge.json"
237
-
238
- run_repopilot inspect knowledge --section rules --format markdown --output "$TMP_DIR/inspect-knowledge-rules.md"
239
- assert_non_empty "$TMP_DIR/inspect-knowledge-rules.md"
240
-
241
- run_repopilot inspect explain "$EXPLAIN_FILE" --format json --output "$TMP_DIR/inspect-explain.json"
242
- assert_non_empty "$TMP_DIR/inspect-explain.json"
243
- validate_json_if_possible "$TMP_DIR/inspect-explain.json"
244
-
245
- if [[ "$RUN_LEGACY" -eq 1 ]]; then
246
- run_repopilot vibe . --focus security --budget 2k --output "$TMP_DIR/legacy-vibe.md"
247
- assert_non_empty "$TMP_DIR/legacy-vibe.md"
248
-
249
- run_repopilot harden . --focus all --budget 2k --output "$TMP_DIR/legacy-harden.md"
250
- assert_non_empty "$TMP_DIR/legacy-harden.md"
251
-
252
- run_repopilot prompt . --focus quality --budget 2k --output "$TMP_DIR/legacy-prompt.md"
253
- assert_non_empty "$TMP_DIR/legacy-prompt.md"
254
-
255
- run_repopilot knowledge --format json --output "$TMP_DIR/legacy-knowledge.json"
256
- assert_non_empty "$TMP_DIR/legacy-knowledge.json"
257
- validate_json_if_possible "$TMP_DIR/legacy-knowledge.json"
258
-
259
- run_repopilot explain "$EXPLAIN_FILE" --format json --output "$TMP_DIR/legacy-explain.json"
260
- assert_non_empty "$TMP_DIR/legacy-explain.json"
261
- validate_json_if_possible "$TMP_DIR/legacy-explain.json"
262
- fi
263
-
264
- echo
265
- echo "==> Product smoke suite passed"
266
- echo "Artifacts: $TMP_DIR"
@@ -1,84 +0,0 @@
1
- #!/usr/bin/env bash
2
- set -euo pipefail
3
-
4
- ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
5
- cd "$ROOT_DIR"
6
-
7
- echo "==> RepoPilot release verification"
8
-
9
- echo "==> Rust formatting"
10
- cargo fmt --all -- --check
11
-
12
- echo "==> Rust clippy"
13
- cargo clippy --all-targets --all-features -- -D warnings
14
-
15
- echo "==> Rust tests"
16
- cargo test --all
17
-
18
- echo "==> Rust dependency security audit"
19
- cargo audit
20
-
21
- echo "==> Cargo dependency policy"
22
- cargo deny check advisories licenses
23
-
24
- echo "==> Shell script syntax"
25
- scripts=()
26
- if [[ -f install.sh ]]; then
27
- scripts+=(install.sh)
28
- fi
29
- if [[ -d scripts ]]; then
30
- while IFS= read -r -d '' script; do
31
- scripts+=("$script")
32
- done < <(find scripts -maxdepth 1 -type f -name '*.sh' -print0)
33
- fi
34
-
35
- if ((${#scripts[@]} > 0)); then
36
- for script in "${scripts[@]}"; do
37
- bash -n "$script"
38
- done
39
-
40
- echo "==> ShellCheck"
41
- shellcheck "${scripts[@]}"
42
- else
43
- echo "==> No shell scripts found"
44
- fi
45
-
46
- echo "==> GitHub Actions validation"
47
- workflows=()
48
- if [[ -d .github/workflows ]]; then
49
- while IFS= read -r -d '' workflow; do
50
- workflows+=("$workflow")
51
- done < <(find .github/workflows -maxdepth 1 -type f \( -name '*.yml' -o -name '*.yaml' \) -print0)
52
- fi
53
-
54
- if ((${#workflows[@]} > 0)); then
55
- actionlint "${workflows[@]}"
56
- else
57
- echo "==> No GitHub Actions workflows found"
58
- fi
59
-
60
- echo "==> CLI release smoke tests"
61
- cargo test --test cli_release_smoke
62
-
63
- echo "==> Cargo package contents"
64
- cargo package --list
65
-
66
- echo "==> Cargo publish dry-run"
67
- cargo publish --dry-run
68
-
69
- echo "==> npm wrapper tests"
70
- npm run test:npm
71
-
72
- echo "==> npm package dry-run"
73
- npm pack --dry-run
74
-
75
- echo "==> Build release binary"
76
- cargo build --release
77
-
78
- echo "==> Product readiness smoke suite"
79
- ./scripts/smoke-product.sh \
80
- --binary ./target/release/repopilot \
81
- --repo . \
82
- --tmp-dir /tmp/repopilot-release-smoke
83
-
84
- echo "==> Release verification passed"