@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.
- package/CHANGELOG.md +140 -0
- package/LICENSE +661 -0
- package/README.md +196 -0
- package/config/agents.json +25 -0
- package/config/agents.local.json.template +8 -0
- package/config/state-map.json +4 -0
- package/config/state-map.json.template +31 -0
- package/config/workflow.config.schema.json +98 -0
- package/data/.gitkeep +0 -0
- package/docs/adr/0031-ai-agent-instruction-boundary-and-command-floor.md +114 -0
- package/docs/adr/0032-mission-refinement-state-and-usage-budget-signals.md +135 -0
- package/docs/adr/0034-module-and-skill-invocation-model.md +202 -0
- package/docs/adr/0036-mission-sizing-and-dependency-wave-heuristics.md +79 -0
- package/docs/adr/0037-ai-workflow-coordination-architecture.md +162 -0
- package/docs/adr/0041-integration-pipeline-gates.md +165 -0
- package/docs/adr/0042-workflow-cli-color-rendering-approach.md +106 -0
- package/docs/adr/0043-git-target-resolution-strategy.md +185 -0
- package/docs/adr/0044-workflow-distribution-model.md +277 -0
- package/docs/adr/0045-parallax-branch-model.md +182 -0
- package/docs/adr/0046-npm-publish-process-and-security.md +138 -0
- package/docs/adr/index.md +20 -0
- package/docs/agents.md +212 -0
- package/docs/authority-reference.md +298 -0
- package/docs/forgejo-setup.md +31 -0
- package/docs/migration/extraction.md +61 -0
- package/docs/migration/task-classification.md +36 -0
- package/docs/operator-setup.md +76 -0
- package/docs/readme-rewrite-benchmark.md +188 -0
- package/docs/use-cases.md +105 -0
- package/examples/README.md +62 -0
- package/examples/run-enterprise-tarball-workflow-smoke.sh +257 -0
- package/examples/run-verify-env-smoke.sh +40 -0
- package/index.js +250 -0
- package/lib/README.md +13 -0
- package/lib/agents/agents.js +867 -0
- package/lib/agents/claude-telemetry.js +233 -0
- package/lib/agents/claude.js +139 -0
- package/lib/agents/codex-telemetry.js +202 -0
- package/lib/agents/codex.js +219 -0
- package/lib/agents/limit-hit.js +252 -0
- package/lib/agents/mistral-telemetry.js +44 -0
- package/lib/agents/mistral.js +68 -0
- package/lib/agents/opencode-export.js +110 -0
- package/lib/agents/opencode-telemetry.js +356 -0
- package/lib/agents/opencode.js +218 -0
- package/lib/agents/stage-telemetry.js +37 -0
- package/lib/commands/active.js +625 -0
- package/lib/commands/checkpoint.js +76 -0
- package/lib/commands/config.js +39 -0
- package/lib/commands/coverage-gate.js +358 -0
- package/lib/commands/diff.js +119 -0
- package/lib/commands/draft.js +854 -0
- package/lib/commands/handoff.js +501 -0
- package/lib/commands/integrate.js +1528 -0
- package/lib/commands/mission-start.js +246 -0
- package/lib/commands/rebase.js +597 -0
- package/lib/commands/repair-handoff.js +227 -0
- package/lib/commands/resolve-conflict.js +109 -0
- package/lib/commands/review.js +13 -0
- package/lib/commands/setup-review.js +13 -0
- package/lib/commands/setup.js +3 -0
- package/lib/commands/stats-backfill.js +395 -0
- package/lib/commands/stats.js +1601 -0
- package/lib/commands/status.js +183 -0
- package/lib/commands/verify.js +1 -0
- package/lib/core/fmt.js +202 -0
- package/lib/core/git.js +73 -0
- package/lib/core/gitignore.js +110 -0
- package/lib/core/mission-utils.js +1017 -0
- package/lib/core/persistent-data-migration.js +201 -0
- package/lib/core/product-config.js +508 -0
- package/lib/core/runtime-matrix.js +82 -0
- package/lib/core/spawn-tee.js +173 -0
- package/lib/core/state-map.js +89 -0
- package/lib/core/storage.js +165 -0
- package/lib/core/verification.js +149 -0
- package/lib/index.js +77 -0
- package/lib/review/rebase.js +163 -0
- package/lib/review/review-adapter.js +135 -0
- package/lib/review/review-artifacts.js +619 -0
- package/lib/review/review-commands.js +1375 -0
- package/lib/review/review-events.js +1007 -0
- package/lib/review/review-loop.js +1004 -0
- package/lib/review/review-polling.js +141 -0
- package/lib/review/review-prompts.js +212 -0
- package/lib/review/review-state.js +280 -0
- package/lib/review/review.js +96 -0
- package/lib/tools/backlog.js +680 -0
- package/lib/tools/forgejo.js +1585 -0
- package/lib/tools/gatekeeper.js +106 -0
- package/lib/tools/sessions.js +74 -0
- package/lib/tools/setup-review.js +1053 -0
- package/package.json +56 -0
- package/prompts/act-on-review-verbose.md +20 -0
- package/prompts/act-on-review.md +22 -0
- package/prompts/draft.md +20 -0
- package/prompts/execute.md +24 -0
- package/prompts/portfolio.md +30 -0
- package/prompts/review-verbose.md +20 -0
- package/prompts/review.md +17 -0
- package/px.js +236 -0
- package/templates/AGENTS-snippet.md +14 -0
- package/templates/AGENTS.md.template +34 -0
- package/templates/CLAUDE.md.template +27 -0
- package/templates/CODEX.md.template +38 -0
- package/templates/MISTRAL.md.template +24 -0
- package/templates/claude-commands/act-on-review.md +3 -0
- package/templates/claude-commands/area-review.md +3 -0
- package/templates/claude-commands/draft.md +6 -0
- package/templates/claude-commands/execute.md +6 -0
- package/templates/claude-commands/integrate.md +4 -0
- package/templates/claude-commands/portfolio.md +5 -0
- package/templates/claude-commands/review.md +4 -0
- package/templates/codex/config.toml +6 -0
- package/templates/mission-scaffold.md +39 -0
- package/templates/vibe/skills/act-on-review/SKILL.md +16 -0
- package/templates/vibe/skills/area-review/SKILL.md +16 -0
- package/templates/vibe/skills/draft/SKILL.md +16 -0
- package/templates/vibe/skills/execute/SKILL.md +16 -0
- package/templates/vibe/skills/integrate/SKILL.md +16 -0
- package/templates/vibe/skills/portfolio/SKILL.md +21 -0
- package/templates/vibe/skills/review/SKILL.md +16 -0
- package/tools/setup-forgejo-docker.sh +84 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const { git } = require('../core/git');
|
|
3
|
+
const missionUtils = require('../core/mission-utils');
|
|
4
|
+
const rebase = require('./rebase');
|
|
5
|
+
const fmt = require('../core/fmt');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Check if an error message indicates a relaunchable content error (missing/empty goal-check table).
|
|
9
|
+
*
|
|
10
|
+
* @param {string} errorMsg - The error message to check
|
|
11
|
+
* @returns {boolean} True if the error is relaunchable
|
|
12
|
+
*/
|
|
13
|
+
function isRelaunchableError(errorMsg) {
|
|
14
|
+
if (!errorMsg || typeof errorMsg !== 'string') {
|
|
15
|
+
return false;
|
|
16
|
+
}
|
|
17
|
+
// Match the exact error message from handoff.js when final checkpoint has no evidence rows
|
|
18
|
+
return errorMsg.includes('has a "## Goal Check" section but no evidence rows') &&
|
|
19
|
+
errorMsg.includes('A goal-check table with real evidence is required before handoff');
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Build a relaunch prompt for an agent to fix a relaunchable error.
|
|
24
|
+
*
|
|
25
|
+
* @param {string} errorMsg - The error message from the failed handoff
|
|
26
|
+
* @param {string} slug - Mission slug
|
|
27
|
+
* @param {string} worktree - Path to the mission worktree
|
|
28
|
+
* @returns {string} The relaunch prompt
|
|
29
|
+
*/
|
|
30
|
+
function buildRelaunchPrompt(errorMsg, slug, worktree) {
|
|
31
|
+
const year = missionUtils.getMissionYear(slug, worktree);
|
|
32
|
+
const missionDir = missionUtils.findMissionDir(slug, worktree) || missionUtils.missionDirForSlug(worktree, slug);
|
|
33
|
+
|
|
34
|
+
return `Automated handoff failed for mission ${slug} with a repairable error: ${errorMsg}
|
|
35
|
+
|
|
36
|
+
` +
|
|
37
|
+
`Please fix the final checkpoint document in ${missionDir} by adding a Goal Check table ` +
|
|
38
|
+
`with real evidence rows (file:line references, test names). The Goal Check table must have ` +
|
|
39
|
+
`a header row and at least one evidence row using pipe syntax (|).
|
|
40
|
+
|
|
41
|
+
` +
|
|
42
|
+
`Steps:
|
|
43
|
+
` +
|
|
44
|
+
`1. Open the final checkpoint document (CP-N.md) in ${missionDir}
|
|
45
|
+
` +
|
|
46
|
+
`2. Add or update the "## Goal Check" section
|
|
47
|
+
` +
|
|
48
|
+
`3. Create a markdown table with columns for: Goal Check description | Evidence | Status
|
|
49
|
+
` +
|
|
50
|
+
`4. Add at least one evidence row with real file:line or test name references
|
|
51
|
+
` +
|
|
52
|
+
`5. Commit the updated checkpoint with a descriptive commit message
|
|
53
|
+
` +
|
|
54
|
+
`6. Re-run: node parallix review ${slug} --submit
|
|
55
|
+
|
|
56
|
+
` +
|
|
57
|
+
`Example Goal Check table:
|
|
58
|
+
` +
|
|
59
|
+
`| Goal Check | Evidence | Status |
|
|
60
|
+
` +
|
|
61
|
+
`|---|---|---|
|
|
62
|
+
` +
|
|
63
|
+
`| Final checkpoint has Goal Check section | docs/missions/${year}/${slug}/CP-1.md:15 | PASS |
|
|
64
|
+
` +
|
|
65
|
+
`| Tests pass | npm test -- parallix/test/repair-handoff.test.js | PASS |
|
|
66
|
+
|
|
67
|
+
` +
|
|
68
|
+
`Do NOT add placeholder or generic evidence. Each row must cite real, verifiable artifacts.`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Attempt to repair a failed automated handoff by auto-committing mission
|
|
73
|
+
* artifacts or rebasing.
|
|
74
|
+
*
|
|
75
|
+
* @param {string} slug - Mission slug
|
|
76
|
+
* @param {string} worktree - Path to the mission worktree
|
|
77
|
+
* @param {string} errorMsg - The error message from the failed handoff
|
|
78
|
+
* @param {object} [options]
|
|
79
|
+
* @returns {Promise<{repaired: boolean, blocker: string|null}>} Result and optional blocker reason
|
|
80
|
+
*/
|
|
81
|
+
async function repairHandoff(slug, worktree, errorMsg, options = {}) {
|
|
82
|
+
const {
|
|
83
|
+
taskFile = null,
|
|
84
|
+
gitFn = git,
|
|
85
|
+
rebaseFn = rebase,
|
|
86
|
+
log = fmt.log.plain,
|
|
87
|
+
error = fmt.log.plainError
|
|
88
|
+
} = options;
|
|
89
|
+
|
|
90
|
+
const rootDir = worktree || process.cwd();
|
|
91
|
+
let repaired = false;
|
|
92
|
+
let blocker = null;
|
|
93
|
+
|
|
94
|
+
function parsePorcelainPath(line) {
|
|
95
|
+
const xy = line.slice(0, 2);
|
|
96
|
+
const rawPath = line.slice(3).trim();
|
|
97
|
+
const pathPart = rawPath.includes('->') ? rawPath.split('->').pop().trim() : rawPath;
|
|
98
|
+
const cleanPath = (pathPart.startsWith('"') && pathPart.endsWith('"')) ? pathPart.slice(1, -1) : pathPart;
|
|
99
|
+
return { xy, file: cleanPath };
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// 0. Check if error is repairable
|
|
103
|
+
const isDirtyError = errorMsg && (
|
|
104
|
+
errorMsg.includes('is modified but uncommitted') ||
|
|
105
|
+
errorMsg.includes('Commit the mission contract before handoff') ||
|
|
106
|
+
errorMsg.includes('Commit the implementation evidence before handoff')
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
const isBehind = errorMsg && (
|
|
110
|
+
errorMsg.includes('Updates were rejected') ||
|
|
111
|
+
errorMsg.includes('fetch first') ||
|
|
112
|
+
errorMsg.includes('non-fast-forward') ||
|
|
113
|
+
errorMsg.includes('behind its remote') ||
|
|
114
|
+
// Ensure we don't match generic git push failed unless it has non-fast-forward hints
|
|
115
|
+
(errorMsg.includes('git push failed') && (
|
|
116
|
+
errorMsg.includes('rejected') ||
|
|
117
|
+
errorMsg.includes('remote contains work')
|
|
118
|
+
))
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
if (!isDirtyError && !isBehind) {
|
|
122
|
+
log(`Handoff error is not automatically repairable: ${errorMsg}`);
|
|
123
|
+
return { repaired: false, blocker: null };
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// 1. Auto-commit mission artifacts if uncommitted
|
|
127
|
+
if (isDirtyError || isBehind) {
|
|
128
|
+
const statusResult = gitFn(['-C', rootDir, 'status', '--porcelain']);
|
|
129
|
+
if (statusResult.status === 0 && statusResult.stdout) {
|
|
130
|
+
const dirtyLines = statusResult.stdout.split('\n')
|
|
131
|
+
.filter(line => line.trim().length > 0);
|
|
132
|
+
|
|
133
|
+
const dirtyFilesWithStatus = dirtyLines.map(parsePorcelainPath);
|
|
134
|
+
|
|
135
|
+
const unmerged = dirtyFilesWithStatus.filter(f =>
|
|
136
|
+
['DD', 'AU', 'UD', 'UA', 'DU', 'AA', 'UU'].includes(f.xy)
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
if (unmerged.length > 0) {
|
|
140
|
+
blocker = `Conflicted files detected:\n${unmerged.map(f => ` - ${f.file}`).join('\n')}`;
|
|
141
|
+
log(`Cannot auto-commit: ${blocker}`);
|
|
142
|
+
return { repaired: false, blocker };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const dirtyFiles = dirtyFilesWithStatus.map(f => f.file);
|
|
146
|
+
|
|
147
|
+
const isSafeToCommit = (file) =>
|
|
148
|
+
missionUtils.isWorkflowGeneratedArtifact(file)
|
|
149
|
+
|| missionUtils.isMissionArtifact(file, slug, rootDir);
|
|
150
|
+
|
|
151
|
+
const safeFiles = dirtyFiles.filter(isSafeToCommit);
|
|
152
|
+
const unsafeFiles = dirtyFiles.filter(f => !isSafeToCommit(f));
|
|
153
|
+
|
|
154
|
+
if (unsafeFiles.length > 0) {
|
|
155
|
+
log(`Cannot auto-commit: dirty files include non-mission paths:`);
|
|
156
|
+
unsafeFiles.forEach(f => log(` - ${f}`));
|
|
157
|
+
blocker = `dirty files include non-mission paths: ${unsafeFiles.join(', ')}`;
|
|
158
|
+
return { repaired: false, blocker };
|
|
159
|
+
} else if (safeFiles.length > 0) {
|
|
160
|
+
log(`Auto-committing mission artifacts:`);
|
|
161
|
+
const stageFailures = [];
|
|
162
|
+
safeFiles.forEach(f => {
|
|
163
|
+
log(` - ${f}`);
|
|
164
|
+
const addResult = gitFn(['-C', rootDir, 'add', '--', f]);
|
|
165
|
+
if (addResult.status === 0) {
|
|
166
|
+
return;
|
|
167
|
+
} else {
|
|
168
|
+
const failureText = [addResult.stderr, addResult.stdout].filter(Boolean).join('\n').trim();
|
|
169
|
+
stageFailures.push(`${f}${failureText ? `: ${failureText}` : ''}`);
|
|
170
|
+
}
|
|
171
|
+
});
|
|
172
|
+
if (stageFailures.length > 0) {
|
|
173
|
+
blocker = `failed to stage mission artifacts: ${stageFailures.join(', ')}`;
|
|
174
|
+
error(fmt.status('FAIL', blocker));
|
|
175
|
+
return { repaired: false, blocker };
|
|
176
|
+
}
|
|
177
|
+
const commitRes = gitFn(['-C', rootDir, 'commit', '-m', `workflow(${slug}): auto-commit mission artifacts before handoff`]);
|
|
178
|
+
if (commitRes.status === 0) {
|
|
179
|
+
log(fmt.status('PASS', 'Mission artifacts committed.'));
|
|
180
|
+
repaired = true;
|
|
181
|
+
} else {
|
|
182
|
+
error(fmt.status('WARN', `Failed to commit mission artifacts: ${commitRes.stderr}`));
|
|
183
|
+
blocker = `failed to commit mission artifacts: ${commitRes.stderr}`;
|
|
184
|
+
return { repaired: false, blocker };
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// 2. Auto-rebase if branch is behind
|
|
191
|
+
if (isBehind) {
|
|
192
|
+
log('Branch appears behind primary branch. Calling rebase...');
|
|
193
|
+
let rebaseSuccess = false;
|
|
194
|
+
let rebaseError = null;
|
|
195
|
+
try {
|
|
196
|
+
const { getCurrentBranch } = require('../core/git');
|
|
197
|
+
await rebaseFn([slug], {
|
|
198
|
+
gitFn: (args, opts) => gitFn(args, { ...opts, cwd: rootDir }),
|
|
199
|
+
getCurrentBranchFn: () => getCurrentBranch(rootDir),
|
|
200
|
+
exitFn: (code) => {
|
|
201
|
+
if (code === 0) {
|
|
202
|
+
rebaseSuccess = true;
|
|
203
|
+
} else {
|
|
204
|
+
rebaseError = `rebase exited with code ${code}`;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
});
|
|
208
|
+
} catch (err) {
|
|
209
|
+
rebaseError = err.message;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
if (!rebaseSuccess) {
|
|
213
|
+
const msg = rebaseError || 'unknown rebase failure';
|
|
214
|
+
error(fmt.status('WARN', `Auto-rebase failed: ${msg}`));
|
|
215
|
+
blocker = `Auto-rebase failed: ${msg}`;
|
|
216
|
+
repaired = false; // Reset repaired if rebase fails, even if auto-commit succeeded
|
|
217
|
+
} else {
|
|
218
|
+
repaired = true;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
return { repaired, blocker };
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
module.exports = repairHandoff;
|
|
226
|
+
module.exports.isRelaunchableError = isRelaunchableError;
|
|
227
|
+
module.exports.buildRelaunchPrompt = buildRelaunchPrompt;
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
const { resolveConflictsForMission } = require('./integrate');
|
|
2
|
+
const { findMissionDir, findMissionArea, inferSlug, getPrimaryBranch } = require('../core/mission-utils');
|
|
3
|
+
const { startAgent } = require('../agents/agents');
|
|
4
|
+
const fmt = require('../core/fmt');
|
|
5
|
+
const { formatVerificationCommand } = require('../core/verification');
|
|
6
|
+
|
|
7
|
+
function shellQuote(value) {
|
|
8
|
+
return `"${String(value).replace(/(["\\$`])/g, '\\$1')}"`;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function buildAgentResolutionPrompt({ slug, area, worktreePath, missionSpecificFiles }) {
|
|
12
|
+
const fileCommands = missionSpecificFiles
|
|
13
|
+
.map(f => ` git checkout --theirs "${f}" && git add "${f}"`)
|
|
14
|
+
.join('\n');
|
|
15
|
+
const quotedWorktreePath = shellQuote(worktreePath);
|
|
16
|
+
|
|
17
|
+
return [
|
|
18
|
+
'Mode: conflict-resolution.',
|
|
19
|
+
'',
|
|
20
|
+
`Mission: ${slug}`,
|
|
21
|
+
`Mission worktree: ${worktreePath}`,
|
|
22
|
+
'',
|
|
23
|
+
`Conflicts detected between the mission branch and ${getPrimaryBranch()}.`,
|
|
24
|
+
'All conflicts are in mission-specific files that can be resolved by keeping',
|
|
25
|
+
'the mission branch version (--theirs). Execute these steps in order:',
|
|
26
|
+
'',
|
|
27
|
+
` cd ${quotedWorktreePath}`,
|
|
28
|
+
` git rebase ${getPrimaryBranch()}`,
|
|
29
|
+
' # When the rebase pauses on conflicts, run for each file:',
|
|
30
|
+
fileCommands,
|
|
31
|
+
' git rebase --continue',
|
|
32
|
+
` ${formatVerificationCommand(area, worktreePath)}`,
|
|
33
|
+
` px integrate ${slug} --dry-run`,
|
|
34
|
+
'',
|
|
35
|
+
'Rules:',
|
|
36
|
+
'- Take --theirs for every file listed above. Do not inspect or edit conflict content.',
|
|
37
|
+
'- If the rebase pauses on a file NOT in the list above, stop immediately and',
|
|
38
|
+
' report the unexpected file. Do not guess the resolution.',
|
|
39
|
+
'- If any command fails, stop immediately and report the failure.',
|
|
40
|
+
' Do not continue to the next step.',
|
|
41
|
+
].join('\n');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function resolveConflict(args, {
|
|
45
|
+
resolveConflictsFn = resolveConflictsForMission,
|
|
46
|
+
startAgentFn = startAgent,
|
|
47
|
+
exitFn = (code) => process.exit(code),
|
|
48
|
+
} = {}) {
|
|
49
|
+
const explicitSlug = args[0];
|
|
50
|
+
const slug = inferSlug(explicitSlug);
|
|
51
|
+
if (!slug) {
|
|
52
|
+
fmt.log.fail('Usage: px resolve-conflict [<slug>]');
|
|
53
|
+
exitFn(1);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const missionDir = findMissionDir(slug);
|
|
58
|
+
const area = missionDir ? findMissionArea(missionDir) : 'docs';
|
|
59
|
+
|
|
60
|
+
const result = resolveConflictsFn(slug, area);
|
|
61
|
+
|
|
62
|
+
// No conflicts — nothing to resolve.
|
|
63
|
+
if (result.ok && result.conflictFiles.length === 0) {
|
|
64
|
+
exitFn(0);
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Shared-file conflicts: the agent cannot guess the correct resolution.
|
|
69
|
+
// Stop and require human intervention.
|
|
70
|
+
if (!result.ok && result.error === 'shared-file-conflicts') {
|
|
71
|
+
fmt.log.fail('Shared-file conflicts require human resolution. Agent cannot guess the correct merge outcome.');
|
|
72
|
+
fmt.log.info(`Shared files: ${result.sharedFiles.map(f => fmt.path(f)).join(', ')}`);
|
|
73
|
+
fmt.log.info(`Resolve manually, then re-run: ${fmt.command(`px integrate ${slug} --dry-run`)}`);
|
|
74
|
+
exitFn(1);
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Other errors (worktree missing, merge-failed, etc.) — already logged by resolveConflictsFn.
|
|
79
|
+
if (!result.ok) {
|
|
80
|
+
exitFn(1);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// All conflicts are mission-specific: launch an agent to execute the --theirs rebase.
|
|
85
|
+
const prompt = buildAgentResolutionPrompt({
|
|
86
|
+
slug,
|
|
87
|
+
area,
|
|
88
|
+
worktreePath: result.worktreePath,
|
|
89
|
+
missionSpecificFiles: result.missionSpecificFiles,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
fmt.log.info('Launching agent to execute mission-specific conflict resolution...');
|
|
93
|
+
const { agent, result: agentResult } = await startAgentFn('conflict-resolution', {
|
|
94
|
+
prompt,
|
|
95
|
+
worktree: result.worktreePath,
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
if (agentResult.status !== 0) {
|
|
99
|
+
fmt.log.fail(`Agent (${fmt.agent(agent)}) exited with status ${agentResult.status}.`);
|
|
100
|
+
exitFn(agentResult.status || 1);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
fmt.log.pass(`Agent (${fmt.agent(agent)}) completed conflict resolution.`);
|
|
105
|
+
exitFn(0);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
module.exports = resolveConflict;
|
|
109
|
+
module.exports.buildAgentResolutionPrompt = buildAgentResolutionPrompt;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const review = require('../review/review-commands').review;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Workflow command entry point for `review`.
|
|
5
|
+
* Delegates to the review subsystem (review-commands.js).
|
|
6
|
+
*
|
|
7
|
+
* Usage: node parallix review [<slug>] [--verify] [--submit] [--push] ...
|
|
8
|
+
*/
|
|
9
|
+
async function reviewCommand(args) {
|
|
10
|
+
await review(args);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = reviewCommand;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const setupReview = require('../tools/setup-review');
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Workflow command entry point for `setup-review`.
|
|
5
|
+
* Delegates to the Forgejo bootstrap tool (tools/setup-review.js).
|
|
6
|
+
*
|
|
7
|
+
* Usage: node parallix setup-review
|
|
8
|
+
*/
|
|
9
|
+
async function setupReviewCommand(args, options) {
|
|
10
|
+
await setupReview(args, options);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
module.exports = setupReviewCommand;
|