@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
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
#!/usr/bin/env
|
|
1
|
+
#!/usr/bin/env python
|
|
2
2
|
"""
|
|
3
3
|
Task CRUD operations.
|
|
4
4
|
|
|
@@ -9,6 +9,8 @@ Provides:
|
|
|
9
9
|
cmd_set_branch - Set git branch for task
|
|
10
10
|
cmd_set_base_branch - Set PR target branch
|
|
11
11
|
cmd_set_scope - Set scope for PR title
|
|
12
|
+
cmd_set_deps - Set task-level depends_on (declare + soft checks)
|
|
13
|
+
cmd_set_depends_mode - Set meta.depends_mode (warn | block | off)
|
|
12
14
|
cmd_add_subtask - Link child task to parent
|
|
13
15
|
cmd_remove_subtask - Unlink child task from parent
|
|
14
16
|
cmd_prepare_child_worktree - Create/register Child git worktree
|
|
@@ -25,6 +27,11 @@ import sys
|
|
|
25
27
|
from datetime import datetime
|
|
26
28
|
from pathlib import Path
|
|
27
29
|
|
|
30
|
+
from .artifact_locale import (
|
|
31
|
+
default_prd_content,
|
|
32
|
+
default_verify_content,
|
|
33
|
+
resolve_artifact_locale,
|
|
34
|
+
)
|
|
28
35
|
from .config import (
|
|
29
36
|
get_packages,
|
|
30
37
|
get_session_auto_commit,
|
|
@@ -67,6 +74,7 @@ from .task_map import (
|
|
|
67
74
|
get_child_state,
|
|
68
75
|
record_child_worktree,
|
|
69
76
|
remove_child_from_task_map,
|
|
77
|
+
resolve_child_map_id,
|
|
70
78
|
set_child_state,
|
|
71
79
|
set_parent_child_integration_state,
|
|
72
80
|
validate_parent_child_integration,
|
|
@@ -249,11 +257,10 @@ _SUBAGENT_CONFIG_DIRS: tuple[str, ...] = (
|
|
|
249
257
|
".pi", # Pi Agent
|
|
250
258
|
)
|
|
251
259
|
|
|
252
|
-
|
|
253
|
-
"
|
|
254
|
-
"
|
|
255
|
-
"
|
|
256
|
-
"Delete this line once real entries are added."
|
|
260
|
+
_STABLE_SPEC_SEED_PATHS: tuple[str, ...] = (
|
|
261
|
+
".cstl/spec/guides/index.md",
|
|
262
|
+
".cstl/spec/guides/verification-strength-guide.md",
|
|
263
|
+
".cstl/spec/guides/injection-budget-guide.md",
|
|
257
264
|
)
|
|
258
265
|
|
|
259
266
|
|
|
@@ -270,41 +277,70 @@ def _has_subagent_platform(repo_root: Path) -> bool:
|
|
|
270
277
|
return False
|
|
271
278
|
|
|
272
279
|
|
|
273
|
-
def
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
280
|
+
def _resolve_seed_spec_paths(
|
|
281
|
+
repo_root: Path,
|
|
282
|
+
task_dir: Path,
|
|
283
|
+
task_data: dict,
|
|
284
|
+
) -> list[str]:
|
|
285
|
+
"""Return 2–3 existing spec file paths for jsonl create seeds."""
|
|
286
|
+
from .task_gates import suggest_spec_targets
|
|
287
|
+
|
|
288
|
+
candidates: list[str] = []
|
|
289
|
+
for path in suggest_spec_targets(repo_root, task_dir, task_data):
|
|
290
|
+
if path not in candidates:
|
|
291
|
+
candidates.append(path)
|
|
292
|
+
for rel in _STABLE_SPEC_SEED_PATHS:
|
|
293
|
+
if rel not in candidates:
|
|
294
|
+
candidates.append(rel)
|
|
295
|
+
|
|
296
|
+
existing = [rel for rel in candidates if (repo_root / rel).is_file()]
|
|
297
|
+
if len(existing) >= 2:
|
|
298
|
+
return existing[:3]
|
|
299
|
+
|
|
300
|
+
fallback = [
|
|
301
|
+
rel for rel in _STABLE_SPEC_SEED_PATHS if (repo_root / rel).is_file()
|
|
302
|
+
]
|
|
303
|
+
return fallback[:3]
|
|
295
304
|
|
|
296
|
-
- TBD
|
|
297
305
|
|
|
298
|
-
|
|
306
|
+
def _write_seed_jsonl(
|
|
307
|
+
path: Path,
|
|
308
|
+
repo_root: Path,
|
|
309
|
+
task_dir: Path,
|
|
310
|
+
task_data: dict,
|
|
311
|
+
) -> None:
|
|
312
|
+
"""Write 2–3 JSONL rows with real spec ``file`` paths for sub-agent context."""
|
|
313
|
+
spec_paths = _resolve_seed_spec_paths(repo_root, task_dir, task_data)
|
|
314
|
+
if len(spec_paths) < 2:
|
|
315
|
+
raise ValueError(
|
|
316
|
+
"task create requires at least two existing spec guide paths for jsonl seed"
|
|
317
|
+
)
|
|
299
318
|
|
|
300
|
-
|
|
319
|
+
lines: list[str] = []
|
|
320
|
+
for spec_path in spec_paths:
|
|
321
|
+
lines.append(
|
|
322
|
+
json.dumps(
|
|
323
|
+
{
|
|
324
|
+
"file": spec_path,
|
|
325
|
+
"reason": "default spec seed — curate entries for this task",
|
|
326
|
+
},
|
|
327
|
+
ensure_ascii=False,
|
|
328
|
+
)
|
|
329
|
+
)
|
|
330
|
+
path.write_text("\n".join(lines) + "\n", encoding="utf-8")
|
|
301
331
|
|
|
302
|
-
## Notes
|
|
303
332
|
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
333
|
+
def _default_prd_content(
|
|
334
|
+
title: str,
|
|
335
|
+
description: str | None = None,
|
|
336
|
+
*,
|
|
337
|
+
repo_root: Path | None = None,
|
|
338
|
+
task_dir: Path | None = None,
|
|
339
|
+
) -> str:
|
|
340
|
+
"""Return the default PRD skeleton created with every task."""
|
|
341
|
+
root = repo_root or get_repo_root()
|
|
342
|
+
locale = resolve_artifact_locale(task_dir, root)
|
|
343
|
+
return default_prd_content(title, description, locale, root)
|
|
308
344
|
|
|
309
345
|
|
|
310
346
|
# =============================================================================
|
|
@@ -412,7 +448,14 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
412
448
|
prd_path = task_dir / "prd.md"
|
|
413
449
|
if not prd_path.exists():
|
|
414
450
|
prd_path.write_text(
|
|
415
|
-
_default_prd_content(args.title, args.description),
|
|
451
|
+
_default_prd_content(args.title, args.description, repo_root=repo_root, task_dir=task_dir),
|
|
452
|
+
encoding="utf-8",
|
|
453
|
+
)
|
|
454
|
+
|
|
455
|
+
verify_path = task_dir / "verify.md"
|
|
456
|
+
if not verify_path.exists():
|
|
457
|
+
verify_path.write_text(
|
|
458
|
+
default_verify_content(repo_root=repo_root, task_dir=task_dir),
|
|
416
459
|
encoding="utf-8",
|
|
417
460
|
)
|
|
418
461
|
|
|
@@ -425,7 +468,7 @@ def cmd_create(args: argparse.Namespace) -> int:
|
|
|
425
468
|
for jsonl_name in ("implement.jsonl", "check.jsonl"):
|
|
426
469
|
jsonl_path = task_dir / jsonl_name
|
|
427
470
|
if not jsonl_path.exists():
|
|
428
|
-
_write_seed_jsonl(jsonl_path)
|
|
471
|
+
_write_seed_jsonl(jsonl_path, repo_root, task_dir, task_data)
|
|
429
472
|
seeded_jsonl = True
|
|
430
473
|
|
|
431
474
|
# Handle --parent: establish bidirectional link
|
|
@@ -1086,6 +1129,129 @@ def cmd_prepare_child_worktree(args: argparse.Namespace) -> int:
|
|
|
1086
1129
|
return 0
|
|
1087
1130
|
|
|
1088
1131
|
|
|
1132
|
+
# =============================================================================
|
|
1133
|
+
# Plan B: depends_mode block helpers
|
|
1134
|
+
# =============================================================================
|
|
1135
|
+
|
|
1136
|
+
def append_depends_ignore_event(
|
|
1137
|
+
task_data: dict,
|
|
1138
|
+
*,
|
|
1139
|
+
command: str,
|
|
1140
|
+
mode: str,
|
|
1141
|
+
blocking_summary: list[str],
|
|
1142
|
+
evidence: str | None = None,
|
|
1143
|
+
reason: str | None = None,
|
|
1144
|
+
) -> None:
|
|
1145
|
+
"""Append a depends-ignore audit event to meta.depends_ignore_events.
|
|
1146
|
+
|
|
1147
|
+
Mutates task_data in memory; the caller persists it in the SAME
|
|
1148
|
+
write_json as the mutation it accompanies (execution approval / child
|
|
1149
|
+
state), so the event can never dangle as a separate stale write. The
|
|
1150
|
+
list is capped at MAX_IGNORE_EVENTS (FIFO drops the oldest entry).
|
|
1151
|
+
"""
|
|
1152
|
+
from .task_dependencies import MAX_IGNORE_EVENTS
|
|
1153
|
+
from .task_map import utc_now
|
|
1154
|
+
|
|
1155
|
+
meta = task_data.get("meta")
|
|
1156
|
+
if not isinstance(meta, dict):
|
|
1157
|
+
meta = {}
|
|
1158
|
+
task_data["meta"] = meta
|
|
1159
|
+
events = meta.get("depends_ignore_events")
|
|
1160
|
+
if not isinstance(events, list):
|
|
1161
|
+
events = []
|
|
1162
|
+
event: dict = {
|
|
1163
|
+
"at": utc_now(),
|
|
1164
|
+
"command": command,
|
|
1165
|
+
"by": "user",
|
|
1166
|
+
"mode": mode,
|
|
1167
|
+
"blocking_summary": list(blocking_summary),
|
|
1168
|
+
}
|
|
1169
|
+
if evidence:
|
|
1170
|
+
event["evidence"] = evidence
|
|
1171
|
+
if reason:
|
|
1172
|
+
event["reason"] = reason
|
|
1173
|
+
events.append(event)
|
|
1174
|
+
del events[:-MAX_IGNORE_EVENTS]
|
|
1175
|
+
meta["depends_ignore_events"] = events
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
def _guard_child_working_deps(
|
|
1179
|
+
args: argparse.Namespace,
|
|
1180
|
+
parent_dir: Path,
|
|
1181
|
+
parent_data: dict,
|
|
1182
|
+
child_dir: Path,
|
|
1183
|
+
child_data: dict,
|
|
1184
|
+
child_map_id: str,
|
|
1185
|
+
) -> int:
|
|
1186
|
+
"""Enforce depends_mode=block before a Child transitions to `working`.
|
|
1187
|
+
|
|
1188
|
+
Reads the CHILD's meta.depends_mode (the Parent mode never proxies for
|
|
1189
|
+
the Child). The dependency union covers the Child task.json depends_on
|
|
1190
|
+
plus the Parent task-map children[].depends_on entry; either source
|
|
1191
|
+
blocking rejects the transition unless --ignore-deps is passed, which
|
|
1192
|
+
appends an audit event to the Child task.json and a line to the Parent
|
|
1193
|
+
task-map Event Log. Returns 0 to allow, nonzero to reject.
|
|
1194
|
+
"""
|
|
1195
|
+
from .task_dependencies import describe_child_dependencies, read_depends_mode
|
|
1196
|
+
from .task_map import get_child_entry, load_task_map, write_task_map
|
|
1197
|
+
|
|
1198
|
+
mode = read_depends_mode(child_data)
|
|
1199
|
+
if mode != "block":
|
|
1200
|
+
return 0
|
|
1201
|
+
|
|
1202
|
+
repo_root = get_repo_root()
|
|
1203
|
+
map_data, _ = load_task_map(parent_dir)
|
|
1204
|
+
entry = get_child_entry(map_data, child_map_id)
|
|
1205
|
+
report = describe_child_dependencies(
|
|
1206
|
+
child_dir, child_data, entry, repo_root=repo_root
|
|
1207
|
+
)
|
|
1208
|
+
blocking = report.blocking_errors()
|
|
1209
|
+
if not blocking:
|
|
1210
|
+
return 0
|
|
1211
|
+
|
|
1212
|
+
if not getattr(args, "ignore_deps", False):
|
|
1213
|
+
print(
|
|
1214
|
+
colored(
|
|
1215
|
+
"Error: cannot set Child state to 'working': blocking dependencies "
|
|
1216
|
+
"(depends_mode=block).",
|
|
1217
|
+
Colors.RED,
|
|
1218
|
+
),
|
|
1219
|
+
file=sys.stderr,
|
|
1220
|
+
)
|
|
1221
|
+
for item in blocking:
|
|
1222
|
+
print(f" - {item}", file=sys.stderr)
|
|
1223
|
+
print(
|
|
1224
|
+
"Run with --ignore-deps to override (audited via meta.depends_ignore_events).",
|
|
1225
|
+
file=sys.stderr,
|
|
1226
|
+
)
|
|
1227
|
+
return 1
|
|
1228
|
+
|
|
1229
|
+
append_depends_ignore_event(
|
|
1230
|
+
child_data,
|
|
1231
|
+
command="set-child-state",
|
|
1232
|
+
mode=mode,
|
|
1233
|
+
blocking_summary=blocking,
|
|
1234
|
+
evidence=args.evidence,
|
|
1235
|
+
reason=getattr(args, "reason", None),
|
|
1236
|
+
)
|
|
1237
|
+
child_json_path = child_dir / FILE_TASK_JSON
|
|
1238
|
+
if not write_json(child_json_path, child_data):
|
|
1239
|
+
print(
|
|
1240
|
+
colored("Error: failed to write task.json (ignore event)", Colors.RED),
|
|
1241
|
+
file=sys.stderr,
|
|
1242
|
+
)
|
|
1243
|
+
return 1
|
|
1244
|
+
|
|
1245
|
+
map_data_evt, map_body = load_task_map(parent_dir)
|
|
1246
|
+
if map_data_evt is not None:
|
|
1247
|
+
event = (
|
|
1248
|
+
f"Ignored dependencies (mode=block) for child `{child_dir.name}` "
|
|
1249
|
+
f"while setting state `working`: {'; '.join(blocking)}"
|
|
1250
|
+
)
|
|
1251
|
+
write_task_map(parent_dir, map_data_evt, map_body, event)
|
|
1252
|
+
return 0
|
|
1253
|
+
|
|
1254
|
+
|
|
1089
1255
|
# =============================================================================
|
|
1090
1256
|
# Command: set-child-state
|
|
1091
1257
|
# =============================================================================
|
|
@@ -1136,10 +1302,23 @@ def cmd_set_child_state(args: argparse.Namespace) -> int:
|
|
|
1136
1302
|
)
|
|
1137
1303
|
return 1
|
|
1138
1304
|
|
|
1305
|
+
child_map_id = resolve_child_map_id(parent_data, child_dir, child_data)
|
|
1306
|
+
if state == "working":
|
|
1307
|
+
guard_rc = _guard_child_working_deps(
|
|
1308
|
+
args,
|
|
1309
|
+
parent_dir,
|
|
1310
|
+
parent_data,
|
|
1311
|
+
child_dir,
|
|
1312
|
+
child_data,
|
|
1313
|
+
child_map_id,
|
|
1314
|
+
)
|
|
1315
|
+
if guard_rc != 0:
|
|
1316
|
+
return guard_rc
|
|
1317
|
+
|
|
1139
1318
|
ok, errors = set_child_state(
|
|
1140
1319
|
parent_dir,
|
|
1141
1320
|
parent_data,
|
|
1142
|
-
|
|
1321
|
+
child_map_id,
|
|
1143
1322
|
state,
|
|
1144
1323
|
args.evidence,
|
|
1145
1324
|
getattr(args, "reason", None),
|
|
@@ -1265,6 +1444,29 @@ def cmd_integrate_child(args: argparse.Namespace) -> int:
|
|
|
1265
1444
|
if merge_ref:
|
|
1266
1445
|
print(f"Merge executed: git merge --no-ff --no-commit {merge_ref}")
|
|
1267
1446
|
print(f"Parent map: {_repo_relative_path(parent_dir / 'task-map.md', repo_root)}")
|
|
1447
|
+
|
|
1448
|
+
if state == "integrated" and not getattr(args, "no_publish_pack", False):
|
|
1449
|
+
from .parent_orchestration import build_publish_pack
|
|
1450
|
+
|
|
1451
|
+
summary, pack_errors = build_publish_pack(parent_dir)
|
|
1452
|
+
if pack_errors or summary is None:
|
|
1453
|
+
for item in pack_errors:
|
|
1454
|
+
print(f" - {item}", file=sys.stderr)
|
|
1455
|
+
print(
|
|
1456
|
+
colored(
|
|
1457
|
+
"Warning: integrate succeeded but publish-pack failed; "
|
|
1458
|
+
"PACK may be stale (see parent-status stalePack).",
|
|
1459
|
+
Colors.YELLOW,
|
|
1460
|
+
),
|
|
1461
|
+
file=sys.stderr,
|
|
1462
|
+
)
|
|
1463
|
+
return 2
|
|
1464
|
+
print(colored("✓ PACK refreshed after integrate", Colors.GREEN))
|
|
1465
|
+
for line in summary.splitlines():
|
|
1466
|
+
if line.startswith("- Generated:"):
|
|
1467
|
+
print(line)
|
|
1468
|
+
break
|
|
1469
|
+
|
|
1268
1470
|
return 0
|
|
1269
1471
|
|
|
1270
1472
|
|
|
@@ -1638,3 +1840,162 @@ def cmd_set_scope(args: argparse.Namespace) -> int:
|
|
|
1638
1840
|
|
|
1639
1841
|
print(colored(f"✓ Scope set to: {scope}", Colors.GREEN))
|
|
1640
1842
|
return 0
|
|
1843
|
+
|
|
1844
|
+
|
|
1845
|
+
# =============================================================================
|
|
1846
|
+
# Command: set-deps
|
|
1847
|
+
# =============================================================================
|
|
1848
|
+
|
|
1849
|
+
def cmd_set_deps(args: argparse.Namespace) -> int:
|
|
1850
|
+
"""Set task-level depends_on (Plan A: declare + soft checks, no blocking).
|
|
1851
|
+
|
|
1852
|
+
Bare task ids and `pool:Pxx` refs are supported. Dangling references are
|
|
1853
|
+
warned but still written (Plan A is discovery-first; `warn` never errors).
|
|
1854
|
+
"""
|
|
1855
|
+
from .task_dependencies import KIND_MISSING, normalize_dep_list, resolve_dep_ref
|
|
1856
|
+
|
|
1857
|
+
repo_root = get_repo_root()
|
|
1858
|
+
target_dir = resolve_task_dir(args.dir, repo_root)
|
|
1859
|
+
|
|
1860
|
+
task_json = target_dir / FILE_TASK_JSON
|
|
1861
|
+
if not task_json.is_file():
|
|
1862
|
+
print(
|
|
1863
|
+
colored(f"Error: task.json not found at {target_dir}", Colors.RED),
|
|
1864
|
+
file=sys.stderr,
|
|
1865
|
+
)
|
|
1866
|
+
return 1
|
|
1867
|
+
|
|
1868
|
+
data = read_json(task_json)
|
|
1869
|
+
if not data:
|
|
1870
|
+
return 1
|
|
1871
|
+
|
|
1872
|
+
normalized = normalize_dep_list(args.dep)
|
|
1873
|
+
data["depends_on"] = normalized
|
|
1874
|
+
|
|
1875
|
+
dangling = [
|
|
1876
|
+
ref
|
|
1877
|
+
for ref in normalized
|
|
1878
|
+
if resolve_dep_ref(ref, repo_root=repo_root).kind == KIND_MISSING
|
|
1879
|
+
]
|
|
1880
|
+
|
|
1881
|
+
if not write_json(task_json, data):
|
|
1882
|
+
print(colored("Error: failed to write task.json", Colors.RED), file=sys.stderr)
|
|
1883
|
+
return 1
|
|
1884
|
+
|
|
1885
|
+
if normalized:
|
|
1886
|
+
print(
|
|
1887
|
+
colored(
|
|
1888
|
+
f"✓ depends_on set: {', '.join(normalized)}",
|
|
1889
|
+
Colors.GREEN,
|
|
1890
|
+
)
|
|
1891
|
+
)
|
|
1892
|
+
else:
|
|
1893
|
+
print(colored("✓ depends_on cleared to []", Colors.GREEN))
|
|
1894
|
+
for ref in dangling:
|
|
1895
|
+
print(
|
|
1896
|
+
colored(
|
|
1897
|
+
f"[dependencies] WARN: dangling dependency: {ref} "
|
|
1898
|
+
"(no matching task or child; declaration kept)",
|
|
1899
|
+
Colors.YELLOW,
|
|
1900
|
+
),
|
|
1901
|
+
file=sys.stderr,
|
|
1902
|
+
)
|
|
1903
|
+
return 0
|
|
1904
|
+
|
|
1905
|
+
|
|
1906
|
+
# =============================================================================
|
|
1907
|
+
# Command: set-depends-mode
|
|
1908
|
+
# =============================================================================
|
|
1909
|
+
|
|
1910
|
+
def cmd_set_depends_mode(args: argparse.Namespace) -> int:
|
|
1911
|
+
"""Set meta.depends_mode (warn | block | off) for a task.
|
|
1912
|
+
|
|
1913
|
+
warn is the default (= Plan A soft checks); block opts the task into
|
|
1914
|
+
hard dependency gates at start-execution --approved and
|
|
1915
|
+
set-child-state working; off silences dependency output entirely.
|
|
1916
|
+
"""
|
|
1917
|
+
from .task_dependencies import DEPENDS_MODES, read_depends_mode
|
|
1918
|
+
|
|
1919
|
+
repo_root = get_repo_root()
|
|
1920
|
+
target_dir = resolve_task_dir(args.dir, repo_root)
|
|
1921
|
+
|
|
1922
|
+
task_json = target_dir / FILE_TASK_JSON
|
|
1923
|
+
if not task_json.is_file():
|
|
1924
|
+
print(
|
|
1925
|
+
colored(f"Error: task.json not found at {target_dir}", Colors.RED),
|
|
1926
|
+
file=sys.stderr,
|
|
1927
|
+
)
|
|
1928
|
+
return 1
|
|
1929
|
+
|
|
1930
|
+
data = read_json(task_json)
|
|
1931
|
+
if not data:
|
|
1932
|
+
return 1
|
|
1933
|
+
|
|
1934
|
+
previous = read_depends_mode(data)
|
|
1935
|
+
if args.mode not in DEPENDS_MODES:
|
|
1936
|
+
print(
|
|
1937
|
+
colored(
|
|
1938
|
+
f"Error: depends_mode must be one of {', '.join(DEPENDS_MODES)}",
|
|
1939
|
+
Colors.RED,
|
|
1940
|
+
),
|
|
1941
|
+
file=sys.stderr,
|
|
1942
|
+
)
|
|
1943
|
+
return 1
|
|
1944
|
+
|
|
1945
|
+
meta = data.get("meta")
|
|
1946
|
+
if not isinstance(meta, dict):
|
|
1947
|
+
meta = {}
|
|
1948
|
+
data["meta"] = meta
|
|
1949
|
+
meta["depends_mode"] = args.mode
|
|
1950
|
+
|
|
1951
|
+
if not write_json(task_json, data):
|
|
1952
|
+
print(colored("Error: failed to write task.json", Colors.RED), file=sys.stderr)
|
|
1953
|
+
return 1
|
|
1954
|
+
|
|
1955
|
+
print(
|
|
1956
|
+
colored(
|
|
1957
|
+
f"✓ depends_mode: {previous} -> {args.mode}",
|
|
1958
|
+
Colors.GREEN,
|
|
1959
|
+
)
|
|
1960
|
+
)
|
|
1961
|
+
return 0
|
|
1962
|
+
|
|
1963
|
+
|
|
1964
|
+
# =============================================================================
|
|
1965
|
+
# Command: artifact-locale
|
|
1966
|
+
# =============================================================================
|
|
1967
|
+
|
|
1968
|
+
def cmd_artifact_locale(args: argparse.Namespace) -> int:
|
|
1969
|
+
"""Get or set human-reviewed artifact locale (zh | en)."""
|
|
1970
|
+
from .artifact_locale import (
|
|
1971
|
+
resolve_artifact_locale,
|
|
1972
|
+
set_task_artifact_locale,
|
|
1973
|
+
set_workspace_artifact_locale,
|
|
1974
|
+
)
|
|
1975
|
+
|
|
1976
|
+
repo_root = get_repo_root()
|
|
1977
|
+
subcommand = getattr(args, "artifact_locale_command", None)
|
|
1978
|
+
|
|
1979
|
+
if subcommand == "get":
|
|
1980
|
+
task_dir = resolve_task_dir(args.task, repo_root) if args.task else None
|
|
1981
|
+
print(resolve_artifact_locale(task_dir, repo_root))
|
|
1982
|
+
return 0
|
|
1983
|
+
|
|
1984
|
+
if subcommand == "set":
|
|
1985
|
+
try:
|
|
1986
|
+
if args.task:
|
|
1987
|
+
task_dir = resolve_task_dir(args.task, repo_root)
|
|
1988
|
+
set_task_artifact_locale(task_dir, args.locale, repo_root)
|
|
1989
|
+
scope = f"task {task_dir.name}"
|
|
1990
|
+
else:
|
|
1991
|
+
set_workspace_artifact_locale(args.locale, repo_root)
|
|
1992
|
+
scope = "workspace"
|
|
1993
|
+
except (ValueError, FileNotFoundError) as exc:
|
|
1994
|
+
print(colored(f"Error: {exc}", Colors.RED), file=sys.stderr)
|
|
1995
|
+
return 1
|
|
1996
|
+
|
|
1997
|
+
print(colored(f"✓ artifact_locale set to {args.locale} ({scope})", Colors.GREEN))
|
|
1998
|
+
return 0
|
|
1999
|
+
|
|
2000
|
+
print(colored("Error: use artifact-locale get|set", Colors.RED), file=sys.stderr)
|
|
2001
|
+
return 1
|