@elmundi/ship-cli 0.15.3 → 0.15.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.
@@ -158,7 +158,10 @@ export async function runCommand(ctx, rest) {
158
158
  if (!roleResolved) {
159
159
  die(EXIT_USAGE, `unknown agent role '${specialistSlug}' for this workspace`);
160
160
  }
161
- const fsmStage = roleResolved.fsm_stage || null;
161
+ // Per-routine FSM stage override takes precedence over the role's
162
+ // default. Lets one role (``ba``) drive both ``ba_requirements`` for
163
+ // SDLC and ``wbs`` for decomposition without per-process role clones.
164
+ const fsmStage = resolved.executable?.fsm_stage || roleResolved.fsm_stage || null;
162
165
  const roleBody = roleResolved.prompt || "";
163
166
  const systemBody = systemResolved?.prompt || "";
164
167
 
@@ -484,6 +487,24 @@ function renderPrompt({ patternBody, baseBody, role, routineSpec, task, fsmStage
484
487
  out.push("");
485
488
  out.push(renderLifecycleHooks());
486
489
  if (task) {
490
+ // ELS-86: parent project context (Brief / WBS / Architecture /
491
+ // Test architecture / Tasks). The server lifts and caps it; we
492
+ // render it BEFORE the per-ticket block so the agent sees the
493
+ // surrounding plan first, then narrows to its own scope. Only
494
+ // present when the ticket is part of a decomposed project — the
495
+ // server returns ``project_context: null`` otherwise and we
496
+ // skip the block silently.
497
+ if (typeof task.project_context === "string" && task.project_context.trim()) {
498
+ out.push("");
499
+ out.push("## Project context");
500
+ out.push("");
501
+ out.push(
502
+ "_Excerpt of the parent project body. Read for surrounding plan;",
503
+ "your scope is the per-task block below, not the whole project._",
504
+ );
505
+ out.push("");
506
+ out.push(task.project_context.trim());
507
+ }
487
508
  out.push("");
488
509
  out.push("## Task");
489
510
  out.push(`- **Ticket:** \`${task.ticket_ref}\` (${task.kind})`);
@@ -637,11 +658,19 @@ as a one-shot credential for this run.
637
658
 
638
659
  function makeBranchName(routine, ticketRef) {
639
660
  const stamp = Date.now().toString(36);
661
+ // Sanitize ``routine`` too — for pipeline-pick runs ``runHandle`` is
662
+ // ``pipeline:<specialist>`` and the bare ``:`` is in git's reserved
663
+ // character set, which Cursor's ``/v0/agents`` validator rejects
664
+ // with HTTP 400 ("Invalid branch name. Branch names cannot start
665
+ // with '-', contain invalid characters (spaces, ~, ^, :, ?, *, [,
666
+ // ], \\, .., @{, //), end with '/', '.lock', or '.', or be named
667
+ // 'HEAD'."). Same regex as the ticketRef path.
668
+ const safeRoutine = String(routine).replace(/[^a-zA-Z0-9_-]/g, "-");
640
669
  if (ticketRef) {
641
670
  const safe = String(ticketRef).replace(/[^a-zA-Z0-9_-]/g, "-");
642
- return `cursor/ship-${routine}-${safe}-${stamp}`;
671
+ return `cursor/ship-${safeRoutine}-${safe}-${stamp}`;
643
672
  }
644
- return `cursor/ship-${routine}-${stamp}`;
673
+ return `cursor/ship-${safeRoutine}-${stamp}`;
645
674
  }
646
675
 
647
676
 
@@ -558,6 +558,10 @@ function validateProcessRoutines(value, errors, warnings) {
558
558
  "schedule",
559
559
  "window",
560
560
  "event",
561
+ // ``fsm_stage`` overrides the role's default stage so one role
562
+ // can drive multiple processes (BA serves both
563
+ // ``ba_requirements`` for SDLC and ``wbs`` for decomposition).
564
+ "fsm_stage",
561
565
  ]),
562
566
  prefix,
563
567
  warnings,
@@ -616,6 +620,7 @@ function validateProcessRoutines(value, errors, warnings) {
616
620
  );
617
621
  }
618
622
  validateProcessAgentProfile(routine.agent_profile, `${prefix}.agent_profile`, errors);
623
+ requireOptionalString(routine.fsm_stage, `${prefix}.fsm_stage`, errors, { required: false });
619
624
  validateRoutineTrigger(routine.trigger, `${prefix}.trigger`, errors);
620
625
  if (routine.schedule !== undefined && routine.schedule !== null) {
621
626
  if (typeof routine.schedule !== "string" && !isPlainObject(routine.schedule)) {
@@ -64,6 +64,11 @@ export function routineToExecutable(id, routine) {
64
64
  idempotency: routine.idempotency || null,
65
65
  prompt: stringOrNull(routine.prompt) || stringOrNull(routine.instructions),
66
66
  agent_profile: stringOrNull(routine.agent_profile) || stringOrNull(routine.specialist?.agent_profile),
67
+ // Per-routine FSM stage override. When set, ``shipctl run`` uses
68
+ // it instead of the role's default ``fsm_stage`` — that's how a
69
+ // single role (e.g. ``ba``) serves both SDLC (``ba_requirements``)
70
+ // and decomposition (``wbs``) without per-process role clones.
71
+ fsm_stage: stringOrNull(routine.fsm_stage),
67
72
  };
68
73
  }
69
74
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@elmundi/ship-cli",
3
- "version": "0.15.3",
3
+ "version": "0.15.4",
4
4
  "type": "module",
5
5
  "description": "Ship CLI: bootstrap a repo, sync the catalog, run process routines, report outcomes.",
6
6
  "license": "Apache-2.0",