@zigrivers/mmr 1.2.2 → 1.4.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.
Files changed (126) hide show
  1. package/README.md +422 -0
  2. package/dist/cli.d.ts.map +1 -1
  3. package/dist/cli.js +4 -0
  4. package/dist/cli.js.map +1 -1
  5. package/dist/commands/ack.d.ts +11 -0
  6. package/dist/commands/ack.d.ts.map +1 -0
  7. package/dist/commands/ack.js +123 -0
  8. package/dist/commands/ack.js.map +1 -0
  9. package/dist/commands/config.d.ts +5 -0
  10. package/dist/commands/config.d.ts.map +1 -1
  11. package/dist/commands/config.js +270 -27
  12. package/dist/commands/config.js.map +1 -1
  13. package/dist/commands/jobs.d.ts.map +1 -1
  14. package/dist/commands/jobs.js +3 -4
  15. package/dist/commands/jobs.js.map +1 -1
  16. package/dist/commands/reconcile.d.ts.map +1 -1
  17. package/dist/commands/reconcile.js +12 -5
  18. package/dist/commands/reconcile.js.map +1 -1
  19. package/dist/commands/results.d.ts.map +1 -1
  20. package/dist/commands/results.js +13 -5
  21. package/dist/commands/results.js.map +1 -1
  22. package/dist/commands/review.d.ts +25 -0
  23. package/dist/commands/review.d.ts.map +1 -1
  24. package/dist/commands/review.js +457 -44
  25. package/dist/commands/review.js.map +1 -1
  26. package/dist/commands/sessions.d.ts +58 -0
  27. package/dist/commands/sessions.d.ts.map +1 -0
  28. package/dist/commands/sessions.js +266 -0
  29. package/dist/commands/sessions.js.map +1 -0
  30. package/dist/commands/status.d.ts.map +1 -1
  31. package/dist/commands/status.js +2 -3
  32. package/dist/commands/status.js.map +1 -1
  33. package/dist/config/defaults.d.ts +2 -2
  34. package/dist/config/defaults.d.ts.map +1 -1
  35. package/dist/config/defaults.js +66 -0
  36. package/dist/config/defaults.js.map +1 -1
  37. package/dist/config/loader.d.ts +22 -0
  38. package/dist/config/loader.d.ts.map +1 -1
  39. package/dist/config/loader.js +279 -36
  40. package/dist/config/loader.js.map +1 -1
  41. package/dist/config/schema.d.ts +869 -53
  42. package/dist/config/schema.d.ts.map +1 -1
  43. package/dist/config/schema.js +151 -4
  44. package/dist/config/schema.js.map +1 -1
  45. package/dist/core/ack-store.d.ts +109 -0
  46. package/dist/core/ack-store.d.ts.map +1 -0
  47. package/dist/core/ack-store.js +363 -0
  48. package/dist/core/ack-store.js.map +1 -0
  49. package/dist/core/auth.d.ts +10 -1
  50. package/dist/core/auth.d.ts.map +1 -1
  51. package/dist/core/auth.js +65 -2
  52. package/dist/core/auth.js.map +1 -1
  53. package/dist/core/compensator.d.ts +32 -4
  54. package/dist/core/compensator.d.ts.map +1 -1
  55. package/dist/core/compensator.js +118 -15
  56. package/dist/core/compensator.js.map +1 -1
  57. package/dist/core/diff-introspect.d.ts +21 -0
  58. package/dist/core/diff-introspect.d.ts.map +1 -0
  59. package/dist/core/diff-introspect.js +42 -0
  60. package/dist/core/diff-introspect.js.map +1 -0
  61. package/dist/core/dispatcher.d.ts +7 -0
  62. package/dist/core/dispatcher.d.ts.map +1 -1
  63. package/dist/core/dispatcher.js +21 -3
  64. package/dist/core/dispatcher.js.map +1 -1
  65. package/dist/core/git-show.d.ts +31 -0
  66. package/dist/core/git-show.d.ts.map +1 -0
  67. package/dist/core/git-show.js +72 -0
  68. package/dist/core/git-show.js.map +1 -0
  69. package/dist/core/http-dispatcher.d.ts +20 -0
  70. package/dist/core/http-dispatcher.d.ts.map +1 -0
  71. package/dist/core/http-dispatcher.js +125 -0
  72. package/dist/core/http-dispatcher.js.map +1 -0
  73. package/dist/core/job-store.d.ts +7 -1
  74. package/dist/core/job-store.d.ts.map +1 -1
  75. package/dist/core/job-store.js +21 -1
  76. package/dist/core/job-store.js.map +1 -1
  77. package/dist/core/jsonpath.d.ts +15 -0
  78. package/dist/core/jsonpath.d.ts.map +1 -0
  79. package/dist/core/jsonpath.js +63 -0
  80. package/dist/core/jsonpath.js.map +1 -0
  81. package/dist/core/oss-examples.d.ts +18 -0
  82. package/dist/core/oss-examples.d.ts.map +1 -0
  83. package/dist/core/oss-examples.js +66 -0
  84. package/dist/core/oss-examples.js.map +1 -0
  85. package/dist/core/parser.d.ts +8 -3
  86. package/dist/core/parser.d.ts.map +1 -1
  87. package/dist/core/parser.js +157 -6
  88. package/dist/core/parser.js.map +1 -1
  89. package/dist/core/project-root.d.ts +10 -0
  90. package/dist/core/project-root.d.ts.map +1 -0
  91. package/dist/core/project-root.js +23 -0
  92. package/dist/core/project-root.js.map +1 -0
  93. package/dist/core/reconciler.d.ts +1 -1
  94. package/dist/core/reconciler.d.ts.map +1 -1
  95. package/dist/core/reconciler.js +100 -18
  96. package/dist/core/reconciler.js.map +1 -1
  97. package/dist/core/redact.d.ts +17 -0
  98. package/dist/core/redact.d.ts.map +1 -0
  99. package/dist/core/redact.js +140 -0
  100. package/dist/core/redact.js.map +1 -0
  101. package/dist/core/results-pipeline.d.ts +8 -2
  102. package/dist/core/results-pipeline.d.ts.map +1 -1
  103. package/dist/core/results-pipeline.js +51 -2
  104. package/dist/core/results-pipeline.js.map +1 -1
  105. package/dist/core/runtime-probe.d.ts +14 -0
  106. package/dist/core/runtime-probe.d.ts.map +1 -0
  107. package/dist/core/runtime-probe.js +57 -0
  108. package/dist/core/runtime-probe.js.map +1 -0
  109. package/dist/core/stable-id.d.ts +19 -0
  110. package/dist/core/stable-id.d.ts.map +1 -0
  111. package/dist/core/stable-id.js +148 -0
  112. package/dist/core/stable-id.js.map +1 -0
  113. package/dist/core/trust-mode.d.ts +29 -0
  114. package/dist/core/trust-mode.d.ts.map +1 -0
  115. package/dist/core/trust-mode.js +103 -0
  116. package/dist/core/trust-mode.js.map +1 -0
  117. package/dist/formatters/markdown.d.ts.map +1 -1
  118. package/dist/formatters/markdown.js +18 -2
  119. package/dist/formatters/markdown.js.map +1 -1
  120. package/dist/formatters/text.d.ts.map +1 -1
  121. package/dist/formatters/text.js +17 -2
  122. package/dist/formatters/text.js.map +1 -1
  123. package/dist/types.d.ts +45 -1
  124. package/dist/types.d.ts.map +1 -1
  125. package/dist/types.js.map +1 -1
  126. package/package.json +2 -2
@@ -1,4 +1,5 @@
1
1
  import { dispatchChannel } from './dispatcher.js';
2
+ import { dispatchHttpChannel } from './http-dispatcher.js';
2
3
  /** Focus areas for compensating passes, keyed by the channel being compensated */
3
4
  const COMPENSATING_FOCUS = {
4
5
  codex: 'Focus your review on: implementation correctness, security vulnerabilities,'
@@ -7,30 +8,114 @@ const COMPENSATING_FOCUS = {
7
8
  gemini: 'Focus your review on: architectural patterns, design consistency,'
8
9
  + ' broad-context reasoning, separation of concerns, and dependency analysis.'
9
10
  + ' You are compensating for a missing Gemini review.',
11
+ grok: 'Focus your review on: an independent second-opinion pass over correctness'
12
+ + ' and code quality — edge cases, logic errors, and risky assumptions other'
13
+ + ' reviewers may have anchored past. You are compensating for a missing Grok review.',
10
14
  };
11
- /** Channels that should NOT be compensated (e.g., claude can't compensate for itself) */
12
- const SKIP_COMPENSATION = new Set(['claude']);
15
+ function defaultCompensatorDispatch(config) {
16
+ return {
17
+ command: 'claude',
18
+ flags: ['-p', '--output-format', 'json'],
19
+ env: {},
20
+ timeout: config.defaults.timeout,
21
+ prompt_wrapper: '{{prompt}}',
22
+ stderr: 'capture',
23
+ output_parser: 'default',
24
+ };
25
+ }
26
+ export function resolveCompensatorDispatch(config) {
27
+ const { defaults } = config;
28
+ const channelName = defaults.compensator?.channel;
29
+ if (!channelName) {
30
+ return defaultCompensatorDispatch(config);
31
+ }
32
+ const channelConfig = getDispatchableCompensatorChannel(config, channelName);
33
+ return {
34
+ command: channelConfig.command,
35
+ flags: channelConfig.flags,
36
+ env: channelConfig.env,
37
+ timeout: channelConfig.timeout ?? config.defaults.timeout,
38
+ prompt_wrapper: channelConfig.prompt_wrapper ?? '{{prompt}}',
39
+ stderr: channelConfig.stderr,
40
+ output_parser: channelConfig.output_parser,
41
+ prompt_delivery: channelConfig.prompt_delivery,
42
+ };
43
+ }
44
+ export function resolveCompensatorChannelName(config) {
45
+ return config.defaults.compensator?.channel ?? 'claude';
46
+ }
47
+ /**
48
+ * Resolve the configured compensator channel config (any kind), or undefined
49
+ * when none is configured (the default `claude -p` subprocess fallback applies).
50
+ * Throws only for a missing / abstract reference. Unlike
51
+ * getDispatchableCompensatorChannel this does NOT require a `command`, so it is
52
+ * safe for http compensators.
53
+ */
54
+ export function getCompensatorChannel(config) {
55
+ const channelName = config.defaults.compensator?.channel;
56
+ if (!channelName)
57
+ return undefined;
58
+ const channelConfig = config.channels[channelName];
59
+ if (!channelConfig) {
60
+ throw new Error(`Compensator channel "${channelName}" not found in config`);
61
+ }
62
+ if (channelConfig.abstract) {
63
+ throw new Error(`Compensator channel "${channelName}" is abstract and cannot be dispatched`);
64
+ }
65
+ return channelConfig;
66
+ }
67
+ /** Output parser for the configured compensator channel (any kind); 'default' when none. */
68
+ export function resolveCompensatorOutputParser(config) {
69
+ return getCompensatorChannel(config)?.output_parser ?? 'default';
70
+ }
71
+ export function getDispatchableCompensatorChannel(config, channelName) {
72
+ const channelConfig = config.channels[channelName];
73
+ if (!channelConfig) {
74
+ throw new Error(`Compensator channel "${channelName}" not found in config`);
75
+ }
76
+ if (channelConfig.abstract) {
77
+ throw new Error(`Compensator channel "${channelName}" is abstract and cannot be dispatched`);
78
+ }
79
+ if (!channelConfig.command) {
80
+ throw new Error(`Compensator channel "${channelName}" has no command`);
81
+ }
82
+ return channelConfig;
83
+ }
84
+ function applyPromptWrapper(wrapper, prompt) {
85
+ return wrapper === '{{prompt}}'
86
+ ? prompt
87
+ : wrapper.replaceAll('{{prompt}}', () => prompt);
88
+ }
89
+ /**
90
+ * Resolve the focus-area prompt prefix for a compensating pass.
91
+ */
92
+ export function resolveCompensatorFocus(config, originalChannel) {
93
+ const override = config.defaults.compensator?.channel_focus_map?.[originalChannel];
94
+ if (typeof override === 'string' && override.trim().length > 0)
95
+ return override;
96
+ const builtin = COMPENSATING_FOCUS[originalChannel];
97
+ if (builtin)
98
+ return builtin;
99
+ return `Focus your review on areas typically covered by ${originalChannel}.`
100
+ + ` You are compensating for a missing ${originalChannel} review.`;
101
+ }
13
102
  /**
14
103
  * Determine which channels need compensating passes.
15
104
  * Returns a list of compensating channel descriptors.
16
105
  */
17
- export function getCompensatingChannels(channelStatuses) {
106
+ export function getCompensatingChannels(channelStatuses, compensatorChannel) {
18
107
  const compensating = [];
19
108
  for (const [name, status] of Object.entries(channelStatuses)) {
20
- if (SKIP_COMPENSATION.has(name))
109
+ if (name === compensatorChannel)
21
110
  continue;
22
111
  if (status === 'not_installed'
23
112
  || status === 'auth_failed'
24
113
  || status === 'timeout'
25
114
  || status === 'skipped'
26
115
  || status === 'failed') {
27
- const focus = COMPENSATING_FOCUS[name]
28
- ?? `Focus your review on areas typically covered by ${name}.`
29
- + ` You are compensating for a missing ${name} review.`;
30
116
  compensating.push({
31
117
  originalChannel: name,
32
118
  compensatingName: `compensating-${name}`,
33
- focusPrompt: focus,
34
119
  });
35
120
  }
36
121
  }
@@ -40,16 +125,34 @@ export function getCompensatingChannels(channelStatuses) {
40
125
  * Dispatch compensating passes via claude CLI for unavailable channels.
41
126
  * Each compensating pass uses the same prompt but with a focused preamble.
42
127
  */
43
- export async function dispatchCompensatingPasses(store, jobId, prompt, compensatingChannels, timeout) {
128
+ export async function dispatchCompensatingPasses(store, jobId, prompt, compensatingChannels, config) {
129
+ const compChannel = getCompensatorChannel(config);
130
+ // HTTP compensator: route each pass through the HTTP dispatcher.
131
+ if (compChannel && compChannel.kind === 'http') {
132
+ await Promise.all(compensatingChannels.map((comp) => {
133
+ const focus = resolveCompensatorFocus(config, comp.originalChannel);
134
+ const compensatingPrompt = applyPromptWrapper(compChannel.prompt_wrapper ?? '{{prompt}}', `${focus}\n\n${prompt}`);
135
+ return dispatchHttpChannel(store, jobId, comp.compensatingName, {
136
+ channel: compChannel,
137
+ prompt: compensatingPrompt,
138
+ timeout: compChannel.timeout ?? config.defaults.timeout,
139
+ });
140
+ }));
141
+ return;
142
+ }
143
+ // Subprocess compensator (default `claude -p` or a configured command).
144
+ const dispatch = resolveCompensatorDispatch(config);
44
145
  await Promise.all(compensatingChannels.map((comp) => {
45
- const compensatingPrompt = `${comp.focusPrompt}\n\n${prompt}`;
146
+ const focus = resolveCompensatorFocus(config, comp.originalChannel);
147
+ const compensatingPrompt = applyPromptWrapper(dispatch.prompt_wrapper, `${focus}\n\n${prompt}`);
46
148
  return dispatchChannel(store, jobId, comp.compensatingName, {
47
- command: 'claude -p',
149
+ command: dispatch.command,
48
150
  prompt: compensatingPrompt,
49
- flags: ['--output-format', 'json'],
50
- env: {},
51
- timeout,
52
- stderr: 'capture',
151
+ flags: dispatch.flags,
152
+ env: dispatch.env,
153
+ timeout: dispatch.timeout,
154
+ stderr: dispatch.stderr,
155
+ promptDelivery: dispatch.prompt_delivery,
53
156
  });
54
157
  }));
55
158
  }
@@ -1 +1 @@
1
- {"version":3,"file":"compensator.js","sourceRoot":"","sources":["../../src/core/compensator.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,iBAAiB,CAAA;AAIjD,kFAAkF;AAClF,MAAM,kBAAkB,GAA2B;IACjD,KAAK,EACH,6EAA6E;UAC3E,iEAAiE;UACjE,mDAAmD;IACvD,MAAM,EACJ,mEAAmE;UACjE,4EAA4E;UAC5E,oDAAoD;CACzD,CAAA;AAED,yFAAyF;AACzF,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAA;AAW7C;;;GAGG;AACH,MAAM,UAAU,uBAAuB,CACrC,eAA8C;IAE9C,MAAM,YAAY,GAA0B,EAAE,CAAA;IAE9C,KAAK,MAAM,CAAC,IAAI,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,eAAe,CAAC,EAAE,CAAC;QAC7D,IAAI,iBAAiB,CAAC,GAAG,CAAC,IAAI,CAAC;YAAE,SAAQ;QACzC,IACE,MAAM,KAAK,eAAe;eACvB,MAAM,KAAK,aAAa;eACxB,MAAM,KAAK,SAAS;eACpB,MAAM,KAAK,SAAS;eACpB,MAAM,KAAK,QAAQ,EACtB,CAAC;YACD,MAAM,KAAK,GAAG,kBAAkB,CAAC,IAAI,CAAC;mBACjC,mDAAmD,IAAI,GAAG;sBAC3D,uCAAuC,IAAI,UAAU,CAAA;YACzD,YAAY,CAAC,IAAI,CAAC;gBAChB,eAAe,EAAE,IAAI;gBACrB,gBAAgB,EAAE,gBAAgB,IAAI,EAAE;gBACxC,WAAW,EAAE,KAAK;aACnB,CAAC,CAAA;QACJ,CAAC;IACH,CAAC;IAED,OAAO,YAAY,CAAA;AACrB,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,0BAA0B,CAC9C,KAAe,EACf,KAAa,EACb,MAAc,EACd,oBAA2C,EAC3C,OAAe;IAEf,MAAM,OAAO,CAAC,GAAG,CACf,oBAAoB,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE;QAChC,MAAM,kBAAkB,GAAG,GAAG,IAAI,CAAC,WAAW,OAAO,MAAM,EAAE,CAAA;QAC7D,OAAO,eAAe,CAAC,KAAK,EAAE,KAAK,EAAE,IAAI,CAAC,gBAAgB,EAAE;YAC1D,OAAO,EAAE,WAAW;YACpB,MAAM,EAAE,kBAAkB;YAC1B,KAAK,EAAE,CAAC,iBAAiB,EAAE,MAAM,CAAC;YAClC,GAAG,EAAE,EAAE;YACP,OAAO;YACP,MAAM,EAAE,SAAS;SAClB,CAAC,CAAA;IACJ,CAAC,CAAC,CACH,CAAA;AACH,CAAC"}
1
+ {"version":3,"file":"compensator.js","sourceRoot":"","sources":["../../src/core/compensator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ export interface ConfigChangeReport {
2
+ /** Whether the diff touches `./.mmr.yaml` at all. */
3
+ config_file_changed: boolean;
4
+ /** Paths under `./.mmr/acks/` touched by the diff (added/modified/removed). */
5
+ ack_files_changed: string[];
6
+ }
7
+ /**
8
+ * Inspect a unified diff for changes to MMR trust-relevant files: the project
9
+ * config (`.mmr.yaml`) and any project acks (`.mmr/acks/*`). Callers use this
10
+ * to force `needs-user-decision` when a diff under review proposes new
11
+ * config/acks, so an untrusted PR can't silently change channels or suppress
12
+ * its own findings.
13
+ *
14
+ * The diff is attacker-controllable (`--diff`/stdin), so detection is
15
+ * deliberately liberal: it matches across diff producers (git a/b, --no-prefix,
16
+ * raw diff -u, merge `--cc`, rename/copy) and is case-insensitive. Spurious
17
+ * over-detection only forces a (overridable) user decision — the safe
18
+ * direction — while a miss would silently trust attacker config/acks.
19
+ */
20
+ export declare function detectConfigChanges(diff: string): ConfigChangeReport;
21
+ //# sourceMappingURL=diff-introspect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-introspect.d.ts","sourceRoot":"","sources":["../../src/core/diff-introspect.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,kBAAkB;IACjC,qDAAqD;IACrD,mBAAmB,EAAE,OAAO,CAAA;IAC5B,+EAA+E;IAC/E,iBAAiB,EAAE,MAAM,EAAE,CAAA;CAC5B;AAgBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,kBAAkB,CAWpE"}
@@ -0,0 +1,42 @@
1
+ // Lines that name files in a unified/combined diff, across producers:
2
+ // `git diff` (a/ b/), `git diff --no-prefix`, raw `diff -u` (---/+++ only),
3
+ // merge diffs (`diff --cc`), and rename/copy metadata. We inspect only these
4
+ // header-ish lines (not hunk bodies) for the sentinel paths.
5
+ const HEADER_RE = /^(?:diff --git |diff --cc |diff --combined |--- |\+\+\+ |rename (?:from|to) |copy (?:from|to) )/;
6
+ // `.mmr.yaml` as a path component (start, separator, or quote on each side),
7
+ // case-insensitive so a casing variant on case-insensitive filesystems still
8
+ // trips the gate (over-detection is the safe failure mode here).
9
+ const CONFIG_RE = /(?:^|[\s/"'])\.mmr\.yaml(?:["'\s]|$)/i;
10
+ // Any `.mmr/acks/<file>` occurrence; the match starts at `.mmr` so a leading
11
+ // a//b/ prefix is naturally excluded.
12
+ const ACK_RE = /\.mmr\/acks\/[^\s"']+/gi;
13
+ /**
14
+ * Inspect a unified diff for changes to MMR trust-relevant files: the project
15
+ * config (`.mmr.yaml`) and any project acks (`.mmr/acks/*`). Callers use this
16
+ * to force `needs-user-decision` when a diff under review proposes new
17
+ * config/acks, so an untrusted PR can't silently change channels or suppress
18
+ * its own findings.
19
+ *
20
+ * The diff is attacker-controllable (`--diff`/stdin), so detection is
21
+ * deliberately liberal: it matches across diff producers (git a/b, --no-prefix,
22
+ * raw diff -u, merge `--cc`, rename/copy) and is case-insensitive. Spurious
23
+ * over-detection only forces a (overridable) user decision — the safe
24
+ * direction — while a miss would silently trust attacker config/acks.
25
+ */
26
+ export function detectConfigChanges(diff) {
27
+ const report = { config_file_changed: false, ack_files_changed: [] };
28
+ if (!diff)
29
+ return report;
30
+ const acks = new Set();
31
+ for (const line of diff.split('\n')) {
32
+ if (!HEADER_RE.test(line))
33
+ continue;
34
+ if (CONFIG_RE.test(line))
35
+ report.config_file_changed = true;
36
+ for (const m of line.matchAll(ACK_RE))
37
+ acks.add(m[0]);
38
+ }
39
+ report.ack_files_changed = [...acks];
40
+ return report;
41
+ }
42
+ //# sourceMappingURL=diff-introspect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff-introspect.js","sourceRoot":"","sources":["../../src/core/diff-introspect.ts"],"names":[],"mappings":"AAOA,sEAAsE;AACtE,4EAA4E;AAC5E,6EAA6E;AAC7E,6DAA6D;AAC7D,MAAM,SAAS,GACb,iGAAiG,CAAA;AACnG,6EAA6E;AAC7E,6EAA6E;AAC7E,iEAAiE;AACjE,MAAM,SAAS,GAAG,uCAAuC,CAAA;AACzD,6EAA6E;AAC7E,sCAAsC;AACtC,MAAM,MAAM,GAAG,yBAAyB,CAAA;AAExC;;;;;;;;;;;;GAYG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,MAAM,MAAM,GAAuB,EAAE,mBAAmB,EAAE,KAAK,EAAE,iBAAiB,EAAE,EAAE,EAAE,CAAA;IACxF,IAAI,CAAC,IAAI;QAAE,OAAO,MAAM,CAAA;IACxB,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,KAAK,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACpC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,SAAQ;QACnC,IAAI,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC;YAAE,MAAM,CAAC,mBAAmB,GAAG,IAAI,CAAA;QAC3D,KAAK,MAAM,CAAC,IAAI,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC;YAAE,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA;IACvD,CAAC;IACD,MAAM,CAAC,iBAAiB,GAAG,CAAC,GAAG,IAAI,CAAC,CAAA;IACpC,OAAO,MAAM,CAAA;AACf,CAAC"}
@@ -7,6 +7,13 @@ export interface DispatchOptions {
7
7
  env: Record<string, string>;
8
8
  timeout: number;
9
9
  stderr: 'capture' | 'suppress' | 'passthrough';
10
+ /**
11
+ * How to hand the prompt to the process. 'stdin' (default) pipes it to
12
+ * stdin. 'prompt-file' writes it to a file in the channel dir and passes
13
+ * the path via a {{prompt_file}} placeholder in flags (or appended when no
14
+ * placeholder is present), for CLIs that require the prompt as an arg.
15
+ */
16
+ promptDelivery?: 'stdin' | 'prompt-file';
10
17
  }
11
18
  /** Check whether a channel status represents a terminal (done) state */
12
19
  export declare function isChannelComplete(status: ChannelStatus): boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/core/dispatcher.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAE9C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,aAAa,CAAA;CAC/C;AAyBD,wEAAwE;AACxE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAEhE;AAED,qEAAqE;AACrE,wBAAsB,eAAe,CACnC,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,IAAI,CAAC,CAqIf"}
1
+ {"version":3,"file":"dispatcher.d.ts","sourceRoot":"","sources":["../../src/core/dispatcher.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAA;AAChD,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAE9C,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,MAAM,CAAA;IACd,KAAK,EAAE,MAAM,EAAE,CAAA;IACf,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;IAC3B,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,EAAE,SAAS,GAAG,UAAU,GAAG,aAAa,CAAA;IAC9C;;;;;OAKG;IACH,cAAc,CAAC,EAAE,OAAO,GAAG,aAAa,CAAA;CACzC;AA4BD,wEAAwE;AACxE,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,aAAa,GAAG,OAAO,CAEhE;AAED,qEAAqE;AACrE,wBAAsB,eAAe,CACnC,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,eAAe,GACpB,OAAO,CAAC,IAAI,CAAC,CAsJf"}
@@ -2,6 +2,8 @@ import { spawn } from 'node:child_process';
2
2
  import fs from 'node:fs';
3
3
  import path from 'node:path';
4
4
  import { TERMINAL_STATUSES } from '../types.js';
5
+ /** Placeholder token replaced with the prompt-file path in prompt-file mode. */
6
+ const PROMPT_FILE_PLACEHOLDER = '{{prompt_file}}';
5
7
  /** Track active child PIDs for cleanup on parent exit */
6
8
  const activeChildren = new Set();
7
9
  function cleanupChildren() {
@@ -38,7 +40,20 @@ export async function dispatchChannel(store, jobId, channelName, opts) {
38
40
  const channelsDir = path.join(jobDir, 'channels');
39
41
  // Split multi-word commands (e.g. "claude -p" → ["claude", "-p"])
40
42
  const [cmd, ...cmdArgs] = opts.command.split(/\s+/);
41
- const args = [...cmdArgs, ...opts.flags];
43
+ let args = [...cmdArgs, ...opts.flags];
44
+ // In prompt-file mode, write the prompt to a file in the channel dir and
45
+ // substitute its path for the {{prompt_file}} placeholder (or append it).
46
+ // The prompt is NOT piped to stdin in this mode.
47
+ const promptDelivery = opts.promptDelivery ?? 'stdin';
48
+ if (promptDelivery === 'prompt-file') {
49
+ const promptFile = path.join(channelsDir, `${channelName}.prompt.txt`);
50
+ // Async write: prompts can carry large diffs and channels dispatch in
51
+ // parallel, so avoid blocking the event loop.
52
+ await fs.promises.writeFile(promptFile, opts.prompt);
53
+ args = args.some((a) => a.includes(PROMPT_FILE_PLACEHOLDER))
54
+ ? args.map((a) => a.split(PROMPT_FILE_PLACEHOLDER).join(promptFile))
55
+ : [...args, promptFile];
56
+ }
42
57
  // Update channel to running
43
58
  store.updateChannel(jobId, channelName, {
44
59
  status: 'running',
@@ -61,8 +76,11 @@ export async function dispatchChannel(store, jobId, channelName, opts) {
61
76
  proc.stdin.on('error', () => {
62
77
  // Swallow EPIPE — the close handler will deal with the process exit
63
78
  });
64
- // Write prompt to stdin
65
- proc.stdin.write(opts.prompt);
79
+ // Write prompt to stdin (stdin delivery only; prompt-file mode passes the
80
+ // prompt as an arg). Always end stdin so processes that read it don't hang.
81
+ if (promptDelivery === 'stdin') {
82
+ proc.stdin.write(opts.prompt);
83
+ }
66
84
  proc.stdin.end();
67
85
  // Write PID file
68
86
  const pidFile = path.join(channelsDir, `${channelName}.pid`);
@@ -1 +1 @@
1
- {"version":3,"file":"dispatcher.js","sourceRoot":"","sources":["../../src/core/dispatcher.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"dispatcher.js","sourceRoot":"","sources":["../../src/core/dispatcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,31 @@
1
+ /** Whether a git ref is safe to embed in `git show <ref>:<path>`. */
2
+ export declare function isSafeRef(ref: string): boolean;
3
+ export interface ReadFileAtRefOptions {
4
+ cwd: string;
5
+ ref: string;
6
+ /** Path relative to the repo root, with leading `./` allowed. */
7
+ filePath: string;
8
+ }
9
+ /**
10
+ * Read file contents at a specific Git ref via `git show <ref>:<path>`.
11
+ * Returns `undefined` when the ref or the path does not exist at the ref.
12
+ * Never throws; callers must handle the undefined-fallback case.
13
+ *
14
+ * Fails closed (returns undefined) on an unsafe ref or path rather than
15
+ * trusting callers to pre-validate — this is the §5-decision-1 trust boundary,
16
+ * so the guard lives at the boundary itself.
17
+ */
18
+ export declare function readFileAtRef(opts: ReadFileAtRefOptions): string | undefined;
19
+ export interface ListFilesAtRefOptions {
20
+ cwd: string;
21
+ ref: string;
22
+ /** Repo-relative directory to list, with leading `./` allowed. */
23
+ dirPath: string;
24
+ }
25
+ /**
26
+ * List the file paths (repo-root-relative) under a directory at a Git ref via
27
+ * `git ls-tree`. Returns `[]` on an unsafe ref/path, a missing ref/dir, or any
28
+ * git error. Never throws.
29
+ */
30
+ export declare function listFilesAtRef(opts: ListFilesAtRefOptions): string[];
31
+ //# sourceMappingURL=git-show.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-show.d.ts","sourceRoot":"","sources":["../../src/core/git-show.ts"],"names":[],"mappings":"AAQA,qEAAqE;AACrE,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAS9C;AAQD,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,iEAAiE;IACjE,QAAQ,EAAE,MAAM,CAAA;CACjB;AAED;;;;;;;;GAQG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,oBAAoB,GAAG,MAAM,GAAG,SAAS,CAa5E;AAED,MAAM,WAAW,qBAAqB;IACpC,GAAG,EAAE,MAAM,CAAA;IACX,GAAG,EAAE,MAAM,CAAA;IACX,kEAAkE;IAClE,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,MAAM,EAAE,CAiBpE"}
@@ -0,0 +1,72 @@
1
+ import { execFileSync } from 'node:child_process';
2
+ // Conservative git refname allow-list: letters, digits, . _ - / and the safe
3
+ // relative-rev modifiers ~ ^. Rejects the constructs that could smuggle
4
+ // surprising rev/path syntax into `git show <ref>:<path>` — ':' (the rev:path
5
+ // separator), '..' (ranges), '@{' (reflog), and leading '-'/'/'.
6
+ const SAFE_REF_RE = /^[A-Za-z0-9._/~^-]+$/;
7
+ /** Whether a git ref is safe to embed in `git show <ref>:<path>`. */
8
+ export function isSafeRef(ref) {
9
+ return (SAFE_REF_RE.test(ref) &&
10
+ !ref.includes('..') &&
11
+ !ref.includes('@{') &&
12
+ !ref.startsWith('-') &&
13
+ !ref.startsWith('/') &&
14
+ !ref.endsWith('/'));
15
+ }
16
+ /** Whether a repo-relative path is safe (no rev separator, no traversal). */
17
+ function isSafePath(p) {
18
+ if (p.length === 0 || p.includes(':'))
19
+ return false;
20
+ return !p.split('/').includes('..');
21
+ }
22
+ /**
23
+ * Read file contents at a specific Git ref via `git show <ref>:<path>`.
24
+ * Returns `undefined` when the ref or the path does not exist at the ref.
25
+ * Never throws; callers must handle the undefined-fallback case.
26
+ *
27
+ * Fails closed (returns undefined) on an unsafe ref or path rather than
28
+ * trusting callers to pre-validate — this is the §5-decision-1 trust boundary,
29
+ * so the guard lives at the boundary itself.
30
+ */
31
+ export function readFileAtRef(opts) {
32
+ const cleanedPath = opts.filePath.replace(/^\.\//, '');
33
+ if (!isSafeRef(opts.ref) || !isSafePath(cleanedPath))
34
+ return undefined;
35
+ try {
36
+ return execFileSync('git', ['-C', opts.cwd, 'show', `${opts.ref}:${cleanedPath}`], {
37
+ encoding: 'utf-8',
38
+ stdio: ['ignore', 'pipe', 'ignore'],
39
+ maxBuffer: 10 * 1024 * 1024,
40
+ timeout: 10000,
41
+ });
42
+ }
43
+ catch {
44
+ return undefined;
45
+ }
46
+ }
47
+ /**
48
+ * List the file paths (repo-root-relative) under a directory at a Git ref via
49
+ * `git ls-tree`. Returns `[]` on an unsafe ref/path, a missing ref/dir, or any
50
+ * git error. Never throws.
51
+ */
52
+ export function listFilesAtRef(opts) {
53
+ const cleaned = opts.dirPath.replace(/^\.\//, '').replace(/\/$/, '');
54
+ if (!isSafeRef(opts.ref) || !isSafePath(cleaned))
55
+ return [];
56
+ try {
57
+ const out = execFileSync('git', ['-C', opts.cwd, 'ls-tree', '--name-only', opts.ref, '--', `${cleaned}/`], {
58
+ encoding: 'utf-8',
59
+ stdio: ['ignore', 'pipe', 'ignore'],
60
+ maxBuffer: 10 * 1024 * 1024,
61
+ timeout: 10000,
62
+ });
63
+ return out
64
+ .split('\n')
65
+ .map((line) => line.trim())
66
+ .filter((line) => line.length > 0);
67
+ }
68
+ catch {
69
+ return [];
70
+ }
71
+ }
72
+ //# sourceMappingURL=git-show.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"git-show.js","sourceRoot":"","sources":["../../src/core/git-show.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { JobStore } from './job-store.js';
2
+ import type { HttpChannelParsed } from '../config/schema.js';
3
+ export interface DispatchHttpOptions {
4
+ channel: HttpChannelParsed;
5
+ prompt: string;
6
+ /** Per-channel timeout in seconds. */
7
+ timeout: number;
8
+ }
9
+ /**
10
+ * Dispatch a review prompt to an HTTP (openai-chat) channel.
11
+ *
12
+ * Security invariant (T1-C / §5 decision 1): the API key value read from
13
+ * `process.env[api_key_env]` is used ONLY to build the request header. It is
14
+ * NEVER persisted or logged. Failure diagnostics are SYNTHETIC strings derived
15
+ * from the status code / error name — never the response body, because a
16
+ * misconfigured or hostile endpoint can reflect the Authorization header back
17
+ * in its body, and persisting that would leak the secret to disk.
18
+ */
19
+ export declare function dispatchHttpChannel(store: JobStore, jobId: string, channelName: string, opts: DispatchHttpOptions): Promise<void>;
20
+ //# sourceMappingURL=http-dispatcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-dispatcher.d.ts","sourceRoot":"","sources":["../../src/core/http-dispatcher.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAA;AAG5D,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,iBAAiB,CAAA;IAC1B,MAAM,EAAE,MAAM,CAAA;IACd,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAA;CAChB;AAsCD;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,KAAK,EAAE,QAAQ,EACf,KAAK,EAAE,MAAM,EACb,WAAW,EAAE,MAAM,EACnB,IAAI,EAAE,mBAAmB,GACxB,OAAO,CAAC,IAAI,CAAC,CA0Ef"}
@@ -0,0 +1,125 @@
1
+ /** Status-code → ChannelStatus mapping per §5 decision 8 / T1-C. */
2
+ function classifyStatus(status) {
3
+ if (status === 200)
4
+ return 'completed';
5
+ if (status === 401)
6
+ return 'auth_failed';
7
+ return 'failed';
8
+ }
9
+ /**
10
+ * True when the channel's parser consumes JSON, so we request
11
+ * `response_format: {type:'json_object'}`. `regex-findings` is a text scanner
12
+ * (it runs a RegExp over prose output), so it must NOT force JSON.
13
+ */
14
+ function impliesJsonOutput(parser) {
15
+ if (typeof parser === 'string') {
16
+ return parser === 'default' || parser === 'gemini' || parser === 'doc-conformance';
17
+ }
18
+ return parser.kind === 'unwrap-jsonpath';
19
+ }
20
+ /**
21
+ * Extract the assistant message from an openai-chat completion envelope so the
22
+ * saved channel output matches what a subprocess channel writes (the model's
23
+ * direct text), letting the existing parsers consume it unchanged. Falls back
24
+ * to the raw body if the response is not the expected envelope shape.
25
+ */
26
+ function extractOpenAiContent(body) {
27
+ try {
28
+ const parsed = JSON.parse(body);
29
+ const content = parsed?.choices?.[0]?.message?.content;
30
+ if (typeof content === 'string')
31
+ return content;
32
+ }
33
+ catch {
34
+ // Not JSON / not the expected shape — fall through to the raw body.
35
+ }
36
+ return body;
37
+ }
38
+ /**
39
+ * Dispatch a review prompt to an HTTP (openai-chat) channel.
40
+ *
41
+ * Security invariant (T1-C / §5 decision 1): the API key value read from
42
+ * `process.env[api_key_env]` is used ONLY to build the request header. It is
43
+ * NEVER persisted or logged. Failure diagnostics are SYNTHETIC strings derived
44
+ * from the status code / error name — never the response body, because a
45
+ * misconfigured or hostile endpoint can reflect the Authorization header back
46
+ * in its body, and persisting that would leak the secret to disk.
47
+ */
48
+ export async function dispatchHttpChannel(store, jobId, channelName, opts) {
49
+ const { channel, prompt, timeout } = opts;
50
+ const startedAt = new Date().toISOString();
51
+ store.updateChannel(jobId, channelName, { status: 'running', started_at: startedAt });
52
+ const finalize = (status, logDetail) => {
53
+ // Synthetic diagnostics only — surfaced by the results pipeline via the
54
+ // channel log. NEVER the response body (secret-reflection risk).
55
+ if (logDetail)
56
+ store.saveChannelLog(jobId, channelName, logDetail);
57
+ // Match the subprocess dispatcher's persisted shape: only completed
58
+ // channels carry output_parser (it drives parsing); error paths write
59
+ // status + completed_at only.
60
+ const update = { status, completed_at: new Date().toISOString() };
61
+ if (status === 'completed')
62
+ update.output_parser = channel.output_parser;
63
+ store.updateChannel(jobId, channelName, update);
64
+ };
65
+ const headers = {
66
+ 'content-type': 'application/json',
67
+ ...(channel.headers ?? {}),
68
+ };
69
+ if (channel.api_key_env) {
70
+ const value = process.env[channel.api_key_env];
71
+ if (!value) {
72
+ // Env-var name only — never the value (absent here anyway).
73
+ finalize('auth_failed', `API key env var ${channel.api_key_env} not set`);
74
+ return;
75
+ }
76
+ // Drop any case-variant of the target header from `channel.headers` so the
77
+ // api-key header is authoritative and we never emit a duplicate (fetch
78
+ // implementations normalize header casing inconsistently).
79
+ const target = channel.api_key_header.toLowerCase();
80
+ for (const k of Object.keys(headers)) {
81
+ if (k.toLowerCase() === target)
82
+ delete headers[k];
83
+ }
84
+ headers[channel.api_key_header] = `${channel.api_key_prefix}${value}`;
85
+ }
86
+ const body = {
87
+ model: channel.model,
88
+ messages: [{ role: 'user', content: prompt }],
89
+ };
90
+ if (impliesJsonOutput(channel.output_parser)) {
91
+ body.response_format = { type: 'json_object' };
92
+ }
93
+ const controller = new AbortController();
94
+ const timer = setTimeout(() => controller.abort(), Math.max(0, timeout * 1000));
95
+ try {
96
+ const res = await fetch(channel.endpoint, {
97
+ method: 'POST',
98
+ headers,
99
+ body: JSON.stringify(body),
100
+ signal: controller.signal,
101
+ });
102
+ const status = classifyStatus(res.status);
103
+ if (status === 'completed') {
104
+ // Save the model's direct output (unwrapped from the envelope) so the
105
+ // existing parsers consume it exactly like subprocess stdout.
106
+ store.saveChannelOutput(jobId, channelName, extractOpenAiContent(await res.text()));
107
+ finalize('completed');
108
+ }
109
+ else {
110
+ finalize(status, `HTTP ${res.status}`);
111
+ }
112
+ }
113
+ catch (err) {
114
+ if (err.name === 'AbortError') {
115
+ finalize('timeout', `request timed out after ${timeout}s`);
116
+ }
117
+ else {
118
+ finalize('failed', 'request failed');
119
+ }
120
+ }
121
+ finally {
122
+ clearTimeout(timer);
123
+ }
124
+ }
125
+ //# sourceMappingURL=http-dispatcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"http-dispatcher.js","sourceRoot":"","sources":["../../src/core/http-dispatcher.ts"],"names":[],"mappings":"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"}