@mindfoldhq/trellis 0.6.9 → 0.7.0-beta.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/dist/cli/index.js +2 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +0 -8
- package/dist/commands/update.js.map +1 -1
- package/dist/commands/workflow.d.ts +6 -0
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +124 -0
- package/dist/commands/workflow.js.map +1 -1
- package/dist/configurators/workflow.d.ts +0 -14
- package/dist/configurators/workflow.d.ts.map +1 -1
- package/dist/configurators/workflow.js +1 -37
- package/dist/configurators/workflow.js.map +1 -1
- package/dist/migrations/manifests/0.6.10.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.0.json +9 -0
- package/dist/templates/claude/settings.json +22 -0
- package/dist/templates/codex/agents/trellis-check.toml +7 -5
- package/dist/templates/codex/agents/trellis-implement.toml +7 -5
- package/dist/templates/codex/agents/trellis-research.toml +13 -8
- package/dist/templates/codex/hooks/session-start.py +20 -1
- package/dist/templates/codex/hooks.json +25 -0
- package/dist/templates/copilot/hooks/session-start.py +20 -1
- package/dist/templates/opencode/plugins/inject-workflow-state.js +111 -10
- package/dist/templates/pi/extensions/trellis/index.ts.txt +4 -3
- package/dist/templates/shared-hooks/index.d.ts +5 -1
- package/dist/templates/shared-hooks/index.d.ts.map +1 -1
- package/dist/templates/shared-hooks/index.js +10 -1
- package/dist/templates/shared-hooks/index.js.map +1 -1
- package/dist/templates/shared-hooks/inject-spec-context.py +840 -0
- package/dist/templates/shared-hooks/inject-subagent-context.py +4 -3
- package/dist/templates/shared-hooks/inject-workflow-state.py +35 -9
- package/dist/templates/shared-hooks/session-start.py +22 -1
- package/dist/templates/trellis/config.yaml +47 -0
- package/dist/templates/trellis/index.d.ts +4 -3
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +7 -3
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/add_session.py +0 -45
- package/dist/templates/trellis/scripts/common/active_task.py +5 -2
- package/dist/templates/trellis/scripts/common/config.py +18 -0
- package/dist/templates/trellis/scripts/common/git_context.py +34 -2
- package/dist/templates/trellis/scripts/common/paths.py +28 -0
- package/dist/templates/trellis/scripts/common/spec_inject.py +439 -0
- package/dist/templates/trellis/scripts/common/spec_match.py +395 -0
- package/dist/templates/trellis/scripts/common/task_context.py +10 -8
- package/dist/templates/trellis/scripts/common/task_store.py +30 -0
- package/dist/templates/trellis/scripts/common/workflow_phase.py +3 -2
- package/dist/templates/trellis/scripts/common/workflow_selection.py +177 -0
- package/dist/templates/trellis/scripts/task.py +82 -0
- package/package.json +2 -2
- package/dist/templates/trellis/gitattributes.txt +0 -9
|
@@ -207,11 +207,12 @@ def truncate_utf8(data: bytes, cap: int) -> bytes:
|
|
|
207
207
|
seq_len = 4
|
|
208
208
|
else:
|
|
209
209
|
seq_len = 1
|
|
210
|
-
#
|
|
210
|
+
# Cut before the lead byte when its full sequence didn't fit;
|
|
211
|
+
# otherwise the trailing sequence is complete — keep it whole.
|
|
211
212
|
if (i - 1) + seq_len > len(truncated):
|
|
212
|
-
i
|
|
213
|
+
return truncated[: i - 1]
|
|
213
214
|
|
|
214
|
-
return truncated
|
|
215
|
+
return truncated
|
|
215
216
|
|
|
216
217
|
|
|
217
218
|
class _Budget:
|
|
@@ -10,9 +10,11 @@ The emitted ``hookEventName`` field is platform-aware: most hosts expect
|
|
|
10
10
|
CodeBuddy / Droid / Codex / Copilot wiring), but Gemini CLI 0.40.x renamed
|
|
11
11
|
its per-turn event to ``BeforeAgent`` and its schema validator rejects the
|
|
12
12
|
legacy name. ``_detect_platform`` picks the right value at runtime.
|
|
13
|
-
Breadcrumb text is pulled exclusively from workflow
|
|
14
|
-
[workflow-state:STATUS] tag blocks —
|
|
15
|
-
|
|
13
|
+
Breadcrumb text is pulled exclusively from the resolved workflow file's
|
|
14
|
+
[workflow-state:STATUS] tag blocks — the active task may select a
|
|
15
|
+
per-task variant (`.trellis/workflows/<id>.md` via task.json `workflow`),
|
|
16
|
+
otherwise personal, team, and global defaults are resolved in order.
|
|
17
|
+
There are no fallback dicts in this script: when the resolved workflow is
|
|
16
18
|
missing or a tag is absent, the breadcrumb degrades to a generic
|
|
17
19
|
"Refer to workflow.md for current step." line so users see (and fix)
|
|
18
20
|
the broken state instead of the hook silently masking it.
|
|
@@ -183,16 +185,40 @@ _TAG_RE = re.compile(
|
|
|
183
185
|
re.DOTALL,
|
|
184
186
|
)
|
|
185
187
|
|
|
186
|
-
def
|
|
187
|
-
"""
|
|
188
|
+
def _resolve_workflow_md(root: Path, input_data: dict) -> Path:
|
|
189
|
+
"""Resolve the active task's workflow file, falling back to the global one.
|
|
188
190
|
|
|
189
|
-
|
|
191
|
+
The per-task resolution rule lives in common.workflow_selection inside
|
|
192
|
+
.trellis/scripts. Older installed projects may not ship that module, and
|
|
193
|
+
hooks must never crash the session — ANY failure (import error, old
|
|
194
|
+
scripts tree, resolver bug) falls back to the global workflow.md.
|
|
195
|
+
"""
|
|
196
|
+
try:
|
|
197
|
+
scripts_dir = root / ".trellis" / "scripts"
|
|
198
|
+
if str(scripts_dir) not in sys.path:
|
|
199
|
+
sys.path.insert(0, str(scripts_dir))
|
|
200
|
+
from common.workflow_selection import resolve_workflow_md # type: ignore[import-not-found]
|
|
201
|
+
|
|
202
|
+
return resolve_workflow_md(
|
|
203
|
+
root, input_data, platform=_detect_platform(input_data)
|
|
204
|
+
)
|
|
205
|
+
except Exception:
|
|
206
|
+
return root / ".trellis" / "workflow.md"
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def load_breadcrumbs(root: Path, input_data: dict) -> dict[str, str]:
|
|
210
|
+
"""Parse the resolved workflow file for [workflow-state:STATUS] blocks.
|
|
211
|
+
|
|
212
|
+
Returns {status: body_text}. The workflow file is the single source of
|
|
190
213
|
truth — there are no fallback dicts in this script. Missing tags
|
|
191
|
-
(or a missing/unreadable workflow
|
|
214
|
+
(or a missing/unreadable workflow file) fall back to a generic line
|
|
192
215
|
in build_breadcrumb so users see the broken state and fix
|
|
193
216
|
workflow.md, rather than the hook silently masking the issue.
|
|
217
|
+
The active task's per-task workflow selection (task.json `workflow`
|
|
218
|
+
field) is honored via _resolve_workflow_md; without a selection this
|
|
219
|
+
reads the global .trellis/workflow.md exactly as before.
|
|
194
220
|
"""
|
|
195
|
-
workflow = root
|
|
221
|
+
workflow = _resolve_workflow_md(root, input_data)
|
|
196
222
|
if not workflow.is_file():
|
|
197
223
|
return {}
|
|
198
224
|
try:
|
|
@@ -411,7 +437,7 @@ def main() -> int:
|
|
|
411
437
|
if prompt_has_skip_keyword(data.get("prompt", ""), _resolve_skip_keyword(config)):
|
|
412
438
|
return 0 # user opted out of the per-turn breadcrumb for this turn
|
|
413
439
|
|
|
414
|
-
templates = load_breadcrumbs(root)
|
|
440
|
+
templates = load_breadcrumbs(root, data)
|
|
415
441
|
platform = _detect_platform(data)
|
|
416
442
|
task = get_active_task(root, data)
|
|
417
443
|
if task is None:
|
|
@@ -715,6 +715,27 @@ def _strip_breadcrumb_tag_blocks(content: str) -> str:
|
|
|
715
715
|
return re.sub(r"\n{3,}", "\n\n", stripped).strip()
|
|
716
716
|
|
|
717
717
|
|
|
718
|
+
def _resolve_workflow_md(root: Path, input_data: dict) -> Path:
|
|
719
|
+
"""Resolve the active task's workflow file, falling back to the global one.
|
|
720
|
+
|
|
721
|
+
The per-task resolution rule lives in common.workflow_selection inside
|
|
722
|
+
.trellis/scripts. Older installed projects may not ship that module, and
|
|
723
|
+
hooks must never crash the session — ANY failure (import error, old
|
|
724
|
+
scripts tree, resolver bug) falls back to the global workflow.md.
|
|
725
|
+
"""
|
|
726
|
+
try:
|
|
727
|
+
scripts_dir = root / ".trellis" / "scripts"
|
|
728
|
+
if str(scripts_dir) not in sys.path:
|
|
729
|
+
sys.path.insert(0, str(scripts_dir))
|
|
730
|
+
from common.workflow_selection import resolve_workflow_md # type: ignore[import-not-found]
|
|
731
|
+
|
|
732
|
+
return resolve_workflow_md(
|
|
733
|
+
root, input_data, platform=_detect_platform(input_data)
|
|
734
|
+
)
|
|
735
|
+
except Exception:
|
|
736
|
+
return root / ".trellis" / "workflow.md"
|
|
737
|
+
|
|
738
|
+
|
|
718
739
|
def _build_workflow_overview(workflow_path: Path) -> str:
|
|
719
740
|
"""Inject only the compact Phase Index summary for SessionStart."""
|
|
720
741
|
content = read_file(workflow_path)
|
|
@@ -800,7 +821,7 @@ Trellis compact SessionStart context. Use it to orient the session; load details
|
|
|
800
821
|
output.write("\n</current-state>\n\n")
|
|
801
822
|
|
|
802
823
|
output.write("<trellis-workflow>\n")
|
|
803
|
-
output.write(_build_workflow_overview(
|
|
824
|
+
output.write(_build_workflow_overview(_resolve_workflow_md(project_dir, hook_input)))
|
|
804
825
|
output.write("\n</trellis-workflow>\n\n")
|
|
805
826
|
|
|
806
827
|
output.write("<guidelines>\n")
|
|
@@ -76,6 +76,21 @@ max_journal_lines: 2000
|
|
|
76
76
|
# Default package used when --package is not specified.
|
|
77
77
|
# default_package: frontend
|
|
78
78
|
|
|
79
|
+
#-------------------------------------------------------------------------------
|
|
80
|
+
# Default workflow
|
|
81
|
+
#-------------------------------------------------------------------------------
|
|
82
|
+
# Team-shared default workflow for tasks that do not pin one. The id names a
|
|
83
|
+
# variant file in `.trellis/workflows/<id>.md` (populate it with
|
|
84
|
+
# `trellis workflow --save <id>`). This value is committed, so the whole team
|
|
85
|
+
# shares the same default. A per-developer override lives in the gitignored
|
|
86
|
+
# `.developer` file as a `workflow=<id>` line and takes precedence over this.
|
|
87
|
+
#
|
|
88
|
+
# Resolution precedence: per-task (task.json `workflow`) > personal
|
|
89
|
+
# (`.developer` `workflow=`) > this `default_workflow` > global
|
|
90
|
+
# `.trellis/workflow.md`.
|
|
91
|
+
#
|
|
92
|
+
# default_workflow: native
|
|
93
|
+
|
|
79
94
|
#-------------------------------------------------------------------------------
|
|
80
95
|
# Channel worker OOM guard
|
|
81
96
|
#-------------------------------------------------------------------------------
|
|
@@ -145,6 +160,38 @@ channel:
|
|
|
145
160
|
# max_artifact_bytes: 65536 # per task artifact (prd.md / design.md / implement.md)
|
|
146
161
|
# max_total_bytes: 131072 # whole injected payload; overflow degrades to index lines
|
|
147
162
|
|
|
163
|
+
#-------------------------------------------------------------------------------
|
|
164
|
+
# Path-scoped spec injection
|
|
165
|
+
#-------------------------------------------------------------------------------
|
|
166
|
+
# When the agent touches a file (Read/Edit/Write/MultiEdit), spec .md files
|
|
167
|
+
# under .trellis/spec/ whose frontmatter `paths:` globs match the touched path
|
|
168
|
+
# are surfaced into the session right then. The first time a spec matches it is
|
|
169
|
+
# injected in full; while its content is unchanged and the refresh window has
|
|
170
|
+
# not elapsed it stays silent; once the window elapses a short `<spec-ticket>`
|
|
171
|
+
# reminder is emitted to counter recency decay. Editing the spec itself — or a
|
|
172
|
+
# SessionStart after /clear or /compact — re-injects the full text.
|
|
173
|
+
# Oversized specs are truncated with a notice; once the per-event payload cap
|
|
174
|
+
# is reached, remaining full bodies degrade to index lines (path + description)
|
|
175
|
+
# instead of being inlined.
|
|
176
|
+
#
|
|
177
|
+
# Character values: the ceiling this budget respects is Claude Code's
|
|
178
|
+
# documented 10,000-CHARACTER additionalContext limit, so the caps count
|
|
179
|
+
# characters too (byte caps made CJK specs pay 3x for the same text).
|
|
180
|
+
# `0` disables the corresponding limit.
|
|
181
|
+
# The refresh window uses wall-clock seconds. `0` disables time-based reminders;
|
|
182
|
+
# SessionStart resets after /clear or /compact still force a full re-injection.
|
|
183
|
+
#
|
|
184
|
+
# spec_injection:
|
|
185
|
+
# enabled: true # false disables injection entirely
|
|
186
|
+
# max_spec_chars: 9400 # per matched spec file
|
|
187
|
+
# max_total_chars: 9500 # whole per-event payload; overflow degrades to index lines
|
|
188
|
+
# refresh_window_seconds: 2700 # touches past this interval re-emit a ticket
|
|
189
|
+
# tools: # tool events that trigger injection
|
|
190
|
+
# - Read
|
|
191
|
+
# - Edit
|
|
192
|
+
# - Write
|
|
193
|
+
# - MultiEdit
|
|
194
|
+
|
|
148
195
|
#-------------------------------------------------------------------------------
|
|
149
196
|
# Per-turn prompt injection
|
|
150
197
|
#-------------------------------------------------------------------------------
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
* ├── scripts-shell-archive/ # Archived shell scripts (for reference)
|
|
16
16
|
* ├── workflow.md # Workflow guide
|
|
17
17
|
* ├── config.yaml # Trellis configuration
|
|
18
|
-
*
|
|
19
|
-
* └── gitattributes.txt # project-root .gitattributes content
|
|
18
|
+
* └── gitignore.txt # .gitignore content
|
|
20
19
|
*/
|
|
21
20
|
export declare const scriptsInit: string;
|
|
22
21
|
export declare const commonInit: string;
|
|
@@ -38,7 +37,10 @@ export declare const commonTaskStore: string;
|
|
|
38
37
|
export declare const commonSessionContext: string;
|
|
39
38
|
export declare const commonPackagesContext: string;
|
|
40
39
|
export declare const commonWorkflowPhase: string;
|
|
40
|
+
export declare const commonWorkflowSelection: string;
|
|
41
41
|
export declare const commonTrellisConfig: string;
|
|
42
|
+
export declare const commonSpecMatch: string;
|
|
43
|
+
export declare const commonSpecInject: string;
|
|
42
44
|
export declare const commonSafeCommit: string;
|
|
43
45
|
export declare const getDeveloperScript: string;
|
|
44
46
|
export declare const initDeveloperScript: string;
|
|
@@ -48,7 +50,6 @@ export declare const addSessionScript: string;
|
|
|
48
50
|
export declare const workflowMdTemplate: string;
|
|
49
51
|
export declare const configYamlTemplate: string;
|
|
50
52
|
export declare const gitignoreTemplate: string;
|
|
51
|
-
export declare const gitattributesTemplate: string;
|
|
52
53
|
export declare const implementAgentTemplate: string;
|
|
53
54
|
export declare const checkAgentTemplate: string;
|
|
54
55
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/trellis/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/trellis/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAcH,eAAO,MAAM,WAAW,QAAsC,CAAC;AAG/D,eAAO,MAAM,UAAU,QAA6C,CAAC;AACrE,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,eAAe,QAA8C,CAAC;AAC3E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,YAAY,QAA2C,CAAC;AACrE,eAAO,MAAM,QAAQ,QAAuC,CAAC;AAC7D,eAAO,MAAM,SAAS,QAAwC,CAAC;AAC/D,eAAO,MAAM,SAAS,QAAwC,CAAC;AAC/D,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,WAAW,QAA0C,CAAC;AACnE,eAAO,MAAM,iBAAiB,QAAiD,CAAC;AAChF,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,oBAAoB,QAEhC,CAAC;AACF,eAAO,MAAM,qBAAqB,QAEjC,CAAC;AACF,eAAO,MAAM,mBAAmB,QAE/B,CAAC;AACF,eAAO,MAAM,uBAAuB,QAEnC,CAAC;AACF,eAAO,MAAM,mBAAmB,QAE/B,CAAC;AACF,eAAO,MAAM,eAAe,QAA+C,CAAC;AAC5E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAC9E,eAAO,MAAM,gBAAgB,QAAgD,CAAC;AAG9E,eAAO,MAAM,kBAAkB,QAA2C,CAAC;AAC3E,eAAO,MAAM,mBAAmB,QAA4C,CAAC;AAC7E,eAAO,MAAM,UAAU,QAAkC,CAAC;AAC1D,eAAO,MAAM,gBAAgB,QAAyC,CAAC;AACvE,eAAO,MAAM,gBAAgB,QAAyC,CAAC;AAGvE,eAAO,MAAM,kBAAkB,QAA8B,CAAC;AAC9D,eAAO,MAAM,kBAAkB,QAA8B,CAAC;AAC9D,eAAO,MAAM,iBAAiB,QAAgC,CAAC;AAM/D,eAAO,MAAM,sBAAsB,QAAsC,CAAC;AAC1E,eAAO,MAAM,kBAAkB,QAAkC,CAAC;AAElE;;GAEG;AACH,wBAAgB,aAAa,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAwCnD;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,IAAI,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAKlD"}
|
|
@@ -15,8 +15,7 @@
|
|
|
15
15
|
* ├── scripts-shell-archive/ # Archived shell scripts (for reference)
|
|
16
16
|
* ├── workflow.md # Workflow guide
|
|
17
17
|
* ├── config.yaml # Trellis configuration
|
|
18
|
-
*
|
|
19
|
-
* └── gitattributes.txt # project-root .gitattributes content
|
|
18
|
+
* └── gitignore.txt # .gitignore content
|
|
20
19
|
*/
|
|
21
20
|
import { readFileSync } from "node:fs";
|
|
22
21
|
import { dirname, join } from "node:path";
|
|
@@ -48,7 +47,10 @@ export const commonTaskStore = readTemplate("scripts/common/task_store.py");
|
|
|
48
47
|
export const commonSessionContext = readTemplate("scripts/common/session_context.py");
|
|
49
48
|
export const commonPackagesContext = readTemplate("scripts/common/packages_context.py");
|
|
50
49
|
export const commonWorkflowPhase = readTemplate("scripts/common/workflow_phase.py");
|
|
50
|
+
export const commonWorkflowSelection = readTemplate("scripts/common/workflow_selection.py");
|
|
51
51
|
export const commonTrellisConfig = readTemplate("scripts/common/trellis_config.py");
|
|
52
|
+
export const commonSpecMatch = readTemplate("scripts/common/spec_match.py");
|
|
53
|
+
export const commonSpecInject = readTemplate("scripts/common/spec_inject.py");
|
|
52
54
|
export const commonSafeCommit = readTemplate("scripts/common/safe_commit.py");
|
|
53
55
|
// Python scripts - main
|
|
54
56
|
export const getDeveloperScript = readTemplate("scripts/get_developer.py");
|
|
@@ -60,7 +62,6 @@ export const addSessionScript = readTemplate("scripts/add_session.py");
|
|
|
60
62
|
export const workflowMdTemplate = readTemplate("workflow.md");
|
|
61
63
|
export const configYamlTemplate = readTemplate("config.yaml");
|
|
62
64
|
export const gitignoreTemplate = readTemplate("gitignore.txt");
|
|
63
|
-
export const gitattributesTemplate = readTemplate("gitattributes.txt");
|
|
64
65
|
// Channel runtime agent definitions (loaded by
|
|
65
66
|
// `packages/cli/src/commands/channel/agent-loader.ts` from `.trellis/agents/`).
|
|
66
67
|
// These are platform-agnostic Trellis runtime files dispatched at `trellis init`
|
|
@@ -94,7 +95,10 @@ export function getAllScripts() {
|
|
|
94
95
|
scripts.set("common/session_context.py", commonSessionContext);
|
|
95
96
|
scripts.set("common/packages_context.py", commonPackagesContext);
|
|
96
97
|
scripts.set("common/workflow_phase.py", commonWorkflowPhase);
|
|
98
|
+
scripts.set("common/workflow_selection.py", commonWorkflowSelection);
|
|
97
99
|
scripts.set("common/trellis_config.py", commonTrellisConfig);
|
|
100
|
+
scripts.set("common/spec_match.py", commonSpecMatch);
|
|
101
|
+
scripts.set("common/spec_inject.py", commonSpecInject);
|
|
98
102
|
scripts.set("common/safe_commit.py", commonSafeCommit);
|
|
99
103
|
// Main
|
|
100
104
|
scripts.set("get_developer.py", getDeveloperScript);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/trellis/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/trellis/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -41,7 +41,6 @@ from common.paths import (
|
|
|
41
41
|
)
|
|
42
42
|
from common.developer import ensure_developer
|
|
43
43
|
from common.git import run_git
|
|
44
|
-
from common.log import Colors, colored
|
|
45
44
|
from common.safe_commit import (
|
|
46
45
|
print_gitignore_warning,
|
|
47
46
|
safe_git_add,
|
|
@@ -181,49 +180,6 @@ def resolve_session_branch(
|
|
|
181
180
|
return None
|
|
182
181
|
|
|
183
182
|
|
|
184
|
-
def is_git_worktree(repo_root: Path) -> bool:
|
|
185
|
-
"""Return True when repo_root is a linked worktree (not the main working tree).
|
|
186
|
-
|
|
187
|
-
Standard test: `git rev-parse --git-dir` (per-worktree) differs from
|
|
188
|
-
`git rev-parse --git-common-dir` (shared across all worktrees) once both
|
|
189
|
-
are resolved to absolute paths. In the main working tree these are the
|
|
190
|
-
same directory.
|
|
191
|
-
"""
|
|
192
|
-
rc_dir, git_dir, _ = run_git(["rev-parse", "--git-dir"], cwd=repo_root)
|
|
193
|
-
rc_common, git_common_dir, _ = run_git(
|
|
194
|
-
["rev-parse", "--git-common-dir"], cwd=repo_root
|
|
195
|
-
)
|
|
196
|
-
if rc_dir != 0 or rc_common != 0:
|
|
197
|
-
return False
|
|
198
|
-
|
|
199
|
-
git_dir_path = (repo_root / git_dir.strip()).resolve()
|
|
200
|
-
git_common_dir_path = (repo_root / git_common_dir.strip()).resolve()
|
|
201
|
-
return git_dir_path != git_common_dir_path
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
def warn_if_parallel_worktree(repo_root: Path) -> None:
|
|
205
|
-
"""Non-blocking note: index.md conflicts across parallel worktrees/branches
|
|
206
|
-
are expected and safe. Only fires when running in a linked git worktree
|
|
207
|
-
(not the main tree) with `session_auto_commit` enabled (#415 quick-fix tier).
|
|
208
|
-
"""
|
|
209
|
-
if not get_session_auto_commit(repo_root):
|
|
210
|
-
return
|
|
211
|
-
if not is_git_worktree(repo_root):
|
|
212
|
-
return
|
|
213
|
-
print(
|
|
214
|
-
colored(
|
|
215
|
-
"[NOTE] Running in a git worktree with session_auto_commit enabled: "
|
|
216
|
-
"journal-*.md files auto-merge via .gitattributes, but index.md "
|
|
217
|
-
"conflicts across parallel worktrees/branches are expected and safe "
|
|
218
|
-
"to resolve by picking either side (task state lives in task.json, "
|
|
219
|
-
"not index.md). See .trellis/spec/cli/backend/directory-structure.md "
|
|
220
|
-
'("Workspace Journal Merge Behavior").',
|
|
221
|
-
Colors.YELLOW,
|
|
222
|
-
),
|
|
223
|
-
file=sys.stderr,
|
|
224
|
-
)
|
|
225
|
-
|
|
226
|
-
|
|
227
183
|
def create_new_journal_file(
|
|
228
184
|
dev_dir: Path, num: int, developer: str, today: str, max_lines: int = 2000,
|
|
229
185
|
) -> Path:
|
|
@@ -520,7 +476,6 @@ def add_session(
|
|
|
520
476
|
) -> int:
|
|
521
477
|
"""Add a new session."""
|
|
522
478
|
repo_root = get_repo_root()
|
|
523
|
-
warn_if_parallel_worktree(repo_root)
|
|
524
479
|
ensure_developer(repo_root)
|
|
525
480
|
|
|
526
481
|
developer = get_developer(repo_root)
|
|
@@ -612,13 +612,16 @@ def clear_active_task(
|
|
|
612
612
|
platform_input: dict[str, Any] | None = None,
|
|
613
613
|
platform: str | None = None,
|
|
614
614
|
) -> ActiveTask:
|
|
615
|
-
"""Clear the active task by deleting
|
|
615
|
+
"""Clear the active task by deleting its resolved session context file."""
|
|
616
616
|
context_key = resolve_context_key(platform_input, platform)
|
|
617
617
|
if not context_key:
|
|
618
618
|
return ActiveTask(None, "none")
|
|
619
619
|
|
|
620
620
|
previous = resolve_active_task(repo_root, platform_input, platform)
|
|
621
|
-
|
|
621
|
+
if not previous.task_path or not previous.context_key:
|
|
622
|
+
return previous
|
|
623
|
+
|
|
624
|
+
context_path = _context_path(repo_root, previous.context_key)
|
|
622
625
|
if context_path.is_file():
|
|
623
626
|
_remove_file(context_path)
|
|
624
627
|
return previous
|
|
@@ -281,6 +281,24 @@ def get_codex_dispatch_mode(repo_root: Path | None = None) -> str:
|
|
|
281
281
|
return "inline"
|
|
282
282
|
|
|
283
283
|
|
|
284
|
+
def get_default_workflow(repo_root: Path | None = None) -> str | None:
|
|
285
|
+
"""Return the team-shared default workflow id from config.yaml.
|
|
286
|
+
|
|
287
|
+
Reads the top-level ``default_workflow`` key — a slug naming a variant in
|
|
288
|
+
``.trellis/workflows/<id>.md``. Returns None when unset or blank. This is
|
|
289
|
+
the git-tracked, team-shared default; a per-developer override lives in the
|
|
290
|
+
gitignored ``.developer`` file (``workflow=<id>``, see
|
|
291
|
+
``paths.get_developer_workflow``) and outranks it. Fail-open: a missing or
|
|
292
|
+
non-string value simply means "no team default" — no warning (this is read
|
|
293
|
+
on every turn by hooks and must not add per-turn noise).
|
|
294
|
+
"""
|
|
295
|
+
config = _load_config(repo_root)
|
|
296
|
+
raw = config.get("default_workflow")
|
|
297
|
+
if not isinstance(raw, str):
|
|
298
|
+
return None
|
|
299
|
+
return raw.strip() or None
|
|
300
|
+
|
|
301
|
+
|
|
284
302
|
DEFAULT_CONTEXT_INJECTION_MAX_FILE_BYTES = 32768
|
|
285
303
|
DEFAULT_CONTEXT_INJECTION_MAX_ARTIFACT_BYTES = 65536
|
|
286
304
|
DEFAULT_CONTEXT_INJECTION_MAX_TOTAL_BYTES = 131072
|
|
@@ -27,6 +27,8 @@ from .packages_context import (
|
|
|
27
27
|
get_context_packages_text,
|
|
28
28
|
get_context_packages_json,
|
|
29
29
|
)
|
|
30
|
+
from .paths import get_repo_root
|
|
31
|
+
from .spec_match import match_specs_for_file
|
|
30
32
|
from .trellis_config import read_trellis_config
|
|
31
33
|
from .workflow_phase import (
|
|
32
34
|
filter_platform,
|
|
@@ -57,9 +59,9 @@ def main() -> None:
|
|
|
57
59
|
parser.add_argument(
|
|
58
60
|
"--mode",
|
|
59
61
|
"-m",
|
|
60
|
-
choices=["default", "record", "packages", "phase"],
|
|
62
|
+
choices=["default", "record", "packages", "phase", "spec"],
|
|
61
63
|
default="default",
|
|
62
|
-
help="Output mode: default (full context), record (for record-session), packages (package info only), phase (workflow step extraction)",
|
|
64
|
+
help="Output mode: default (full context), record (for record-session), packages (package info only), phase (workflow step extraction), spec (specs governing a file)",
|
|
63
65
|
)
|
|
64
66
|
parser.add_argument(
|
|
65
67
|
"--step",
|
|
@@ -69,6 +71,10 @@ def main() -> None:
|
|
|
69
71
|
"--platform",
|
|
70
72
|
help="Platform name for --mode phase, e.g. cursor, claude-code. Filters platform-tagged blocks.",
|
|
71
73
|
)
|
|
74
|
+
parser.add_argument(
|
|
75
|
+
"--file",
|
|
76
|
+
help="File path (absolute or repo-relative) for --mode spec. Lists spec files whose frontmatter paths match it.",
|
|
77
|
+
)
|
|
72
78
|
|
|
73
79
|
args = parser.parse_args()
|
|
74
80
|
|
|
@@ -95,6 +101,32 @@ def main() -> None:
|
|
|
95
101
|
)
|
|
96
102
|
content = filter_platform(content, effective)
|
|
97
103
|
print(content, end="")
|
|
104
|
+
elif args.mode == "spec":
|
|
105
|
+
if not args.file:
|
|
106
|
+
parser.error("--file is required with --mode spec")
|
|
107
|
+
matches = match_specs_for_file(get_repo_root(), args.file)
|
|
108
|
+
if args.json:
|
|
109
|
+
print(
|
|
110
|
+
json.dumps(
|
|
111
|
+
{
|
|
112
|
+
"file": args.file,
|
|
113
|
+
"matches": [
|
|
114
|
+
{
|
|
115
|
+
"path": match.rel_path,
|
|
116
|
+
"description": match.description,
|
|
117
|
+
}
|
|
118
|
+
for match in matches
|
|
119
|
+
],
|
|
120
|
+
},
|
|
121
|
+
indent=2,
|
|
122
|
+
ensure_ascii=False,
|
|
123
|
+
)
|
|
124
|
+
)
|
|
125
|
+
elif matches:
|
|
126
|
+
for match in matches:
|
|
127
|
+
print(f"{match.rel_path} — {match.description or '(no description)'}")
|
|
128
|
+
else:
|
|
129
|
+
print(f"No spec files declare paths matching {args.file}.")
|
|
98
130
|
else:
|
|
99
131
|
if args.json:
|
|
100
132
|
output_json()
|
|
@@ -94,6 +94,34 @@ def get_developer(repo_root: Path | None = None) -> str | None:
|
|
|
94
94
|
return None
|
|
95
95
|
|
|
96
96
|
|
|
97
|
+
def get_developer_workflow(repo_root: Path | None = None) -> str | None:
|
|
98
|
+
"""Get the personal workflow override from the .developer file.
|
|
99
|
+
|
|
100
|
+
Reads an optional ``workflow=<id>`` line from the gitignored ``.developer``
|
|
101
|
+
file — the per-developer, git-excluded override that outranks the
|
|
102
|
+
team-shared ``default_workflow`` in config.yaml. Returns None when the file
|
|
103
|
+
or the line is absent/blank. Never raises. Additive to ``get_developer``:
|
|
104
|
+
the ``name=`` reader ignores this line and vice versa.
|
|
105
|
+
"""
|
|
106
|
+
if repo_root is None:
|
|
107
|
+
repo_root = get_repo_root()
|
|
108
|
+
|
|
109
|
+
dev_file = repo_root / DIR_WORKFLOW / FILE_DEVELOPER
|
|
110
|
+
|
|
111
|
+
if not dev_file.is_file():
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
try:
|
|
115
|
+
content = dev_file.read_text(encoding="utf-8")
|
|
116
|
+
for line in content.splitlines():
|
|
117
|
+
if line.startswith("workflow="):
|
|
118
|
+
return line.split("=", 1)[1].strip() or None
|
|
119
|
+
except (OSError, IOError):
|
|
120
|
+
pass
|
|
121
|
+
|
|
122
|
+
return None
|
|
123
|
+
|
|
124
|
+
|
|
97
125
|
def check_developer(repo_root: Path | None = None) -> bool:
|
|
98
126
|
"""Check if developer is initialized.
|
|
99
127
|
|