@pennixrv/trellis 0.6.45 → 0.7.0-beta.10
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.d.ts.map +1 -1
- package/dist/cli/index.js +21 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/mem.d.ts.map +1 -1
- package/dist/commands/mem.js +6 -13
- package/dist/commands/mem.js.map +1 -1
- package/dist/commands/workflow.d.ts +17 -0
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +222 -0
- package/dist/commands/workflow.js.map +1 -1
- package/dist/configurators/claude.d.ts.map +1 -1
- package/dist/configurators/claude.js.map +1 -1
- package/dist/configurators/dsh.d.ts +16 -19
- package/dist/configurators/dsh.d.ts.map +1 -1
- package/dist/configurators/dsh.js +38 -26
- package/dist/configurators/dsh.js.map +1 -1
- package/dist/configurators/gemini.d.ts.map +1 -1
- package/dist/configurators/gemini.js.map +1 -1
- package/dist/configurators/opencode.d.ts.map +1 -1
- package/dist/configurators/opencode.js +4 -0
- package/dist/configurators/opencode.js.map +1 -1
- package/dist/migrations/manifests/0.6.17.json +2 -2
- package/dist/migrations/manifests/0.7.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.4.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.4.pennix.1.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.5.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.6.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.7.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.8.json +9 -0
- package/dist/migrations/manifests/0.7.0-beta.9.json +9 -0
- package/dist/templates/claude/hooks/statusline.py +7 -0
- package/dist/templates/claude/settings.json +22 -0
- package/dist/templates/codex/hooks/session-start.py +20 -1
- package/dist/templates/codex/hooks.json +25 -0
- package/dist/templates/common/bundled-skills/trellis-channel/references/subnode-work.md +32 -3
- package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +2 -2
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-task-lifecycle.md +1 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +2 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +3 -2
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +1 -1
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +5 -4
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +1 -0
- package/dist/templates/common/bundled-skills/trellis-session-insight/SKILL.md +2 -2
- package/dist/templates/common/bundled-skills/trellis-session-insight/references/cli-quick-reference.md +3 -2
- package/dist/templates/common/commands/continue.md +4 -2
- package/dist/templates/common/skills/brainstorm.md +14 -6
- package/dist/templates/copilot/hooks/session-start.py +20 -1
- package/dist/templates/copilot/prompts/brainstorm.prompt.md +16 -6
- package/dist/templates/dsh/DSH.md +61 -37
- package/dist/templates/dsh/agents/trellis-check.md +99 -0
- package/dist/templates/dsh/agents/trellis-implement.md +106 -0
- package/dist/templates/dsh/agents/trellis-research.md +134 -0
- package/dist/templates/dsh/index.d.ts +12 -11
- package/dist/templates/dsh/index.d.ts.map +1 -1
- package/dist/templates/dsh/index.js +14 -12
- package/dist/templates/dsh/index.js.map +1 -1
- package/dist/templates/opencode/plugins/inject-spec-context.js +121 -0
- package/dist/templates/opencode/plugins/inject-workflow-state.js +111 -10
- package/dist/templates/pi/extensions/trellis/index.ts.txt +164 -10
- package/dist/templates/shared-hooks/index.d.ts +8 -2
- package/dist/templates/shared-hooks/index.d.ts.map +1 -1
- package/dist/templates/shared-hooks/index.js +13 -1
- package/dist/templates/shared-hooks/index.js.map +1 -1
- package/dist/templates/shared-hooks/inject-shell-session-context.py +3 -3
- package/dist/templates/shared-hooks/inject-spec-context.py +844 -0
- 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/agents/subnode.md +11 -4
- package/dist/templates/trellis/config.yaml +49 -0
- package/dist/templates/trellis/index.d.ts +3 -0
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +6 -0
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/common/active_task.py +26 -4
- package/dist/templates/trellis/scripts/common/cli_adapter.py +38 -6
- package/dist/templates/trellis/scripts/common/config.py +18 -0
- package/dist/templates/trellis/scripts/common/context_projection.py +2 -2
- 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_store.py +88 -1
- package/dist/templates/trellis/scripts/common/trellis_config.py +46 -7
- 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/subnode_artifact.py +153 -15
- package/dist/templates/trellis/scripts/task.py +160 -0
- package/dist/templates/trellis/workflow.md +49 -34
- package/dist/types/ai-tools.d.ts.map +1 -1
- package/dist/types/ai-tools.js +26 -13
- package/dist/types/ai-tools.js.map +1 -1
- package/package.json +2 -2
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: trellis-research
|
|
3
|
+
user-invocable: false
|
|
4
|
+
description: |
|
|
5
|
+
Child-agent-only research role for Trellis. Main sessions must not load this
|
|
6
|
+
skill directly. Finds files, patterns, and technical solutions, and persists
|
|
7
|
+
every finding to the current task's research/ directory. No code changes
|
|
8
|
+
outside that directory.
|
|
9
|
+
---
|
|
10
|
+
# Research Agent
|
|
11
|
+
|
|
12
|
+
You are the Research Agent in the Trellis workflow.
|
|
13
|
+
|
|
14
|
+
## Core Principle
|
|
15
|
+
|
|
16
|
+
**You do one thing: find, explain, and PERSIST information.**
|
|
17
|
+
|
|
18
|
+
Conversations get compacted; files don't. Every research output MUST end up as a file under `{TASK_DIR}/research/`. Returning findings only through the chat reply is a failure - the caller cannot read them next session.
|
|
19
|
+
|
|
20
|
+
## Core Responsibilities
|
|
21
|
+
|
|
22
|
+
1. **Internal Search** - locate files/components, understand code logic, discover patterns
|
|
23
|
+
2. **External Search** - library docs, API references, best practices
|
|
24
|
+
3. **Persist** - write each research topic to `{TASK_DIR}/research/<topic>.md`
|
|
25
|
+
4. **Report** - return file paths + one-line summaries to the main agent, not full content
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Workflow
|
|
30
|
+
|
|
31
|
+
### Step 1: Resolve Current Task
|
|
32
|
+
|
|
33
|
+
Run `python3 ./.trellis/scripts/task.py current --source` to get the active task path. Prefer an `Active task: <path>` line in the dispatch prompt when present. If no active task is set, ask the user where to write output; do NOT guess.
|
|
34
|
+
|
|
35
|
+
Ensure `{TASK_DIR}/research/` exists before writing findings.
|
|
36
|
+
|
|
37
|
+
### Step 2: Understand Search Request
|
|
38
|
+
|
|
39
|
+
Classify the request as internal, external, or mixed. Determine scope and expected output shape.
|
|
40
|
+
|
|
41
|
+
### Step 3: Execute Search
|
|
42
|
+
|
|
43
|
+
Run independent searches in parallel where possible. Read relevant source files, specs, and documentation before forming conclusions.
|
|
44
|
+
|
|
45
|
+
### Step 4: Persist Each Topic
|
|
46
|
+
|
|
47
|
+
For each distinct research topic, write a markdown file at `{TASK_DIR}/research/<topic-slug>.md` using the File Format below.
|
|
48
|
+
|
|
49
|
+
### Step 5: Report to Main Agent
|
|
50
|
+
|
|
51
|
+
Reply with ONLY:
|
|
52
|
+
|
|
53
|
+
- List of files written, paths relative to repo root
|
|
54
|
+
- One-line summary per file
|
|
55
|
+
- Any critical caveats that the main agent needs to know right now
|
|
56
|
+
|
|
57
|
+
Do NOT paste full research content into the reply. The files are the contract.
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## Scope Limits
|
|
62
|
+
|
|
63
|
+
### Write Allowed
|
|
64
|
+
|
|
65
|
+
- `{TASK_DIR}/research/*.md` - your own output
|
|
66
|
+
- Creating `{TASK_DIR}/research/` if it doesn't exist
|
|
67
|
+
|
|
68
|
+
### Write Forbidden
|
|
69
|
+
|
|
70
|
+
- Code files (`src/`, `lib/`, etc.)
|
|
71
|
+
- Spec files (`.trellis/spec/`) - main agent should use the update-spec skill instead
|
|
72
|
+
- `.trellis/scripts/`, `.trellis/workflow.md`, platform config (`.dsh/`, `.claude/`, `.cursor/`, etc.)
|
|
73
|
+
- Other task directories
|
|
74
|
+
- Any git operation
|
|
75
|
+
|
|
76
|
+
If the user asks you to edit code, decline and suggest spawning `trellis-implement` instead.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## File Format
|
|
81
|
+
|
|
82
|
+
Each `{TASK_DIR}/research/<topic>.md` should follow:
|
|
83
|
+
|
|
84
|
+
```markdown
|
|
85
|
+
# Research: <topic>
|
|
86
|
+
|
|
87
|
+
- Query: <original query>
|
|
88
|
+
- Scope: internal / external / mixed
|
|
89
|
+
- Date: YYYY-MM-DD
|
|
90
|
+
|
|
91
|
+
## Findings
|
|
92
|
+
|
|
93
|
+
### Files Found
|
|
94
|
+
|
|
95
|
+
| File Path | Description |
|
|
96
|
+
|---|---|
|
|
97
|
+
| `src/services/xxx.ts` | Main implementation |
|
|
98
|
+
| `src/types/xxx.ts` | Type definitions |
|
|
99
|
+
|
|
100
|
+
### Code Patterns
|
|
101
|
+
|
|
102
|
+
<describe patterns, cite file:line>
|
|
103
|
+
|
|
104
|
+
### External References
|
|
105
|
+
|
|
106
|
+
- [Library X docs](url) - <why relevant, version constraints>
|
|
107
|
+
|
|
108
|
+
### Related Specs
|
|
109
|
+
|
|
110
|
+
- `.trellis/spec/xxx.md` - <description>
|
|
111
|
+
|
|
112
|
+
## Caveats / Not Found
|
|
113
|
+
|
|
114
|
+
<anything incomplete or uncertain>
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## Guidelines
|
|
120
|
+
|
|
121
|
+
### Do
|
|
122
|
+
|
|
123
|
+
- Provide specific file paths and line numbers
|
|
124
|
+
- Quote actual code snippets only when they are relevant
|
|
125
|
+
- Persist every topic to its own file
|
|
126
|
+
- Return file paths in your reply, not the full content
|
|
127
|
+
- Mark "not found" explicitly when searches come up empty
|
|
128
|
+
|
|
129
|
+
### Don't
|
|
130
|
+
|
|
131
|
+
- Don't write code or modify files outside `{TASK_DIR}/research/`
|
|
132
|
+
- Don't guess uncertain info
|
|
133
|
+
- Don't paste full research text into the reply
|
|
134
|
+
- Don't propose implementation changes unless the main agent explicitly asked for research options
|
|
@@ -1,21 +1,22 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DeepSeek Harness (dsh) template module.
|
|
3
3
|
*
|
|
4
|
-
* dsh is a class-2 pull-based
|
|
5
|
-
* - Workflow
|
|
6
|
-
*
|
|
4
|
+
* dsh is a class-2 pull-based platform:
|
|
5
|
+
* - Workflow/bundled skills go to the shared `.agents/skills/` root via the
|
|
6
|
+
* neutral resolver (byte-identical to Codex/Gemini/Pi/Kimi writes).
|
|
7
7
|
* - User-invocable entry points (`trellis-start` / `trellis-continue` /
|
|
8
|
-
* `trellis-finish-work
|
|
9
|
-
*
|
|
10
|
-
* project skill root.
|
|
8
|
+
* `trellis-finish-work`) and Trellis role prompts live under
|
|
9
|
+
* `.dsh/skills/trellis-agent-<role>/SKILL.md`.
|
|
11
10
|
* - Operator guide `.dsh/DSH.md`.
|
|
12
11
|
*
|
|
13
|
-
* dsh has no
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* workflow skills.
|
|
12
|
+
* dsh has no project-level hooks/settings file Trellis may write and no
|
|
13
|
+
* declarative custom sub-agent definitions, so the Trellis agent prompts ship
|
|
14
|
+
* as skills; the main session dispatches them through the `subagent` tool and
|
|
15
|
+
* trellis-agent-implement / trellis-agent-check get the pull-based prelude.
|
|
18
16
|
*/
|
|
17
|
+
import { type AgentTemplate } from "../template-utils.js";
|
|
18
|
+
/** Source role prompts; the configurator installs collision-free DSH skills. */
|
|
19
|
+
export declare function getAllAgents(): AgentTemplate[];
|
|
19
20
|
/** Operator guide copied to `.dsh/DSH.md`. */
|
|
20
21
|
export declare function getDshGuide(): string;
|
|
21
22
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/dsh/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/dsh/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAwB,KAAK,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAIhF,gFAAgF;AAChF,wBAAgB,YAAY,IAAI,aAAa,EAAE,CAE9C;AAED,8CAA8C;AAC9C,wBAAgB,WAAW,IAAI,MAAM,CAEpC"}
|
|
@@ -1,23 +1,25 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* DeepSeek Harness (dsh) template module.
|
|
3
3
|
*
|
|
4
|
-
* dsh is a class-2 pull-based
|
|
5
|
-
* - Workflow
|
|
6
|
-
*
|
|
4
|
+
* dsh is a class-2 pull-based platform:
|
|
5
|
+
* - Workflow/bundled skills go to the shared `.agents/skills/` root via the
|
|
6
|
+
* neutral resolver (byte-identical to Codex/Gemini/Pi/Kimi writes).
|
|
7
7
|
* - User-invocable entry points (`trellis-start` / `trellis-continue` /
|
|
8
|
-
* `trellis-finish-work
|
|
9
|
-
*
|
|
10
|
-
* project skill root.
|
|
8
|
+
* `trellis-finish-work`) and Trellis role prompts live under
|
|
9
|
+
* `.dsh/skills/trellis-agent-<role>/SKILL.md`.
|
|
11
10
|
* - Operator guide `.dsh/DSH.md`.
|
|
12
11
|
*
|
|
13
|
-
* dsh has no
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
* workflow skills.
|
|
12
|
+
* dsh has no project-level hooks/settings file Trellis may write and no
|
|
13
|
+
* declarative custom sub-agent definitions, so the Trellis agent prompts ship
|
|
14
|
+
* as skills; the main session dispatches them through the `subagent` tool and
|
|
15
|
+
* trellis-agent-implement / trellis-agent-check get the pull-based prelude.
|
|
18
16
|
*/
|
|
19
17
|
import { createTemplateReader } from "../template-utils.js";
|
|
20
|
-
const { readTemplate } = createTemplateReader(import.meta.url);
|
|
18
|
+
const { listMdAgents, readTemplate } = createTemplateReader(import.meta.url);
|
|
19
|
+
/** Source role prompts; the configurator installs collision-free DSH skills. */
|
|
20
|
+
export function getAllAgents() {
|
|
21
|
+
return listMdAgents();
|
|
22
|
+
}
|
|
21
23
|
/** Operator guide copied to `.dsh/DSH.md`. */
|
|
22
24
|
export function getDshGuide() {
|
|
23
25
|
return readTemplate("DSH.md");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/dsh/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/dsh/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,oBAAoB,EAAsB,MAAM,sBAAsB,CAAC;AAEhF,MAAM,EAAE,YAAY,EAAE,YAAY,EAAE,GAAG,oBAAoB,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAE7E,gFAAgF;AAChF,MAAM,UAAU,YAAY;IAC1B,OAAO,YAAY,EAAE,CAAC;AACxB,CAAC;AAED,8CAA8C;AAC9C,MAAM,UAAU,WAAW;IACzB,OAAO,YAAY,CAAC,QAAQ,CAAC,CAAC;AAChC,CAAC"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/* global process */
|
|
2
|
+
/**
|
|
3
|
+
* Trellis Dynamic Spec Injection Plugin
|
|
4
|
+
*
|
|
5
|
+
* OpenCode tool hooks cannot add context to the current model turn. A FULL
|
|
6
|
+
* spec emission therefore blocks the write once with a model-visible error;
|
|
7
|
+
* the shared Python engine records the emission, so the model's retry proceeds.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { execFileSync } from "child_process";
|
|
11
|
+
import { existsSync } from "fs";
|
|
12
|
+
import { join } from "path";
|
|
13
|
+
import { debugLog } from "../lib/trellis-context.js";
|
|
14
|
+
|
|
15
|
+
const PYTHON_CMD = process.platform === "win32" ? "python" : "python3";
|
|
16
|
+
|
|
17
|
+
function runSpecHook(directory, payload) {
|
|
18
|
+
const hookPath = join(
|
|
19
|
+
directory,
|
|
20
|
+
".opencode",
|
|
21
|
+
"hooks",
|
|
22
|
+
"inject-spec-context.py",
|
|
23
|
+
);
|
|
24
|
+
if (!existsSync(hookPath)) return null;
|
|
25
|
+
|
|
26
|
+
try {
|
|
27
|
+
const stdout = execFileSync(PYTHON_CMD, [hookPath], {
|
|
28
|
+
cwd: directory,
|
|
29
|
+
encoding: "utf-8",
|
|
30
|
+
input: JSON.stringify(payload),
|
|
31
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
32
|
+
timeout: 15000,
|
|
33
|
+
});
|
|
34
|
+
if (!stdout.trim()) return null;
|
|
35
|
+
const parsed = JSON.parse(stdout);
|
|
36
|
+
return parsed?.hookSpecificOutput ?? null;
|
|
37
|
+
} catch (error) {
|
|
38
|
+
debugLog(
|
|
39
|
+
"spec",
|
|
40
|
+
"Spec hook failed open:",
|
|
41
|
+
error instanceof Error ? error.message : String(error),
|
|
42
|
+
);
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function buildToolPayload(directory, input, output) {
|
|
48
|
+
const tool = input?.tool?.toLowerCase();
|
|
49
|
+
const args = output?.args;
|
|
50
|
+
if (!args) return null;
|
|
51
|
+
|
|
52
|
+
if (tool === "edit" || tool === "write") {
|
|
53
|
+
if (typeof args.filePath !== "string" || !args.filePath.trim()) return null;
|
|
54
|
+
return {
|
|
55
|
+
hook_event_name: "PreToolUse",
|
|
56
|
+
session_id: input?.sessionID,
|
|
57
|
+
cwd: directory,
|
|
58
|
+
tool_name: tool === "edit" ? "Edit" : "Write",
|
|
59
|
+
tool_input: { file_path: args.filePath },
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (tool === "apply_patch") {
|
|
64
|
+
if (typeof args.patchText !== "string" || !args.patchText.trim())
|
|
65
|
+
return null;
|
|
66
|
+
return {
|
|
67
|
+
hook_event_name: "PreToolUse",
|
|
68
|
+
session_id: input?.sessionID,
|
|
69
|
+
cwd: directory,
|
|
70
|
+
tool_name: "apply_patch",
|
|
71
|
+
tool_input: { command: args.patchText },
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return null;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export default async ({ directory }) => ({
|
|
79
|
+
event: async ({ event }) => {
|
|
80
|
+
if (
|
|
81
|
+
process.env.TRELLIS_HOOKS === "0" ||
|
|
82
|
+
process.env.TRELLIS_DISABLE_HOOKS === "1"
|
|
83
|
+
) {
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
if (event?.type !== "session.compacted" || !event?.properties?.sessionID) {
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
runSpecHook(directory, {
|
|
91
|
+
hook_event_name: "SessionStart",
|
|
92
|
+
session_id: event.properties.sessionID,
|
|
93
|
+
source: "compact",
|
|
94
|
+
cwd: directory,
|
|
95
|
+
});
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
"tool.execute.before": async (input, output) => {
|
|
99
|
+
if (
|
|
100
|
+
process.env.TRELLIS_HOOKS === "0" ||
|
|
101
|
+
process.env.TRELLIS_DISABLE_HOOKS === "1"
|
|
102
|
+
) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const payload = buildToolPayload(directory, input, output);
|
|
107
|
+
if (!payload) return;
|
|
108
|
+
|
|
109
|
+
const result = runSpecHook(directory, payload);
|
|
110
|
+
const context = result?.additionalContext;
|
|
111
|
+
if (result?.permissionDecision !== "deny" || typeof context !== "string") {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
throw new Error(
|
|
116
|
+
"Trellis loaded governing specs before this tool call. " +
|
|
117
|
+
"Follow them, then retry the same tool call.\n\n" +
|
|
118
|
+
context,
|
|
119
|
+
);
|
|
120
|
+
},
|
|
121
|
+
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
/* global process */
|
|
1
|
+
/* global process, console */
|
|
2
2
|
/**
|
|
3
3
|
* Trellis Workflow State Injection Plugin
|
|
4
4
|
*
|
|
@@ -9,7 +9,12 @@
|
|
|
9
9
|
* `experimental.chat.messages.transform`. Stored history and the TUI
|
|
10
10
|
* are not modified (issue #553). Breadcrumb text is pulled exclusively
|
|
11
11
|
* from the project's workflow.md [workflow-state:STATUS] tag blocks —
|
|
12
|
-
* workflow.md is the single source of truth.
|
|
12
|
+
* workflow.md is the single source of truth. When the active task's
|
|
13
|
+
* task.json selects a workflow variant ("workflow": "<id>"), the tag
|
|
14
|
+
* blocks are read from .trellis/workflows/<id>.md instead (missing
|
|
15
|
+
* variant file → one stderr warning + fallback through personal/team/global
|
|
16
|
+
* defaults; see
|
|
17
|
+
* resolveWorkflowMd). There are no fallback
|
|
13
18
|
* tables in this plugin: when workflow.md is missing or a tag is
|
|
14
19
|
* absent, the breadcrumb degrades to a generic
|
|
15
20
|
* "Refer to workflow.md for current step." line so users see (and fix)
|
|
@@ -21,7 +26,7 @@
|
|
|
21
26
|
* - task.json malformed or missing status
|
|
22
27
|
*/
|
|
23
28
|
|
|
24
|
-
import { existsSync, readFileSync } from "fs"
|
|
29
|
+
import { existsSync, readFileSync, statSync } from "fs"
|
|
25
30
|
import { join } from "path"
|
|
26
31
|
import {
|
|
27
32
|
MESSAGES_TRANSFORM_HOOK,
|
|
@@ -111,17 +116,113 @@ function promptHasSkipKeyword(text, keyword) {
|
|
|
111
116
|
return pattern.test(text)
|
|
112
117
|
}
|
|
113
118
|
|
|
119
|
+
// Per-task workflow selection (mirrors the Python resolver in
|
|
120
|
+
// .trellis/scripts/common/workflow_selection.py). Ids are restricted to
|
|
121
|
+
// [A-Za-z0-9_-]+ so a task.json value can never traverse outside
|
|
122
|
+
// .trellis/workflows/.
|
|
123
|
+
const WORKFLOW_ID_RE = /^[A-Za-z0-9_-]+$/
|
|
124
|
+
|
|
125
|
+
// Map a workflow id to its .trellis/workflows/<id>.md file if valid and
|
|
126
|
+
// present, else "". Shared by every resolution layer. Never throws.
|
|
127
|
+
function libraryVariant(directory, workflowId) {
|
|
128
|
+
if (!workflowId || !WORKFLOW_ID_RE.test(workflowId)) return ""
|
|
129
|
+
const variantPath = join(directory, ".trellis", "workflows", `${workflowId}.md`)
|
|
130
|
+
try {
|
|
131
|
+
if (statSync(variantPath).isFile()) return variantPath
|
|
132
|
+
} catch {
|
|
133
|
+
// ENOENT etc. — missing variant file.
|
|
134
|
+
}
|
|
135
|
+
return ""
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Personal override id from the gitignored .developer file (`workflow=<id>`
|
|
139
|
+
// line). "" when absent/unreadable. Never throws.
|
|
140
|
+
function developerWorkflowId(directory) {
|
|
141
|
+
try {
|
|
142
|
+
const text = readFileSync(join(directory, ".trellis", ".developer"), "utf-8")
|
|
143
|
+
for (const line of text.split(/\r?\n/)) {
|
|
144
|
+
if (line.startsWith("workflow=")) return line.slice("workflow=".length).trim()
|
|
145
|
+
}
|
|
146
|
+
} catch {
|
|
147
|
+
// No .developer / unreadable — no personal override.
|
|
148
|
+
}
|
|
149
|
+
return ""
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Team-shared default id from config.yaml's top-level `default_workflow:` key.
|
|
153
|
+
// Commented lines (starting with '#') are naturally excluded by the anchor.
|
|
154
|
+
// "" when unset/unreadable. Never throws.
|
|
155
|
+
const DEFAULT_WORKFLOW_RE = /^default_workflow:\s*(['"]?)([A-Za-z0-9_-]+)\1\s*(?:#.*)?$/m
|
|
156
|
+
function configDefaultWorkflowId(directory) {
|
|
157
|
+
try {
|
|
158
|
+
const text = readFileSync(join(directory, ".trellis", "config.yaml"), "utf-8")
|
|
159
|
+
const m = text.match(DEFAULT_WORKFLOW_RE)
|
|
160
|
+
if (m) return m[2]
|
|
161
|
+
} catch {
|
|
162
|
+
// No config.yaml / unreadable — no team default.
|
|
163
|
+
}
|
|
164
|
+
return ""
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Resolve which workflow markdown file feeds this turn's breadcrumbs.
|
|
169
|
+
*
|
|
170
|
+
* Precedence (identical across all Trellis consumers), highest to lowest —
|
|
171
|
+
* each layer resolves an id to .trellis/workflows/<id>.md and falls through
|
|
172
|
+
* when unset, invalid, or naming a missing file:
|
|
173
|
+
* 1. Per-task pin — active task's task.json "workflow" (session-bound; a
|
|
174
|
+
* bad id / missing file warns once on stderr, then falls through).
|
|
175
|
+
* 2. Personal — .developer `workflow=<id>` (gitignored, per-developer).
|
|
176
|
+
* 3. Team default — config.yaml `default_workflow` (git-tracked, shared).
|
|
177
|
+
* 4. Global — .trellis/workflow.md.
|
|
178
|
+
* With neither a pin nor the personal/team keys set, this is the global path.
|
|
179
|
+
* Never throws.
|
|
180
|
+
*/
|
|
181
|
+
function resolveWorkflowMd(ctx, directory, platformInput) {
|
|
182
|
+
const globalPath = join(directory, ".trellis", "workflow.md")
|
|
183
|
+
|
|
184
|
+
// 1. Per-task pin (session-bound explicit choice).
|
|
185
|
+
let workflowId = ""
|
|
186
|
+
try {
|
|
187
|
+
const active = ctx.getActiveTask(platformInput)
|
|
188
|
+
if (active.taskPath && !active.stale) {
|
|
189
|
+
const taskDir = ctx.resolveTaskDir(active.taskPath)
|
|
190
|
+
if (taskDir) {
|
|
191
|
+
const data = JSON.parse(readFileSync(join(taskDir, "task.json"), "utf-8"))
|
|
192
|
+
if (data && typeof data.workflow === "string") workflowId = data.workflow
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
} catch {
|
|
196
|
+
// No active task / unreadable task.json — not a selection.
|
|
197
|
+
workflowId = ""
|
|
198
|
+
}
|
|
199
|
+
if (workflowId) {
|
|
200
|
+
const pinned = libraryVariant(directory, workflowId)
|
|
201
|
+
if (pinned) return pinned
|
|
202
|
+
console.error(
|
|
203
|
+
`Warning: active task selects workflow ${JSON.stringify(workflowId)} but .trellis/workflows/ has no matching file; using default workflow resolution`,
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// 2. Personal override, then 3. team default — silent on miss (defaults,
|
|
208
|
+
// not an explicit per-task choice). 4. Global fallback.
|
|
209
|
+
const personal = libraryVariant(directory, developerWorkflowId(directory))
|
|
210
|
+
if (personal) return personal
|
|
211
|
+
const team = libraryVariant(directory, configDefaultWorkflowId(directory))
|
|
212
|
+
if (team) return team
|
|
213
|
+
return globalPath
|
|
214
|
+
}
|
|
215
|
+
|
|
114
216
|
/**
|
|
115
|
-
* Parse workflow
|
|
217
|
+
* Parse the resolved workflow markdown for [workflow-state:STATUS] blocks.
|
|
116
218
|
*
|
|
117
|
-
* Returns {status: body}. workflow
|
|
118
|
-
* there are no fallback tables here. Missing tags (or a missing /
|
|
119
|
-
* unreadable workflow
|
|
219
|
+
* Returns {status: body}. The workflow file is the single source of
|
|
220
|
+
* truth — there are no fallback tables here. Missing tags (or a missing /
|
|
221
|
+
* unreadable workflow file) fall back to a generic line in
|
|
120
222
|
* buildBreadcrumb so users see the broken state and fix workflow.md
|
|
121
223
|
* rather than the plugin silently masking it.
|
|
122
224
|
*/
|
|
123
|
-
function loadBreadcrumbs(
|
|
124
|
-
const workflowPath = join(directory, ".trellis", "workflow.md")
|
|
225
|
+
function loadBreadcrumbs(workflowPath) {
|
|
125
226
|
if (!existsSync(workflowPath)) return {}
|
|
126
227
|
let content
|
|
127
228
|
try {
|
|
@@ -228,7 +329,7 @@ export default async ({ directory }) => {
|
|
|
228
329
|
return
|
|
229
330
|
}
|
|
230
331
|
|
|
231
|
-
const templates = loadBreadcrumbs(directory)
|
|
332
|
+
const templates = loadBreadcrumbs(resolveWorkflowMd(ctx, directory, platformInput))
|
|
232
333
|
const task = getActiveTask(ctx, platformInput)
|
|
233
334
|
const breadcrumb = task
|
|
234
335
|
? buildBreadcrumb(task.id, task.status, templates, task.candidates)
|