@magnusekdahl/parallix 1.0.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 (123) hide show
  1. package/CHANGELOG.md +140 -0
  2. package/LICENSE +661 -0
  3. package/README.md +196 -0
  4. package/config/agents.json +25 -0
  5. package/config/agents.local.json.template +8 -0
  6. package/config/state-map.json +4 -0
  7. package/config/state-map.json.template +31 -0
  8. package/config/workflow.config.schema.json +98 -0
  9. package/data/.gitkeep +0 -0
  10. package/docs/adr/0031-ai-agent-instruction-boundary-and-command-floor.md +114 -0
  11. package/docs/adr/0032-mission-refinement-state-and-usage-budget-signals.md +135 -0
  12. package/docs/adr/0034-module-and-skill-invocation-model.md +202 -0
  13. package/docs/adr/0036-mission-sizing-and-dependency-wave-heuristics.md +79 -0
  14. package/docs/adr/0037-ai-workflow-coordination-architecture.md +162 -0
  15. package/docs/adr/0041-integration-pipeline-gates.md +165 -0
  16. package/docs/adr/0042-workflow-cli-color-rendering-approach.md +106 -0
  17. package/docs/adr/0043-git-target-resolution-strategy.md +185 -0
  18. package/docs/adr/0044-workflow-distribution-model.md +277 -0
  19. package/docs/adr/0045-parallax-branch-model.md +182 -0
  20. package/docs/adr/0046-npm-publish-process-and-security.md +138 -0
  21. package/docs/adr/index.md +20 -0
  22. package/docs/agents.md +212 -0
  23. package/docs/authority-reference.md +298 -0
  24. package/docs/forgejo-setup.md +31 -0
  25. package/docs/migration/extraction.md +61 -0
  26. package/docs/migration/task-classification.md +36 -0
  27. package/docs/operator-setup.md +76 -0
  28. package/docs/readme-rewrite-benchmark.md +188 -0
  29. package/docs/use-cases.md +105 -0
  30. package/examples/README.md +62 -0
  31. package/examples/run-enterprise-tarball-workflow-smoke.sh +257 -0
  32. package/examples/run-verify-env-smoke.sh +40 -0
  33. package/index.js +250 -0
  34. package/lib/README.md +13 -0
  35. package/lib/agents/agents.js +867 -0
  36. package/lib/agents/claude-telemetry.js +233 -0
  37. package/lib/agents/claude.js +139 -0
  38. package/lib/agents/codex-telemetry.js +202 -0
  39. package/lib/agents/codex.js +219 -0
  40. package/lib/agents/limit-hit.js +252 -0
  41. package/lib/agents/mistral-telemetry.js +44 -0
  42. package/lib/agents/mistral.js +68 -0
  43. package/lib/agents/opencode-export.js +110 -0
  44. package/lib/agents/opencode-telemetry.js +356 -0
  45. package/lib/agents/opencode.js +218 -0
  46. package/lib/agents/stage-telemetry.js +37 -0
  47. package/lib/commands/active.js +625 -0
  48. package/lib/commands/checkpoint.js +76 -0
  49. package/lib/commands/config.js +39 -0
  50. package/lib/commands/coverage-gate.js +358 -0
  51. package/lib/commands/diff.js +119 -0
  52. package/lib/commands/draft.js +854 -0
  53. package/lib/commands/handoff.js +501 -0
  54. package/lib/commands/integrate.js +1528 -0
  55. package/lib/commands/mission-start.js +246 -0
  56. package/lib/commands/rebase.js +597 -0
  57. package/lib/commands/repair-handoff.js +227 -0
  58. package/lib/commands/resolve-conflict.js +109 -0
  59. package/lib/commands/review.js +13 -0
  60. package/lib/commands/setup-review.js +13 -0
  61. package/lib/commands/setup.js +3 -0
  62. package/lib/commands/stats-backfill.js +395 -0
  63. package/lib/commands/stats.js +1601 -0
  64. package/lib/commands/status.js +183 -0
  65. package/lib/commands/verify.js +1 -0
  66. package/lib/core/fmt.js +202 -0
  67. package/lib/core/git.js +73 -0
  68. package/lib/core/gitignore.js +110 -0
  69. package/lib/core/mission-utils.js +1017 -0
  70. package/lib/core/persistent-data-migration.js +201 -0
  71. package/lib/core/product-config.js +508 -0
  72. package/lib/core/runtime-matrix.js +82 -0
  73. package/lib/core/spawn-tee.js +173 -0
  74. package/lib/core/state-map.js +89 -0
  75. package/lib/core/storage.js +165 -0
  76. package/lib/core/verification.js +149 -0
  77. package/lib/index.js +77 -0
  78. package/lib/review/rebase.js +163 -0
  79. package/lib/review/review-adapter.js +135 -0
  80. package/lib/review/review-artifacts.js +619 -0
  81. package/lib/review/review-commands.js +1375 -0
  82. package/lib/review/review-events.js +1007 -0
  83. package/lib/review/review-loop.js +1004 -0
  84. package/lib/review/review-polling.js +141 -0
  85. package/lib/review/review-prompts.js +212 -0
  86. package/lib/review/review-state.js +280 -0
  87. package/lib/review/review.js +96 -0
  88. package/lib/tools/backlog.js +680 -0
  89. package/lib/tools/forgejo.js +1585 -0
  90. package/lib/tools/gatekeeper.js +106 -0
  91. package/lib/tools/sessions.js +74 -0
  92. package/lib/tools/setup-review.js +1053 -0
  93. package/package.json +56 -0
  94. package/prompts/act-on-review-verbose.md +20 -0
  95. package/prompts/act-on-review.md +22 -0
  96. package/prompts/draft.md +20 -0
  97. package/prompts/execute.md +24 -0
  98. package/prompts/portfolio.md +30 -0
  99. package/prompts/review-verbose.md +20 -0
  100. package/prompts/review.md +17 -0
  101. package/px.js +236 -0
  102. package/templates/AGENTS-snippet.md +14 -0
  103. package/templates/AGENTS.md.template +34 -0
  104. package/templates/CLAUDE.md.template +27 -0
  105. package/templates/CODEX.md.template +38 -0
  106. package/templates/MISTRAL.md.template +24 -0
  107. package/templates/claude-commands/act-on-review.md +3 -0
  108. package/templates/claude-commands/area-review.md +3 -0
  109. package/templates/claude-commands/draft.md +6 -0
  110. package/templates/claude-commands/execute.md +6 -0
  111. package/templates/claude-commands/integrate.md +4 -0
  112. package/templates/claude-commands/portfolio.md +5 -0
  113. package/templates/claude-commands/review.md +4 -0
  114. package/templates/codex/config.toml +6 -0
  115. package/templates/mission-scaffold.md +39 -0
  116. package/templates/vibe/skills/act-on-review/SKILL.md +16 -0
  117. package/templates/vibe/skills/area-review/SKILL.md +16 -0
  118. package/templates/vibe/skills/draft/SKILL.md +16 -0
  119. package/templates/vibe/skills/execute/SKILL.md +16 -0
  120. package/templates/vibe/skills/integrate/SKILL.md +16 -0
  121. package/templates/vibe/skills/portfolio/SKILL.md +21 -0
  122. package/templates/vibe/skills/review/SKILL.md +16 -0
  123. package/tools/setup-forgejo-docker.sh +84 -0
package/lib/index.js ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Barrel re-export for parallix/lib/.
3
+ *
4
+ * Re-exports all grouped modules under named properties (e.g.
5
+ * `require('../lib').commands.setup`). This file does NOT restore
6
+ * flat subpath imports (e.g. `require('../lib/setup')`) — those
7
+ * entrypoints were removed during the restructure and any remaining
8
+ * consumers should be updated to use the new grouped paths.
9
+ *
10
+ * Directory layout:
11
+ * agents/ — AI agent launchers and session management
12
+ * commands/ — Workflow command handlers (dispatcher entry points)
13
+ * core/ — Shared infrastructure (no command-handler imports)
14
+ * review/ — Review subsystem (artifacts, commands, loop, polling, prompts, state)
15
+ * tools/ — External integrations and supporting workflow libraries
16
+ */
17
+
18
+ 'use strict';
19
+
20
+ // agents/
21
+ module.exports.agents = require('./agents/agents');
22
+ module.exports.claude = require('./agents/claude');
23
+ module.exports.claudeTelemetry = require('./agents/claude-telemetry');
24
+ module.exports.codex = require('./agents/codex');
25
+ module.exports.codexTelemetry = require('./agents/codex-telemetry');
26
+ module.exports.limitHit = require('./agents/limit-hit');
27
+ module.exports.mistral = require('./agents/mistral');
28
+ module.exports.opencode = require('./agents/opencode');
29
+
30
+ // commands/
31
+ module.exports.active = require('./commands/active');
32
+ module.exports.checkpoint = require('./commands/checkpoint');
33
+ module.exports.config = require('./commands/config');
34
+ module.exports.coverageGate = require('./commands/coverage-gate');
35
+ module.exports.diff = require('./commands/diff');
36
+ module.exports.draft = require('./commands/draft');
37
+ module.exports.handoff = require('./commands/handoff');
38
+ module.exports.integrate = require('./commands/integrate');
39
+ module.exports.missionStart = require('./commands/mission-start');
40
+ module.exports.rebase = require('./commands/rebase');
41
+ module.exports.repairHandoff = require('./commands/repair-handoff');
42
+ module.exports.resolveConflict = require('./commands/resolve-conflict');
43
+ module.exports.setup = require('./commands/setup');
44
+ module.exports.stats = require('./commands/stats');
45
+ module.exports.statsBackfill = require('./commands/stats-backfill');
46
+ module.exports.status = require('./commands/status');
47
+ module.exports.verify = require('./commands/verify');
48
+
49
+ // core/
50
+ module.exports.fmt = require('./core/fmt');
51
+ module.exports.git = require('./core/git');
52
+ module.exports.gitignore = require('./core/gitignore');
53
+ module.exports.missionUtils = require('./core/mission-utils');
54
+ module.exports.persistentDataMigration = require('./core/persistent-data-migration');
55
+ module.exports.productConfig = require('./core/product-config');
56
+ module.exports.runtimeMatrix = require('./core/runtime-matrix');
57
+ module.exports.spawnTee = require('./core/spawn-tee');
58
+ module.exports.stateMap = require('./core/state-map');
59
+ module.exports.storage = require('./core/storage');
60
+ module.exports.verification = require('./core/verification');
61
+
62
+ // review/
63
+ module.exports.review = require('./review/review');
64
+ module.exports.reviewArtifacts = require('./review/review-artifacts');
65
+ module.exports.reviewCommands = require('./review/review-commands');
66
+ module.exports.reviewEvents = require('./review/review-events');
67
+ module.exports.reviewLoop = require('./review/review-loop');
68
+ module.exports.reviewPolling = require('./review/review-polling');
69
+ module.exports.reviewPrompts = require('./review/review-prompts');
70
+ module.exports.reviewState = require('./review/review-state');
71
+
72
+ // tools/
73
+ module.exports.backlog = require('./tools/backlog');
74
+ module.exports.forgejo = require('./tools/forgejo');
75
+ module.exports.gatekeeper = require('./tools/gatekeeper');
76
+ module.exports.setupReview = require('./tools/setup-review');
77
+ module.exports.sessions = require('./tools/sessions');
@@ -0,0 +1,163 @@
1
+ /**
2
+ * Rebase helpers for pre-review workflow.
3
+ *
4
+ * Extracted from review-loop.js to break the circular dependency:
5
+ * handoff.js -> review-loop.js -> handoff.js
6
+ *
7
+ * Both handoff.js and review-loop.js import from this module instead.
8
+ */
9
+
10
+ const path = require('path');
11
+ const fmt = require('../core/fmt');
12
+ const { git } = require('../core/git');
13
+ const { resolveWorktree } = require('../core/mission-utils');
14
+ const { isProviderEnabled } = require('./review-adapter');
15
+
16
+ const SHARED_FILE_REBASE_CONFLICT_RE = /shared file(?:\(s\))? require agent-assisted resolution|shared-file-conflicts/i;
17
+
18
+ /**
19
+ * Auto-commit safe mission artifacts before rebase.
20
+ * Only touches mission-generated files, task files, and stats CSV.
21
+ */
22
+ async function commitSafeMissionArtifacts(slug, worktree, {
23
+ taskFile = null,
24
+ gitFn = git,
25
+ log = fmt.log.plain,
26
+ error = fmt.log.plainError,
27
+ isMissionArtifactFn = require('../core/mission-utils').isMissionArtifact,
28
+ isWorkflowGeneratedArtifactFn = require('../core/mission-utils').isWorkflowGeneratedArtifact,
29
+ resolveStatsRelPathFn = () => null
30
+ } = {}) {
31
+ const rootDir = worktree || process.cwd();
32
+ const statusResult = gitFn(['-C', rootDir, 'status', '--porcelain=v1', '-z']);
33
+ if (statusResult.status !== 0 || !statusResult.stdout) {
34
+ return { ok: true, dirty: false };
35
+ }
36
+
37
+ const parsePorcelainZ = (stdout) => {
38
+ const entries = String(stdout).split('\0').filter(Boolean);
39
+ const dirty = [];
40
+ for (let i = 0; i < entries.length; i += 1) {
41
+ const entry = entries[i];
42
+ const xy = entry.slice(0, 2);
43
+ const file = entry.slice(3);
44
+ const record = { xy, file, paths: [file] };
45
+ if ((xy[0] === 'R' || xy[0] === 'C') && i + 1 < entries.length) {
46
+ const source = entries[i + 1];
47
+ record.source = source;
48
+ record.paths.push(source);
49
+ i += 1;
50
+ }
51
+ dirty.push(record);
52
+ }
53
+ return dirty;
54
+ };
55
+
56
+ const dirtyFiles = parsePorcelainZ(statusResult.stdout);
57
+
58
+ const unmerged = dirtyFiles.filter(f => ['DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU'].includes(f.xy));
59
+ if (unmerged.length > 0) {
60
+ error(fmt.status('FAIL', `Cannot auto-commit: unmerged/conflicting files detected:\n${unmerged.map(f => ` - ${f.file}`).join('\n')}`));
61
+ return { ok: false, dirty: true, unsafe: true };
62
+ }
63
+
64
+ const resolvedTaskFile = taskFile ? path.relative(rootDir, taskFile) : null;
65
+ const statsRelPath = resolveStatsRelPathFn(rootDir);
66
+ const isSafeToCommit = (file) =>
67
+ isWorkflowGeneratedArtifactFn(file)
68
+ || isMissionArtifactFn(file, slug, rootDir)
69
+ || (resolvedTaskFile && file === resolvedTaskFile)
70
+ || (statsRelPath && file === statsRelPath);
71
+
72
+ const unsafeFiles = dirtyFiles
73
+ .flatMap(f => f.paths)
74
+ .filter(file => !isSafeToCommit(file));
75
+ if (unsafeFiles.length > 0) {
76
+ error(fmt.status('FAIL', `Cannot auto-commit: dirty files include non-mission paths:`));
77
+ unsafeFiles.forEach(file => error(` - ${file}`));
78
+ return { ok: false, dirty: true, unsafe: true };
79
+ }
80
+
81
+ log(`Auto-committing safe mission artifacts for ${fmt.branch(`mission/${slug}`)} before rebase...`);
82
+ dirtyFiles.forEach(f => {
83
+ log(` - ${f.file}`);
84
+ gitFn(['-C', rootDir, 'add', '--', f.file]);
85
+ });
86
+
87
+ const commitRes = gitFn(['-C', rootDir, 'commit', '-m', `workflow(${slug}): auto-commit mission artifacts before pre-review rebase`]);
88
+ if (commitRes.status === 0) {
89
+ log(fmt.status('PASS', 'Mission artifacts committed.'));
90
+ return { ok: true, dirty: true };
91
+ } else {
92
+ const failureMsg = [commitRes.stderr, commitRes.stdout].filter(Boolean).join('\n').trim();
93
+ error(fmt.status('FAIL', `Failed to commit mission artifacts: ${failureMsg}`));
94
+ return { ok: false, dirty: true };
95
+ }
96
+ }
97
+
98
+ /**
99
+ * Rebase the mission branch onto the latest primary/main branch before launching a reviewer.
100
+ *
101
+ * Always commits safe worktree state first, then rebases via the workflow CLI.
102
+ * In standalone (provider-disabled) mode, skips the rebase after committing.
103
+ */
104
+ async function rebaseBeforeReviewRound(slug, {
105
+ runFn = require('child_process').spawnSync,
106
+ gitFn = git,
107
+ taskFile = null,
108
+ worktree = resolveWorktree(slug) || process.cwd(),
109
+ log = fmt.log.plain,
110
+ error = fmt.log.plainError,
111
+ isReviewProviderEnabledFn = undefined,
112
+ legacyIsForgejoReviewEnabledFn = null,
113
+ isForgejoReviewEnabledFn = null
114
+ } = {}) {
115
+ const cleanup = await commitSafeMissionArtifacts(slug, worktree, { taskFile, gitFn, log, error });
116
+ if (!cleanup.ok) {
117
+ error(fmt.status('WARN', 'Worktree is dirty with unsafe or conflicted files. Rebase may fail.'));
118
+ return { ok: false, sharedFileConflicts: false };
119
+ }
120
+
121
+ const forgejoEnabledFn = isReviewProviderEnabledFn
122
+ || legacyIsForgejoReviewEnabledFn
123
+ || isForgejoReviewEnabledFn
124
+ || isProviderEnabled;
125
+ const forgejoEnabled = forgejoEnabledFn(worktree);
126
+ if (!forgejoEnabled) {
127
+ log(fmt.status('INFO', `Review provider disabled; committed worktree state and skipping pre-review rebase for ${fmt.branch(`mission/${slug}`)}.`));
128
+ return { ok: true, sharedFileConflicts: false };
129
+ }
130
+
131
+ const workflowCli = path.resolve(__dirname, '..', 'index.js');
132
+
133
+ log(`Rebasing ${fmt.branch(`mission/${slug}`)} onto the latest primary branch before reviewer launch...`);
134
+
135
+ const result = runFn(process.execPath, [workflowCli, 'rebase', slug, '--push'], {
136
+ cwd: worktree,
137
+ encoding: 'utf8',
138
+ stdio: ['ignore', 'pipe', 'pipe']
139
+ });
140
+ const output = [result.stdout, result.stderr].filter(Boolean).join('\n').trim();
141
+
142
+ if (result.status === 0) {
143
+ log(fmt.status('PASS', `Pre-review rebase completed for ${fmt.branch(`mission/${slug}`)}.`));
144
+ return { ok: true, sharedFileConflicts: false };
145
+ }
146
+
147
+ if (output) {
148
+ error(output);
149
+ }
150
+ const sharedFileConflicts = SHARED_FILE_REBASE_CONFLICT_RE.test(output);
151
+ if (sharedFileConflicts) {
152
+ error(fmt.status('FAIL', 'Shared-file rebase conflicts detected. Autonomous review loop cannot continue safely.'));
153
+ log(fmt.status('INFO', `Resolve the conflicts in the worktree, then re-run: px review ${slug} --start`));
154
+ } else {
155
+ error(fmt.status('FAIL', `Rebase failed before launching reviewer for ${fmt.branch(`mission/${slug}`)}.`));
156
+ }
157
+ return { ok: false, sharedFileConflicts };
158
+ }
159
+
160
+ module.exports = {
161
+ commitSafeMissionArtifacts,
162
+ rebaseBeforeReviewRound,
163
+ };
@@ -0,0 +1,135 @@
1
+ /**
2
+ * Review Adapter
3
+ *
4
+ * This module is the only review-facing provider boundary. Review code calls
5
+ * this adapter instead of importing Forgejo or product-config directly.
6
+ */
7
+
8
+ const os = require('os');
9
+ const path = require('path');
10
+ const { resolveReviewAdapter, isForgejoReviewEnabled } = require('../core/product-config');
11
+ const { loadAdapterConfig } = require('../core/product-config');
12
+ const forgejo = require('../tools/forgejo');
13
+
14
+ const NOOP_PR_STATUS = Object.freeze({
15
+ exists: false,
16
+ raw: 'Forgejo PR: skipped (review provider is not forgejo).',
17
+ });
18
+
19
+ function getReviewProvider(rootDir = process.cwd()) {
20
+ return resolveReviewAdapter(rootDir).provider || null;
21
+ }
22
+
23
+ function isEnabled(rootDir = process.cwd()) {
24
+ return isForgejoReviewEnabled(rootDir);
25
+ }
26
+
27
+ function isProviderEnabled(rootDir = process.cwd()) {
28
+ return isEnabled(rootDir);
29
+ }
30
+
31
+ function isNoop(rootDir = process.cwd()) {
32
+ const provider = getReviewProvider(rootDir);
33
+ return provider === null || provider === 'none';
34
+ }
35
+
36
+ function isForgejoProvider(rootDir = process.cwd()) {
37
+ return isEnabled(rootDir);
38
+ }
39
+
40
+ function resolveArtifactDir(rootDir = process.cwd()) {
41
+ const review = loadAdapterConfig(rootDir).review || {};
42
+ const configured = typeof review.tmpDir === 'string' && review.tmpDir.trim()
43
+ ? review.tmpDir.trim()
44
+ : null;
45
+ if (!configured) {
46
+ return os.tmpdir();
47
+ }
48
+ return path.isAbsolute(configured) ? configured : path.resolve(rootDir, configured);
49
+ }
50
+
51
+ function withForgejo(rootDir, liveFn, noopValue) {
52
+ return isEnabled(rootDir) ? liveFn() : noopValue;
53
+ }
54
+
55
+ function getPrStatus(branch, rootDir = process.cwd(), options = {}) {
56
+ return withForgejo(rootDir, () => forgejo.getPrStatus(branch, rootDir, options), NOOP_PR_STATUS);
57
+ }
58
+
59
+ function readToken(user, options = {}) {
60
+ const rootDir = options.rootDir || process.cwd();
61
+ return isEnabled(rootDir) ? forgejo.readToken(user) : null;
62
+ }
63
+
64
+ async function getLatestReviewForPr(prNumber, reviewerUser, sinceIso, token, options = {}) {
65
+ if (!token) return null;
66
+ return withForgejo(options.rootDir || process.cwd(), () => forgejo.getLatestReviewForPr(prNumber, reviewerUser, sinceIso, token, options), null);
67
+ }
68
+
69
+ async function getLatestDispositionForPr(prNumber, implementerUser, sinceIso, token, options = {}) {
70
+ if (!token) return null;
71
+ return withForgejo(options.rootDir || process.cwd(), () => forgejo.getLatestDispositionForPr(prNumber, implementerUser, sinceIso, token, options), null);
72
+ }
73
+
74
+ function postComment(branch, token, body, options = {}) {
75
+ return withForgejo(options.rootDir || process.cwd(), () => forgejo.postComment(branch, token, body, options), { ok: true, skipped: true, reason: 'review-provider-disabled' });
76
+ }
77
+
78
+ function postReview(branch, token, outcome, summary, options = {}) {
79
+ return withForgejo(options.rootDir || process.cwd(), () => forgejo.postReview(branch, token, outcome, summary, options), { ok: true, skipped: true, reason: 'review-provider-disabled' });
80
+ }
81
+
82
+ function createPr(branch, user, token, options = {}) {
83
+ return withForgejo(options.rootDir || process.cwd(), () => forgejo.createPr(branch, user, token, options), { ok: true, skipped: true, url: null });
84
+ }
85
+
86
+ function forgejoAvailable(url = process.env.FORGEJO_URL || 'http://localhost:3300', options = {}) {
87
+ return withForgejo(options.rootDir || process.cwd(), () => forgejo.forgejoAvailable(url, options), false);
88
+ }
89
+
90
+ function providerAvailable(url = process.env.FORGEJO_URL || 'http://localhost:3300', options = {}) {
91
+ return forgejoAvailable(url, options);
92
+ }
93
+
94
+ async function getComments(branch, token, options = {}) {
95
+ return withForgejo(options.rootDir || process.cwd(), () => forgejo.getComments(branch, token, options), []);
96
+ }
97
+
98
+ function closePr(branch, token, user, options = {}) {
99
+ return withForgejo(options.rootDir || process.cwd(), () => forgejo.closePr(branch, token, user, options), { ok: true, skipped: true, reason: 'review-provider-disabled' });
100
+ }
101
+
102
+ function resolveForgejoUser(explicitUser) {
103
+ return explicitUser || process.env.FORGEJO_USER || null;
104
+ }
105
+
106
+ function resolveReviewUser(explicitUser) {
107
+ return resolveForgejoUser(explicitUser);
108
+ }
109
+
110
+ function getPrAuthor(branch, token, options = {}) {
111
+ return withForgejo(options.rootDir || process.cwd(), () => forgejo.getPrAuthor(branch, token, options), null);
112
+ }
113
+
114
+ module.exports = {
115
+ getReviewProvider,
116
+ isEnabled,
117
+ isProviderEnabled,
118
+ isNoop,
119
+ isForgejoProvider,
120
+ resolveArtifactDir,
121
+ getPrStatus,
122
+ readToken,
123
+ getLatestReviewForPr,
124
+ getLatestDispositionForPr,
125
+ postComment,
126
+ postReview,
127
+ createPr,
128
+ forgejoAvailable,
129
+ providerAvailable,
130
+ getComments,
131
+ closePr,
132
+ resolveForgejoUser,
133
+ resolveReviewUser,
134
+ getPrAuthor,
135
+ };