@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.
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|