@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.
- package/index.js +190 -185
- package/lib/agents/agents.js +737 -838
- package/lib/agents/agents.ts +930 -0
- package/lib/agents/claude-telemetry.js +149 -154
- package/lib/agents/claude-telemetry.ts +233 -0
- package/lib/agents/claude.js +121 -131
- package/lib/agents/claude.ts +156 -0
- package/lib/agents/codex-telemetry.js +176 -164
- package/lib/agents/codex-telemetry.ts +205 -0
- package/lib/agents/codex.js +168 -201
- package/lib/agents/codex.ts +236 -0
- package/lib/agents/limit-hit.js +216 -226
- package/lib/agents/limit-hit.ts +262 -0
- package/lib/agents/mistral-telemetry.js +7 -10
- package/lib/agents/mistral-telemetry.ts +44 -0
- package/lib/agents/mistral.js +40 -58
- package/lib/agents/mistral.ts +84 -0
- package/lib/agents/opencode-export.js +126 -127
- package/lib/agents/opencode-export.ts +160 -0
- package/lib/agents/opencode-telemetry.js +282 -261
- package/lib/agents/opencode-telemetry.ts +350 -0
- package/lib/agents/opencode.js +263 -305
- package/lib/agents/opencode.ts +370 -0
- package/lib/agents/stage-telemetry.js +23 -24
- package/lib/agents/stage-telemetry.ts +47 -0
- package/lib/commands/active.js +502 -579
- package/lib/commands/active.ts +665 -0
- package/lib/commands/checkpoint.js +98 -74
- package/lib/commands/checkpoint.ts +79 -0
- package/lib/commands/config.js +61 -31
- package/lib/commands/config.ts +47 -0
- package/lib/commands/coverage-gate.js +271 -246
- package/lib/commands/coverage-gate.ts +362 -0
- package/lib/commands/diff.js +140 -119
- package/lib/commands/diff.ts +125 -0
- package/lib/commands/draft.js +770 -923
- package/lib/commands/draft.ts +1021 -0
- package/lib/commands/handoff.js +588 -608
- package/lib/commands/handoff.ts +694 -0
- package/lib/commands/integrate.js +1332 -1463
- package/lib/commands/integrate.ts +1688 -0
- package/lib/commands/mission-start.js +279 -237
- package/lib/commands/mission-start.ts +263 -0
- package/lib/commands/rebase.js +606 -582
- package/lib/commands/rebase.ts +635 -0
- package/lib/commands/repair-handoff.js +181 -179
- package/lib/commands/repair-handoff.ts +228 -0
- package/lib/commands/resolve-conflict.js +121 -102
- package/lib/commands/resolve-conflict.ts +112 -0
- package/lib/commands/review.js +3 -4
- package/lib/commands/review.ts +14 -0
- package/lib/commands/setup-review.js +4 -5
- package/lib/commands/setup-review.ts +14 -0
- package/lib/commands/setup.js +3 -3
- package/lib/commands/setup.ts +2 -0
- package/lib/commands/stats-backfill.js +394 -359
- package/lib/commands/stats-backfill.ts +418 -0
- package/lib/commands/stats.js +1424 -1665
- package/lib/commands/stats.ts +2186 -0
- package/lib/commands/status.js +219 -200
- package/lib/commands/status.ts +219 -0
- package/lib/commands/verify.js +3 -1
- package/lib/commands/verify.ts +2 -0
- package/lib/core/fmt.js +140 -211
- package/lib/core/fmt.ts +189 -0
- package/lib/core/git.js +121 -102
- package/lib/core/git.ts +146 -0
- package/lib/core/gitignore.js +59 -106
- package/lib/core/gitignore.ts +103 -0
- package/lib/core/mission-utils.js +833 -875
- package/lib/core/mission-utils.ts +998 -0
- package/lib/core/nels.js +112 -127
- package/lib/core/nels.ts +189 -0
- package/lib/core/persistent-data-migration.js +247 -205
- package/lib/core/persistent-data-migration.ts +255 -0
- package/lib/core/product-config.js +400 -476
- package/lib/core/product-config.ts +518 -0
- package/lib/core/runtime-matrix.js +41 -87
- package/lib/core/runtime-matrix.ts +84 -0
- package/lib/core/spawn-tee.js +152 -192
- package/lib/core/spawn-tee.ts +202 -0
- package/lib/core/state-map.js +92 -92
- package/lib/core/state-map.ts +115 -0
- package/lib/core/storage.js +101 -129
- package/lib/core/storage.ts +182 -0
- package/lib/core/subagent-limit.js +15 -16
- package/lib/core/subagent-limit.ts +25 -0
- package/lib/core/verification.js +148 -138
- package/lib/core/verification.ts +200 -0
- package/lib/index.js +142 -59
- package/lib/index.ts +134 -0
- package/lib/review/rebase.js +142 -145
- package/lib/review/rebase.ts +188 -0
- package/lib/review/review-adapter.js +99 -87
- package/lib/review/review-adapter.ts +198 -0
- package/lib/review/review-artifacts.js +486 -629
- package/lib/review/review-artifacts.ts +622 -0
- package/lib/review/review-commands.js +1220 -1373
- package/lib/review/review-commands.ts +1579 -0
- package/lib/review/review-events.js +544 -886
- package/lib/review/review-events.ts +954 -0
- package/lib/review/review-loop.js +945 -1020
- package/lib/review/review-loop.ts +1174 -0
- package/lib/review/review-polling.js +86 -115
- package/lib/review/review-polling.ts +194 -0
- package/lib/review/review-prompts.js +154 -189
- package/lib/review/review-prompts.ts +182 -0
- package/lib/review/review-state.js +246 -225
- package/lib/review/review-state.ts +353 -0
- package/lib/review/review.js +63 -84
- package/lib/review/review.ts +138 -0
- package/lib/tools/backlog.js +694 -680
- package/lib/tools/backlog.ts +835 -0
- package/lib/tools/forgejo.js +1270 -1405
- package/lib/tools/forgejo.ts +1793 -0
- package/lib/tools/gatekeeper.js +106 -96
- package/lib/tools/gatekeeper.ts +124 -0
- package/lib/tools/redgreen.js +200 -181
- package/lib/tools/redgreen.ts +221 -0
- package/lib/tools/sessions.js +58 -52
- package/lib/tools/sessions.ts +81 -0
- package/lib/tools/setup-review.js +902 -975
- package/lib/tools/setup-review.ts +1152 -0
- package/package.json +7 -5
- package/prompts/draft.md +1 -0
- package/prompts/portfolio.md +1 -1
- package/px.js +226 -210
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import { detectRebaseState, getCurrentBranch, getUncommittedCount, getLastThreeCommits, run } from '../core/git.js';
|
|
2
|
+
import { findTaskFile, getTaskStatus } from '../tools/backlog.js';
|
|
3
|
+
import { findMissionDir, findCheckpoints, getFirstLine, inferSlug, missionBranchPrefix, missionBranchName, getPrimaryWorktree } from '../core/mission-utils.js';
|
|
4
|
+
import { WORKFLOW_AGENT_NAMES, eligibleAgentsForStep, readAgentConfigOrExit, workflowLauncherStatus } from '../agents/agents.js';
|
|
5
|
+
import { getPrStatus } from '../tools/forgejo.js';
|
|
6
|
+
import * as path from 'node:path';
|
|
7
|
+
import * as fmt from '../core/fmt.js';
|
|
8
|
+
|
|
9
|
+
/** @param {string} porcelain */
|
|
10
|
+
function parseWorktreeList(porcelain: string) {
|
|
11
|
+
const entries = [];
|
|
12
|
+
/** @type{{path: string, branch: string | null} | null} */
|
|
13
|
+
let current: { path: string; branch: string | null } | null = null;
|
|
14
|
+
|
|
15
|
+
for (const line of porcelain.split('\n')) {
|
|
16
|
+
if (!line.trim()) {
|
|
17
|
+
if (current) {
|
|
18
|
+
entries.push(current);
|
|
19
|
+
current = null;
|
|
20
|
+
}
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (line.startsWith('worktree ')) {
|
|
25
|
+
if (current) {entries.push(current);}
|
|
26
|
+
current = { path: line.slice('worktree '.length).trim(), branch: null };
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (line.startsWith('branch ') && current) {
|
|
31
|
+
current.branch = line.slice('branch '.length).trim() as string;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (current) {entries.push(current);}
|
|
36
|
+
return entries;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function findStaleMissionWorktrees({
|
|
40
|
+
gitRun = run,
|
|
41
|
+
findTaskFileFn = findTaskFile,
|
|
42
|
+
getTaskStatusFn = getTaskStatus,
|
|
43
|
+
primaryWorktree = null
|
|
44
|
+
} = {}) {
|
|
45
|
+
const result = gitRun('git', ['worktree', 'list', '--porcelain']);
|
|
46
|
+
if (result.status !== 0) {
|
|
47
|
+
return [];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** @type {string | null} */
|
|
51
|
+
let resolvedPrimary: string | null = primaryWorktree;
|
|
52
|
+
if (!resolvedPrimary) {
|
|
53
|
+
try {
|
|
54
|
+
resolvedPrimary = getPrimaryWorktree();
|
|
55
|
+
} catch (_) {
|
|
56
|
+
resolvedPrimary = null;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return parseWorktreeList(result.stdout)
|
|
61
|
+
.filter(entry => entry.path !== resolvedPrimary)
|
|
62
|
+
.map(entry => {
|
|
63
|
+
const branchRef = entry.branch || '';
|
|
64
|
+
const prefix = missionBranchPrefix(resolvedPrimary || process.cwd());
|
|
65
|
+
const normalizedRefPrefix = `refs/heads/${prefix}`;
|
|
66
|
+
const match = branchRef.startsWith(normalizedRefPrefix)
|
|
67
|
+
? [branchRef, branchRef.slice(normalizedRefPrefix.length)]
|
|
68
|
+
: null;
|
|
69
|
+
if (!match) {return null;}
|
|
70
|
+
|
|
71
|
+
const slug = match[1];
|
|
72
|
+
const taskFile = findTaskFileFn(slug);
|
|
73
|
+
const taskStatus = taskFile ? getTaskStatusFn(taskFile) : null;
|
|
74
|
+
if (taskStatus !== 'done' && taskFile) {
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
slug,
|
|
80
|
+
path: entry.path,
|
|
81
|
+
branch: branchRef,
|
|
82
|
+
taskStatus: taskStatus || 'missing',
|
|
83
|
+
cleanupCommand: taskStatus === 'done'
|
|
84
|
+
? `scripts/cleanup-mission-worktree.sh ${slug}`
|
|
85
|
+
: `git worktree remove ${entry.path} && git branch -D ${missionBranchName(slug, resolvedPrimary || process.cwd())}`
|
|
86
|
+
};
|
|
87
|
+
})
|
|
88
|
+
.filter(Boolean);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** @param {string} ref */
|
|
92
|
+
function formatWorktreeBranch(ref: string) {
|
|
93
|
+
if (!ref) {return '(detached HEAD)';}
|
|
94
|
+
return ref.replace(/^refs\/heads\//, '');
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** @param {Function} log @param {string} label @param {{detached?: boolean, unmergedFiles: string[]}} rebaseState */
|
|
98
|
+
function logRebaseDiagnostics(log: Function, label: string, rebaseState: {detached?: boolean, unmergedFiles: string[]}) {
|
|
99
|
+
const detachedText = rebaseState.detached ? 'detached HEAD, ' : '';
|
|
100
|
+
log(`${label}: ${detachedText}${rebaseState.unmergedFiles.length} unmerged file(s)`);
|
|
101
|
+
rebaseState.unmergedFiles.forEach(file => {
|
|
102
|
+
log(` - ${file}`);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** @param {string[]} args @param {{exit?: Function, log?: Function, inferSlugFn?: Function, getCurrentBranchFn?: Function, findTaskFileFn?: Function, getTaskStatusFn?: Function, findMissionDirFn?: Function, findCheckpointsFn?: Function, getFirstLineFn?: Function, getPrStatusFn?: Function, findStaleMissionWorktreesFn?: Function, readAgentConfigOrExitFn?: Function, eligibleAgentsForStepFn?: Function, allWorkflowAgentNamesFn?: Function, workflowLauncherStatusFn?: Function, getLastThreeCommitsFn?: Function, getUncommittedCountFn?: Function, detectRebaseStateFn?: Function}} opts */
|
|
107
|
+
function status(args: string[], opts: {exit?: Function, log?: Function, inferSlugFn?: Function, getCurrentBranchFn?: Function, findTaskFileFn?: Function, getTaskStatusFn?: Function, findMissionDirFn?: Function, findCheckpointsFn?: Function, getFirstLineFn?: Function, getPrStatusFn?: Function, findStaleMissionWorktreesFn?: Function, readAgentConfigOrExitFn?: Function, eligibleAgentsForStepFn?: Function, allWorkflowAgentNamesFn?: Function, workflowLauncherStatusFn?: Function, getLastThreeCommitsFn?: Function, getUncommittedCountFn?: Function, detectRebaseStateFn?: Function}) {
|
|
108
|
+
const exit = opts.exit || process.exit;
|
|
109
|
+
const log = opts.log || fmt.log.plain;
|
|
110
|
+
const inferSlugFn = opts.inferSlugFn || inferSlug;
|
|
111
|
+
const getCurrentBranchFn = opts.getCurrentBranchFn || getCurrentBranch;
|
|
112
|
+
const findTaskFileFn = opts.findTaskFileFn || findTaskFile;
|
|
113
|
+
const getTaskStatusFn = opts.getTaskStatusFn || getTaskStatus;
|
|
114
|
+
const findMissionDirFn = opts.findMissionDirFn || findMissionDir;
|
|
115
|
+
const findCheckpointsFn = opts.findCheckpointsFn || findCheckpoints;
|
|
116
|
+
const getFirstLineFn = opts.getFirstLineFn || getFirstLine;
|
|
117
|
+
const getPrStatusFn = opts.getPrStatusFn || getPrStatus;
|
|
118
|
+
const findStaleMissionWorktreesFn = opts.findStaleMissionWorktreesFn || findStaleMissionWorktrees;
|
|
119
|
+
const readAgentConfigOrExitFn = opts.readAgentConfigOrExitFn || readAgentConfigOrExit;
|
|
120
|
+
const eligibleAgentsForStepFn = opts.eligibleAgentsForStepFn || eligibleAgentsForStep;
|
|
121
|
+
const allWorkflowAgentNamesFn = opts.allWorkflowAgentNamesFn || (() => WORKFLOW_AGENT_NAMES);
|
|
122
|
+
const workflowLauncherStatusFn = opts.workflowLauncherStatusFn || workflowLauncherStatus;
|
|
123
|
+
const getLastThreeCommitsFn = opts.getLastThreeCommitsFn || getLastThreeCommits;
|
|
124
|
+
const getUncommittedCountFn = opts.getUncommittedCountFn || getUncommittedCount;
|
|
125
|
+
const detectRebaseStateFn = opts.detectRebaseStateFn || detectRebaseState;
|
|
126
|
+
|
|
127
|
+
const explicitSlug = args[0];
|
|
128
|
+
const slug = inferSlugFn(explicitSlug);
|
|
129
|
+
|
|
130
|
+
log(fmt.bold('--- Mission Status ---'));
|
|
131
|
+
log(`Branch: ${fmt.branch(getCurrentBranchFn())}`);
|
|
132
|
+
log(`Worktree: ${fmt.path(process.cwd())}`);
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
const rebaseState = detectRebaseStateFn(process.cwd());
|
|
136
|
+
if (rebaseState.inProgress && rebaseState.detached) {
|
|
137
|
+
logRebaseDiagnostics(log, 'Detached HEAD: rebase in progress', rebaseState);
|
|
138
|
+
}
|
|
139
|
+
} catch (_) {
|
|
140
|
+
// Ignore transient worktree/git-state failures in status output.
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (slug) {
|
|
144
|
+
const taskFile = findTaskFileFn(slug);
|
|
145
|
+
const taskStatus = getTaskStatusFn(taskFile);
|
|
146
|
+
log(`Backlog status: ${taskStatus || 'unknown'}`);
|
|
147
|
+
|
|
148
|
+
const missionDir = findMissionDirFn(slug);
|
|
149
|
+
if (missionDir) {
|
|
150
|
+
const checkpoints = findCheckpointsFn(missionDir);
|
|
151
|
+
if (checkpoints.length > 0) {
|
|
152
|
+
const lastCP = checkpoints[checkpoints.length - 1];
|
|
153
|
+
const firstLine = getFirstLineFn(lastCP);
|
|
154
|
+
log(`Last checkpoint: ${path.basename(lastCP)} - ${firstLine}`);
|
|
155
|
+
} else {
|
|
156
|
+
log('Last checkpoint: none');
|
|
157
|
+
}
|
|
158
|
+
} else {
|
|
159
|
+
log('Last checkpoint: unknown');
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Forgejo PR state
|
|
163
|
+
const pr = getPrStatusFn(missionBranchName(slug));
|
|
164
|
+
if (pr.exists) {
|
|
165
|
+
log(`Forgejo PR: #${pr.number} (${pr.state})`);
|
|
166
|
+
} else if (pr.raw) {
|
|
167
|
+
log(`Forgejo PR: unavailable (${pr.raw})`);
|
|
168
|
+
} else {
|
|
169
|
+
log('Forgejo PR: none');
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (!explicitSlug) {
|
|
174
|
+
const staleWorktrees = findStaleMissionWorktreesFn();
|
|
175
|
+
staleWorktrees.forEach((entry: { path: string; branch: string | null; taskStatus: string | null; cleanupCommand?: string }) => {
|
|
176
|
+
log(`Stale worktree: ${fmt.path(entry.path)} (task: ${entry.taskStatus})`);
|
|
177
|
+
try {
|
|
178
|
+
const rebaseState = detectRebaseStateFn(entry.path);
|
|
179
|
+
if (rebaseState.inProgress) {
|
|
180
|
+
logRebaseDiagnostics(log, `Rebase in progress on ${formatWorktreeBranch(entry.branch || '')}`, rebaseState);
|
|
181
|
+
}
|
|
182
|
+
} catch (_) {
|
|
183
|
+
// Ignore stale worktrees that disappear during inspection.
|
|
184
|
+
}
|
|
185
|
+
log(` Cleanup: ${fmt.command(entry.cleanupCommand || '')}`);
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Agent eligibility matrix
|
|
190
|
+
const config = readAgentConfigOrExitFn();
|
|
191
|
+
const draftEligible = eligibleAgentsForStepFn('draft', { config });
|
|
192
|
+
const activeEligible = eligibleAgentsForStepFn('active', { config });
|
|
193
|
+
const baseAgents = allWorkflowAgentNamesFn();
|
|
194
|
+
const allAgents = [...new Set([...baseAgents, ...draftEligible, ...activeEligible])].sort();
|
|
195
|
+
const envOverride = process.env.WORKFLOW_AGENT;
|
|
196
|
+
log('Agent launcher matrix:');
|
|
197
|
+
for (const agent of allAgents) {
|
|
198
|
+
const launcher = workflowLauncherStatusFn(agent);
|
|
199
|
+
const support = launcher.supported ? 'supported' : 'blocked';
|
|
200
|
+
const draftMark = draftEligible.includes(agent) ? 'draft' : '-';
|
|
201
|
+
const activeMark = activeEligible.includes(agent) ? 'active' : '-';
|
|
202
|
+
log(` ${fmt.agent(agent)}: ${support} | eligible: ${draftMark},${activeMark}`);
|
|
203
|
+
}
|
|
204
|
+
if (envOverride) {
|
|
205
|
+
log(` (WORKFLOW_AGENT override: ${fmt.agent(envOverride)})`);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
const lastThree = getLastThreeCommitsFn();
|
|
209
|
+
log('Last 3 commits:');
|
|
210
|
+
lastThree.forEach((c: string) => log(` - ${c}`));
|
|
211
|
+
|
|
212
|
+
log(`Uncommitted files: ${getUncommittedCountFn()}`);
|
|
213
|
+
log(fmt.bold('----------------------'));
|
|
214
|
+
exit(0);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
(status as any).parseWorktreeList = parseWorktreeList;
|
|
218
|
+
(status as any).findStaleMissionWorktrees = findStaleMissionWorktrees;
|
|
219
|
+
export = status;
|
package/lib/commands/verify.js
CHANGED
package/lib/core/fmt.js
CHANGED
|
@@ -1,249 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
1
2
|
/**
|
|
2
3
|
* Shared formatting and palette layer for workflow-owned output.
|
|
3
4
|
* Uses Node.js built-in util.styleText for color rendering (ADR 0042).
|
|
4
5
|
* No external dependencies.
|
|
5
6
|
*/
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
8
|
+
exports.colors = exports.log = void 0;
|
|
9
|
+
exports.colorize = colorize;
|
|
10
|
+
exports.stripAnsi = stripAnsi;
|
|
11
|
+
exports.visibleWidth = visibleWidth;
|
|
12
|
+
exports.padVisibleEnd = padVisibleEnd;
|
|
13
|
+
exports.status = status;
|
|
14
|
+
exports.agent = agent;
|
|
15
|
+
exports.bold = bold;
|
|
16
|
+
exports.dim = dim;
|
|
17
|
+
exports.kv = kv;
|
|
18
|
+
exports.table = table;
|
|
19
|
+
exports.list = list;
|
|
20
|
+
exports.path = path;
|
|
21
|
+
exports.slug = slug;
|
|
22
|
+
exports.branch = branch;
|
|
23
|
+
exports.sha = sha;
|
|
24
|
+
exports.command = command;
|
|
25
|
+
exports.setLogger = setLogger;
|
|
26
|
+
const node_util_1 = require("node:util");
|
|
9
27
|
const colors = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
28
|
+
reset: 'reset',
|
|
29
|
+
bold: 'bold',
|
|
30
|
+
red: 'red',
|
|
31
|
+
green: 'green',
|
|
32
|
+
yellow: 'yellow',
|
|
33
|
+
blue: 'blue',
|
|
34
|
+
magenta: 'magenta',
|
|
35
|
+
cyan: 'cyan',
|
|
36
|
+
white: 'white',
|
|
37
|
+
dim: 'gray',
|
|
20
38
|
};
|
|
21
|
-
|
|
39
|
+
exports.colors = colors;
|
|
22
40
|
const statusMap = {
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
PASS: 'green',
|
|
42
|
+
FAIL: 'red',
|
|
43
|
+
WARN: 'yellow',
|
|
44
|
+
INFO: 'cyan',
|
|
45
|
+
DEBUG: 'gray',
|
|
28
46
|
};
|
|
29
|
-
|
|
30
47
|
const agentMap = {
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
48
|
+
codex: 'magenta',
|
|
49
|
+
claude: 'blue',
|
|
50
|
+
gemini: 'cyan',
|
|
51
|
+
custom: 'yellow',
|
|
35
52
|
};
|
|
36
|
-
|
|
37
|
-
/** @param {import('node:util').InspectColor} format @param {string} text */
|
|
38
53
|
function colorize(format, text) {
|
|
39
|
-
|
|
54
|
+
return (0, node_util_1.styleText)(format, String(text ?? ''));
|
|
40
55
|
}
|
|
41
|
-
|
|
42
|
-
/** @param {string} text */
|
|
43
56
|
function stripAnsi(text) {
|
|
44
|
-
|
|
57
|
+
return String(text ?? '').replace(/\x1B\[[0-9;]*m/g, '');
|
|
45
58
|
}
|
|
46
|
-
|
|
47
|
-
/** @param {string} text */
|
|
48
59
|
function visibleWidth(text) {
|
|
49
|
-
|
|
60
|
+
return stripAnsi(text).length;
|
|
50
61
|
}
|
|
51
|
-
|
|
52
|
-
/** @param {string} text @param {number} width */
|
|
53
62
|
function padVisibleEnd(text, width) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
63
|
+
const stringValue = String(text ?? '');
|
|
64
|
+
const padLength = Math.max(0, width - visibleWidth(stringValue));
|
|
65
|
+
return stringValue + ' '.repeat(padLength);
|
|
57
66
|
}
|
|
58
|
-
|
|
59
|
-
/**
|
|
60
|
-
* Formats a status line prefix (e.g. "[PASS]").
|
|
61
|
-
*/
|
|
62
|
-
/** @param {string} type @param {string} text */
|
|
63
67
|
function status(type, text) {
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
68
|
+
const format = statusMap[type];
|
|
69
|
+
if (!format) {
|
|
70
|
+
return `[${type}] ${text}`;
|
|
71
|
+
}
|
|
72
|
+
return `${colorize(format, `[${type}]`)} ${text}`;
|
|
69
73
|
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Formats an agent family name.
|
|
73
|
-
*/
|
|
74
|
-
/** @param {string} family @param {string} [text] */
|
|
75
74
|
function agent(family, text = family) {
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
const am = agentMap;
|
|
86
|
-
const format = am[family];
|
|
87
|
-
if (!format) {return label;}
|
|
88
|
-
return colorize(/** @type {import('node:util').InspectColor} */(format), label);
|
|
75
|
+
let label = text;
|
|
76
|
+
if (family === 'custom' && text !== 'custom') {
|
|
77
|
+
label = 'custom (opencode)';
|
|
78
|
+
}
|
|
79
|
+
const format = agentMap[family];
|
|
80
|
+
if (!format) {
|
|
81
|
+
return label;
|
|
82
|
+
}
|
|
83
|
+
return colorize(format, label);
|
|
89
84
|
}
|
|
90
|
-
|
|
91
|
-
/** @param {string} text */
|
|
92
85
|
function bold(text) {
|
|
93
|
-
|
|
86
|
+
return colorize('bold', text);
|
|
94
87
|
}
|
|
95
|
-
|
|
96
|
-
/** @param {string} text */
|
|
97
88
|
function dim(text) {
|
|
98
|
-
|
|
89
|
+
return colorize('gray', text);
|
|
99
90
|
}
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
* Aligned key-value pair.
|
|
103
|
-
*/
|
|
104
|
-
/** @param {string} key @param {string} value @param {number} width */
|
|
105
91
|
function kv(key, value, width = 30) {
|
|
106
|
-
|
|
92
|
+
return `${bold(key.padEnd(width))} ${value}`;
|
|
107
93
|
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Aligned table of columns.
|
|
111
|
-
*/
|
|
112
|
-
/** @param {string[][]} rows @param {{indent?: number, colPadding?: number}} [opts] */
|
|
113
94
|
function table(rows, { indent = 2, colPadding = 2 } = {}) {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
95
|
+
if (rows.length === 0) {
|
|
96
|
+
return '';
|
|
97
|
+
}
|
|
98
|
+
const colWidths = rows[0].map((_, i) => Math.max(...rows.map(row => visibleWidth(row[i] ?? ''))));
|
|
99
|
+
const padding = ' '.repeat(indent);
|
|
100
|
+
return rows.map(row => padding + row.map((col, i) => padVisibleEnd(col ?? '', colWidths[i] + colPadding)).join('')).join('\n');
|
|
120
101
|
}
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Bulleted list.
|
|
124
|
-
*/
|
|
125
|
-
/** @param {string[]} items @param {{bullet?: string, indent?: number}} [opts] */
|
|
126
102
|
function list(items, { bullet = '-', indent = 2 } = {}) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
return items.map(item => `${padding}${bullet} ${item}`).join('\n');
|
|
103
|
+
const padding = ' '.repeat(indent);
|
|
104
|
+
return items.map(item => `${padding}${bullet} ${item}`).join('\n');
|
|
130
105
|
}
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
sha: /** @param {string} text */(text) => colorize('yellow', text),
|
|
137
|
-
command: /** @param {string} text */(text) => colorize('green', text),
|
|
138
|
-
};
|
|
139
|
-
|
|
140
|
-
/** @type {{log: Function, error: Function}} */
|
|
106
|
+
function path(text) { return colorize('blue', text); }
|
|
107
|
+
function slug(text) { return colorize('cyan', bold(text)); }
|
|
108
|
+
function branch(text) { return colorize('magenta', text); }
|
|
109
|
+
function sha(text) { return colorize('yellow', text); }
|
|
110
|
+
function command(text) { return colorize('green', text); }
|
|
141
111
|
let currentLogger = {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
/** @param {...any} args */
|
|
145
|
-
error: (...args) => console.error(...args),
|
|
112
|
+
log: (...args) => console.log(...args),
|
|
113
|
+
error: (...args) => console.error(...args),
|
|
146
114
|
};
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
return text.toString().split('\n').map(line => {
|
|
202
|
-
const s = status('DEBUG', line);
|
|
203
|
-
currentLogger.log(s);
|
|
204
|
-
return s;
|
|
205
|
-
}).join('\n');
|
|
206
|
-
},
|
|
207
|
-
/** @param {string} text */
|
|
208
|
-
plain: (text) => {
|
|
209
|
-
/** @param {string} line */
|
|
210
|
-
text.toString().split('\n').forEach(line => currentLogger.log(line));
|
|
211
|
-
return text;
|
|
212
|
-
},
|
|
213
|
-
/** @param {string} text */
|
|
214
|
-
plainError: (text) => {
|
|
215
|
-
/** @param {string} line */
|
|
216
|
-
text.toString().split('\n').forEach(line => currentLogger.error(line));
|
|
217
|
-
return text;
|
|
218
|
-
},
|
|
115
|
+
exports.log = {
|
|
116
|
+
info: (text) => {
|
|
117
|
+
return text.toString().split('\n').map(line => {
|
|
118
|
+
const s = status('INFO', line);
|
|
119
|
+
currentLogger.log(s);
|
|
120
|
+
return s;
|
|
121
|
+
}).join('\n');
|
|
122
|
+
},
|
|
123
|
+
pass: (text) => {
|
|
124
|
+
return text.toString().split('\n').map(line => {
|
|
125
|
+
const s = status('PASS', line);
|
|
126
|
+
currentLogger.log(s);
|
|
127
|
+
return s;
|
|
128
|
+
}).join('\n');
|
|
129
|
+
},
|
|
130
|
+
fail: (text) => {
|
|
131
|
+
return text.toString().split('\n').map(line => {
|
|
132
|
+
const s = status('FAIL', line);
|
|
133
|
+
currentLogger.error(s);
|
|
134
|
+
return s;
|
|
135
|
+
}).join('\n');
|
|
136
|
+
},
|
|
137
|
+
warn: (text) => {
|
|
138
|
+
return text.toString().split('\n').map(line => {
|
|
139
|
+
const s = status('WARN', line);
|
|
140
|
+
currentLogger.log(s);
|
|
141
|
+
return s;
|
|
142
|
+
}).join('\n');
|
|
143
|
+
},
|
|
144
|
+
error: (text) => {
|
|
145
|
+
return text.toString().split('\n').map(line => {
|
|
146
|
+
const s = status('FAIL', line);
|
|
147
|
+
currentLogger.error(s);
|
|
148
|
+
return s;
|
|
149
|
+
}).join('\n');
|
|
150
|
+
},
|
|
151
|
+
debug: (text) => {
|
|
152
|
+
if (!process.env.DEBUG) {
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
return text.toString().split('\n').map(line => {
|
|
156
|
+
const s = status('DEBUG', line);
|
|
157
|
+
currentLogger.log(s);
|
|
158
|
+
return s;
|
|
159
|
+
}).join('\n');
|
|
160
|
+
},
|
|
161
|
+
plain: (text) => {
|
|
162
|
+
text.toString().split('\n').forEach(line => currentLogger.log(line));
|
|
163
|
+
return text;
|
|
164
|
+
},
|
|
165
|
+
plainError: (text) => {
|
|
166
|
+
text.toString().split('\n').forEach(line => currentLogger.error(line));
|
|
167
|
+
return text;
|
|
168
|
+
},
|
|
219
169
|
};
|
|
220
|
-
|
|
221
|
-
/** @param {LoggerObj|Function} newLogger */
|
|
222
170
|
function setLogger(newLogger) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
return old;
|
|
171
|
+
const old = currentLogger;
|
|
172
|
+
const logger = newLogger;
|
|
173
|
+
currentLogger = {
|
|
174
|
+
log: typeof logger.log === 'function' ? logger.log : (() => { }),
|
|
175
|
+
error: typeof logger.error === 'function' ? logger.error : (typeof logger.log === 'function' ? logger.log : (() => { })),
|
|
176
|
+
};
|
|
177
|
+
return old;
|
|
231
178
|
}
|
|
232
|
-
|
|
233
|
-
module.exports = {
|
|
234
|
-
colors,
|
|
235
|
-
status,
|
|
236
|
-
agent,
|
|
237
|
-
bold,
|
|
238
|
-
dim,
|
|
239
|
-
colorize,
|
|
240
|
-
stripAnsi,
|
|
241
|
-
visibleWidth,
|
|
242
|
-
padVisibleEnd,
|
|
243
|
-
kv,
|
|
244
|
-
table,
|
|
245
|
-
list,
|
|
246
|
-
...semantic,
|
|
247
|
-
log,
|
|
248
|
-
setLogger,
|
|
249
|
-
};
|