@magnusekdahl/parallix 1.2.0 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (127) hide show
  1. package/index.js +190 -185
  2. package/lib/agents/agents.js +737 -838
  3. package/lib/agents/agents.ts +930 -0
  4. package/lib/agents/claude-telemetry.js +149 -154
  5. package/lib/agents/claude-telemetry.ts +233 -0
  6. package/lib/agents/claude.js +121 -131
  7. package/lib/agents/claude.ts +156 -0
  8. package/lib/agents/codex-telemetry.js +176 -164
  9. package/lib/agents/codex-telemetry.ts +205 -0
  10. package/lib/agents/codex.js +168 -201
  11. package/lib/agents/codex.ts +236 -0
  12. package/lib/agents/limit-hit.js +216 -226
  13. package/lib/agents/limit-hit.ts +262 -0
  14. package/lib/agents/mistral-telemetry.js +7 -10
  15. package/lib/agents/mistral-telemetry.ts +44 -0
  16. package/lib/agents/mistral.js +40 -58
  17. package/lib/agents/mistral.ts +84 -0
  18. package/lib/agents/opencode-export.js +126 -127
  19. package/lib/agents/opencode-export.ts +160 -0
  20. package/lib/agents/opencode-telemetry.js +282 -261
  21. package/lib/agents/opencode-telemetry.ts +350 -0
  22. package/lib/agents/opencode.js +263 -305
  23. package/lib/agents/opencode.ts +370 -0
  24. package/lib/agents/stage-telemetry.js +23 -24
  25. package/lib/agents/stage-telemetry.ts +47 -0
  26. package/lib/commands/active.js +502 -579
  27. package/lib/commands/active.ts +665 -0
  28. package/lib/commands/checkpoint.js +98 -74
  29. package/lib/commands/checkpoint.ts +79 -0
  30. package/lib/commands/config.js +61 -31
  31. package/lib/commands/config.ts +47 -0
  32. package/lib/commands/coverage-gate.js +271 -246
  33. package/lib/commands/coverage-gate.ts +362 -0
  34. package/lib/commands/diff.js +140 -119
  35. package/lib/commands/diff.ts +125 -0
  36. package/lib/commands/draft.js +770 -923
  37. package/lib/commands/draft.ts +1021 -0
  38. package/lib/commands/handoff.js +588 -608
  39. package/lib/commands/handoff.ts +694 -0
  40. package/lib/commands/integrate.js +1332 -1463
  41. package/lib/commands/integrate.ts +1688 -0
  42. package/lib/commands/mission-start.js +279 -237
  43. package/lib/commands/mission-start.ts +263 -0
  44. package/lib/commands/rebase.js +606 -582
  45. package/lib/commands/rebase.ts +635 -0
  46. package/lib/commands/repair-handoff.js +181 -179
  47. package/lib/commands/repair-handoff.ts +228 -0
  48. package/lib/commands/resolve-conflict.js +121 -102
  49. package/lib/commands/resolve-conflict.ts +112 -0
  50. package/lib/commands/review.js +3 -4
  51. package/lib/commands/review.ts +14 -0
  52. package/lib/commands/setup-review.js +4 -5
  53. package/lib/commands/setup-review.ts +14 -0
  54. package/lib/commands/setup.js +3 -3
  55. package/lib/commands/setup.ts +2 -0
  56. package/lib/commands/stats-backfill.js +394 -359
  57. package/lib/commands/stats-backfill.ts +418 -0
  58. package/lib/commands/stats.js +1424 -1665
  59. package/lib/commands/stats.ts +2186 -0
  60. package/lib/commands/status.js +219 -200
  61. package/lib/commands/status.ts +219 -0
  62. package/lib/commands/verify.js +3 -1
  63. package/lib/commands/verify.ts +2 -0
  64. package/lib/core/fmt.js +140 -211
  65. package/lib/core/fmt.ts +189 -0
  66. package/lib/core/git.js +121 -102
  67. package/lib/core/git.ts +146 -0
  68. package/lib/core/gitignore.js +59 -106
  69. package/lib/core/gitignore.ts +103 -0
  70. package/lib/core/mission-utils.js +833 -875
  71. package/lib/core/mission-utils.ts +998 -0
  72. package/lib/core/nels.js +112 -127
  73. package/lib/core/nels.ts +189 -0
  74. package/lib/core/persistent-data-migration.js +247 -205
  75. package/lib/core/persistent-data-migration.ts +255 -0
  76. package/lib/core/product-config.js +400 -476
  77. package/lib/core/product-config.ts +518 -0
  78. package/lib/core/runtime-matrix.js +41 -87
  79. package/lib/core/runtime-matrix.ts +84 -0
  80. package/lib/core/spawn-tee.js +152 -192
  81. package/lib/core/spawn-tee.ts +202 -0
  82. package/lib/core/state-map.js +92 -92
  83. package/lib/core/state-map.ts +115 -0
  84. package/lib/core/storage.js +101 -129
  85. package/lib/core/storage.ts +182 -0
  86. package/lib/core/subagent-limit.js +15 -16
  87. package/lib/core/subagent-limit.ts +25 -0
  88. package/lib/core/verification.js +148 -138
  89. package/lib/core/verification.ts +200 -0
  90. package/lib/index.js +142 -59
  91. package/lib/index.ts +134 -0
  92. package/lib/review/rebase.js +142 -145
  93. package/lib/review/rebase.ts +188 -0
  94. package/lib/review/review-adapter.js +99 -87
  95. package/lib/review/review-adapter.ts +198 -0
  96. package/lib/review/review-artifacts.js +486 -629
  97. package/lib/review/review-artifacts.ts +622 -0
  98. package/lib/review/review-commands.js +1220 -1373
  99. package/lib/review/review-commands.ts +1579 -0
  100. package/lib/review/review-events.js +544 -886
  101. package/lib/review/review-events.ts +954 -0
  102. package/lib/review/review-loop.js +945 -1020
  103. package/lib/review/review-loop.ts +1174 -0
  104. package/lib/review/review-polling.js +86 -115
  105. package/lib/review/review-polling.ts +194 -0
  106. package/lib/review/review-prompts.js +154 -189
  107. package/lib/review/review-prompts.ts +182 -0
  108. package/lib/review/review-state.js +246 -225
  109. package/lib/review/review-state.ts +353 -0
  110. package/lib/review/review.js +63 -84
  111. package/lib/review/review.ts +138 -0
  112. package/lib/tools/backlog.js +694 -680
  113. package/lib/tools/backlog.ts +835 -0
  114. package/lib/tools/forgejo.js +1270 -1405
  115. package/lib/tools/forgejo.ts +1793 -0
  116. package/lib/tools/gatekeeper.js +106 -96
  117. package/lib/tools/gatekeeper.ts +124 -0
  118. package/lib/tools/redgreen.js +200 -181
  119. package/lib/tools/redgreen.ts +221 -0
  120. package/lib/tools/sessions.js +58 -52
  121. package/lib/tools/sessions.ts +81 -0
  122. package/lib/tools/setup-review.js +902 -975
  123. package/lib/tools/setup-review.ts +1152 -0
  124. package/package.json +7 -5
  125. package/prompts/draft.md +1 -0
  126. package/prompts/portfolio.md +1 -1
  127. package/px.js +226 -210
@@ -1,10 +1,13 @@
1
- 'use strict';
2
-
3
- const childProcess = require('child_process');
4
- const fs = require('fs');
5
- const os = require('os');
6
- const path = require('path');
7
-
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.captureOpencodeExport = captureOpencodeExport;
7
+ const node_child_process_1 = __importDefault(require("node:child_process"));
8
+ const node_fs_1 = __importDefault(require("node:fs"));
9
+ const node_os_1 = __importDefault(require("node:os"));
10
+ const node_path_1 = __importDefault(require("node:path"));
8
11
  /**
9
12
  * Capture the complete `opencode export <sessionId>` JSON document.
10
13
  *
@@ -35,125 +38,121 @@ const path = require('path');
35
38
  * @returns {Promise<string|null>} Full JSON string, or null on failure
36
39
  */
37
40
  function captureOpencodeExport(sessionId, opts = {}) {
38
- const {
39
- worktree,
40
- env,
41
- timeoutMs = 30000,
42
- maxBytes = 32 * 1024 * 1024,
43
- spawn = childProcess.spawn,
44
- } = opts;
45
-
46
- return new Promise((resolve) => {
47
- if (!sessionId) {
48
- resolve(null);
49
- return;
50
- }
51
-
52
- let settled = false;
53
- /** @type {NodeJS.Timeout | null} */
54
- let timer = null;
55
- let size = 0;
56
- /** @type {number | null} */
57
- let tmpFd = null;
58
- /** @type {string | null} */
59
- let tmpPath = null;
60
-
61
- /** @param {string | null} value */
62
- const finish = (value) => {
63
- if (settled) {return;}
64
- settled = true;
65
- if (timer) {clearTimeout(timer);}
66
- // Clean up temp file
67
- if (tmpFd !== null) {
68
- try { fs.closeSync(tmpFd); } catch (_) { /* already closed */ }
69
- }
70
- if (tmpPath) {
71
- try { fs.unlinkSync(tmpPath); } catch (_) { /* already gone */ }
72
- }
73
- resolve(value);
74
- };
75
-
76
- /** @param {import('child_process').ChildProcess | undefined} child */
77
- const killChild = (child) => {
78
- try { if (child) { child.kill('SIGKILL'); } } catch (_) { /* already gone */ }
79
- };
80
-
81
- // Create a temp file for stdout to avoid pipe-buffer truncation
82
- let tmpFileError = null;
83
- try {
84
- tmpPath = path.join(os.tmpdir(), `opencode-export-${process.pid}-${Date.now()}.json`);
85
- tmpFd = fs.openSync(tmpPath, 'w');
86
- } catch (e) {
87
- tmpFileError = e;
88
- }
89
-
90
- let child;
91
- try {
92
- child = spawn('opencode', ['export', sessionId], {
93
- cwd: worktree,
94
- env: { ...process.env, ...(env || {}) },
95
- stdio: ['ignore', tmpFd !== null ? tmpFd : 'pipe', 'pipe'],
96
- });
97
- } catch (_) {
98
- finish(null);
99
- return;
100
- }
101
-
102
- if (tmpFileError) {
103
- killChild(child);
104
- finish(null);
105
- return;
106
- }
107
-
108
- timer = setTimeout(() => {
109
- // Hung or slow export: kill it and degrade to null. Do not block.
110
- killChild(child);
111
- finish(null);
112
- }, timeoutMs);
113
- // Intentionally NOT unref'd: the caller always awaits this Promise, and the
114
- // timer is cleared via finish() the moment the export resolves. Leaving it
115
- // ref'd keeps the timeout deterministic under Node's test runner (an unref'd
116
- // timer can be skipped when the loop is otherwise idle, cancelling the test).
117
-
118
- // Consume stderr to prevent pipe-buffer issues with the opencode binary
119
- if (child.stderr) {
120
- child.stderr.on('data', () => { /* drain stderr */ });
121
- }
122
-
123
- child.on('error', () => finish(null));
124
-
125
- child.on('close', () => {
126
- if (settled) {return;}
127
- if (tmpFd === null) {
128
- finish(null);
129
- return;
130
- }
131
-
132
- try {
133
- fs.closeSync(tmpFd);
134
- } catch (_) { /* already closed */ }
135
- tmpFd = null;
136
-
137
- let content;
138
- if (!tmpPath) { finish(null); return; }
139
- try {
140
- content = fs.readFileSync(tmpPath, 'utf8');
141
- } catch (_) {
142
- finish(null);
143
- return;
144
- }
145
-
146
- size = Buffer.byteLength(content);
147
- if (size > maxBytes) {
148
- // Explicit failure rather than silent truncation: a partial JSON
149
- // document would parse to null/garbage and fabricate zero telemetry.
150
- finish(null);
151
- return;
152
- }
153
-
154
- finish(content);
41
+ const { worktree, env, timeoutMs = 30000, maxBytes = 32 * 1024 * 1024, spawn = node_child_process_1.default.spawn, } = opts;
42
+ return new Promise((resolve) => {
43
+ if (!sessionId) {
44
+ resolve(null);
45
+ return;
46
+ }
47
+ let settled = false;
48
+ let timer = null;
49
+ let size = 0;
50
+ let tmpFd = null;
51
+ let tmpPath = null;
52
+ const finish = (value) => {
53
+ if (settled) {
54
+ return;
55
+ }
56
+ settled = true;
57
+ if (timer) {
58
+ clearTimeout(timer);
59
+ }
60
+ // Clean up temp file
61
+ if (tmpFd !== null) {
62
+ try {
63
+ node_fs_1.default.closeSync(tmpFd);
64
+ }
65
+ catch (_) { /* already closed */ }
66
+ }
67
+ if (tmpPath) {
68
+ try {
69
+ node_fs_1.default.unlinkSync(tmpPath);
70
+ }
71
+ catch (_) { /* already gone */ }
72
+ }
73
+ resolve(value);
74
+ };
75
+ const killChild = (child) => {
76
+ try {
77
+ if (child) {
78
+ child.kill('SIGKILL');
79
+ }
80
+ }
81
+ catch (_) { /* already gone */ }
82
+ };
83
+ // Create a temp file for stdout to avoid pipe-buffer truncation
84
+ let tmpFileError = null;
85
+ try {
86
+ tmpPath = node_path_1.default.join(node_os_1.default.tmpdir(), `opencode-export-${process.pid}-${Date.now()}.json`);
87
+ tmpFd = node_fs_1.default.openSync(tmpPath, 'w');
88
+ }
89
+ catch (e) {
90
+ tmpFileError = e;
91
+ }
92
+ let child;
93
+ try {
94
+ child = spawn('opencode', ['export', sessionId], {
95
+ cwd: worktree,
96
+ env: { ...process.env, ...(env || {}) },
97
+ stdio: ['ignore', tmpFd !== null ? tmpFd : 'pipe', 'pipe'],
98
+ });
99
+ }
100
+ catch (_) {
101
+ finish(null);
102
+ return;
103
+ }
104
+ if (tmpFileError) {
105
+ killChild(child);
106
+ finish(null);
107
+ return;
108
+ }
109
+ timer = setTimeout(() => {
110
+ // Hung or slow export: kill it and degrade to null. Do not block.
111
+ killChild(child);
112
+ finish(null);
113
+ }, timeoutMs);
114
+ // Intentionally NOT unref'd: the caller always awaits this Promise, and the
115
+ // timer is cleared via finish() the moment the export resolves. Leaving it
116
+ // ref'd keeps the timeout deterministic under Node's test runner (an unref'd
117
+ // timer can be skipped when the loop is otherwise idle, cancelling the test).
118
+ // Consume stderr to prevent pipe-buffer issues with the opencode binary
119
+ if (child.stderr) {
120
+ child.stderr.on('data', () => { });
121
+ }
122
+ child.on('error', () => finish(null));
123
+ child.on('close', () => {
124
+ if (settled) {
125
+ return;
126
+ }
127
+ if (tmpFd === null) {
128
+ finish(null);
129
+ return;
130
+ }
131
+ try {
132
+ node_fs_1.default.closeSync(tmpFd);
133
+ }
134
+ catch (_) { /* already closed */ }
135
+ tmpFd = null;
136
+ let content;
137
+ if (!tmpPath) {
138
+ finish(null);
139
+ return;
140
+ }
141
+ try {
142
+ content = node_fs_1.default.readFileSync(tmpPath, 'utf8');
143
+ }
144
+ catch (_) {
145
+ finish(null);
146
+ return;
147
+ }
148
+ size = Buffer.byteLength(content);
149
+ if (size > maxBytes) {
150
+ // Explicit failure rather than silent truncation: a partial JSON
151
+ // document would parse to null/garbage and fabricate zero telemetry.
152
+ finish(null);
153
+ return;
154
+ }
155
+ finish(content);
156
+ });
155
157
  });
156
- });
157
158
  }
158
-
159
- module.exports = { captureOpencodeExport };
@@ -0,0 +1,160 @@
1
+ import childProcess from 'node:child_process';
2
+ import fs from 'node:fs';
3
+ import os from 'node:os';
4
+ import path from 'node:path';
5
+
6
+ interface CaptureOpencodeExportOptions {
7
+ worktree?: string;
8
+ env?: Record<string, string>;
9
+ timeoutMs?: number;
10
+ maxBytes?: number;
11
+ spawn?: typeof childProcess.spawn;
12
+ }
13
+
14
+ /**
15
+ * Capture the complete `opencode export <sessionId>` JSON document.
16
+ *
17
+ * Unlike the limit-hit tail buffer in spawn-tee.js, telemetry needs the WHOLE
18
+ * export document (it is a single JSON object beginning with `{`), so a tail
19
+ * buffer would discard the prefix and yield invalid JSON. This collector keeps
20
+ * the full stdout in memory up to an explicit cap and, critically:
21
+ *
22
+ * - Enforces a timeout that kills a hung/non-exiting export child so the
23
+ * launcher can never block on it (mission risk + stop rule).
24
+ * - Fails explicitly (resolves null) when output exceeds `maxBytes` rather
25
+ * than silently truncating into unparseable JSON.
26
+ *
27
+ * Uses a temp-file fd for stdout to avoid pipe-buffer data loss that occurs
28
+ * with large (>100 KB) child-process output. The child writes directly to
29
+ * the fd; after exit we read the file back as a string.
30
+ *
31
+ * Never rejects: any error/timeout/oversize resolves to null so telemetry
32
+ * capture stays best-effort and cannot break the launch.
33
+ *
34
+ * @param {string} sessionId - opencode session id (e.g. ses_...)
35
+ * @param {object} opts
36
+ * @param {string} [opts.worktree] - cwd for the export process
37
+ * @param {object} [opts.env] - extra env vars
38
+ * @param {number} [opts.timeoutMs] - hard timeout before the child is killed
39
+ * @param {number} [opts.maxBytes] - explicit byte cap; exceeding it fails
40
+ * @param {Function} [opts.spawn] - injectable spawn (defaults to child_process.spawn)
41
+ * @returns {Promise<string|null>} Full JSON string, or null on failure
42
+ */
43
+ function captureOpencodeExport(sessionId: string, opts: CaptureOpencodeExportOptions = {}) {
44
+ const {
45
+ worktree,
46
+ env,
47
+ timeoutMs = 30000,
48
+ maxBytes = 32 * 1024 * 1024,
49
+ spawn = childProcess.spawn,
50
+ } = opts;
51
+
52
+ return new Promise<string | null>((resolve) => {
53
+ if (!sessionId) {
54
+ resolve(null);
55
+ return;
56
+ }
57
+
58
+ let settled = false;
59
+ let timer: NodeJS.Timeout | null = null;
60
+ let size = 0;
61
+ let tmpFd: number | null = null;
62
+ let tmpPath: string | null = null;
63
+
64
+ const finish = (value: string | null) => {
65
+ if (settled) {return;}
66
+ settled = true;
67
+ if (timer) {clearTimeout(timer);}
68
+ // Clean up temp file
69
+ if (tmpFd !== null) {
70
+ try { fs.closeSync(tmpFd); } catch (_) { /* already closed */ }
71
+ }
72
+ if (tmpPath) {
73
+ try { fs.unlinkSync(tmpPath); } catch (_) { /* already gone */ }
74
+ }
75
+ resolve(value);
76
+ };
77
+
78
+ const killChild = (child: childProcess.ChildProcess | undefined) => {
79
+ try { if (child) { child.kill('SIGKILL'); } } catch (_) { /* already gone */ }
80
+ };
81
+
82
+ // Create a temp file for stdout to avoid pipe-buffer truncation
83
+ let tmpFileError = null;
84
+ try {
85
+ tmpPath = path.join(os.tmpdir(), `opencode-export-${process.pid}-${Date.now()}.json`);
86
+ tmpFd = fs.openSync(tmpPath, 'w');
87
+ } catch (e) {
88
+ tmpFileError = e;
89
+ }
90
+
91
+ let child;
92
+ try {
93
+ child = spawn('opencode', ['export', sessionId], {
94
+ cwd: worktree,
95
+ env: { ...process.env, ...(env || {}) },
96
+ stdio: ['ignore', tmpFd !== null ? tmpFd : 'pipe', 'pipe'],
97
+ });
98
+ } catch (_) {
99
+ finish(null);
100
+ return;
101
+ }
102
+
103
+ if (tmpFileError) {
104
+ killChild(child);
105
+ finish(null);
106
+ return;
107
+ }
108
+
109
+ timer = setTimeout(() => {
110
+ // Hung or slow export: kill it and degrade to null. Do not block.
111
+ killChild(child);
112
+ finish(null);
113
+ }, timeoutMs);
114
+ // Intentionally NOT unref'd: the caller always awaits this Promise, and the
115
+ // timer is cleared via finish() the moment the export resolves. Leaving it
116
+ // ref'd keeps the timeout deterministic under Node's test runner (an unref'd
117
+ // timer can be skipped when the loop is otherwise idle, cancelling the test).
118
+
119
+ // Consume stderr to prevent pipe-buffer issues with the opencode binary
120
+ if (child.stderr) {
121
+ child.stderr.on('data', () => { /* drain stderr */ });
122
+ }
123
+
124
+ child.on('error', () => finish(null));
125
+
126
+ child.on('close', () => {
127
+ if (settled) {return;}
128
+ if (tmpFd === null) {
129
+ finish(null);
130
+ return;
131
+ }
132
+
133
+ try {
134
+ fs.closeSync(tmpFd);
135
+ } catch (_) { /* already closed */ }
136
+ tmpFd = null;
137
+
138
+ let content;
139
+ if (!tmpPath) { finish(null); return; }
140
+ try {
141
+ content = fs.readFileSync(tmpPath, 'utf8');
142
+ } catch (_) {
143
+ finish(null);
144
+ return;
145
+ }
146
+
147
+ size = Buffer.byteLength(content);
148
+ if (size > maxBytes) {
149
+ // Explicit failure rather than silent truncation: a partial JSON
150
+ // document would parse to null/garbage and fabricate zero telemetry.
151
+ finish(null);
152
+ return;
153
+ }
154
+
155
+ finish(content);
156
+ });
157
+ });
158
+ }
159
+
160
+ export { captureOpencodeExport };