@mindfoldhq/trellis 0.6.0-beta.10 → 0.6.0-beta.12
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/dist/commands/channel/create.d.ts +5 -0
- package/dist/commands/channel/create.d.ts.map +1 -1
- package/dist/commands/channel/create.js +19 -15
- package/dist/commands/channel/create.js.map +1 -1
- package/dist/commands/channel/index.d.ts.map +1 -1
- package/dist/commands/channel/index.js +90 -9
- package/dist/commands/channel/index.js.map +1 -1
- package/dist/commands/channel/kill.d.ts +1 -0
- package/dist/commands/channel/kill.d.ts.map +1 -1
- package/dist/commands/channel/kill.js +16 -13
- package/dist/commands/channel/kill.js.map +1 -1
- package/dist/commands/channel/list.d.ts +1 -0
- package/dist/commands/channel/list.d.ts.map +1 -1
- package/dist/commands/channel/list.js +18 -6
- package/dist/commands/channel/list.js.map +1 -1
- package/dist/commands/channel/messages.d.ts +3 -0
- package/dist/commands/channel/messages.d.ts.map +1 -1
- package/dist/commands/channel/messages.js +88 -58
- package/dist/commands/channel/messages.js.map +1 -1
- package/dist/commands/channel/rm.d.ts +2 -0
- package/dist/commands/channel/rm.d.ts.map +1 -1
- package/dist/commands/channel/rm.js +15 -5
- package/dist/commands/channel/rm.js.map +1 -1
- package/dist/commands/channel/run.js +1 -1
- package/dist/commands/channel/run.js.map +1 -1
- package/dist/commands/channel/send.d.ts +2 -0
- package/dist/commands/channel/send.d.ts.map +1 -1
- package/dist/commands/channel/send.js +9 -10
- package/dist/commands/channel/send.js.map +1 -1
- package/dist/commands/channel/spawn.d.ts +1 -0
- package/dist/commands/channel/spawn.d.ts.map +1 -1
- package/dist/commands/channel/spawn.js +14 -11
- package/dist/commands/channel/spawn.js.map +1 -1
- package/dist/commands/channel/store/events.d.ts +61 -6
- package/dist/commands/channel/store/events.d.ts.map +1 -1
- package/dist/commands/channel/store/events.js +53 -9
- package/dist/commands/channel/store/events.js.map +1 -1
- package/dist/commands/channel/store/filter.d.ts +33 -0
- package/dist/commands/channel/store/filter.d.ts.map +1 -0
- package/dist/commands/channel/store/filter.js +63 -0
- package/dist/commands/channel/store/filter.js.map +1 -0
- package/dist/commands/channel/store/paths.d.ts +7 -0
- package/dist/commands/channel/store/paths.d.ts.map +1 -1
- package/dist/commands/channel/store/paths.js +76 -17
- package/dist/commands/channel/store/paths.js.map +1 -1
- package/dist/commands/channel/store/schema.d.ts +36 -0
- package/dist/commands/channel/store/schema.d.ts.map +1 -0
- package/dist/commands/channel/store/schema.js +91 -0
- package/dist/commands/channel/store/schema.js.map +1 -0
- package/dist/commands/channel/store/thread-state.d.ts +19 -0
- package/dist/commands/channel/store/thread-state.d.ts.map +1 -0
- package/dist/commands/channel/store/thread-state.js +81 -0
- package/dist/commands/channel/store/thread-state.js.map +1 -0
- package/dist/commands/channel/store/watch.d.ts +4 -21
- package/dist/commands/channel/store/watch.d.ts.map +1 -1
- package/dist/commands/channel/store/watch.js +8 -54
- package/dist/commands/channel/store/watch.js.map +1 -1
- package/dist/commands/channel/supervisor.d.ts +1 -1
- package/dist/commands/channel/supervisor.d.ts.map +1 -1
- package/dist/commands/channel/supervisor.js +11 -10
- package/dist/commands/channel/supervisor.js.map +1 -1
- package/dist/commands/channel/threads.d.ts +28 -0
- package/dist/commands/channel/threads.d.ts.map +1 -0
- package/dist/commands/channel/threads.js +96 -0
- package/dist/commands/channel/threads.js.map +1 -0
- package/dist/commands/channel/wait.d.ts +3 -0
- package/dist/commands/channel/wait.d.ts.map +1 -1
- package/dist/commands/channel/wait.js +9 -8
- package/dist/commands/channel/wait.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +70 -41
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/uninstall.d.ts.map +1 -1
- package/dist/commands/uninstall.js +28 -2
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +21 -1
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/claude.d.ts.map +1 -1
- package/dist/configurators/claude.js +1 -0
- package/dist/configurators/claude.js.map +1 -1
- package/dist/migrations/manifests/0.5.14.json +9 -0
- package/dist/migrations/manifests/0.5.15.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.11.json +9 -0
- package/dist/migrations/manifests/0.6.0-beta.12.json +9 -0
- package/dist/templates/codex/hooks/session-start.py +22 -0
- package/dist/templates/codex/hooks.json +1 -1
- package/dist/templates/copilot/hooks/session-start.py +24 -0
- package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md.txt +128 -9
- package/dist/templates/markdown/spec/guides/cross-layer-thinking-guide.md.txt +62 -0
- package/dist/templates/markdown/spec/guides/index.md.txt +18 -0
- package/dist/templates/shared-hooks/inject-workflow-state.py +22 -0
- package/dist/templates/shared-hooks/session-start.py +19 -6
- package/dist/templates/trellis/scripts/common/safe_commit.py +49 -19
- package/dist/templates/trellis/scripts/common/task_store.py +42 -12
- package/dist/utils/cwd-guard.d.ts +38 -0
- package/dist/utils/cwd-guard.d.ts.map +1 -0
- package/dist/utils/cwd-guard.js +62 -0
- package/dist/utils/cwd-guard.js.map +1 -0
- package/dist/utils/file-writer.d.ts +13 -0
- package/dist/utils/file-writer.d.ts.map +1 -1
- package/dist/utils/file-writer.js +59 -1
- package/dist/utils/file-writer.js.map +1 -1
- package/dist/utils/manifest-prune.d.ts +61 -0
- package/dist/utils/manifest-prune.d.ts.map +1 -0
- package/dist/utils/manifest-prune.js +136 -0
- package/dist/utils/manifest-prune.js.map +1 -0
- package/dist/utils/template-hash.d.ts +32 -6
- package/dist/utils/template-hash.d.ts.map +1 -1
- package/dist/utils/template-hash.js +53 -31
- package/dist/utils/template-hash.js.map +1 -1
- package/package.json +1 -1
|
@@ -71,6 +71,35 @@ For each boundary:
|
|
|
71
71
|
|
|
72
72
|
**Good**: Each layer only knows its neighbors
|
|
73
73
|
|
|
74
|
+
### Mistake 4: Every Consumer Parses The Same Payload
|
|
75
|
+
|
|
76
|
+
**Bad**: A command reads JSONL events and casts fields inline:
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
const thread = (ev as { thread?: string }).thread;
|
|
80
|
+
const labels = (ev as { labels?: string[] }).labels;
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
This looks local, but it means every consumer owns a private version of the
|
|
84
|
+
event contract. The next field change will update one command and miss another.
|
|
85
|
+
|
|
86
|
+
**Good**: Decode once at the event boundary, then export typed projections:
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
if (!isThreadEvent(ev)) return false;
|
|
90
|
+
return ev.thread === filter.thread;
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**Rule**: For append-only logs, JSON streams, RPC payloads, or config files,
|
|
94
|
+
create one owner for:
|
|
95
|
+
|
|
96
|
+
- event / payload type definitions
|
|
97
|
+
- type guards and normalization from `unknown`
|
|
98
|
+
- metadata projections used by UI commands
|
|
99
|
+
- reducers that replay state from the source of truth
|
|
100
|
+
|
|
101
|
+
Rendering code may format fields, but it must not redefine the payload contract.
|
|
102
|
+
|
|
74
103
|
---
|
|
75
104
|
|
|
76
105
|
## Checklist for Cross-Layer Features
|
|
@@ -87,6 +116,10 @@ After implementation:
|
|
|
87
116
|
- [ ] Tested with edge cases (null, empty, invalid)
|
|
88
117
|
- [ ] Verified error handling at each boundary
|
|
89
118
|
- [ ] Checked data survives round-trip
|
|
119
|
+
- [ ] Checked that consumers import shared decoders / projections instead of
|
|
120
|
+
casting payload fields locally
|
|
121
|
+
- [ ] Checked that derived state points back to the source event identifier
|
|
122
|
+
(`seq`, `id`, `version`) instead of inventing a second cursor
|
|
90
123
|
|
|
91
124
|
---
|
|
92
125
|
|
|
@@ -195,3 +228,32 @@ Create detailed flow docs when:
|
|
|
195
228
|
- Multiple teams are involved
|
|
196
229
|
- Data format is complex
|
|
197
230
|
- Feature has caused bugs before
|
|
231
|
+
|
|
232
|
+
---
|
|
233
|
+
|
|
234
|
+
## Event Log / Projection Boundary
|
|
235
|
+
|
|
236
|
+
Append-only logs are cross-layer contracts. A single event travels through:
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
CLI input → event writer → events.jsonl → reader → filter → reducer → display
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### Checklist: After Adding A New Event Kind Or Field
|
|
243
|
+
|
|
244
|
+
- [ ] Add the event kind to the central event taxonomy
|
|
245
|
+
- [ ] Add a typed event variant or type guard at the event layer
|
|
246
|
+
- [ ] Add normalization helpers for array/object fields that come from
|
|
247
|
+
user input or JSON
|
|
248
|
+
- [ ] Keep `seq` / `id` assignment in the event writer only
|
|
249
|
+
- [ ] Make filters and reducers consume the typed event guard, not local casts
|
|
250
|
+
- [ ] Make display code consume reducer output or typed events, not raw JSON
|
|
251
|
+
- [ ] Add at least one regression that proves history replay and live filtering
|
|
252
|
+
use the same filter model
|
|
253
|
+
|
|
254
|
+
**Real-world example**: Thread channels added `kind: "thread"`, `description`,
|
|
255
|
+
`linkedContext`, labels, and `lastSeq`. The first implementation replayed state
|
|
256
|
+
correctly, but several commands still re-parsed event payload fields with local
|
|
257
|
+
casts. The fix was to make `store/events.ts` own `ThreadChannelEvent`,
|
|
258
|
+
`isThreadEvent`, and `metadataFromCreateEvent`, while `store/thread-state.ts`
|
|
259
|
+
became the only replay reducer.
|
|
@@ -34,6 +34,8 @@ These guides help you **ask the right questions before coding**.
|
|
|
34
34
|
- [ ] Data format changes between layers
|
|
35
35
|
- [ ] Multiple consumers need the same data
|
|
36
36
|
- [ ] You're not sure where to put some logic
|
|
37
|
+
- [ ] You are adding an event kind, JSONL record, RPC payload, or config field
|
|
38
|
+
- [ ] UI / command code starts casting raw payload fields directly
|
|
37
39
|
|
|
38
40
|
→ Read [Cross-Layer Thinking Guide](./cross-layer-thinking-guide.md)
|
|
39
41
|
|
|
@@ -44,9 +46,25 @@ These guides help you **ask the right questions before coding**.
|
|
|
44
46
|
- [ ] You're adding a new field to multiple places
|
|
45
47
|
- [ ] **You're modifying any constant or config**
|
|
46
48
|
- [ ] **You're creating a new utility/helper function** ← Search first!
|
|
49
|
+
- [ ] Two files read the same untyped payload field with local casts
|
|
50
|
+
- [ ] Multiple branches update the same derived state from `kind` / `action`
|
|
47
51
|
|
|
48
52
|
→ Read [Code Reuse Thinking Guide](./code-reuse-thinking-guide.md)
|
|
49
53
|
|
|
54
|
+
### When Verifying AI Cross-Review Results
|
|
55
|
+
|
|
56
|
+
- [ ] Reviewer claims "user input can be malicious" → Check the actual data source (internal manifest? user config? external API?)
|
|
57
|
+
- [ ] Reviewer flags "missing validation" → Is the data from a trusted internal source?
|
|
58
|
+
- [ ] Reviewer says "behavior change" → Read the code comments — is it intentional design?
|
|
59
|
+
- [ ] Reviewer identifies a "bug" in test → Mentally delete the feature being tested — does the test still pass? If yes → tautological test
|
|
60
|
+
|
|
61
|
+
**Common AI reviewer false-positive patterns**:
|
|
62
|
+
1. **Trust boundary confusion**: Treating internal data (bundled JSON manifests) as untrusted external input
|
|
63
|
+
2. **Ignoring design comments**: Flagging intentional behavior documented in code comments as bugs
|
|
64
|
+
3. **Variable misreading**: Not tracing a variable to its actual definition (e.g., Map keyed by path vs name)
|
|
65
|
+
|
|
66
|
+
**Verification rule**: Every CRITICAL/WARNING finding must be verified against the actual code before prioritizing. Budget ~35% false-positive rate for AI reviews.
|
|
67
|
+
|
|
50
68
|
---
|
|
51
69
|
|
|
52
70
|
## Pre-Modification Rule (CRITICAL)
|
|
@@ -33,6 +33,28 @@ import os
|
|
|
33
33
|
import re
|
|
34
34
|
import sys
|
|
35
35
|
from pathlib import Path
|
|
36
|
+
|
|
37
|
+
# Force UTF-8 on stdin/stdout/stderr on Windows. Default codepage there is
|
|
38
|
+
# cp936 / cp1252 / etc. — non-ASCII content (Chinese task names, prd snippets)
|
|
39
|
+
# both in stdin (hook payload from host CLI) and stdout (our emitted blocks)
|
|
40
|
+
# raises UnicodeDecodeError / UnicodeEncodeError. Equivalent to `python -X utf8`
|
|
41
|
+
# but applied per-stream so we don't depend on host CLI's command wiring.
|
|
42
|
+
if sys.platform.startswith("win"):
|
|
43
|
+
import io as _io
|
|
44
|
+
for _stream_name in ("stdin", "stdout", "stderr"):
|
|
45
|
+
_stream = getattr(sys, _stream_name, None)
|
|
46
|
+
if _stream is None:
|
|
47
|
+
continue
|
|
48
|
+
if hasattr(_stream, "reconfigure"):
|
|
49
|
+
try:
|
|
50
|
+
_stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
51
|
+
except Exception:
|
|
52
|
+
pass
|
|
53
|
+
elif hasattr(_stream, "detach"):
|
|
54
|
+
try:
|
|
55
|
+
setattr(sys, _stream_name, _io.TextIOWrapper(_stream.detach(), encoding="utf-8", errors="replace"))
|
|
56
|
+
except Exception:
|
|
57
|
+
pass
|
|
36
58
|
from typing import Optional
|
|
37
59
|
|
|
38
60
|
|
|
@@ -72,14 +72,27 @@ First visible reply: say once in Chinese that Trellis SessionStart context is lo
|
|
|
72
72
|
This notice is one-shot: do not repeat it after the first assistant reply in the same session.
|
|
73
73
|
</first-reply-notice>"""
|
|
74
74
|
|
|
75
|
-
#
|
|
76
|
-
#
|
|
75
|
+
# Force UTF-8 on stdin/stdout/stderr on Windows. Default codepage there is
|
|
76
|
+
# cp936 / cp1252 / etc. — non-ASCII content (Chinese task names, prd snippets)
|
|
77
|
+
# both in stdin (hook payload from host CLI) and stdout (our emitted blocks)
|
|
78
|
+
# raises UnicodeDecodeError / UnicodeEncodeError. Equivalent to `python -X utf8`
|
|
79
|
+
# but applied per-stream so we don't depend on host CLI's command wiring.
|
|
77
80
|
if sys.platform.startswith("win"):
|
|
78
81
|
import io as _io
|
|
79
|
-
|
|
80
|
-
sys
|
|
81
|
-
|
|
82
|
-
|
|
82
|
+
for _stream_name in ("stdin", "stdout", "stderr"):
|
|
83
|
+
_stream = getattr(sys, _stream_name, None)
|
|
84
|
+
if _stream is None:
|
|
85
|
+
continue
|
|
86
|
+
if hasattr(_stream, "reconfigure"):
|
|
87
|
+
try:
|
|
88
|
+
_stream.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
89
|
+
except Exception:
|
|
90
|
+
pass
|
|
91
|
+
elif hasattr(_stream, "detach"):
|
|
92
|
+
try:
|
|
93
|
+
setattr(sys, _stream_name, _io.TextIOWrapper(_stream.detach(), encoding="utf-8", errors="replace"))
|
|
94
|
+
except Exception:
|
|
95
|
+
pass
|
|
83
96
|
|
|
84
97
|
|
|
85
98
|
|
|
@@ -111,31 +111,61 @@ def safe_trellis_paths_to_add(repo_root: Path) -> list[str]:
|
|
|
111
111
|
return paths
|
|
112
112
|
|
|
113
113
|
|
|
114
|
-
def safe_archive_paths_to_add(
|
|
114
|
+
def safe_archive_paths_to_add(
|
|
115
|
+
repo_root: Path,
|
|
116
|
+
task_name: str | None = None,
|
|
117
|
+
modified_children: list[str] | None = None,
|
|
118
|
+
) -> list[str]:
|
|
115
119
|
"""Return paths to stage after `task.py archive`.
|
|
116
120
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
121
|
+
Scoped to ONLY the paths the archive operation actually touched:
|
|
122
|
+
|
|
123
|
+
- the archive subtree (where the freshly-moved task lives)
|
|
124
|
+
- the source task directory (for source-side deletes; caller pairs
|
|
125
|
+
this with `git rm --cached` since `git add` won't stage deletes
|
|
126
|
+
for a path that no longer exists in the working tree)
|
|
127
|
+
- any child task directories whose `task.json` was edited to drop
|
|
128
|
+
the archived parent (parent-children relationship update)
|
|
129
|
+
|
|
130
|
+
This narrow scope avoids "scope creep" — dirty changes in OTHER
|
|
131
|
+
active task dirs (parallel-window edits) are NOT bundled into the
|
|
132
|
+
archive commit. Callers handle each kind of change in its own
|
|
133
|
+
commit boundary.
|
|
134
|
+
|
|
135
|
+
Backwards-compat: with no arguments, the function walks the whole
|
|
136
|
+
`.trellis/tasks/` subtree the old way (active tasks + archive). New
|
|
137
|
+
callers should always pass `task_name`.
|
|
122
138
|
"""
|
|
123
139
|
paths: list[str] = []
|
|
124
140
|
tasks_dir = repo_root / DIR_WORKFLOW / DIR_TASKS
|
|
125
|
-
if tasks_dir.is_dir():
|
|
126
|
-
|
|
127
|
-
|
|
141
|
+
if not tasks_dir.is_dir():
|
|
142
|
+
return paths
|
|
143
|
+
|
|
144
|
+
archive_dir = tasks_dir / DIR_ARCHIVE
|
|
145
|
+
|
|
146
|
+
if task_name is not None:
|
|
147
|
+
# Narrow scope — only paths that still exist on disk (so
|
|
148
|
+
# `git add` doesn't choke on the moved-away source). The caller
|
|
149
|
+
# handles the source-side deletes via `git rm --cached`
|
|
150
|
+
# explicitly.
|
|
128
151
|
if archive_dir.is_dir():
|
|
129
|
-
paths.append(
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
152
|
+
paths.append(
|
|
153
|
+
f"{DIR_WORKFLOW}/{DIR_TASKS}/{DIR_ARCHIVE}"
|
|
154
|
+
)
|
|
155
|
+
for child_name in modified_children or []:
|
|
156
|
+
paths.append(f"{DIR_WORKFLOW}/{DIR_TASKS}/{child_name}")
|
|
157
|
+
return paths
|
|
158
|
+
|
|
159
|
+
# Legacy wide scope (no task_name): preserve old behavior so callers
|
|
160
|
+
# that have not been updated keep working.
|
|
161
|
+
if archive_dir.is_dir():
|
|
162
|
+
paths.append(f"{DIR_WORKFLOW}/{DIR_TASKS}/{DIR_ARCHIVE}")
|
|
163
|
+
for child in sorted(tasks_dir.iterdir()):
|
|
164
|
+
if not child.is_dir():
|
|
165
|
+
continue
|
|
166
|
+
if child.name == DIR_ARCHIVE:
|
|
167
|
+
continue
|
|
168
|
+
paths.append(f"{DIR_WORKFLOW}/{DIR_TASKS}/{child.name}")
|
|
139
169
|
return paths
|
|
140
170
|
|
|
141
171
|
|
|
@@ -369,6 +369,9 @@ def cmd_archive(args: argparse.Namespace) -> int:
|
|
|
369
369
|
|
|
370
370
|
# Update status before archiving
|
|
371
371
|
today = datetime.now().strftime("%Y-%m-%d")
|
|
372
|
+
# Names of child task dirs whose task.json gets modified below; passed
|
|
373
|
+
# into safe_archive_paths_to_add so they're staged in this commit.
|
|
374
|
+
modified_children: list[str] = []
|
|
372
375
|
if task_json_path.is_file():
|
|
373
376
|
data = read_json(task_json_path)
|
|
374
377
|
if data:
|
|
@@ -393,6 +396,7 @@ def cmd_archive(args: argparse.Namespace) -> int:
|
|
|
393
396
|
if child_data:
|
|
394
397
|
child_data["parent"] = None
|
|
395
398
|
write_json(child_json, child_data)
|
|
399
|
+
modified_children.append(child_dir_path.name)
|
|
396
400
|
|
|
397
401
|
# Clear any session that still points at this task before the path moves.
|
|
398
402
|
from .active_task import clear_task_from_sessions
|
|
@@ -407,7 +411,7 @@ def cmd_archive(args: argparse.Namespace) -> int:
|
|
|
407
411
|
|
|
408
412
|
# Auto-commit unless --no-commit
|
|
409
413
|
if not getattr(args, "no_commit", False):
|
|
410
|
-
_auto_commit_archive(dir_name, repo_root)
|
|
414
|
+
_auto_commit_archive(dir_name, repo_root, modified_children)
|
|
411
415
|
|
|
412
416
|
# Return the archive path
|
|
413
417
|
print(f"{DIR_WORKFLOW}/{DIR_TASKS}/{DIR_ARCHIVE}/{year_month}/{dir_name}")
|
|
@@ -420,18 +424,26 @@ def cmd_archive(args: argparse.Namespace) -> int:
|
|
|
420
424
|
return 1
|
|
421
425
|
|
|
422
426
|
|
|
423
|
-
def _auto_commit_archive(
|
|
427
|
+
def _auto_commit_archive(
|
|
428
|
+
task_name: str,
|
|
429
|
+
repo_root: Path,
|
|
430
|
+
modified_children: list[str] | None = None,
|
|
431
|
+
) -> None:
|
|
424
432
|
"""Stage Trellis-owned task paths and commit after archive.
|
|
425
433
|
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
caches/backups) and points users at ``session_auto_commit: false``.
|
|
434
|
+
Scoped narrowly to the archived task's source + destination paths
|
|
435
|
+
plus any child task dirs whose ``task.json`` was edited (parent →
|
|
436
|
+
children relationship update). Dirty changes in OTHER active task
|
|
437
|
+
dirs are NOT bundled into the archive commit.
|
|
431
438
|
|
|
432
|
-
|
|
433
|
-
``
|
|
434
|
-
|
|
439
|
+
If ``.gitignore`` blocks the paths, we warn + skip — we do NOT
|
|
440
|
+
retry with ``git add -f``. The warning explicitly forbids
|
|
441
|
+
``git add -f .trellis/`` (which would fan out to caches/backups)
|
|
442
|
+
and points users at ``session_auto_commit: false``.
|
|
443
|
+
|
|
444
|
+
Honors ``session_auto_commit`` in ``.trellis/config.yaml``: when
|
|
445
|
+
set to ``false``, this function returns immediately without
|
|
446
|
+
touching git (the archive directory move on disk is unaffected).
|
|
435
447
|
"""
|
|
436
448
|
if not get_session_auto_commit(repo_root):
|
|
437
449
|
print(
|
|
@@ -440,7 +452,9 @@ def _auto_commit_archive(task_name: str, repo_root: Path) -> None:
|
|
|
440
452
|
)
|
|
441
453
|
return
|
|
442
454
|
|
|
443
|
-
paths = safe_archive_paths_to_add(
|
|
455
|
+
paths = safe_archive_paths_to_add(
|
|
456
|
+
repo_root, task_name=task_name, modified_children=modified_children
|
|
457
|
+
)
|
|
444
458
|
if not paths:
|
|
445
459
|
print("[OK] No task changes to commit.", file=sys.stderr)
|
|
446
460
|
return
|
|
@@ -456,8 +470,24 @@ def _auto_commit_archive(task_name: str, repo_root: Path) -> None:
|
|
|
456
470
|
)
|
|
457
471
|
return
|
|
458
472
|
|
|
473
|
+
# Belt-and-suspenders for the phantom-delete bug: `safe_git_add` uses
|
|
474
|
+
# `git add` (no -A) which only stages additions/modifications. The
|
|
475
|
+
# source task directory was moved away by `shutil.move`, so its files
|
|
476
|
+
# need an explicit `git rm --cached` to stage the deletions in this
|
|
477
|
+
# same commit — otherwise they sit as uncommitted "phantom deletes"
|
|
478
|
+
# against HEAD until something later picks them up.
|
|
479
|
+
#
|
|
480
|
+
# `--ignore-unmatch` makes this a no-op when the task was never tracked
|
|
481
|
+
# (e.g. archiving a task that lived only in working tree).
|
|
482
|
+
source_rel = f"{DIR_WORKFLOW}/{DIR_TASKS}/{task_name}"
|
|
483
|
+
run_git(
|
|
484
|
+
["rm", "-r", "--cached", "--ignore-unmatch", "--", source_rel],
|
|
485
|
+
cwd=repo_root,
|
|
486
|
+
)
|
|
487
|
+
|
|
459
488
|
rc, _, _ = run_git(
|
|
460
|
-
["diff", "--cached", "--quiet", "--", *paths],
|
|
489
|
+
["diff", "--cached", "--quiet", "--", *paths, source_rel],
|
|
490
|
+
cwd=repo_root,
|
|
461
491
|
)
|
|
462
492
|
if rc == 0:
|
|
463
493
|
print("[OK] No task changes to commit.", file=sys.stderr)
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Homedir guard for destructive commands (init, uninstall).
|
|
3
|
+
*
|
|
4
|
+
* Running `trellis init` / `trellis uninstall` in `$HOME` is catastrophic:
|
|
5
|
+
* platforms like Claude Code, Codex, OpenCode all store global runtime data
|
|
6
|
+
* (`.claude/projects/<sanitized-cwd>/*.jsonl` chat history, `.codex/sessions/`,
|
|
7
|
+
* `.opencode/` caches, etc.) directly in the user's home directory. If
|
|
8
|
+
* trellis manages the same `.{platform}/` config dirs and the hash manifest
|
|
9
|
+
* picks up runtime data, uninstall would later unlink it.
|
|
10
|
+
*
|
|
11
|
+
* Subdirectories of home (`~/Documents/projects/foo/`) are NOT blocked — only
|
|
12
|
+
* exact-home match.
|
|
13
|
+
*
|
|
14
|
+
* Bypass: `TRELLIS_ALLOW_HOMEDIR=1`.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Returns true if `process.cwd()` is exactly the user's home directory.
|
|
18
|
+
*
|
|
19
|
+
* Uses `realpathSync.native()` on both sides so symlinks, `..` segments, and
|
|
20
|
+
* case differences (Windows) don't confuse the comparison. On Windows the
|
|
21
|
+
* comparison is also case-insensitive — `C:\Users\Alice` matches
|
|
22
|
+
* `c:\users\alice`.
|
|
23
|
+
*
|
|
24
|
+
* Permissive on lookup failure: if realpath fails for any reason (broken
|
|
25
|
+
* symlink, EACCES, etc.) we return false so a safety check doesn't crash
|
|
26
|
+
* the command.
|
|
27
|
+
*/
|
|
28
|
+
export declare function isCwdHomedir(): boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Error message printed by both `trellis init` and `trellis uninstall` when
|
|
31
|
+
* the homedir guard trips.
|
|
32
|
+
*/
|
|
33
|
+
export declare function homedirGuardMessage(commandName: "init" | "uninstall"): string;
|
|
34
|
+
/**
|
|
35
|
+
* Returns true when the bypass env var is set.
|
|
36
|
+
*/
|
|
37
|
+
export declare function homedirBypassEnabled(): boolean;
|
|
38
|
+
//# sourceMappingURL=cwd-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cwd-guard.d.ts","sourceRoot":"","sources":["../../src/utils/cwd-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,IAAI,OAAO,CAYtC;AAED;;;GAGG;AACH,wBAAgB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,WAAW,GAAG,MAAM,CAS7E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAE9C"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Homedir guard for destructive commands (init, uninstall).
|
|
3
|
+
*
|
|
4
|
+
* Running `trellis init` / `trellis uninstall` in `$HOME` is catastrophic:
|
|
5
|
+
* platforms like Claude Code, Codex, OpenCode all store global runtime data
|
|
6
|
+
* (`.claude/projects/<sanitized-cwd>/*.jsonl` chat history, `.codex/sessions/`,
|
|
7
|
+
* `.opencode/` caches, etc.) directly in the user's home directory. If
|
|
8
|
+
* trellis manages the same `.{platform}/` config dirs and the hash manifest
|
|
9
|
+
* picks up runtime data, uninstall would later unlink it.
|
|
10
|
+
*
|
|
11
|
+
* Subdirectories of home (`~/Documents/projects/foo/`) are NOT blocked — only
|
|
12
|
+
* exact-home match.
|
|
13
|
+
*
|
|
14
|
+
* Bypass: `TRELLIS_ALLOW_HOMEDIR=1`.
|
|
15
|
+
*/
|
|
16
|
+
import { realpathSync } from "node:fs";
|
|
17
|
+
import * as os from "node:os";
|
|
18
|
+
/**
|
|
19
|
+
* Returns true if `process.cwd()` is exactly the user's home directory.
|
|
20
|
+
*
|
|
21
|
+
* Uses `realpathSync.native()` on both sides so symlinks, `..` segments, and
|
|
22
|
+
* case differences (Windows) don't confuse the comparison. On Windows the
|
|
23
|
+
* comparison is also case-insensitive — `C:\Users\Alice` matches
|
|
24
|
+
* `c:\users\alice`.
|
|
25
|
+
*
|
|
26
|
+
* Permissive on lookup failure: if realpath fails for any reason (broken
|
|
27
|
+
* symlink, EACCES, etc.) we return false so a safety check doesn't crash
|
|
28
|
+
* the command.
|
|
29
|
+
*/
|
|
30
|
+
export function isCwdHomedir() {
|
|
31
|
+
try {
|
|
32
|
+
let cwd = realpathSync.native(process.cwd());
|
|
33
|
+
let home = realpathSync.native(os.homedir());
|
|
34
|
+
if (process.platform === "win32") {
|
|
35
|
+
cwd = cwd.toLowerCase();
|
|
36
|
+
home = home.toLowerCase();
|
|
37
|
+
}
|
|
38
|
+
return cwd === home;
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Error message printed by both `trellis init` and `trellis uninstall` when
|
|
46
|
+
* the homedir guard trips.
|
|
47
|
+
*/
|
|
48
|
+
export function homedirGuardMessage(commandName) {
|
|
49
|
+
return (`✗ Refusing to run \`trellis ${commandName}\` in your home directory.\n\n` +
|
|
50
|
+
`Trellis manages platform config dirs like .claude/, .codex/, .opencode/, which\n` +
|
|
51
|
+
`in your home directory also contain runtime data from those CLIs (chat history,\n` +
|
|
52
|
+
`session JSONLs, caches). Running here can wipe that data.\n\n` +
|
|
53
|
+
`Run trellis from your project directory instead. If you really want to run in\n` +
|
|
54
|
+
`$HOME, set TRELLIS_ALLOW_HOMEDIR=1.`);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Returns true when the bypass env var is set.
|
|
58
|
+
*/
|
|
59
|
+
export function homedirBypassEnabled() {
|
|
60
|
+
return process.env.TRELLIS_ALLOW_HOMEDIR === "1";
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=cwd-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cwd-guard.js","sourceRoot":"","sources":["../../src/utils/cwd-guard.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,KAAK,EAAE,MAAM,SAAS,CAAC;AAE9B;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,YAAY;IAC1B,IAAI,CAAC;QACH,IAAI,GAAG,GAAG,YAAY,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAC7C,IAAI,IAAI,GAAG,YAAY,CAAC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC;QAC7C,IAAI,OAAO,CAAC,QAAQ,KAAK,OAAO,EAAE,CAAC;YACjC,GAAG,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;YACxB,IAAI,GAAG,IAAI,CAAC,WAAW,EAAE,CAAC;QAC5B,CAAC;QACD,OAAO,GAAG,KAAK,IAAI,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,mBAAmB,CAAC,WAAiC;IACnE,OAAO,CACL,+BAA+B,WAAW,gCAAgC;QAC1E,kFAAkF;QAClF,mFAAmF;QACnF,+DAA+D;QAC/D,iFAAiF;QACjF,qCAAqC,CACtC,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,oBAAoB;IAClC,OAAO,OAAO,CAAC,GAAG,CAAC,qBAAqB,KAAK,GAAG,CAAC;AACnD,CAAC"}
|
|
@@ -4,6 +4,19 @@ export interface WriteOptions {
|
|
|
4
4
|
}
|
|
5
5
|
export declare function setWriteMode(mode: WriteMode): void;
|
|
6
6
|
export declare function getWriteMode(): WriteMode;
|
|
7
|
+
/**
|
|
8
|
+
* Begin recording every write into the returned Set. Calls accumulate into the
|
|
9
|
+
* same set until `stopRecordingWrites` runs. POSIX relative paths (relative to
|
|
10
|
+
* `cwd`) are stored, matching `.template-hashes.json` keys.
|
|
11
|
+
*
|
|
12
|
+
* Nested recording sessions are NOT supported — the caller must ensure
|
|
13
|
+
* `stopRecordingWrites` runs before the next `startRecordingWrites`. Failure
|
|
14
|
+
* is silent (the second `start` replaces the first set), so callers should
|
|
15
|
+
* always pair start/stop in try/finally.
|
|
16
|
+
*/
|
|
17
|
+
export declare function startRecordingWrites(cwd: string): Set<string>;
|
|
18
|
+
/** End recording. Subsequent writes are not captured until `start` is called again. */
|
|
19
|
+
export declare function stopRecordingWrites(): void;
|
|
7
20
|
/**
|
|
8
21
|
* Write file with conflict handling
|
|
9
22
|
* - If file doesn't exist: write directly
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-writer.d.ts","sourceRoot":"","sources":["../../src/utils/file-writer.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"file-writer.d.ts","sourceRoot":"","sources":["../../src/utils/file-writer.ts"],"names":[],"mappings":"AAOA,MAAM,MAAM,SAAS,GAAG,KAAK,GAAG,OAAO,GAAG,MAAM,GAAG,QAAQ,CAAC;AAE5D,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,SAAS,CAAC;CACjB;AASD,wBAAgB,YAAY,CAAC,IAAI,EAAE,SAAS,GAAG,IAAI,CAElD;AAED,wBAAgB,YAAY,IAAI,SAAS,CAExC;AAkBD;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,CAK7D;AAED,uFAAuF;AACvF,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C;AAsCD;;;;;;;;GAQG;AACH,wBAAsB,SAAS,CAC7B,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE;IAAE,UAAU,CAAC,EAAE,OAAO,CAAA;CAAE,GACjC,OAAO,CAAC,OAAO,CAAC,CA0HlB;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAE/C"}
|
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import chalk from "chalk";
|
|
4
4
|
import inquirer from "inquirer";
|
|
5
|
+
import { toPosix } from "./posix.js";
|
|
5
6
|
// Global write mode (set from CLI options)
|
|
6
7
|
let globalWriteMode = "ask";
|
|
7
8
|
export function setWriteMode(mode) {
|
|
@@ -10,6 +11,51 @@ export function setWriteMode(mode) {
|
|
|
10
11
|
export function getWriteMode() {
|
|
11
12
|
return globalWriteMode;
|
|
12
13
|
}
|
|
14
|
+
// ---------------------------------------------------------------------------
|
|
15
|
+
// Write recording
|
|
16
|
+
//
|
|
17
|
+
// `trellis init` uses recording to capture exactly which files were actually
|
|
18
|
+
// written this run (vs skipped because they already existed). The captured
|
|
19
|
+
// set is what `.template-hashes.json` should contain — NOT a blind directory
|
|
20
|
+
// walk of `.codex/` / `.claude/` / etc, which would include user-owned files
|
|
21
|
+
// that pre-dated init. See `pruneOrphanManifestKeys` for the self-heal side
|
|
22
|
+
// of the same contract.
|
|
23
|
+
// ---------------------------------------------------------------------------
|
|
24
|
+
/** When recording is active, every actual `writeFile` disk write appends here. */
|
|
25
|
+
let writeRecorder = null;
|
|
26
|
+
/** Project root used to convert absolute write paths to POSIX-relative keys. */
|
|
27
|
+
let writeRecorderRoot = null;
|
|
28
|
+
/**
|
|
29
|
+
* Begin recording every write into the returned Set. Calls accumulate into the
|
|
30
|
+
* same set until `stopRecordingWrites` runs. POSIX relative paths (relative to
|
|
31
|
+
* `cwd`) are stored, matching `.template-hashes.json` keys.
|
|
32
|
+
*
|
|
33
|
+
* Nested recording sessions are NOT supported — the caller must ensure
|
|
34
|
+
* `stopRecordingWrites` runs before the next `startRecordingWrites`. Failure
|
|
35
|
+
* is silent (the second `start` replaces the first set), so callers should
|
|
36
|
+
* always pair start/stop in try/finally.
|
|
37
|
+
*/
|
|
38
|
+
export function startRecordingWrites(cwd) {
|
|
39
|
+
const sink = new Set();
|
|
40
|
+
writeRecorder = sink;
|
|
41
|
+
writeRecorderRoot = cwd;
|
|
42
|
+
return sink;
|
|
43
|
+
}
|
|
44
|
+
/** End recording. Subsequent writes are not captured until `start` is called again. */
|
|
45
|
+
export function stopRecordingWrites() {
|
|
46
|
+
writeRecorder = null;
|
|
47
|
+
writeRecorderRoot = null;
|
|
48
|
+
}
|
|
49
|
+
/** Record a successful write. Called internally by `writeFile`. */
|
|
50
|
+
function recordWrite(absPath) {
|
|
51
|
+
if (!writeRecorder || !writeRecorderRoot)
|
|
52
|
+
return;
|
|
53
|
+
const rel = path.relative(writeRecorderRoot, absPath);
|
|
54
|
+
// Defensive: skip writes outside cwd (no meaningful manifest key).
|
|
55
|
+
if (rel.startsWith("..") || path.isAbsolute(rel))
|
|
56
|
+
return;
|
|
57
|
+
writeRecorder.add(toPosix(rel));
|
|
58
|
+
}
|
|
13
59
|
/**
|
|
14
60
|
* Get relative path from cwd for display
|
|
15
61
|
*/
|
|
@@ -49,12 +95,15 @@ export async function writeFile(filePath, content, options) {
|
|
|
49
95
|
if (options?.executable) {
|
|
50
96
|
fs.chmodSync(filePath, "755");
|
|
51
97
|
}
|
|
98
|
+
recordWrite(filePath);
|
|
52
99
|
return true;
|
|
53
100
|
}
|
|
54
101
|
// File exists, check if content is identical
|
|
55
102
|
const existingContent = fs.readFileSync(filePath, "utf-8");
|
|
56
103
|
if (existingContent === content) {
|
|
57
|
-
// Content identical,
|
|
104
|
+
// Content identical, but no disk write happened. Do not record it for
|
|
105
|
+
// init-time manifests: pre-existing user files can legitimately be
|
|
106
|
+
// byte-identical to a Trellis template and still not be Trellis-owned.
|
|
58
107
|
return false;
|
|
59
108
|
}
|
|
60
109
|
// File exists with different content, handle based on mode.
|
|
@@ -70,15 +119,22 @@ export async function writeFile(filePath, content, options) {
|
|
|
70
119
|
fs.chmodSync(filePath, "755");
|
|
71
120
|
}
|
|
72
121
|
console.log(chalk.yellow(` ↻ Overwritten: ${displayPath}`));
|
|
122
|
+
recordWrite(filePath);
|
|
73
123
|
return true;
|
|
74
124
|
}
|
|
75
125
|
if (mode === "skip") {
|
|
76
126
|
console.log(chalk.gray(` ○ Skipped: ${displayPath} (already exists)`));
|
|
127
|
+
// Skipped: trellis did NOT write this file — caller should not track it
|
|
128
|
+
// in the manifest. This is the AGENTS.md skip-existing case.
|
|
77
129
|
return false;
|
|
78
130
|
}
|
|
79
131
|
if (mode === "append") {
|
|
80
132
|
appendToFile(filePath, content, options);
|
|
81
133
|
console.log(chalk.blue(` + Appended: ${displayPath}`));
|
|
134
|
+
// Append: trellis added trellis content to a user-owned file. Tracking
|
|
135
|
+
// is risky here (uninstall would unlink the whole file), so we do NOT
|
|
136
|
+
// record appended files. Users on `--append` get a fresh manifest miss
|
|
137
|
+
// on next update; that's the safer default.
|
|
82
138
|
return true;
|
|
83
139
|
}
|
|
84
140
|
// mode === 'ask': Interactive prompt
|
|
@@ -107,6 +163,7 @@ export async function writeFile(filePath, content, options) {
|
|
|
107
163
|
fs.chmodSync(filePath, "755");
|
|
108
164
|
}
|
|
109
165
|
console.log(chalk.yellow(` ↻ Overwritten: ${displayPath}`));
|
|
166
|
+
recordWrite(filePath);
|
|
110
167
|
return true;
|
|
111
168
|
}
|
|
112
169
|
if (action === "append") {
|
|
@@ -126,6 +183,7 @@ export async function writeFile(filePath, content, options) {
|
|
|
126
183
|
fs.chmodSync(filePath, "755");
|
|
127
184
|
}
|
|
128
185
|
console.log(chalk.yellow(` ↻ Overwritten: ${displayPath}`));
|
|
186
|
+
recordWrite(filePath);
|
|
129
187
|
return true;
|
|
130
188
|
}
|
|
131
189
|
if (action === "append-all") {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"file-writer.js","sourceRoot":"","sources":["../../src/utils/file-writer.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,QAAQ,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"file-writer.js","sourceRoot":"","sources":["../../src/utils/file-writer.ts"],"names":[],"mappings":"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"}
|