gsd-pi 2.71.0-dev.e17e0ce → 2.72.0-dev.de4c4b3

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 (159) hide show
  1. package/README.md +34 -1
  2. package/dist/cli.js +17 -0
  3. package/dist/mcp-server.js +37 -14
  4. package/dist/resources/agents/debugger.md +58 -0
  5. package/dist/resources/agents/doc-writer.md +43 -0
  6. package/dist/resources/agents/git-ops.md +56 -0
  7. package/dist/resources/agents/javascript-pro.md +46 -271
  8. package/dist/resources/agents/planner.md +55 -0
  9. package/dist/resources/agents/refactorer.md +47 -0
  10. package/dist/resources/agents/reviewer.md +48 -0
  11. package/dist/resources/agents/security.md +59 -0
  12. package/dist/resources/agents/tester.md +50 -0
  13. package/dist/resources/agents/typescript-pro.md +41 -235
  14. package/dist/resources/extensions/claude-code-cli/stream-adapter.js +103 -6
  15. package/dist/resources/extensions/gsd/auto/phases.js +4 -0
  16. package/dist/resources/extensions/gsd/auto-prompts.js +88 -33
  17. package/dist/resources/extensions/gsd/auto-start.js +24 -4
  18. package/dist/resources/extensions/gsd/auto.js +4 -0
  19. package/dist/resources/extensions/gsd/bootstrap/db-tools.js +3 -3
  20. package/dist/resources/extensions/gsd/bootstrap/register-shortcuts.js +2 -5
  21. package/dist/resources/extensions/gsd/doctor-providers.js +23 -0
  22. package/dist/resources/extensions/gsd/error-classifier.js +4 -1
  23. package/dist/resources/extensions/gsd/gate-registry.js +208 -0
  24. package/dist/resources/extensions/gsd/gsd-db.js +41 -0
  25. package/dist/resources/extensions/gsd/milestone-validation-gates.js +11 -12
  26. package/dist/resources/extensions/gsd/notification-overlay.js +26 -12
  27. package/dist/resources/extensions/gsd/notification-store.js +5 -4
  28. package/dist/resources/extensions/gsd/prompt-validation.js +126 -0
  29. package/dist/resources/extensions/gsd/prompts/complete-slice.md +3 -1
  30. package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -0
  31. package/dist/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  32. package/dist/resources/extensions/gsd/shortcut-defs.js +7 -1
  33. package/dist/resources/extensions/gsd/state.js +9 -2
  34. package/dist/resources/extensions/gsd/tools/complete-slice.js +52 -1
  35. package/dist/resources/extensions/gsd/tools/complete-task.js +51 -1
  36. package/dist/resources/extensions/gsd/tools/workflow-tool-executors.js +4 -1
  37. package/dist/resources/extensions/ollama/index.js +13 -5
  38. package/dist/resources/extensions/shared/gsd-phase-state.js +35 -0
  39. package/dist/resources/extensions/subagent/agents.js +8 -0
  40. package/dist/resources/extensions/subagent/index.js +17 -0
  41. package/dist/startup-model-validation.d.ts +0 -1
  42. package/dist/startup-model-validation.js +6 -2
  43. package/dist/web/standalone/.next/BUILD_ID +1 -1
  44. package/dist/web/standalone/.next/app-path-routes-manifest.json +8 -8
  45. package/dist/web/standalone/.next/build-manifest.json +2 -2
  46. package/dist/web/standalone/.next/prerender-manifest.json +3 -3
  47. package/dist/web/standalone/.next/server/app/_global-error.html +1 -1
  48. package/dist/web/standalone/.next/server/app/_global-error.rsc +1 -1
  49. package/dist/web/standalone/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  50. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  51. package/dist/web/standalone/.next/server/app/_global-error.segments/_global-error.segment.rsc +1 -1
  52. package/dist/web/standalone/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  53. package/dist/web/standalone/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  54. package/dist/web/standalone/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  55. package/dist/web/standalone/.next/server/app/_not-found.html +1 -1
  56. package/dist/web/standalone/.next/server/app/_not-found.rsc +1 -1
  57. package/dist/web/standalone/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  58. package/dist/web/standalone/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  59. package/dist/web/standalone/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  60. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  61. package/dist/web/standalone/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  62. package/dist/web/standalone/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  63. package/dist/web/standalone/.next/server/app/index.html +1 -1
  64. package/dist/web/standalone/.next/server/app/index.rsc +1 -1
  65. package/dist/web/standalone/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  66. package/dist/web/standalone/.next/server/app/index.segments/_full.segment.rsc +1 -1
  67. package/dist/web/standalone/.next/server/app/index.segments/_head.segment.rsc +1 -1
  68. package/dist/web/standalone/.next/server/app/index.segments/_index.segment.rsc +1 -1
  69. package/dist/web/standalone/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  70. package/dist/web/standalone/.next/server/app-paths-manifest.json +8 -8
  71. package/dist/web/standalone/.next/server/middleware-build-manifest.js +1 -1
  72. package/dist/web/standalone/.next/server/pages/404.html +1 -1
  73. package/dist/web/standalone/.next/server/pages/500.html +1 -1
  74. package/dist/web/standalone/.next/server/server-reference-manifest.json +1 -1
  75. package/package.json +1 -1
  76. package/packages/mcp-server/dist/server.d.ts +12 -1
  77. package/packages/mcp-server/dist/server.d.ts.map +1 -1
  78. package/packages/mcp-server/dist/server.js +90 -42
  79. package/packages/mcp-server/dist/server.js.map +1 -1
  80. package/packages/mcp-server/dist/workflow-tools.js +1 -1
  81. package/packages/mcp-server/dist/workflow-tools.js.map +1 -1
  82. package/packages/mcp-server/src/server.ts +110 -38
  83. package/packages/mcp-server/src/workflow-tools.ts +1 -1
  84. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts +8 -0
  85. package/packages/pi-coding-agent/dist/core/model-resolver.test.d.ts.map +1 -0
  86. package/packages/pi-coding-agent/dist/core/model-resolver.test.js +75 -0
  87. package/packages/pi-coding-agent/dist/core/model-resolver.test.js.map +1 -0
  88. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts +5 -0
  89. package/packages/pi-coding-agent/dist/core/retry-handler.d.ts.map +1 -1
  90. package/packages/pi-coding-agent/dist/core/retry-handler.js +55 -1
  91. package/packages/pi-coding-agent/dist/core/retry-handler.js.map +1 -1
  92. package/packages/pi-coding-agent/dist/core/retry-handler.test.js +57 -0
  93. package/packages/pi-coding-agent/dist/core/retry-handler.test.js.map +1 -1
  94. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.d.ts.map +1 -1
  95. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js +9 -2
  96. package/packages/pi-coding-agent/dist/modes/interactive/components/model-selector.js.map +1 -1
  97. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.d.ts.map +1 -1
  98. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js +6 -1
  99. package/packages/pi-coding-agent/dist/modes/interactive/controllers/model-controller.js.map +1 -1
  100. package/packages/pi-coding-agent/package.json +1 -1
  101. package/packages/pi-coding-agent/src/core/model-resolver.test.ts +85 -0
  102. package/packages/pi-coding-agent/src/core/retry-handler.test.ts +83 -0
  103. package/packages/pi-coding-agent/src/core/retry-handler.ts +60 -1
  104. package/packages/pi-coding-agent/src/modes/interactive/components/model-selector.ts +15 -6
  105. package/packages/pi-coding-agent/src/modes/interactive/controllers/model-controller.ts +6 -1
  106. package/pkg/package.json +1 -1
  107. package/src/resources/agents/debugger.md +58 -0
  108. package/src/resources/agents/doc-writer.md +43 -0
  109. package/src/resources/agents/git-ops.md +56 -0
  110. package/src/resources/agents/javascript-pro.md +46 -271
  111. package/src/resources/agents/planner.md +55 -0
  112. package/src/resources/agents/refactorer.md +47 -0
  113. package/src/resources/agents/reviewer.md +48 -0
  114. package/src/resources/agents/security.md +59 -0
  115. package/src/resources/agents/tester.md +50 -0
  116. package/src/resources/agents/typescript-pro.md +41 -235
  117. package/src/resources/extensions/claude-code-cli/stream-adapter.ts +109 -3
  118. package/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +133 -2
  119. package/src/resources/extensions/gsd/auto/phases.ts +4 -0
  120. package/src/resources/extensions/gsd/auto-prompts.ts +111 -33
  121. package/src/resources/extensions/gsd/auto-start.ts +31 -4
  122. package/src/resources/extensions/gsd/auto.ts +4 -0
  123. package/src/resources/extensions/gsd/bootstrap/db-tools.ts +3 -3
  124. package/src/resources/extensions/gsd/bootstrap/register-shortcuts.ts +2 -5
  125. package/src/resources/extensions/gsd/doctor-providers.ts +24 -0
  126. package/src/resources/extensions/gsd/error-classifier.ts +4 -1
  127. package/src/resources/extensions/gsd/gate-registry.ts +251 -0
  128. package/src/resources/extensions/gsd/gsd-db.ts +51 -0
  129. package/src/resources/extensions/gsd/milestone-validation-gates.ts +11 -13
  130. package/src/resources/extensions/gsd/notification-overlay.ts +27 -11
  131. package/src/resources/extensions/gsd/notification-store.ts +5 -4
  132. package/src/resources/extensions/gsd/prompt-validation.ts +157 -0
  133. package/src/resources/extensions/gsd/prompts/complete-slice.md +3 -1
  134. package/src/resources/extensions/gsd/prompts/execute-task.md +2 -0
  135. package/src/resources/extensions/gsd/prompts/validate-milestone.md +2 -0
  136. package/src/resources/extensions/gsd/shortcut-defs.ts +8 -1
  137. package/src/resources/extensions/gsd/state.ts +13 -2
  138. package/src/resources/extensions/gsd/tests/auto-start-model-capture.test.ts +14 -0
  139. package/src/resources/extensions/gsd/tests/complete-slice-gate-closure.test.ts +167 -0
  140. package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +36 -0
  141. package/src/resources/extensions/gsd/tests/format-shortcut.test.ts +16 -0
  142. package/src/resources/extensions/gsd/tests/gate-dispatch.test.ts +27 -0
  143. package/src/resources/extensions/gsd/tests/gate-registry.test.ts +140 -0
  144. package/src/resources/extensions/gsd/tests/prompt-system-gate-coverage.test.ts +208 -0
  145. package/src/resources/extensions/gsd/tests/provider-errors.test.ts +9 -0
  146. package/src/resources/extensions/gsd/tests/register-shortcuts.test.ts +3 -2
  147. package/src/resources/extensions/gsd/tools/complete-slice.ts +63 -0
  148. package/src/resources/extensions/gsd/tools/complete-task.ts +63 -0
  149. package/src/resources/extensions/gsd/tools/workflow-tool-executors.ts +4 -1
  150. package/src/resources/extensions/gsd/types.ts +26 -0
  151. package/src/resources/extensions/ollama/index.ts +13 -3
  152. package/src/resources/extensions/ollama/ollama-status-indicator.test.ts +28 -0
  153. package/src/resources/extensions/shared/gsd-phase-state.ts +42 -0
  154. package/src/resources/extensions/shared/tests/gsd-phase-state.test.ts +48 -0
  155. package/src/resources/extensions/subagent/agents.ts +10 -0
  156. package/src/resources/extensions/subagent/index.ts +18 -0
  157. package/src/resources/extensions/subagent/tests/agents-conflicts.test.ts +33 -0
  158. /package/dist/web/standalone/.next/static/{cYPZv_bAhZk2ms-Pz6vsY → f-Gremw0nLxxFUySaHRPw}/_buildManifest.js +0 -0
  159. /package/dist/web/standalone/.next/static/{cYPZv_bAhZk2ms-Pz6vsY → f-Gremw0nLxxFUySaHRPw}/_ssgManifest.js +0 -0
@@ -536,6 +536,24 @@ export interface CompleteTaskParams {
536
536
  verdict: string;
537
537
  durationMs: number;
538
538
  }>;
539
+ /**
540
+ * Q5 failure-modes section content (what breaks when dependencies fail).
541
+ * Populated → `pass`; omitted/empty → `omitted`.
542
+ * @optional
543
+ */
544
+ failureModes?: string;
545
+ /**
546
+ * Q6 load-profile section content (10x breakpoint + protection).
547
+ * Populated → `pass`; omitted/empty → `omitted`.
548
+ * @optional
549
+ */
550
+ loadProfile?: string;
551
+ /**
552
+ * Q7 negative-tests section content (malformed inputs, error paths,
553
+ * boundaries). Populated → `pass`; omitted/empty → `omitted`.
554
+ * @optional
555
+ */
556
+ negativeTests?: string;
539
557
  /** Optional caller-provided identity for audit trail */
540
558
  actorName?: string;
541
559
  /** Optional caller-provided reason this action was triggered */
@@ -584,6 +602,14 @@ export interface CompleteSliceParams {
584
602
  affects?: string[];
585
603
  /** @optional — defaults to [] when omitted */
586
604
  drillDownPaths?: string[];
605
+ /**
606
+ * Q8 operational readiness section content (health signal, failure signal,
607
+ * recovery, monitoring gaps). When populated, the complete-slice handler
608
+ * records Q8 as `pass`; when omitted or empty, Q8 is recorded as `omitted`.
609
+ * See gate-registry.ts.
610
+ * @optional
611
+ */
612
+ operationalReadiness?: string;
587
613
  /** Optional caller-provided identity for audit trail */
588
614
  actorName?: string;
589
615
  /** Optional caller-provided reason this action was triggered */
@@ -57,7 +57,15 @@ async function probeAndRegister(pi: ExtensionAPI): Promise<boolean> {
57
57
  }
58
58
 
59
59
  const models = await discoverModels();
60
- if (models.length === 0) return true; // Running but no models pulled
60
+ if (models.length === 0) {
61
+ // No local models means there's nothing usable to register in GSD.
62
+ // Keep the footer/status clean instead of advertising Ollama availability.
63
+ if (providerRegistered) {
64
+ pi.unregisterProvider("ollama");
65
+ providerRegistered = false;
66
+ }
67
+ return false;
68
+ }
61
69
 
62
70
  const baseUrl = client.getOllamaHost();
63
71
 
@@ -115,9 +123,11 @@ export default function ollama(pi: ExtensionAPI) {
115
123
  } else {
116
124
  probeAndRegister(pi)
117
125
  .then((found) => {
118
- if (found) ctx.ui.setStatus("ollama", "Ollama");
126
+ ctx.ui.setStatus("ollama", found ? "Ollama" : undefined);
119
127
  })
120
- .catch(() => {});
128
+ .catch(() => {
129
+ ctx.ui.setStatus("ollama", undefined);
130
+ });
121
131
  }
122
132
  });
123
133
 
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Regression test: don't show an Ollama footer status unless Ollama is
3
+ * actually usable (running with at least one discovered model).
4
+ */
5
+ import { test } from "node:test";
6
+ import assert from "node:assert/strict";
7
+ import { readFileSync } from "node:fs";
8
+ import { join, dirname } from "node:path";
9
+ import { fileURLToPath } from "node:url";
10
+
11
+ const __dirname = dirname(fileURLToPath(import.meta.url));
12
+ const src = readFileSync(join(__dirname, "index.ts"), "utf-8");
13
+
14
+ test("probeAndRegister returns false when no Ollama models are discovered", () => {
15
+ assert.match(
16
+ src,
17
+ /if \(models\.length === 0\)[\s\S]*return false;/,
18
+ "running-without-models should not be treated as available",
19
+ );
20
+ });
21
+
22
+ test("interactive session clears ollama footer status when unavailable", () => {
23
+ assert.match(
24
+ src,
25
+ /ctx\.ui\.setStatus\("ollama", found \? "Ollama" : undefined\)/,
26
+ "status should be cleared when probeAndRegister reports unavailable",
27
+ );
28
+ });
@@ -0,0 +1,42 @@
1
+ /**
2
+ * GSD Phase State — cross-extension coordination
3
+ * Copyright (c) 2026 Jeremy McSpadden <jeremy@fluxlabs.net>
4
+ *
5
+ * Lightweight module-level state that GSD auto-mode writes to and the
6
+ * subagent tool reads from. Both extensions run in the same process so
7
+ * a module variable is sufficient — no file I/O needed.
8
+ */
9
+
10
+ let _active = false;
11
+ let _currentPhase: string | null = null;
12
+
13
+ /** Mark GSD auto-mode as active. */
14
+ export function activateGSD(): void {
15
+ _active = true;
16
+ }
17
+
18
+ /** Mark GSD auto-mode as inactive and clear the current phase. */
19
+ export function deactivateGSD(): void {
20
+ _active = false;
21
+ _currentPhase = null;
22
+ }
23
+
24
+ /** Set the currently dispatched GSD phase (e.g. "plan-milestone"). */
25
+ export function setCurrentPhase(phase: string): void {
26
+ _currentPhase = phase;
27
+ }
28
+
29
+ /** Clear the current phase (unit completed or aborted). */
30
+ export function clearCurrentPhase(): void {
31
+ _currentPhase = null;
32
+ }
33
+
34
+ /** Returns true if GSD auto-mode is currently active. */
35
+ export function isGSDActive(): boolean {
36
+ return _active;
37
+ }
38
+
39
+ /** Returns the current GSD phase, or null if none is active. */
40
+ export function getCurrentPhase(): string | null {
41
+ return _active ? _currentPhase : null;
42
+ }
@@ -0,0 +1,48 @@
1
+ import { describe, it, beforeEach } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import {
4
+ activateGSD,
5
+ deactivateGSD,
6
+ setCurrentPhase,
7
+ clearCurrentPhase,
8
+ isGSDActive,
9
+ getCurrentPhase,
10
+ } from "../gsd-phase-state.js";
11
+
12
+ describe("gsd-phase-state", () => {
13
+ beforeEach(() => {
14
+ deactivateGSD();
15
+ });
16
+
17
+ it("tracks active/inactive state", () => {
18
+ assert.equal(isGSDActive(), false);
19
+ activateGSD();
20
+ assert.equal(isGSDActive(), true);
21
+ deactivateGSD();
22
+ assert.equal(isGSDActive(), false);
23
+ });
24
+
25
+ it("tracks the current phase when active", () => {
26
+ activateGSD();
27
+ assert.equal(getCurrentPhase(), null);
28
+ setCurrentPhase("plan-milestone");
29
+ assert.equal(getCurrentPhase(), "plan-milestone");
30
+ clearCurrentPhase();
31
+ assert.equal(getCurrentPhase(), null);
32
+ });
33
+
34
+ it("returns null phase when inactive even if phase was set", () => {
35
+ activateGSD();
36
+ setCurrentPhase("plan-milestone");
37
+ deactivateGSD();
38
+ assert.equal(getCurrentPhase(), null);
39
+ });
40
+
41
+ it("deactivation clears the current phase", () => {
42
+ activateGSD();
43
+ setCurrentPhase("execute-task");
44
+ deactivateGSD();
45
+ activateGSD();
46
+ assert.equal(getCurrentPhase(), null);
47
+ });
48
+ });
@@ -15,6 +15,7 @@ export interface AgentConfig {
15
15
  description: string;
16
16
  tools?: string[];
17
17
  model?: string;
18
+ conflictsWith?: string[];
18
19
  systemPrompt: string;
19
20
  source: "user" | "project";
20
21
  filePath: string;
@@ -30,6 +31,13 @@ interface AgentFrontmatter extends Record<string, unknown> {
30
31
  description?: string;
31
32
  tools?: string | string[];
32
33
  model?: string;
34
+ conflicts_with?: string;
35
+ }
36
+
37
+ export function parseConflictsWith(value: string | undefined): string[] | undefined {
38
+ if (typeof value !== "string") return undefined;
39
+ const conflicts = value.split(",").map((s) => s.trim()).filter(Boolean);
40
+ return conflicts.length > 0 ? conflicts : undefined;
33
41
  }
34
42
 
35
43
  function parseAgentTools(value: string | string[] | undefined): string[] | undefined {
@@ -85,12 +93,14 @@ function loadAgentsFromDir(dir: string, source: "user" | "project"): AgentConfig
85
93
  }
86
94
 
87
95
  const tools = parseAgentTools(frontmatter.tools);
96
+ const conflictsWith = parseConflictsWith(frontmatter.conflicts_with);
88
97
 
89
98
  agents.push({
90
99
  name: frontmatter.name,
91
100
  description: frontmatter.description,
92
101
  tools: tools && tools.length > 0 ? tools : undefined,
93
102
  model: frontmatter.model,
103
+ conflictsWith,
94
104
  systemPrompt: body,
95
105
  source,
96
106
  filePath,
@@ -24,6 +24,7 @@ import { type ExtensionAPI, getMarkdownTheme } from "@gsd/pi-coding-agent";
24
24
  import { Container, Markdown, Spacer, Text } from "@gsd/pi-tui";
25
25
  import { Type } from "@sinclair/typebox";
26
26
  import { formatTokenCount } from "../shared/mod.js";
27
+ import { getCurrentPhase } from "../shared/gsd-phase-state.js";
27
28
  import { type AgentConfig, type AgentScope, discoverAgents } from "./agents.js";
28
29
  import {
29
30
  type IsolationEnvironment,
@@ -352,6 +353,23 @@ async function runSingleAgent(
352
353
  };
353
354
  }
354
355
 
356
+ // GSD phase guard: block agents that conflict with the active GSD phase
357
+ if (agent.conflictsWith && agent.conflictsWith.length > 0) {
358
+ const activePhase = getCurrentPhase();
359
+ if (activePhase && agent.conflictsWith.includes(activePhase)) {
360
+ return {
361
+ agent: agentName,
362
+ agentSource: agent.source,
363
+ task,
364
+ exitCode: 1,
365
+ messages: [],
366
+ stderr: `Agent "${agentName}" is blocked: it conflicts with the active GSD phase "${activePhase}". Use the built-in GSD workflow instead.`,
367
+ usage: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0, cost: 0, contextTokens: 0, turns: 0 },
368
+ step,
369
+ };
370
+ }
371
+ }
372
+
355
373
  let tmpPromptDir: string | null = null;
356
374
  let tmpPromptPath: string | null = null;
357
375
 
@@ -0,0 +1,33 @@
1
+ import { describe, it } from "node:test";
2
+ import assert from "node:assert/strict";
3
+ import { parseConflictsWith } from "../agents.js";
4
+
5
+ describe("parseConflictsWith", () => {
6
+ it("parses comma-separated conflict list", () => {
7
+ const result = parseConflictsWith("plan-milestone, plan-slice, research-milestone");
8
+ assert.deepEqual(result, ["plan-milestone", "plan-slice", "research-milestone"]);
9
+ });
10
+
11
+ it("returns undefined for undefined input", () => {
12
+ assert.equal(parseConflictsWith(undefined), undefined);
13
+ });
14
+
15
+ it("returns undefined for empty string", () => {
16
+ assert.equal(parseConflictsWith(""), undefined);
17
+ });
18
+
19
+ it("handles single value without commas", () => {
20
+ const result = parseConflictsWith("plan-milestone");
21
+ assert.deepEqual(result, ["plan-milestone"]);
22
+ });
23
+
24
+ it("trims whitespace from values", () => {
25
+ const result = parseConflictsWith(" plan-milestone , plan-slice ");
26
+ assert.deepEqual(result, ["plan-milestone", "plan-slice"]);
27
+ });
28
+
29
+ it("filters out empty entries from trailing commas", () => {
30
+ const result = parseConflictsWith("plan-milestone,,plan-slice,");
31
+ assert.deepEqual(result, ["plan-milestone", "plan-slice"]);
32
+ });
33
+ });