@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@magnusekdahl/parallix",
3
- "version": "1.2.0",
3
+ "version": "1.3.0",
4
4
  "description": "AI mission workflow toolkit with a px CLI — local-first, human-in-the-loop multi-agent development",
5
5
  "license": "AGPL-3.0-or-later",
6
6
  "private": false,
@@ -48,16 +48,18 @@
48
48
  ],
49
49
  "scripts": {
50
50
  "build": "tsc",
51
- "prepublishOnly": "test -z \"$(find . -name '*.ts' ! -path './node_modules/*')\" || tsc",
51
+ "build:cjs": "tsc --rootDir . --outDir . --module CommonJS --moduleResolution Node --esModuleInterop",
52
+ "prepublishOnly": "npm run build:cjs",
53
+ "pretest": "npm run build:cjs",
52
54
  "test": "FORCE_COLOR=0 node --test test/*.test.js",
53
55
  "test:coverage": "FORCE_COLOR=0 node lib/commands/coverage-gate.js --lcov",
54
56
  "typecheck": "tsc --noEmit"
55
57
  },
56
58
  "devDependencies": {
57
59
  "@types/node": "^26.0.1",
58
- "@typescript-eslint/eslint-plugin": "^7.0.0",
59
- "@typescript-eslint/parser": "^7.0.0",
60
- "eslint": "^8.57.0",
60
+ "@typescript-eslint/eslint-plugin": "^8.62.1",
61
+ "@typescript-eslint/parser": "^8.62.1",
62
+ "eslint": "^9.39.4",
61
63
  "sonarqube-scanner": "^4.0.1",
62
64
  "typescript": "^5.4.0"
63
65
  }
package/prompts/draft.md CHANGED
@@ -24,6 +24,7 @@ Drafting requirements:
24
24
  - {{classificationInstructions}}
25
25
  - preserve `{{taskPath}}`: update content as needed but do not delete, rename, or move the file
26
26
  - do not edit the backlog `assignee` field; the workflow records ownership itself
27
+ - Refinement Signals section must use net engineering lines (NEL) bucket format (`Predicted NEL bucket: Small (0–80) / Medium (81–235) / Large (235+)`) and must NOT use the old agent-percentage-usage format
27
28
 
28
29
  Bug-labeled missions (regression-test-first / "lock the bug"):
29
30
  - this section applies only when the backlog task at `{{taskPath}}` carries a `bug` label (in addition to its `ai_sdlc` or `user_value` classification). If there is no `bug` label, ignore this section entirely.
@@ -14,7 +14,7 @@ Output: propose exactly 10 candidate missions. For each, provide:
14
14
  - Primary leverage (confidence / credibility / promotion signal)
15
15
  - Primary skill domains (BE / Web / Android / iOS / Ops / Security)
16
16
  - Estimated scope rank (1–10, 1 = largest)
17
- - Estimated agent % usage limit (`n/a` if not ready)
17
+ - Predicted NEL bucket (`n/a` if not ready)
18
18
  - Selection note (`activate as-is` | `split first` | `defer` | `n/a`)
19
19
  - Main risk mitigated (skill rot / credibility gap / system weakness)
20
20
  - Duplicate check: none, or follow-up of <existing mission>
package/px.js CHANGED
@@ -1,236 +1,252 @@
1
1
  #!/usr/bin/env node
2
-
3
- const fs = require('fs');
4
- const path = require('path');
5
- const fmt = require('./lib/core/fmt');
6
- const missionStart = require('./lib/commands/mission-start');
7
- const { createEvent } = require('./lib/review/review-events');
8
- const workflow = require('./index');
9
- const packageJson = require('./package.json');
10
-
2
+ "use strict";
3
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
4
+ if (k2 === undefined) k2 = k;
5
+ var desc = Object.getOwnPropertyDescriptor(m, k);
6
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
7
+ desc = { enumerable: true, get: function() { return m[k]; } };
8
+ }
9
+ Object.defineProperty(o, k2, desc);
10
+ }) : (function(o, m, k, k2) {
11
+ if (k2 === undefined) k2 = k;
12
+ o[k2] = m[k];
13
+ }));
14
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
15
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
16
+ }) : function(o, v) {
17
+ o["default"] = v;
18
+ });
19
+ var __importStar = (this && this.__importStar) || (function () {
20
+ var ownKeys = function(o) {
21
+ ownKeys = Object.getOwnPropertyNames || function (o) {
22
+ var ar = [];
23
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
24
+ return ar;
25
+ };
26
+ return ownKeys(o);
27
+ };
28
+ return function (mod) {
29
+ if (mod && mod.__esModule) return mod;
30
+ var result = {};
31
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
32
+ __setModuleDefault(result, mod);
33
+ return result;
34
+ };
35
+ })();
36
+ var __importDefault = (this && this.__importDefault) || function (mod) {
37
+ return (mod && mod.__esModule) ? mod : { "default": mod };
38
+ };
39
+ Object.defineProperty(exports, "__esModule", { value: true });
40
+ exports.parseArgs = parseArgs;
41
+ exports.shellInit = shellInit;
42
+ exports.versionInfo = versionInfo;
43
+ exports.formatVersionInfo = formatVersionInfo;
44
+ exports.parseReviewEventArgs = parseReviewEventArgs;
45
+ exports.run = run;
46
+ const node_fs_1 = __importDefault(require("node:fs"));
47
+ const node_path_1 = __importDefault(require("node:path"));
48
+ const fmt = __importStar(require("./lib/core/fmt.js"));
49
+ const missionStart = require("./lib/commands/mission-start.js");
50
+ const review_events_js_1 = require("./lib/review/review-events.js");
51
+ const workflow = __importStar(require("./index.js"));
52
+ const package_json_1 = __importDefault(require("./package.json"));
11
53
  function parseArgs(argv, baseCwd = process.cwd()) {
12
- const args = [...argv];
13
- if (args[0] === '--version' || args[0] === '-v') {
14
- return { target: path.resolve(baseCwd), command: 'version', args: [] };
15
- }
16
-
17
- const command = args.shift();
18
- if (!command) {
19
- throw new Error('Missing command');
20
- }
21
-
22
- return { target: path.resolve(baseCwd), command, args };
54
+ const args = [...argv];
55
+ if (args[0] === '--version' || args[0] === '-v') {
56
+ return { target: node_path_1.default.resolve(baseCwd), command: 'version', args: [] };
57
+ }
58
+ const command = args.shift();
59
+ if (!command) {
60
+ throw new Error('Missing command');
61
+ }
62
+ return { target: node_path_1.default.resolve(baseCwd), command, args };
23
63
  }
24
-
25
64
  // Emits a shell function named `px` that wraps the globally installed `px`
26
65
  // runner and switches the caller's terminal into the next mission worktree
27
66
  // when the runtime prints a transition signal. A shell function always runs in
28
67
  // the current shell, so it can `cd` the caller (an npm `bin` subprocess cannot).
29
68
  // Install with: eval "$(px shell-init bash)" (or zsh) in your shell rc.
30
69
  function shellInit(shell = 'bash') {
31
- const normalized = String(shell || 'bash').toLowerCase();
32
- if (normalized !== 'bash' && normalized !== 'zsh') {
33
- throw new Error(`Unsupported shell for shell-init: ${shell} (supported: bash, zsh)`);
34
- }
35
- // zsh exposes pipe statuses via the lowercase 1-indexed `pipestatus` array;
36
- // bash uses the uppercase 0-indexed `PIPESTATUS`.
37
- const exitCapture = normalized === 'zsh'
38
- ? '_px_exit=${pipestatus[1]}'
39
- : '_px_exit=${PIPESTATUS[0]}';
40
-
41
- return [
42
- '# px shell integration. Add to your shell rc:',
43
- `# eval "$(px shell-init ${normalized})"`,
44
- '# Defines a `px` shell function that runs the globally installed `px` and',
45
- '# changes your terminal into the next mission worktree on transitions.',
46
- 'px() {',
47
- ' local _px_log _px_exit _px_signal _px_target _px_current',
48
- ' _px_log="$(mktemp)" || return 1',
49
- ' command px "$@" 2>&1 | tee "$_px_log"',
50
- ` ${exitCapture}`,
51
- ' _px_signal="$(grep "\\[INFO\\] Next: cd " "$_px_log" | tail -n 1 | sed "s/.*\\[INFO\\] Next: cd //")"',
52
- ' if [ -z "$_px_signal" ]; then',
53
- ' _px_signal="$(grep "\\[INFO\\] Working directory: " "$_px_log" | tail -n 1 | sed "s/.*\\[INFO\\] Working directory: //")"',
54
- ' fi',
55
- ' rm -f "$_px_log"',
56
- ' if [ -n "$_px_signal" ]; then',
57
- ' _px_target="${_px_signal#"${_px_signal%%[![:space:]]*}"}"',
58
- ' _px_target="${_px_target%"${_px_target##*[![:space:]]}"}"',
59
- ' if [ -d "$_px_target" ]; then',
60
- ' _px_current="$(pwd -P 2>/dev/null)"',
61
- ' if [ "$_px_current" != "$(cd "$_px_target" && pwd -P)" ]; then',
62
- ' cd "$_px_target" && echo "[px] Switched terminal context to: $(pwd)"',
63
- ' fi',
64
- ' else',
65
- ' echo "[px] ERROR: target directory \'$_px_target\' not found." >&2',
66
- ' fi',
67
- ' fi',
68
- ' return $_px_exit',
69
- '}',
70
- '',
71
- ].join('\n');
70
+ const normalized = String(shell || 'bash').toLowerCase();
71
+ if (normalized !== 'bash' && normalized !== 'zsh') {
72
+ throw new Error(`Unsupported shell for shell-init: ${shell} (supported: bash, zsh)`);
73
+ }
74
+ // zsh exposes pipe statuses via the lowercase 1-indexed `pipestatus` array;
75
+ // bash uses the uppercase 0-indexed `PIPESTATUS`.
76
+ const exitCapture = normalized === 'zsh'
77
+ ? '_px_exit=${pipestatus[1]}'
78
+ : '_px_exit=${PIPESTATUS[0]}';
79
+ return [
80
+ '# px shell integration. Add to your shell rc:',
81
+ `# eval "$(px shell-init ${normalized})"`,
82
+ '# Defines a `px` shell function that runs the globally installed `px` and',
83
+ '# changes your terminal into the next mission worktree on transitions.',
84
+ 'px() {',
85
+ ' local _px_log _px_exit _px_signal _px_target _px_current',
86
+ ' _px_log="$(mktemp)" || return 1',
87
+ ' command px "$@" 2>&1 | tee "$_px_log"',
88
+ ` ${exitCapture}`,
89
+ ' _px_signal="$(grep "\\\\[INFO\\\\] Next: cd " "$_px_log" | tail -n 1 | sed "s/.*\\\\[INFO\\\\] Next: cd //")"',
90
+ ' if [ -z "$_px_signal" ]; then',
91
+ ' _px_signal="$(grep "\\\\[INFO\\\\] Working directory: " "$_px_log" | tail -n 1 | sed "s/.*\\\\[INFO\\\\] Working directory: //")"',
92
+ ' fi',
93
+ ' rm -f "$_px_log"',
94
+ ' if [ -n "$_px_signal" ]; then',
95
+ ' _px_target="${_px_signal#"${_px_signal%%[![:space:]]*}"}"',
96
+ ' _px_target="${_px_target%"${_px_target##*[![:space:]]}"}"',
97
+ ' if [ -d "$_px_target" ]; then',
98
+ ' _px_current="$(pwd -P 2>/dev/null)"',
99
+ ' if [ "$_px_current" != "$(cd "$_px_target" && pwd -P)" ]; then',
100
+ ' cd "$_px_target" && echo "[px] Switched terminal context to: $(pwd)"',
101
+ ' fi',
102
+ ' fi',
103
+ ' fi',
104
+ ' return $_px_exit',
105
+ '}',
106
+ '',
107
+ ].join('\n');
72
108
  }
73
-
74
109
  function versionInfo() {
75
- return {
76
- name: packageJson.name,
77
- version: packageJson.version,
78
- pxPath: __filename,
79
- packageRoot: __dirname,
80
- node: process.version,
81
- };
110
+ return {
111
+ name: package_json_1.default.name,
112
+ version: package_json_1.default.version,
113
+ pxPath: __filename,
114
+ packageRoot: __dirname,
115
+ node: process.version,
116
+ };
82
117
  }
83
-
84
118
  function formatVersionInfo(info = versionInfo()) {
85
- return [
86
- `${info.name} ${info.version}`,
87
- `px: ${info.pxPath}`,
88
- `package: ${info.packageRoot}`,
89
- `node: ${info.node}`,
90
- ].join('\n');
119
+ return [
120
+ `${info.name} ${info.version}`,
121
+ `px: ${info.pxPath}`,
122
+ `package: ${info.packageRoot}`,
123
+ `node: ${info.node}`,
124
+ ].join('\n');
91
125
  }
92
-
93
126
  function parseReviewEventArgs(args) {
94
- const slug = args[0];
95
- if (!slug) {
96
- throw new Error('Usage: review-event <slug> --type <event-type> --actor <actor> --content <text> [--timestamp <stamp>] [--skip-git]');
97
- }
98
-
99
- const parsed = {
100
- slug,
101
- type: null,
102
- actor: null,
103
- content: '',
104
- timestamp: null,
105
- skipGit: false,
106
- };
107
-
108
- for (let i = 1; i < args.length; i += 1) {
109
- const arg = args[i];
110
- if (arg === '--skip-git') {
111
- parsed.skipGit = true;
112
- continue;
127
+ const slug = args[0];
128
+ if (!slug) {
129
+ throw new Error('Usage: review-event <slug> --type <event-type> --actor <actor> --content <text> [--timestamp <stamp>] [--skip-git]');
113
130
  }
114
- if (!arg.startsWith('--')) {
115
- throw new Error(`Unexpected review-event argument: ${arg}`);
131
+ const parsed = {
132
+ slug,
133
+ type: null,
134
+ actor: null,
135
+ content: '',
136
+ timestamp: null,
137
+ skipGit: false,
138
+ };
139
+ for (let i = 1; i < args.length; i += 1) {
140
+ const arg = args[i];
141
+ if (arg === '--skip-git') {
142
+ parsed.skipGit = true;
143
+ continue;
144
+ }
145
+ if (!arg.startsWith('--')) {
146
+ throw new Error(`Unexpected review-event argument: ${arg}`);
147
+ }
148
+ const key = arg.slice(2);
149
+ const value = args[i + 1];
150
+ if (!value) {
151
+ throw new Error(`${arg} requires a value`);
152
+ }
153
+ i += 1;
154
+ if (key === 'type') {
155
+ parsed.type = value;
156
+ }
157
+ else if (key === 'actor') {
158
+ parsed.actor = value;
159
+ }
160
+ else if (key === 'content') {
161
+ parsed.content = value;
162
+ }
163
+ else if (key === 'timestamp') {
164
+ parsed.timestamp = value;
165
+ }
166
+ else {
167
+ throw new Error(`Unknown review-event option: ${arg}`);
168
+ }
116
169
  }
117
- const key = arg.slice(2);
118
- const value = args[i + 1];
119
- if (!value) {
120
- throw new Error(`${arg} requires a value`);
170
+ if (!parsed.type) {
171
+ throw new Error('review-event requires --type');
121
172
  }
122
- i += 1;
123
-
124
- if (key === 'type') parsed.type = value;
125
- else if (key === 'actor') parsed.actor = value;
126
- else if (key === 'content') parsed.content = value;
127
- else if (key === 'timestamp') parsed.timestamp = value;
128
- else throw new Error(`Unknown review-event option: ${arg}`);
129
- }
130
-
131
- if (!parsed.type) {
132
- throw new Error('review-event requires --type');
133
- }
134
-
135
- return parsed;
173
+ return parsed;
136
174
  }
137
-
138
175
  async function run(argv = process.argv.slice(2), options = {}) {
139
- const log = options.log || fmt.log.plain;
140
- const error = options.error || fmt.log.plainError;
141
- const baseCwd = options.baseCwd || process.cwd();
142
-
143
- let parsed;
144
- try {
145
- parsed = parseArgs(argv, baseCwd);
146
- } catch (err) {
147
- error(fmt.status('FAIL', err.message));
148
- return 1;
149
- }
150
-
151
- // shell-init prints a shell snippet and never touches a target repository, so
152
- // it runs before the target-path check.
153
- if (parsed.command === 'shell-init') {
176
+ const log = options.log || fmt.log.plain;
177
+ const error = options.error || fmt.log.plainError;
178
+ const baseCwd = options.baseCwd || process.cwd();
179
+ let parsed;
154
180
  try {
155
- log(shellInit(parsed.args[0]));
156
- return 0;
157
- } catch (err) {
158
- error(fmt.status('FAIL', err.message));
159
- return 1;
181
+ parsed = parseArgs(argv, baseCwd);
160
182
  }
161
- }
162
-
163
- if (!fs.existsSync(parsed.target) || !fs.statSync(parsed.target).isDirectory()) {
164
- error(fmt.status('FAIL', `Target repository path not found: ${parsed.target}`));
165
- return 1;
166
- }
167
-
168
- if (parsed.command === 'version') {
169
- log(formatVersionInfo());
170
- return 0;
171
- }
172
-
173
- const previousCwd = process.cwd();
174
- process.chdir(parsed.target);
175
-
176
- try {
177
- if (parsed.command === 'review-event') {
178
- const eventArgs = parseReviewEventArgs(parsed.args);
179
- const result = createEvent(
180
- eventArgs.slug,
181
- eventArgs.type,
182
- {
183
- actor: eventArgs.actor,
184
- content: eventArgs.content,
185
- timestamp: eventArgs.timestamp,
186
- },
187
- {
188
- worktree: parsed.target,
189
- skipGit: eventArgs.skipGit,
190
- log,
191
- error,
192
- },
193
- );
194
- if (result.ok) {
195
- log(fmt.status('PASS', `Review event path: ${path.relative(parsed.target, result.path)}`));
196
- }
197
- return result.ok ? 0 : 1;
183
+ catch (err) {
184
+ error(fmt.status('FAIL', err.message));
185
+ return 1;
198
186
  }
199
-
200
- if (parsed.command === 'verify-env') {
201
- const result = missionStart([], { command: 'verify-env', returnResult: true, log, error });
202
- return result && result.pass ? 0 : 1;
187
+ // shell-init prints a shell snippet and never touches a target repository, so
188
+ // it runs before the target-path check.
189
+ if (parsed.command === 'shell-init') {
190
+ try {
191
+ log(shellInit(parsed.args[0]));
192
+ return 0;
193
+ }
194
+ catch (err) {
195
+ error(fmt.status('FAIL', err.message));
196
+ return 1;
197
+ }
198
+ }
199
+ if (!node_fs_1.default.existsSync(parsed.target) || !node_fs_1.default.statSync(parsed.target).isDirectory()) {
200
+ error(fmt.status('FAIL', `Target repository path not found: ${parsed.target}`));
201
+ return 1;
202
+ }
203
+ if (parsed.command === 'version') {
204
+ log(formatVersionInfo());
205
+ return 0;
206
+ }
207
+ const previousCwd = process.cwd();
208
+ process.chdir(parsed.target);
209
+ try {
210
+ if (parsed.command === 'review-event') {
211
+ const eventArgs = parseReviewEventArgs(parsed.args);
212
+ const result = (0, review_events_js_1.createEvent)(eventArgs.slug, eventArgs.type || '', {
213
+ actor: eventArgs.actor || '',
214
+ content: eventArgs.content,
215
+ timestamp: eventArgs.timestamp || undefined,
216
+ }, {
217
+ worktree: parsed.target,
218
+ skipGit: eventArgs.skipGit,
219
+ log,
220
+ error,
221
+ });
222
+ if (result.ok && result.path) {
223
+ log(fmt.status('PASS', `Review event path: ${node_path_1.default.relative(parsed.target, result.path)}`));
224
+ }
225
+ return result.ok ? 0 : 1;
226
+ }
227
+ if (parsed.command === 'verify-env') {
228
+ const result = missionStart([], { command: 'verify-env', returnResult: true, log, error });
229
+ return result && result.pass ? 0 : 1;
230
+ }
231
+ let exitCode = 0;
232
+ await workflow.main([parsed.command, ...parsed.args], {
233
+ cwdFn: () => parsed.target,
234
+ exitFn: ((code) => { exitCode = typeof code === 'number' ? code : 0; }),
235
+ logFn: log,
236
+ errorFn: error,
237
+ });
238
+ return exitCode;
239
+ }
240
+ catch (err) {
241
+ error(fmt.status('FAIL', err.message));
242
+ return 1;
243
+ }
244
+ finally {
245
+ process.chdir(previousCwd);
203
246
  }
204
-
205
- let exitCode = 0;
206
- await workflow.main([parsed.command, ...parsed.args], {
207
- cwdFn: () => parsed.target,
208
- exitFn: code => {
209
- exitCode = typeof code === 'number' ? code : 0;
210
- },
211
- logFn: log,
212
- errorFn: error,
213
- });
214
- return exitCode;
215
- } catch (err) {
216
- error(fmt.status('FAIL', err.message));
217
- return 1;
218
- } finally {
219
- process.chdir(previousCwd);
220
- }
221
247
  }
222
-
223
- if (require.main === module) {
224
- run().then(code => {
225
- process.exitCode = code;
226
- });
248
+ if (typeof require !== 'undefined' && require.main === module) {
249
+ run().then(code => {
250
+ process.exitCode = code;
251
+ });
227
252
  }
228
-
229
- module.exports = {
230
- formatVersionInfo,
231
- parseArgs,
232
- parseReviewEventArgs,
233
- run,
234
- shellInit,
235
- versionInfo,
236
- };