@magnusekdahl/parallix 1.2.1 → 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 (79) hide show
  1. package/index.js +190 -185
  2. package/lib/commands/active.js +502 -579
  3. package/lib/commands/active.ts +665 -0
  4. package/lib/commands/checkpoint.js +98 -74
  5. package/lib/commands/checkpoint.ts +79 -0
  6. package/lib/commands/config.js +61 -31
  7. package/lib/commands/config.ts +47 -0
  8. package/lib/commands/coverage-gate.js +271 -246
  9. package/lib/commands/coverage-gate.ts +362 -0
  10. package/lib/commands/diff.js +140 -119
  11. package/lib/commands/diff.ts +125 -0
  12. package/lib/commands/draft.js +770 -923
  13. package/lib/commands/draft.ts +1021 -0
  14. package/lib/commands/handoff.js +588 -608
  15. package/lib/commands/handoff.ts +694 -0
  16. package/lib/commands/integrate.js +1331 -1468
  17. package/lib/commands/integrate.ts +1688 -0
  18. package/lib/commands/mission-start.js +279 -237
  19. package/lib/commands/mission-start.ts +263 -0
  20. package/lib/commands/rebase.js +606 -582
  21. package/lib/commands/rebase.ts +635 -0
  22. package/lib/commands/repair-handoff.js +181 -179
  23. package/lib/commands/repair-handoff.ts +228 -0
  24. package/lib/commands/resolve-conflict.js +121 -102
  25. package/lib/commands/resolve-conflict.ts +112 -0
  26. package/lib/commands/review.js +3 -4
  27. package/lib/commands/review.ts +14 -0
  28. package/lib/commands/setup-review.js +4 -5
  29. package/lib/commands/setup-review.ts +14 -0
  30. package/lib/commands/setup.js +3 -3
  31. package/lib/commands/setup.ts +2 -0
  32. package/lib/commands/stats-backfill.js +394 -359
  33. package/lib/commands/stats-backfill.ts +418 -0
  34. package/lib/commands/stats.js +1424 -1665
  35. package/lib/commands/stats.ts +2186 -0
  36. package/lib/commands/status.js +219 -200
  37. package/lib/commands/status.ts +219 -0
  38. package/lib/commands/verify.js +3 -1
  39. package/lib/commands/verify.ts +2 -0
  40. package/lib/core/nels.js +112 -127
  41. package/lib/core/nels.ts +189 -0
  42. package/lib/core/subagent-limit.js +15 -16
  43. package/lib/core/subagent-limit.ts +25 -0
  44. package/lib/index.js +142 -59
  45. package/lib/index.ts +134 -0
  46. package/lib/review/rebase.js +142 -145
  47. package/lib/review/rebase.ts +188 -0
  48. package/lib/review/review-adapter.js +99 -87
  49. package/lib/review/review-adapter.ts +198 -0
  50. package/lib/review/review-artifacts.js +486 -629
  51. package/lib/review/review-artifacts.ts +622 -0
  52. package/lib/review/review-commands.js +1220 -1373
  53. package/lib/review/review-commands.ts +1579 -0
  54. package/lib/review/review-events.js +544 -886
  55. package/lib/review/review-events.ts +954 -0
  56. package/lib/review/review-loop.js +945 -1020
  57. package/lib/review/review-loop.ts +1174 -0
  58. package/lib/review/review-polling.js +86 -115
  59. package/lib/review/review-polling.ts +194 -0
  60. package/lib/review/review-prompts.js +154 -189
  61. package/lib/review/review-prompts.ts +182 -0
  62. package/lib/review/review-state.js +246 -225
  63. package/lib/review/review-state.ts +353 -0
  64. package/lib/review/review.js +63 -84
  65. package/lib/review/review.ts +138 -0
  66. package/lib/tools/backlog.js +694 -680
  67. package/lib/tools/backlog.ts +835 -0
  68. package/lib/tools/forgejo.js +1270 -1405
  69. package/lib/tools/forgejo.ts +1793 -0
  70. package/lib/tools/gatekeeper.js +106 -96
  71. package/lib/tools/gatekeeper.ts +124 -0
  72. package/lib/tools/redgreen.js +200 -181
  73. package/lib/tools/redgreen.ts +221 -0
  74. package/lib/tools/sessions.js +58 -52
  75. package/lib/tools/sessions.ts +81 -0
  76. package/lib/tools/setup-review.js +902 -975
  77. package/lib/tools/setup-review.ts +1152 -0
  78. package/package.json +4 -4
  79. package/px.js +226 -208
@@ -1,3 +1,4 @@
1
+ "use strict";
1
2
  /**
2
3
  * Assemble review and act-on-review prompts for the autonomous review loop.
3
4
  * Owned by the Node workflow harness (ADR 0037 / task-089).
@@ -6,61 +7,78 @@
6
7
  * (same pattern as active.js / draft.js). Full verbose prompts are kept
7
8
  * for --dry-run diagnostics and human/manual invocation.
8
9
  */
9
-
10
- const fs = require('fs');
11
- const path = require('path');
12
- const { getMissionYear, getPrimaryBranch, missionPathForSlug } = require('../core/mission-utils');
13
- const { resolveArtifactDir } = require('./review-artifacts');
14
-
10
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ var desc = Object.getOwnPropertyDescriptor(m, k);
13
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
14
+ desc = { enumerable: true, get: function() { return m[k]; } };
15
+ }
16
+ Object.defineProperty(o, k2, desc);
17
+ }) : (function(o, m, k, k2) {
18
+ if (k2 === undefined) k2 = k;
19
+ o[k2] = m[k];
20
+ }));
21
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
22
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
23
+ }) : function(o, v) {
24
+ o["default"] = v;
25
+ });
26
+ var __importStar = (this && this.__importStar) || (function () {
27
+ var ownKeys = function(o) {
28
+ ownKeys = Object.getOwnPropertyNames || function (o) {
29
+ var ar = [];
30
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
31
+ return ar;
32
+ };
33
+ return ownKeys(o);
34
+ };
35
+ return function (mod) {
36
+ if (mod && mod.__esModule) return mod;
37
+ var result = {};
38
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
39
+ __setModuleDefault(result, mod);
40
+ return result;
41
+ };
42
+ })();
43
+ Object.defineProperty(exports, "__esModule", { value: true });
44
+ exports.PROMPT_ENTRYPOINTS = void 0;
45
+ exports.reviewEntrypoint = reviewEntrypoint;
46
+ exports.actOnReviewEntrypoint = actOnReviewEntrypoint;
47
+ exports.buildReviewPrompt = buildReviewPrompt;
48
+ exports.buildActOnReviewPrompt = buildActOnReviewPrompt;
49
+ exports.buildCompactReviewPrompt = buildCompactReviewPrompt;
50
+ exports.buildCompactActOnReviewPrompt = buildCompactActOnReviewPrompt;
51
+ const fs = __importStar(require("fs"));
52
+ const path = __importStar(require("path"));
53
+ const mission_utils_js_1 = require("../core/mission-utils.js");
54
+ const review_artifacts_js_1 = require("./review-artifacts.js");
15
55
  const REVIEW_PROMPT_PATH = path.join(__dirname, '..', '..', 'prompts', 'review.md');
16
56
  const ACT_ON_REVIEW_PROMPT_PATH = path.join(__dirname, '..', '..', 'prompts', 'act-on-review.md');
17
57
  const REVIEW_VERBOSE_PROMPT_PATH = path.join(__dirname, '..', '..', 'prompts', 'review-verbose.md');
18
58
  const ACT_ON_REVIEW_VERBOSE_PROMPT_PATH = path.join(__dirname, '..', '..', 'prompts', 'act-on-review-verbose.md');
19
-
20
- /**
21
- * Slash-command / entrypoint used to invoke the review skill for each agent family.
22
- * Moved from scripts/autonomous-review.sh prompt_entrypoint_for() into Node.
23
- */
24
- /**
25
- * @typedef {{review: string, actOnReview: string}} PromptEntry
26
- * @typedef {{codex: PromptEntry, claude: PromptEntry, mistral: PromptEntry, custom: PromptEntry, autonomous: PromptEntry}} PromptEntrypoints
27
- */
28
-
29
- /** @type {PromptEntrypoints} */
30
- const PROMPT_ENTRYPOINTS = {
31
- codex: { review: '$review all', actOnReview: '$act-on-review' },
32
- claude: { review: '/review all', actOnReview: '/act-on-review' },
33
- mistral: { review: '$review all', actOnReview: '$act-on-review' },
34
- custom: { review: '$review all', actOnReview: '$act-on-review' },
35
- autonomous: { review: '$review all', actOnReview: '$act-on-review' }
59
+ exports.PROMPT_ENTRYPOINTS = {
60
+ codex: { review: '$review all', actOnReview: '$act-on-review' },
61
+ claude: { review: '/review all', actOnReview: '/act-on-review' },
62
+ mistral: { review: '$review all', actOnReview: '/act-on-review' },
63
+ custom: { review: '$review all', actOnReview: '/act-on-review' },
64
+ autonomous: { review: '$review all', actOnReview: '/act-on-review' }
36
65
  };
37
-
38
66
  /** @param {string} agent */
39
67
  function reviewEntrypoint(agent) {
40
- const entry = /** @type {PromptEntry} */ (/** @type {{[key: string]: PromptEntry}} */ (PROMPT_ENTRYPOINTS)[agent]);
41
- if (!entry) {throw new Error(`Unknown agent family for review entrypoint: ${agent}`);}
42
- return entry.review;
68
+ const entry = exports.PROMPT_ENTRYPOINTS[agent];
69
+ if (!entry) {
70
+ throw new Error(`Unknown agent family for review entrypoint: ${agent}`);
71
+ }
72
+ return entry.review;
43
73
  }
44
-
45
74
  /** @param {string} agent */
46
75
  function actOnReviewEntrypoint(agent) {
47
- const entry = /** @type {PromptEntry} */ (/** @type {{[key: string]: PromptEntry}} */ (PROMPT_ENTRYPOINTS)[agent]);
48
- if (!entry) {throw new Error(`Unknown agent family for act-on-review entrypoint: ${agent}`);}
49
- return entry.actOnReview;
76
+ const entry = exports.PROMPT_ENTRYPOINTS[agent];
77
+ if (!entry) {
78
+ throw new Error(`Unknown agent family for act-on-review entrypoint: ${agent}`);
79
+ }
80
+ return entry.actOnReview;
50
81
  }
51
-
52
- /**
53
- * Build the prompt sent to the reviewer agent for a given round.
54
- *
55
- * @param {object} opts
56
- * @param {string} opts.reviewer - Reviewer agent family (codex|claude)
57
- * @param {string} opts.branch - Mission branch name (e.g. 'mission/task-089')
58
- * @param {string} opts.implementer - Implementer agent family
59
- * @param {string} opts.focus - Review focus (default: 'all')
60
- * @param {number} opts.attempt - Round number (1-based)
61
- * @param {string} [opts.repoRoot] - Absolute repo root path
62
- * @returns {string}
63
- */
64
82
  /**
65
83
  * @param {string} slug
66
84
  * @param {string} [repoRoot]
@@ -68,176 +86,123 @@ function actOnReviewEntrypoint(agent) {
68
86
  * @returns {string}
69
87
  */
70
88
  function resolveMissionPath(slug, repoRoot, missionPathOverride) {
71
- // `--mission <path>` override: when the caller points the review loop at a
72
- // mission contract in a non-standard location, agents must read that file
73
- // instead of the slug-derived standard path (task-1272 SC7).
74
- if (missionPathOverride) {return missionPathOverride;}
75
- const root = repoRoot || process.cwd();
76
- try {
77
- return missionPathForSlug(root, slug);
78
- } catch (_) {
79
- const year = getMissionYear(slug, root);
80
- return path.join(root, 'docs', 'missions', String(year), slug, 'MISSION.md');
81
- }
89
+ if (missionPathOverride) {
90
+ return missionPathOverride;
91
+ }
92
+ const root = repoRoot || process.cwd();
93
+ try {
94
+ return (0, mission_utils_js_1.missionPathForSlug)(root, slug);
95
+ }
96
+ catch {
97
+ const year = (0, mission_utils_js_1.getMissionYear)(slug, root);
98
+ return path.join(root, 'docs', 'missions', String(year), slug, 'MISSION.md');
99
+ }
82
100
  }
83
-
84
101
  /** @param {string} [repoRoot] */
85
102
  function resolvePrimaryBranch(repoRoot) {
86
- try {
87
- return getPrimaryBranch(repoRoot || process.cwd());
88
- } catch (_) {
89
- return 'main';
90
- }
103
+ try {
104
+ return (0, mission_utils_js_1.getPrimaryBranch)(repoRoot || process.cwd());
105
+ }
106
+ catch {
107
+ return 'main';
108
+ }
91
109
  }
92
-
93
110
  /**
94
111
  * @param {{reviewer: string, branch: string, implementer: string, focus?: string, attempt: number, repoRoot?: string, missionPath?: string}} opts
95
112
  * @returns {string}
96
113
  */
97
114
  function buildReviewPrompt({ reviewer, branch, implementer, focus = 'all', attempt, repoRoot = '', missionPath: missionPathOverride }) {
98
- const entrypoint = reviewEntrypoint(reviewer);
99
- const repoLine = repoRoot ? `\noperate from repo root: ${repoRoot}` : '';
100
- const slug = branch.replace(/^mission\//, '');
101
- const year = getMissionYear(slug, repoRoot || process.cwd());
102
- const missionPath = resolveMissionPath(slug, repoRoot, missionPathOverride);
103
- const artifactDir = resolveArtifactDir(repoRoot || process.cwd());
104
-
105
- const template = fs.readFileSync(REVIEW_VERBOSE_PROMPT_PATH, 'utf8');
106
- return template
107
- .replaceAll('{{branch}}', branch)
108
- .replaceAll('{{reviewer}}', reviewer)
109
- .replaceAll('{{implementer}}', implementer)
110
- .replaceAll('{{focus}}', focus)
111
- .replaceAll('{{attempt}}', String(attempt))
112
- .replaceAll('{{slug}}', slug)
113
- .replaceAll('{{missionPath}}', missionPath)
114
- .replaceAll('{{artifactDir}}', artifactDir)
115
- .replaceAll('{{primaryBranch}}', resolvePrimaryBranch(repoRoot))
116
- .replaceAll('{{review_entrypoint}}', entrypoint)
117
- .replaceAll('YYYY', year)
118
- .replaceAll('{{repo_line}}', repoLine ? repoLine.trim() + '\n- ' : '');
115
+ const entrypoint = reviewEntrypoint(reviewer);
116
+ const repoLine = repoRoot ? `\noperate from repo root: ${repoRoot}` : '';
117
+ const slug = branch.replace(/^mission\//, '');
118
+ const year = (0, mission_utils_js_1.getMissionYear)(slug, repoRoot || process.cwd());
119
+ const missionPath = resolveMissionPath(slug, repoRoot, missionPathOverride);
120
+ const artifactDir = (0, review_artifacts_js_1.resolveArtifactDir)(repoRoot || process.cwd());
121
+ const template = fs.readFileSync(REVIEW_VERBOSE_PROMPT_PATH, 'utf8');
122
+ return template
123
+ .replaceAll('{{branch}}', branch)
124
+ .replaceAll('{{reviewer}}', reviewer)
125
+ .replaceAll('{{implementer}}', implementer)
126
+ .replaceAll('{{focus}}', focus)
127
+ .replaceAll('{{attempt}}', String(attempt))
128
+ .replaceAll('{{slug}}', slug)
129
+ .replaceAll('{{missionPath}}', missionPath)
130
+ .replaceAll('{{artifactDir}}', artifactDir)
131
+ .replaceAll('{{primaryBranch}}', resolvePrimaryBranch(repoRoot))
132
+ .replaceAll('{{review_entrypoint}}', entrypoint)
133
+ .replaceAll('YYYY', year)
134
+ .replaceAll('{{repo_line}}', repoLine ? repoLine.trim() + '\n- ' : '');
119
135
  }
120
-
121
- /**
122
- * Build the prompt sent back to the implementer agent after a review round.
123
- *
124
- * @param {object} opts
125
- * @param {string} opts.implementer - Implementer agent family
126
- * @param {string} opts.branch - Mission branch name
127
- * @param {number} opts.attempt - Round number (1-based)
128
- * @param {string} [opts.repoRoot] - Absolute repo root path
129
- * @returns {string}
130
- */
131
136
  /**
132
137
  * @param {{implementer: string, branch: string, attempt: number, repoRoot?: string, missionPath?: string}} opts
133
138
  * @returns {string}
134
139
  */
135
140
  function buildActOnReviewPrompt({ implementer, branch, attempt, repoRoot = '', missionPath: missionPathOverride }) {
136
- const entrypoint = actOnReviewEntrypoint(implementer);
137
- const repoLine = repoRoot ? `\noperate from repo root: ${repoRoot}` : '';
138
- const slug = branch.replace(/^mission\//, '');
139
- const year = getMissionYear(slug, repoRoot || process.cwd());
140
- const missionPath = resolveMissionPath(slug, repoRoot, missionPathOverride);
141
- const artifactDir = resolveArtifactDir(repoRoot || process.cwd());
142
-
143
- const template = fs.readFileSync(ACT_ON_REVIEW_VERBOSE_PROMPT_PATH, 'utf8');
144
- return template
145
- .replaceAll('{{branch}}', branch)
146
- .replaceAll('{{implementer}}', implementer)
147
- .replaceAll('{{attempt}}', String(attempt))
148
- .replaceAll('{{slug}}', slug)
149
- .replaceAll('{{missionPath}}', missionPath)
150
- .replaceAll('{{artifactDir}}', artifactDir)
151
- .replaceAll('{{primaryBranch}}', resolvePrimaryBranch(repoRoot))
152
- .replaceAll('{{act_on_review_entrypoint}}', entrypoint)
153
- .replaceAll('YYYY', year)
154
- .replaceAll('{{repo_line}}', repoLine ? repoLine.trim() + '\n- ' : '');
141
+ const entrypoint = actOnReviewEntrypoint(implementer);
142
+ const repoLine = repoRoot ? `\noperate from repo root: ${repoRoot}` : '';
143
+ const slug = branch.replace(/^mission\//, '');
144
+ const year = (0, mission_utils_js_1.getMissionYear)(slug, repoRoot || process.cwd());
145
+ const missionPath = resolveMissionPath(slug, repoRoot, missionPathOverride);
146
+ const artifactDir = (0, review_artifacts_js_1.resolveArtifactDir)(repoRoot || process.cwd());
147
+ const template = fs.readFileSync(ACT_ON_REVIEW_VERBOSE_PROMPT_PATH, 'utf8');
148
+ return template
149
+ .replaceAll('{{branch}}', branch)
150
+ .replaceAll('{{implementer}}', implementer)
151
+ .replaceAll('{{attempt}}', String(attempt))
152
+ .replaceAll('{{slug}}', slug)
153
+ .replaceAll('{{missionPath}}', missionPath)
154
+ .replaceAll('{{artifactDir}}', artifactDir)
155
+ .replaceAll('{{primaryBranch}}', resolvePrimaryBranch(repoRoot))
156
+ .replaceAll('{{act_on_review_entrypoint}}', entrypoint)
157
+ .replaceAll('YYYY', year)
158
+ .replaceAll('{{repo_line}}', repoLine ? repoLine.trim() + '\n- ' : '');
155
159
  }
156
-
157
-
158
- /**
159
- * Node-invoked reviewer prompt built from parallix/prompts/review.md template.
160
- * Uses the same template-substitution pattern as active.js (execute.md).
161
- *
162
- * @param {object} opts
163
- * @param {string} opts.reviewer - Reviewer agent family
164
- * @param {string} opts.branch - Mission branch name
165
- * @param {string} opts.implementer - Implementer agent family
166
- * @param {string} opts.focus - Review focus (default: 'all')
167
- * @param {number} opts.attempt - Round number (1-based)
168
- * @param {string} [opts.repoRoot] - Absolute repo root path (unused but kept for API symmetry)
169
- * @param {string} [opts.actualReviewer] - The actual agent family launched (overrides reviewer for identity text, TASK-1051)
170
- * @returns {string}
171
- */
172
160
  /**
173
161
  * @param {{reviewer: string, branch: string, implementer: string, focus?: string, attempt: number, actualReviewer?: string, repoRoot?: string, missionPath?: string}} opts
174
162
  * @returns {string}
175
163
  */
176
164
  function buildCompactReviewPrompt({ reviewer, branch, implementer, focus = 'all', attempt, actualReviewer, repoRoot = '', missionPath: missionPathOverride }) {
177
- const slug = branch.replace(/^mission\//, '');
178
- const finalReviewer = actualReviewer || reviewer;
179
- const year = getMissionYear(slug, repoRoot || process.cwd());
180
- const missionPath = resolveMissionPath(slug, repoRoot, missionPathOverride);
181
- const primaryBranch = resolvePrimaryBranch(repoRoot);
182
- const artifactDir = resolveArtifactDir(repoRoot || process.cwd());
183
- const template = fs.readFileSync(REVIEW_PROMPT_PATH, 'utf8');
184
- return template
185
- .replaceAll('{{branch}}', branch)
186
- .replaceAll('{{reviewer}}', finalReviewer)
187
- .replaceAll('{{implementer}}', implementer)
188
- .replaceAll('{{focus}}', focus)
189
- .replaceAll('{{attempt}}', String(attempt))
190
- .replaceAll('{{slug}}', slug)
191
- .replaceAll('{{missionPath}}', missionPath)
192
- .replaceAll('{{artifactDir}}', artifactDir)
193
- .replaceAll('{{primaryBranch}}', primaryBranch)
194
- .replaceAll('YYYY', year)
195
- .replaceAll('{{review_entrypoint}}', reviewEntrypoint(finalReviewer));
165
+ const slug = branch.replace(/^mission\//, '');
166
+ const finalReviewer = actualReviewer || reviewer;
167
+ const year = (0, mission_utils_js_1.getMissionYear)(slug, repoRoot || process.cwd());
168
+ const missionPath = resolveMissionPath(slug, repoRoot, missionPathOverride);
169
+ const primaryBranch = resolvePrimaryBranch(repoRoot);
170
+ const artifactDir = (0, review_artifacts_js_1.resolveArtifactDir)(repoRoot || process.cwd());
171
+ const template = fs.readFileSync(REVIEW_PROMPT_PATH, 'utf8');
172
+ return template
173
+ .replaceAll('{{branch}}', branch)
174
+ .replaceAll('{{reviewer}}', finalReviewer)
175
+ .replaceAll('{{implementer}}', implementer)
176
+ .replaceAll('{{focus}}', focus)
177
+ .replaceAll('{{attempt}}', String(attempt))
178
+ .replaceAll('{{slug}}', slug)
179
+ .replaceAll('{{missionPath}}', missionPath)
180
+ .replaceAll('{{artifactDir}}', artifactDir)
181
+ .replaceAll('{{primaryBranch}}', primaryBranch)
182
+ .replaceAll('YYYY', year)
183
+ .replaceAll('{{review_entrypoint}}', reviewEntrypoint(finalReviewer));
196
184
  }
197
-
198
- /**
199
- * Node-invoked act-on-review prompt built from parallix/prompts/act-on-review.md template.
200
- *
201
- * @param {object} opts
202
- * @param {string} opts.implementer - Implementer agent family
203
- * @param {string} opts.branch - Mission branch name
204
- * @param {number} opts.attempt - Round number (1-based)
205
- * @param {string} [opts.reviewOutcome] - Outcome of latest review (e.g. 'REQUEST_CHANGES')
206
- * @param {string} [opts.repoRoot] - Absolute repo root path
207
- * @param {string} [opts.actualImplementer] - The actual agent family launched (overrides implementer for identity text, TASK-1051)
208
- * @returns {string}
209
- */
210
185
  /**
211
186
  * @param {{implementer: string, branch: string, attempt: number, reviewOutcome?: string, actualImplementer?: string, repoRoot?: string, missionPath?: string}} opts
212
187
  * @returns {string}
213
188
  */
214
189
  function buildCompactActOnReviewPrompt({ implementer, branch, attempt, reviewOutcome = '?', actualImplementer, repoRoot = '', missionPath: missionPathOverride }) {
215
- const slug = branch.replace(/^mission\//, '');
216
- const finalImplementer = actualImplementer || implementer;
217
- const year = getMissionYear(slug, repoRoot || process.cwd());
218
- const missionPath = resolveMissionPath(slug, repoRoot, missionPathOverride);
219
- const primaryBranch = resolvePrimaryBranch(repoRoot);
220
- const artifactDir = resolveArtifactDir(repoRoot || process.cwd());
221
- const template = fs.readFileSync(ACT_ON_REVIEW_PROMPT_PATH, 'utf8');
222
- return template
223
- .replaceAll('{{branch}}', branch)
224
- .replaceAll('{{implementer}}', finalImplementer)
225
- .replaceAll('{{attempt}}', String(attempt))
226
- .replaceAll('{{slug}}', slug)
227
- .replaceAll('{{missionPath}}', missionPath)
228
- .replaceAll('{{artifactDir}}', artifactDir)
229
- .replaceAll('{{primaryBranch}}', primaryBranch)
230
- .replaceAll('{{review_outcome}}', reviewOutcome)
231
- .replaceAll('YYYY', year)
232
- .replaceAll('{{act_on_review_entrypoint}}', actOnReviewEntrypoint(finalImplementer));
190
+ const slug = branch.replace(/^mission\//, '');
191
+ const finalImplementer = actualImplementer || implementer;
192
+ const year = (0, mission_utils_js_1.getMissionYear)(slug, repoRoot || process.cwd());
193
+ const missionPath = resolveMissionPath(slug, repoRoot, missionPathOverride);
194
+ const primaryBranch = resolvePrimaryBranch(repoRoot);
195
+ const artifactDir = (0, review_artifacts_js_1.resolveArtifactDir)(repoRoot || process.cwd());
196
+ const template = fs.readFileSync(ACT_ON_REVIEW_PROMPT_PATH, 'utf8');
197
+ return template
198
+ .replaceAll('{{branch}}', branch)
199
+ .replaceAll('{{implementer}}', finalImplementer)
200
+ .replaceAll('{{attempt}}', String(attempt))
201
+ .replaceAll('{{slug}}', slug)
202
+ .replaceAll('{{missionPath}}', missionPath)
203
+ .replaceAll('{{artifactDir}}', artifactDir)
204
+ .replaceAll('{{primaryBranch}}', primaryBranch)
205
+ .replaceAll('{{review_outcome}}', reviewOutcome)
206
+ .replaceAll('YYYY', year)
207
+ .replaceAll('{{act_on_review_entrypoint}}', actOnReviewEntrypoint(finalImplementer));
233
208
  }
234
-
235
- module.exports = {
236
- PROMPT_ENTRYPOINTS,
237
- reviewEntrypoint,
238
- actOnReviewEntrypoint,
239
- buildReviewPrompt,
240
- buildActOnReviewPrompt,
241
- buildCompactReviewPrompt,
242
- buildCompactActOnReviewPrompt
243
- };
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Assemble review and act-on-review prompts for the autonomous review loop.
3
+ * Owned by the Node workflow harness (ADR 0037 / task-089).
4
+ *
5
+ * Node-invoked agent prompts read from parallix/prompts/*.md templates
6
+ * (same pattern as active.js / draft.js). Full verbose prompts are kept
7
+ * for --dry-run diagnostics and human/manual invocation.
8
+ */
9
+
10
+ import * as fs from 'fs';
11
+ import * as path from 'path';
12
+ import { getMissionYear, getPrimaryBranch, missionPathForSlug } from '../core/mission-utils.js';
13
+ import { resolveArtifactDir } from './review-artifacts.js';
14
+
15
+ const REVIEW_PROMPT_PATH = path.join(__dirname, '..', '..', 'prompts', 'review.md');
16
+ const ACT_ON_REVIEW_PROMPT_PATH = path.join(__dirname, '..', '..', 'prompts', 'act-on-review.md');
17
+ const REVIEW_VERBOSE_PROMPT_PATH = path.join(__dirname, '..', '..', 'prompts', 'review-verbose.md');
18
+ const ACT_ON_REVIEW_VERBOSE_PROMPT_PATH = path.join(__dirname, '..', '..', 'prompts', 'act-on-review-verbose.md');
19
+
20
+ type PromptEntry = { review: string; actOnReview: string };
21
+ type PromptEntrypoints = { codex: PromptEntry; claude: PromptEntry; mistral: PromptEntry; custom: PromptEntry; autonomous: PromptEntry };
22
+
23
+ export const PROMPT_ENTRYPOINTS: PromptEntrypoints = {
24
+ codex: { review: '$review all', actOnReview: '$act-on-review' },
25
+ claude: { review: '/review all', actOnReview: '/act-on-review' },
26
+ mistral: { review: '$review all', actOnReview: '/act-on-review' },
27
+ custom: { review: '$review all', actOnReview: '/act-on-review' },
28
+ autonomous: { review: '$review all', actOnReview: '/act-on-review' }
29
+ };
30
+
31
+ /** @param {string} agent */
32
+ export function reviewEntrypoint(agent: string): string {
33
+ const entry = (PROMPT_ENTRYPOINTS as Record<string, PromptEntry>)[agent];
34
+ if (!entry) { throw new Error(`Unknown agent family for review entrypoint: ${agent}`); }
35
+ return entry.review;
36
+ }
37
+
38
+ /** @param {string} agent */
39
+ export function actOnReviewEntrypoint(agent: string): string {
40
+ const entry = (PROMPT_ENTRYPOINTS as Record<string, PromptEntry>)[agent];
41
+ if (!entry) { throw new Error(`Unknown agent family for act-on-review entrypoint: ${agent}`); }
42
+ return entry.actOnReview;
43
+ }
44
+
45
+ /**
46
+ * @param {string} slug
47
+ * @param {string} [repoRoot]
48
+ * @param {string} [missionPathOverride]
49
+ * @returns {string}
50
+ */
51
+ function resolveMissionPath(slug: string, repoRoot?: string, missionPathOverride?: string): string {
52
+ if (missionPathOverride) { return missionPathOverride; }
53
+ const root = repoRoot || process.cwd();
54
+ try {
55
+ return missionPathForSlug(root, slug);
56
+ } catch {
57
+ const year = getMissionYear(slug, root);
58
+ return path.join(root, 'docs', 'missions', String(year), slug, 'MISSION.md');
59
+ }
60
+ }
61
+
62
+ /** @param {string} [repoRoot] */
63
+ function resolvePrimaryBranch(repoRoot?: string): string {
64
+ try {
65
+ return getPrimaryBranch(repoRoot || process.cwd());
66
+ } catch {
67
+ return 'main';
68
+ }
69
+ }
70
+
71
+ /**
72
+ * @param {{reviewer: string, branch: string, implementer: string, focus?: string, attempt: number, repoRoot?: string, missionPath?: string}} opts
73
+ * @returns {string}
74
+ */
75
+ export function buildReviewPrompt({ reviewer, branch, implementer, focus = 'all', attempt, repoRoot = '', missionPath: missionPathOverride }: {
76
+ reviewer: string; branch: string; implementer: string; focus?: string; attempt: number; repoRoot?: string; missionPath?: string;
77
+ }): string {
78
+ const entrypoint = reviewEntrypoint(reviewer);
79
+ const repoLine = repoRoot ? `\noperate from repo root: ${repoRoot}` : '';
80
+ const slug = branch.replace(/^mission\//, '');
81
+ const year = getMissionYear(slug, repoRoot || process.cwd());
82
+ const missionPath = resolveMissionPath(slug, repoRoot, missionPathOverride);
83
+ const artifactDir = resolveArtifactDir(repoRoot || process.cwd());
84
+
85
+ const template = fs.readFileSync(REVIEW_VERBOSE_PROMPT_PATH, 'utf8');
86
+ return template
87
+ .replaceAll('{{branch}}', branch)
88
+ .replaceAll('{{reviewer}}', reviewer)
89
+ .replaceAll('{{implementer}}', implementer)
90
+ .replaceAll('{{focus}}', focus)
91
+ .replaceAll('{{attempt}}', String(attempt))
92
+ .replaceAll('{{slug}}', slug)
93
+ .replaceAll('{{missionPath}}', missionPath)
94
+ .replaceAll('{{artifactDir}}', artifactDir)
95
+ .replaceAll('{{primaryBranch}}', resolvePrimaryBranch(repoRoot))
96
+ .replaceAll('{{review_entrypoint}}', entrypoint)
97
+ .replaceAll('YYYY', year)
98
+ .replaceAll('{{repo_line}}', repoLine ? repoLine.trim() + '\n- ' : '');
99
+ }
100
+
101
+ /**
102
+ * @param {{implementer: string, branch: string, attempt: number, repoRoot?: string, missionPath?: string}} opts
103
+ * @returns {string}
104
+ */
105
+ export function buildActOnReviewPrompt({ implementer, branch, attempt, repoRoot = '', missionPath: missionPathOverride }: {
106
+ implementer: string; branch: string; attempt: number; repoRoot?: string; missionPath?: string;
107
+ }): string {
108
+ const entrypoint = actOnReviewEntrypoint(implementer);
109
+ const repoLine = repoRoot ? `\noperate from repo root: ${repoRoot}` : '';
110
+ const slug = branch.replace(/^mission\//, '');
111
+ const year = getMissionYear(slug, repoRoot || process.cwd());
112
+ const missionPath = resolveMissionPath(slug, repoRoot, missionPathOverride);
113
+ const artifactDir = resolveArtifactDir(repoRoot || process.cwd());
114
+
115
+ const template = fs.readFileSync(ACT_ON_REVIEW_VERBOSE_PROMPT_PATH, 'utf8');
116
+ return template
117
+ .replaceAll('{{branch}}', branch)
118
+ .replaceAll('{{implementer}}', implementer)
119
+ .replaceAll('{{attempt}}', String(attempt))
120
+ .replaceAll('{{slug}}', slug)
121
+ .replaceAll('{{missionPath}}', missionPath)
122
+ .replaceAll('{{artifactDir}}', artifactDir)
123
+ .replaceAll('{{primaryBranch}}', resolvePrimaryBranch(repoRoot))
124
+ .replaceAll('{{act_on_review_entrypoint}}', entrypoint)
125
+ .replaceAll('YYYY', year)
126
+ .replaceAll('{{repo_line}}', repoLine ? repoLine.trim() + '\n- ' : '');
127
+ }
128
+
129
+ /**
130
+ * @param {{reviewer: string, branch: string, implementer: string, focus?: string, attempt: number, actualReviewer?: string, repoRoot?: string, missionPath?: string}} opts
131
+ * @returns {string}
132
+ */
133
+ export function buildCompactReviewPrompt({ reviewer, branch, implementer, focus = 'all', attempt, actualReviewer, repoRoot = '', missionPath: missionPathOverride }: {
134
+ reviewer: string; branch: string; implementer: string; focus?: string; attempt: number; actualReviewer?: string; repoRoot?: string; missionPath?: string;
135
+ }): string {
136
+ const slug = branch.replace(/^mission\//, '');
137
+ const finalReviewer = actualReviewer || reviewer;
138
+ const year = getMissionYear(slug, repoRoot || process.cwd());
139
+ const missionPath = resolveMissionPath(slug, repoRoot, missionPathOverride);
140
+ const primaryBranch = resolvePrimaryBranch(repoRoot);
141
+ const artifactDir = resolveArtifactDir(repoRoot || process.cwd());
142
+ const template = fs.readFileSync(REVIEW_PROMPT_PATH, 'utf8');
143
+ return template
144
+ .replaceAll('{{branch}}', branch)
145
+ .replaceAll('{{reviewer}}', finalReviewer)
146
+ .replaceAll('{{implementer}}', implementer)
147
+ .replaceAll('{{focus}}', focus)
148
+ .replaceAll('{{attempt}}', String(attempt))
149
+ .replaceAll('{{slug}}', slug)
150
+ .replaceAll('{{missionPath}}', missionPath)
151
+ .replaceAll('{{artifactDir}}', artifactDir)
152
+ .replaceAll('{{primaryBranch}}', primaryBranch)
153
+ .replaceAll('YYYY', year)
154
+ .replaceAll('{{review_entrypoint}}', reviewEntrypoint(finalReviewer));
155
+ }
156
+
157
+ /**
158
+ * @param {{implementer: string, branch: string, attempt: number, reviewOutcome?: string, actualImplementer?: string, repoRoot?: string, missionPath?: string}} opts
159
+ * @returns {string}
160
+ */
161
+ export function buildCompactActOnReviewPrompt({ implementer, branch, attempt, reviewOutcome = '?', actualImplementer, repoRoot = '', missionPath: missionPathOverride }: {
162
+ implementer: string; branch: string; attempt: number; reviewOutcome?: string; actualImplementer?: string; repoRoot?: string; missionPath?: string;
163
+ }): string {
164
+ const slug = branch.replace(/^mission\//, '');
165
+ const finalImplementer = actualImplementer || implementer;
166
+ const year = getMissionYear(slug, repoRoot || process.cwd());
167
+ const missionPath = resolveMissionPath(slug, repoRoot, missionPathOverride);
168
+ const primaryBranch = resolvePrimaryBranch(repoRoot);
169
+ const artifactDir = resolveArtifactDir(repoRoot || process.cwd());
170
+ const template = fs.readFileSync(ACT_ON_REVIEW_PROMPT_PATH, 'utf8');
171
+ return template
172
+ .replaceAll('{{branch}}', branch)
173
+ .replaceAll('{{implementer}}', finalImplementer)
174
+ .replaceAll('{{attempt}}', String(attempt))
175
+ .replaceAll('{{slug}}', slug)
176
+ .replaceAll('{{missionPath}}', missionPath)
177
+ .replaceAll('{{artifactDir}}', artifactDir)
178
+ .replaceAll('{{primaryBranch}}', primaryBranch)
179
+ .replaceAll('{{review_outcome}}', reviewOutcome)
180
+ .replaceAll('YYYY', year)
181
+ .replaceAll('{{act_on_review_entrypoint}}', actOnReviewEntrypoint(finalImplementer));
182
+ }