@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
|
@@ -1,241 +1,283 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
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
|
-
|
|
9
|
-
|
|
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
|
-
|
|
56
|
+
return getStats().STATS_HEADERS;
|
|
14
57
|
}
|
|
15
|
-
|
|
16
58
|
const ESSENTIAL_STATS_COLUMNS = ['date', 'mission', 'classification', 'implementer', 'pr_fix_rounds'];
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
57
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
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
|
-
|
|
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
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
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
|
-
|
|
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
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
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
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
233
|
+
return JSON.stringify(left) === JSON.stringify(right);
|
|
185
234
|
}
|
|
186
|
-
|
|
187
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
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 };
|