@blxzer/cursor-trellis 0.3.3 → 0.3.5

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 (111) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/README.md +0 -13
  3. package/README.zh-CN.md +2 -1
  4. package/bin/cstl.js +1 -1
  5. package/bin/smart-search.js +1 -1
  6. package/dist/cli/index.d.ts.map +1 -1
  7. package/dist/cli/index.js +14 -2
  8. package/dist/cli/index.js.map +1 -1
  9. package/dist/commands/campaign/canvas-render.d.ts +68 -0
  10. package/dist/commands/campaign/canvas-render.d.ts.map +1 -0
  11. package/dist/commands/campaign/canvas-render.js +414 -0
  12. package/dist/commands/campaign/canvas-render.js.map +1 -0
  13. package/dist/commands/campaign/compose.d.ts +15 -0
  14. package/dist/commands/campaign/compose.d.ts.map +1 -0
  15. package/dist/commands/campaign/compose.js +110 -0
  16. package/dist/commands/campaign/compose.js.map +1 -0
  17. package/dist/commands/campaign/index.d.ts +13 -0
  18. package/dist/commands/campaign/index.d.ts.map +1 -0
  19. package/dist/commands/campaign/index.js +134 -0
  20. package/dist/commands/campaign/index.js.map +1 -0
  21. package/dist/commands/campaign/kind-map.d.ts +6 -0
  22. package/dist/commands/campaign/kind-map.d.ts.map +1 -0
  23. package/dist/commands/campaign/kind-map.js +21 -0
  24. package/dist/commands/campaign/kind-map.js.map +1 -0
  25. package/dist/commands/campaign/mcp-server.d.ts +6 -0
  26. package/dist/commands/campaign/mcp-server.d.ts.map +1 -0
  27. package/dist/commands/campaign/mcp-server.js +167 -0
  28. package/dist/commands/campaign/mcp-server.js.map +1 -0
  29. package/dist/commands/campaign/render.d.ts +5 -0
  30. package/dist/commands/campaign/render.d.ts.map +1 -0
  31. package/dist/commands/campaign/render.js +103 -0
  32. package/dist/commands/campaign/render.js.map +1 -0
  33. package/dist/commands/campaign/trellis-load.d.ts +13 -0
  34. package/dist/commands/campaign/trellis-load.d.ts.map +1 -0
  35. package/dist/commands/campaign/trellis-load.js +122 -0
  36. package/dist/commands/campaign/trellis-load.js.map +1 -0
  37. package/dist/commands/campaign/types.d.ts +61 -0
  38. package/dist/commands/campaign/types.d.ts.map +1 -0
  39. package/dist/commands/campaign/types.js +3 -0
  40. package/dist/commands/campaign/types.js.map +1 -0
  41. package/dist/commands/init.d.ts.map +1 -1
  42. package/dist/commands/init.js +11 -1
  43. package/dist/commands/init.js.map +1 -1
  44. package/dist/commands/rpc/client.d.ts +51 -0
  45. package/dist/commands/rpc/client.d.ts.map +1 -0
  46. package/dist/commands/rpc/client.js +147 -0
  47. package/dist/commands/rpc/client.js.map +1 -0
  48. package/dist/commands/rpc/index.d.ts +10 -0
  49. package/dist/commands/rpc/index.d.ts.map +1 -0
  50. package/dist/commands/rpc/index.js +157 -0
  51. package/dist/commands/rpc/index.js.map +1 -0
  52. package/dist/commands/rpc/serve.d.ts +23 -0
  53. package/dist/commands/rpc/serve.d.ts.map +1 -0
  54. package/dist/commands/rpc/serve.js +60 -0
  55. package/dist/commands/rpc/serve.js.map +1 -0
  56. package/dist/commands/rpc/smoke.d.ts +16 -0
  57. package/dist/commands/rpc/smoke.d.ts.map +1 -0
  58. package/dist/commands/rpc/smoke.js +92 -0
  59. package/dist/commands/rpc/smoke.js.map +1 -0
  60. package/dist/commands/sdk/index.d.ts +9 -0
  61. package/dist/commands/sdk/index.d.ts.map +1 -0
  62. package/dist/commands/sdk/index.js +58 -0
  63. package/dist/commands/sdk/index.js.map +1 -0
  64. package/dist/commands/sdk/run.d.ts +42 -0
  65. package/dist/commands/sdk/run.d.ts.map +1 -0
  66. package/dist/commands/sdk/run.js +245 -0
  67. package/dist/commands/sdk/run.js.map +1 -0
  68. package/dist/commands/sdk/status.d.ts +14 -0
  69. package/dist/commands/sdk/status.d.ts.map +1 -0
  70. package/dist/commands/sdk/status.js +69 -0
  71. package/dist/commands/sdk/status.js.map +1 -0
  72. package/dist/migrations/manifests/0.3.2.json +16 -0
  73. package/dist/migrations/manifests/0.3.3.json +9 -0
  74. package/dist/migrations/manifests/0.3.4.json +9 -0
  75. package/dist/templates/common/bundled-skills/cstl-meta/references/platform-files/hooks-and-settings.md +2 -1
  76. package/dist/templates/common/bundled-skills/smart-search-cli/SKILL.md +35 -29
  77. package/dist/templates/common/bundled-skills/smart-search-cli/references/cli-contract.md +11 -25
  78. package/dist/templates/cursor/fixtures/expected-rules.d.ts.map +1 -1
  79. package/dist/templates/cursor/fixtures/expected-rules.js +5 -0
  80. package/dist/templates/cursor/fixtures/expected-rules.js.map +1 -1
  81. package/dist/templates/cursor/hooks.json +7 -0
  82. package/dist/templates/cursor/rules/cstl-session-rename.mdc +31 -0
  83. package/dist/templates/cursor/rules/retrieval-routing.mdc +1 -1
  84. package/dist/templates/markdown/agents.md +2 -0
  85. package/dist/templates/markdown/spec/guides/retrieval-daily-guide.md.txt +14 -0
  86. package/dist/templates/shared-hooks/index.d.ts +1 -1
  87. package/dist/templates/shared-hooks/index.d.ts.map +1 -1
  88. package/dist/templates/shared-hooks/index.js +1 -0
  89. package/dist/templates/shared-hooks/index.js.map +1 -1
  90. package/dist/templates/shared-hooks/rename-session-for-task.py +231 -0
  91. package/dist/templates/trellis/scripts/common/codebase_retrieval_router.py +14 -0
  92. package/dist/templates/trellis/scripts/common/parent_orchestration.py +341 -2
  93. package/dist/templates/trellis/scripts/common/task_map.py +638 -614
  94. package/dist/templates/trellis/scripts/common/task_store.py +47 -1
  95. package/dist/templates/trellis/scripts/task.py +32 -0
  96. package/dist/templates/trellis/tasks/templates/release-execution/design.md +60 -60
  97. package/dist/utils/codebase-retrieval-router.d.ts.map +1 -1
  98. package/dist/utils/codebase-retrieval-router.js +14 -0
  99. package/dist/utils/codebase-retrieval-router.js.map +1 -1
  100. package/dist/utils/cursor-sdk-gate.d.ts +10 -0
  101. package/dist/utils/cursor-sdk-gate.d.ts.map +1 -0
  102. package/dist/utils/cursor-sdk-gate.js +32 -0
  103. package/dist/utils/cursor-sdk-gate.js.map +1 -0
  104. package/dist/utils/project-capabilities.d.ts +30 -3
  105. package/dist/utils/project-capabilities.d.ts.map +1 -1
  106. package/dist/utils/project-capabilities.js +182 -11
  107. package/dist/utils/project-capabilities.js.map +1 -1
  108. package/dist/utils/readiness.d.ts.map +1 -1
  109. package/dist/utils/readiness.js +26 -0
  110. package/dist/utils/readiness.js.map +1 -1
  111. package/package.json +4 -3
@@ -289,6 +289,317 @@ def build_child_prompt(
289
289
  return "\n".join(lines), errors
290
290
 
291
291
 
292
+
293
+ def _children_by_id(data: dict) -> dict[str, dict]:
294
+ return {
295
+ item.get("id"): item
296
+ for item in data.get("children", [])
297
+ if isinstance(item, dict) and isinstance(item.get("id"), str)
298
+ }
299
+
300
+
301
+ def _normalize_stages(data: dict) -> tuple[list[dict], list[str], bool]:
302
+ """Return (stages, errors, used_legacy_default)."""
303
+ errors: list[str] = []
304
+ children = _children_by_id(data)
305
+ raw_stages = data.get("stages") or []
306
+ if not isinstance(raw_stages, list):
307
+ return [], ["task-map stages must be a list"], False
308
+
309
+ if not raw_stages:
310
+ units = list(children.keys())
311
+ return (
312
+ [{"id": "default", "title": "All children (no stages declared)", "units": units}],
313
+ [],
314
+ True,
315
+ )
316
+
317
+ stages: list[dict] = []
318
+ seen_units: set[str] = set()
319
+ for idx, stage in enumerate(raw_stages):
320
+ if not isinstance(stage, dict):
321
+ errors.append(f"stages[{idx}] must be a mapping")
322
+ continue
323
+ stage_id = stage.get("id")
324
+ if not isinstance(stage_id, str) or not stage_id.strip():
325
+ errors.append(f"stages[{idx}] missing id")
326
+ continue
327
+ units = stage.get("units") or []
328
+ if not isinstance(units, list):
329
+ errors.append(f"stage `{stage_id}` units must be a list")
330
+ continue
331
+ norm_units: list[str] = []
332
+ for unit in units:
333
+ if not isinstance(unit, str) or not unit.strip():
334
+ errors.append(f"stage `{stage_id}` has invalid unit entry")
335
+ continue
336
+ if unit not in children:
337
+ errors.append(f"stage `{stage_id}` unit `{unit}` not in children[]")
338
+ continue
339
+ if unit in seen_units:
340
+ errors.append(f"unit `{unit}` appears in multiple stages")
341
+ continue
342
+ seen_units.add(unit)
343
+ norm_units.append(unit)
344
+ stages.append(
345
+ {
346
+ "id": stage_id,
347
+ "title": stage.get("title") if isinstance(stage.get("title"), str) else "",
348
+ "units": norm_units,
349
+ }
350
+ )
351
+ return stages, errors, False
352
+
353
+
354
+ def _unit_readiness(
355
+ unit_id: str,
356
+ children_by_id: dict[str, dict],
357
+ ) -> tuple[str, list[str]]:
358
+ entry = children_by_id.get(unit_id) or {}
359
+ blocked = _unmet_dependencies(entry, children_by_id)
360
+ return ("ready" if not blocked else "blocked", blocked)
361
+
362
+
363
+ def build_publish_pack(
364
+ parent_dir: Path,
365
+ *,
366
+ stage_id: str | None = None,
367
+ mode: str = "inline",
368
+ dry_run: bool = False,
369
+ ) -> tuple[str | None, list[str]]:
370
+ """Build campaign PACK index + per-unit prompts under child-prompts/."""
371
+ errors: list[str] = []
372
+ repo_root = get_repo_root()
373
+ parent_rel = _repo_rel(parent_dir, repo_root)
374
+
375
+ if not (parent_dir / "task.json").is_file():
376
+ return None, [f"parent task.json missing: {parent_dir}"]
377
+
378
+ data, _ = load_task_map(parent_dir)
379
+ if data is None:
380
+ return None, ["parent task-map.md missing or invalid"]
381
+
382
+ stages, stage_errors, legacy = _normalize_stages(data)
383
+ errors.extend(stage_errors)
384
+ if errors:
385
+ return None, errors
386
+
387
+ if stage_id:
388
+ stages = [s for s in stages if s.get("id") == stage_id]
389
+ if not stages:
390
+ return None, [f"unknown stage id: {stage_id}"]
391
+
392
+ children_by_id = _children_by_id(data)
393
+ prompts_dir = parent_dir / "child-prompts"
394
+ ready_rows: list[tuple[str, str, str]] = []
395
+ blocked_rows: list[tuple[str, str, list[str]]] = []
396
+
397
+ pack_lines = [
398
+ f"# Campaign PACK — `{parent_dir.name}`",
399
+ "",
400
+ f"- Parent: `{parent_rel}`",
401
+ f"- Generated: {_utc_now()}",
402
+ f"- Mode: `{mode}`",
403
+ "- Path rule: every worker must `task.py select <explicit-child-path>`; "
404
+ "do not rely on selected_task inheritance across windows.",
405
+ "",
406
+ "## Manual window path (PACK)",
407
+ "",
408
+ "1. Run `publish-pack` (this file) on the Parent.",
409
+ "2. For each **ready** unit: IDE **New Chat** or Agents Window → paste `child-prompts/<child>.md`.",
410
+ "3. In that session run `python ./.cstl/scripts/task.py select <child-path>`.",
411
+ "4. Parent retains `review-child` / `integrate-child`. PACK is not RUN auto-dispatch.",
412
+ "",
413
+ "## Stages",
414
+ "",
415
+ ]
416
+ if legacy:
417
+ pack_lines.append(
418
+ "> Warning: no `stages:` in task-map; using implicit `default` stage "
419
+ "with all children. Declare `stages:` for HYBRID campaign packs."
420
+ )
421
+ pack_lines.append("")
422
+
423
+ written: list[str] = []
424
+ for stage in stages:
425
+ sid = stage["id"]
426
+ title = stage.get("title") or ""
427
+ heading = f"### `{sid}`" + (f" — {title}" if title else "")
428
+ pack_lines.append(heading)
429
+ pack_lines.append("")
430
+ for unit in stage.get("units") or []:
431
+ readiness, blocked = _unit_readiness(unit, children_by_id)
432
+ child_dir = parent_dir.parent / unit
433
+ child_rel = _repo_rel(child_dir, repo_root)
434
+ state = (children_by_id.get(unit) or {}).get("state", "?")
435
+ if readiness == "ready":
436
+ prompt, prompt_errors = build_child_prompt(
437
+ parent_dir,
438
+ child_dir,
439
+ include_artifacts=False,
440
+ mode=mode,
441
+ )
442
+ if prompt_errors or not prompt:
443
+ errors.extend(prompt_errors or [f"could not build prompt for `{unit}`"])
444
+ continue
445
+ out_name = f"{unit}.md"
446
+ out_rel = f"{parent_rel}/child-prompts/{out_name}"
447
+ if not dry_run:
448
+ prompts_dir.mkdir(parents=True, exist_ok=True)
449
+ (prompts_dir / out_name).write_text(prompt + "\n", encoding="utf-8")
450
+ written.append(out_rel)
451
+ ready_rows.append((sid, unit, out_rel))
452
+ pack_lines.append(
453
+ f"- `{unit}` — `{state}` — **ready** — prompt: `child-prompts/{out_name}` "
454
+ f"(select `{child_rel}`)"
455
+ )
456
+ else:
457
+ blocked_rows.append((sid, unit, blocked))
458
+ stub = "\n".join(
459
+ [
460
+ f"# BLOCKED — `{unit}`",
461
+ "",
462
+ f"- Parent: `{parent_rel}`",
463
+ f"- Child: `{child_rel}`",
464
+ f"- Stage: `{sid}`",
465
+ "- Do not start implementation until dependencies reach "
466
+ "`integrated` or `cancelled`.",
467
+ "",
468
+ "Unmet dependencies:",
469
+ *[f"- {item}" for item in blocked],
470
+ "",
471
+ ]
472
+ )
473
+ out_name = f"{unit}.md"
474
+ out_rel = f"{parent_rel}/child-prompts/{out_name}"
475
+ if not dry_run:
476
+ prompts_dir.mkdir(parents=True, exist_ok=True)
477
+ (prompts_dir / out_name).write_text(stub, encoding="utf-8")
478
+ written.append(out_rel)
479
+ pack_lines.append(
480
+ f"- `{unit}` — `{state}` — **blocked** — {', '.join(blocked)}"
481
+ )
482
+ pack_lines.append("")
483
+
484
+ pack_lines.extend(["## Ready", ""])
485
+ if ready_rows:
486
+ for sid, unit, path in ready_rows:
487
+ pack_lines.append(f"- `{sid}` / `{unit}` → `{path}`")
488
+ else:
489
+ pack_lines.append("(none)")
490
+ pack_lines.extend(["", "## Blocked", ""])
491
+ if blocked_rows:
492
+ for sid, unit, deps in blocked_rows:
493
+ pack_lines.append(f"- `{sid}` / `{unit}` — {', '.join(deps)}")
494
+ else:
495
+ pack_lines.append("(none)")
496
+ pack_lines.append("")
497
+
498
+ pack_body = "\n".join(pack_lines)
499
+ pack_rel = f"{parent_rel}/child-prompts/PACK.md"
500
+ if not dry_run:
501
+ prompts_dir.mkdir(parents=True, exist_ok=True)
502
+ (prompts_dir / "PACK.md").write_text(pack_body + "\n", encoding="utf-8")
503
+ written.append(pack_rel)
504
+
505
+ if errors:
506
+ return None, errors
507
+
508
+ summary = [
509
+ f"# publish-pack — `{parent_dir.name}`",
510
+ "",
511
+ f"- Parent: `{parent_rel}`",
512
+ f"- dry_run: {dry_run}",
513
+ f"- legacy_no_stages: {legacy}",
514
+ f"- ready: {len(ready_rows)}",
515
+ f"- blocked: {len(blocked_rows)}",
516
+ f"- pack_index: `{pack_rel}`",
517
+ "",
518
+ ]
519
+ if written:
520
+ summary.append("## Written")
521
+ summary.append("")
522
+ for path in written:
523
+ summary.append(f"- `{path}`")
524
+ summary.append("")
525
+ elif dry_run:
526
+ summary.append("(dry-run: no files written)")
527
+ summary.append("")
528
+ summary.append(pack_body)
529
+ return "\n".join(summary), []
530
+
531
+
532
+ def build_parent_status_dict(parent_dir: Path) -> dict | None:
533
+ """Machine-readable parent orchestration snapshot (campaign UI / MCP)."""
534
+ repo_root = get_repo_root()
535
+ parent_rel = _repo_rel(parent_dir, repo_root)
536
+ data, _ = load_task_map(parent_dir)
537
+ if data is None:
538
+ return None
539
+
540
+ children_by_id = _children_by_id(data)
541
+ stages, stage_errors, legacy = _normalize_stages(data)
542
+
543
+ stage_rows: list[dict] = []
544
+ for stage in stages:
545
+ units: list[dict] = []
546
+ for unit in stage.get("units") or []:
547
+ entry = children_by_id.get(unit) or {}
548
+ readiness, blocked = _unit_readiness(unit, children_by_id)
549
+ units.append(
550
+ {
551
+ "id": unit,
552
+ "state": entry.get("state", "?"),
553
+ "readiness": readiness,
554
+ "blockedBy": blocked,
555
+ }
556
+ )
557
+ stage_rows.append(
558
+ {
559
+ "id": stage["id"],
560
+ "title": stage.get("title") or "",
561
+ "units": units,
562
+ }
563
+ )
564
+
565
+ children_rows: list[dict] = []
566
+ for child in data.get("children") or []:
567
+ if not isinstance(child, dict):
568
+ continue
569
+ cid = child.get("id")
570
+ if not isinstance(cid, str):
571
+ continue
572
+ child_dir = parent_dir.parent / cid
573
+ children_rows.append(
574
+ {
575
+ "id": cid,
576
+ "state": child.get("state", "?"),
577
+ "dependsOn": child.get("depends_on") or [],
578
+ "touches": child.get("touches") or [],
579
+ "isolation": child.get("isolation"),
580
+ "branch": child.get("branch"),
581
+ "worktreePath": child.get("worktree_path"),
582
+ "evidence": child.get("evidence"),
583
+ "ref": child.get("ref"),
584
+ "verifyMd": (child_dir / "verify.md").is_file(),
585
+ "handoffMd": (child_dir / "handoff.md").is_file(),
586
+ }
587
+ )
588
+
589
+ return {
590
+ "id": parent_dir.name,
591
+ "path": parent_rel,
592
+ "contractEpoch": data.get("contract_epoch"),
593
+ "executionTopology": data.get("execution_topology"),
594
+ "mergeLimit": data.get("merge_limit"),
595
+ "stages": stage_rows,
596
+ "children": children_rows,
597
+ "integrationQueue": data.get("integration_queue") or [],
598
+ "stageErrors": stage_errors,
599
+ "legacyStages": legacy,
600
+ }
601
+
602
+
292
603
  def build_parent_status(parent_dir: Path) -> str:
293
604
  """Render a parent task-map status summary for the reviewer."""
294
605
  repo_root = get_repo_root()
@@ -305,10 +616,37 @@ def build_parent_status(parent_dir: Path) -> str:
305
616
  f"- execution_topology: {data.get('execution_topology', '?')}",
306
617
  f"- merge_limit: {data.get('merge_limit', '?')}",
307
618
  "",
308
- "## Children",
309
- "",
310
619
  ]
311
620
 
621
+ children_by_id = _children_by_id(data)
622
+ stages, stage_errors, legacy = _normalize_stages(data)
623
+ lines.append("## Stages")
624
+ lines.append("")
625
+ if stage_errors:
626
+ for err in stage_errors:
627
+ lines.append(f"- error: {err}")
628
+ lines.append("")
629
+ elif legacy:
630
+ lines.append("stages: (none declared — implicit `default` for publish-pack)")
631
+ lines.append("")
632
+ else:
633
+ for stage in stages:
634
+ sid = stage["id"]
635
+ title = stage.get("title") or ""
636
+ heading = f"### `{sid}`" + (f" — {title}" if title else "")
637
+ lines.append(heading)
638
+ for unit in stage.get("units") or []:
639
+ entry = children_by_id.get(unit) or {}
640
+ state = entry.get("state", "?")
641
+ readiness, blocked = _unit_readiness(unit, children_by_id)
642
+ if readiness == "ready":
643
+ lines.append(f"- `{unit}` — `{state}` — ready")
644
+ else:
645
+ lines.append(f"- `{unit}` — `{state}` — blocked ({', '.join(blocked)})")
646
+ lines.append("")
647
+
648
+ lines.extend(["## Children", ""])
649
+
312
650
  children = data.get("children") or []
313
651
  if not children:
314
652
  lines.append("(no children in task-map)")
@@ -354,6 +692,7 @@ def build_parent_status(parent_dir: Path) -> str:
354
692
  lines.append("")
355
693
  lines.append("```bash")
356
694
  lines.append(f"python ./.cstl/scripts/task.py parent-status {parent_rel}")
695
+ lines.append(f"python ./.cstl/scripts/task.py publish-pack {parent_rel}")
357
696
  lines.append(f"python ./.cstl/scripts/task.py generate-child-prompt {parent_rel} <child> --mode subagent")
358
697
  lines.append(f"python ./.cstl/scripts/task.py generate-child-prompt {parent_rel} <child> --mode inline")
359
698
  lines.append(f"python ./.cstl/scripts/task.py review-child {parent_rel} <child> --check")