@delorenj/pjangler 1.2.6 → 1.2.9

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 (27) hide show
  1. package/dist/index.js +54 -16
  2. package/dist/mcp-server.js +66 -17
  3. package/package.json +1 -1
  4. package/templates/commonproject/AGENTS.md +3 -3
  5. package/templates/commonproject/README.md +12 -11
  6. package/templates/commonproject/copier.yml +24 -8
  7. package/templates/commonproject/mise.toml +3 -3
  8. package/templates/commonproject/template/.project.json.jinja +13 -6
  9. package/templates/hermes-agent/README.md +8 -8
  10. package/templates/hermes-agent/copier.yml +3 -4
  11. package/templates/hermes-agent/docs/architecture.md +4 -4
  12. package/templates/hermes-agent/docs/operations.md +2 -2
  13. package/templates/hermes-agent/docs/sentinel/README.md +6 -8
  14. package/templates/hermes-agent/docs/sentinel/architecture.md +2 -1
  15. package/templates/hermes-agent/docs/sentinel/development.md +13 -12
  16. package/templates/hermes-agent/docs/sentinel/providers.md +12 -32
  17. package/templates/hermes-agent/install-local.sh +6 -13
  18. package/templates/hermes-agent/template/.scripts/42-ticket-provider.sh +14 -37
  19. package/templates/hermes-agent/template/.scripts/70-systemd.sh +1 -0
  20. package/templates/hermes-agent/template/.scripts/lib/ticket-provider.sh +6 -2
  21. package/templates/hermes-agent/template/.scripts/sentinel/docs/autonomous-delegated-review.md +2 -2
  22. package/templates/hermes-agent/template/.scripts/sentinel/docs/continuous-ticket-orchestration.md +1 -1
  23. package/templates/hermes-agent/template/.scripts/sentinel.prompt.md.jinja +5 -7
  24. package/templates/hermes-agent/template/SOUL.md.jinja +8 -7
  25. package/templates/hermes-agent/template/role.yaml.jinja +3 -6
  26. package/templates/hermes-agent/template/.scripts/40-plane.sh +0 -51
  27. package/templates/hermes-agent/template/.scripts/providers/linear.sh +0 -176
package/dist/index.js CHANGED
@@ -727,7 +727,7 @@ import * as p from "@clack/prompts";
727
727
  function detectTicketProvider(targetDir) {
728
728
  try {
729
729
  const t = JSON.parse(readFileSync(join4(targetDir, ".project.json"), "utf8"))?.ticket_provider?.type;
730
- return t === "plane" || t === "linear" || t === "trello" ? t : void 0;
730
+ return t === "plane" || t === "trello" ? t : void 0;
731
731
  } catch {
732
732
  return void 0;
733
733
  }
@@ -1274,6 +1274,34 @@ function validateProjectRegistry(registry) {
1274
1274
  }
1275
1275
  }
1276
1276
  }
1277
+ function normalizeTicketProvider(value) {
1278
+ const type = (value || "plane").trim().toLowerCase();
1279
+ if (type === "plane" || type === "trello") return type;
1280
+ throw new Error(`Unsupported ticket provider: ${value}. Supported providers: plane, trello`);
1281
+ }
1282
+ function buildTicketProviderBlock(input) {
1283
+ const type = normalizeTicketProvider(input.type);
1284
+ const boardId = input.boardId ?? "";
1285
+ if (type === "trello") {
1286
+ return {
1287
+ type,
1288
+ workspace: input.workspace ?? "",
1289
+ identifier: input.identifier,
1290
+ board_id: boardId,
1291
+ board_url: input.boardUrl ?? (boardId ? `https://trello.com/b/${boardId}` : ""),
1292
+ state: "planned"
1293
+ };
1294
+ }
1295
+ const workspace = input.workspace ?? "33god";
1296
+ return {
1297
+ type,
1298
+ workspace,
1299
+ identifier: input.identifier,
1300
+ board_id: boardId,
1301
+ board_url: input.boardUrl ?? (boardId ? `https://plane.delo.sh/${workspace}/projects/${boardId}/issues/` : ""),
1302
+ state: "planned"
1303
+ };
1304
+ }
1277
1305
  function slugifyProjectName(value) {
1278
1306
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
1279
1307
  }
@@ -1352,14 +1380,13 @@ function planProjectInit(input) {
1352
1380
  primary_language: input.primaryLanguage ?? "python"
1353
1381
  }
1354
1382
  },
1355
- ticket_provider: {
1383
+ ticket_provider: buildTicketProviderBlock({
1356
1384
  type: input.ticketProvider ?? "plane",
1357
- workspace: input.planeWorkspace ?? "33god",
1358
1385
  identifier,
1359
- board_id: input.planeProjectId ?? "",
1360
- board_url: input.planeProjectId ? `https://plane.delo.sh/${input.planeWorkspace ?? "33god"}/projects/${input.planeProjectId}/issues/` : "",
1361
- state: input.live ? "planned" : "planned"
1362
- },
1386
+ boardId: input.boardId ?? input.planeProjectId,
1387
+ boardUrl: input.boardUrl,
1388
+ workspace: input.boardWorkspace ?? input.planeWorkspace
1389
+ }),
1363
1390
  agents,
1364
1391
  created_at: existing?.created_at ?? now,
1365
1392
  updated_at: now
@@ -1386,6 +1413,9 @@ function planProjectInit(input) {
1386
1413
  ticketProvider: project.ticket_provider.type,
1387
1414
  planeWorkspace: project.ticket_provider.workspace ?? "33god",
1388
1415
  planeProjectId: project.ticket_provider.board_id ?? "",
1416
+ ticketWorkspace: project.ticket_provider.workspace ?? "",
1417
+ boardId: project.ticket_provider.board_id ?? "",
1418
+ boardUrl: project.ticket_provider.board_url ?? "",
1389
1419
  projectIdentifier: identifier,
1390
1420
  primaryLanguage: project.template.commonproject.primary_language,
1391
1421
  agentHooksLayer: resolveAgentHooksLayer(input.agentHooksLayer),
@@ -1395,9 +1425,10 @@ function planProjectInit(input) {
1395
1425
  actions.push(
1396
1426
  { kind: "project.write-manifest", path: join8(targetDir, ".project.json"), manifest },
1397
1427
  {
1398
- kind: "plane.create-or-link",
1428
+ kind: "ticket-provider.create-or-link",
1399
1429
  enabled: live,
1400
1430
  live,
1431
+ provider: project.ticket_provider.type,
1401
1432
  workspace: project.ticket_provider.workspace ?? "33god",
1402
1433
  identifier,
1403
1434
  state: live ? "planned" : "planned",
@@ -1460,8 +1491,8 @@ function executeProjectInitPlan(plan) {
1460
1491
  }
1461
1492
  } else if (action.kind === "registry.upsert") {
1462
1493
  pendingRegistryAction = action;
1463
- } else if (action.kind === "plane.create-or-link") {
1464
- logs.push(action.enabled ? "plane.create-or-link requires a live provider integration" : "plane.create-or-link skipped (requires --live)");
1494
+ } else if (action.kind === "ticket-provider.create-or-link") {
1495
+ logs.push(action.enabled ? "ticket-provider.create-or-link requires a live provider integration" : "ticket-provider.create-or-link skipped (requires --live)");
1465
1496
  } else if (action.kind === "hermes.provision-agent") {
1466
1497
  logs.push(action.enabled ? "hermes.provision-agent planned for the caller to execute" : "hermes.provision-agent skipped");
1467
1498
  }
@@ -1515,7 +1546,7 @@ function formatProjectInitPlan(plan) {
1515
1546
  lines.push(` ${cyan(glyph.bullet)} ${action.kind}`);
1516
1547
  if (action.kind === "copier.copy.commonproject") lines.push(` ${dim(`target: ${action.targetDir}`)}`);
1517
1548
  if (action.kind === "project.write-manifest") lines.push(` ${dim(`path: ${action.path}`)}`);
1518
- if (action.kind === "plane.create-or-link" && action.reason) lines.push(` ${dim(`note: ${action.reason}`)}`);
1549
+ if (action.kind === "ticket-provider.create-or-link" && action.reason) lines.push(` ${dim(`note: ${action.reason}`)}`);
1519
1550
  }
1520
1551
  lines.push("");
1521
1552
  return lines.join("\n");
@@ -1578,6 +1609,9 @@ function buildCommonProjectCopierAction(input) {
1578
1609
  ticket_provider: input.ticketProvider,
1579
1610
  plane_workspace: input.planeWorkspace,
1580
1611
  plane_project_id: input.planeProjectId ?? "",
1612
+ ticket_workspace: input.ticketWorkspace ?? input.planeWorkspace,
1613
+ board_id: input.boardId ?? input.planeProjectId ?? "",
1614
+ board_url: input.boardUrl ?? "",
1581
1615
  project_identifier: input.projectIdentifier,
1582
1616
  primary_language: input.primaryLanguage,
1583
1617
  agent_hooks_layer: input.agentHooksLayer ?? true ? "true" : "false"
@@ -3416,7 +3450,7 @@ function projectInitActionLabel(kind) {
3416
3450
  return "Render CommonProject scaffold";
3417
3451
  case "project.write-manifest":
3418
3452
  return "Write repo-local .project.json projection";
3419
- case "plane.create-or-link":
3453
+ case "ticket-provider.create-or-link":
3420
3454
  return "Create/link ticket provider project";
3421
3455
  case "hermes.provision-agent":
3422
3456
  return "Provision Hermes agent";
@@ -3442,7 +3476,7 @@ function actionNeedsRun(plan, kind, syncMode) {
3442
3476
  return !existsSync9(action.path) || readFileSync6(action.path, "utf8") !== next;
3443
3477
  }
3444
3478
  if (kind === "copier.copy.commonproject") return true;
3445
- if (kind === "plane.create-or-link") return plan.actions.some((action) => action.kind === kind && action.enabled);
3479
+ if (kind === "ticket-provider.create-or-link") return plan.actions.some((action) => action.kind === kind && action.enabled);
3446
3480
  if (kind === "hermes.provision-agent") return plan.actions.some((action) => action.kind === kind && action.enabled);
3447
3481
  return true;
3448
3482
  }
@@ -3540,7 +3574,7 @@ async function runRecipeSubsystem(name, options) {
3540
3574
  }
3541
3575
  var program = new Command3();
3542
3576
  program.name("pjangler").description("Project subsystem bootstrapper CLI").version(PJANGLER_VERSION);
3543
- program.command("init").argument("[name]", "Project name to bootstrap (omit inside an existing git repo)").description("Bootstrap a project: registry entry + CommonProject scaffold + .project.json").option("--description <text>", "Project description").option("--target-dir <path>", "Target repo path").option("--source-skill <path>", "Source skill/template provenance path").option("--primary-language <language>", "Primary language for CommonProject rendering", "python").option("--provision-agent", "Plan local Hermes PM agent provisioning").option("--agent-role <role>", "Hermes agent role to plan when --provision-agent is set", "pm").option("--apply", "Write the registry and render the repo scaffold").option("--dry-run", "Preview changes without writing files (default)").option("--live", "Allow live/network/cloud provisioning actions").option("--slug <slug>", "Project registry slug override").option("--identifier <identifier>", "Ticket identifier override").option("--registry <path>", `Registry path override (default: ${projectRegistryPath()})`).option("-f, --force", "Allow replacing an existing registry entry and re-rendering files").option("-y, --yes", "Apply every proposed operation without prompting").option("--no-tui", "Disable interactive prompts").option("--json", "Output machine-parseable JSON").action(async (name, options) => {
3577
+ program.command("init").argument("[name]", "Project name to bootstrap (omit inside an existing git repo)").description("Bootstrap a project: registry entry + CommonProject scaffold + .project.json").option("--description <text>", "Project description").option("--target-dir <path>", "Target repo path").option("--source-skill <path>", "Source skill/template provenance path").option("--primary-language <language>", "Primary language for CommonProject rendering", "python").option("--provision-agent", "Plan local Hermes PM agent provisioning").option("--agent-role <role>", "Hermes agent role to plan when --provision-agent is set", "pm").option("--apply", "Write the registry and render the repo scaffold").option("--dry-run", "Preview changes without writing files (default)").option("--live", "Allow live/network/cloud provisioning actions").option("--slug <slug>", "Project registry slug override").option("--identifier <identifier>", "Ticket identifier override").option("--ticket-provider <type>", "Ticket provider: plane | trello", "plane").option("--board-id <id>", "Board id (Plane project UUID or Trello board id)").option("--board-url <url>", "Board URL override (derived from provider + board-id if omitted)").option("--workspace <name>", "Ticket workspace/org (Plane workspace; blank for Trello)").option("--registry <path>", `Registry path override (default: ${projectRegistryPath()})`).option("-f, --force", "Allow replacing an existing registry entry and re-rendering files").option("-y, --yes", "Apply every proposed operation without prompting").option("--no-tui", "Disable interactive prompts").option("--json", "Output machine-parseable JSON").action(async (name, options) => {
3544
3578
  if (name && getRecipeNames().includes(name)) {
3545
3579
  if (!options.json) {
3546
3580
  console.error(`${yellow(glyph.warn)} ${dim(`"pjangler init ${name}" is deprecated \u2014 use "pjangler add ${name}". Forwarding\u2026`)}`);
@@ -3569,7 +3603,7 @@ program.command("list").description("List available subsystems").action(() => {
3569
3603
  console.log("");
3570
3604
  });
3571
3605
  var projectCmd = program.command("project").description("Manage the pjangler project registry");
3572
- projectCmd.command("init").argument("[name]", "Project display name").description("Plan or apply a registry-backed CommonProject initialization or legacy repo sync").option("--description <text>", "Project description").option("--target-dir <path>", "Target repo path").option("--source-skill <path>", "Source skill/template provenance path").option("--primary-language <language>", "Primary language for CommonProject rendering", "python").option("--provision-agent", "Plan local Hermes PM agent provisioning").option("--agent-role <role>", "Hermes agent role to plan when --provision-agent is set", "pm").option("--apply", "Write the registry and render the repo scaffold").option("--dry-run", "Preview changes without writing files (default)").option("--live", "Allow live/network/cloud provisioning actions").option("--slug <slug>", "Project registry slug override").option("--identifier <identifier>", "Ticket identifier override").option("--registry <path>", `Registry path override (default: ${projectRegistryPath()})`).option("-f, --force", "Allow replacing an existing registry entry and re-rendering files").option("-y, --yes", "Apply every proposed operation without prompting").option("--no-tui", "Disable interactive prompts").option("--json", "Output machine-parseable JSON").action((name, options) => {
3606
+ projectCmd.command("init").argument("[name]", "Project display name").description("Plan or apply a registry-backed CommonProject initialization or legacy repo sync").option("--description <text>", "Project description").option("--target-dir <path>", "Target repo path").option("--source-skill <path>", "Source skill/template provenance path").option("--primary-language <language>", "Primary language for CommonProject rendering", "python").option("--provision-agent", "Plan local Hermes PM agent provisioning").option("--agent-role <role>", "Hermes agent role to plan when --provision-agent is set", "pm").option("--apply", "Write the registry and render the repo scaffold").option("--dry-run", "Preview changes without writing files (default)").option("--live", "Allow live/network/cloud provisioning actions").option("--slug <slug>", "Project registry slug override").option("--identifier <identifier>", "Ticket identifier override").option("--ticket-provider <type>", "Ticket provider: plane | trello", "plane").option("--board-id <id>", "Board id (Plane project UUID or Trello board id)").option("--board-url <url>", "Board URL override (derived from provider + board-id if omitted)").option("--workspace <name>", "Ticket workspace/org (Plane workspace; blank for Trello)").option("--registry <path>", `Registry path override (default: ${projectRegistryPath()})`).option("-f, --force", "Allow replacing an existing registry entry and re-rendering files").option("-y, --yes", "Apply every proposed operation without prompting").option("--no-tui", "Disable interactive prompts").option("--json", "Output machine-parseable JSON").action((name, options) => {
3573
3607
  if (!options.json) console.error(`${yellow(glyph.warn)} ${dim('"pjangler project init" is deprecated \u2014 use "pjangler init".')}`);
3574
3608
  return runProjectInit(name, options);
3575
3609
  });
@@ -3590,6 +3624,10 @@ async function runProjectInit(name, options) {
3590
3624
  live: options.live ?? false,
3591
3625
  projectSlug: target.slug,
3592
3626
  projectIdentifier: target.identifier,
3627
+ ticketProvider: options.ticketProvider,
3628
+ boardId: options.boardId,
3629
+ boardUrl: options.boardUrl,
3630
+ boardWorkspace: options.workspace,
3593
3631
  registryPath: options.registry,
3594
3632
  force: options.force ?? false,
3595
3633
  overwrite: options.force ?? false,
@@ -3886,7 +3924,7 @@ program.command("migrate").argument("[rule-id]", "Rule ID to migrate (omit to op
3886
3924
  process.exit(1);
3887
3925
  }
3888
3926
  });
3889
- program.command("hermes-agent").alias("hermes").description("Provision the PM agent for the current repo (defaults everything; only asks about Telegram)").option("-y, --yes", "Non-interactive: accept all defaults (also skips the Telegram prompt)").option("--target-repo <name>", "Target repo name (default: basename of cwd)").option("--role <role>", "Agent role override (default: pm \u2014 the only role in the fleet)").option("--purpose <text>", 'One-line agent purpose (default: "pm agent for <repo>")').option(`--tone <tone>`, `Personality tone (default: direct; ${SOUL_TONES.join(" | ")})`).option("--model-provider <name>", 'Inference provider override ("" = inherit shared default profile)').option("--model-name <name>", 'Model name override ("" = inherit shared default profile)').option("--skip-telegram", "Skip the Telegram wire-up (no BotFather prompt)").option("--email", "Also provision the delo.sh email address (off by default; never prompted)").option("--skip-runtime-repo", "Skip creating the per-agent runtime GH repo").option("--skip-plane", "Skip creating the Plane project").option("--skip-bloodbank", "Skip installing the Bloodbank NATS consumer").option("--skip-systemd", "Skip installing systemd --user units").option("--local", "Local-only: skip runtime repo, Plane, Bloodbank, and systemd (safe for laptops/macOS/non-technical operators)").option("--force-config", "Regenerate ~/.config/hermes-agent-template/config.toml even if it exists").option("--dry-run", "Preview what would run; don't execute copier").option("-f, --force", "Re-render even if agents/hermes/<role>/role.yaml already exists").action(async (options) => {
3927
+ program.command("hermes-agent").alias("hermes").description("Provision the PM agent for the current repo (defaults everything; only asks about Telegram)").option("-y, --yes", "Non-interactive: accept all defaults (also skips the Telegram prompt)").option("--target-repo <name>", "Target repo name (default: basename of cwd)").option("--role <role>", "Agent role override (default: pm \u2014 the only role in the fleet)").option("--purpose <text>", 'One-line agent purpose (default: "pm agent for <repo>")').option(`--tone <tone>`, `Personality tone (default: direct; ${SOUL_TONES.join(" | ")})`).option("--model-provider <name>", 'Inference provider override ("" = inherit shared default profile)').option("--model-name <name>", 'Model name override ("" = inherit shared default profile)').option("--skip-telegram", "Skip the Telegram wire-up (no BotFather prompt)").option("--email", "Also provision the delo.sh email address (off by default; never prompted)").option("--skip-runtime-repo", "Skip creating the per-agent runtime GH repo").option("--skip-plane", "Skip creating or linking the ticket board").option("--skip-bloodbank", "Skip installing the Bloodbank NATS consumer").option("--skip-systemd", "Skip installing systemd --user units").option("--local", "Local-only: skip runtime repo, ticket-board creation, Bloodbank, and systemd (safe for laptops/macOS/non-technical operators)").option("--force-config", "Regenerate ~/.config/hermes-agent-template/config.toml even if it exists").option("--dry-run", "Preview what would run; don't execute copier").option("-f, --force", "Re-render even if agents/hermes/<role>/role.yaml already exists").action(async (options) => {
3890
3928
  const isDarwin = process.platform === "darwin";
3891
3929
  const local = options.local ?? false;
3892
3930
  const context = {
@@ -713,7 +713,7 @@ function deriveProfileName(repo, role) {
713
713
  function detectTicketProvider(targetDir) {
714
714
  try {
715
715
  const t = JSON.parse(readFileSync(join4(targetDir, ".project.json"), "utf8"))?.ticket_provider?.type;
716
- return t === "plane" || t === "linear" || t === "trello" ? t : void 0;
716
+ return t === "plane" || t === "trello" ? t : void 0;
717
717
  } catch {
718
718
  return void 0;
719
719
  }
@@ -1260,6 +1260,34 @@ function validateProjectRegistry(registry) {
1260
1260
  }
1261
1261
  }
1262
1262
  }
1263
+ function normalizeTicketProvider(value) {
1264
+ const type = (value || "plane").trim().toLowerCase();
1265
+ if (type === "plane" || type === "trello") return type;
1266
+ throw new Error(`Unsupported ticket provider: ${value}. Supported providers: plane, trello`);
1267
+ }
1268
+ function buildTicketProviderBlock(input) {
1269
+ const type = normalizeTicketProvider(input.type);
1270
+ const boardId = input.boardId ?? "";
1271
+ if (type === "trello") {
1272
+ return {
1273
+ type,
1274
+ workspace: input.workspace ?? "",
1275
+ identifier: input.identifier,
1276
+ board_id: boardId,
1277
+ board_url: input.boardUrl ?? (boardId ? `https://trello.com/b/${boardId}` : ""),
1278
+ state: "planned"
1279
+ };
1280
+ }
1281
+ const workspace = input.workspace ?? "33god";
1282
+ return {
1283
+ type,
1284
+ workspace,
1285
+ identifier: input.identifier,
1286
+ board_id: boardId,
1287
+ board_url: input.boardUrl ?? (boardId ? `https://plane.delo.sh/${workspace}/projects/${boardId}/issues/` : ""),
1288
+ state: "planned"
1289
+ };
1290
+ }
1263
1291
  function slugifyProjectName(value) {
1264
1292
  return value.trim().toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "project";
1265
1293
  }
@@ -1338,14 +1366,13 @@ function planProjectInit(input) {
1338
1366
  primary_language: input.primaryLanguage ?? "python"
1339
1367
  }
1340
1368
  },
1341
- ticket_provider: {
1369
+ ticket_provider: buildTicketProviderBlock({
1342
1370
  type: input.ticketProvider ?? "plane",
1343
- workspace: input.planeWorkspace ?? "33god",
1344
1371
  identifier,
1345
- board_id: input.planeProjectId ?? "",
1346
- board_url: input.planeProjectId ? `https://plane.delo.sh/${input.planeWorkspace ?? "33god"}/projects/${input.planeProjectId}/issues/` : "",
1347
- state: input.live ? "planned" : "planned"
1348
- },
1372
+ boardId: input.boardId ?? input.planeProjectId,
1373
+ boardUrl: input.boardUrl,
1374
+ workspace: input.boardWorkspace ?? input.planeWorkspace
1375
+ }),
1349
1376
  agents,
1350
1377
  created_at: existing?.created_at ?? now,
1351
1378
  updated_at: now
@@ -1372,6 +1399,9 @@ function planProjectInit(input) {
1372
1399
  ticketProvider: project.ticket_provider.type,
1373
1400
  planeWorkspace: project.ticket_provider.workspace ?? "33god",
1374
1401
  planeProjectId: project.ticket_provider.board_id ?? "",
1402
+ ticketWorkspace: project.ticket_provider.workspace ?? "",
1403
+ boardId: project.ticket_provider.board_id ?? "",
1404
+ boardUrl: project.ticket_provider.board_url ?? "",
1375
1405
  projectIdentifier: identifier,
1376
1406
  primaryLanguage: project.template.commonproject.primary_language,
1377
1407
  agentHooksLayer: resolveAgentHooksLayer(input.agentHooksLayer),
@@ -1381,9 +1411,10 @@ function planProjectInit(input) {
1381
1411
  actions.push(
1382
1412
  { kind: "project.write-manifest", path: join8(targetDir, ".project.json"), manifest },
1383
1413
  {
1384
- kind: "plane.create-or-link",
1414
+ kind: "ticket-provider.create-or-link",
1385
1415
  enabled: live,
1386
1416
  live,
1417
+ provider: project.ticket_provider.type,
1387
1418
  workspace: project.ticket_provider.workspace ?? "33god",
1388
1419
  identifier,
1389
1420
  state: live ? "planned" : "planned",
@@ -1446,8 +1477,8 @@ function executeProjectInitPlan(plan) {
1446
1477
  }
1447
1478
  } else if (action.kind === "registry.upsert") {
1448
1479
  pendingRegistryAction = action;
1449
- } else if (action.kind === "plane.create-or-link") {
1450
- logs.push(action.enabled ? "plane.create-or-link requires a live provider integration" : "plane.create-or-link skipped (requires --live)");
1480
+ } else if (action.kind === "ticket-provider.create-or-link") {
1481
+ logs.push(action.enabled ? "ticket-provider.create-or-link requires a live provider integration" : "ticket-provider.create-or-link skipped (requires --live)");
1451
1482
  } else if (action.kind === "hermes.provision-agent") {
1452
1483
  logs.push(action.enabled ? "hermes.provision-agent planned for the caller to execute" : "hermes.provision-agent skipped");
1453
1484
  }
@@ -1502,6 +1533,9 @@ function buildCommonProjectCopierAction(input) {
1502
1533
  ticket_provider: input.ticketProvider,
1503
1534
  plane_workspace: input.planeWorkspace,
1504
1535
  plane_project_id: input.planeProjectId ?? "",
1536
+ ticket_workspace: input.ticketWorkspace ?? input.planeWorkspace,
1537
+ board_id: input.boardId ?? input.planeProjectId ?? "",
1538
+ board_url: input.boardUrl ?? "",
1505
1539
  project_identifier: input.projectIdentifier,
1506
1540
  primary_language: input.primaryLanguage,
1507
1541
  agent_hooks_layer: input.agentHooksLayer ?? true ? "true" : "false"
@@ -3221,7 +3255,7 @@ var server = new McpServer({
3221
3255
  name: "pjangler-mcp",
3222
3256
  version: PJANGLER_VERSION
3223
3257
  });
3224
- var TICKET_PROVIDER_SCHEMA = z.enum(["plane", "linear", "trello"]);
3258
+ var TICKET_PROVIDER_SCHEMA = z.enum(["plane", "trello"]);
3225
3259
  function resolveTargetDir(targetDir) {
3226
3260
  const dir = resolve3(targetDir ?? process.cwd());
3227
3261
  if (!existsSync9(dir)) {
@@ -3412,6 +3446,9 @@ server.registerTool(
3412
3446
  projectDescription: z.string().optional(),
3413
3447
  projectSlug: z.string().optional(),
3414
3448
  ticketProvider: TICKET_PROVIDER_SCHEMA.optional(),
3449
+ boardId: z.string().optional(),
3450
+ boardUrl: z.string().optional(),
3451
+ workspace: z.string().optional(),
3415
3452
  planeWorkspace: z.string().optional(),
3416
3453
  planeProjectId: z.string().optional(),
3417
3454
  projectIdentifier: z.string().optional(),
@@ -3440,9 +3477,10 @@ server.registerTool(
3440
3477
  const dryRun = input.dryRun ?? true;
3441
3478
  const local = input.local ?? true;
3442
3479
  const skipPlane = input.skipPlane ?? true;
3443
- const planeProjectId = input.planeProjectId ?? "";
3444
- if (!skipPlane && !planeProjectId) {
3445
- throw new Error("planeProjectId is required when skipPlane=false; keep skipPlane=true for safe local bootstrap");
3480
+ const ticketProvider = input.ticketProvider ?? "plane";
3481
+ const boardId = input.boardId ?? input.planeProjectId ?? "";
3482
+ if (!skipPlane && ticketProvider === "plane" && !boardId) {
3483
+ throw new Error("boardId or planeProjectId is required when skipPlane=false for Plane; keep skipPlane=true for safe local bootstrap");
3446
3484
  }
3447
3485
  if (!dryRun && existsSync9(targetDir) && !overwrite) throw new Error(`Target already exists: ${targetDir} (set force/overwrite=true to re-render)`);
3448
3486
  const plan = planProjectInit({
@@ -3458,9 +3496,12 @@ server.registerTool(
3458
3496
  live: input.live ?? false,
3459
3497
  registryPath: input.registryPath,
3460
3498
  projectIdentifier: input.projectIdentifier ?? projectSlug.slice(0, 4).toUpperCase(),
3461
- ticketProvider: input.ticketProvider ?? "plane",
3499
+ ticketProvider,
3500
+ boardId,
3501
+ boardUrl: input.boardUrl,
3502
+ boardWorkspace: input.workspace ?? input.planeWorkspace,
3462
3503
  planeWorkspace: input.planeWorkspace ?? "33god",
3463
- planeProjectId,
3504
+ planeProjectId: input.planeProjectId,
3464
3505
  pjanglerRoot,
3465
3506
  overwrite
3466
3507
  });
@@ -3512,6 +3553,10 @@ server.registerTool(
3512
3553
  live: z.boolean().optional(),
3513
3554
  slug: z.string().optional(),
3514
3555
  identifier: z.string().optional(),
3556
+ ticketProvider: TICKET_PROVIDER_SCHEMA.optional(),
3557
+ boardId: z.string().optional(),
3558
+ boardUrl: z.string().optional(),
3559
+ workspace: z.string().optional(),
3515
3560
  registryPath: z.string().optional(),
3516
3561
  force: z.boolean().optional()
3517
3562
  }
@@ -3530,6 +3575,10 @@ server.registerTool(
3530
3575
  live: input.live ?? false,
3531
3576
  projectSlug: input.slug,
3532
3577
  projectIdentifier: input.identifier,
3578
+ ticketProvider: input.ticketProvider,
3579
+ boardId: input.boardId,
3580
+ boardUrl: input.boardUrl,
3581
+ boardWorkspace: input.workspace,
3533
3582
  registryPath: input.registryPath,
3534
3583
  force: input.force ?? false,
3535
3584
  overwrite: input.force ?? false
@@ -3637,7 +3686,7 @@ server.registerTool(
3637
3686
  "pjangler_deploy_hermes_agent",
3638
3687
  {
3639
3688
  title: "Deploy Hermes agent",
3640
- description: "Provision a Hermes agent role for @33god-projects. For safe MCP use local=true defaults skip runtime repo, Plane, Bloodbank, and systemd; opt out with local=false plus explicit skip flags.",
3689
+ description: "Provision a Hermes agent role for @33god-projects. For safe MCP use local=true defaults skip runtime repo, ticket-board creation, Bloodbank, and systemd; opt out with local=false plus explicit skip flags.",
3641
3690
  inputSchema: {
3642
3691
  targetDir: z.string(),
3643
3692
  targetRepo: z.string().optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@delorenj/pjangler",
3
- "version": "1.2.6",
3
+ "version": "1.2.9",
4
4
  "description": "Project subsystem bootstrapper CLI",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,7 +20,7 @@ CommonProject/
20
20
  │ ├── .agentvibes/ # AgentVibes config
21
21
  │ ├── .mise/tasks/ # File-based mise tasks
22
22
  │ ├── .scripts/ # Post-generation utilities
23
- │ │ └── setup-plane.py # Creates Plane project + ticket_provider block in .project.json
23
+ │ │ └── setup-plane.py # Creates/links ticket provider board + ticket_provider block in .project.json
24
24
  │ ├── AGENTS.md.jinja # Generated project's agent SSOT
25
25
  │ ├── CLAUDE.md # Symlink → AGENTS.md
26
26
  │ ├── GEMINI.md # Symlink → AGENTS.md
@@ -46,7 +46,7 @@ Root-level files describe the template itself. Files in `template/` are what Cop
46
46
  Only two questions asked: `project_name` and `project_description`. Everything else is derived or automated:
47
47
  - `project_slug` derived from project_name
48
48
  - `user_name` / `user_skill_level` hardcoded for BMAD config
49
- - Plane project created via API in post-generation task
49
+ - Ticket board created or linked via post-generation task
50
50
  - .gitignore copied from ~/.config/git/ignore
51
51
  - git init + initial commit run automatically
52
52
 
@@ -55,7 +55,7 @@ Only two questions asked: `project_name` and `project_description`. Everything e
55
55
  After rendering, Copier automatically:
56
56
  1. Copies .gitignore from ~/.config/git/ignore
57
57
  2. Makes scripts executable
58
- 3. Runs setup-plane.py (creates Plane project, writes the ticket_provider block in .project.json)
58
+ 3. Runs setup-plane.py (creates/links the ticket board, writes the ticket_provider block in .project.json)
59
59
  4. Runs git init + git add -A + git commit
60
60
 
61
61
  ### BMAD System
@@ -1,13 +1,13 @@
1
1
  # CommonProject
2
2
 
3
- Copier template for creating new 33GOD ecosystem components with BMAD methodology, Plane ticket enforcement, and multi-agent development tooling.
3
+ Copier template for creating new 33GOD ecosystem components with BMAD methodology, ticket-provider enforcement, and multi-agent development tooling.
4
4
 
5
5
  ## Features
6
6
 
7
7
  - **Language Support**: Python, TypeScript, Rust
8
8
  - **Event-Driven Architecture**: Optional [Bloodbank](https://github.com/delorenj/Bloodbank) integration
9
- - **Plane Integration**: `.project.json` for ticket management and project config
10
- - **Ticket Enforcement**: Git hooks enforce Plane ticket requirements
9
+ - **Ticket Provider Integration**: `.project.json` records Plane or Trello board bindings
10
+ - **Ticket Enforcement**: Git hooks enforce configured ticket-provider requirements
11
11
  - [TODO] **BMAD Integration**: Full BMAD methodology structure by auto running `npx bmad-method@latest install`
12
12
  - [TODO] Multiple-choice selection for different workflows: BMAD, GSD, GoogleAgentSkills, None.
13
13
  - **Containerization**: Optional Docker + Docker Compose
@@ -25,7 +25,7 @@ uv tool install copier
25
25
  # Or with pip
26
26
  pip install copier
27
27
 
28
- # Set up Plane API key (for automated project creation)
28
+ # Set up a provider key if you want automated board creation/linking
29
29
  export PLANE_API_KEY="your-plane-api-key"
30
30
  OR
31
31
  [TODO] export PLANE_API_KEY=op://VaultName/Key/Field
@@ -36,14 +36,14 @@ OR
36
36
 
37
37
  ### Option 1: Automated Init (Recommended)
38
38
 
39
- [TODO] One command handles everything: Plane project creation + Copier template:
39
+ [TODO] One command handles everything: ticket board creation/linking + Copier template:
40
40
 
41
41
  > TASK - Implement the following:
42
42
  > Currently, I have to run `copier copy --trust ~/code CommonProject .` globally, or `mise run init-project` while in the CommonProject source dir - but this is not ideal.
43
43
  > I would rather run `commonProject .` globally.
44
44
 
45
45
  ```bash
46
- # Interactive wizard - creates Plane project automatically
46
+ # Interactive wizard - creates/links the configured ticket board automatically
47
47
  mise run init-project
48
48
 
49
49
  # Or non-interactive (uses defaults)
@@ -56,7 +56,7 @@ mise run init-project-non-interactive
56
56
  The wizard will:
57
57
 
58
58
  1. Ask for project details (name, description, type)
59
- 2. Create the Plane project automatically
59
+ 2. Create or link the configured ticket board
60
60
  3. Run Copier with all answers pre-filled
61
61
  4. Output next steps
62
62
 
@@ -83,9 +83,10 @@ The template asks for:
83
83
  - **Project Slug**: Directory/package name (auto-generated from name)
84
84
  - **Description**: One-sentence project description
85
85
 
86
- ### Plane Integration
86
+ ### Ticket Provider Integration
87
87
 
88
- - **Workspace**: Plane workspace slug (default: 33god)
88
+ - **Provider**: Plane or Trello
89
+ - **Workspace**: Provider workspace slug when applicable (Plane default: 33god)
89
90
  - **Project Name**
90
91
  - **Project Identifier**: 2+ character ticket prefix (e.g., HOLO, VERN)
91
92
 
@@ -110,10 +111,10 @@ The template asks for:
110
111
  git commit -m "Initial commit from template"
111
112
  ```
112
113
 
113
- 3. **Create Plane Ticket**
114
+ 3. **Create or Claim Ticket**
114
115
 
115
116
  ```bash
116
- # Create ticket in Plane (or it was already created for you!)
117
+ # Create or claim a ticket in the configured provider
117
118
  # Move to "In Progress"
118
119
  git checkout -b <IDENTIFIER>-123-initial-setup
119
120
  ```
@@ -3,7 +3,7 @@
3
3
  #
4
4
  # Generic base project template. Creates a project skeleton with:
5
5
  # - BMAD methodology (pre-initialized)
6
- # - Plane project (auto-created in 33god workspace)
6
+ # - Ticket-provider metadata (Plane or Trello)
7
7
  # - mise environment management
8
8
  # - CLI coder configs (Claude, Codex, Gemini, etc.)
9
9
 
@@ -30,16 +30,15 @@ project_description:
30
30
  help: One-line description
31
31
  default: ""
32
32
 
33
- # Ticket board provider for this project. The board itself is created by
34
- # init-project.sh (create-plane-project.sh) BEFORE copier runs; its identity is
35
- # threaded back in via plane_* below and persisted into .project.json so it is
36
- # the single source of truth shared with the hermes-agent template's agents.
33
+ # Ticket board provider for this project. Plane boards can be created before
34
+ # Copier runs; Trello boards are linked by id/url. The board identity is
35
+ # persisted into .project.json so it is the single source of truth shared with
36
+ # the hermes-agent template's agents.
37
37
  ticket_provider:
38
38
  type: str
39
39
  help: Ticket board provider this project's board lives in
40
40
  default: plane
41
41
  choices:
42
- Linear: linear
43
42
  Plane: plane
44
43
  Trello: trello
45
44
 
@@ -50,8 +49,7 @@ project_slug:
50
49
  default: "{{ project_name | lower | replace(' ', '-') | replace('_', '-') }}"
51
50
  when: false
52
51
 
53
- # Board binding supplied by init-project.sh's --data-file after the board is
54
- # created. Defaults keep a bare `copier copy` rendering valid (empty binding).
52
+ # Back-compat aliases kept for existing pjangler/CommonProject callers.
55
53
  plane_workspace:
56
54
  type: str
57
55
  default: "33god"
@@ -72,6 +70,24 @@ plane_base:
72
70
  default: "https://plane.delo.sh"
73
71
  when: false
74
72
 
73
+ # Provider-agnostic board binding — supplied by pjangler's --data values after
74
+ # the board is created or linked. Defaults keep a bare `copier copy` rendering
75
+ # valid, and fall back to the legacy plane_* aliases above.
76
+ board_id:
77
+ type: str
78
+ default: "{{ plane_project_id }}"
79
+ when: false
80
+
81
+ board_url:
82
+ type: str
83
+ default: ""
84
+ when: false
85
+
86
+ ticket_workspace:
87
+ type: str
88
+ default: "{{ plane_workspace if ticket_provider == 'plane' else '' }}"
89
+ when: false
90
+
75
91
  # Drives conditional blocks in rendered files (e.g. _.python.venv in mise.toml).
76
92
  # Not asked interactively; init-project.sh can override via --data-file.
77
93
  primary_language:
@@ -17,11 +17,11 @@ patterns = ["AGENTS.md"]
17
17
  task = "link-agentfiles"
18
18
 
19
19
  [tasks]
20
- # Project initialization - creates Plane project + runs Copier
21
- init-project = { run = ".mise/scripts/init-project.sh", description = "Initialize new 33GOD project with Plane + Copier" }
20
+ # Project initialization - creates/links ticket board + runs Copier
21
+ init-project = { run = ".mise/scripts/init-project.sh", description = "Initialize new 33GOD project with ticket provider + Copier" }
22
22
  init-project-non-interactive = { run = ".mise/scripts/init-project.sh --non-interactive", description = "Non-interactive project init" }
23
23
 
24
- # Direct Plane project creation
24
+ # Direct Plane project creation helper
25
25
  create-plane-project = { run = ".mise/scripts/create-plane-project.sh", description = "Create a Plane project and output ID" }
26
26
 
27
27
  # Template testing
@@ -1,7 +1,14 @@
1
- {% if ticket_provider == 'plane' and plane_project_id -%}
2
- {% set board_url = plane_base ~ '/' ~ plane_workspace ~ '/projects/' ~ plane_project_id ~ '/issues/' -%}
1
+ {% set ticket_board_id = board_id | default(plane_project_id) -%}
2
+ {% set ticket_board_url = board_url | default('') -%}
3
+ {% set workspace = ticket_workspace | default(plane_workspace if ticket_provider == 'plane' else '') -%}
4
+ {% if ticket_board_url -%}
5
+ {% set resolved_board_url = ticket_board_url -%}
6
+ {% elif ticket_provider == 'plane' and ticket_board_id -%}
7
+ {% set resolved_board_url = plane_base ~ '/' ~ workspace ~ '/projects/' ~ ticket_board_id ~ '/issues/' -%}
8
+ {% elif ticket_provider == 'trello' and ticket_board_id -%}
9
+ {% set resolved_board_url = 'https://trello.com/b/' ~ ticket_board_id -%}
3
10
  {% else -%}
4
- {% set board_url = '' -%}
11
+ {% set resolved_board_url = '' -%}
5
12
  {% endif -%}
6
13
  {
7
14
  "project_name": {{ project_name | tojson }},
@@ -10,10 +17,10 @@
10
17
  "repo_path": "",
11
18
  "ticket_provider": {
12
19
  "type": {{ ticket_provider | tojson }},
13
- "workspace": {{ plane_workspace | tojson }},
20
+ "workspace": {{ workspace | tojson }},
14
21
  "identifier": {{ project_identifier | tojson }},
15
- "board_id": {{ plane_project_id | tojson }},
16
- "board_url": {{ board_url | tojson }}
22
+ "board_id": {{ ticket_board_id | tojson }},
23
+ "board_url": {{ resolved_board_url | tojson }}
17
24
  },
18
25
  "agents": {}
19
26
  }
@@ -11,7 +11,7 @@ memory/state checkpointing.
11
11
  | What it scaffolds | A new top-level project | An agent role inside an existing project |
12
12
  | Copier target | `./my-new-project/` | `./agents/hermes/<role>/` |
13
13
  | Asks | project name + description | role + purpose + tone |
14
- | Post-gen artifacts | Plane project, bmad init, mise tasks | Plane project, Telegram bot wiring, agent-hm-* runtime repo, systemd units |
14
+ | Post-gen artifacts | Ticket board binding, bmad init, mise tasks | Ticket board binding, Telegram bot wiring, agent-hm-* runtime repo, systemd units |
15
15
 
16
16
  CommonProject runs first to create the umbrella project. hermes-agent-template
17
17
  runs second (for each agent role you want) to drop agents into it.
@@ -20,8 +20,8 @@ runs second (for each agent role you want) to drop agents into it.
20
20
 
21
21
  The template provisions a single Hermes role per invocation. The `pm` role
22
22
  handles project management and triage, and also runs the continuous ticket
23
- sentinel out-of-band: a provider-agnostic board-reconciliation pass (Linear,
24
- Plane, or Trello) with an autonomous adversarial review (act, do not wait). The
23
+ sentinel out-of-band: a provider-agnostic board-reconciliation pass (Plane or
24
+ Trello) with an autonomous adversarial review (act, do not wait). The
25
25
  sentinel runs as the PM's **heartbeat** — a fused systemd timer tick that does
26
26
  the reconciliation pass and then a gated runtime checkpoint. (There is no
27
27
  separate `scrum-master` role; its duties folded into the PM heartbeat.)
@@ -47,7 +47,7 @@ The template will:
47
47
  5. Populate it with the runtime scaffold (config.yaml, SOUL.md, memories, consumer.py)
48
48
  6. Add it as a git submodule at `agents/hermes/pm/runtime/` (== HERMES_HOME)
49
49
  7. Prompt for a BotFather token, store it in `runtime/.env`
50
- 8. Create a Plane project in your configured workspace
50
+ 8. Create or bind the configured ticket board
51
51
  9. Install systemd `--user` units: gateway, consumer, heartbeat timer (reconcile + checkpoint)
52
52
  10. Append the agent to `~/.hermes/agents-registry.yaml`
53
53
 
@@ -138,14 +138,14 @@ to force re-run:
138
138
 
139
139
  ```bash
140
140
  cd agents/hermes/pm
141
- rm .scripts/.done-40-plane
142
- SKIP_TELEGRAM=1 SKIP_SYSTEMD=1 ./.scripts/40-plane.sh
141
+ rm .scripts/.done-42-ticket-provider
142
+ SKIP_SYSTEMD=1 ./.scripts/42-ticket-provider.sh
143
143
  ```
144
144
 
145
145
  ## Retiring an agent
146
146
 
147
147
  (TODO: ship `retire.sh` in v1.1)
148
148
 
149
- Manual: stop systemd units, `hermes profile delete`, archive the Plane
150
- project, `/deletebot` in BotFather, archive the
149
+ Manual: stop systemd units, `hermes profile delete`, archive the ticket board
150
+ if the provider supports archival, `/deletebot` in BotFather, archive the
151
151
  `agent-hm-*` runtime repo, remove the registry entry.