@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
@@ -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;
@@ -1 +1,3 @@
1
- module.exports = require('../core/verification');
1
+ "use strict";
2
+ const verify = require("../core/verification.js");
3
+ module.exports = verify;
@@ -0,0 +1,2 @@
1
+ import verify = require('../core/verification.js');
2
+ export = verify;
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
- const { styleText } = require('node:util');
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
- reset: 'reset',
11
- bold: 'bold',
12
- red: 'red',
13
- green: 'green',
14
- yellow: 'yellow',
15
- blue: 'blue',
16
- magenta: 'magenta',
17
- cyan: 'cyan',
18
- white: 'white',
19
- dim: 'gray',
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
- PASS: 'green',
24
- FAIL: 'red',
25
- WARN: 'yellow',
26
- INFO: 'cyan',
27
- DEBUG: 'gray',
41
+ PASS: 'green',
42
+ FAIL: 'red',
43
+ WARN: 'yellow',
44
+ INFO: 'cyan',
45
+ DEBUG: 'gray',
28
46
  };
29
-
30
47
  const agentMap = {
31
- codex: 'magenta',
32
- claude: 'blue',
33
- gemini: 'cyan',
34
- custom: 'yellow',
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
- return styleText(format, String(text ?? ''));
54
+ return (0, node_util_1.styleText)(format, String(text ?? ''));
40
55
  }
41
-
42
- /** @param {string} text */
43
56
  function stripAnsi(text) {
44
- return String(text ?? '').replace(/\x1B\[[0-9;]*m/g, '');
57
+ return String(text ?? '').replace(/\x1B\[[0-9;]*m/g, '');
45
58
  }
46
-
47
- /** @param {string} text */
48
59
  function visibleWidth(text) {
49
- return stripAnsi(text).length;
60
+ return stripAnsi(text).length;
50
61
  }
51
-
52
- /** @param {string} text @param {number} width */
53
62
  function padVisibleEnd(text, width) {
54
- const stringValue = String(text ?? '');
55
- const padLength = Math.max(0, width - visibleWidth(stringValue));
56
- return stringValue + ' '.repeat(padLength);
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
- /** @type {Record<string,string>} */
65
- const sm = statusMap;
66
- const format = sm[type];
67
- if (!format) {return `[${type}] ${text}`;}
68
- return `${colorize(/** @type {import('node:util').InspectColor} */(format), `[${type}]`)} ${text}`;
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
- let label = text;
77
- // Disambiguate the opencode-backed `custom` family only when the caller
78
- // passes a distinct display text (e.g. a model id): show `custom (opencode)`
79
- // so the underlying runtime is explicit. When text is just `custom`, render
80
- // it plainly to avoid the redundant `custom (opencode)` suffix.
81
- if (family === 'custom' && text !== 'custom') {
82
- label = 'custom (opencode)';
83
- }
84
- /** @type {Record<string,string>} */
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
- return colorize('bold', text);
86
+ return colorize('bold', text);
94
87
  }
95
-
96
- /** @param {string} text */
97
88
  function dim(text) {
98
- return colorize('gray', text);
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
- return `${bold(key.padEnd(width))} ${value}`;
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
- if (rows.length === 0) {return '';}
115
- /** @param {unknown} _ @param {number} i */
116
- const colWidths = rows[0].map((_, i) => Math.max(...rows.map(row => visibleWidth(row[i] ?? ''))));
117
- const padding = ' '.repeat(indent);
118
- /** @param {string[]} row */
119
- return rows.map(row => padding + row.map((col, i) => padVisibleEnd(col ?? '', colWidths[i] + colPadding)).join('')).join('\n');
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
- const padding = ' '.repeat(indent);
128
- /** @param {string} item */
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
- const semantic = {
133
- path: /** @param {string} text */(text) => colorize('blue', text),
134
- slug: /** @param {string} text */(text) => colorize('cyan', bold(text)),
135
- branch: /** @param {string} text */(text) => colorize('magenta', text),
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
- /** @param {...any} args */
143
- log: (...args) => console.log(...args),
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
- /** @typedef {{log?: Function, error?: Function}} LoggerObj */
149
-
150
- /** @type{{info: Function, pass: Function, fail: Function, warn: Function, error: Function, debug: Function, plain: Function, plainError: Function}} */
151
- const log = {
152
- /** @param {string} text */
153
- info: (text) => {
154
- /** @param {string} line */
155
- return text.toString().split('\n').map(line => {
156
- const s = status('INFO', line);
157
- currentLogger.log(s);
158
- return s;
159
- }).join('\n');
160
- },
161
- /** @param {string} text */
162
- pass: (text) => {
163
- /** @param {string} line */
164
- return text.toString().split('\n').map(line => {
165
- const s = status('PASS', line);
166
- currentLogger.log(s);
167
- return s;
168
- }).join('\n');
169
- },
170
- /** @param {string} text */
171
- fail: (text) => {
172
- /** @param {string} line */
173
- return text.toString().split('\n').map(line => {
174
- const s = status('FAIL', line);
175
- currentLogger.error(s);
176
- return s;
177
- }).join('\n');
178
- },
179
- /** @param {string} text */
180
- warn: (text) => {
181
- /** @param {string} line */
182
- return text.toString().split('\n').map(line => {
183
- const s = status('WARN', line);
184
- currentLogger.log(s);
185
- return s;
186
- }).join('\n');
187
- },
188
- /** @param {string} text */
189
- error: (text) => {
190
- /** @param {string} line */
191
- return text.toString().split('\n').map(line => {
192
- const s = status('FAIL', line);
193
- currentLogger.error(s);
194
- return s;
195
- }).join('\n');
196
- },
197
- /** @param {string} text */
198
- debug: (text) => {
199
- if (!process.env.DEBUG) {return null;}
200
- /** @param {string} line */
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
- const old = currentLogger;
224
- /** @type {LoggerObj} */
225
- const logger = /** @type {LoggerObj} */(newLogger);
226
- currentLogger = {
227
- log: typeof logger.log === 'function' ? logger.log : (() => {}),
228
- error: typeof logger.error === 'function' ? logger.error : (typeof logger.log === 'function' ? logger.log : (() => {})),
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
- };