@magnusekdahl/parallix 1.2.0 → 1.3.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 (127) hide show
  1. package/index.js +190 -185
  2. package/lib/agents/agents.js +737 -838
  3. package/lib/agents/agents.ts +930 -0
  4. package/lib/agents/claude-telemetry.js +149 -154
  5. package/lib/agents/claude-telemetry.ts +233 -0
  6. package/lib/agents/claude.js +121 -131
  7. package/lib/agents/claude.ts +156 -0
  8. package/lib/agents/codex-telemetry.js +176 -164
  9. package/lib/agents/codex-telemetry.ts +205 -0
  10. package/lib/agents/codex.js +168 -201
  11. package/lib/agents/codex.ts +236 -0
  12. package/lib/agents/limit-hit.js +216 -226
  13. package/lib/agents/limit-hit.ts +262 -0
  14. package/lib/agents/mistral-telemetry.js +7 -10
  15. package/lib/agents/mistral-telemetry.ts +44 -0
  16. package/lib/agents/mistral.js +40 -58
  17. package/lib/agents/mistral.ts +84 -0
  18. package/lib/agents/opencode-export.js +126 -127
  19. package/lib/agents/opencode-export.ts +160 -0
  20. package/lib/agents/opencode-telemetry.js +282 -261
  21. package/lib/agents/opencode-telemetry.ts +350 -0
  22. package/lib/agents/opencode.js +263 -305
  23. package/lib/agents/opencode.ts +370 -0
  24. package/lib/agents/stage-telemetry.js +23 -24
  25. package/lib/agents/stage-telemetry.ts +47 -0
  26. package/lib/commands/active.js +502 -579
  27. package/lib/commands/active.ts +665 -0
  28. package/lib/commands/checkpoint.js +98 -74
  29. package/lib/commands/checkpoint.ts +79 -0
  30. package/lib/commands/config.js +61 -31
  31. package/lib/commands/config.ts +47 -0
  32. package/lib/commands/coverage-gate.js +271 -246
  33. package/lib/commands/coverage-gate.ts +362 -0
  34. package/lib/commands/diff.js +140 -119
  35. package/lib/commands/diff.ts +125 -0
  36. package/lib/commands/draft.js +770 -923
  37. package/lib/commands/draft.ts +1021 -0
  38. package/lib/commands/handoff.js +588 -608
  39. package/lib/commands/handoff.ts +694 -0
  40. package/lib/commands/integrate.js +1332 -1463
  41. package/lib/commands/integrate.ts +1688 -0
  42. package/lib/commands/mission-start.js +279 -237
  43. package/lib/commands/mission-start.ts +263 -0
  44. package/lib/commands/rebase.js +606 -582
  45. package/lib/commands/rebase.ts +635 -0
  46. package/lib/commands/repair-handoff.js +181 -179
  47. package/lib/commands/repair-handoff.ts +228 -0
  48. package/lib/commands/resolve-conflict.js +121 -102
  49. package/lib/commands/resolve-conflict.ts +112 -0
  50. package/lib/commands/review.js +3 -4
  51. package/lib/commands/review.ts +14 -0
  52. package/lib/commands/setup-review.js +4 -5
  53. package/lib/commands/setup-review.ts +14 -0
  54. package/lib/commands/setup.js +3 -3
  55. package/lib/commands/setup.ts +2 -0
  56. package/lib/commands/stats-backfill.js +394 -359
  57. package/lib/commands/stats-backfill.ts +418 -0
  58. package/lib/commands/stats.js +1424 -1665
  59. package/lib/commands/stats.ts +2186 -0
  60. package/lib/commands/status.js +219 -200
  61. package/lib/commands/status.ts +219 -0
  62. package/lib/commands/verify.js +3 -1
  63. package/lib/commands/verify.ts +2 -0
  64. package/lib/core/fmt.js +140 -211
  65. package/lib/core/fmt.ts +189 -0
  66. package/lib/core/git.js +121 -102
  67. package/lib/core/git.ts +146 -0
  68. package/lib/core/gitignore.js +59 -106
  69. package/lib/core/gitignore.ts +103 -0
  70. package/lib/core/mission-utils.js +833 -875
  71. package/lib/core/mission-utils.ts +998 -0
  72. package/lib/core/nels.js +112 -127
  73. package/lib/core/nels.ts +189 -0
  74. package/lib/core/persistent-data-migration.js +247 -205
  75. package/lib/core/persistent-data-migration.ts +255 -0
  76. package/lib/core/product-config.js +400 -476
  77. package/lib/core/product-config.ts +518 -0
  78. package/lib/core/runtime-matrix.js +41 -87
  79. package/lib/core/runtime-matrix.ts +84 -0
  80. package/lib/core/spawn-tee.js +152 -192
  81. package/lib/core/spawn-tee.ts +202 -0
  82. package/lib/core/state-map.js +92 -92
  83. package/lib/core/state-map.ts +115 -0
  84. package/lib/core/storage.js +101 -129
  85. package/lib/core/storage.ts +182 -0
  86. package/lib/core/subagent-limit.js +15 -16
  87. package/lib/core/subagent-limit.ts +25 -0
  88. package/lib/core/verification.js +148 -138
  89. package/lib/core/verification.ts +200 -0
  90. package/lib/index.js +142 -59
  91. package/lib/index.ts +134 -0
  92. package/lib/review/rebase.js +142 -145
  93. package/lib/review/rebase.ts +188 -0
  94. package/lib/review/review-adapter.js +99 -87
  95. package/lib/review/review-adapter.ts +198 -0
  96. package/lib/review/review-artifacts.js +486 -629
  97. package/lib/review/review-artifacts.ts +622 -0
  98. package/lib/review/review-commands.js +1220 -1373
  99. package/lib/review/review-commands.ts +1579 -0
  100. package/lib/review/review-events.js +544 -886
  101. package/lib/review/review-events.ts +954 -0
  102. package/lib/review/review-loop.js +945 -1020
  103. package/lib/review/review-loop.ts +1174 -0
  104. package/lib/review/review-polling.js +86 -115
  105. package/lib/review/review-polling.ts +194 -0
  106. package/lib/review/review-prompts.js +154 -189
  107. package/lib/review/review-prompts.ts +182 -0
  108. package/lib/review/review-state.js +246 -225
  109. package/lib/review/review-state.ts +353 -0
  110. package/lib/review/review.js +63 -84
  111. package/lib/review/review.ts +138 -0
  112. package/lib/tools/backlog.js +694 -680
  113. package/lib/tools/backlog.ts +835 -0
  114. package/lib/tools/forgejo.js +1270 -1405
  115. package/lib/tools/forgejo.ts +1793 -0
  116. package/lib/tools/gatekeeper.js +106 -96
  117. package/lib/tools/gatekeeper.ts +124 -0
  118. package/lib/tools/redgreen.js +200 -181
  119. package/lib/tools/redgreen.ts +221 -0
  120. package/lib/tools/sessions.js +58 -52
  121. package/lib/tools/sessions.ts +81 -0
  122. package/lib/tools/setup-review.js +902 -975
  123. package/lib/tools/setup-review.ts +1152 -0
  124. package/package.json +7 -5
  125. package/prompts/draft.md +1 -0
  126. package/prompts/portfolio.md +1 -1
  127. package/px.js +226 -210
@@ -1,124 +1,134 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const fmt = require('../core/fmt');
4
-
5
- const { findMissionDir, findCheckpoints, missionDirForSlug, missionBranchName } = require('../core/mission-utils');
6
- const { resolveTaskFile, getTaskStorage } = require('./backlog');
7
- const { readToken, postReview } = require('./forgejo');
8
-
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.DEFAULT_GATEKEEPER_USER = void 0;
40
+ exports.checkMandatoryFiles = checkMandatoryFiles;
41
+ exports.buildPushbackBody = buildPushbackBody;
42
+ exports.runGatekeeper = runGatekeeper;
43
+ const fs_1 = __importDefault(require("fs"));
44
+ const path_1 = __importDefault(require("path"));
45
+ const fmt = __importStar(require("../core/fmt.js"));
46
+ const mission_utils_js_1 = require("../core/mission-utils.js");
47
+ const backlog_js_1 = require("./backlog.js");
48
+ const forgejo_js_1 = require("./forgejo.js");
9
49
  const DEFAULT_GATEKEEPER_USER = 'forgejo-gatekeeper';
10
-
50
+ exports.DEFAULT_GATEKEEPER_USER = DEFAULT_GATEKEEPER_USER;
11
51
  // Pre-review pushback bot. Runs after performHandoff has created/updated the
12
52
  // PR and before the human/agent reviewer engages, so missing mandatory
13
53
  // artifacts surface as a request-changes review instead of consuming a
14
54
  // reviewer cycle. Returns { ok, missing, skipped, posted } so callers can
15
55
  // surface a clear log line without needing to re-derive state.
16
-
17
56
  /**
18
57
  * @param {string} slug
19
58
  * @param {{rootDir?: string, findMissionDirFn?: Function, findCheckpointsFn?: Function, resolveTaskFileFn?: Function}} [options]
20
59
  * @returns {{ok: boolean, missing: string[]}}
21
60
  */
22
61
  function checkMandatoryFiles(slug, options = {}) {
23
- const {
24
- rootDir = process.cwd(),
25
- findMissionDirFn = findMissionDir,
26
- findCheckpointsFn = findCheckpoints,
27
- resolveTaskFileFn = resolveTaskFile
28
- } = options;
29
-
30
- const missing = [];
31
- const expectedMissionDir = missionDirForSlug(rootDir, slug);
32
- const expectedMissionRel = path.relative(rootDir, expectedMissionDir).split(path.sep).join('/');
33
-
34
- const missionDir = findMissionDirFn(slug, rootDir);
35
- const missionPath = missionDir ? path.join(missionDir, 'MISSION.md') : null;
36
- if (!missionPath || !fs.existsSync(missionPath)) {
37
- missing.push(`${expectedMissionRel}/MISSION.md`);
38
- }
39
-
40
- if (missionDir) {
41
- const checkpoints = findCheckpointsFn(missionDir);
42
- if (!checkpoints || checkpoints.length === 0) {
43
- missing.push(`${expectedMissionRel}/CP-*.md (at least one checkpoint document)`);
62
+ const { rootDir = process.cwd(), findMissionDirFn = mission_utils_js_1.findMissionDir, findCheckpointsFn = mission_utils_js_1.findCheckpoints, resolveTaskFileFn = backlog_js_1.resolveTaskFile } = options;
63
+ const missing = [];
64
+ const expectedMissionDir = (0, mission_utils_js_1.missionDirForSlug)(rootDir, slug);
65
+ const expectedMissionRel = path_1.default.relative(rootDir, expectedMissionDir).split(path_1.default.sep).join('/');
66
+ const missionDir = findMissionDirFn(slug, rootDir);
67
+ const missionPath = missionDir ? path_1.default.join(missionDir, 'MISSION.md') : null;
68
+ if (!missionPath || !fs_1.default.existsSync(missionPath)) {
69
+ missing.push(`${expectedMissionRel}/MISSION.md`);
70
+ }
71
+ if (missionDir) {
72
+ const checkpoints = findCheckpointsFn(missionDir);
73
+ if (!checkpoints || checkpoints.length === 0) {
74
+ missing.push(`${expectedMissionRel}/CP-*.md (at least one checkpoint document)`);
75
+ }
76
+ }
77
+ else {
78
+ missing.push(`${expectedMissionRel}/CP-*.md (at least one checkpoint document)`);
79
+ }
80
+ const taskResolution = resolveTaskFileFn(slug, rootDir);
81
+ const missionArtifactsPresent = Boolean(missionPath && fs_1.default.existsSync(missionPath) && missionDir && findCheckpointsFn(missionDir).length > 0);
82
+ if ((!taskResolution || !taskResolution.ok) && !missionArtifactsPresent) {
83
+ const { tasksDir } = (0, backlog_js_1.getTaskStorage)(rootDir);
84
+ const taskDirRel = path_1.default.relative(rootDir, tasksDir).split(path_1.default.sep).join('/');
85
+ missing.push(`${taskDirRel}/${slug} - *.md`);
44
86
  }
45
- } else {
46
- missing.push(`${expectedMissionRel}/CP-*.md (at least one checkpoint document)`);
47
- }
48
-
49
- const taskResolution = resolveTaskFileFn(slug, rootDir);
50
- const missionArtifactsPresent = Boolean(missionPath && fs.existsSync(missionPath) && missionDir && findCheckpointsFn(missionDir).length > 0);
51
- if ((!taskResolution || !taskResolution.ok) && !missionArtifactsPresent) {
52
- const { tasksDir } = getTaskStorage(rootDir);
53
- const taskDirRel = path.relative(rootDir, tasksDir).split(path.sep).join('/');
54
- missing.push(`${taskDirRel}/${slug} - *.md`);
55
- }
56
-
57
- return { ok: missing.length === 0, missing };
87
+ return { ok: missing.length === 0, missing };
58
88
  }
59
-
60
89
  /**
61
90
  * @param {string} slug
62
91
  * @param {string[]} missing
63
92
  * @returns {string}
64
93
  */
65
94
  function buildPushbackBody(slug, missing) {
66
- const bullets = missing.map(item => `- ${item}`).join('\n');
67
- return [
68
- `**Pre-review gatekeeper: missing mandatory artifacts for \`${slug}\`.**`,
69
- '',
70
- 'The following files are required before a reviewer engages but were not found on this branch:',
71
- '',
72
- bullets,
73
- '',
74
- 'Push the missing artifacts and re-request review. This comment is automated; once the artifacts are present the gatekeeper will not block again.'
75
- ].join('\n');
95
+ const bullets = missing.map(item => `- ${item}`).join('\n');
96
+ return [
97
+ `**Pre-review gatekeeper: missing mandatory artifacts for \`${slug}\`.**`,
98
+ '',
99
+ 'The following files are required before a reviewer engages but were not found on this branch:',
100
+ '',
101
+ bullets,
102
+ '',
103
+ 'Push the missing artifacts and re-request review. This comment is automated; once the artifacts are present the gatekeeper will not block again.'
104
+ ].join('\n');
76
105
  }
77
-
78
106
  /**
79
107
  * @param {string} slug
80
108
  * @param {{rootDir?: string, branch?: string, user?: string, log?: Function, readTokenFn?: Function, postReviewFn?: Function, checkFn?: Function}} [options]
81
109
  * @returns {{ok: boolean, missing: string[], skipped: boolean, posted: boolean}}
82
110
  */
83
111
  function runGatekeeper(slug, options = {}) {
84
- const {
85
- rootDir = process.cwd(),
86
- user = process.env.FORGEJO_GATEKEEPER_USER || DEFAULT_GATEKEEPER_USER,
87
- log = fmt.log.plain,
88
- readTokenFn = readToken,
89
- postReviewFn = postReview,
90
- checkFn = checkMandatoryFiles
91
- } = options;
92
- const branch = options.branch || missionBranchName(slug, rootDir);
93
-
94
- const check = checkFn(slug, { rootDir });
95
- if (check.ok) {
96
- log(fmt.status('INFO', `Gatekeeper: all mandatory artifacts present for ${fmt.slug(slug)}.`));
97
- return { ok: true, missing: [], skipped: false, posted: false };
98
- }
99
-
100
- const token = readTokenFn(user);
101
- if (!token) {
102
- log(fmt.status('WARN', `Gatekeeper: no Forgejo token for "${fmt.agent(user)}"; skipping pushback for ${fmt.slug(slug)}. Missing: ${check.missing.join(', ')}.`));
103
- return { ok: false, missing: check.missing, skipped: true, posted: false };
104
- }
105
-
106
- const body = buildPushbackBody(slug, check.missing);
107
- log(fmt.status('INFO', `Gatekeeper: posting request-changes pushback on ${fmt.branch(branch)} as ${fmt.agent(user)}. Missing: ${check.missing.join(', ')}.`));
108
- const result = postReviewFn(branch, token, 'request-changes', body);
109
-
110
- if (!result || !result.ok) {
111
- const detail = result && result.error ? result.error : 'unknown postReview failure';
112
- log(fmt.status('WARN', `Gatekeeper: pushback post failed for ${fmt.slug(slug)}: ${detail}.`));
113
- return { ok: false, missing: check.missing, skipped: false, posted: false };
114
- }
115
-
116
- return { ok: false, missing: check.missing, skipped: false, posted: true };
112
+ const { rootDir = process.cwd(), user = process.env.FORGEJO_GATEKEEPER_USER || DEFAULT_GATEKEEPER_USER, log = fmt.log.plain, readTokenFn = forgejo_js_1.readToken, postReviewFn = forgejo_js_1.postReview, checkFn = checkMandatoryFiles } = options;
113
+ const branch = options.branch || (0, mission_utils_js_1.missionBranchName)(slug, rootDir);
114
+ const check = checkFn(slug, { rootDir });
115
+ if (check.ok) {
116
+ log(fmt.status('INFO', `Gatekeeper: all mandatory artifacts present for ${fmt.slug(slug)}.`));
117
+ return { ok: true, missing: [], skipped: false, posted: false };
118
+ }
119
+ const token = readTokenFn(user);
120
+ if (!token) {
121
+ log(fmt.status('WARN', `Gatekeeper: no Forgejo token for "${fmt.agent(user)}"; skipping pushback for ${fmt.slug(slug)}. Missing: ${check.missing.join(', ')}.`));
122
+ return { ok: false, missing: check.missing, skipped: true, posted: false };
123
+ }
124
+ const body = buildPushbackBody(slug, check.missing);
125
+ log(fmt.status('INFO', `Gatekeeper: posting request-changes pushback on ${fmt.branch(branch)} as ${fmt.agent(user)}. Missing: ${check.missing.join(', ')}.`));
126
+ const result = postReviewFn(branch, token, 'request-changes', body);
127
+ if (!result || !result.ok) {
128
+ const detail = result && result.error ? result.error : 'unknown postReview failure';
129
+ log(fmt.status('WARN', `Gatekeeper: pushback post failed for ${fmt.slug(slug)}: ${detail}.`));
130
+ return { ok: false, missing: check.missing, skipped: false, posted: false };
131
+ }
132
+ return { ok: false, missing: check.missing, skipped: false, posted: true };
117
133
  }
118
-
119
- module.exports = {
120
- DEFAULT_GATEKEEPER_USER,
121
- checkMandatoryFiles,
122
- buildPushbackBody,
123
- runGatekeeper
124
- };
134
+ ;
@@ -0,0 +1,124 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import * as fmt from '../core/fmt.js';
4
+
5
+ import { findMissionDir, findCheckpoints, missionDirForSlug, missionBranchName } from '../core/mission-utils.js';
6
+ import { resolveTaskFile, getTaskStorage } from './backlog.js';
7
+ import { readToken, postReview } from './forgejo.js';
8
+
9
+ const DEFAULT_GATEKEEPER_USER = 'forgejo-gatekeeper';
10
+
11
+ // Pre-review pushback bot. Runs after performHandoff has created/updated the
12
+ // PR and before the human/agent reviewer engages, so missing mandatory
13
+ // artifacts surface as a request-changes review instead of consuming a
14
+ // reviewer cycle. Returns { ok, missing, skipped, posted } so callers can
15
+ // surface a clear log line without needing to re-derive state.
16
+
17
+ /**
18
+ * @param {string} slug
19
+ * @param {{rootDir?: string, findMissionDirFn?: Function, findCheckpointsFn?: Function, resolveTaskFileFn?: Function}} [options]
20
+ * @returns {{ok: boolean, missing: string[]}}
21
+ */
22
+ function checkMandatoryFiles(slug: string, options: { rootDir?: string, findMissionDirFn?: Function, findCheckpointsFn?: Function, resolveTaskFileFn?: Function } = {} as any) {
23
+ const {
24
+ rootDir = process.cwd(),
25
+ findMissionDirFn = findMissionDir,
26
+ findCheckpointsFn = findCheckpoints,
27
+ resolveTaskFileFn = resolveTaskFile
28
+ } = options;
29
+
30
+ const missing = [];
31
+ const expectedMissionDir = missionDirForSlug(rootDir, slug);
32
+ const expectedMissionRel = path.relative(rootDir, expectedMissionDir).split(path.sep).join('/');
33
+
34
+ const missionDir = findMissionDirFn(slug, rootDir);
35
+ const missionPath = missionDir ? path.join(missionDir, 'MISSION.md') : null;
36
+ if (!missionPath || !fs.existsSync(missionPath)) {
37
+ missing.push(`${expectedMissionRel}/MISSION.md`);
38
+ }
39
+
40
+ if (missionDir) {
41
+ const checkpoints = findCheckpointsFn(missionDir);
42
+ if (!checkpoints || checkpoints.length === 0) {
43
+ missing.push(`${expectedMissionRel}/CP-*.md (at least one checkpoint document)`);
44
+ }
45
+ } else {
46
+ missing.push(`${expectedMissionRel}/CP-*.md (at least one checkpoint document)`);
47
+ }
48
+
49
+ const taskResolution = resolveTaskFileFn(slug, rootDir);
50
+ const missionArtifactsPresent = Boolean(missionPath && fs.existsSync(missionPath) && missionDir && findCheckpointsFn(missionDir).length > 0);
51
+ if ((!taskResolution || !taskResolution.ok) && !missionArtifactsPresent) {
52
+ const { tasksDir } = getTaskStorage(rootDir);
53
+ const taskDirRel = path.relative(rootDir, tasksDir).split(path.sep).join('/');
54
+ missing.push(`${taskDirRel}/${slug} - *.md`);
55
+ }
56
+
57
+ return { ok: missing.length === 0, missing };
58
+ }
59
+
60
+ /**
61
+ * @param {string} slug
62
+ * @param {string[]} missing
63
+ * @returns {string}
64
+ */
65
+ function buildPushbackBody(slug: string, missing: string[]): string {
66
+ const bullets = missing.map(item => `- ${item}`).join('\n');
67
+ return [
68
+ `**Pre-review gatekeeper: missing mandatory artifacts for \`${slug}\`.**`,
69
+ '',
70
+ 'The following files are required before a reviewer engages but were not found on this branch:',
71
+ '',
72
+ bullets,
73
+ '',
74
+ 'Push the missing artifacts and re-request review. This comment is automated; once the artifacts are present the gatekeeper will not block again.'
75
+ ].join('\n');
76
+ }
77
+
78
+ /**
79
+ * @param {string} slug
80
+ * @param {{rootDir?: string, branch?: string, user?: string, log?: Function, readTokenFn?: Function, postReviewFn?: Function, checkFn?: Function}} [options]
81
+ * @returns {{ok: boolean, missing: string[], skipped: boolean, posted: boolean}}
82
+ */
83
+ function runGatekeeper(slug: string, options: { rootDir?: string, branch?: string, user?: string, log?: Function, readTokenFn?: Function, postReviewFn?: Function, checkFn?: Function } = {} as any) {
84
+ const {
85
+ rootDir = process.cwd(),
86
+ user = process.env.FORGEJO_GATEKEEPER_USER || DEFAULT_GATEKEEPER_USER,
87
+ log = fmt.log.plain,
88
+ readTokenFn = readToken,
89
+ postReviewFn = postReview,
90
+ checkFn = checkMandatoryFiles
91
+ } = options;
92
+ const branch = options.branch || missionBranchName(slug, rootDir);
93
+
94
+ const check = checkFn(slug, { rootDir });
95
+ if (check.ok) {
96
+ log(fmt.status('INFO', `Gatekeeper: all mandatory artifacts present for ${fmt.slug(slug)}.`));
97
+ return { ok: true, missing: [], skipped: false, posted: false };
98
+ }
99
+
100
+ const token = readTokenFn(user);
101
+ if (!token) {
102
+ log(fmt.status('WARN', `Gatekeeper: no Forgejo token for "${fmt.agent(user)}"; skipping pushback for ${fmt.slug(slug)}. Missing: ${check.missing.join(', ')}.`));
103
+ return { ok: false, missing: check.missing, skipped: true, posted: false };
104
+ }
105
+
106
+ const body = buildPushbackBody(slug, check.missing);
107
+ log(fmt.status('INFO', `Gatekeeper: posting request-changes pushback on ${fmt.branch(branch)} as ${fmt.agent(user)}. Missing: ${check.missing.join(', ')}.`));
108
+ const result = postReviewFn(branch, token, 'request-changes', body);
109
+
110
+ if (!result || !result.ok) {
111
+ const detail = result && result.error ? result.error : 'unknown postReview failure';
112
+ log(fmt.status('WARN', `Gatekeeper: pushback post failed for ${fmt.slug(slug)}: ${detail}.`));
113
+ return { ok: false, missing: check.missing, skipped: false, posted: false };
114
+ }
115
+
116
+ return { ok: false, missing: check.missing, skipped: false, posted: true };
117
+ }
118
+
119
+
120
+ export { DEFAULT_GATEKEEPER_USER };
121
+ export { checkMandatoryFiles };
122
+ export { buildPushbackBody };
123
+ export { runGatekeeper };
124
+ ;