@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,241 +1,283 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const storage = require('./storage');
4
-
5
- /** @type {any} */
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports._internals = void 0;
40
+ exports.migrateStats = migrateStats;
41
+ exports.migrateAgentBlocklists = migrateAgentBlocklists;
42
+ const node_fs_1 = __importDefault(require("node:fs"));
43
+ const node_path_1 = __importDefault(require("node:path"));
44
+ const storage = __importStar(require("./storage.js"));
45
+ /** Cached stats module — loaded lazily to break circular dependency with `../commands/stats.js`. */
6
46
  let _stats = null;
47
+ /** Lazily load the stats module to break the circular dependency. */
7
48
  function getStats() {
8
- if (!_stats) {_stats = require('../commands/stats');}
9
- return _stats;
49
+ if (!_stats) {
50
+ _stats = require('../commands/stats.js');
51
+ }
52
+ return _stats;
10
53
  }
11
-
54
+ /** Resolve the canonical stats CSV headers. */
12
55
  function getStatsHeaders() {
13
- return getStats().STATS_HEADERS;
56
+ return getStats().STATS_HEADERS;
14
57
  }
15
-
16
58
  const ESSENTIAL_STATS_COLUMNS = ['date', 'mission', 'classification', 'implementer', 'pr_fix_rounds'];
17
-
18
- /** @param {Record<string, unknown>} row @param {number} rowIndex @param {string} filePath */
59
+ /**
60
+ * Validate that a stats row has all essential columns populated.
61
+ * Throws on the first missing column found.
62
+ * @param row - Row object to validate
63
+ * @param rowIndex - Zero-based row index (for error messages)
64
+ * @param filePath - Source file path (for error messages)
65
+ */
19
66
  function validateStatsRow(row, rowIndex, filePath) {
20
- const missing = ESSENTIAL_STATS_COLUMNS.filter(col => !(col in row) || row[col] === '');
21
- if (missing.length > 0) {
22
- throw new Error(
23
- `Malformed telemetry row ${rowIndex + 1} in ${path.resolve(filePath)}: ` +
24
- `missing essential columns: ${missing.join(', ')}`
25
- );
26
- }
67
+ const missing = ESSENTIAL_STATS_COLUMNS.filter(col => !(col in row) || row[col] === '');
68
+ if (missing.length > 0) {
69
+ throw new Error(`Malformed telemetry row ${rowIndex + 1} in ${node_path_1.default.resolve(filePath)}: ` +
70
+ `missing essential columns: ${missing.join(', ')}`);
71
+ }
27
72
  }
28
-
29
- /** @param {string} line */
73
+ /**
74
+ * Parse a single CSV line, respecting quoted fields and escaped quotes.
75
+ * @param line - Raw CSV line string
76
+ * @returns Array of parsed field values
77
+ */
30
78
  function parseCsvLine(line) {
31
- const values = [];
32
- let value = '';
33
- let quoted = false;
34
- for (let index = 0; index < line.length; index += 1) {
35
- const char = line[index];
36
- if (char === '"') {
37
- if (quoted && line[index + 1] === '"') {
38
- value += '"';
39
- index += 1;
40
- } else {
41
- quoted = !quoted;
42
- }
43
- } else if (char === ',' && !quoted) {
44
- values.push(value);
45
- value = '';
46
- } else {
47
- value += char;
48
- }
49
- }
50
- values.push(value);
51
- return values;
79
+ const values = [];
80
+ let value = '';
81
+ let quoted = false;
82
+ for (let index = 0; index < line.length; index += 1) {
83
+ const char = line[index];
84
+ if (char === '"') {
85
+ if (quoted && line[index + 1] === '"') {
86
+ value += '"';
87
+ index += 1;
88
+ }
89
+ else {
90
+ quoted = !quoted;
91
+ }
92
+ }
93
+ else if (char === ',' && !quoted) {
94
+ values.push(value);
95
+ value = '';
96
+ }
97
+ else {
98
+ value += char;
99
+ }
100
+ }
101
+ values.push(value);
102
+ return values;
52
103
  }
53
-
54
- /** @param {unknown} value */
104
+ /**
105
+ * Escape a single CSV value — wraps in quotes if it contains commas, double-quotes, or newlines.
106
+ * @param value - Value to escape
107
+ * @returns Escaped CSV-safe string
108
+ */
55
109
  function escapeCsvValue(value) {
56
- const text = String(value ?? '');
57
- return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
110
+ const text = String(value ?? '');
111
+ return /[",\n]/.test(text) ? `"${text.replace(/"/g, '""')}"` : text;
58
112
  }
59
-
60
113
  /**
61
114
  * Minimal CSV loader — avoids circular dependency with stats module (which
62
115
  * imports this module at the top). Defined locally so `migrateStats()` can
63
116
  * read any schema without triggering a require-cycle.
64
117
  */
65
- /** @param {string} filePath */
66
118
  function loadCsv(filePath) {
67
- if (!fs.existsSync(filePath)) {
68
- return { headers: [], rows: [] };
69
- }
70
- const content = fs.readFileSync(filePath, 'utf8');
71
- const lines = content.split('\n').filter(line => line.trim());
72
- if (lines.length === 0) {
73
- return { headers: [], rows: [] };
74
- }
75
- const headers = parseCsvLine(lines[0]);
76
- const rows = [];
77
- for (let i = 1; i < lines.length; i += 1) {
78
- const values = parseCsvLine(lines[i]);
79
- const row = {};
80
- /** @param {string} header @param {number} idx */
81
- headers.forEach((header, idx) => {
82
- /** @type {Record<string, unknown>} */
83
- const r = /** @type {Record<string, unknown>} */(row);
84
- r[header] = values[idx] || '';
85
- });
86
- rows.push(row);
87
- }
88
- return { headers, rows };
119
+ if (!node_fs_1.default.existsSync(filePath)) {
120
+ return { headers: [], rows: [] };
121
+ }
122
+ const content = node_fs_1.default.readFileSync(filePath, 'utf8');
123
+ const lines = content.split('\n').filter(line => line.trim());
124
+ if (lines.length === 0) {
125
+ return { headers: [], rows: [] };
126
+ }
127
+ const headers = parseCsvLine(lines[0]);
128
+ const rows = [];
129
+ for (let i = 1; i < lines.length; i += 1) {
130
+ const values = parseCsvLine(lines[i]);
131
+ const row = {};
132
+ headers.forEach((header, idx) => {
133
+ row[header] = values[idx] || '';
134
+ });
135
+ rows.push(row);
136
+ }
137
+ return { headers, rows };
89
138
  }
90
-
91
139
  /**
92
140
  * Read a stats CSV file with any schema (legacy 5-col or extended 21-col).
93
141
  * Returns rows as arrays keyed by the 21-column STATS_HEADERS, with missing
94
142
  * columns filled by normalizeStatsRow defaults.
95
143
  */
96
- /** @param {string} filePath @param {{defaultRepo?: string}} [options] */
97
144
  function readStatsRows(filePath, options = {}) {
98
- if (!filePath || !fs.existsSync(filePath)) {return [];}
99
- const data = loadCsv(filePath);
100
- if (data.headers.length === 0) {return [];}
101
- const headers = getStatsHeaders();
102
- /** @type {{defaultRepo?: string}} */
103
- const opts = options;
104
- /** @type {Record<string, unknown>[]} */
105
- const validated = /** @type {Record<string, unknown>[]} */(data.rows).map((/** @type {Record<string, unknown>} */ row, idx) => {
106
- validateStatsRow(row, idx, filePath);
107
- return row;
108
- });
109
- /** @param {string} h */
110
- return validated
111
- .map(row => getStats().normalizeStatsRow(row, { repo: opts.defaultRepo }))
112
- .map(row => headers.map((/** @type {string} */ h) => row[h] || ''));
145
+ if (!filePath || !node_fs_1.default.existsSync(filePath)) {
146
+ return [];
147
+ }
148
+ const data = loadCsv(filePath);
149
+ if (data.headers.length === 0) {
150
+ return [];
151
+ }
152
+ const headers = getStatsHeaders();
153
+ const opts = options;
154
+ const validated = data.rows.map((row, idx) => {
155
+ validateStatsRow(row, idx, filePath);
156
+ return row;
157
+ });
158
+ return validated
159
+ .map(row => getStats().normalizeStatsRow(row, { repo: opts.defaultRepo }))
160
+ .map((row) => headers.map((h) => String(row[h] || '')));
113
161
  }
114
-
115
- /** @param {string[][]} rows */
162
+ /**
163
+ * Serialize an array of stats rows back to CSV format with canonical headers.
164
+ * @param rows - Array of row arrays (each row is an array of string values)
165
+ * @returns CSV string with headers and data rows
166
+ */
116
167
  function serializeStatsRows(rows) {
117
- const headers = getStatsHeaders();
118
- return `${[
119
- headers.join(','),
120
- ...rows.map(row => row.map(escapeCsvValue).join(','))
121
- ].join('\n')}\n`;
168
+ const headers = getStatsHeaders();
169
+ return `${[
170
+ headers.join(','),
171
+ ...rows.map(row => row.map(escapeCsvValue).join(','))
172
+ ].join('\n')}\n`;
122
173
  }
123
-
124
- /** @param {{sourcePaths?: string[], sourcePath?: string, destinationPath?: string}} [options] */
174
+ /**
175
+ * Merge stats from one or more source CSV files into the destination file.
176
+ * Deduplicates full rows. Creates the destination directory if needed.
177
+ * When no source data is available, returns early without writing a header-only file.
178
+ * @param options - Migration configuration (sourcePaths, sourcePath, destinationPath)
179
+ * @returns Result with destination path, import counts, and optional warning
180
+ */
125
181
  function migrateStats(options = {}) {
126
- /** @type {{sourcePaths?: string[], sourcePath?: string, destinationPath?: string}} */
127
- const opts = options;
128
- const sourcePaths = /** @type {string[]} */(opts.sourcePaths || [opts.sourcePath].filter(Boolean));
129
- const destinationPath = opts.destinationPath || storage.resolveStatsPath({ ensureDir: true });
130
- /** @param {string} sourcePath */
131
- const sourceRows = sourcePaths.flatMap((/** @type {string} */ sourcePath) => readStatsRows(sourcePath, /** @type {{defaultRepo?: string}} */({})));
132
-
133
- // Fresh-install guard: when no source file exists or all sources are empty,
134
- // there is no telemetry to import. Returning early prevents writing a
135
- // header-only destination file that would masquerade as valid stats.
136
- if (sourceRows.length === 0) {
137
- return { destinationPath, imported: 0, rows: 0, warn: 'no source data available' };
138
- }
139
-
140
- const destinationRows = readStatsRows(destinationPath, /** @type {{defaultRepo?: string}} */({}));
141
- const rows = [];
142
- const seen = new Set();
143
-
144
- for (const row of [...destinationRows, ...sourceRows]) {
145
- const key = JSON.stringify(row);
146
- if (!seen.has(key)) {
147
- seen.add(key);
148
- rows.push(row);
149
- }
150
- }
151
-
152
- const content = serializeStatsRows(rows);
153
- const current = fs.existsSync(destinationPath) ? fs.readFileSync(destinationPath, 'utf8') : null;
154
- if (current !== content) {storage.writeFileAtomic(destinationPath, content);}
155
- return { destinationPath, imported: rows.length - destinationRows.length, rows: rows.length };
182
+ const opts = options;
183
+ const sourcePaths = (opts.sourcePaths || [opts.sourcePath].filter(Boolean));
184
+ const destinationPath = opts.destinationPath || storage.resolveStatsPath({ ensureDir: true });
185
+ const sourceRows = sourcePaths.flatMap((sourcePath) => readStatsRows(sourcePath, {}));
186
+ // Fresh-install guard: when no source file exists or all sources are empty,
187
+ // there is no telemetry to import. Returning early prevents writing a
188
+ // header-only destination file that would masquerade as valid stats.
189
+ if (sourceRows.length === 0) {
190
+ return { destinationPath, imported: 0, rows: 0, warn: 'no source data available' };
191
+ }
192
+ const destinationRows = readStatsRows(destinationPath, {});
193
+ const rows = [];
194
+ const seen = new Set();
195
+ for (const row of [...destinationRows, ...sourceRows]) {
196
+ const key = JSON.stringify(row);
197
+ if (!seen.has(key)) {
198
+ seen.add(key);
199
+ rows.push(row);
200
+ }
201
+ }
202
+ const content = serializeStatsRows(rows);
203
+ const current = node_fs_1.default.existsSync(destinationPath) ? node_fs_1.default.readFileSync(destinationPath, 'utf8') : null;
204
+ if (current !== content) {
205
+ storage.writeFileAtomic(destinationPath, content);
206
+ }
207
+ return { destinationPath, imported: rows.length - destinationRows.length, rows: rows.length };
156
208
  }
157
-
158
- /** @param {string} filePath @param {Function} warn @param {boolean} [hardFailure] */
159
209
  function readBlocklistSource(filePath, warn, hardFailure = false) {
160
- if (!filePath || !fs.existsSync(filePath)) {return null;}
161
- try {
162
- const payload = JSON.parse(fs.readFileSync(filePath, 'utf8'));
163
- if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
164
- throw new Error('expected a JSON object at the file root');
165
- }
166
- if (
167
- payload.blocklist !== undefined &&
168
- (!payload.blocklist || typeof payload.blocklist !== 'object' || Array.isArray(payload.blocklist))
169
- ) {
170
- throw new Error('expected blocklist to be a JSON object');
171
- }
172
- return { filePath, payload, blocklist: payload.blocklist || {} };
173
- } catch (error) {
174
- if (hardFailure) {throw error;}
175
- /** @type {Error} */
176
- const e = /** @type {Error} */(error);
177
- warn(`Skipping malformed legacy agent blocklist ${path.resolve(filePath)}: ${e.message}`);
178
- return null;
179
- }
210
+ if (!filePath || !node_fs_1.default.existsSync(filePath)) {
211
+ return null;
212
+ }
213
+ try {
214
+ const payload = JSON.parse(node_fs_1.default.readFileSync(filePath, 'utf8'));
215
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
216
+ throw new Error('expected a JSON object at the file root');
217
+ }
218
+ if (payload.blocklist !== undefined &&
219
+ (!payload.blocklist || typeof payload.blocklist !== 'object' || Array.isArray(payload.blocklist))) {
220
+ throw new Error('expected blocklist to be a JSON object');
221
+ }
222
+ return { filePath, payload, blocklist: payload.blocklist || {} };
223
+ }
224
+ catch (error) {
225
+ if (hardFailure) {
226
+ throw error;
227
+ }
228
+ warn(`Skipping malformed legacy agent blocklist ${node_path_1.default.resolve(filePath)}: ${error.message}`);
229
+ return null;
230
+ }
180
231
  }
181
-
182
- /** @param {unknown} left @param {unknown} right */
183
232
  function sameValue(left, right) {
184
- return JSON.stringify(left) === JSON.stringify(right);
233
+ return JSON.stringify(left) === JSON.stringify(right);
185
234
  }
186
-
187
- /** @param {{warn?: Function, destinationPath?: string, sourcePaths?: string[]}} [options] */
235
+ /**
236
+ * Merge agent blocklists from legacy sources into the destination file.
237
+ * Reports conflicts via `warn` when the same agent appears in multiple sources
238
+ * with different values. Existing destination entries take lowest precedence.
239
+ * @param options - Migration configuration (warn callback, destinationPath, sourcePaths)
240
+ * @returns Result with destination path, merged blocklist, and conflict details
241
+ */
188
242
  function migrateAgentBlocklists(options = {}) {
189
- /** @type {{warn?: Function, destinationPath?: string, sourcePaths?: string[]}} */
190
- const opts = options;
191
- const warn = opts.warn || (() => {});
192
- const destinationPath = opts.destinationPath || storage.resolveAgentsLocalPath({ ensureDir: true });
193
- /** @param {string} filePath */
194
- const sources = (opts.sourcePaths || [])
195
- .map(filePath => readBlocklistSource(filePath, warn))
196
- .filter(Boolean);
197
- const destination = readBlocklistSource(destinationPath, warn, true);
198
- /** @type {Record<string, unknown>} */
199
- const selected = {};
200
- /** @type {Record<string, string>} */
201
- const selectedFrom = {};
202
- const conflicts = [];
203
-
204
- for (const source of [...sources, ...(destination ? [destination] : [])]) {
205
- if (!source) {continue;}
206
- /** @type {{filePath: string, blocklist: Record<string,unknown>}} */
207
- const s = source;
208
- for (const [agent, value] of Object.entries(s.blocklist)) {
209
- if (Object.prototype.hasOwnProperty.call(selected, agent) && !sameValue(selected[agent], value)) {
210
- const conflict = {
211
- agent,
212
- previousSource: selectedFrom[agent],
213
- previousValue: selected[agent],
214
- selectedSource: s.filePath,
215
- selectedValue: value
216
- };
217
- conflicts.push(conflict);
218
- warn(
219
- `Agent blocklist conflict for "${agent}": ${path.resolve(s.filePath)} takes precedence over ` +
220
- `${path.resolve(selectedFrom[agent])}; selected=${JSON.stringify(value)} previous=${JSON.stringify(selected[agent])}`
221
- );
222
- }
223
- selected[agent] = value;
224
- selectedFrom[agent] = s.filePath;
225
- }
226
- }
227
-
228
- /** @type{Record<string,unknown>} */
229
- const payload = destination ? { ...destination.payload } : {};
230
- payload.blocklist = selected;
231
- const content = `${JSON.stringify(payload, null, 2)}\n`;
232
- const current = fs.existsSync(destinationPath) ? fs.readFileSync(destinationPath, 'utf8') : null;
233
- if (current !== content) {storage.writeFileAtomic(destinationPath, content);}
234
- return { destinationPath, blocklist: selected, conflicts };
243
+ const opts = options;
244
+ const warn = opts.warn || (() => { });
245
+ const destinationPath = opts.destinationPath || storage.resolveAgentsLocalPath({ ensureDir: true });
246
+ const sources = (opts.sourcePaths || [])
247
+ .map(filePath => readBlocklistSource(filePath, warn))
248
+ .filter(Boolean);
249
+ const destination = readBlocklistSource(destinationPath, warn, true);
250
+ const selected = {};
251
+ const selectedFrom = {};
252
+ const conflicts = [];
253
+ for (const s of [...sources, ...(destination ? [destination] : [])]) {
254
+ if (!s) {
255
+ continue;
256
+ }
257
+ for (const [agent, value] of Object.entries(s.blocklist)) {
258
+ if (Object.prototype.hasOwnProperty.call(selected, agent) && !sameValue(selected[agent], value)) {
259
+ const conflict = {
260
+ agent,
261
+ previousSource: selectedFrom[agent],
262
+ previousValue: selected[agent],
263
+ selectedSource: s.filePath,
264
+ selectedValue: value
265
+ };
266
+ conflicts.push(conflict);
267
+ warn(`Agent blocklist conflict for "${agent}": ${node_path_1.default.resolve(s.filePath)} takes precedence over ` +
268
+ `${node_path_1.default.resolve(selectedFrom[agent])}; selected=${JSON.stringify(value)} previous=${JSON.stringify(selected[agent])}`);
269
+ }
270
+ selected[agent] = value;
271
+ selectedFrom[agent] = s.filePath;
272
+ }
273
+ }
274
+ const payload = destination ? { ...destination.payload } : {};
275
+ payload.blocklist = selected;
276
+ const content = `${JSON.stringify(payload, null, 2)}\n`;
277
+ const current = node_fs_1.default.existsSync(destinationPath) ? node_fs_1.default.readFileSync(destinationPath, 'utf8') : null;
278
+ if (current !== content) {
279
+ storage.writeFileAtomic(destinationPath, content);
280
+ }
281
+ return { destinationPath, blocklist: selected, conflicts };
235
282
  }
236
-
237
- module.exports = {
238
- migrateStats,
239
- migrateAgentBlocklists,
240
- _internals: { parseCsvLine, readStatsRows, serializeStatsRows }
241
- };
283
+ exports._internals = { parseCsvLine, readStatsRows, serializeStatsRows };