@blxzer/cursor-trellis 0.3.4 → 0.3.6
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 +46 -0
- package/README.md +13 -13
- package/README.zh-CN.md +1 -1
- package/bin/cstl.js +1 -1
- package/bin/smart-search.js +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +17 -2
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/campaign/canvas-render.d.ts +68 -0
- package/dist/commands/campaign/canvas-render.d.ts.map +1 -0
- package/dist/commands/campaign/canvas-render.js +414 -0
- package/dist/commands/campaign/canvas-render.js.map +1 -0
- package/dist/commands/campaign/compose.d.ts +15 -0
- package/dist/commands/campaign/compose.d.ts.map +1 -0
- package/dist/commands/campaign/compose.js +110 -0
- package/dist/commands/campaign/compose.js.map +1 -0
- package/dist/commands/campaign/index.d.ts +13 -0
- package/dist/commands/campaign/index.d.ts.map +1 -0
- package/dist/commands/campaign/index.js +134 -0
- package/dist/commands/campaign/index.js.map +1 -0
- package/dist/commands/campaign/kind-map.d.ts +6 -0
- package/dist/commands/campaign/kind-map.d.ts.map +1 -0
- package/dist/commands/campaign/kind-map.js +21 -0
- package/dist/commands/campaign/kind-map.js.map +1 -0
- package/dist/commands/campaign/mcp-server.d.ts +6 -0
- package/dist/commands/campaign/mcp-server.d.ts.map +1 -0
- package/dist/commands/campaign/mcp-server.js +167 -0
- package/dist/commands/campaign/mcp-server.js.map +1 -0
- package/dist/commands/campaign/render.d.ts +5 -0
- package/dist/commands/campaign/render.d.ts.map +1 -0
- package/dist/commands/campaign/render.js +103 -0
- package/dist/commands/campaign/render.js.map +1 -0
- package/dist/commands/campaign/trellis-load.d.ts +13 -0
- package/dist/commands/campaign/trellis-load.d.ts.map +1 -0
- package/dist/commands/campaign/trellis-load.js +122 -0
- package/dist/commands/campaign/trellis-load.js.map +1 -0
- package/dist/commands/campaign/types.d.ts +61 -0
- package/dist/commands/campaign/types.d.ts.map +1 -0
- package/dist/commands/campaign/types.js +3 -0
- package/dist/commands/campaign/types.js.map +1 -0
- 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/init.d.ts.map +1 -1
- package/dist/commands/init.js +11 -1
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/rpc/client.d.ts +51 -0
- package/dist/commands/rpc/client.d.ts.map +1 -0
- package/dist/commands/rpc/client.js +147 -0
- package/dist/commands/rpc/client.js.map +1 -0
- package/dist/commands/rpc/index.d.ts +10 -0
- package/dist/commands/rpc/index.d.ts.map +1 -0
- package/dist/commands/rpc/index.js +157 -0
- package/dist/commands/rpc/index.js.map +1 -0
- package/dist/commands/rpc/serve.d.ts +23 -0
- package/dist/commands/rpc/serve.d.ts.map +1 -0
- package/dist/commands/rpc/serve.js +60 -0
- package/dist/commands/rpc/serve.js.map +1 -0
- package/dist/commands/rpc/smoke.d.ts +16 -0
- package/dist/commands/rpc/smoke.d.ts.map +1 -0
- package/dist/commands/rpc/smoke.js +92 -0
- package/dist/commands/rpc/smoke.js.map +1 -0
- package/dist/commands/sdk/index.d.ts +9 -0
- package/dist/commands/sdk/index.d.ts.map +1 -0
- package/dist/commands/sdk/index.js +58 -0
- package/dist/commands/sdk/index.js.map +1 -0
- package/dist/commands/sdk/run.d.ts +42 -0
- package/dist/commands/sdk/run.d.ts.map +1 -0
- package/dist/commands/sdk/run.js +220 -0
- package/dist/commands/sdk/run.js.map +1 -0
- package/dist/commands/sdk/status.d.ts +14 -0
- package/dist/commands/sdk/status.d.ts.map +1 -0
- package/dist/commands/sdk/status.js +69 -0
- package/dist/commands/sdk/status.js.map +1 -0
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +13 -1
- package/dist/configurators/workflow.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/templates/common/bundled-skills/smart-search-cli/SKILL.md +35 -29
- package/dist/templates/common/bundled-skills/smart-search-cli/references/cli-contract.md +11 -25
- 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/agents.md +2 -0
- package/dist/templates/markdown/index.d.ts +3 -0
- package/dist/templates/markdown/index.d.ts.map +1 -1
- package/dist/templates/markdown/index.js +3 -0
- package/dist/templates/markdown/index.js.map +1 -1
- 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/index.md.txt +107 -104
- package/dist/templates/markdown/spec/guides/injection-budget-guide.md.txt +111 -0
- package/dist/templates/markdown/spec/guides/retrieval-daily-guide.md.txt +38 -6
- package/dist/templates/markdown/spec/guides/verification-strength-guide.md.txt +184 -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 +5 -0
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +14 -0
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/common/artifact_locale.py +278 -0
- package/dist/templates/trellis/scripts/common/codebase_retrieval_router.py +51 -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 +500 -5
- 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 +16 -1
- package/dist/templates/trellis/scripts/common/task_map.py +670 -614
- package/dist/templates/trellis/scripts/common/task_store.py +191 -40
- package/dist/templates/trellis/scripts/common/test_observable_defaults.py +135 -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/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 +51 -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 +23 -3
- package/dist/utils/codebase-retrieval-router.d.ts.map +1 -1
- package/dist/utils/codebase-retrieval-router.js +14 -0
- package/dist/utils/codebase-retrieval-router.js.map +1 -1
- package/dist/utils/cursor-sdk-gate.d.ts +10 -0
- package/dist/utils/cursor-sdk-gate.d.ts.map +1 -0
- package/dist/utils/cursor-sdk-gate.js +32 -0
- package/dist/utils/cursor-sdk-gate.js.map +1 -0
- package/dist/utils/project-capabilities.d.ts +30 -3
- package/dist/utils/project-capabilities.d.ts.map +1 -1
- package/dist/utils/project-capabilities.js +182 -11
- package/dist/utils/project-capabilities.js.map +1 -1
- package/dist/utils/readiness.d.ts.map +1 -1
- package/dist/utils/readiness.js +26 -0
- package/dist/utils/readiness.js.map +1 -1
- package/package.json +4 -3
|
@@ -12,6 +12,7 @@ import re
|
|
|
12
12
|
from datetime import datetime, timezone
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
|
|
15
|
+
from .io import read_json
|
|
15
16
|
from .paths import get_repo_root
|
|
16
17
|
from .task_gates import (
|
|
17
18
|
build_spec_update_scaffold,
|
|
@@ -22,6 +23,7 @@ from .task_map import (
|
|
|
22
23
|
PARENT_TERMINAL_STATES,
|
|
23
24
|
get_child_entry,
|
|
24
25
|
load_task_map,
|
|
26
|
+
resolve_child_map_id,
|
|
25
27
|
validate_parent_child_integration,
|
|
26
28
|
)
|
|
27
29
|
|
|
@@ -100,7 +102,7 @@ def _child_prompt_section(
|
|
|
100
102
|
*,
|
|
101
103
|
include_artifacts: bool,
|
|
102
104
|
) -> str:
|
|
103
|
-
child_name = child_dir.name
|
|
105
|
+
child_name = child_entry.get("id") or child_dir.name
|
|
104
106
|
parent_rel = _repo_rel(parent_dir, repo_root)
|
|
105
107
|
child_rel = _repo_rel(child_dir, repo_root)
|
|
106
108
|
state = child_entry.get("state", "open")
|
|
@@ -164,12 +166,15 @@ def build_child_prompt(
|
|
|
164
166
|
"""Build an implementation prompt for a linked child task."""
|
|
165
167
|
errors: list[str] = []
|
|
166
168
|
repo_root = get_repo_root()
|
|
167
|
-
child_name = child_dir.name
|
|
168
169
|
|
|
169
170
|
parent_json = parent_dir / "task.json"
|
|
170
171
|
if not parent_json.is_file():
|
|
171
172
|
return None, [f"parent task.json missing: {parent_dir}"]
|
|
172
173
|
|
|
174
|
+
parent_data = read_json(parent_json) or {}
|
|
175
|
+
child_data = read_json(child_dir / "task.json") or {}
|
|
176
|
+
child_name = resolve_child_map_id(parent_data, child_dir, child_data)
|
|
177
|
+
|
|
173
178
|
data, body = load_task_map(parent_dir)
|
|
174
179
|
if data is None:
|
|
175
180
|
return None, ["parent task-map.md missing or invalid"]
|
|
@@ -289,6 +294,457 @@ def build_child_prompt(
|
|
|
289
294
|
return "\n".join(lines), errors
|
|
290
295
|
|
|
291
296
|
|
|
297
|
+
|
|
298
|
+
def _children_by_id(data: dict) -> dict[str, dict]:
|
|
299
|
+
return {
|
|
300
|
+
item.get("id"): item
|
|
301
|
+
for item in data.get("children", [])
|
|
302
|
+
if isinstance(item, dict) and isinstance(item.get("id"), str)
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
|
|
306
|
+
def _normalize_stages(data: dict) -> tuple[list[dict], list[str], bool]:
|
|
307
|
+
"""Return (stages, errors, used_legacy_default)."""
|
|
308
|
+
errors: list[str] = []
|
|
309
|
+
children = _children_by_id(data)
|
|
310
|
+
raw_stages = data.get("stages") or []
|
|
311
|
+
if not isinstance(raw_stages, list):
|
|
312
|
+
return [], ["task-map stages must be a list"], False
|
|
313
|
+
|
|
314
|
+
if not raw_stages:
|
|
315
|
+
units = list(children.keys())
|
|
316
|
+
return (
|
|
317
|
+
[{"id": "default", "title": "All children (no stages declared)", "units": units}],
|
|
318
|
+
[],
|
|
319
|
+
True,
|
|
320
|
+
)
|
|
321
|
+
|
|
322
|
+
stages: list[dict] = []
|
|
323
|
+
seen_units: set[str] = set()
|
|
324
|
+
for idx, stage in enumerate(raw_stages):
|
|
325
|
+
if not isinstance(stage, dict):
|
|
326
|
+
errors.append(f"stages[{idx}] must be a mapping")
|
|
327
|
+
continue
|
|
328
|
+
stage_id = stage.get("id")
|
|
329
|
+
if not isinstance(stage_id, str) or not stage_id.strip():
|
|
330
|
+
errors.append(f"stages[{idx}] missing id")
|
|
331
|
+
continue
|
|
332
|
+
units = stage.get("units") or []
|
|
333
|
+
if not isinstance(units, list):
|
|
334
|
+
errors.append(f"stage `{stage_id}` units must be a list")
|
|
335
|
+
continue
|
|
336
|
+
norm_units: list[str] = []
|
|
337
|
+
for unit in units:
|
|
338
|
+
if not isinstance(unit, str) or not unit.strip():
|
|
339
|
+
errors.append(f"stage `{stage_id}` has invalid unit entry")
|
|
340
|
+
continue
|
|
341
|
+
if unit not in children:
|
|
342
|
+
errors.append(f"stage `{stage_id}` unit `{unit}` not in children[]")
|
|
343
|
+
continue
|
|
344
|
+
if unit in seen_units:
|
|
345
|
+
errors.append(f"unit `{unit}` appears in multiple stages")
|
|
346
|
+
continue
|
|
347
|
+
seen_units.add(unit)
|
|
348
|
+
norm_units.append(unit)
|
|
349
|
+
stages.append(
|
|
350
|
+
{
|
|
351
|
+
"id": stage_id,
|
|
352
|
+
"title": stage.get("title") if isinstance(stage.get("title"), str) else "",
|
|
353
|
+
"units": norm_units,
|
|
354
|
+
}
|
|
355
|
+
)
|
|
356
|
+
return stages, errors, False
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def _unit_readiness(
|
|
360
|
+
unit_id: str,
|
|
361
|
+
children_by_id: dict[str, dict],
|
|
362
|
+
) -> tuple[str, list[str]]:
|
|
363
|
+
entry = children_by_id.get(unit_id) or {}
|
|
364
|
+
blocked = _unmet_dependencies(entry, children_by_id)
|
|
365
|
+
return ("ready" if not blocked else "blocked", blocked)
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
_PACK_GENERATED_RE = re.compile(r"^- Generated:\s*(.+)$", re.MULTILINE)
|
|
369
|
+
_PACK_DASH = r"[\u2014\-]"
|
|
370
|
+
_PACK_UNIT_LINE_RE = re.compile(
|
|
371
|
+
rf"^- `([^`]+)` {_PACK_DASH} `([^`]+)` {_PACK_DASH} \*\*(ready|blocked)\*\*",
|
|
372
|
+
re.MULTILINE,
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
|
|
376
|
+
def _parse_iso_timestamp(raw: str) -> datetime | None:
|
|
377
|
+
text = raw.strip()
|
|
378
|
+
if not text:
|
|
379
|
+
return None
|
|
380
|
+
normalized = text[:-1] + "+00:00" if text.endswith("Z") else text
|
|
381
|
+
try:
|
|
382
|
+
parsed = datetime.fromisoformat(normalized)
|
|
383
|
+
except ValueError:
|
|
384
|
+
try:
|
|
385
|
+
parsed = datetime.strptime(text, "%Y-%m-%d")
|
|
386
|
+
except ValueError:
|
|
387
|
+
return None
|
|
388
|
+
if parsed.tzinfo is None:
|
|
389
|
+
return parsed.replace(tzinfo=timezone.utc)
|
|
390
|
+
return parsed
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
def _parse_pack_generated(pack_text: str) -> datetime | None:
|
|
394
|
+
match = _PACK_GENERATED_RE.search(pack_text)
|
|
395
|
+
if not match:
|
|
396
|
+
return None
|
|
397
|
+
return _parse_iso_timestamp(match.group(1))
|
|
398
|
+
|
|
399
|
+
|
|
400
|
+
def _last_integrate_event_time(body: str) -> datetime | None:
|
|
401
|
+
last: datetime | None = None
|
|
402
|
+
for line in body.splitlines():
|
|
403
|
+
stripped = line.strip()
|
|
404
|
+
if not stripped.startswith("- "):
|
|
405
|
+
continue
|
|
406
|
+
remainder = stripped[2:]
|
|
407
|
+
parts = remainder.split(" - ", 1)
|
|
408
|
+
if len(parts) != 2:
|
|
409
|
+
continue
|
|
410
|
+
ts_raw, message = parts[0].strip(), parts[1]
|
|
411
|
+
lowered = message.lower()
|
|
412
|
+
if "integration state" not in message and "integrate-through" not in lowered:
|
|
413
|
+
continue
|
|
414
|
+
parsed = _parse_iso_timestamp(ts_raw)
|
|
415
|
+
if parsed is not None and (last is None or parsed > last):
|
|
416
|
+
last = parsed
|
|
417
|
+
return last
|
|
418
|
+
|
|
419
|
+
|
|
420
|
+
def _pack_ready_units(pack_text: str) -> set[str]:
|
|
421
|
+
ready: set[str] = set()
|
|
422
|
+
for unit_id, _state, readiness in _PACK_UNIT_LINE_RE.findall(pack_text):
|
|
423
|
+
if readiness == "ready":
|
|
424
|
+
ready.add(unit_id)
|
|
425
|
+
return ready
|
|
426
|
+
|
|
427
|
+
|
|
428
|
+
def _expected_pack_ready_units(
|
|
429
|
+
data: dict,
|
|
430
|
+
children_by_id: dict[str, dict],
|
|
431
|
+
) -> set[str]:
|
|
432
|
+
stages, _, _ = _normalize_stages(data)
|
|
433
|
+
ready: set[str] = set()
|
|
434
|
+
for stage in stages:
|
|
435
|
+
for unit in stage.get("units") or []:
|
|
436
|
+
if not isinstance(unit, str):
|
|
437
|
+
continue
|
|
438
|
+
readiness, _ = _unit_readiness(unit, children_by_id)
|
|
439
|
+
if readiness == "ready":
|
|
440
|
+
ready.add(unit)
|
|
441
|
+
return ready
|
|
442
|
+
|
|
443
|
+
|
|
444
|
+
def _pack_unit_states_mismatch(
|
|
445
|
+
pack_text: str,
|
|
446
|
+
children_by_id: dict[str, dict],
|
|
447
|
+
) -> bool:
|
|
448
|
+
for unit_id, pack_state, _readiness in _PACK_UNIT_LINE_RE.findall(pack_text):
|
|
449
|
+
entry = children_by_id.get(unit_id) or {}
|
|
450
|
+
current_state = entry.get("state", "?")
|
|
451
|
+
if str(current_state) != pack_state:
|
|
452
|
+
return True
|
|
453
|
+
return False
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def _compute_stale_pack(
|
|
457
|
+
parent_dir: Path,
|
|
458
|
+
data: dict,
|
|
459
|
+
body: str,
|
|
460
|
+
children_by_id: dict[str, dict],
|
|
461
|
+
) -> bool | None:
|
|
462
|
+
pack_path = parent_dir / "child-prompts" / "PACK.md"
|
|
463
|
+
if not pack_path.is_file():
|
|
464
|
+
return None
|
|
465
|
+
try:
|
|
466
|
+
pack_text = pack_path.read_text(encoding="utf-8")
|
|
467
|
+
except OSError:
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
generated = _parse_pack_generated(pack_text)
|
|
471
|
+
if generated is None:
|
|
472
|
+
return None
|
|
473
|
+
|
|
474
|
+
stale = False
|
|
475
|
+
last_integrate = _last_integrate_event_time(body)
|
|
476
|
+
if last_integrate is not None and generated < last_integrate:
|
|
477
|
+
stale = True
|
|
478
|
+
if _pack_unit_states_mismatch(pack_text, children_by_id):
|
|
479
|
+
stale = True
|
|
480
|
+
expected_ready = _expected_pack_ready_units(data, children_by_id)
|
|
481
|
+
if _pack_ready_units(pack_text) != expected_ready:
|
|
482
|
+
stale = True
|
|
483
|
+
return stale
|
|
484
|
+
|
|
485
|
+
|
|
486
|
+
def _compute_newly_ready(children_by_id: dict[str, dict]) -> list[str]:
|
|
487
|
+
ready_ids: list[str] = []
|
|
488
|
+
for unit_id in sorted(children_by_id):
|
|
489
|
+
entry = children_by_id[unit_id]
|
|
490
|
+
if entry.get("state") != "open":
|
|
491
|
+
continue
|
|
492
|
+
readiness, _ = _unit_readiness(unit_id, children_by_id)
|
|
493
|
+
if readiness == "ready":
|
|
494
|
+
ready_ids.append(unit_id)
|
|
495
|
+
return ready_ids
|
|
496
|
+
|
|
497
|
+
|
|
498
|
+
def _child_prompt_filename(unit_id: str) -> str:
|
|
499
|
+
"""Flat filename for child-prompts (nested slash unit ids use `--`)."""
|
|
500
|
+
return f"{unit_id.replace('/', '--')}.md"
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
def build_publish_pack(
|
|
504
|
+
parent_dir: Path,
|
|
505
|
+
*,
|
|
506
|
+
stage_id: str | None = None,
|
|
507
|
+
mode: str = "inline",
|
|
508
|
+
dry_run: bool = False,
|
|
509
|
+
) -> tuple[str | None, list[str]]:
|
|
510
|
+
"""Build campaign PACK index + per-unit prompts under child-prompts/."""
|
|
511
|
+
errors: list[str] = []
|
|
512
|
+
repo_root = get_repo_root()
|
|
513
|
+
parent_rel = _repo_rel(parent_dir, repo_root)
|
|
514
|
+
|
|
515
|
+
if not (parent_dir / "task.json").is_file():
|
|
516
|
+
return None, [f"parent task.json missing: {parent_dir}"]
|
|
517
|
+
|
|
518
|
+
data, _ = load_task_map(parent_dir)
|
|
519
|
+
if data is None:
|
|
520
|
+
return None, ["parent task-map.md missing or invalid"]
|
|
521
|
+
|
|
522
|
+
stages, stage_errors, legacy = _normalize_stages(data)
|
|
523
|
+
errors.extend(stage_errors)
|
|
524
|
+
if errors:
|
|
525
|
+
return None, errors
|
|
526
|
+
|
|
527
|
+
if stage_id:
|
|
528
|
+
stages = [s for s in stages if s.get("id") == stage_id]
|
|
529
|
+
if not stages:
|
|
530
|
+
return None, [f"unknown stage id: {stage_id}"]
|
|
531
|
+
|
|
532
|
+
children_by_id = _children_by_id(data)
|
|
533
|
+
prompts_dir = parent_dir / "child-prompts"
|
|
534
|
+
ready_rows: list[tuple[str, str, str]] = []
|
|
535
|
+
blocked_rows: list[tuple[str, str, list[str]]] = []
|
|
536
|
+
|
|
537
|
+
pack_lines = [
|
|
538
|
+
f"# Campaign PACK — `{parent_dir.name}`",
|
|
539
|
+
"",
|
|
540
|
+
f"- Parent: `{parent_rel}`",
|
|
541
|
+
f"- Generated: {_utc_now()}",
|
|
542
|
+
f"- Mode: `{mode}`",
|
|
543
|
+
"- Path rule: every worker must `task.py select <explicit-child-path>`; "
|
|
544
|
+
"do not rely on selected_task inheritance across windows.",
|
|
545
|
+
"",
|
|
546
|
+
"## Manual window path (PACK)",
|
|
547
|
+
"",
|
|
548
|
+
"1. Run `publish-pack` (this file) on the Parent.",
|
|
549
|
+
"2. For each **ready** unit: IDE **New Chat** or Agents Window → paste `child-prompts/<child>.md`.",
|
|
550
|
+
"3. In that session run `python ./.cstl/scripts/task.py select <child-path>`.",
|
|
551
|
+
"4. Parent retains `review-child` / `integrate-child`. PACK is not RUN auto-dispatch.",
|
|
552
|
+
"",
|
|
553
|
+
"## Stages",
|
|
554
|
+
"",
|
|
555
|
+
]
|
|
556
|
+
if legacy:
|
|
557
|
+
pack_lines.append(
|
|
558
|
+
"> Warning: no `stages:` in task-map; using implicit `default` stage "
|
|
559
|
+
"with all children. Declare `stages:` for HYBRID campaign packs."
|
|
560
|
+
)
|
|
561
|
+
pack_lines.append("")
|
|
562
|
+
|
|
563
|
+
written: list[str] = []
|
|
564
|
+
for stage in stages:
|
|
565
|
+
sid = stage["id"]
|
|
566
|
+
title = stage.get("title") or ""
|
|
567
|
+
heading = f"### `{sid}`" + (f" — {title}" if title else "")
|
|
568
|
+
pack_lines.append(heading)
|
|
569
|
+
pack_lines.append("")
|
|
570
|
+
for unit in stage.get("units") or []:
|
|
571
|
+
readiness, blocked = _unit_readiness(unit, children_by_id)
|
|
572
|
+
child_dir = parent_dir.parent / unit
|
|
573
|
+
child_rel = _repo_rel(child_dir, repo_root)
|
|
574
|
+
state = (children_by_id.get(unit) or {}).get("state", "?")
|
|
575
|
+
if readiness == "ready":
|
|
576
|
+
prompt, prompt_errors = build_child_prompt(
|
|
577
|
+
parent_dir,
|
|
578
|
+
child_dir,
|
|
579
|
+
include_artifacts=False,
|
|
580
|
+
mode=mode,
|
|
581
|
+
)
|
|
582
|
+
if prompt_errors or not prompt:
|
|
583
|
+
errors.extend(prompt_errors or [f"could not build prompt for `{unit}`"])
|
|
584
|
+
continue
|
|
585
|
+
out_name = _child_prompt_filename(unit)
|
|
586
|
+
out_rel = f"{parent_rel}/child-prompts/{out_name}"
|
|
587
|
+
if not dry_run:
|
|
588
|
+
prompts_dir.mkdir(parents=True, exist_ok=True)
|
|
589
|
+
(prompts_dir / out_name).write_text(prompt + "\n", encoding="utf-8")
|
|
590
|
+
written.append(out_rel)
|
|
591
|
+
ready_rows.append((sid, unit, out_rel))
|
|
592
|
+
pack_lines.append(
|
|
593
|
+
f"- `{unit}` — `{state}` — **ready** — prompt: `child-prompts/{out_name}` "
|
|
594
|
+
f"(select `{child_rel}`)"
|
|
595
|
+
)
|
|
596
|
+
else:
|
|
597
|
+
blocked_rows.append((sid, unit, blocked))
|
|
598
|
+
stub = "\n".join(
|
|
599
|
+
[
|
|
600
|
+
f"# BLOCKED — `{unit}`",
|
|
601
|
+
"",
|
|
602
|
+
f"- Parent: `{parent_rel}`",
|
|
603
|
+
f"- Child: `{child_rel}`",
|
|
604
|
+
f"- Stage: `{sid}`",
|
|
605
|
+
"- Do not start implementation until dependencies reach "
|
|
606
|
+
"`integrated` or `cancelled`.",
|
|
607
|
+
"",
|
|
608
|
+
"Unmet dependencies:",
|
|
609
|
+
*[f"- {item}" for item in blocked],
|
|
610
|
+
"",
|
|
611
|
+
]
|
|
612
|
+
)
|
|
613
|
+
out_name = _child_prompt_filename(unit)
|
|
614
|
+
out_rel = f"{parent_rel}/child-prompts/{out_name}"
|
|
615
|
+
if not dry_run:
|
|
616
|
+
prompts_dir.mkdir(parents=True, exist_ok=True)
|
|
617
|
+
(prompts_dir / out_name).write_text(stub, encoding="utf-8")
|
|
618
|
+
written.append(out_rel)
|
|
619
|
+
pack_lines.append(
|
|
620
|
+
f"- `{unit}` — `{state}` — **blocked** — {', '.join(blocked)}"
|
|
621
|
+
)
|
|
622
|
+
pack_lines.append("")
|
|
623
|
+
|
|
624
|
+
pack_lines.extend(["## Ready", ""])
|
|
625
|
+
if ready_rows:
|
|
626
|
+
for sid, unit, path in ready_rows:
|
|
627
|
+
pack_lines.append(f"- `{sid}` / `{unit}` → `{path}`")
|
|
628
|
+
else:
|
|
629
|
+
pack_lines.append("(none)")
|
|
630
|
+
pack_lines.extend(["", "## Blocked", ""])
|
|
631
|
+
if blocked_rows:
|
|
632
|
+
for sid, unit, deps in blocked_rows:
|
|
633
|
+
pack_lines.append(f"- `{sid}` / `{unit}` — {', '.join(deps)}")
|
|
634
|
+
else:
|
|
635
|
+
pack_lines.append("(none)")
|
|
636
|
+
pack_lines.append("")
|
|
637
|
+
|
|
638
|
+
pack_body = "\n".join(pack_lines)
|
|
639
|
+
pack_rel = f"{parent_rel}/child-prompts/PACK.md"
|
|
640
|
+
if not dry_run:
|
|
641
|
+
prompts_dir.mkdir(parents=True, exist_ok=True)
|
|
642
|
+
(prompts_dir / "PACK.md").write_text(pack_body + "\n", encoding="utf-8")
|
|
643
|
+
written.append(pack_rel)
|
|
644
|
+
|
|
645
|
+
if errors:
|
|
646
|
+
return None, errors
|
|
647
|
+
|
|
648
|
+
summary = [
|
|
649
|
+
f"# publish-pack — `{parent_dir.name}`",
|
|
650
|
+
"",
|
|
651
|
+
f"- Parent: `{parent_rel}`",
|
|
652
|
+
f"- dry_run: {dry_run}",
|
|
653
|
+
f"- legacy_no_stages: {legacy}",
|
|
654
|
+
f"- ready: {len(ready_rows)}",
|
|
655
|
+
f"- blocked: {len(blocked_rows)}",
|
|
656
|
+
f"- pack_index: `{pack_rel}`",
|
|
657
|
+
"",
|
|
658
|
+
]
|
|
659
|
+
if written:
|
|
660
|
+
summary.append("## Written")
|
|
661
|
+
summary.append("")
|
|
662
|
+
for path in written:
|
|
663
|
+
summary.append(f"- `{path}`")
|
|
664
|
+
summary.append("")
|
|
665
|
+
elif dry_run:
|
|
666
|
+
summary.append("(dry-run: no files written)")
|
|
667
|
+
summary.append("")
|
|
668
|
+
summary.append(pack_body)
|
|
669
|
+
return "\n".join(summary), []
|
|
670
|
+
|
|
671
|
+
|
|
672
|
+
def build_parent_status_dict(parent_dir: Path) -> dict | None:
|
|
673
|
+
"""Machine-readable parent orchestration snapshot (campaign UI / MCP)."""
|
|
674
|
+
repo_root = get_repo_root()
|
|
675
|
+
parent_rel = _repo_rel(parent_dir, repo_root)
|
|
676
|
+
data, _ = load_task_map(parent_dir)
|
|
677
|
+
if data is None:
|
|
678
|
+
return None
|
|
679
|
+
|
|
680
|
+
children_by_id = _children_by_id(data)
|
|
681
|
+
stages, stage_errors, legacy = _normalize_stages(data)
|
|
682
|
+
_, body = load_task_map(parent_dir)
|
|
683
|
+
stale_pack = _compute_stale_pack(parent_dir, data, body, children_by_id)
|
|
684
|
+
newly_ready = _compute_newly_ready(children_by_id)
|
|
685
|
+
|
|
686
|
+
stage_rows: list[dict] = []
|
|
687
|
+
for stage in stages:
|
|
688
|
+
units: list[dict] = []
|
|
689
|
+
for unit in stage.get("units") or []:
|
|
690
|
+
entry = children_by_id.get(unit) or {}
|
|
691
|
+
readiness, blocked = _unit_readiness(unit, children_by_id)
|
|
692
|
+
units.append(
|
|
693
|
+
{
|
|
694
|
+
"id": unit,
|
|
695
|
+
"state": entry.get("state", "?"),
|
|
696
|
+
"readiness": readiness,
|
|
697
|
+
"blockedBy": blocked,
|
|
698
|
+
}
|
|
699
|
+
)
|
|
700
|
+
stage_rows.append(
|
|
701
|
+
{
|
|
702
|
+
"id": stage["id"],
|
|
703
|
+
"title": stage.get("title") or "",
|
|
704
|
+
"units": units,
|
|
705
|
+
}
|
|
706
|
+
)
|
|
707
|
+
|
|
708
|
+
children_rows: list[dict] = []
|
|
709
|
+
for child in data.get("children") or []:
|
|
710
|
+
if not isinstance(child, dict):
|
|
711
|
+
continue
|
|
712
|
+
cid = child.get("id")
|
|
713
|
+
if not isinstance(cid, str):
|
|
714
|
+
continue
|
|
715
|
+
child_dir = parent_dir.parent / cid
|
|
716
|
+
children_rows.append(
|
|
717
|
+
{
|
|
718
|
+
"id": cid,
|
|
719
|
+
"state": child.get("state", "?"),
|
|
720
|
+
"dependsOn": child.get("depends_on") or [],
|
|
721
|
+
"touches": child.get("touches") or [],
|
|
722
|
+
"isolation": child.get("isolation"),
|
|
723
|
+
"branch": child.get("branch"),
|
|
724
|
+
"worktreePath": child.get("worktree_path"),
|
|
725
|
+
"evidence": child.get("evidence"),
|
|
726
|
+
"ref": child.get("ref"),
|
|
727
|
+
"verifyMd": (child_dir / "verify.md").is_file(),
|
|
728
|
+
"handoffMd": (child_dir / "handoff.md").is_file(),
|
|
729
|
+
}
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
return {
|
|
733
|
+
"id": parent_dir.name,
|
|
734
|
+
"path": parent_rel,
|
|
735
|
+
"contractEpoch": data.get("contract_epoch"),
|
|
736
|
+
"executionTopology": data.get("execution_topology"),
|
|
737
|
+
"mergeLimit": data.get("merge_limit"),
|
|
738
|
+
"stages": stage_rows,
|
|
739
|
+
"children": children_rows,
|
|
740
|
+
"integrationQueue": data.get("integration_queue") or [],
|
|
741
|
+
"stageErrors": stage_errors,
|
|
742
|
+
"legacyStages": legacy,
|
|
743
|
+
"stalePack": stale_pack,
|
|
744
|
+
"newlyReady": newly_ready,
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
|
|
292
748
|
def build_parent_status(parent_dir: Path) -> str:
|
|
293
749
|
"""Render a parent task-map status summary for the reviewer."""
|
|
294
750
|
repo_root = get_repo_root()
|
|
@@ -305,10 +761,48 @@ def build_parent_status(parent_dir: Path) -> str:
|
|
|
305
761
|
f"- execution_topology: {data.get('execution_topology', '?')}",
|
|
306
762
|
f"- merge_limit: {data.get('merge_limit', '?')}",
|
|
307
763
|
"",
|
|
308
|
-
"## Children",
|
|
309
|
-
"",
|
|
310
764
|
]
|
|
311
765
|
|
|
766
|
+
children_by_id = _children_by_id(data)
|
|
767
|
+
stages, stage_errors, legacy = _normalize_stages(data)
|
|
768
|
+
_, body = load_task_map(parent_dir)
|
|
769
|
+
stale_pack = _compute_stale_pack(parent_dir, data, body, children_by_id)
|
|
770
|
+
newly_ready = _compute_newly_ready(children_by_id)
|
|
771
|
+
lines.append("## Pack freshness")
|
|
772
|
+
lines.append("")
|
|
773
|
+
lines.append(f"- stalePack: {stale_pack}")
|
|
774
|
+
if newly_ready:
|
|
775
|
+
lines.append(f"- newlyReady: {', '.join(newly_ready)}")
|
|
776
|
+
else:
|
|
777
|
+
lines.append("- newlyReady: (none)")
|
|
778
|
+
lines.append("")
|
|
779
|
+
lines.append("## Stages")
|
|
780
|
+
lines.append("")
|
|
781
|
+
if stage_errors:
|
|
782
|
+
for err in stage_errors:
|
|
783
|
+
lines.append(f"- error: {err}")
|
|
784
|
+
lines.append("")
|
|
785
|
+
elif legacy:
|
|
786
|
+
lines.append("stages: (none declared — implicit `default` for publish-pack)")
|
|
787
|
+
lines.append("")
|
|
788
|
+
else:
|
|
789
|
+
for stage in stages:
|
|
790
|
+
sid = stage["id"]
|
|
791
|
+
title = stage.get("title") or ""
|
|
792
|
+
heading = f"### `{sid}`" + (f" — {title}" if title else "")
|
|
793
|
+
lines.append(heading)
|
|
794
|
+
for unit in stage.get("units") or []:
|
|
795
|
+
entry = children_by_id.get(unit) or {}
|
|
796
|
+
state = entry.get("state", "?")
|
|
797
|
+
readiness, blocked = _unit_readiness(unit, children_by_id)
|
|
798
|
+
if readiness == "ready":
|
|
799
|
+
lines.append(f"- `{unit}` — `{state}` — ready")
|
|
800
|
+
else:
|
|
801
|
+
lines.append(f"- `{unit}` — `{state}` — blocked ({', '.join(blocked)})")
|
|
802
|
+
lines.append("")
|
|
803
|
+
|
|
804
|
+
lines.extend(["## Children", ""])
|
|
805
|
+
|
|
312
806
|
children = data.get("children") or []
|
|
313
807
|
if not children:
|
|
314
808
|
lines.append("(no children in task-map)")
|
|
@@ -354,6 +848,7 @@ def build_parent_status(parent_dir: Path) -> str:
|
|
|
354
848
|
lines.append("")
|
|
355
849
|
lines.append("```bash")
|
|
356
850
|
lines.append(f"python ./.cstl/scripts/task.py parent-status {parent_rel}")
|
|
851
|
+
lines.append(f"python ./.cstl/scripts/task.py publish-pack {parent_rel}")
|
|
357
852
|
lines.append(f"python ./.cstl/scripts/task.py generate-child-prompt {parent_rel} <child> --mode subagent")
|
|
358
853
|
lines.append(f"python ./.cstl/scripts/task.py generate-child-prompt {parent_rel} <child> --mode inline")
|
|
359
854
|
lines.append(f"python ./.cstl/scripts/task.py review-child {parent_rel} <child> --check")
|
|
@@ -451,7 +946,7 @@ def build_review_report(
|
|
|
451
946
|
notes: str | None,
|
|
452
947
|
) -> tuple[str, list[str], dict]:
|
|
453
948
|
"""Build review summary and validate integration transitions for a decision."""
|
|
454
|
-
child_name = child_dir
|
|
949
|
+
child_name = resolve_child_map_id(parent_data, child_dir, child_data)
|
|
455
950
|
summary = _summarize_handoff(child_dir)
|
|
456
951
|
errors: list[str] = []
|
|
457
952
|
actions: dict = {"integrate": None, "gates": []}
|
|
@@ -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
|
|