taskplane 0.1.2 → 0.1.4

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/bin/taskplane.mjs CHANGED
@@ -255,6 +255,42 @@ monitoring:
255
255
  // COMMANDS
256
256
  // ═════════════════════════════════════════════════════════════════════════════
257
257
 
258
+ /** Auto-commit task files to git so they're available in orchestrator worktrees. */
259
+ async function autoCommitTaskFiles(projectRoot, tasksRoot) {
260
+ // Check if we're in a git repo
261
+ try {
262
+ execSync("git rev-parse --is-inside-work-tree", { cwd: projectRoot, stdio: "pipe" });
263
+ } catch {
264
+ // Not a git repo — skip silently
265
+ return;
266
+ }
267
+
268
+ // Stage the tasks directory (not .pi/ — that's gitignored)
269
+ const tasksDir = path.join(projectRoot, tasksRoot);
270
+ if (!fs.existsSync(tasksDir)) return;
271
+
272
+ try {
273
+ // Check if there's anything new to commit
274
+ execSync(`git add "${tasksRoot}"`, { cwd: projectRoot, stdio: "pipe" });
275
+ const status = execSync("git diff --cached --name-only", { cwd: projectRoot, stdio: "pipe" })
276
+ .toString()
277
+ .trim();
278
+
279
+ if (!status) return; // nothing staged
280
+
281
+ execSync('git commit -m "chore: initialize taskplane tasks"', {
282
+ cwd: projectRoot,
283
+ stdio: "pipe",
284
+ });
285
+ console.log(`\n ${c.green}git${c.reset} committed ${tasksRoot}/ to git`);
286
+ console.log(` ${c.dim}(orchestrator worktrees require committed files)${c.reset}`);
287
+ } catch (err) {
288
+ // Git commit failed — warn but don't block init
289
+ console.log(`\n ${WARN} Could not auto-commit task files to git.`);
290
+ console.log(` ${c.dim}Run manually before using /orch: git add ${tasksRoot} && git commit -m "add taskplane tasks"${c.reset}`);
291
+ }
292
+ }
293
+
258
294
  // ─── init ───────────────────────────────────────────────────────────────────
259
295
 
260
296
  async function cmdInit(args) {
@@ -359,6 +395,11 @@ async function cmdInit(args) {
359
395
  }
360
396
  }
361
397
 
398
+ // Auto-commit task files to git so they're available in worktrees
399
+ if (!dryRun) {
400
+ await autoCommitTaskFiles(projectRoot, vars.tasks_root);
401
+ }
402
+
362
403
  // Report
363
404
  console.log(`\n${OK} ${c.bold}Taskplane initialized!${c.reset}\n`);
364
405
  console.log(`${c.bold}Quick start:${c.reset}`);
@@ -18,7 +18,8 @@ const { execFileSync, exec } = require("child_process");
18
18
  // ─── Configuration ──────────────────────────────────────────────────────────
19
19
 
20
20
  const PUBLIC_DIR = path.join(__dirname, "public");
21
- const DEFAULT_PORT = 8099;
21
+ const DEFAULT_PORT = 8100;
22
+ const MAX_PORT_ATTEMPTS = 20;
22
23
  const POLL_INTERVAL = 2000; // ms between state checks
23
24
 
24
25
  // REPO_ROOT is resolved after parseArgs() — see initialization below.
@@ -427,7 +428,7 @@ function serveHistoryEntry(req, res, batchId) {
427
428
 
428
429
  // ─── HTTP Server ────────────────────────────────────────────────────────────
429
430
 
430
- function createServer(port) {
431
+ function createServer() {
431
432
  const server = http.createServer((req, res) => {
432
433
  const pathname = new URL(req.url, "http://localhost").pathname;
433
434
 
@@ -456,10 +457,6 @@ function createServer(port) {
456
457
  }
457
458
  });
458
459
 
459
- server.listen(port, () => {
460
- console.log(`\n Orchestrator Dashboard → http://localhost:${port}\n`);
461
- });
462
-
463
460
  return server;
464
461
  }
465
462
 
@@ -473,7 +470,57 @@ function openBrowser(url) {
473
470
 
474
471
  // ─── Main ───────────────────────────────────────────────────────────────────
475
472
 
476
- function main() {
473
+ /** Try to listen on a port. Resolves with the port on success, rejects on EADDRINUSE. */
474
+ function tryListen(server, port) {
475
+ return new Promise((resolve, reject) => {
476
+ const onError = (err) => {
477
+ server.removeListener("listening", onListening);
478
+ reject(err);
479
+ };
480
+ const onListening = () => {
481
+ server.removeListener("error", onError);
482
+ resolve(port);
483
+ };
484
+ server.once("error", onError);
485
+ server.once("listening", onListening);
486
+ server.listen(port);
487
+ });
488
+ }
489
+
490
+ /** Find an available port starting from `start`, trying up to MAX_PORT_ATTEMPTS. */
491
+ async function findPort(server, start, explicit) {
492
+ // If the user explicitly passed --port, only try that one
493
+ if (explicit) {
494
+ try {
495
+ return await tryListen(server, start);
496
+ } catch (err) {
497
+ if (err.code === "EADDRINUSE") {
498
+ console.error(`\n Port ${start} is already in use.`);
499
+ console.error(` Try: taskplane dashboard --port ${start + 1}\n`);
500
+ process.exit(1);
501
+ }
502
+ throw err;
503
+ }
504
+ }
505
+ // Auto-scan for an available port
506
+ for (let port = start; port < start + MAX_PORT_ATTEMPTS; port++) {
507
+ try {
508
+ return await tryListen(server, port);
509
+ } catch (err) {
510
+ if (err.code === "EADDRINUSE") {
511
+ // Close the server so we can retry on the next port
512
+ server.close();
513
+ server = createServer();
514
+ continue;
515
+ }
516
+ throw err;
517
+ }
518
+ }
519
+ console.error(`\n No available port found in range ${start}-${start + MAX_PORT_ATTEMPTS - 1}.\n`);
520
+ process.exit(1);
521
+ }
522
+
523
+ async function main() {
477
524
  const opts = parseArgs();
478
525
 
479
526
  // Resolve project root: --root flag > cwd
@@ -481,7 +528,11 @@ function main() {
481
528
  BATCH_STATE_PATH = path.join(REPO_ROOT, ".pi", "batch-state.json");
482
529
  BATCH_HISTORY_PATH = path.join(REPO_ROOT, ".pi", "batch-history.json");
483
530
 
484
- const server = createServer(opts.port);
531
+ const server = createServer();
532
+ const explicitPort = process.argv.slice(2).includes("--port");
533
+ const port = await findPort(server, opts.port, explicitPort);
534
+
535
+ console.log(`\n Orchestrator Dashboard → http://localhost:${port}\n`);
485
536
 
486
537
  // Broadcast state to all SSE clients on interval
487
538
  const pollTimer = setInterval(broadcastState, POLL_INTERVAL);
@@ -507,7 +558,7 @@ function main() {
507
558
 
508
559
  // Auto-open browser
509
560
  if (opts.open) {
510
- setTimeout(() => openBrowser(`http://localhost:${opts.port}`), 500);
561
+ setTimeout(() => openBrowser(`http://localhost:${port}`), 500);
511
562
  }
512
563
 
513
564
  // Graceful shutdown
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "taskplane",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "AI agent orchestration for pi — parallel task execution with checkpoint discipline",
5
5
  "keywords": [
6
6
  "pi-package",