@blxzer/cursor-trellis 0.3.6 → 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 +23 -0
- package/README.md +6 -6
- 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 +39 -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/migrations/manifests/0.4.0.json +9 -0
- package/dist/templates/markdown/index.d.ts +6 -0
- package/dist/templates/markdown/index.d.ts.map +1 -1
- package/dist/templates/markdown/index.js +6 -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-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 +37 -0
- package/dist/templates/markdown/spec/guides/prototype-guide.md.txt +139 -0
- package/dist/templates/markdown/spec/guides/retrieval-daily-guide.md.txt +4 -0
- package/dist/templates/markdown/spec/guides/test-discipline-guide.md.txt +138 -0
- package/dist/templates/markdown/spec/guides/verification-strength-guide.md.txt +1 -0
- package/dist/templates/trellis/index.d.ts +12 -0
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +26 -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/pool_store.py +702 -0
- package/dist/templates/trellis/scripts/common/task_dashboard.py +8 -0
- 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_store.py +256 -0
- package/dist/templates/trellis/scripts/common/test_depends_mode_block.py +489 -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/pool.py +192 -0
- package/dist/templates/trellis/scripts/task.py +66 -1
- package/dist/templates/trellis/scripts/verify_evidence_probe.py +138 -0
- package/dist/templates/trellis/workflow.md +32 -2
- package/package.json +2 -2
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Verification evidence probe — repeatable false-green detection.
|
|
3
|
+
|
|
4
|
+
Evaluates a fixture verify.md against closeout-profile evidence requirements
|
|
5
|
+
by reusing task_gates.verify_evidence_status() (no duplicate rule set).
|
|
6
|
+
|
|
7
|
+
Exit 0 when actual outcome matches --expect; exit 1 on mismatch or error.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import io
|
|
14
|
+
import shutil
|
|
15
|
+
import sys
|
|
16
|
+
import tempfile
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
_SCRIPTS = Path(__file__).resolve().parent
|
|
20
|
+
if str(_SCRIPTS) not in sys.path:
|
|
21
|
+
sys.path.insert(0, str(_SCRIPTS))
|
|
22
|
+
|
|
23
|
+
from common.task_gates import verify_evidence_status # noqa: E402
|
|
24
|
+
|
|
25
|
+
if sys.platform.startswith("win"):
|
|
26
|
+
for _stream_name in ("stdin", "stdout", "stderr"):
|
|
27
|
+
_stream = getattr(sys, _stream_name, None)
|
|
28
|
+
if _stream is None and _stream_name != "stdin":
|
|
29
|
+
continue
|
|
30
|
+
if _stream is None:
|
|
31
|
+
continue
|
|
32
|
+
if hasattr(_stream, "reconfigure"):
|
|
33
|
+
try:
|
|
34
|
+
_stream.reconfigure(encoding="utf-8", errors="replace")
|
|
35
|
+
except Exception:
|
|
36
|
+
pass
|
|
37
|
+
elif hasattr(_stream, "detach"):
|
|
38
|
+
try:
|
|
39
|
+
setattr(
|
|
40
|
+
sys,
|
|
41
|
+
_stream_name,
|
|
42
|
+
io.TextIOWrapper(_stream.detach(), encoding="utf-8", errors="replace"),
|
|
43
|
+
)
|
|
44
|
+
except Exception:
|
|
45
|
+
pass
|
|
46
|
+
|
|
47
|
+
PROFILE_REQUIRED_SIGNALS: dict[str, list[str]] = {
|
|
48
|
+
"lite": ["validation", "acceptance", "durable_learning"],
|
|
49
|
+
"full": [
|
|
50
|
+
"validation",
|
|
51
|
+
"acceptance",
|
|
52
|
+
"durable_learning",
|
|
53
|
+
"check_evidence",
|
|
54
|
+
"reviewed_change_set",
|
|
55
|
+
],
|
|
56
|
+
"parent": [
|
|
57
|
+
"validation",
|
|
58
|
+
"acceptance",
|
|
59
|
+
"durable_learning",
|
|
60
|
+
"integration",
|
|
61
|
+
],
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def evaluate_fixture(fixture_path: Path, profile: str) -> tuple[bool, dict[str, bool], list[str]]:
|
|
66
|
+
"""Return (passes, status_map, missing_signals) for a fixture verify.md."""
|
|
67
|
+
profile = profile.lower().strip()
|
|
68
|
+
if profile not in PROFILE_REQUIRED_SIGNALS:
|
|
69
|
+
raise ValueError(f"unknown profile: {profile}")
|
|
70
|
+
|
|
71
|
+
required = PROFILE_REQUIRED_SIGNALS[profile]
|
|
72
|
+
task_data: dict = {"children": ["probe-child"] if profile == "parent" else []}
|
|
73
|
+
|
|
74
|
+
with tempfile.TemporaryDirectory(prefix="cstl-verify-probe-") as tmp:
|
|
75
|
+
task_dir = Path(tmp)
|
|
76
|
+
shutil.copy2(fixture_path, task_dir / "verify.md")
|
|
77
|
+
status = verify_evidence_status(task_dir, task_data)
|
|
78
|
+
|
|
79
|
+
missing = [signal for signal in required if not status.get(signal)]
|
|
80
|
+
return len(missing) == 0, status, missing
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def main(argv: list[str] | None = None) -> int:
|
|
84
|
+
parser = argparse.ArgumentParser(
|
|
85
|
+
description="Probe verify.md fixture against closeout evidence requirements.",
|
|
86
|
+
)
|
|
87
|
+
parser.add_argument(
|
|
88
|
+
"--fixture",
|
|
89
|
+
required=True,
|
|
90
|
+
type=Path,
|
|
91
|
+
help="Path to fixture verify.md",
|
|
92
|
+
)
|
|
93
|
+
parser.add_argument(
|
|
94
|
+
"--profile",
|
|
95
|
+
required=True,
|
|
96
|
+
choices=sorted(PROFILE_REQUIRED_SIGNALS),
|
|
97
|
+
help="Closeout profile to evaluate against",
|
|
98
|
+
)
|
|
99
|
+
parser.add_argument(
|
|
100
|
+
"--expect",
|
|
101
|
+
required=True,
|
|
102
|
+
choices=("pass", "fail"),
|
|
103
|
+
help="Expected probe outcome",
|
|
104
|
+
)
|
|
105
|
+
args = parser.parse_args(argv)
|
|
106
|
+
|
|
107
|
+
fixture = args.fixture.resolve()
|
|
108
|
+
if not fixture.is_file():
|
|
109
|
+
print(f"verify-evidence-probe: fixture not found: {fixture}", file=sys.stderr)
|
|
110
|
+
return 2
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
passes, status, missing = evaluate_fixture(fixture, args.profile)
|
|
114
|
+
except ValueError as exc:
|
|
115
|
+
print(f"verify-evidence-probe: {exc}", file=sys.stderr)
|
|
116
|
+
return 2
|
|
117
|
+
|
|
118
|
+
actual = "pass" if passes else "fail"
|
|
119
|
+
expected = args.expect.lower()
|
|
120
|
+
|
|
121
|
+
print(f"fixture: {fixture}")
|
|
122
|
+
print(f"profile: {args.profile}")
|
|
123
|
+
print(f"status: {status}")
|
|
124
|
+
if missing:
|
|
125
|
+
print(f"missing: {', '.join(missing)}")
|
|
126
|
+
print(f"actual: {actual}")
|
|
127
|
+
print(f"expect: {expected}")
|
|
128
|
+
|
|
129
|
+
if actual == expected:
|
|
130
|
+
print("result: OK")
|
|
131
|
+
return 0
|
|
132
|
+
|
|
133
|
+
print("result: MISMATCH", file=sys.stderr)
|
|
134
|
+
return 1
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
if __name__ == "__main__":
|
|
138
|
+
raise SystemExit(main())
|
|
@@ -188,6 +188,8 @@ Consent gate. After classifying into any mode that creates a task, ask the user
|
|
|
188
188
|
|
|
189
189
|
Selected-task continuity. When a `selected_task` already exists, do not rerun global classification on every follow-up; continue inside the selected task unless a strong conflict exists (explicit exit/switch/create language, out-of-scope request, different artifact/archive target, new independent deliverable, contract-changing request, or evidence pollution risk).
|
|
190
190
|
|
|
191
|
+
Review-pool boundary. In-session requests that are clear and directly actionable go straight to Lite / Full / Parent task creation and do **not** enter the pool (`.cstl/pool/`). Ideas, directions, gaps, or unformed thoughts go into the pool. Only `accepted` pool entries may be turned into tasks via `task.py create`. The pool is a candidate queue; a Task is a commitment. See `.cstl/pool/README.md` for the full state machine and role split. Pool entry ↔ task links and pool/plan validation are maintained via the pool CLI: `python3 ./.cstl/scripts/pool.py --help` (link/unlink/validate/plan-check/show). Choosing the next item: treat `plan.md` closed/mainline sections as authoritative → item status → `task.py list` → ask the user; landed items/mechanisms must leave fog (discipline in `.cstl/pool/README.md` 「下一项怎么选 + fog 卫生」).
|
|
192
|
+
|
|
191
193
|
### Task Ladder And Routing
|
|
192
194
|
|
|
193
195
|
Classify by risk and persistence, not raw effort size. A short change to durable framework semantics can require a Full Task; a long conversation can remain No Task when it leaves no durable project state.
|
|
@@ -254,7 +256,35 @@ Details: archived `06-15-child-phase3-task-ladder` → `research/task-ladder-ite
|
|
|
254
256
|
|
|
255
257
|
Use a parent task when one user request contains several independently verifiable deliverables. The parent task owns the source requirement set, the task map, cross-child acceptance criteria, and final integration review; it normally should not be the implementation target unless it also has direct work.
|
|
256
258
|
|
|
257
|
-
Use child tasks for deliverables that can be planned, implemented, checked, and archived independently.
|
|
259
|
+
Use child tasks for deliverables that can be planned, implemented, checked, and archived independently. Ordering between deliverables is an explicit declaration, not an implicit system: child-level ordering lives in the Parent `task-map.md` `children[].depends_on`, and task-level ordering in task.json `depends_on` (`task.py set-deps <task> <dep...>`). By default these declarations are soft hints only — the dashboard and `start-execution --check` show unmet / dangling / cyclic dependencies as warnings and do not block a transition (a task may opt into hard gates with `depends_mode: block`, see Task dependencies below); `depends_on` declares ordering, it does not schedule work. Keep each child's acceptance criteria testable on its own, and keep the Parent as the integration authority.
|
|
260
|
+
|
|
261
|
+
### Task dependencies (Plan A / Plan B)
|
|
262
|
+
|
|
263
|
+
**任务依赖(Plan A / Plan B)**:顺序须显式声明;默认(`warn`)仅 dashboard / `start-execution --check` 软提示(WARN),不挡门禁;可 opt-in `block` 在 mutation 点硬挡(见下);不是调度器,也不替代 Parent 集成权威。
|
|
264
|
+
|
|
265
|
+
Ordering between deliverables is declared, never inferred: **Child-level** ordering lives in the Parent `task-map.md` `children[].depends_on` (consumed by `generate-child-prompt` and Child readiness; satisfied when the dependency is `integrated` or `cancelled`); **task-level** ordering lives in task.json `depends_on` (consumed by the dashboard and `start-execution --check`; satisfied when the dependency is `completed` — archived tasks count — or `cancelled`). Declare a dependency in exactly one place (do not dual-write).
|
|
266
|
+
|
|
267
|
+
Write task-level dependencies only via `task.py set-deps <task> <dep...>` (it normalizes the list, warns on dangling refs, and clears with no args). A dependency declared on a task that is later deleted becomes a dangling warning; clear it with `set-deps` first. Fix cycles by reordering who depends on whom.
|
|
268
|
+
|
|
269
|
+
`depends_on` is advisory by default: the dashboard, `start-execution --check`/`--approved`, and `set-deps` print `[dependencies] WARN:` lines and do **not block** any transition unless the task opts into Plan B. A WARN does not mean you may not proceed. It is not a scheduler (no auto-start, no auto-unlock, no cascade), and it does not replace the Parent's integration authority (integration and archive gates do not read dependencies). Task-level dependencies are not injected into child prompts (`parent_orchestration.py` is unchanged).
|
|
270
|
+
|
|
271
|
+
**Plan B(opt-in block)**:`meta.depends_mode`(`warn`(默认)| `block` | `off`)经 `task.py set-depends-mode <dir> <mode>` 设置。`block` 时,未满足(NOT_SATISFIED)/ 悬空(missing)/ pool 未就绪(UNRESOLVED)/ 环(cycle)在 **mutation 点** 升为 error;已满足(含 `cancelled`)不挡。`off` 静音(无 WARN 无 error)。
|
|
272
|
+
|
|
273
|
+
- `start-execution --check`:**永不因 deps FAIL**(仍 WARN,block 时提示 would-fail-approved)。
|
|
274
|
+
- `start-execution --approved`:本任务 meta 为 `block` 且未传 `--ignore-deps` → deps 进 errors / FAIL。
|
|
275
|
+
- `set-child-state <p> <c> working`:**Child** 的 meta 为 `block` 且未传 `--ignore-deps` → 拒绝(合并 Child task.json `depends_on` 与 task-map `children[].depends_on`,任一边 blocking 即挡);其它 state 不查。
|
|
276
|
+
- `--ignore-deps`:挂于 `--approved` 与 `set-child-state working`;成功路径把事件(`command`/`by`/`mode`/`blocking_summary` 等)append 到 `meta.depends_ignore_events`(cap 20),start-execution 与 `execution_approval` 同一次写盘。
|
|
277
|
+
- archive / integrate-child / set-deps / dashboard 不挡;`parent_orchestration.py` 零改。
|
|
278
|
+
|
|
279
|
+
`pool:Pxx` refs require the pool item to be linked (`pool.py link`); satisfaction is all-linked-tasks-done, otherwise UNRESOLVED — see `.cstl/pool/README.md`.
|
|
280
|
+
|
|
281
|
+
#### Vertical slice grain (Parent children)
|
|
282
|
+
|
|
283
|
+
- Prefer **vertical slices**: each Child cuts through the layers needed for one demonstrable outcome (not a horizontal layer-batch across the whole feature).
|
|
284
|
+
- Grain: **independently demoable** + **finishable in one context window**.
|
|
285
|
+
- Wide shape changes (renames, dual-write APIs, schema moves): prefer **expand → migrate → contract** (ship coexistence first, migrate callers in batches, then delete the old shape). Do not big-bang cut over.
|
|
286
|
+
- Ordering between children: declare in the Parent `task-map.md` `children[].depends_on` (Child level) or in task.json `depends_on` via `task.py set-deps` (task level, cross-Parent allowed). General `depends_on` (Plan A) is landed: it is parsed and surfaced as warnings by the dashboard and `start-execution --check`, never as hard blocks by default; optional hard blocking (Plan B, `depends_mode: block` via `task.py set-depends-mode`) is landed for `start-execution --approved` and `set-child-state working` (`--ignore-deps` + audit events). `parent_orchestration.py` stays unchanged.
|
|
287
|
+
- Pool / plan blocking edges already use **item ids** only (see `.cstl/pool/plan.md` conventions). Terminology: Child / slice / deliverable — not ticket/issue.
|
|
258
288
|
|
|
259
289
|
Create new children with `task.py create "<title>" --slug <name> --parent <parent-dir>`. Link existing tasks with `task.py add-subtask <parent> <child>`, and unlink mistakes with `task.py remove-subtask <parent> <child>`.
|
|
260
290
|
|
|
@@ -447,7 +477,7 @@ When considering a parent/child split:
|
|
|
447
477
|
- Use a parent task when one request contains several independently verifiable deliverables.
|
|
448
478
|
- Parent tasks own source requirements, child-task mapping, cross-child acceptance criteria, and final integration review.
|
|
449
479
|
- Child tasks own actual deliverables that can be planned, implemented, checked, and archived independently.
|
|
450
|
-
-
|
|
480
|
+
- Ordering between children is explicit `depends_on` (Parent `task-map.md` child entries and/or task-level `task.py set-deps`), not an implicit Parent/child edge. See Task dependencies (Plan A / Plan B).
|
|
451
481
|
- Start the child task that owns the next deliverable. Do not start the parent unless the parent itself has direct implementation work.
|
|
452
482
|
|
|
453
483
|
Return to this step whenever requirements change and revise the relevant artifact.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blxzer/cursor-trellis",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "AI capabilities grow like ivy — Trellis provides the structure to guide them along a disciplined path",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"inquirer": "^9.3.7",
|
|
37
37
|
"undici": "^6.21.0",
|
|
38
38
|
"zod": "^4.4.2",
|
|
39
|
-
"@blxzer/cursor-trellis-core": "0.
|
|
39
|
+
"@blxzer/cursor-trellis-core": "0.4.0"
|
|
40
40
|
},
|
|
41
41
|
"devDependencies": {
|
|
42
42
|
"@eslint/js": "^9.18.0",
|