@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,108 +1,108 @@
1
- const fs = require('fs');
2
- const path = require('path');
3
- const { loadEffectiveConfig } = require('./product-config');
4
-
5
- const SHIPPED_STATE_MAP_PATH = path.join(__dirname, '..', '..', 'config', 'state-map.json');
6
-
7
- /** @param {string} rootDir @param {string} repoRelativePath */
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.SHIPPED_STATE_MAP_PATH = void 0;
7
+ exports.resolveStateMapPath = resolveStateMapPath;
8
+ exports.loadStateMap = loadStateMap;
9
+ exports.normalizeState = normalizeState;
10
+ exports.toActual = toActual;
11
+ exports.toVirtual = toVirtual;
12
+ exports.transitionVirtual = transitionVirtual;
13
+ const node_fs_1 = __importDefault(require("node:fs"));
14
+ const node_path_1 = __importDefault(require("node:path"));
15
+ const product_config_js_1 = require("./product-config.js");
16
+ const fmt_js_1 = require("./fmt.js");
17
+ const SHIPPED_STATE_MAP_PATH = node_path_1.default.join(__dirname, '..', '..', 'config', 'state-map.json');
18
+ exports.SHIPPED_STATE_MAP_PATH = SHIPPED_STATE_MAP_PATH;
8
19
  function resolveRepoRelativePath(rootDir, repoRelativePath) {
9
- if (!repoRelativePath || typeof repoRelativePath !== 'string') {return null;}
10
- return path.isAbsolute(repoRelativePath)
11
- ? repoRelativePath
12
- : path.join(rootDir, repoRelativePath);
20
+ if (!repoRelativePath || typeof repoRelativePath !== 'string') {
21
+ return null;
22
+ }
23
+ return node_path_1.default.isAbsolute(repoRelativePath)
24
+ ? repoRelativePath
25
+ : node_path_1.default.join(rootDir, repoRelativePath);
13
26
  }
14
-
15
- /** @param {{rootDir?: string, config?: any, fallbackPath?: string}|string} [options] */
16
27
  function resolveStateMapPath(options = {}) {
17
- if (typeof options === 'string') {return options;}
18
-
19
- /** @type {{rootDir?: string, config?: any, fallbackPath?: string}} */
20
- const opts = options;
21
- const rootDir = opts.rootDir || process.cwd();
22
- const config = opts.config || loadEffectiveConfig(rootDir);
23
- const configuredPath = config.adapters?.tasks?.stateMap;
24
- const repoPath = resolveRepoRelativePath(rootDir, configuredPath);
25
-
26
- if (repoPath && fs.existsSync(repoPath)) {
27
- return repoPath;
28
- }
29
- return opts.fallbackPath || SHIPPED_STATE_MAP_PATH;
28
+ if (typeof options === 'string') {
29
+ return options;
30
+ }
31
+ const opts = options;
32
+ const rootDir = opts.rootDir || process.cwd();
33
+ const config = opts.config || (0, product_config_js_1.loadEffectiveConfig)(rootDir);
34
+ const adapters = config.adapters;
35
+ const configuredPath = adapters?.tasks?.stateMap;
36
+ const repoPath = resolveRepoRelativePath(rootDir, configuredPath ?? null);
37
+ if (repoPath && node_fs_1.default.existsSync(repoPath)) {
38
+ return repoPath;
39
+ }
40
+ return opts.fallbackPath || SHIPPED_STATE_MAP_PATH;
30
41
  }
31
-
32
42
  function loadStateMap(options = {}) {
33
- const stateMapPath = resolveStateMapPath(options);
34
- try {
35
- return JSON.parse(fs.readFileSync(stateMapPath, 'utf8'));
36
- } catch (_) {
37
- return {};
38
- }
43
+ const stateMapPath = resolveStateMapPath(options);
44
+ try {
45
+ return JSON.parse(node_fs_1.default.readFileSync(stateMapPath, 'utf8'));
46
+ }
47
+ catch {
48
+ return {};
49
+ }
39
50
  }
40
-
41
- /** @param {unknown} value */
42
51
  function normalizeState(value) {
43
- if (typeof value !== 'string') {return value;}
44
- return value.trim().toLowerCase();
52
+ if (typeof value !== 'string') {
53
+ return value;
54
+ }
55
+ return value.trim().toLowerCase();
45
56
  }
46
-
47
- // Virtual workflow state → actual backlog.md state name.
48
- // Returns null if the virtual state explicitly maps to null (no backlog.md write).
49
- // Returns the virtual name unchanged if it has no entry in the map (identity).
50
- /** @param {Record<string, any> | any} mapOrOptions */
51
57
  function resolveMap(mapOrOptions) {
52
- if (mapOrOptions && typeof mapOrOptions === 'object' && !Array.isArray(mapOrOptions)) {
53
- if (Object.prototype.hasOwnProperty.call(mapOrOptions, 'rootDir')
54
- || Object.prototype.hasOwnProperty.call(mapOrOptions, 'config')
55
- || Object.prototype.hasOwnProperty.call(mapOrOptions, 'fallbackPath')) {
56
- return loadStateMap(mapOrOptions);
58
+ if (mapOrOptions && typeof mapOrOptions === 'object' && !Array.isArray(mapOrOptions)) {
59
+ if (Object.prototype.hasOwnProperty.call(mapOrOptions, 'rootDir')
60
+ || Object.prototype.hasOwnProperty.call(mapOrOptions, 'config')
61
+ || Object.prototype.hasOwnProperty.call(mapOrOptions, 'fallbackPath')) {
62
+ return loadStateMap(mapOrOptions);
63
+ }
57
64
  }
58
- }
59
- return mapOrOptions || loadStateMap();
65
+ return mapOrOptions || loadStateMap();
60
66
  }
61
-
62
- /** @param {string} virtualState @param {Record<string, any>} [map] */
63
67
  function toActual(virtualState, map = loadStateMap()) {
64
- /** @type {Record<string, any>} */
65
- const m = resolveMap(map) || {};
66
- if (Object.prototype.hasOwnProperty.call(m, virtualState)) {
67
- return m[virtualState];
68
- }
69
- return virtualState;
68
+ const m = resolveMap(map) || {};
69
+ if (Object.prototype.hasOwnProperty.call(m, virtualState)) {
70
+ return m[virtualState];
71
+ }
72
+ return virtualState;
70
73
  }
71
-
72
- // Actual backlog.md state name → virtual workflow state name.
73
- // Returns the actual name unchanged if no reverse mapping exists (identity).
74
- /** @param {string} actualState @param {Record<string, any>} [map] */
75
74
  function toVirtual(actualState, map = loadStateMap()) {
76
- /** @type {Record<string, any>} */
77
- const m = resolveMap(map) || {};
78
- const normalizedActual = normalizeState(actualState);
79
- for (const [virtual, actual] of Object.entries(m)) {
80
- if (normalizeState(actual) === normalizedActual) {return virtual;}
81
- }
82
- return actualState;
75
+ const m = resolveMap(map) || {};
76
+ const normalizedActual = normalizeState(actualState);
77
+ for (const [virtual, actual] of Object.entries(m)) {
78
+ if (normalizeState(actual) === normalizedActual) {
79
+ return virtual;
80
+ }
81
+ }
82
+ return actualState;
83
83
  }
84
-
85
- // Wraps a transitionTask call: resolves virtual → actual, then delegates.
86
- // If the virtual state maps to null, logs an info line and returns true (no-op).
87
- /**
88
- * @param {Function} transitionTaskFn
89
- * @param {string} slug
90
- * @param {string} virtualState
91
- * @param {{log?: Function}} [options]
92
- * @param {Record<string, any>} [map]
93
- */
94
- function transitionVirtual(transitionTaskFn, slug, virtualState, options = {}, map = loadStateMap(options)) {
95
- map = resolveMap(map);
96
- const actual = toActual(virtualState, map);
97
- if (actual === null) {
98
- const fmt = require('./fmt');
99
- /** @type {{log?: Function}} */
100
- const opts = options;
101
- const log = opts.log || fmt.log.plain;
102
- log(fmt.status('INFO', `Virtual state '${virtualState}' has no backlog.md mapping for this board; skipping status write.`));
103
- return true;
104
- }
105
- return transitionTaskFn(slug, actual, options);
84
+ function transitionVirtual(transitionTaskFn, slug, virtualState, options = {}, mapParam) {
85
+ let map;
86
+ if (mapParam && typeof mapParam === 'object' && !Array.isArray(mapParam)) {
87
+ if (Object.prototype.hasOwnProperty.call(mapParam, 'rootDir')
88
+ || Object.prototype.hasOwnProperty.call(mapParam, 'config')
89
+ || Object.prototype.hasOwnProperty.call(mapParam, 'fallbackPath')) {
90
+ map = loadStateMap(mapParam);
91
+ }
92
+ else {
93
+ map = mapParam;
94
+ }
95
+ }
96
+ else {
97
+ map = loadStateMap();
98
+ }
99
+ map = resolveMap(map);
100
+ const actual = toActual(virtualState, map);
101
+ if (actual === null) {
102
+ const opts = options;
103
+ const logFn = opts.log || fmt_js_1.log.plain;
104
+ logFn((0, fmt_js_1.status)('INFO', `Virtual state '${virtualState}' has no backlog.md mapping for this board; skipping status write.`));
105
+ return true;
106
+ }
107
+ return transitionTaskFn(slug, actual, options);
106
108
  }
107
-
108
- module.exports = { SHIPPED_STATE_MAP_PATH, loadStateMap, normalizeState, resolveStateMapPath, toActual, toVirtual, transitionVirtual };
@@ -0,0 +1,115 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { loadEffectiveConfig } from './product-config.js';
4
+ import { log, status } from './fmt.js';
5
+
6
+ const SHIPPED_STATE_MAP_PATH = path.join(__dirname, '..', '..', 'config', 'state-map.json');
7
+
8
+ interface StateMapOptions {
9
+ rootDir?: string;
10
+ config?: Record<string, unknown>;
11
+ fallbackPath?: string;
12
+ }
13
+
14
+ interface AdaptersConfig {
15
+ tasks?: { stateMap?: string };
16
+ }
17
+
18
+ function resolveRepoRelativePath(rootDir: string, repoRelativePath: string | null): string | null {
19
+ if (!repoRelativePath || typeof repoRelativePath !== 'string') {return null;}
20
+ return path.isAbsolute(repoRelativePath)
21
+ ? repoRelativePath
22
+ : path.join(rootDir, repoRelativePath);
23
+ }
24
+
25
+ export function resolveStateMapPath(options: StateMapOptions | string = {}): string {
26
+ if (typeof options === 'string') {return options;}
27
+
28
+ const opts: StateMapOptions = options;
29
+ const rootDir = opts.rootDir || process.cwd();
30
+ const config = opts.config || loadEffectiveConfig(rootDir);
31
+ const adapters = (config as Record<string, unknown>).adapters as AdaptersConfig | undefined;
32
+ const configuredPath = adapters?.tasks?.stateMap as string | undefined;
33
+ const repoPath = resolveRepoRelativePath(rootDir, configuredPath ?? null);
34
+
35
+ if (repoPath && fs.existsSync(repoPath)) {
36
+ return repoPath;
37
+ }
38
+ return opts.fallbackPath || SHIPPED_STATE_MAP_PATH;
39
+ }
40
+
41
+ export function loadStateMap(options: StateMapOptions = {}): Record<string, unknown> {
42
+ const stateMapPath = resolveStateMapPath(options);
43
+ try {
44
+ return JSON.parse(fs.readFileSync(stateMapPath, 'utf8')) as Record<string, unknown>;
45
+ } catch {
46
+ return {};
47
+ }
48
+ }
49
+
50
+ export function normalizeState(value: unknown): string | unknown {
51
+ if (typeof value !== 'string') {return value;}
52
+ return value.trim().toLowerCase();
53
+ }
54
+
55
+ function resolveMap(mapOrOptions: Record<string, unknown> | unknown): Record<string, unknown> {
56
+ if (mapOrOptions && typeof mapOrOptions === 'object' && !Array.isArray(mapOrOptions)) {
57
+ if (Object.prototype.hasOwnProperty.call(mapOrOptions, 'rootDir')
58
+ || Object.prototype.hasOwnProperty.call(mapOrOptions, 'config')
59
+ || Object.prototype.hasOwnProperty.call(mapOrOptions, 'fallbackPath')) {
60
+ return loadStateMap(mapOrOptions as StateMapOptions);
61
+ }
62
+ }
63
+ return (mapOrOptions as Record<string, unknown>) || loadStateMap();
64
+ }
65
+
66
+ export function toActual(virtualState: string, map: Record<string, unknown> = loadStateMap()): string | null {
67
+ const m = resolveMap(map) || {};
68
+ if (Object.prototype.hasOwnProperty.call(m, virtualState)) {
69
+ return m[virtualState] as string | null;
70
+ }
71
+ return virtualState;
72
+ }
73
+
74
+ export function toVirtual(actualState: string, map: Record<string, unknown> = loadStateMap()): string {
75
+ const m = resolveMap(map) || {};
76
+ const normalizedActual = normalizeState(actualState) as string;
77
+ for (const [virtual, actual] of Object.entries(m)) {
78
+ if (normalizeState(actual) === normalizedActual) {return virtual;}
79
+ }
80
+ return actualState;
81
+ }
82
+
83
+ interface TransitionVirtualOptions {
84
+ log?: (msg: string) => void;
85
+ }
86
+
87
+ interface TransitionTaskFn {
88
+ (slug: string, actual: string, options: TransitionVirtualOptions): boolean;
89
+ }
90
+
91
+ export function transitionVirtual(transitionTaskFn: TransitionTaskFn, slug: string, virtualState: string, options: TransitionVirtualOptions = {}, mapParam?: Record<string, unknown>): boolean {
92
+ let map: Record<string, unknown>;
93
+ if (mapParam && typeof mapParam === 'object' && !Array.isArray(mapParam)) {
94
+ if (Object.prototype.hasOwnProperty.call(mapParam, 'rootDir')
95
+ || Object.prototype.hasOwnProperty.call(mapParam, 'config')
96
+ || Object.prototype.hasOwnProperty.call(mapParam, 'fallbackPath')) {
97
+ map = loadStateMap(mapParam as StateMapOptions);
98
+ } else {
99
+ map = mapParam;
100
+ }
101
+ } else {
102
+ map = loadStateMap();
103
+ }
104
+ map = resolveMap(map);
105
+ const actual = toActual(virtualState, map);
106
+ if (actual === null) {
107
+ const opts = options;
108
+ const logFn = opts.log || log.plain;
109
+ logFn(status('INFO', `Virtual state '${virtualState}' has no backlog.md mapping for this board; skipping status write.`));
110
+ return true;
111
+ }
112
+ return transitionTaskFn(slug, actual, options);
113
+ }
114
+
115
+ export { SHIPPED_STATE_MAP_PATH };
@@ -1,7 +1,18 @@
1
- const fs = require('fs');
2
- const os = require('os');
3
- const path = require('path');
4
-
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.resolveParallixHome = resolveParallixHome;
7
+ exports.resolveStatsPath = resolveStatsPath;
8
+ exports.resolveAgentsLocalPath = resolveAgentsLocalPath;
9
+ exports.readJson = readJson;
10
+ exports.writeJson = writeJson;
11
+ exports.writeFileAtomic = writeFileAtomic;
12
+ exports.isInitialized = isInitialized;
13
+ const node_fs_1 = __importDefault(require("node:fs"));
14
+ const node_os_1 = __importDefault(require("node:os"));
15
+ const node_path_1 = __importDefault(require("node:path"));
5
16
  /**
6
17
  * Resolve the parallix-owned persistent-data root.
7
18
  *
@@ -17,86 +28,62 @@ const path = require('path');
17
28
  * call with `ensureDir: true`). Read-side callers may omit ensureDir so they
18
29
  * fail gracefully when PARALLIX_HOME has never been initialized.
19
30
  */
20
- /**
21
- * @param {{ensureDir?: boolean, platform?: string, env?: Record<string,string>, homedir?: Function} | boolean | string} [options]
22
- */
23
31
  function resolveParallixHome(options = {}) {
24
- if (typeof options === 'boolean' || typeof options === 'string') {
25
- // Legacy shim: resolveParallixHome(true) === { ensureDir: true }
26
- options = { ensureDir: Boolean(options) };
27
- }
28
-
29
- /** @type {{ensureDir?: boolean, platform?: string, env?: Record<string,string>, homedir?: Function}} */
30
- const opts = /** @type {{ensureDir?: boolean, platform?: string, env?: Record<string,string>, homedir?: Function}} */(options);
31
- const {
32
- ensureDir = false,
33
- platform = process.platform,
34
- env = process.env,
35
- homedir = os.homedir
36
- } = opts;
37
-
38
- let home;
39
-
40
- // --- env override (highest precedence) ---
41
- if (env.PARALLIX_HOME && typeof env.PARALLIX_HOME === 'string' && env.PARALLIX_HOME.trim().length > 0) {
42
- home = path.resolve(env.PARALLIX_HOME);
43
- } else if (platform === 'linux') {
44
- home = path.join(homedir(), '.local', 'state', 'parallix');
45
- } else if (platform === 'darwin') {
46
- home = path.join(homedir(), 'Library', 'Application Support', 'parallix');
47
- } else if (platform === 'win32') {
48
- const localAppData = env.LOCALAPPDATA;
49
- if (localAppData && typeof localAppData === 'string' && localAppData.trim().length > 0) {
50
- home = path.join(localAppData.trim(), 'parallix');
51
- } else {
52
- home = path.join(homedir(), '.parallix');
53
- }
54
- } else {
55
- // WSL, CI, other UNIX variants
56
- home = path.join(homedir(), '.parallix');
57
- }
58
-
59
- home = path.resolve(home);
60
-
61
- if (ensureDir) {
62
- fs.mkdirSync(home, { recursive: true });
63
- }
64
-
65
- return home;
32
+ if (typeof options === 'boolean' || typeof options === 'string') {
33
+ // Legacy shim: resolveParallixHome(true) === { ensureDir: true }
34
+ options = { ensureDir: Boolean(options) };
35
+ }
36
+ const opts = options;
37
+ const { ensureDir = false, platform = process.platform, env = process.env, homedir = node_os_1.default.homedir } = opts;
38
+ let home;
39
+ // --- env override (highest precedence) ---
40
+ if (env.PARALLIX_HOME && typeof env.PARALLIX_HOME === 'string' && env.PARALLIX_HOME.trim().length > 0) {
41
+ home = node_path_1.default.resolve(env.PARALLIX_HOME);
42
+ }
43
+ else if (platform === 'linux') {
44
+ home = node_path_1.default.join(homedir(), '.local', 'state', 'parallix');
45
+ }
46
+ else if (platform === 'darwin') {
47
+ home = node_path_1.default.join(homedir(), 'Library', 'Application Support', 'parallix');
48
+ }
49
+ else if (platform === 'win32') {
50
+ const localAppData = env.LOCALAPPDATA;
51
+ if (localAppData && typeof localAppData === 'string' && localAppData.trim().length > 0) {
52
+ home = node_path_1.default.join(localAppData.trim(), 'parallix');
53
+ }
54
+ else {
55
+ home = node_path_1.default.join(homedir(), '.parallix');
56
+ }
57
+ }
58
+ else {
59
+ // WSL, CI, other UNIX variants
60
+ home = node_path_1.default.join(homedir(), '.parallix');
61
+ }
62
+ home = node_path_1.default.resolve(home);
63
+ if (ensureDir) {
64
+ node_fs_1.default.mkdirSync(home, { recursive: true });
65
+ }
66
+ return home;
66
67
  }
67
-
68
- /**
69
- * Resolve the effective stats CSV path.
70
- *
71
- * Returns `<PARALLIX_HOME>/stats.csv` (creating PARALLIX_HOME if `ensureDir`
72
- * is true). Callers that pass an explicit `--csv-file` path bypass this
73
- * resolver entirely — the caller supplies the path before calling here.
74
- */
75
- /** @param {{ensureDir?: boolean, warn?: Function}} [options] */
76
68
  function resolveStatsPath(options = {}) {
77
- /** @type {{ensureDir?: boolean, warn?: Function}} */
78
- const opts = options;
79
- const home = resolveParallixHome({ ensureDir: opts.ensureDir !== false });
80
- return path.join(home, 'stats.csv');
69
+ const opts = options;
70
+ const home = resolveParallixHome({ ensureDir: opts.ensureDir !== false });
71
+ return node_path_1.default.join(home, 'stats.csv');
81
72
  }
82
-
83
73
  /**
84
74
  * Resolve the effective agent blocklist path.
85
75
  *
86
76
  * Returns `<PARALLIX_HOME>/agents.local.json`. Callers that pass an
87
77
  * explicit `targetPath` bypass this resolver.
88
78
  */
89
- /** @param {{ensureDir?: boolean} | string} [options] */
90
- function resolveAgentsLocalPath(options = {}) {
91
- if (typeof options === 'string') {
92
- return path.resolve(options);
93
- }
94
- /** @type {{ensureDir?: boolean}} */
95
- const opts = options;
96
- const home = resolveParallixHome({ ensureDir: opts.ensureDir !== false });
97
- return path.join(home, 'agents.local.json');
79
+ function resolveAgentsLocalPath(options) {
80
+ if (typeof options === 'string') {
81
+ return node_path_1.default.resolve(options);
82
+ }
83
+ const opts = options;
84
+ const home = resolveParallixHome({ ensureDir: opts?.ensureDir !== false });
85
+ return node_path_1.default.join(home, 'agents.local.json');
98
86
  }
99
-
100
87
  /**
101
88
  * Read a JSON file that lives under PARALLIX_HOME.
102
89
  *
@@ -105,75 +92,60 @@ function resolveAgentsLocalPath(options = {}) {
105
92
  * Malformed JSON returns `{ ok: false, error }` rather than throwing so
106
93
  * callers can decide whether this is a hard failure.
107
94
  */
108
- /** @param {string | (() => string)} pathOrResolution */
109
95
  function readJson(pathOrResolution) {
110
- let filePath;
111
- if (typeof pathOrResolution === 'function') {
112
- filePath = pathOrResolution();
113
- } else {
114
- filePath = pathOrResolution;
115
- }
116
-
117
- if (!filePath || !fs.existsSync(filePath)) {
118
- return { ok: false, error: null, data: null };
119
- }
120
-
121
- try {
122
- const raw = fs.readFileSync(filePath, 'utf8');
123
- const data = JSON.parse(raw);
124
- return { ok: true, error: null, data };
125
- } catch (err) {
126
- return { ok: false, error: err, data: null };
127
- }
96
+ let filePath;
97
+ if (typeof pathOrResolution === 'function') {
98
+ filePath = pathOrResolution();
99
+ }
100
+ else {
101
+ filePath = pathOrResolution;
102
+ }
103
+ if (!filePath || !node_fs_1.default.existsSync(filePath)) {
104
+ return { ok: false, error: null, data: null };
105
+ }
106
+ try {
107
+ const raw = node_fs_1.default.readFileSync(filePath, 'utf8');
108
+ const data = JSON.parse(raw);
109
+ return { ok: true, error: null, data };
110
+ }
111
+ catch (err) {
112
+ return { ok: false, error: err, data: null };
113
+ }
128
114
  }
129
-
130
115
  /**
131
116
  * Write JSON to a path under PARALLIX_HOME (or an explicit path).
132
117
  * Creates parent directories as needed.
133
118
  */
134
- /** @param {string | (() => string)} filePath @param {any} data */
135
119
  function writeJson(filePath, data) {
136
- if (typeof filePath === 'function') {
137
- filePath = filePath();
138
- }
139
- writeFileAtomic(filePath, `${JSON.stringify(data, null, 2)}\n`);
140
- return filePath;
120
+ if (typeof filePath === 'function') {
121
+ filePath = filePath();
122
+ }
123
+ writeFileAtomic(filePath, `${JSON.stringify(data, null, 2)}\n`);
124
+ return filePath;
141
125
  }
142
-
143
- /** @param {string} filePath @param {string} content */
144
126
  function writeFileAtomic(filePath, content) {
145
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
146
- const tempPath = path.join(
147
- path.dirname(filePath),
148
- `.${path.basename(filePath)}.${process.pid}.${Date.now()}.tmp`
149
- );
150
- try {
151
- fs.writeFileSync(tempPath, content, 'utf8');
152
- fs.renameSync(tempPath, filePath);
153
- } finally {
154
- if (fs.existsSync(tempPath)) {fs.unlinkSync(tempPath);}
155
- }
127
+ node_fs_1.default.mkdirSync(node_path_1.default.dirname(filePath), { recursive: true });
128
+ const tempPath = node_path_1.default.join(node_path_1.default.dirname(filePath), `.${node_path_1.default.basename(filePath)}.${process.pid}.${Date.now()}.tmp`);
129
+ try {
130
+ node_fs_1.default.writeFileSync(tempPath, content, 'utf8');
131
+ node_fs_1.default.renameSync(tempPath, filePath);
132
+ }
133
+ finally {
134
+ if (node_fs_1.default.existsSync(tempPath)) {
135
+ node_fs_1.default.unlinkSync(tempPath);
136
+ }
137
+ }
156
138
  }
157
-
158
139
  /**
159
140
  * Check whether PARALLIX_HOME has been initialized (directory exists).
160
141
  * Does NOT create the directory.
161
142
  */
162
143
  function isInitialized() {
163
- const home = resolveParallixHome({ ensureDir: false });
164
- try {
165
- return fs.statSync(home).isDirectory();
166
- } catch {
167
- return false;
168
- }
144
+ const home = resolveParallixHome({ ensureDir: false });
145
+ try {
146
+ return node_fs_1.default.statSync(home).isDirectory();
147
+ }
148
+ catch {
149
+ return false;
150
+ }
169
151
  }
170
-
171
- module.exports = {
172
- resolveParallixHome,
173
- resolveStatsPath,
174
- resolveAgentsLocalPath,
175
- readJson,
176
- writeJson,
177
- writeFileAtomic,
178
- isInitialized,
179
- };