solo-cto-agent 1.0.0 → 1.1.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/CHANGELOG CHANGED
@@ -1,5 +1,59 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.1.0 — Tier-aware reviews, security signals, plugins & telegram
4
+
5
+ **Theme**: closing the last gaps around signal quality and agent
6
+ extensibility. The review loop now reasons about Haiku/Sonnet/Opus
7
+ tier-appropriately, surfaces live CVE/GHSA advisories via OSV.dev,
8
+ captures screenshots without Playwright, and gains a first-cut
9
+ plugin system + experimental telegram setup wizard.
10
+
11
+ ### External signals (PR-G4)
12
+ * **T2 Security Advisories (OSV.dev)** — CVE + GHSA scan across
13
+ `dependencies` + `devDependencies`. Severity normalized (DB-specific
14
+ > CVSS numeric > UNKNOWN) and merged into the external-knowledge
15
+ context block. Gate: `COWORK_EXTERNAL_KNOWLEDGE_SECURITY=0` to skip.
16
+
17
+ ### Review tiering (PR-G2)
18
+ * **Per-tier Claude model resolution** — Haiku (cheap triage) / Sonnet
19
+ (default) / Opus (deep review) selected automatically based on watch
20
+ tier. Overridable via `ANTHROPIC_MODEL_HAIKU|SONNET|OPUS`.
21
+
22
+ ### UI/UX loop (PR-G5)
23
+ * **Playwright-free screenshot capture** — `uiux vision-review --url`
24
+ and `uiux capture --url` now fall back to thum.io when Playwright is
25
+ unavailable. Viewports: mobile 375x812 / tablet 768x1024 / desktop
26
+ 1280x800.
27
+
28
+ ### Plugins & integrations (PR-G6 / G7)
29
+ * **`docs/plugin-api-v2.md`** — capability manifest spec
30
+ (env/net/fs/cli/hook/schedule prefixes), contribution points, agent
31
+ targeting (`claude` / `codex` / `cowork` / `headless`).
32
+ * **`plugin` subcommand** — filesystem-only manager:
33
+ `solo-cto-agent plugin list|show|add --path <dir>|remove`. Records
34
+ metadata only; does NOT execute plugin code. Runtime loader lands
35
+ in a follow-up behind the capability gate.
36
+ * **`telegram wizard`** (experimental — `SOLO_CTO_EXPERIMENTAL=1`)
37
+ — one-command bot token + chat_id capture + `.env` / shell profile
38
+ / GitHub secret writeback + live sendMessage verification.
39
+ * **`docs/telegram-wizard-spec.md`** — full spec including failure
40
+ modes and i18n hooks.
41
+
42
+ ### Developer experience
43
+ * **375 tests** (up from 247 in 1.0.0) across 28 files — all offline,
44
+ all network calls stubbed via injected `fetchImpl`.
45
+ * **Shared `prompt-utils.js`** — `ask` / `askYesNo` / `askChoice` /
46
+ `isTTY` / `createRl` extracted from `wizard.js` for future wizards.
47
+ * **npm publish automation** — tag `v*` now triggers full CI +
48
+ `npm publish` + GitHub Release in one workflow.
49
+
50
+ ### Upgrade notes
51
+ * No breaking changes. All new features are additive and gated on
52
+ env vars (`COWORK_EXTERNAL_KNOWLEDGE_SECURITY`,
53
+ `SOLO_CTO_EXPERIMENTAL`).
54
+ * `solo-cto-agent plugin` and `solo-cto-agent telegram` are new
55
+ commands — existing commands are unchanged.
56
+
3
57
  ## 1.0.0 — First stable release
4
58
 
5
59
  **Why 1.0**: the loop is now closable end-to-end. Previous 0.x releases were
package/README.md CHANGED
@@ -313,10 +313,13 @@ Run a Claude-powered code review directly from your terminal, no GitHub Actions
313
313
  ANTHROPIC_API_KEY=sk-xxx solo-cto-agent review
314
314
 
315
315
  # Review a branch diff (dry-run: see the prompt without calling API)
316
- solo-cto-agent review --diff main..feature --dry-run
316
+ solo-cto-agent review --branch --dry-run
317
317
 
318
- # Review specific directory
319
- solo-cto-agent review --path ./src --diff HEAD~3
318
+ # Review a branch diff against a specific base (e.g., master)
319
+ solo-cto-agent review --branch --target master
320
+
321
+ # Review specific file
322
+ solo-cto-agent review --file src/app/page.tsx
320
323
  ```
321
324
 
322
325
  The review checks your diff against the local failure catalog (known error patterns), then sends it to Claude for security, performance, correctness, and style analysis. Results are saved as markdown reports in `~/.claude/skills/solo-cto-agent/reviews/`. This is the same review quality as the CI/CD pipeline, but runs entirely locally — useful for private repos, offline work, or pre-push checks.
package/bin/cli.js CHANGED
@@ -8,6 +8,7 @@ const { execSync } = require("child_process");
8
8
 
9
9
  const { syncCommand } = require("./sync");
10
10
  const { runWizard, hasWizardFlag } = require("./wizard");
11
+ const i18n = require("./i18n");
11
12
  const { localReview, knowledgeCapture, dualReview, detectMode, sessionSave, sessionRestore, sessionList, recordFeedback, setLogChannel } = require("./cowork-engine");
12
13
  // Lazy-load optional modules so missing files don't break older installs.
13
14
  let uiux, rework, watch, notify, inboundFeedback;
@@ -16,6 +17,10 @@ try { rework = require("./rework"); } catch (_) { rework = null; }
16
17
  try { watch = require("./watch"); } catch (_) { watch = null; }
17
18
  try { notify = require("./notify"); } catch (_) { notify = null; }
18
19
  try { inboundFeedback = require("./inbound-feedback"); } catch (_) { inboundFeedback = null; }
20
+ let pluginManager;
21
+ try { pluginManager = require("./plugin-manager"); } catch (_) { pluginManager = null; }
22
+ let telegramWizard;
23
+ try { telegramWizard = require("./telegram-wizard"); } catch (_) { telegramWizard = null; }
19
24
 
20
25
  const ROOT = path.resolve(__dirname, "..");
21
26
  const DEFAULT_CATALOG = path.join(ROOT, "failure-catalog.json");
@@ -34,7 +39,7 @@ const DEFAULT_PRESET = "builder";
34
39
  // ─── Helpers ────────────────────────────────────────────────
35
40
 
36
41
  function printHelp() {
37
- console.log(`solo-cto-agent — Dual-Agent CI/CD Orchestrator
42
+ console.log(`solo-cto-agent — ${i18n.t("cli.tagline")}
38
43
 
39
44
  Usage:
40
45
  solo-cto-agent init [--force] [--preset maker|builder|cto] [--wizard]
@@ -50,6 +55,7 @@ Usage:
50
55
  solo-cto-agent lint [path]
51
56
  solo-cto-agent doctor
52
57
  solo-cto-agent --help
58
+ solo-cto-agent --lang <en|ko> <command> # override CLI locale (or SOLO_CTO_LANG env)
53
59
 
54
60
  Commands:
55
61
  init Install skills to ~/.claude/skills/ (add --wizard for interactive setup)
@@ -1064,6 +1070,7 @@ function doctorCommand() {
1064
1070
  const hasLocalReview = typeof engine.localReview === "function";
1065
1071
  const hasKnowledge = typeof engine.knowledgeCapture === "function";
1066
1072
  const hasDualReview = typeof engine.dualReview === "function";
1073
+ const hasDetectDefaultBranch = typeof engine.detectDefaultBranch === "function";
1067
1074
  const sessionSave = typeof engine.sessionSave === "function";
1068
1075
  const sessionRestore = typeof engine.sessionRestore === "function";
1069
1076
  const sessionList = typeof engine.sessionList === "function";
@@ -1081,6 +1088,19 @@ function doctorCommand() {
1081
1088
  console.log(" ⚠️ Session functions not available");
1082
1089
  issues.push({ level: "warn", msg: "Engine missing session functions" });
1083
1090
  }
1091
+
1092
+ const isGitRepo = fs.existsSync(path.join(process.cwd(), ".git"));
1093
+ if (hasDetectDefaultBranch && isGitRepo) {
1094
+ try {
1095
+ const base = engine.detectDefaultBranch({ cwd: process.cwd() });
1096
+ console.log(` ℹ️ Default branch: ${base}`);
1097
+ } catch (err) {
1098
+ console.log(` ⚠️ Default branch detection failed: ${err.message}`);
1099
+ issues.push({ level: "warn", msg: "Default branch detection failed" });
1100
+ }
1101
+ } else if (!isGitRepo) {
1102
+ console.log(" ℹ️ Default branch: N/A (not a git repo)");
1103
+ }
1084
1104
  } catch (err) {
1085
1105
  console.log(` ⚠️ Engine load failed: ${err.message}`);
1086
1106
  issues.push({ level: "warn", msg: `Engine load failed: ${err.message}` });
@@ -1364,6 +1384,11 @@ function upgradeCommand(org, repos, orchName) {
1364
1384
 
1365
1385
  async function main() {
1366
1386
  const args = process.argv.slice(2);
1387
+
1388
+ // i18n: resolve locale from --lang flag, env, or default. Must happen before
1389
+ // any user-facing output so printHelp() and error messages respect the choice.
1390
+ i18n.setLocale(i18n.parseLangFlag(args));
1391
+
1367
1392
  const cmd = args[0];
1368
1393
 
1369
1394
  if (!cmd || cmd === "--help" || cmd === "-h" || cmd === "help") {
@@ -1570,13 +1595,26 @@ async function main() {
1570
1595
  } else if (sub === "vision") {
1571
1596
  await uiux.uiuxVisionReview({
1572
1597
  screenshotPath: get("--screenshot"),
1598
+ url: get("--url"),
1573
1599
  viewport: get("--viewport") || "desktop",
1574
1600
  projectSlug: get("--project") || path.basename(process.cwd()),
1575
1601
  });
1602
+ } else if (sub === "capture") {
1603
+ // PR-G5 — standalone URL→screenshot capture (no Playwright)
1604
+ const url = get("--url");
1605
+ if (!url) { console.error("❌ --url required for 'uiux capture'"); process.exit(1); }
1606
+ const out = get("--out") || null;
1607
+ const cap = await uiux.captureScreenshotFromUrl(url, {
1608
+ viewport: get("--viewport") || "desktop",
1609
+ outPath: out,
1610
+ });
1611
+ if (!cap.ok) { console.error(`❌ capture failed: ${cap.error}`); process.exit(1); }
1612
+ console.log(`✓ captured ${(cap.bytes / 1024).toFixed(1)} KB via ${cap.source} → ${cap.path}`);
1576
1613
  } else if (sub === "cross-verify") {
1577
1614
  await uiux.uiuxCrossVerify({
1578
1615
  diffSource: args.includes("--branch") ? "branch" : "staged",
1579
1616
  screenshotPath: get("--screenshot"),
1617
+ url: get("--url"),
1580
1618
  viewport: get("--viewport") || "desktop",
1581
1619
  projectSlug: get("--project") || path.basename(process.cwd()),
1582
1620
  projectDir: get("--cwd") || process.cwd(),
@@ -1782,6 +1820,116 @@ async function main() {
1782
1820
  return;
1783
1821
  }
1784
1822
 
1823
+ if (cmd === "telegram") {
1824
+ if (!telegramWizard) {
1825
+ console.error("❌ telegram-wizard module not available in this install.");
1826
+ process.exit(1);
1827
+ }
1828
+ const sub = args[1] || "wizard";
1829
+ if (sub !== "wizard") {
1830
+ console.error(`❌ Unknown telegram subcommand: ${sub}`);
1831
+ console.error(` Use: solo-cto-agent telegram wizard`);
1832
+ process.exit(1);
1833
+ }
1834
+ const opts = {};
1835
+ const tokIdx = args.indexOf("--token"); if (tokIdx >= 0) opts.token = args[tokIdx + 1];
1836
+ const chatIdx = args.indexOf("--chat"); if (chatIdx >= 0) opts.chat = args[chatIdx + 1];
1837
+ const stoIdx = args.indexOf("--storage"); if (stoIdx >= 0) opts.storage = parseInt(args[stoIdx + 1], 10);
1838
+ const toutIdx = args.indexOf("--timeout"); if (toutIdx >= 0) opts.timeout = parseInt(args[toutIdx + 1], 10);
1839
+ if (args.includes("--non-interactive")) opts.nonInteractive = true;
1840
+ if (args.includes("--force")) opts.force = true;
1841
+ telegramWizard.runWizard(opts).then((res) => {
1842
+ if (!res || !res.ok) process.exit(1);
1843
+ }).catch((e) => {
1844
+ console.error(`❌ ${e.message}`);
1845
+ process.exit(1);
1846
+ });
1847
+ return;
1848
+ }
1849
+
1850
+ if (cmd === "plugin") {
1851
+ if (!pluginManager) {
1852
+ console.error("❌ plugin-manager module not available in this install.");
1853
+ process.exit(1);
1854
+ }
1855
+ const sub = args[1] || "list";
1856
+
1857
+ if (sub === "list") {
1858
+ const plugins = pluginManager.listPlugins();
1859
+ if (args.includes("--json")) {
1860
+ console.log(JSON.stringify(plugins, null, 2));
1861
+ } else {
1862
+ console.log(pluginManager.formatPluginListText(plugins));
1863
+ }
1864
+ return;
1865
+ }
1866
+
1867
+ if (sub === "show") {
1868
+ const name = args[2];
1869
+ if (!name) {
1870
+ console.error("❌ Usage: solo-cto-agent plugin show <name>");
1871
+ process.exit(1);
1872
+ }
1873
+ const manifest = pluginManager.readManifest();
1874
+ const plugin = pluginManager.findPlugin(manifest, name);
1875
+ if (!plugin) {
1876
+ console.error(`❌ Plugin not found: ${name}`);
1877
+ process.exit(1);
1878
+ }
1879
+ console.log(JSON.stringify(plugin, null, 2));
1880
+ return;
1881
+ }
1882
+
1883
+ if (sub === "add") {
1884
+ const pathIdx = args.indexOf("--path");
1885
+ const nameIdx = args.indexOf("--name");
1886
+ if (pathIdx < 0) {
1887
+ console.error("❌ Usage: solo-cto-agent plugin add --path <dir> [--name <override>]");
1888
+ console.error(" (npm install coming in a later release; use --path for local dirs.)");
1889
+ process.exit(1);
1890
+ }
1891
+ const dir = path.resolve(args[pathIdx + 1]);
1892
+ const read = pluginManager.readPackageJsonFromPath(dir);
1893
+ if (!read.ok) {
1894
+ console.error(`❌ ${read.error}`);
1895
+ process.exit(1);
1896
+ }
1897
+ const pkg = nameIdx >= 0 ? { ...read.pkg, name: args[nameIdx + 1] } : read.pkg;
1898
+ const source = `path:${dir}`;
1899
+ const res = pluginManager.addPlugin({ pkg, source });
1900
+ if (!res.ok) {
1901
+ console.error("❌ Plugin validation failed:");
1902
+ for (const e of res.errors) console.error(` - ${e}`);
1903
+ process.exit(1);
1904
+ }
1905
+ console.log(`✓ ${res.replaced ? "Updated" : "Added"} ${res.plugin.name}@${res.plugin.version} (${source})`);
1906
+ console.log(` agents: ${res.plugin.agents.join(", ")}`);
1907
+ if (res.plugin.capabilities.length) {
1908
+ console.log(` capabilities: ${res.plugin.capabilities.join(", ")}`);
1909
+ }
1910
+ return;
1911
+ }
1912
+
1913
+ if (sub === "remove" || sub === "rm") {
1914
+ const name = args[2];
1915
+ if (!name) {
1916
+ console.error("❌ Usage: solo-cto-agent plugin remove <name>");
1917
+ process.exit(1);
1918
+ }
1919
+ const res = pluginManager.removePlugin(name);
1920
+ if (!res.removed) {
1921
+ console.error(`❌ Plugin not found: ${name}`);
1922
+ process.exit(1);
1923
+ }
1924
+ console.log(`✓ Removed ${name}`);
1925
+ return;
1926
+ }
1927
+
1928
+ console.error(`❌ Unknown plugin subcommand: ${sub}`);
1929
+ console.error(` Use: solo-cto-agent plugin list|show|add|remove`);
1930
+ process.exit(1);
1931
+ }
1932
+
1785
1933
  printHelp();
1786
1934
  process.exit(1);
1787
1935
  }