@blxzer/cursor-trellis 0.3.5 → 0.4.0
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/CHANGELOG.md +69 -0
- package/README.md +13 -0
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +3 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/goal/index.d.ts +3 -0
- package/dist/commands/goal/index.d.ts.map +1 -0
- package/dist/commands/goal/index.js +159 -0
- package/dist/commands/goal/index.js.map +1 -0
- package/dist/commands/goal/review.d.ts +6 -0
- package/dist/commands/goal/review.d.ts.map +1 -0
- package/dist/commands/goal/review.js +28 -0
- package/dist/commands/goal/review.js.map +1 -0
- package/dist/commands/sdk/run.d.ts.map +1 -1
- package/dist/commands/sdk/run.js +2 -27
- package/dist/commands/sdk/run.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +5 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +51 -2
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/constants/paths.d.ts +4 -0
- package/dist/constants/paths.d.ts.map +1 -1
- package/dist/constants/paths.js +4 -0
- package/dist/constants/paths.js.map +1 -1
- package/dist/goal/action-packet.d.ts +89 -0
- package/dist/goal/action-packet.d.ts.map +1 -0
- package/dist/goal/action-packet.js +113 -0
- package/dist/goal/action-packet.js.map +1 -0
- package/dist/goal/audit.d.ts +14 -0
- package/dist/goal/audit.d.ts.map +1 -0
- package/dist/goal/audit.js +12 -0
- package/dist/goal/audit.js.map +1 -0
- package/dist/goal/constants.d.ts +26 -0
- package/dist/goal/constants.d.ts.map +1 -0
- package/dist/goal/constants.js +24 -0
- package/dist/goal/constants.js.map +1 -0
- package/dist/goal/paths.d.ts +8 -0
- package/dist/goal/paths.d.ts.map +1 -0
- package/dist/goal/paths.js +36 -0
- package/dist/goal/paths.js.map +1 -0
- package/dist/goal/preflight.d.ts +21 -0
- package/dist/goal/preflight.d.ts.map +1 -0
- package/dist/goal/preflight.js +97 -0
- package/dist/goal/preflight.js.map +1 -0
- package/dist/goal/review-seam.d.ts +44 -0
- package/dist/goal/review-seam.d.ts.map +1 -0
- package/dist/goal/review-seam.js +153 -0
- package/dist/goal/review-seam.js.map +1 -0
- package/dist/goal/reviewer-rules.d.ts +13 -0
- package/dist/goal/reviewer-rules.d.ts.map +1 -0
- package/dist/goal/reviewer-rules.js +153 -0
- package/dist/goal/reviewer-rules.js.map +1 -0
- package/dist/goal/reviewer.d.ts +9 -0
- package/dist/goal/reviewer.d.ts.map +1 -0
- package/dist/goal/reviewer.js +54 -0
- package/dist/goal/reviewer.js.map +1 -0
- package/dist/goal/runtime.d.ts +32 -0
- package/dist/goal/runtime.d.ts.map +1 -0
- package/dist/goal/runtime.js +242 -0
- package/dist/goal/runtime.js.map +1 -0
- package/dist/goal/sdk-client.d.ts +10 -0
- package/dist/goal/sdk-client.d.ts.map +1 -0
- package/dist/goal/sdk-client.js +34 -0
- package/dist/goal/sdk-client.js.map +1 -0
- package/dist/goal/state.d.ts +40 -0
- package/dist/goal/state.d.ts.map +1 -0
- package/dist/goal/state.js +37 -0
- package/dist/goal/state.js.map +1 -0
- package/dist/goal/task-triggers.d.ts +15 -0
- package/dist/goal/task-triggers.d.ts.map +1 -0
- package/dist/goal/task-triggers.js +23 -0
- package/dist/goal/task-triggers.js.map +1 -0
- package/dist/goal/walls.d.ts +12 -0
- package/dist/goal/walls.d.ts.map +1 -0
- package/dist/goal/walls.js +65 -0
- package/dist/goal/walls.js.map +1 -0
- package/dist/goal/worker-prompt.d.ts +13 -0
- package/dist/goal/worker-prompt.d.ts.map +1 -0
- package/dist/goal/worker-prompt.js +41 -0
- package/dist/goal/worker-prompt.js.map +1 -0
- package/dist/goal/worker.d.ts +42 -0
- package/dist/goal/worker.d.ts.map +1 -0
- package/dist/goal/worker.js +180 -0
- package/dist/goal/worker.js.map +1 -0
- package/dist/migrations/manifests/0.3.5.json +9 -0
- package/dist/migrations/manifests/0.3.6.json +9 -0
- package/dist/migrations/manifests/0.4.0.json +9 -0
- package/dist/templates/common/commands/continue.md +15 -3
- package/dist/templates/common/skills/check.md +7 -4
- package/dist/templates/cursor/agents/cstl-check.md +1 -0
- package/dist/templates/cursor/fixtures/expected-rules.d.ts.map +1 -1
- package/dist/templates/cursor/fixtures/expected-rules.js +6 -1
- package/dist/templates/cursor/fixtures/expected-rules.js.map +1 -1
- package/dist/templates/cursor/rules/cstl-cursor-modes.mdc +32 -0
- package/dist/templates/cursor/rules/cstl-triage.mdc +2 -4
- package/dist/templates/cursor/rules/retrieval-routing.mdc +26 -7
- package/dist/templates/markdown/index.d.ts +9 -0
- package/dist/templates/markdown/index.d.ts.map +1 -1
- package/dist/templates/markdown/index.js +9 -0
- package/dist/templates/markdown/index.js.map +1 -1
- package/dist/templates/markdown/spec/guides/artifact-locale-guide.md.txt +93 -0
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +7 -7
- package/dist/templates/markdown/spec/guides/cursor-context-injection-guide.md.txt +38 -21
- package/dist/templates/markdown/spec/guides/cursor-native-modes-guide.md.txt +125 -0
- package/dist/templates/markdown/spec/guides/cursor-subagent-policy.md.txt +10 -8
- package/dist/templates/markdown/spec/guides/debug-loop-guide.md.txt +227 -0
- package/dist/templates/markdown/spec/guides/goal-release-regression-runbook.md.txt +132 -0
- package/dist/templates/markdown/spec/guides/index.md.txt +144 -104
- package/dist/templates/markdown/spec/guides/injection-budget-guide.md.txt +111 -0
- package/dist/templates/markdown/spec/guides/prototype-guide.md.txt +139 -0
- package/dist/templates/markdown/spec/guides/retrieval-daily-guide.md.txt +28 -6
- package/dist/templates/markdown/spec/guides/test-discipline-guide.md.txt +138 -0
- package/dist/templates/markdown/spec/guides/verification-strength-guide.md.txt +185 -0
- package/dist/templates/shared-hooks/session-start.py +10 -0
- package/dist/templates/trellis/config.yaml +18 -0
- package/dist/templates/trellis/index.d.ts +17 -0
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +40 -0
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/pool/README.md +103 -0
- package/dist/templates/trellis/pool/items/.gitkeep +0 -0
- package/dist/templates/trellis/pool/plan.md +26 -0
- package/dist/templates/trellis/scripts/common/artifact_locale.py +278 -0
- package/dist/templates/trellis/scripts/common/codebase_retrieval_router.py +37 -2
- package/dist/templates/trellis/scripts/common/config.py +39 -0
- package/dist/templates/trellis/scripts/common/injection_budget.py +317 -0
- package/dist/templates/trellis/scripts/common/parent_orchestration.py +161 -5
- package/dist/templates/trellis/scripts/common/pool_store.py +702 -0
- package/dist/templates/trellis/scripts/common/retrieval_agent_instructions.py +9 -3
- package/dist/templates/trellis/scripts/common/session_context.py +32 -4
- package/dist/templates/trellis/scripts/common/session_memory.py +26 -0
- package/dist/templates/trellis/scripts/common/subagent_dispatch.py +74 -9
- package/dist/templates/trellis/scripts/common/task_context.py +18 -0
- package/dist/templates/trellis/scripts/common/task_dashboard.py +24 -1
- package/dist/templates/trellis/scripts/common/task_dependencies.py +673 -0
- package/dist/templates/trellis/scripts/common/task_gates.py +58 -5
- package/dist/templates/trellis/scripts/common/task_map.py +40 -8
- package/dist/templates/trellis/scripts/common/task_store.py +400 -39
- package/dist/templates/trellis/scripts/common/test_depends_mode_block.py +489 -0
- package/dist/templates/trellis/scripts/common/test_observable_defaults.py +135 -0
- package/dist/templates/trellis/scripts/common/test_pool_store.py +428 -0
- package/dist/templates/trellis/scripts/common/test_task_dependencies.py +345 -0
- package/dist/templates/trellis/scripts/cursor_retrieval_probe_prompt.md +14 -12
- package/dist/templates/trellis/scripts/generate_dispatch_prompt.py +182 -0
- package/dist/templates/trellis/scripts/injection_budget_probe.py +69 -0
- package/dist/templates/trellis/scripts/pool.py +192 -0
- package/dist/templates/trellis/scripts/retrieval_probe_matrix_template.json +3 -3
- package/dist/templates/trellis/scripts/spec_health_outcomes.py +160 -0
- package/dist/templates/trellis/scripts/task.py +85 -1
- package/dist/templates/trellis/scripts/verify_evidence_probe.py +138 -0
- package/dist/templates/trellis/tasks/locale/en/default-prd.md +19 -0
- package/dist/templates/trellis/tasks/locale/zh/default-prd.md +19 -0
- package/dist/templates/trellis/workflow.md +55 -5
- package/package.json +2 -2
|
@@ -138,10 +138,15 @@ def _cursor_step_for_route(
|
|
|
138
138
|
return (
|
|
139
139
|
f"使用 **codegraph_search**,路径侧重 `extensions/`,符号 `{symbol}`。"
|
|
140
140
|
)
|
|
141
|
+
if route_id == "definition-jump-native":
|
|
142
|
+
return (
|
|
143
|
+
f"**Prefer 原生**:用 **Grep** 定位 `{symbol}` 的定义行,再用 **Read** 验证文件与行号 "
|
|
144
|
+
"(Agent 无 GO_TO_DEFINITION)。仅当跨包 trap、重载歧义或需 blast 时再上 codegraph。"
|
|
145
|
+
)
|
|
141
146
|
if route_id == "lsp-navigation":
|
|
142
147
|
return (
|
|
143
|
-
f"
|
|
144
|
-
"的定义/引用(
|
|
148
|
+
f"**Own/codegraph**:用 **codegraph_explore** / **codegraph_search** 定位 `{symbol}` "
|
|
149
|
+
"的定义/引用(Grep+Read 不足或计划含 trap/blast 时);再用 **Read** 验证行号与正文。"
|
|
145
150
|
)
|
|
146
151
|
if role == "ast" or route_id in ("ast-codegraph",):
|
|
147
152
|
return (
|
|
@@ -294,7 +299,8 @@ def render_agent_instructions(
|
|
|
294
299
|
|
|
295
300
|
lines.append("")
|
|
296
301
|
lines.append(
|
|
297
|
-
"**
|
|
302
|
+
"**Prefer / Adapt / Own**:定义跳转 Prefer **Grep → Read**;诊断 **ReadLints**;"
|
|
303
|
+
"codegraph 独用调用链、跨包 trap、extension 符号、影响面,以及 Grep+Read 歧义时的定义/引用;"
|
|
298
304
|
"纯字面搜索用 Grep(勿用 GO_TO_DEFINITION,Agent 未暴露)。"
|
|
299
305
|
)
|
|
300
306
|
|
|
@@ -20,7 +20,9 @@ import subprocess
|
|
|
20
20
|
from pathlib import Path
|
|
21
21
|
|
|
22
22
|
from .active_task import resolve_context_key
|
|
23
|
-
from .
|
|
23
|
+
from .artifact_locale import artifact_locale_summary, resolve_artifact_locale
|
|
24
|
+
from .config import get_git_packages, get_journal_snippet_enabled
|
|
25
|
+
from .session_memory import get_latest_journal_summary
|
|
24
26
|
from .git import run_git
|
|
25
27
|
from .packages_context import get_packages_section
|
|
26
28
|
from .tasks import iter_active_tasks, load_task, get_all_statuses, children_progress
|
|
@@ -46,7 +48,7 @@ from .paths import (
|
|
|
46
48
|
# =============================================================================
|
|
47
49
|
|
|
48
50
|
_PACKAGE_NAME = "@blxzer/cursor-trellis"
|
|
49
|
-
_PYTHON_CMD = "
|
|
51
|
+
_PYTHON_CMD = "python"
|
|
50
52
|
_UPDATE_CHECK_TIMEOUT_SECONDS = 1.0
|
|
51
53
|
_VERSION_RE = re.compile(
|
|
52
54
|
r"^\s*(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?\s*$"
|
|
@@ -797,6 +799,10 @@ def get_context_json(repo_root: Path | None = None) -> dict:
|
|
|
797
799
|
|
|
798
800
|
result = {
|
|
799
801
|
"developer": developer or "",
|
|
802
|
+
"artifactLocale": resolve_artifact_locale(
|
|
803
|
+
repo_root / selected_task if selected_task else None,
|
|
804
|
+
repo_root,
|
|
805
|
+
),
|
|
800
806
|
"git": {
|
|
801
807
|
"isRepo": root_git_info["isRepo"],
|
|
802
808
|
"branch": root_git_info["branch"],
|
|
@@ -819,9 +825,26 @@ def get_context_json(repo_root: Path | None = None) -> dict:
|
|
|
819
825
|
if pkg_git_info:
|
|
820
826
|
result["packageGit"] = pkg_git_info
|
|
821
827
|
|
|
828
|
+
if get_journal_snippet_enabled(repo_root):
|
|
829
|
+
result["journal"]["snippet"] = _resolve_journal_snippet(
|
|
830
|
+
repo_root,
|
|
831
|
+
journal_file,
|
|
832
|
+
developer,
|
|
833
|
+
)
|
|
834
|
+
|
|
822
835
|
return result
|
|
823
836
|
|
|
824
837
|
|
|
838
|
+
def _resolve_journal_snippet(
|
|
839
|
+
repo_root: Path,
|
|
840
|
+
journal_file: Path | None,
|
|
841
|
+
developer: str | None,
|
|
842
|
+
) -> str:
|
|
843
|
+
if not journal_file or not developer:
|
|
844
|
+
return ""
|
|
845
|
+
return get_latest_journal_summary(journal_file, repo_root, developer)
|
|
846
|
+
|
|
847
|
+
|
|
825
848
|
def output_json(repo_root: Path | None = None) -> None:
|
|
826
849
|
"""Output context in JSON format.
|
|
827
850
|
|
|
@@ -860,11 +883,12 @@ def get_context_text(repo_root: Path | None = None) -> str:
|
|
|
860
883
|
lines.append("## DEVELOPER")
|
|
861
884
|
if not developer:
|
|
862
885
|
lines.append(
|
|
863
|
-
f"ERROR: Not initialized. Run:
|
|
886
|
+
f"ERROR: Not initialized. Run: python ./{DIR_WORKFLOW}/{DIR_SCRIPTS}/init_developer.py <name>"
|
|
864
887
|
)
|
|
865
888
|
return "\n".join(lines)
|
|
866
889
|
|
|
867
890
|
lines.append(f"Name: {developer}")
|
|
891
|
+
lines.append(artifact_locale_summary(repo_root))
|
|
868
892
|
lines.append("")
|
|
869
893
|
|
|
870
894
|
root_git_info = _collect_root_git_info(repo_root)
|
|
@@ -966,6 +990,10 @@ def get_context_text(repo_root: Path | None = None) -> str:
|
|
|
966
990
|
lines.append(f"Line count: {journal_lines} / 2000")
|
|
967
991
|
if journal_lines > 1800:
|
|
968
992
|
lines.append("[!] WARNING: Approaching 2000 line limit!")
|
|
993
|
+
if get_journal_snippet_enabled(repo_root):
|
|
994
|
+
snippet = _resolve_journal_snippet(repo_root, journal_file, developer)
|
|
995
|
+
if snippet:
|
|
996
|
+
lines.append(f"Recent snippet: {snippet}")
|
|
969
997
|
else:
|
|
970
998
|
lines.append("No journal file found")
|
|
971
999
|
lines.append("")
|
|
@@ -1085,7 +1113,7 @@ def get_context_text_record(repo_root: Path | None = None) -> str:
|
|
|
1085
1113
|
developer = get_developer(repo_root)
|
|
1086
1114
|
if not developer:
|
|
1087
1115
|
lines.append(
|
|
1088
|
-
f"ERROR: Not initialized. Run:
|
|
1116
|
+
f"ERROR: Not initialized. Run: python ./{DIR_WORKFLOW}/{DIR_SCRIPTS}/init_developer.py <name>"
|
|
1089
1117
|
)
|
|
1090
1118
|
return "\n".join(lines)
|
|
1091
1119
|
|
|
@@ -20,6 +20,7 @@ from .paths import DIR_WORKFLOW, DIR_WORKSPACE, FILE_JOURNAL_PREFIX, get_develop
|
|
|
20
20
|
|
|
21
21
|
|
|
22
22
|
SECTION_NAMES = {"summary", "main changes", "git commits", "testing", "status", "next steps"}
|
|
23
|
+
JOURNAL_SNIPPET_MAX_TOKENS = 150
|
|
23
24
|
FIELD_WEIGHTS = {
|
|
24
25
|
"title": 8,
|
|
25
26
|
"task": 8,
|
|
@@ -337,6 +338,31 @@ def normalize_space(value: str) -> str:
|
|
|
337
338
|
return re.sub(r"\s+", " ", value).strip()
|
|
338
339
|
|
|
339
340
|
|
|
341
|
+
def truncate_to_token_budget(text: str, max_tokens: int = JOURNAL_SNIPPET_MAX_TOKENS) -> str:
|
|
342
|
+
"""Collapse whitespace and hard-cap by whitespace-split token count."""
|
|
343
|
+
normalized = normalize_space(text)
|
|
344
|
+
if not normalized:
|
|
345
|
+
return ""
|
|
346
|
+
tokens = normalized.split()
|
|
347
|
+
if len(tokens) <= max_tokens:
|
|
348
|
+
return normalized
|
|
349
|
+
return " ".join(tokens[:max_tokens])
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def get_latest_journal_summary(
|
|
353
|
+
journal_path: Path,
|
|
354
|
+
repo_root: Path,
|
|
355
|
+
developer: str,
|
|
356
|
+
max_tokens: int = JOURNAL_SNIPPET_MAX_TOKENS,
|
|
357
|
+
) -> str:
|
|
358
|
+
"""Return the latest session summary from one journal file (≤ max_tokens)."""
|
|
359
|
+
entries = parse_journal(journal_path, repo_root, developer)
|
|
360
|
+
if not entries:
|
|
361
|
+
return ""
|
|
362
|
+
summary = entries[-1].sections.get("summary", "")
|
|
363
|
+
return truncate_to_token_budget(summary, max_tokens)
|
|
364
|
+
|
|
365
|
+
|
|
340
366
|
def to_repo_path(path: Path, repo_root: Path) -> str:
|
|
341
367
|
try:
|
|
342
368
|
return path.resolve().relative_to(repo_root.resolve()).as_posix()
|
|
@@ -12,6 +12,11 @@ import sys
|
|
|
12
12
|
from pathlib import Path
|
|
13
13
|
from typing import Literal
|
|
14
14
|
|
|
15
|
+
from .injection_budget import (
|
|
16
|
+
JSONL_MAX_ENTRIES,
|
|
17
|
+
JSONL_MAX_FILE_CHARS,
|
|
18
|
+
JSONL_MAX_TOTAL_CHARS,
|
|
19
|
+
)
|
|
15
20
|
from .io import read_json
|
|
16
21
|
from .paths import FILE_TASK_JSON
|
|
17
22
|
|
|
@@ -87,7 +92,8 @@ def read_jsonl_entries(
|
|
|
87
92
|
jsonl_path: str,
|
|
88
93
|
*,
|
|
89
94
|
warn_prefix: str = "subagent-dispatch",
|
|
90
|
-
|
|
95
|
+
apply_budget: bool = True,
|
|
96
|
+
) -> tuple[list[tuple[str, str]], list[str]]:
|
|
91
97
|
full_path = os.path.join(base_path, jsonl_path)
|
|
92
98
|
if not os.path.exists(full_path):
|
|
93
99
|
print(
|
|
@@ -95,10 +101,13 @@ def read_jsonl_entries(
|
|
|
95
101
|
"sub-agent will receive only task artifacts",
|
|
96
102
|
file=sys.stderr,
|
|
97
103
|
)
|
|
98
|
-
return []
|
|
104
|
+
return [], []
|
|
99
105
|
|
|
100
106
|
results: list[tuple[str, str]] = []
|
|
107
|
+
skipped: list[str] = []
|
|
101
108
|
saw_real_entry = False
|
|
109
|
+
loaded_entries = 0
|
|
110
|
+
total_chars = 0
|
|
102
111
|
try:
|
|
103
112
|
with open(full_path, "r", encoding="utf-8") as f:
|
|
104
113
|
for line in f:
|
|
@@ -112,12 +121,38 @@ def read_jsonl_entries(
|
|
|
112
121
|
if not file_path:
|
|
113
122
|
continue
|
|
114
123
|
saw_real_entry = True
|
|
124
|
+
if apply_budget and loaded_entries >= JSONL_MAX_ENTRIES:
|
|
125
|
+
skipped.append(f"{file_path}: entry cap ({JSONL_MAX_ENTRIES})")
|
|
126
|
+
continue
|
|
127
|
+
|
|
128
|
+
expanded: list[tuple[str, str]] = []
|
|
115
129
|
if entry_type == "directory":
|
|
116
|
-
|
|
130
|
+
expanded = read_directory_contents(base_path, file_path)
|
|
117
131
|
else:
|
|
118
132
|
content = read_file_content(base_path, file_path)
|
|
119
133
|
if content:
|
|
120
|
-
|
|
134
|
+
expanded = [(file_path, content)]
|
|
135
|
+
|
|
136
|
+
if not expanded:
|
|
137
|
+
skipped.append(f"{file_path}: missing or empty")
|
|
138
|
+
continue
|
|
139
|
+
|
|
140
|
+
block_chars = sum(len(content) for _, content in expanded)
|
|
141
|
+
if apply_budget and block_chars > JSONL_MAX_FILE_CHARS:
|
|
142
|
+
skipped.append(
|
|
143
|
+
f"{file_path}: file cap ({block_chars}>{JSONL_MAX_FILE_CHARS} chars)"
|
|
144
|
+
)
|
|
145
|
+
continue
|
|
146
|
+
if apply_budget and total_chars + block_chars > JSONL_MAX_TOTAL_CHARS:
|
|
147
|
+
skipped.append(
|
|
148
|
+
f"{file_path}: total cap "
|
|
149
|
+
f"({total_chars + block_chars}>{JSONL_MAX_TOTAL_CHARS})"
|
|
150
|
+
)
|
|
151
|
+
continue
|
|
152
|
+
|
|
153
|
+
results.extend(expanded)
|
|
154
|
+
total_chars += block_chars
|
|
155
|
+
loaded_entries += 1
|
|
121
156
|
except json.JSONDecodeError:
|
|
122
157
|
continue
|
|
123
158
|
except OSError:
|
|
@@ -129,13 +164,23 @@ def read_jsonl_entries(
|
|
|
129
164
|
"(only seed / empty) — sub-agent will receive only task artifacts.",
|
|
130
165
|
file=sys.stderr,
|
|
131
166
|
)
|
|
132
|
-
|
|
167
|
+
elif apply_budget and (results or skipped):
|
|
168
|
+
loaded_paths = ", ".join(path for path, _ in results) or "(none)"
|
|
169
|
+
print(
|
|
170
|
+
f"[{warn_prefix}] injection-budget: {jsonl_path} "
|
|
171
|
+
f"loaded {loaded_entries} entries ({total_chars} chars): {loaded_paths}",
|
|
172
|
+
file=sys.stderr,
|
|
173
|
+
)
|
|
174
|
+
for reason in skipped:
|
|
175
|
+
print(f"[{warn_prefix}] injection-budget: skipped {reason}", file=sys.stderr)
|
|
176
|
+
return results, skipped
|
|
133
177
|
|
|
134
178
|
|
|
135
179
|
def get_agent_context(repo_root: str, task_dir: str, agent_type: str) -> str:
|
|
136
180
|
context_parts: list[str] = []
|
|
137
181
|
agent_jsonl = f"{task_dir}/{agent_type}.jsonl"
|
|
138
|
-
|
|
182
|
+
entries, _ = read_jsonl_entries(repo_root, agent_jsonl)
|
|
183
|
+
for file_path, content in entries:
|
|
139
184
|
context_parts.append(f"=== {file_path} ===\n{content}")
|
|
140
185
|
return "\n\n".join(context_parts)
|
|
141
186
|
|
|
@@ -166,7 +211,8 @@ def get_implement_context(repo_root: str, task_dir: str) -> str:
|
|
|
166
211
|
|
|
167
212
|
def get_check_context(repo_root: str, task_dir: str) -> str:
|
|
168
213
|
context_parts: list[str] = []
|
|
169
|
-
|
|
214
|
+
entries, _ = read_jsonl_entries(repo_root, f"{task_dir}/check.jsonl")
|
|
215
|
+
for file_path, content in entries:
|
|
170
216
|
context_parts.append(f"=== {file_path} ===\n{content}")
|
|
171
217
|
|
|
172
218
|
prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
|
|
@@ -192,7 +238,25 @@ def get_finish_context(repo_root: str, task_dir: str) -> str:
|
|
|
192
238
|
|
|
193
239
|
|
|
194
240
|
def get_research_context(repo_root: str, task_dir: str | None) -> str:
|
|
195
|
-
|
|
241
|
+
context_parts: list[str] = []
|
|
242
|
+
|
|
243
|
+
if task_dir:
|
|
244
|
+
entries, _ = read_jsonl_entries(repo_root, f"{task_dir}/research.jsonl")
|
|
245
|
+
for file_path, content in entries:
|
|
246
|
+
context_parts.append(f"=== {file_path} ===\n{content}")
|
|
247
|
+
|
|
248
|
+
prd_content = read_file_content(repo_root, f"{task_dir}/prd.md")
|
|
249
|
+
if prd_content:
|
|
250
|
+
context_parts.append(
|
|
251
|
+
f"=== {task_dir}/prd.md (Requirements) ===\n{prd_content}"
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
design_content = read_file_content(repo_root, f"{task_dir}/design.md")
|
|
255
|
+
if design_content:
|
|
256
|
+
context_parts.append(
|
|
257
|
+
f"=== {task_dir}/design.md (Technical Design) ===\n{design_content}"
|
|
258
|
+
)
|
|
259
|
+
|
|
196
260
|
spec_path = f"{DIR_WORKFLOW}/{DIR_SPEC}"
|
|
197
261
|
spec_root = Path(repo_root) / DIR_WORKFLOW / DIR_SPEC
|
|
198
262
|
|
|
@@ -219,7 +283,8 @@ To get structured package info, run: `python ./{DIR_WORKFLOW}/scripts/get_contex
|
|
|
219
283
|
- Spec files: `{spec_path}/**/*.md`
|
|
220
284
|
- Code search: Use Glob and Grep tools
|
|
221
285
|
- External facts / docs: load `smart-search-cli` skill and use Bash (`smart-search` CLI), not Cursor WebSearch/WebFetch by default"""
|
|
222
|
-
|
|
286
|
+
context_parts.append(project_structure)
|
|
287
|
+
return "\n\n".join(context_parts)
|
|
223
288
|
|
|
224
289
|
|
|
225
290
|
def build_implement_prompt(original_prompt: str, context: str) -> str:
|
|
@@ -24,6 +24,11 @@ from pathlib import Path
|
|
|
24
24
|
from .log import Colors, colored
|
|
25
25
|
from .paths import get_repo_root
|
|
26
26
|
from .task_utils import resolve_task_dir
|
|
27
|
+
from .injection_budget import (
|
|
28
|
+
JSONL_MAX_ENTRIES,
|
|
29
|
+
JSONL_MAX_TOTAL_CHARS,
|
|
30
|
+
measure_jsonl_manifest,
|
|
31
|
+
)
|
|
27
32
|
|
|
28
33
|
|
|
29
34
|
# =============================================================================
|
|
@@ -159,6 +164,19 @@ def _validate_jsonl(jsonl_file: Path, repo_root: Path) -> int:
|
|
|
159
164
|
|
|
160
165
|
if errors == 0:
|
|
161
166
|
print(f" {colored(f'{file_name}: ✓ ({real_entries} entries)', Colors.GREEN)}")
|
|
167
|
+
budget = measure_jsonl_manifest(repo_root, jsonl_file.parent, file_name)
|
|
168
|
+
if real_entries > JSONL_MAX_ENTRIES:
|
|
169
|
+
print(
|
|
170
|
+
f" {colored(f'{file_name}: budget WARN — {real_entries} entries (max {JSONL_MAX_ENTRIES})', Colors.YELLOW)}"
|
|
171
|
+
)
|
|
172
|
+
if budget.expanded_chars > JSONL_MAX_TOTAL_CHARS:
|
|
173
|
+
print(
|
|
174
|
+
f" {colored(f'{file_name}: budget WARN — expanded {budget.expanded_chars} chars (max {JSONL_MAX_TOTAL_CHARS})', Colors.YELLOW)}"
|
|
175
|
+
)
|
|
176
|
+
elif budget.skipped:
|
|
177
|
+
print(
|
|
178
|
+
f" {colored(f'{file_name}: budget WARN — {len(budget.skipped)} entries would be skipped at dispatch', Colors.YELLOW)}"
|
|
179
|
+
)
|
|
162
180
|
else:
|
|
163
181
|
print(f" {colored(f'{file_name}: ✗ ({errors} errors)', Colors.RED)}")
|
|
164
182
|
|
|
@@ -7,6 +7,8 @@ from pathlib import Path
|
|
|
7
7
|
|
|
8
8
|
from .active_task import resolve_selected_task
|
|
9
9
|
from .paths import DIR_TASKS, DIR_WORKFLOW, get_developer, get_repo_root, get_tasks_dir
|
|
10
|
+
from .task_dependencies import dashboard_deps_line
|
|
11
|
+
from .task_gates import verify_evidence_status
|
|
10
12
|
from .task_map import get_child_state
|
|
11
13
|
from .tasks import (
|
|
12
14
|
children_progress,
|
|
@@ -23,6 +25,19 @@ def _task_path(dir_name: str) -> str:
|
|
|
23
25
|
return f"{DIR_WORKFLOW}/{DIR_TASKS}/{dir_name}"
|
|
24
26
|
|
|
25
27
|
|
|
28
|
+
def format_verify_summary(task_dir: Path, task_data: dict) -> str:
|
|
29
|
+
"""Return a compact verify evidence label for dashboard task lines."""
|
|
30
|
+
verify_path = task_dir / "verify.md"
|
|
31
|
+
if not verify_path.is_file():
|
|
32
|
+
return "[verify: missing]"
|
|
33
|
+
status = verify_evidence_status(task_dir, task_data)
|
|
34
|
+
if all(status.values()):
|
|
35
|
+
return "[verify: ok]"
|
|
36
|
+
if any(status.values()):
|
|
37
|
+
return "[verify: partial]"
|
|
38
|
+
return "[verify: missing]"
|
|
39
|
+
|
|
40
|
+
|
|
26
41
|
def _selected_line(
|
|
27
42
|
repo_root: Path,
|
|
28
43
|
platform_input: dict | None = None,
|
|
@@ -123,10 +138,18 @@ def _append_task(
|
|
|
123
138
|
integration_state = get_child_state(parent_dir, name)
|
|
124
139
|
status_display = format_child_task_display(task.status, integration_state)
|
|
125
140
|
assignee = task.assignee or "-"
|
|
141
|
+
verify_summary = format_verify_summary(task.directory, task.raw or {})
|
|
126
142
|
prefix = " " * indent + " - "
|
|
127
143
|
lines.append(
|
|
128
|
-
f"{prefix}{_task_path(name)} ({status_display}){progress} [{assignee}]"
|
|
144
|
+
f"{prefix}{_task_path(name)} ({status_display}){progress} [{assignee}] {verify_summary}"
|
|
145
|
+
)
|
|
146
|
+
deps_line = dashboard_deps_line(
|
|
147
|
+
task.directory,
|
|
148
|
+
task.raw or {},
|
|
149
|
+
parent_dir=parent_dir,
|
|
129
150
|
)
|
|
151
|
+
if deps_line:
|
|
152
|
+
lines.append(f"{prefix}{deps_line}")
|
|
130
153
|
for child_name in task.children:
|
|
131
154
|
if child_name in all_tasks:
|
|
132
155
|
_append_task(
|