@mindfoldhq/trellis 0.5.0-rc.4 → 0.5.0-rc.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/dist/migrations/manifests/0.5.0-rc.5.json +9 -0
- package/dist/migrations/manifests/0.5.0-rc.6.json +9 -0
- package/dist/templates/codex/config.toml +27 -10
- package/dist/templates/codex/hooks/session-start.py +47 -1
- package/dist/templates/codex/skills/finish-work/SKILL.md +14 -4
- package/dist/templates/common/commands/finish-work.md +14 -4
- package/dist/templates/copilot/hooks/session-start.py +47 -1
- package/dist/templates/copilot/prompts/finish-work.prompt.md +14 -4
- package/dist/templates/markdown/agents.md +5 -2
- package/dist/templates/shared-hooks/session-start.py +48 -2
- package/package.json +1 -1
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.5.0-rc.5",
|
|
3
|
+
"description": "Codex template enables multi_agent_v2 by default with an 8-minute wait floor. AGENTS.md adds wait tool rules. No new migrations.",
|
|
4
|
+
"breaking": false,
|
|
5
|
+
"recommendMigrate": false,
|
|
6
|
+
"changelog": "**Enhancements:**\n- feat(codex): `.codex/config.toml` template now writes `[features.multi_agent_v2] { enabled = true, max_concurrent_threads_per_session = 6, min_wait_timeout_ms = 480000 }`. `min_wait_timeout_ms = 480000` (8 min) is the `wait()` timeout floor, up from Codex's `10000` (10 s) default; forces the parent to wait through subagent runtime instead of cancelling. `enabled = true` is required inside the table — the table form alone does not enable the feature. Project-level `[features]` activates only when the project is trusted in `~/.codex/config.toml`.\n- fix(codex): drop the `[features].codex_hooks = true` line — `CodexHooks` is now `Stage::Stable` with `default_enabled: true` in Codex's feature registry, so hooks load automatically once the project is trusted. The legacy `codex_hooks` alias was redundant.\n- feat(agents): `AGENTS.md` Subagents section names Codex's `wait` tool and bans cancel/re-spawn before `wait` returns. Raise the timeout (default 30 s, max 1 h) before judging a subagent stuck.",
|
|
7
|
+
"migrations": [],
|
|
8
|
+
"notes": "RC install: `npm install -g @mindfoldhq/trellis@rc`. Codex users: trust this project (`[projects.\"<abs path>\"].trust_level = \"trusted\"` in `~/.codex/config.toml`) so hooks and the project-level [features] block activate."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.5.0-rc.6",
|
|
3
|
+
"description": "Windows session-start.py normalizes MSYS/Cygwin/WSL paths. finish-work Step 2 classifies dirty paths instead of aborting unconditionally. No new migrations.",
|
|
4
|
+
"breaking": false,
|
|
5
|
+
"recommendMigrate": false,
|
|
6
|
+
"changelog": "**Bug Fixes:**\n- fix(hooks): Windows `session-start.py` normalizes MSYS `/d/...`, Cygwin `/cygdrive/d/...`, WSL `/mnt/d/...` to `D:\\...` before `Path.resolve()`. Synced to 6 copies (`.claude/`, `.codex/`, `.cursor/` + templates `shared-hooks/`, `codex/`, `copilot/`). Fixes #226.\n\n**Enhancements:**\n- feat(finish-work): Step 2 classifies dirty paths into current-task / other-window / indeterminate. Only current-task paths abort; other-window paths are reported and skipped; indeterminate paths prompt the user. Synced to 8 copies.",
|
|
7
|
+
"migrations": [],
|
|
8
|
+
"notes": "RC install: `npm install -g @mindfoldhq/trellis@rc`. Windows + Git Bash users should upgrade to restore Trellis context injection."
|
|
9
|
+
}
|
|
@@ -1,15 +1,32 @@
|
|
|
1
1
|
# Project-scoped Codex defaults for Trellis workflows.
|
|
2
|
-
# Codex
|
|
2
|
+
# Codex merges this layer after the user-level config when the project
|
|
3
|
+
# is marked as a trusted project. To trust this project, add it under
|
|
4
|
+
# `[projects]` in ~/.codex/config.toml, e.g.:
|
|
5
|
+
#
|
|
6
|
+
# [projects."/abs/path/to/this/repo"]
|
|
7
|
+
# trust_level = "trusted"
|
|
8
|
+
#
|
|
9
|
+
# Without trust, the [features] block below is loaded but disabled.
|
|
3
10
|
|
|
4
11
|
# Keep AGENTS.md as the primary project instruction file.
|
|
5
12
|
project_doc_fallback_filenames = ["AGENTS.md"]
|
|
6
13
|
|
|
7
|
-
#
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
#
|
|
13
|
-
#
|
|
14
|
-
#
|
|
15
|
-
#
|
|
14
|
+
# Codex hooks (`hooks.json` in this directory) load automatically once
|
|
15
|
+
# the project is trusted — no feature flag needed. CodexHooks is Stable
|
|
16
|
+
# and default_enabled: true in codex's feature registry; the legacy
|
|
17
|
+
# `[features].codex_hooks = true` flag is no longer required.
|
|
18
|
+
|
|
19
|
+
# multi_agent_v2 forces structured subagent orchestration with the
|
|
20
|
+
# `wait` tool — parent must block on terminal status before acting,
|
|
21
|
+
# instead of cancelling / re-spawning. Incompatible with
|
|
22
|
+
# [agents].max_threads (codex will reject the combination).
|
|
23
|
+
# `enabled = true` is required inside the table — the table form does
|
|
24
|
+
# NOT auto-enable the feature without it.
|
|
25
|
+
# - max_concurrent_threads_per_session: bumps default 4 → 6.
|
|
26
|
+
# - min_wait_timeout_ms: 480000 ms = 8 min. Codex default is 10 s, too
|
|
27
|
+
# short for Trellis subagents that routinely take 2-10 min. Hard
|
|
28
|
+
# ceiling is 3,600,000 (1 h).
|
|
29
|
+
[features.multi_agent_v2]
|
|
30
|
+
enabled = true
|
|
31
|
+
max_concurrent_threads_per_session = 6
|
|
32
|
+
min_wait_timeout_ms = 480000
|
|
@@ -18,6 +18,52 @@ import warnings
|
|
|
18
18
|
from io import StringIO
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
|
|
21
|
+
|
|
22
|
+
def _normalize_windows_shell_path(path_str: str) -> str:
|
|
23
|
+
"""Normalize Unix-style shell paths to real Windows paths.
|
|
24
|
+
|
|
25
|
+
On Windows, shells like Git Bash / MSYS2 / Cygwin may report paths like
|
|
26
|
+
`/d/Users/...` or `/cygdrive/d/Users/...`. `Path.resolve()` will misinterpret
|
|
27
|
+
these as `D:/d/Users...` on drive D: (or similar), breaking repo root
|
|
28
|
+
detection.
|
|
29
|
+
|
|
30
|
+
This function is intentionally conservative: it only rewrites patterns that
|
|
31
|
+
unambiguously represent a drive letter mount.
|
|
32
|
+
"""
|
|
33
|
+
if not isinstance(path_str, str) or not path_str:
|
|
34
|
+
return path_str
|
|
35
|
+
|
|
36
|
+
# Only relevant on Windows; keep other platforms untouched.
|
|
37
|
+
if not sys.platform.startswith("win"):
|
|
38
|
+
return path_str
|
|
39
|
+
|
|
40
|
+
p = path_str.strip()
|
|
41
|
+
|
|
42
|
+
# Already a Windows drive path (C:\... or C:/...)
|
|
43
|
+
if re.match(r"^[A-Za-z]:[\/]", p):
|
|
44
|
+
return p
|
|
45
|
+
|
|
46
|
+
# MSYS/Git-Bash style: /c/Users/... or /d/Work/...
|
|
47
|
+
m = re.match(r"^/([A-Za-z])/(.*)", p)
|
|
48
|
+
if m:
|
|
49
|
+
drive, rest = m.group(1).upper(), m.group(2)
|
|
50
|
+
return f"{drive}:\\{rest.replace('/', '\\')}"
|
|
51
|
+
|
|
52
|
+
# Cygwin style: /cygdrive/c/Users/...
|
|
53
|
+
m = re.match(r"^/cygdrive/([A-Za-z])/(.*)", p)
|
|
54
|
+
if m:
|
|
55
|
+
drive, rest = m.group(1).upper(), m.group(2)
|
|
56
|
+
return f"{drive}:\\{rest.replace('/', '\\')}"
|
|
57
|
+
|
|
58
|
+
# WSL mounted drive (sometimes leaked into env): /mnt/c/Users/...
|
|
59
|
+
m = re.match(r"^/mnt/([A-Za-z])/(.*)", p)
|
|
60
|
+
if m:
|
|
61
|
+
drive, rest = m.group(1).upper(), m.group(2)
|
|
62
|
+
return f"{drive}:\\{rest.replace('/', '\\')}"
|
|
63
|
+
|
|
64
|
+
return path_str
|
|
65
|
+
|
|
66
|
+
|
|
21
67
|
warnings.filterwarnings("ignore")
|
|
22
68
|
|
|
23
69
|
FIRST_REPLY_NOTICE = """<first-reply-notice>
|
|
@@ -271,7 +317,7 @@ def main() -> None:
|
|
|
271
317
|
hook_input = json.loads(sys.stdin.read())
|
|
272
318
|
if not isinstance(hook_input, dict):
|
|
273
319
|
hook_input = {}
|
|
274
|
-
project_dir = Path(hook_input.get("cwd", ".")).resolve()
|
|
320
|
+
project_dir = Path(_normalize_windows_shell_path(hook_input.get("cwd", "."))).resolve()
|
|
275
321
|
except (json.JSONDecodeError, KeyError):
|
|
276
322
|
hook_input = {}
|
|
277
323
|
project_dir = Path(".").resolve()
|
|
@@ -21,7 +21,7 @@ This prints:
|
|
|
21
21
|
|
|
22
22
|
If `--mode record` surfaces other completed tasks not tied to the current session, surface them to the user with a one-shot confirmation: "These N tasks look done — archive them too in this round? [y/N]". Default is no; the current active task is always archived in Step 3 regardless.
|
|
23
23
|
|
|
24
|
-
## Step 2: Sanity check —
|
|
24
|
+
## Step 2: Sanity check — classify dirty paths
|
|
25
25
|
|
|
26
26
|
Run:
|
|
27
27
|
|
|
@@ -31,11 +31,21 @@ git status --porcelain
|
|
|
31
31
|
|
|
32
32
|
Filter out paths under `.trellis/workspace/` and `.trellis/tasks/` — those are managed by `add_session.py` and `task.py archive` auto-commits and will appear dirty as part of this skill's own work.
|
|
33
33
|
|
|
34
|
-
|
|
34
|
+
For each remaining dirty path, decide whether it belongs to **the current task** or to **other parallel work** (e.g., another terminal window editing the same repo). Heuristics:
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
- Paths referenced in the current task's `prd.md` / `implement.jsonl` / `check.jsonl` → current task
|
|
37
|
+
- Paths in code areas matching the task's stated scope, or that you remember editing this session → current task
|
|
38
|
+
- Paths in unrelated areas you have no recollection of touching this session → other parallel work
|
|
37
39
|
|
|
38
|
-
|
|
40
|
+
Then route:
|
|
41
|
+
|
|
42
|
+
- **Any remaining path looks like current-task work** — bail out with:
|
|
43
|
+
> "Working tree has uncommitted code changes from this task: `<list>`. Return to workflow Phase 3.4 to commit them before running `$finish-work`."
|
|
44
|
+
|
|
45
|
+
Do NOT run `git commit` here. Do NOT prompt the user to commit. The user goes back to Phase 3.4 and the AI drives the batched commit there.
|
|
46
|
+
- **All remaining paths look unrelated** (other parallel-window work) — report them once and continue to Step 3:
|
|
47
|
+
> "FYI, dirty files outside this task's scope — leaving them for the other window: `<list>`."
|
|
48
|
+
- **Genuinely unsure** — ask the user once: "Are `<list>` this task's work I forgot to commit, or another window's? (commit / ignore)" — then route per their answer.
|
|
39
49
|
|
|
40
50
|
## Step 3: Archive task(s)
|
|
41
51
|
|
|
@@ -16,7 +16,7 @@ This prints:
|
|
|
16
16
|
|
|
17
17
|
If `--mode record` surfaces other completed tasks not tied to the current session, surface them to the user with a one-shot confirmation: "These N tasks look done — archive them too in this round? [y/N]". Default is no; the current active task is always archived in Step 3 regardless.
|
|
18
18
|
|
|
19
|
-
## Step 2: Sanity check —
|
|
19
|
+
## Step 2: Sanity check — classify dirty paths
|
|
20
20
|
|
|
21
21
|
Run:
|
|
22
22
|
|
|
@@ -26,11 +26,21 @@ git status --porcelain
|
|
|
26
26
|
|
|
27
27
|
Filter out paths under `.trellis/workspace/` and `.trellis/tasks/` — those are managed by `add_session.py` and `task.py archive` auto-commits and will appear dirty as part of this skill's own work.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
For each remaining dirty path, decide whether it belongs to **the current task** or to **other parallel work** (e.g., another terminal window editing the same repo). Heuristics:
|
|
30
30
|
|
|
31
|
-
|
|
31
|
+
- Paths referenced in the current task's `prd.md` / `implement.jsonl` / `check.jsonl` → current task
|
|
32
|
+
- Paths in code areas matching the task's stated scope, or that you remember editing this session → current task
|
|
33
|
+
- Paths in unrelated areas you have no recollection of touching this session → other parallel work
|
|
32
34
|
|
|
33
|
-
|
|
35
|
+
Then route:
|
|
36
|
+
|
|
37
|
+
- **Any remaining path looks like current-task work** — bail out with:
|
|
38
|
+
> "Working tree has uncommitted code changes from this task: `<list>`. Return to workflow Phase 3.4 to commit them before running `{{CMD_REF:finish-work}}`."
|
|
39
|
+
|
|
40
|
+
Do NOT run `git commit` here. Do NOT prompt the user to commit. The user goes back to Phase 3.4 and the AI drives the batched commit there.
|
|
41
|
+
- **All remaining paths look unrelated** (other parallel-window work) — report them once and continue to Step 3:
|
|
42
|
+
> "FYI, dirty files outside this task's scope — leaving them for the other window: `<list>`."
|
|
43
|
+
- **Genuinely unsure** — ask the user once: "Are `<list>` this task's work I forgot to commit, or another window's? (commit / ignore)" — then route per their answer.
|
|
34
44
|
|
|
35
45
|
## Step 3: Archive task(s)
|
|
36
46
|
|
|
@@ -21,6 +21,52 @@ import warnings
|
|
|
21
21
|
from io import StringIO
|
|
22
22
|
from pathlib import Path
|
|
23
23
|
|
|
24
|
+
|
|
25
|
+
def _normalize_windows_shell_path(path_str: str) -> str:
|
|
26
|
+
"""Normalize Unix-style shell paths to real Windows paths.
|
|
27
|
+
|
|
28
|
+
On Windows, shells like Git Bash / MSYS2 / Cygwin may report paths like
|
|
29
|
+
`/d/Users/...` or `/cygdrive/d/Users/...`. `Path.resolve()` will misinterpret
|
|
30
|
+
these as `D:/d/Users...` on drive D: (or similar), breaking repo root
|
|
31
|
+
detection.
|
|
32
|
+
|
|
33
|
+
This function is intentionally conservative: it only rewrites patterns that
|
|
34
|
+
unambiguously represent a drive letter mount.
|
|
35
|
+
"""
|
|
36
|
+
if not isinstance(path_str, str) or not path_str:
|
|
37
|
+
return path_str
|
|
38
|
+
|
|
39
|
+
# Only relevant on Windows; keep other platforms untouched.
|
|
40
|
+
if not sys.platform.startswith("win"):
|
|
41
|
+
return path_str
|
|
42
|
+
|
|
43
|
+
p = path_str.strip()
|
|
44
|
+
|
|
45
|
+
# Already a Windows drive path (C:\... or C:/...)
|
|
46
|
+
if re.match(r"^[A-Za-z]:[\/]", p):
|
|
47
|
+
return p
|
|
48
|
+
|
|
49
|
+
# MSYS/Git-Bash style: /c/Users/... or /d/Work/...
|
|
50
|
+
m = re.match(r"^/([A-Za-z])/(.*)", p)
|
|
51
|
+
if m:
|
|
52
|
+
drive, rest = m.group(1).upper(), m.group(2)
|
|
53
|
+
return f"{drive}:\\{rest.replace('/', '\\')}"
|
|
54
|
+
|
|
55
|
+
# Cygwin style: /cygdrive/c/Users/...
|
|
56
|
+
m = re.match(r"^/cygdrive/([A-Za-z])/(.*)", p)
|
|
57
|
+
if m:
|
|
58
|
+
drive, rest = m.group(1).upper(), m.group(2)
|
|
59
|
+
return f"{drive}:\\{rest.replace('/', '\\')}"
|
|
60
|
+
|
|
61
|
+
# WSL mounted drive (sometimes leaked into env): /mnt/c/Users/...
|
|
62
|
+
m = re.match(r"^/mnt/([A-Za-z])/(.*)", p)
|
|
63
|
+
if m:
|
|
64
|
+
drive, rest = m.group(1).upper(), m.group(2)
|
|
65
|
+
return f"{drive}:\\{rest.replace('/', '\\')}"
|
|
66
|
+
|
|
67
|
+
return path_str
|
|
68
|
+
|
|
69
|
+
|
|
24
70
|
warnings.filterwarnings("ignore")
|
|
25
71
|
|
|
26
72
|
|
|
@@ -267,7 +313,7 @@ def main() -> None:
|
|
|
267
313
|
hook_input = json.loads(sys.stdin.read())
|
|
268
314
|
if not isinstance(hook_input, dict):
|
|
269
315
|
hook_input = {}
|
|
270
|
-
project_dir = Path(hook_input.get("cwd", ".")).resolve()
|
|
316
|
+
project_dir = Path(_normalize_windows_shell_path(hook_input.get("cwd", "."))).resolve()
|
|
271
317
|
except (json.JSONDecodeError, KeyError):
|
|
272
318
|
hook_input = {}
|
|
273
319
|
project_dir = Path(".").resolve()
|
|
@@ -24,7 +24,7 @@ This prints:
|
|
|
24
24
|
|
|
25
25
|
If `--mode record` surfaces other completed tasks not tied to the current session, surface them to the user with a one-shot confirmation: "These N tasks look done — archive them too in this round? [y/N]". Default is no; the current active task is always archived in Step 3 regardless.
|
|
26
26
|
|
|
27
|
-
## Step 2: Sanity check —
|
|
27
|
+
## Step 2: Sanity check — classify dirty paths
|
|
28
28
|
|
|
29
29
|
Run:
|
|
30
30
|
|
|
@@ -34,11 +34,21 @@ git status --porcelain
|
|
|
34
34
|
|
|
35
35
|
Filter out paths under `.trellis/workspace/` and `.trellis/tasks/` — those are managed by `add_session.py` and `task.py archive` auto-commits and will appear dirty as part of this prompt's own work.
|
|
36
36
|
|
|
37
|
-
|
|
37
|
+
For each remaining dirty path, decide whether it belongs to **the current task** or to **other parallel work** (e.g., another terminal window editing the same repo). Heuristics:
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
- Paths referenced in the current task's `prd.md` / `implement.jsonl` / `check.jsonl` → current task
|
|
40
|
+
- Paths in code areas matching the task's stated scope, or that you remember editing this session → current task
|
|
41
|
+
- Paths in unrelated areas you have no recollection of touching this session → other parallel work
|
|
40
42
|
|
|
41
|
-
|
|
43
|
+
Then route:
|
|
44
|
+
|
|
45
|
+
- **Any remaining path looks like current-task work** — bail out with:
|
|
46
|
+
> "Working tree has uncommitted code changes from this task: `<list>`. Return to workflow Phase 3.4 to commit them before running `/finish-work`."
|
|
47
|
+
|
|
48
|
+
Do NOT run `git commit` here. Do NOT prompt the user to commit. The user goes back to Phase 3.4 and the AI drives the batched commit there.
|
|
49
|
+
- **All remaining paths look unrelated** (other parallel-window work) — report them once and continue to Step 3:
|
|
50
|
+
> "FYI, dirty files outside this task's scope — leaving them for the other window: `<list>`."
|
|
51
|
+
- **Genuinely unsure** — ask the user once: "Are `<list>` this task's work I forgot to commit, or another window's? (commit / ignore)" — then route per their answer.
|
|
42
52
|
|
|
43
53
|
## Step 3: Archive task(s)
|
|
44
54
|
|
|
@@ -18,10 +18,13 @@ If you're using Codex or another agent-capable tool, additional project-scoped h
|
|
|
18
18
|
|
|
19
19
|
## Subagents
|
|
20
20
|
|
|
21
|
-
- ALWAYS wait for
|
|
21
|
+
- ALWAYS wait for every spawned subagent to reach a terminal status before yielding, acting on partial results, or spawning followups.
|
|
22
|
+
- On Codex, this means calling the `wait` tool with the subagent's thread id (requires `multi_agent_v2`). Do NOT infer completion from elapsed time.
|
|
23
|
+
- On Claude Code / OpenCode, this means awaiting the Task/agent tool result before continuing.
|
|
24
|
+
- NEVER cancel or re-spawn a subagent that hasn't finished. If a subagent appears stuck, raise the wait timeout (Codex default 30s, max 1h) before judging it broken.
|
|
22
25
|
- Spawn subagents automatically when:
|
|
23
26
|
- Parallelizable work (e.g., install + verify, npm test + typecheck, multiple tasks from plan)
|
|
24
|
-
- Long-running or blocking tasks where a worker can run independently
|
|
27
|
+
- Long-running or blocking tasks where a worker can run independently
|
|
25
28
|
- Isolation for risky changes or checks
|
|
26
29
|
|
|
27
30
|
Managed by Trellis. Edits outside this block are preserved; edits inside may be overwritten by a future `trellis update`.
|
|
@@ -18,6 +18,52 @@ import sys
|
|
|
18
18
|
from io import StringIO
|
|
19
19
|
from pathlib import Path
|
|
20
20
|
|
|
21
|
+
|
|
22
|
+
def _normalize_windows_shell_path(path_str: str) -> str:
|
|
23
|
+
"""Normalize Unix-style shell paths to real Windows paths.
|
|
24
|
+
|
|
25
|
+
On Windows, shells like Git Bash / MSYS2 / Cygwin may report paths like
|
|
26
|
+
`/d/Users/...` or `/cygdrive/d/Users/...`. `Path.resolve()` will misinterpret
|
|
27
|
+
these as `D:/d/Users...` on drive D: (or similar), breaking repo root
|
|
28
|
+
detection.
|
|
29
|
+
|
|
30
|
+
This function is intentionally conservative: it only rewrites patterns that
|
|
31
|
+
unambiguously represent a drive letter mount.
|
|
32
|
+
"""
|
|
33
|
+
if not isinstance(path_str, str) or not path_str:
|
|
34
|
+
return path_str
|
|
35
|
+
|
|
36
|
+
# Only relevant on Windows; keep other platforms untouched.
|
|
37
|
+
if not sys.platform.startswith("win"):
|
|
38
|
+
return path_str
|
|
39
|
+
|
|
40
|
+
p = path_str.strip()
|
|
41
|
+
|
|
42
|
+
# Already a Windows drive path (C:\... or C:/...)
|
|
43
|
+
if re.match(r"^[A-Za-z]:[\/]", p):
|
|
44
|
+
return p
|
|
45
|
+
|
|
46
|
+
# MSYS/Git-Bash style: /c/Users/... or /d/Work/...
|
|
47
|
+
m = re.match(r"^/([A-Za-z])/(.*)", p)
|
|
48
|
+
if m:
|
|
49
|
+
drive, rest = m.group(1).upper(), m.group(2)
|
|
50
|
+
return f"{drive}:\\{rest.replace('/', '\\')}"
|
|
51
|
+
|
|
52
|
+
# Cygwin style: /cygdrive/c/Users/...
|
|
53
|
+
m = re.match(r"^/cygdrive/([A-Za-z])/(.*)", p)
|
|
54
|
+
if m:
|
|
55
|
+
drive, rest = m.group(1).upper(), m.group(2)
|
|
56
|
+
return f"{drive}:\\{rest.replace('/', '\\')}"
|
|
57
|
+
|
|
58
|
+
# WSL mounted drive (sometimes leaked into env): /mnt/c/Users/...
|
|
59
|
+
m = re.match(r"^/mnt/([A-Za-z])/(.*)", p)
|
|
60
|
+
if m:
|
|
61
|
+
drive, rest = m.group(1).upper(), m.group(2)
|
|
62
|
+
return f"{drive}:\\{rest.replace('/', '\\')}"
|
|
63
|
+
|
|
64
|
+
return path_str
|
|
65
|
+
|
|
66
|
+
|
|
21
67
|
FIRST_REPLY_NOTICE = """<first-reply-notice>
|
|
22
68
|
On the first visible assistant reply in this session, begin with exactly one short Chinese sentence:
|
|
23
69
|
Trellis SessionStart 已注入:workflow、当前任务状态、开发者身份、git 状态、active tasks、spec 索引已加载。
|
|
@@ -594,10 +640,10 @@ def main():
|
|
|
594
640
|
for var in project_dir_env_vars:
|
|
595
641
|
val = os.environ.get(var)
|
|
596
642
|
if val:
|
|
597
|
-
project_dir = Path(val).resolve()
|
|
643
|
+
project_dir = Path(_normalize_windows_shell_path(val)).resolve()
|
|
598
644
|
break
|
|
599
645
|
if project_dir is None:
|
|
600
|
-
project_dir = Path(hook_input.get("cwd", ".")).resolve()
|
|
646
|
+
project_dir = Path(_normalize_windows_shell_path(hook_input.get("cwd", "."))).resolve()
|
|
601
647
|
|
|
602
648
|
trellis_dir = project_dir / ".trellis"
|
|
603
649
|
context_key = _resolve_context_key(trellis_dir, hook_input)
|
package/package.json
CHANGED