co-maintainer 0.4.13 → 0.5.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +57 -38
  3. package/dist/main.js +2 -3
  4. package/dist/package.json +21 -15
  5. package/dist/src/ai/batch.d.ts +24 -1
  6. package/dist/src/ai/batch.js +75 -23
  7. package/dist/src/ai/estimate.d.ts +40 -0
  8. package/dist/src/ai/estimate.js +113 -0
  9. package/dist/src/ai/fake.d.ts +1 -1
  10. package/dist/src/ai/fake.js +1 -1
  11. package/dist/src/ai/hetzner.js +1 -1
  12. package/dist/src/ai/openrouter.d.ts +13 -0
  13. package/dist/src/ai/openrouter.js +66 -12
  14. package/dist/src/ai/pricing.d.ts +14 -0
  15. package/dist/src/ai/pricing.js +77 -0
  16. package/dist/src/ai/provider.js +8 -0
  17. package/dist/src/ai/verify.d.ts +25 -0
  18. package/dist/src/ai/verify.js +78 -0
  19. package/dist/src/cli/args.js +25 -29
  20. package/dist/src/cli/commands/config.d.ts +44 -0
  21. package/dist/src/cli/commands/config.js +220 -0
  22. package/dist/src/cli/commands/probe.js +70 -103
  23. package/dist/src/cli/commands/registry.d.ts +57 -0
  24. package/dist/src/cli/commands/registry.js +713 -0
  25. package/dist/src/cli/commands/review.js +66 -43
  26. package/dist/src/cli/commands/serve.d.ts +4 -2
  27. package/dist/src/cli/commands/serve.js +14 -10
  28. package/dist/src/cli/commands/set.d.ts +2 -1
  29. package/dist/src/cli/commands/set.js +63 -17
  30. package/dist/src/cli/commands/view.d.ts +1 -0
  31. package/dist/src/cli/commands/view.js +180 -0
  32. package/dist/src/cli/error.d.ts +39 -0
  33. package/dist/src/cli/error.js +64 -0
  34. package/dist/src/cli/main.d.ts +7 -0
  35. package/dist/src/cli/main.js +82 -1
  36. package/dist/src/cli/prompt.js +21 -7
  37. package/dist/src/cli/review_args.d.ts +4 -0
  38. package/dist/src/cli/review_args.js +22 -9
  39. package/dist/src/cli/review_output.d.ts +2 -2
  40. package/dist/src/cli/review_output.js +7 -6
  41. package/dist/src/cli/review_result.d.ts +59 -8
  42. package/dist/src/cli/review_result.js +145 -103
  43. package/dist/src/config.d.ts +15 -0
  44. package/dist/src/config.js +31 -0
  45. package/dist/src/github/app.d.ts +13 -0
  46. package/dist/src/github/app.js +23 -0
  47. package/dist/src/github/app_manifest.d.ts +50 -0
  48. package/dist/src/github/app_manifest.js +138 -0
  49. package/dist/src/github/client.js +7 -1
  50. package/dist/src/github/collect.js +1 -1
  51. package/dist/src/github/gh.js +75 -18
  52. package/dist/src/knowledge/facts.js +8 -3
  53. package/dist/src/knowledge/guide.d.ts +8 -0
  54. package/dist/src/knowledge/guide.js +32 -8
  55. package/dist/src/knowledge/probe.js +13 -3
  56. package/dist/src/knowledge/sections.d.ts +9 -0
  57. package/dist/src/knowledge/sections.js +16 -0
  58. package/dist/src/knowledge/skill.d.ts +5 -0
  59. package/dist/src/knowledge/skill.js +68 -21
  60. package/dist/src/knowledge/synthesis.d.ts +17 -1
  61. package/dist/src/knowledge/synthesis.js +70 -15
  62. package/dist/src/knowledge/types.d.ts +5 -0
  63. package/dist/src/local/codegraph_prepare.d.ts +3 -2
  64. package/dist/src/local/codegraph_prepare.js +4 -2
  65. package/dist/src/local/git_ops.d.ts +5 -4
  66. package/dist/src/local/git_ops.js +8 -9
  67. package/dist/src/local/review_local.js +36 -14
  68. package/dist/src/pr/checkout.js +14 -3
  69. package/dist/src/pr/codegraph_tools.js +3 -3
  70. package/dist/src/pr/diff_summary.js +3 -3
  71. package/dist/src/pr/findings.js +16 -5
  72. package/dist/src/pr/findings_json.d.ts +26 -0
  73. package/dist/src/pr/findings_json.js +187 -0
  74. package/dist/src/pr/review_copy.d.ts +20 -0
  75. package/dist/src/pr/review_copy.js +40 -0
  76. package/dist/src/pr/reviewer.d.ts +2 -1
  77. package/dist/src/pr/reviewer.js +84 -91
  78. package/dist/src/remote/client.js +42 -42
  79. package/dist/src/remote/http.d.ts +5 -0
  80. package/dist/src/remote/http.js +49 -0
  81. package/dist/src/remote/server/guides.d.ts +22 -0
  82. package/dist/src/remote/server/guides.js +81 -0
  83. package/dist/src/remote/server/routes.js +24 -0
  84. package/dist/src/review/blocking.d.ts +35 -0
  85. package/dist/src/review/blocking.js +45 -0
  86. package/dist/src/review/carry_over.d.ts +12 -1
  87. package/dist/src/review/carry_over.js +35 -9
  88. package/dist/src/review/engine.d.ts +1 -0
  89. package/dist/src/review/guides.js +28 -1
  90. package/dist/src/server/api/installations.js +1 -1
  91. package/dist/src/server/api/repos.js +86 -3
  92. package/dist/src/server/api/settings.js +3 -3
  93. package/dist/src/server/app.d.ts +3 -0
  94. package/dist/src/server/app.js +12 -5
  95. package/dist/src/server/pages/activity.d.ts +1 -0
  96. package/dist/src/server/pages/activity.js +2 -2
  97. package/dist/src/server/pages/add_repo.js +73 -5
  98. package/dist/src/server/pages/client.d.ts +1 -1
  99. package/dist/src/server/pages/client.js +1 -1
  100. package/dist/src/server/pages/home.d.ts +6 -1
  101. package/dist/src/server/pages/home.js +25 -2
  102. package/dist/src/server/pages/knowledge.js +1 -1
  103. package/dist/src/server/pages/layout.js +2 -2
  104. package/dist/src/server/pages/repo.d.ts +1 -1
  105. package/dist/src/server/pages/repo.js +21 -2
  106. package/dist/src/server/pages/repo_prs.d.ts +5 -1
  107. package/dist/src/server/pages/repo_prs.js +36 -1
  108. package/dist/src/server/pages/repo_remote.js +2 -2
  109. package/dist/src/server/pages/repo_settings.js +2 -2
  110. package/dist/src/server/pages/router.d.ts +6 -0
  111. package/dist/src/server/pages/router.js +129 -9
  112. package/dist/src/server/pages/settings.d.ts +1 -1
  113. package/dist/src/server/pages/settings.js +87 -9
  114. package/dist/src/server/pages/styles.d.ts +1 -1
  115. package/dist/src/server/pages/styles.js +1 -1
  116. package/dist/src/services/probe.d.ts +28 -0
  117. package/dist/src/services/probe.js +133 -0
  118. package/dist/src/services/remake_cron.d.ts +1 -1
  119. package/dist/src/services/remake_cron.js +3 -3
  120. package/dist/src/services/remote_review.js +14 -4
  121. package/dist/src/services/review.d.ts +11 -0
  122. package/dist/src/services/review.js +39 -11
  123. package/dist/src/services/setup.js +37 -19
  124. package/dist/src/services/setup_checklist.d.ts +10 -0
  125. package/dist/src/services/setup_checklist.js +87 -0
  126. package/dist/src/store/app_db.js +5 -2
  127. package/dist/src/store/cache_db.d.ts +4 -0
  128. package/dist/src/store/cache_db.js +27 -4
  129. package/dist/src/store/deliveries.d.ts +7 -0
  130. package/dist/src/store/deliveries.js +15 -0
  131. package/dist/src/tools/codegraph.d.ts +30 -0
  132. package/dist/src/tools/codegraph.js +49 -12
  133. package/dist/src/types.d.ts +10 -0
  134. package/dist/src/util/log.d.ts +3 -0
  135. package/dist/src/util/log.js +12 -0
  136. package/dist/src/util/run_summary.d.ts +33 -0
  137. package/dist/src/util/run_summary.js +47 -0
  138. package/dist/src/util/webhook_reachability.d.ts +11 -0
  139. package/dist/src/util/webhook_reachability.js +64 -0
  140. package/package.json +21 -15
@@ -0,0 +1,64 @@
1
+ /** One error shape for every CLI failure (CORE-10).
2
+ *
3
+ * The message is the first line the user reads, `hint` is an optional
4
+ * follow-up line printed as `Hint: ...`, and `exitCode` is the documented
5
+ * contract: 1 only for blocking review findings, 2 for usage and precondition
6
+ * errors, 3 for runtime failures. Anything thrown that is not a `CliError` is
7
+ * treated as a runtime failure (3), which is the safe default for the network,
8
+ * provider and filesystem errors the CLI does not classify yet.
9
+ */
10
+ export const EXIT_FINDINGS = 1;
11
+ export const EXIT_USAGE = 2;
12
+ export const EXIT_RUNTIME = 3;
13
+ export class CliError extends Error {
14
+ code;
15
+ hint;
16
+ exitCode;
17
+ constructor(code, message, hint, exitCode = EXIT_USAGE) {
18
+ super(message);
19
+ this.name = "CliError";
20
+ this.code = code;
21
+ this.hint = hint;
22
+ this.exitCode = exitCode;
23
+ }
24
+ }
25
+ /** Throws a usage or precondition error. Kept as a helper so the existing
26
+ * `die(...)` call sites read the same as before. */
27
+ export function die(message, code = "usage") {
28
+ throw new CliError(code, message, undefined, EXIT_USAGE);
29
+ }
30
+ /** Ends the CLI with `code`, letting the event loop drain first.
31
+ *
32
+ * `process.exit` while undici's fetch connection pool is still open asserts in
33
+ * libuv on Windows (`Assertion failed: !(handle->flags & UV_HANDLE_CLOSING),
34
+ * file src\win\async.c, line 94`) and the process dies with 0xC0000409 instead
35
+ * of the intended code. Every provider and remote call uses fetch, so this is
36
+ * reachable from any error path after a request. Setting `exitCode` and letting
37
+ * the loop empty is what the probe confirms is crash-free and immediate (the
38
+ * undici pool does not keep the process alive). CORE-11. */
39
+ export function exitWith(code) {
40
+ process.exitCode = code;
41
+ }
42
+ /** Renders an error the way the CLI prints it: the message, then `Hint: ...`
43
+ * when there is one. Used where an error becomes a log line rather than the
44
+ * top-level exit (CORE-12), so a hint is not lost just because the failure was
45
+ * quarantined. */
46
+ export function formatError(error) {
47
+ if (error instanceof CliError) {
48
+ return error.hint ? `${error.message} Hint: ${error.hint}` : error.message;
49
+ }
50
+ return error instanceof Error ? error.message : String(error);
51
+ }
52
+ /** A failed `fetch` as a message that names the host and the cause (CORE-12).
53
+ * Node's own text is only `fetch failed`, which says neither. */
54
+ export function networkFailure(url, error) {
55
+ const code = error.cause?.code;
56
+ let host = url;
57
+ try {
58
+ host = new URL(url).host;
59
+ }
60
+ catch {
61
+ // Keep the raw value when it does not parse.
62
+ }
63
+ return new CliError("network_failed", `Could not reach ${host}${code ? `: ${code}` : "."}`, undefined, EXIT_RUNTIME);
64
+ }
@@ -1 +1,8 @@
1
1
  export declare function run(args: string[]): Promise<void>;
2
+ /** The single place a CLI error turns into output and an exit code (CORE-10).
3
+ * The message's first line and the optional `Hint:` line keep the 0.4.13
4
+ * shape. With `--json` the error goes to stdout as `schemaVersion 1` JSON, the
5
+ * same shape local review already used. CORE-11 later replaces the direct
6
+ * `process.exit` with `exitCode` plus handle cleanup, which is why that work is
7
+ * separate. */
8
+ export declare function reportCliError(error: unknown): void;
@@ -3,12 +3,55 @@
3
3
  import { parseArgs } from "./args.js";
4
4
  import { runServe } from "./commands/serve.js";
5
5
  import { runSet } from "./commands/set.js";
6
+ import { runConfig } from "./commands/config.js";
7
+ import { runView } from "./commands/view.js";
6
8
  import { runProbe } from "./commands/probe.js";
7
9
  import { runReviewFromCli } from "./commands/review.js";
8
10
  import { runInitOrRemake } from "../services/setup.js";
9
11
  import { VERSION } from "../version.js";
12
+ import { CliError, EXIT_RUNTIME, exitWith } from "./error.js";
13
+ import { findCommand, renderCommandHelp, renderGlobalHelp, unknownCommandMessage, } from "./commands/registry.js";
14
+ /** Every command's help is rendered from the registry, so a new flag cannot
15
+ * drift out of it. `help <command>`, `<command> --help` and `<command> -h` all
16
+ * land here and all exit 0 (CORE-20). */
17
+ function printHelpFor(name) {
18
+ const help = renderCommandHelp(name);
19
+ console.log(help ?? renderGlobalHelp());
20
+ exitWith(0);
21
+ }
22
+ /** True when the help was printed and `run` should stop. */
23
+ function handleHelp(args) {
24
+ const first = args[0];
25
+ if (first === "help") {
26
+ const target = args.find((arg) => !arg.startsWith("-") && arg !== "help");
27
+ if (target)
28
+ printHelpFor(target);
29
+ else {
30
+ console.log(renderGlobalHelp());
31
+ exitWith(0);
32
+ }
33
+ return true;
34
+ }
35
+ if (first === "--help" || first === "-h") {
36
+ console.log(renderGlobalHelp());
37
+ exitWith(0);
38
+ return true;
39
+ }
40
+ // `<command> --help` without a repo: `serve --help` and `set --help` must not
41
+ // reach their handlers, which would start a server or demand a flag (CORE-20).
42
+ if (first && (args.includes("--help") || args.includes("-h"))) {
43
+ const spec = findCommand(first);
44
+ if (spec) {
45
+ printHelpFor(first);
46
+ return true;
47
+ }
48
+ }
49
+ return false;
50
+ }
10
51
  export async function run(args) {
11
- if (args[0] === "-v" || args[0] === "--version") {
52
+ if (handleHelp(args))
53
+ return;
54
+ if (args[0] === "-v" || args[0] === "--version" || args[0] === "version") {
12
55
  console.log(VERSION);
13
56
  return;
14
57
  }
@@ -16,6 +59,14 @@ export async function run(args) {
16
59
  await runSet(args.slice(1));
17
60
  return;
18
61
  }
62
+ if (args[0] === "config") {
63
+ await runConfig(args.slice(1));
64
+ return;
65
+ }
66
+ if (args[0] === "view") {
67
+ await runView(args.slice(1));
68
+ return;
69
+ }
19
70
  if (args[0] === "serve") {
20
71
  await runServe(args.slice(1));
21
72
  return;
@@ -24,9 +75,39 @@ export async function run(args) {
24
75
  await runReviewFromCli(args.slice(1));
25
76
  return;
26
77
  }
78
+ // An unknown command is a usage error with a suggestion, and it must resolve
79
+ // through the registry so hidden aliases still work (CORE-20).
80
+ if (args[0] && !args[0].startsWith("-") && !findCommand(args[0])) {
81
+ throw new CliError("usage", unknownCommandMessage(args[0]));
82
+ }
27
83
  const options = await parseArgs(args);
28
84
  if (options.command === "probe")
29
85
  await runProbe(options);
30
86
  else
31
87
  await runInitOrRemake(options);
32
88
  }
89
+ /** The single place a CLI error turns into output and an exit code (CORE-10).
90
+ * The message's first line and the optional `Hint:` line keep the 0.4.13
91
+ * shape. With `--json` the error goes to stdout as `schemaVersion 1` JSON, the
92
+ * same shape local review already used. CORE-11 later replaces the direct
93
+ * `process.exit` with `exitCode` plus handle cleanup, which is why that work is
94
+ * separate. */
95
+ export function reportCliError(error) {
96
+ const cli = error instanceof CliError
97
+ ? error
98
+ : new CliError("runtime", error instanceof Error ? error.message : String(error), undefined, EXIT_RUNTIME);
99
+ if (process.argv.includes("--json")) {
100
+ console.log(JSON.stringify({
101
+ schemaVersion: 1,
102
+ ok: false,
103
+ error: { code: cli.code, message: cli.message, hint: cli.hint },
104
+ exitCode: cli.exitCode,
105
+ }));
106
+ }
107
+ else {
108
+ console.error(`[error] ${cli.message}`);
109
+ if (cli.hint)
110
+ console.error(`Hint: ${cli.hint}`);
111
+ }
112
+ exitWith(cli.exitCode);
113
+ }
@@ -4,16 +4,30 @@
4
4
  * more than a couple of questions, and an interface left open keeps stdin
5
5
  * referenced, which would hang a non-interactive pipeline. */
6
6
  import { createInterface } from "node:readline/promises";
7
+ /** Asks one question and always settles.
8
+ *
9
+ * `rl.question` never settles when stdin reaches EOF before an answer, which
10
+ * is the F01 hang: `co-maintainer review </dev/null` awaited the answer
11
+ * forever, printed `Warning: Detected unsettled top-level await`, and exited
12
+ * 13. Racing the question against `close`/`end` and treating those as "no
13
+ * answer" keeps the caller's fallback path alive instead. */
7
14
  async function ask(question) {
8
15
  const rl = createInterface({ input: process.stdin, output: process.stdout });
9
16
  try {
10
- return (await rl.question(question)).trim();
11
- }
12
- catch {
13
- // `question` rejects when stdin closes mid-prompt (Ctrl-D, a detached
14
- // pipeline, a killed parent). Treat that as "no answer" so the caller's
15
- // own fallback/required validation runs instead of an uncaught rejection.
16
- return "";
17
+ return (await new Promise((resolve) => {
18
+ let settled = false;
19
+ const done = (value) => {
20
+ if (settled)
21
+ return;
22
+ settled = true;
23
+ resolve(value);
24
+ };
25
+ // `question` rejects when stdin closes mid-prompt (Ctrl-D, a detached
26
+ // pipeline, a killed parent); treat that as "no answer" too.
27
+ void rl.question(question).then(done, () => done(""));
28
+ rl.once("close", () => done(""));
29
+ process.stdin.once("end", () => done(""));
30
+ })).trim();
17
31
  }
18
32
  finally {
19
33
  rl.close();
@@ -10,6 +10,10 @@ type ReviewFlags = {
10
10
  branch?: string;
11
11
  repoOverride?: string;
12
12
  remakeBeforeReview: boolean;
13
+ /** `--remote-host` override for this run only (CORE-25). */
14
+ remoteHost?: string;
15
+ /** `--remote-token` override for this run only (CORE-25). */
16
+ remoteToken?: string;
13
17
  };
14
18
  export type ReviewCliArgs = ({
15
19
  mode: "pr";
@@ -1,16 +1,16 @@
1
1
  import { parseArgs, setCliInteractive } from "./args.js";
2
- function die(message) {
3
- throw new Error(message);
4
- }
2
+ import { die } from "./error.js";
5
3
  function reviewFlags(rest) {
6
4
  const text = (name) => rest.find((item) => item.startsWith(`--${name}=`))?.slice(name.length + 3);
5
+ // `--remake-before-review` is the 0.4.13 spelling; both names mean the same
6
+ // thing, and only the new one is documented (CORE-21).
7
+ const syncBeforeReview = rest.some((arg) => arg === "--sync-before-review" || arg === "--remake-before-review");
7
8
  for (const arg of rest) {
8
9
  if (arg === "--codegraph") {
9
10
  die("Unknown option: --codegraph");
10
11
  }
11
- if (arg === "--remote" &&
12
- rest.some((r) => r === "--remake-before-review")) {
13
- die("--remake-before-review cannot be used with --remote");
12
+ if (arg === "--remote" && syncBeforeReview) {
13
+ die("--sync-before-review cannot be used with --remote");
14
14
  }
15
15
  }
16
16
  return {
@@ -22,7 +22,9 @@ function reviewFlags(rest) {
22
22
  toBranch: text("to-branch"),
23
23
  branch: text("branch"),
24
24
  repoOverride: text("repo"),
25
- remakeBeforeReview: rest.includes("--remake-before-review"),
25
+ remakeBeforeReview: syncBeforeReview,
26
+ remoteHost: text("remote-host"),
27
+ remoteToken: text("remote-token"),
26
28
  };
27
29
  }
28
30
  const LOCAL_ONLY_FLAGS = new Set([
@@ -32,6 +34,7 @@ const LOCAL_ONLY_FLAGS = new Set([
32
34
  "--allow-tool-install",
33
35
  "--fresh",
34
36
  "--remake-before-review",
37
+ "--sync-before-review",
35
38
  ]);
36
39
  /** Strip local-only flags before `parseArgs` for PR-style options. */
37
40
  export function filterReviewConfigArgs(raw) {
@@ -40,7 +43,9 @@ export function filterReviewConfigArgs(raw) {
40
43
  return false;
41
44
  if (arg.startsWith("--to-branch=") ||
42
45
  arg.startsWith("--branch=") ||
43
- arg.startsWith("--repo=")) {
46
+ arg.startsWith("--repo=") ||
47
+ arg.startsWith("--remote-host=") ||
48
+ arg.startsWith("--remote-token=")) {
44
49
  return false;
45
50
  }
46
51
  return true;
@@ -50,6 +55,11 @@ export function filterReviewConfigArgs(raw) {
50
55
  export async function parseReviewArgs(args) {
51
56
  const positional = args.filter((a) => !a.startsWith("--"));
52
57
  const flags = reviewFlags(args);
58
+ // `--remote-host` / `--remote-token` only mean something together with
59
+ // `--remote`; silently dropping them is exactly the F33 surprise (CORE-25).
60
+ if ((flags.remoteHost || flags.remoteToken) && !flags.remote) {
61
+ die("--remote-host and --remote-token require --remote");
62
+ }
53
63
  // `--json` must never prompt (plan §8.7). `parseArgs` only receives the
54
64
  // filtered args, and `--json` is stripped before it sees them, so the flag
55
65
  // has to be applied here rather than after the parse returns.
@@ -58,6 +68,9 @@ export async function parseReviewArgs(args) {
58
68
  /^[^/]+\/[^/]+$/.test(positional[0]) &&
59
69
  /^\d+$/.test(positional[1]);
60
70
  if (isPr) {
71
+ if (flags.remoteHost || flags.remoteToken) {
72
+ die("--remote-host is only for remote review without a PR number");
73
+ }
61
74
  const options = await parseArgs([
62
75
  "review",
63
76
  positional[0],
@@ -67,7 +80,7 @@ export async function parseReviewArgs(args) {
67
80
  if (flags.remote)
68
81
  die("--remote is only for local review without a PR number");
69
82
  if (flags.remakeBeforeReview) {
70
- die("--remake-before-review is not supported for PR review");
83
+ die("--sync-before-review is not supported for PR review");
71
84
  }
72
85
  return {
73
86
  mode: "pr",
@@ -1,2 +1,2 @@
1
- export declare function reviewExitCode(markdown: string): number;
2
- export declare function printLocalReview(header: string, markdown: string): void;
1
+ import { type ReviewBlocking } from "../review/blocking.ts";
2
+ export declare function reviewExitCode(markdown: string, mode?: ReviewBlocking): number;
@@ -1,10 +1,11 @@
1
1
  import { parseFindings } from "../pr/findings.js";
2
- import { humanCopy } from "../services/review.js";
3
- export function reviewExitCode(markdown) {
2
+ import { DEFAULT_REVIEW_BLOCKING, isBlocking, } from "../review/blocking.js";
3
+ export function reviewExitCode(markdown, mode = DEFAULT_REVIEW_BLOCKING) {
4
4
  const findings = parseFindings(markdown);
5
- const blocking = findings.some((f) => f.blocking === true || /\[P0\b/i.test(f.heading));
5
+ const blocking = findings.some((f) => isBlocking(mode, {
6
+ severity: f.severity,
7
+ blocked: f.blocking,
8
+ text: f.heading,
9
+ }));
6
10
  return blocking ? 1 : 0;
7
11
  }
8
- export function printLocalReview(header, markdown) {
9
- console.log(`\n${header}\n\n${humanCopy(markdown)}\n`);
10
- }
@@ -1,5 +1,6 @@
1
1
  import { type ParsedFinding } from "../pr/findings.ts";
2
2
  import { type ResolvedFinding } from "../review/carry_over.ts";
3
+ import { type ReviewBlocking } from "../review/blocking.ts";
3
4
  import type { Revision } from "../review/revision.ts";
4
5
  export type ReviewWarning = {
5
6
  code: string;
@@ -22,29 +23,77 @@ export type JsonReviewFinding = {
22
23
  text: string;
23
24
  } | null;
24
25
  };
25
- export declare function isBlockingFinding(title: string, severity: string): boolean;
26
+ export declare function isBlockingFinding(title: string, severity: string, mode?: ReviewBlocking): boolean;
26
27
  export declare function resolvedFromFirstReview(parsed: ParsedFinding[], filesByPath: Map<string, {
27
28
  patch: string;
28
29
  }>): ResolvedFinding[];
29
30
  export declare function sortResolvedFindings(findings: ResolvedFinding[]): ResolvedFinding[];
30
- export declare function toJsonFinding(row: ResolvedFinding): JsonReviewFinding;
31
+ export declare function toJsonFinding(row: ResolvedFinding, mode?: ReviewBlocking): JsonReviewFinding;
31
32
  export declare function revisionStats(revision: Revision): {
32
33
  files: number;
33
34
  additions: number;
34
35
  deletions: number;
35
36
  truncated: boolean;
36
37
  };
37
- export declare function summaryCounts(findings: ResolvedFinding[]): {
38
+ export declare function summaryCounts(findings: ResolvedFinding[], mode?: ReviewBlocking): {
38
39
  new: number;
39
40
  open: number;
40
41
  closed: number;
41
42
  blocking: number;
42
43
  };
43
- export declare function reviewExitCodeFromResolved(findings: ResolvedFinding[]): number;
44
- /** Plan §9.1 human-readable local review output. */
45
- export declare function formatHumanLocalReview(header: string, revision: Revision, guideBuiltAt: string | null, codegraphState: "used" | "disabled" | "unavailable", findings: ResolvedFinding[], warnings: ReviewWarning[]): string;
46
- /** Human-readable findings block for remote sync JSON (same shape as `toJsonFinding`). */
47
- export declare function formatHumanJsonFindings(findings: JsonReviewFinding[]): string;
44
+ export declare function reviewExitCodeFromResolved(findings: ResolvedFinding[], mode?: ReviewBlocking): number;
45
+ /** One finding as the human renderer prints it. Local, PR and remote reviews
46
+ * all normalize into this shape before printing, so the three modes cannot
47
+ * drift into different products (CORE-43 / F21, F22). */
48
+ export type HumanFinding = {
49
+ state: "new" | "open" | "closed";
50
+ severity: string;
51
+ blocking: boolean;
52
+ path: string | null;
53
+ lineFrom: number | null;
54
+ lineTo: number | null;
55
+ title: string;
56
+ body: string;
57
+ suggestion: {
58
+ lineFrom: number;
59
+ lineTo: number;
60
+ text: string;
61
+ } | null;
62
+ };
63
+ /** Local and PR findings: blocking is decided here from the configured rule,
64
+ * exactly as the exit code decides it (CORE-41). */
65
+ export declare function humanFindingsFromResolved(findings: ResolvedFinding[], mode?: ReviewBlocking): HumanFinding[];
66
+ /** Remote findings already carry the server's blocking decision, so the label
67
+ * uses that field rather than re-deriving the rule on a client that may not
68
+ * share the server's config (CORE-41). */
69
+ export declare function humanFindingsFromJson(findings: JsonReviewFinding[]): HumanFinding[];
70
+ export type HumanReviewInput = {
71
+ /** Header line: repo, subject and mode. */
72
+ title: string;
73
+ /** Optional `N files · +a −b` fragment, present for a local review. */
74
+ stats?: string;
75
+ guideBuiltAt: string | null;
76
+ /** `null` when the run cannot say (a server older than 0.5.0 sends no
77
+ * codegraph block). A wrong "used" is what F23 was about, so unknown is
78
+ * reported as unknown rather than guessed. */
79
+ codegraphState: "used" | "disabled" | "unavailable" | null;
80
+ findings: HumanFinding[];
81
+ warnings?: ReviewWarning[];
82
+ };
83
+ /** The single human-readable review format (CORE-43 / F21, F22, F23). Local,
84
+ * PR and remote reviews print this: a header naming the guide build date and
85
+ * the codegraph state, the findings grouped by state, then one summary line.
86
+ *
87
+ * The severity legend and the "ask for more detail" sentence are deliberately
88
+ * absent: a terminal has nobody to ask, and the legend is fixed noise. Both
89
+ * stay in the GitHub comment, which is a different reader. */
90
+ export declare function formatHumanReview(input: HumanReviewInput): string;
91
+ /** Local review output: the shared format plus the working-tree file stats. */
92
+ export declare function formatHumanLocalReview(header: string, revision: Revision, guideBuiltAt: string | null, codegraphState: "used" | "disabled" | "unavailable", findings: ResolvedFinding[], warnings: ReviewWarning[], mode?: ReviewBlocking): string;
93
+ /** The exit code for a remote review (CORE-41). The server already decided
94
+ * each finding's `blocking` under its own configured rule, so the client
95
+ * trusts that field instead of re-deriving the rule from a title: the client
96
+ * may not even have the same config as the server. */
48
97
  export declare function reviewExitCodeFromJsonFindings(findings: JsonReviewFinding[]): number;
49
98
  export type LocalReviewJsonInput = {
50
99
  repo: string;
@@ -62,6 +111,7 @@ export type LocalReviewJsonInput = {
62
111
  costUsd: number | null;
63
112
  };
64
113
  durationMs: number;
114
+ reviewBlocking?: ReviewBlocking;
65
115
  };
66
116
  export type PrReviewJsonInput = {
67
117
  repo: string;
@@ -76,6 +126,7 @@ export type PrReviewJsonInput = {
76
126
  costUsd: number | null;
77
127
  };
78
128
  durationMs: number;
129
+ reviewBlocking?: ReviewBlocking;
79
130
  };
80
131
  export declare function buildPrReviewJson(input: PrReviewJsonInput): string;
81
132
  export declare function buildLocalReviewJson(input: LocalReviewJsonInput): string;