@stylusnexus/work-plan 2026.6.18 → 2026.6.19

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/VERSION CHANGED
@@ -1 +1 @@
1
- 2026.06.18+64ae461
1
+ 2026.06.19+9cf7cdc
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stylusnexus/work-plan",
3
- "version": "2026.6.18",
3
+ "version": "2026.6.19",
4
4
  "description": "Track-aware daily work planning over GitHub issues. Shared tracks (git-synced .work-plan/ in each repo), AI clustering (group/auto-triage), VS Code viewer, Claude Code + Codex plugins. Pure Python stdlib.",
5
5
  "bin": {
6
6
  "work-plan": "bin/work-plan"
@@ -100,6 +100,7 @@ Rules:
100
100
  def run(args: list[str]) -> int:
101
101
  apply_mode = "--apply" in args
102
102
  heuristic = "--heuristic" in args
103
+ json_mode = "--json" in args
103
104
  repo_arg = next((a for a in args if a.startswith("--repo=")), None)
104
105
 
105
106
  limit = 100
@@ -153,7 +154,14 @@ def run(args: list[str]) -> int:
153
154
  and t.meta.get("status") in ("active", "in-progress", "blocked")
154
155
  ]
155
156
  if not active_tracks:
156
- print(f"No active tracks found for {repo}. Run /work-plan group first.")
157
+ # --json callers (the VS Code viewer) parse stdout as JSON, so this
158
+ # informational early-exit must stay machine-readable rather than a bare
159
+ # human line (which crashed Suggest Tracks with a JSON parse error).
160
+ if json_mode:
161
+ print(json.dumps({"repo": repo, "folder": folder,
162
+ "tracks": [], "untracked": [], "note": "no_active_tracks"}))
163
+ else:
164
+ print(f"No active tracks found for {repo}. Run /work-plan group first.")
157
165
  return 0
158
166
 
159
167
  # Build per-repo set of already-tracked issue numbers
@@ -168,7 +176,11 @@ def run(args: list[str]) -> int:
168
176
  untracked = [i for i in open_issues if i.get("number") not in tracked_nums]
169
177
 
170
178
  if not untracked:
171
- print(f"No untracked issues found for {repo} — full coverage!")
179
+ if json_mode:
180
+ print(json.dumps({"repo": repo, "folder": folder,
181
+ "tracks": [], "untracked": [], "note": "full_coverage"}))
182
+ else:
183
+ print(f"No untracked issues found for {repo} — full coverage!")
172
184
  return 0
173
185
 
174
186
  batch_id = _make_batch_id(repo)
@@ -399,6 +399,40 @@ class AutoTriageHeuristicTest(unittest.TestCase):
399
399
  self.assertEqual(sug["track"], "auth-flow")
400
400
 
401
401
 
402
+ class AutoTriageJsonEarlyExitTest(unittest.TestCase):
403
+ """--json informational early-exits must stay parseable JSON, not human text
404
+ (the bug that crashed Suggest Tracks with 'could not parse auto-triage JSON')."""
405
+
406
+ def test_no_active_tracks_emits_json_note(self):
407
+ cfg = _make_cfg()
408
+ # A parked track → no ACTIVE tracks for the repo.
409
+ parked = _make_track("old", "org/myrepo", [1], status="parked")
410
+ rc, out, _ = _drive_prepare(["--json"], cfg=cfg, tracks=[parked],
411
+ open_issues=_open_issues(2, 3))
412
+ self.assertEqual(rc, 0)
413
+ data = json.loads(out.strip()) # MUST parse — was a bare human line
414
+ self.assertEqual(data["note"], "no_active_tracks")
415
+ self.assertEqual(data["tracks"], [])
416
+
417
+ def test_no_untracked_emits_json_note(self):
418
+ cfg = _make_cfg()
419
+ tracks = [_make_track("auth-flow", "org/myrepo", [1, 2])]
420
+ rc, out, _ = _drive_prepare(["--json"], cfg=cfg, tracks=tracks,
421
+ open_issues=_open_issues(1, 2)) # all tracked
422
+ self.assertEqual(rc, 0)
423
+ data = json.loads(out.strip())
424
+ self.assertEqual(data["note"], "full_coverage")
425
+ self.assertEqual(data["untracked"], [])
426
+
427
+ def test_no_active_tracks_human_text_without_json(self):
428
+ cfg = _make_cfg()
429
+ parked = _make_track("old", "org/myrepo", [1], status="parked")
430
+ rc, out, _ = _drive_prepare([], cfg=cfg, tracks=[parked],
431
+ open_issues=_open_issues(2, 3))
432
+ self.assertEqual(rc, 0)
433
+ self.assertIn("group", out) # the human guidance still shows
434
+
435
+
402
436
  class AutoTriageV2AnswersTest(unittest.TestCase):
403
437
  """v2 abstain-first answers schema (#241) + back-compat with v1."""
404
438