@mindfoldhq/trellis 0.5.0-beta.8 → 0.5.0-rc.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/README.md +60 -95
- package/dist/cli/index.js +7 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/init.d.ts +13 -0
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +474 -210
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +295 -54
- package/dist/commands/update.js.map +1 -1
- package/dist/configurators/antigravity.d.ts.map +1 -1
- package/dist/configurators/antigravity.js +2 -8
- package/dist/configurators/antigravity.js.map +1 -1
- package/dist/configurators/claude.d.ts.map +1 -1
- package/dist/configurators/claude.js +4 -10
- package/dist/configurators/claude.js.map +1 -1
- package/dist/configurators/codebuddy.d.ts.map +1 -1
- package/dist/configurators/codebuddy.js +3 -3
- package/dist/configurators/codebuddy.js.map +1 -1
- package/dist/configurators/codex.d.ts.map +1 -1
- package/dist/configurators/codex.js +5 -13
- package/dist/configurators/codex.js.map +1 -1
- package/dist/configurators/copilot.d.ts.map +1 -1
- package/dist/configurators/copilot.js +5 -19
- package/dist/configurators/copilot.js.map +1 -1
- package/dist/configurators/cursor.d.ts.map +1 -1
- package/dist/configurators/cursor.js +3 -3
- package/dist/configurators/cursor.js.map +1 -1
- package/dist/configurators/droid.d.ts.map +1 -1
- package/dist/configurators/droid.js +3 -3
- package/dist/configurators/droid.js.map +1 -1
- package/dist/configurators/gemini.d.ts.map +1 -1
- package/dist/configurators/gemini.js +3 -5
- package/dist/configurators/gemini.js.map +1 -1
- package/dist/configurators/index.d.ts.map +1 -1
- package/dist/configurators/index.js +44 -55
- package/dist/configurators/index.js.map +1 -1
- package/dist/configurators/kilo.d.ts.map +1 -1
- package/dist/configurators/kilo.js +2 -8
- package/dist/configurators/kilo.js.map +1 -1
- package/dist/configurators/kiro.d.ts.map +1 -1
- package/dist/configurators/kiro.js +3 -3
- package/dist/configurators/kiro.js.map +1 -1
- package/dist/configurators/opencode.d.ts.map +1 -1
- package/dist/configurators/opencode.js +7 -4
- package/dist/configurators/opencode.js.map +1 -1
- package/dist/configurators/pi.d.ts +3 -0
- package/dist/configurators/pi.d.ts.map +1 -0
- package/dist/configurators/pi.js +44 -0
- package/dist/configurators/pi.js.map +1 -0
- package/dist/configurators/qoder.d.ts +7 -6
- package/dist/configurators/qoder.d.ts.map +1 -1
- package/dist/configurators/qoder.js +18 -12
- package/dist/configurators/qoder.js.map +1 -1
- package/dist/configurators/shared.d.ts +30 -6
- package/dist/configurators/shared.d.ts.map +1 -1
- package/dist/configurators/shared.js +65 -15
- package/dist/configurators/shared.js.map +1 -1
- package/dist/configurators/windsurf.d.ts.map +1 -1
- package/dist/configurators/windsurf.js +2 -8
- package/dist/configurators/windsurf.js.map +1 -1
- package/dist/constants/paths.d.ts +2 -0
- package/dist/constants/paths.d.ts.map +1 -1
- package/dist/constants/paths.js +2 -0
- package/dist/constants/paths.js.map +1 -1
- package/dist/migrations/manifests/0.5.0-beta.0.json +2 -0
- package/dist/migrations/manifests/0.5.0-beta.10.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.11.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.12.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.13.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.14.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.15.json +116 -0
- package/dist/migrations/manifests/0.5.0-beta.16.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.17.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.18.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.19.json +9 -0
- package/dist/migrations/manifests/0.5.0-beta.5.json +2 -0
- package/dist/migrations/manifests/0.5.0-beta.9.json +48 -0
- package/dist/migrations/manifests/0.5.0-rc.0.json +9 -0
- package/dist/templates/claude/agents/trellis-research.md +1 -1
- package/dist/templates/claude/settings.json +0 -4
- package/dist/templates/codebuddy/agents/trellis-research.md +1 -1
- package/dist/templates/codex/agents/trellis-research.toml +3 -2
- package/dist/templates/codex/hooks/session-start.py +126 -26
- package/dist/templates/codex/skills/finish-work/SKILL.md +41 -109
- package/dist/templates/codex/skills/start/SKILL.md +12 -9
- package/dist/templates/common/bundled-skills/trellis-meta/SKILL.md +73 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/add-project-local-conventions.md +83 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-agents.md +54 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-context-loading.md +81 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-hooks.md +57 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-skills-or-commands.md +78 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-spec-structure.md +83 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-task-lifecycle.md +90 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/change-workflow.md +64 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/customize-local/overview.md +55 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/context-injection.md +68 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/generated-files.md +80 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/overview.md +51 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/spec-system.md +102 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/task-system.md +101 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workflow.md +75 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/local-architecture/workspace-memory.md +71 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/agents.md +79 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/hooks-and-settings.md +69 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/overview.md +59 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/platform-map.md +74 -0
- package/dist/templates/common/bundled-skills/trellis-meta/references/platform-files/skills-and-commands.md +83 -0
- package/dist/templates/common/commands/continue.md +9 -5
- package/dist/templates/common/commands/finish-work.md +34 -10
- package/dist/templates/common/index.d.ts +22 -2
- package/dist/templates/common/index.d.ts.map +1 -1
- package/dist/templates/common/index.js +53 -4
- package/dist/templates/common/index.js.map +1 -1
- package/dist/templates/common/skills/brainstorm.md +50 -4
- package/dist/templates/copilot/hooks/session-start.py +127 -30
- package/dist/templates/copilot/prompts/finish-work.prompt.md +44 -112
- package/dist/templates/copilot/prompts/start.prompt.md +12 -9
- package/dist/templates/cursor/agents/trellis-check.md +1 -1
- package/dist/templates/cursor/agents/trellis-implement.md +1 -1
- package/dist/templates/cursor/agents/trellis-research.md +2 -2
- package/dist/templates/cursor/hooks.json +7 -1
- package/dist/templates/droid/droids/trellis-research.md +1 -1
- package/dist/templates/extract.d.ts +6 -0
- package/dist/templates/extract.d.ts.map +1 -1
- package/dist/templates/extract.js +14 -0
- package/dist/templates/extract.js.map +1 -1
- package/dist/templates/gemini/agents/trellis-research.md +1 -1
- package/dist/templates/kiro/agents/trellis-research.json +1 -1
- package/dist/templates/markdown/agents.md +19 -12
- package/dist/templates/markdown/gitignore.txt +3 -0
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md.txt +24 -0
- package/dist/templates/opencode/agents/trellis-check.md +1 -1
- package/dist/templates/opencode/agents/trellis-implement.md +7 -4
- package/dist/templates/opencode/agents/trellis-research.md +2 -2
- package/dist/templates/opencode/lib/trellis-context.js +100 -13
- package/dist/templates/opencode/plugins/inject-subagent-context.js +70 -5
- package/dist/templates/opencode/plugins/inject-workflow-state.js +38 -44
- package/dist/templates/opencode/plugins/session-start.js +76 -31
- package/dist/templates/pi/agents/trellis-check.md +28 -0
- package/dist/templates/pi/agents/trellis-implement.md +33 -0
- package/dist/templates/pi/agents/trellis-research.md +25 -0
- package/dist/templates/pi/extensions/trellis/index.ts.txt +997 -0
- package/dist/templates/pi/index.d.ts +5 -0
- package/dist/templates/pi/index.d.ts.map +1 -0
- package/dist/templates/pi/index.js +12 -0
- package/dist/templates/pi/index.js.map +1 -0
- package/dist/templates/pi/settings.json +12 -0
- package/dist/templates/qoder/agents/trellis-research.md +1 -1
- package/dist/templates/shared-hooks/index.d.ts +31 -0
- package/dist/templates/shared-hooks/index.d.ts.map +1 -1
- package/dist/templates/shared-hooks/index.js +59 -0
- package/dist/templates/shared-hooks/index.js.map +1 -1
- package/dist/templates/shared-hooks/inject-shell-session-context.py +180 -0
- package/dist/templates/shared-hooks/inject-subagent-context.py +156 -27
- package/dist/templates/shared-hooks/inject-workflow-state.py +85 -92
- package/dist/templates/shared-hooks/session-start.py +232 -36
- package/dist/templates/trellis/config.yaml +6 -0
- package/dist/templates/trellis/gitignore.txt +3 -0
- package/dist/templates/trellis/index.d.ts +1 -1
- package/dist/templates/trellis/index.d.ts.map +1 -1
- package/dist/templates/trellis/index.js +2 -2
- package/dist/templates/trellis/index.js.map +1 -1
- package/dist/templates/trellis/scripts/common/__init__.py +8 -0
- package/dist/templates/trellis/scripts/common/active_task.py +593 -0
- package/dist/templates/trellis/scripts/common/cli_adapter.py +72 -14
- package/dist/templates/trellis/scripts/common/paths.py +61 -58
- package/dist/templates/trellis/scripts/common/session_context.py +12 -0
- package/dist/templates/trellis/scripts/common/task_context.py +27 -194
- package/dist/templates/trellis/scripts/common/task_store.py +102 -26
- package/dist/templates/trellis/scripts/common/tasks.py +4 -1
- package/dist/templates/trellis/scripts/common/types.py +0 -2
- package/dist/templates/trellis/scripts/common/workflow_phase.py +15 -3
- package/dist/templates/trellis/scripts/task.py +99 -34
- package/dist/templates/trellis/workflow.md +332 -64
- package/dist/types/ai-tools.d.ts +12 -3
- package/dist/types/ai-tools.d.ts.map +1 -1
- package/dist/types/ai-tools.js +29 -0
- package/dist/types/ai-tools.js.map +1 -1
- package/dist/utils/file-writer.d.ts.map +1 -1
- package/dist/utils/file-writer.js +7 -2
- package/dist/utils/file-writer.js.map +1 -1
- package/dist/utils/posix.d.ts +13 -0
- package/dist/utils/posix.d.ts.map +1 -0
- package/dist/utils/posix.js +15 -0
- package/dist/utils/posix.js.map +1 -0
- package/dist/utils/project-detector.d.ts +2 -0
- package/dist/utils/project-detector.d.ts.map +1 -1
- package/dist/utils/project-detector.js +120 -11
- package/dist/utils/project-detector.js.map +1 -1
- package/dist/utils/task-json.d.ts +46 -0
- package/dist/utils/task-json.d.ts.map +1 -0
- package/dist/utils/task-json.js +49 -0
- package/dist/utils/task-json.js.map +1 -0
- package/dist/utils/template-fetcher.d.ts +22 -6
- package/dist/utils/template-fetcher.d.ts.map +1 -1
- package/dist/utils/template-fetcher.js +405 -27
- package/dist/utils/template-fetcher.js.map +1 -1
- package/dist/utils/template-hash.d.ts +22 -3
- package/dist/utils/template-hash.d.ts.map +1 -1
- package/dist/utils/template-hash.js +99 -19
- package/dist/utils/template-hash.js.map +1 -1
- package/package.json +7 -7
- package/dist/templates/markdown/spec/backend/directory-structure.md +0 -292
- package/dist/templates/markdown/spec/backend/index.md +0 -40
- package/dist/templates/markdown/spec/backend/script-conventions.md +0 -742
- package/dist/templates/markdown/spec/guides/code-reuse-thinking-guide.md +0 -118
- package/dist/templates/markdown/spec/guides/cross-platform-thinking-guide.md +0 -394
- package/dist/templates/shared-hooks/statusline.py +0 -218
- package/dist/templates/trellis/scripts/create_bootstrap.py +0 -298
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type AgentTemplate, type HookTemplate } from "../template-utils.js";
|
|
2
|
+
export declare function getAllAgents(): AgentTemplate[];
|
|
3
|
+
export declare function getSettingsTemplate(): HookTemplate;
|
|
4
|
+
export declare function getExtensionTemplate(): string;
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/pi/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,sBAAsB,CAAC;AAM9B,wBAAgB,YAAY,IAAI,aAAa,EAAE,CAE9C;AAED,wBAAgB,mBAAmB,IAAI,YAAY,CAElD;AAED,wBAAgB,oBAAoB,IAAI,MAAM,CAE7C"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { createTemplateReader, } from "../template-utils.js";
|
|
2
|
+
const { listMdAgents, getSettings, readTemplate } = createTemplateReader(import.meta.url);
|
|
3
|
+
export function getAllAgents() {
|
|
4
|
+
return listMdAgents();
|
|
5
|
+
}
|
|
6
|
+
export function getSettingsTemplate() {
|
|
7
|
+
return getSettings();
|
|
8
|
+
}
|
|
9
|
+
export function getExtensionTemplate() {
|
|
10
|
+
return readTemplate("extensions/trellis/index.ts.txt");
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/pi/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,oBAAoB,GAGrB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,GAAG,oBAAoB,CACtE,MAAM,CAAC,IAAI,CAAC,GAAG,CAChB,CAAC;AAEF,MAAM,UAAU,YAAY;IAC1B,OAAO,YAAY,EAAE,CAAC;AACxB,CAAC;AAED,MAAM,UAAU,mBAAmB;IACjC,OAAO,WAAW,EAAE,CAAC;AACvB,CAAC;AAED,MAAM,UAAU,oBAAoB;IAClC,OAAO,YAAY,CAAC,iCAAiC,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -29,7 +29,7 @@ Conversations get compacted; files don't. Every research output MUST end up as a
|
|
|
29
29
|
|
|
30
30
|
### Step 1: Resolve Current Task
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
Run `python3 ./.trellis/scripts/task.py current --source` → active task path. If no active task is set, ask the user where to write output; do NOT guess.
|
|
33
33
|
|
|
34
34
|
Ensure `{TASK_DIR}/research/` exists:
|
|
35
35
|
|
|
@@ -11,9 +11,40 @@ export interface HookScript {
|
|
|
11
11
|
/** Script content — no placeholders, ready to write directly */
|
|
12
12
|
content: string;
|
|
13
13
|
}
|
|
14
|
+
export type SharedHookName = "session-start.py" | "inject-shell-session-context.py" | "inject-workflow-state.py" | "inject-subagent-context.py";
|
|
15
|
+
export type SharedHookPlatform = "claude" | "cursor" | "codex" | "gemini" | "qoder" | "copilot" | "codebuddy" | "droid" | "kiro";
|
|
16
|
+
/**
|
|
17
|
+
* Which shared hooks each platform actually invokes. Single source of truth
|
|
18
|
+
* for shared-hook distribution — both `writeSharedHooks` (runtime install)
|
|
19
|
+
* and `collectSharedHooks` (`trellis update` diff) read from this table.
|
|
20
|
+
*
|
|
21
|
+
* Routing rules encoded here:
|
|
22
|
+
* - `session-start.py` — shipped by every platform with a SessionStart
|
|
23
|
+
* hook event *except* codex + copilot, which bundle a platform-specific
|
|
24
|
+
* session-start.py under their own template dirs.
|
|
25
|
+
* - `inject-workflow-state.py` — every platform with a UserPromptSubmit
|
|
26
|
+
* (or equivalent) event. Kiro + codex self-included; platforms without
|
|
27
|
+
* per-turn main-session hooks are excluded.
|
|
28
|
+
* - `inject-subagent-context.py` — class-1 (push-based) platforms only.
|
|
29
|
+
* Class-2 (pull-based) platforms (codex, copilot, gemini, qoder) can't
|
|
30
|
+
* have hooks mutate sub-agent prompts — their sub-agents load context
|
|
31
|
+
* via a prelude instead.
|
|
32
|
+
* - Kiro supports only `agentSpawn` (no SessionStart / UserPromptSubmit
|
|
33
|
+
* event), so it takes just `inject-subagent-context.py`.
|
|
34
|
+
* - Claude Code `statusLine` is intentionally not installed by default.
|
|
35
|
+
* Users can add their own statusLine command in `.claude/settings.json`
|
|
36
|
+
* without Trellis owning a generated hook file.
|
|
37
|
+
*/
|
|
38
|
+
export declare const SHARED_HOOKS_BY_PLATFORM: Record<SharedHookPlatform, readonly SharedHookName[]>;
|
|
14
39
|
/**
|
|
15
40
|
* Get all shared hook scripts. Content is platform-independent and can be
|
|
16
41
|
* written directly without placeholder resolution.
|
|
17
42
|
*/
|
|
18
43
|
export declare function getSharedHookScripts(): HookScript[];
|
|
44
|
+
/**
|
|
45
|
+
* Get the shared hook scripts that a given platform actually registers.
|
|
46
|
+
* Drives both `writeSharedHooks` and `collectSharedHooks` so distribution
|
|
47
|
+
* never drifts from the per-platform capability declared above.
|
|
48
|
+
*/
|
|
49
|
+
export declare function getSharedHookScriptsForPlatform(platform: SharedHookPlatform): HookScript[];
|
|
19
50
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/shared-hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,UAAU,EAAE,CAWnD"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/templates/shared-hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAaH,MAAM,WAAW,UAAU;IACzB,0CAA0C;IAC1C,IAAI,EAAE,MAAM,CAAC;IACb,gEAAgE;IAChE,OAAO,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,MAAM,cAAc,GACtB,kBAAkB,GAClB,iCAAiC,GACjC,0BAA0B,GAC1B,4BAA4B,CAAC;AAEjC,MAAM,MAAM,kBAAkB,GAC1B,QAAQ,GACR,QAAQ,GACR,OAAO,GACP,QAAQ,GACR,OAAO,GACP,SAAS,GACT,WAAW,GACX,OAAO,GACP,MAAM,CAAC;AAEX;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,wBAAwB,EAAE,MAAM,CAC3C,kBAAkB,EAClB,SAAS,cAAc,EAAE,CA4B1B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,UAAU,EAAE,CAWnD;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,kBAAkB,GAC3B,UAAU,EAAE,CAGd"}
|
|
@@ -13,6 +13,56 @@ const __dirname = dirname(__filename);
|
|
|
13
13
|
function readTemplate(relativePath) {
|
|
14
14
|
return readFileSync(join(__dirname, relativePath), "utf-8");
|
|
15
15
|
}
|
|
16
|
+
/**
|
|
17
|
+
* Which shared hooks each platform actually invokes. Single source of truth
|
|
18
|
+
* for shared-hook distribution — both `writeSharedHooks` (runtime install)
|
|
19
|
+
* and `collectSharedHooks` (`trellis update` diff) read from this table.
|
|
20
|
+
*
|
|
21
|
+
* Routing rules encoded here:
|
|
22
|
+
* - `session-start.py` — shipped by every platform with a SessionStart
|
|
23
|
+
* hook event *except* codex + copilot, which bundle a platform-specific
|
|
24
|
+
* session-start.py under their own template dirs.
|
|
25
|
+
* - `inject-workflow-state.py` — every platform with a UserPromptSubmit
|
|
26
|
+
* (or equivalent) event. Kiro + codex self-included; platforms without
|
|
27
|
+
* per-turn main-session hooks are excluded.
|
|
28
|
+
* - `inject-subagent-context.py` — class-1 (push-based) platforms only.
|
|
29
|
+
* Class-2 (pull-based) platforms (codex, copilot, gemini, qoder) can't
|
|
30
|
+
* have hooks mutate sub-agent prompts — their sub-agents load context
|
|
31
|
+
* via a prelude instead.
|
|
32
|
+
* - Kiro supports only `agentSpawn` (no SessionStart / UserPromptSubmit
|
|
33
|
+
* event), so it takes just `inject-subagent-context.py`.
|
|
34
|
+
* - Claude Code `statusLine` is intentionally not installed by default.
|
|
35
|
+
* Users can add their own statusLine command in `.claude/settings.json`
|
|
36
|
+
* without Trellis owning a generated hook file.
|
|
37
|
+
*/
|
|
38
|
+
export const SHARED_HOOKS_BY_PLATFORM = {
|
|
39
|
+
claude: [
|
|
40
|
+
"session-start.py",
|
|
41
|
+
"inject-workflow-state.py",
|
|
42
|
+
"inject-subagent-context.py",
|
|
43
|
+
],
|
|
44
|
+
cursor: [
|
|
45
|
+
"session-start.py",
|
|
46
|
+
"inject-shell-session-context.py",
|
|
47
|
+
"inject-workflow-state.py",
|
|
48
|
+
"inject-subagent-context.py",
|
|
49
|
+
],
|
|
50
|
+
codex: ["inject-workflow-state.py"],
|
|
51
|
+
gemini: ["session-start.py", "inject-workflow-state.py"],
|
|
52
|
+
qoder: ["session-start.py", "inject-workflow-state.py"],
|
|
53
|
+
copilot: ["inject-workflow-state.py"],
|
|
54
|
+
codebuddy: [
|
|
55
|
+
"session-start.py",
|
|
56
|
+
"inject-workflow-state.py",
|
|
57
|
+
"inject-subagent-context.py",
|
|
58
|
+
],
|
|
59
|
+
droid: [
|
|
60
|
+
"session-start.py",
|
|
61
|
+
"inject-workflow-state.py",
|
|
62
|
+
"inject-subagent-context.py",
|
|
63
|
+
],
|
|
64
|
+
kiro: ["inject-subagent-context.py"],
|
|
65
|
+
};
|
|
16
66
|
/**
|
|
17
67
|
* Get all shared hook scripts. Content is platform-independent and can be
|
|
18
68
|
* written directly without placeholder resolution.
|
|
@@ -27,4 +77,13 @@ export function getSharedHookScripts() {
|
|
|
27
77
|
}
|
|
28
78
|
return scripts;
|
|
29
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Get the shared hook scripts that a given platform actually registers.
|
|
82
|
+
* Drives both `writeSharedHooks` and `collectSharedHooks` so distribution
|
|
83
|
+
* never drifts from the per-platform capability declared above.
|
|
84
|
+
*/
|
|
85
|
+
export function getSharedHookScriptsForPlatform(platform) {
|
|
86
|
+
const allowed = new Set(SHARED_HOOKS_BY_PLATFORM[platform]);
|
|
87
|
+
return getSharedHookScripts().filter((h) => allowed.has(h.name));
|
|
88
|
+
}
|
|
30
89
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/shared-hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,SAAS,YAAY,CAAC,YAAoB;IACxC,OAAO,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/templates/shared-hooks/index.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;AAEtC,SAAS,YAAY,CAAC,YAAoB;IACxC,OAAO,YAAY,CAAC,IAAI,CAAC,SAAS,EAAE,YAAY,CAAC,EAAE,OAAO,CAAC,CAAC;AAC9D,CAAC;AA0BD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAGjC;IACF,MAAM,EAAE;QACN,kBAAkB;QAClB,0BAA0B;QAC1B,4BAA4B;KAC7B;IACD,MAAM,EAAE;QACN,kBAAkB;QAClB,iCAAiC;QACjC,0BAA0B;QAC1B,4BAA4B;KAC7B;IACD,KAAK,EAAE,CAAC,0BAA0B,CAAC;IACnC,MAAM,EAAE,CAAC,kBAAkB,EAAE,0BAA0B,CAAC;IACxD,KAAK,EAAE,CAAC,kBAAkB,EAAE,0BAA0B,CAAC;IACvD,OAAO,EAAE,CAAC,0BAA0B,CAAC;IACrC,SAAS,EAAE;QACT,kBAAkB;QAClB,0BAA0B;QAC1B,4BAA4B;KAC7B;IACD,KAAK,EAAE;QACL,kBAAkB;QAClB,0BAA0B;QAC1B,4BAA4B;KAC7B;IACD,IAAI,EAAE,CAAC,4BAA4B,CAAC;CACrC,CAAC;AAEF;;;GAGG;AACH,MAAM,UAAU,oBAAoB;IAClC,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,MAAM,KAAK,GAAG,WAAW,CAAC,SAAS,CAAC;SACjC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;SAChC,IAAI,EAAE,CAAC;IAEV,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,IAAI,EAAE,OAAO,EAAE,YAAY,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC5D,CAAC;IAED,OAAO,OAAO,CAAC;AACjB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,+BAA+B,CAC7C,QAA4B;IAE5B,MAAM,OAAO,GAAG,IAAI,GAAG,CAAS,wBAAwB,CAAC,QAAQ,CAAC,CAAC,CAAC;IACpE,OAAO,oBAAoB,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC;AACnE,CAAC"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Cursor beforeShellExecution hook: bridge conversation identity to task.py.
|
|
3
|
+
|
|
4
|
+
Cursor's shell command environment does not inherit SessionStart data. This
|
|
5
|
+
hook writes a short-lived runtime ticket before Cursor runs a shell command
|
|
6
|
+
that calls `task.py start/current/finish`. The task script then consumes the
|
|
7
|
+
ticket only when it has no native session environment.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import shlex
|
|
15
|
+
import sys
|
|
16
|
+
import time
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
DIR_WORKFLOW = ".trellis"
|
|
22
|
+
DIR_RUNTIME = ".runtime"
|
|
23
|
+
DIR_CURSOR_SHELL = "cursor-shell"
|
|
24
|
+
SESSION_SUBCOMMANDS = {"start", "current", "finish"}
|
|
25
|
+
TICKET_TTL_SECONDS = 30
|
|
26
|
+
CONTEXT_IDENTITY_KEYS = (
|
|
27
|
+
"session_id",
|
|
28
|
+
"sessionId",
|
|
29
|
+
"sessionID",
|
|
30
|
+
"conversation_id",
|
|
31
|
+
"conversationId",
|
|
32
|
+
"conversationID",
|
|
33
|
+
"transcript_path",
|
|
34
|
+
"transcriptPath",
|
|
35
|
+
"transcript",
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def _string_value(value: Any) -> str | None:
|
|
40
|
+
if isinstance(value, str):
|
|
41
|
+
stripped = value.strip()
|
|
42
|
+
return stripped or None
|
|
43
|
+
return None
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def _find_trellis_root(start: Path) -> Path | None:
|
|
47
|
+
current = start.resolve()
|
|
48
|
+
while True:
|
|
49
|
+
if (current / DIR_WORKFLOW).is_dir():
|
|
50
|
+
return current
|
|
51
|
+
if current == current.parent:
|
|
52
|
+
return None
|
|
53
|
+
current = current.parent
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def _runtime_ticket_dir(root: Path) -> Path:
|
|
57
|
+
return root / DIR_WORKFLOW / DIR_RUNTIME / DIR_CURSOR_SHELL
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def _load_active_task_resolver(root: Path):
|
|
61
|
+
scripts_dir = root / DIR_WORKFLOW / "scripts"
|
|
62
|
+
if str(scripts_dir) not in sys.path:
|
|
63
|
+
sys.path.insert(0, str(scripts_dir))
|
|
64
|
+
from common.active_task import resolve_context_key # type: ignore[import-not-found]
|
|
65
|
+
|
|
66
|
+
return resolve_context_key
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _extract_task_subcommands(command: str) -> list[dict[str, str]]:
|
|
70
|
+
try:
|
|
71
|
+
tokens = shlex.split(command, posix=os.name != "nt")
|
|
72
|
+
except ValueError:
|
|
73
|
+
return []
|
|
74
|
+
|
|
75
|
+
subcommands: list[dict[str, str]] = []
|
|
76
|
+
for index, token in enumerate(tokens[:-1]):
|
|
77
|
+
if Path(token.strip("\"'")).name != "task.py":
|
|
78
|
+
continue
|
|
79
|
+
name = tokens[index + 1]
|
|
80
|
+
if name not in SESSION_SUBCOMMANDS:
|
|
81
|
+
continue
|
|
82
|
+
item = {"name": name}
|
|
83
|
+
if name == "start" and index + 2 < len(tokens):
|
|
84
|
+
item["task_ref"] = tokens[index + 2]
|
|
85
|
+
subcommands.append(item)
|
|
86
|
+
return subcommands
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def _cleanup_expired_tickets(ticket_dir: Path, now: float) -> None:
|
|
90
|
+
if not ticket_dir.is_dir():
|
|
91
|
+
return
|
|
92
|
+
for ticket_path in ticket_dir.glob("*.json"):
|
|
93
|
+
try:
|
|
94
|
+
data = json.loads(ticket_path.read_text(encoding="utf-8"))
|
|
95
|
+
except (json.JSONDecodeError, OSError):
|
|
96
|
+
continue
|
|
97
|
+
expires_at = data.get("expires_at_epoch")
|
|
98
|
+
if isinstance(expires_at, (int, float)) and expires_at < now:
|
|
99
|
+
try:
|
|
100
|
+
ticket_path.unlink()
|
|
101
|
+
except OSError:
|
|
102
|
+
pass
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def _has_context_identity(hook_input: dict[str, Any]) -> bool:
|
|
106
|
+
return any(_string_value(hook_input.get(key)) for key in CONTEXT_IDENTITY_KEYS)
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def _write_ticket(
|
|
110
|
+
root: Path,
|
|
111
|
+
hook_input: dict[str, Any],
|
|
112
|
+
context_key: str,
|
|
113
|
+
subcommands: list[dict[str, str]],
|
|
114
|
+
) -> None:
|
|
115
|
+
now = time.time()
|
|
116
|
+
ticket_dir = _runtime_ticket_dir(root)
|
|
117
|
+
ticket_dir.mkdir(parents=True, exist_ok=True)
|
|
118
|
+
_cleanup_expired_tickets(ticket_dir, now)
|
|
119
|
+
|
|
120
|
+
command = _string_value(hook_input.get("command")) or ""
|
|
121
|
+
digest = hashlib.sha256(
|
|
122
|
+
f"{context_key}\0{command}\0{now}".encode("utf-8"),
|
|
123
|
+
).hexdigest()[:16]
|
|
124
|
+
ticket_path = ticket_dir / f"{int(now * 1000)}-{digest}.json"
|
|
125
|
+
|
|
126
|
+
payload = {
|
|
127
|
+
"platform": "cursor",
|
|
128
|
+
"context_key": context_key,
|
|
129
|
+
"conversation_id": _string_value(hook_input.get("conversation_id")),
|
|
130
|
+
"session_id": _string_value(hook_input.get("session_id")),
|
|
131
|
+
"generation_id": _string_value(hook_input.get("generation_id")),
|
|
132
|
+
"cwd": _string_value(hook_input.get("cwd")),
|
|
133
|
+
"command": command,
|
|
134
|
+
"subcommands": subcommands,
|
|
135
|
+
"created_at_epoch": now,
|
|
136
|
+
"expires_at_epoch": now + TICKET_TTL_SECONDS,
|
|
137
|
+
}
|
|
138
|
+
ticket_path.write_text(
|
|
139
|
+
json.dumps(payload, indent=2, ensure_ascii=False) + "\n",
|
|
140
|
+
encoding="utf-8",
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
def main() -> int:
|
|
145
|
+
try:
|
|
146
|
+
hook_input = json.loads(sys.stdin.read())
|
|
147
|
+
except (json.JSONDecodeError, ValueError):
|
|
148
|
+
hook_input = {}
|
|
149
|
+
if not isinstance(hook_input, dict):
|
|
150
|
+
hook_input = {}
|
|
151
|
+
|
|
152
|
+
command = _string_value(hook_input.get("command")) or ""
|
|
153
|
+
subcommands = _extract_task_subcommands(command)
|
|
154
|
+
if not subcommands:
|
|
155
|
+
return 0
|
|
156
|
+
|
|
157
|
+
cwd = Path(_string_value(hook_input.get("cwd")) or os.getcwd())
|
|
158
|
+
root = _find_trellis_root(cwd)
|
|
159
|
+
if root is None:
|
|
160
|
+
return 0
|
|
161
|
+
|
|
162
|
+
if not _has_context_identity(hook_input):
|
|
163
|
+
return 0
|
|
164
|
+
|
|
165
|
+
resolve_context_key = _load_active_task_resolver(root)
|
|
166
|
+
context_key = resolve_context_key(hook_input, platform="cursor")
|
|
167
|
+
if not context_key:
|
|
168
|
+
return 0
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
_write_ticket(root, hook_input, context_key, subcommands)
|
|
172
|
+
except OSError:
|
|
173
|
+
return 0
|
|
174
|
+
|
|
175
|
+
print(json.dumps({"permission": "allow"}, ensure_ascii=False))
|
|
176
|
+
return 0
|
|
177
|
+
|
|
178
|
+
|
|
179
|
+
if __name__ == "__main__":
|
|
180
|
+
sys.exit(main())
|
|
@@ -12,13 +12,14 @@ Core Design Philosophy:
|
|
|
12
12
|
|
|
13
13
|
Trigger: PreToolUse (before Task tool call)
|
|
14
14
|
|
|
15
|
-
Context Source:
|
|
15
|
+
Context Source: Trellis active task resolver points to task directory
|
|
16
16
|
- implement.jsonl - Implement agent dedicated context
|
|
17
17
|
- check.jsonl - Check agent dedicated context
|
|
18
18
|
- prd.md - Requirements document
|
|
19
19
|
- info.md - Technical design
|
|
20
20
|
- codex-review-output.txt - Code Review results
|
|
21
21
|
"""
|
|
22
|
+
from __future__ import annotations
|
|
22
23
|
|
|
23
24
|
# IMPORTANT: Suppress all warnings FIRST
|
|
24
25
|
import warnings
|
|
@@ -28,10 +29,11 @@ import json
|
|
|
28
29
|
import os
|
|
29
30
|
import sys
|
|
30
31
|
from pathlib import Path
|
|
32
|
+
from typing import Any
|
|
31
33
|
|
|
32
34
|
# IMPORTANT: Force stdout to use UTF-8 on Windows
|
|
33
35
|
# This fixes UnicodeEncodeError when outputting non-ASCII characters
|
|
34
|
-
if sys.platform
|
|
36
|
+
if sys.platform.startswith("win"):
|
|
35
37
|
import io as _io
|
|
36
38
|
if hasattr(sys.stdout, "reconfigure"):
|
|
37
39
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace") # type: ignore[union-attr]
|
|
@@ -45,7 +47,6 @@ if sys.platform == "win32":
|
|
|
45
47
|
|
|
46
48
|
DIR_WORKFLOW = ".trellis"
|
|
47
49
|
DIR_SPEC = "spec"
|
|
48
|
-
FILE_CURRENT_TASK = ".current-task"
|
|
49
50
|
FILE_TASK_JSON = "task.json"
|
|
50
51
|
|
|
51
52
|
# =============================================================================
|
|
@@ -77,32 +78,57 @@ def find_repo_root(start_path: str) -> str | None:
|
|
|
77
78
|
return None
|
|
78
79
|
|
|
79
80
|
|
|
80
|
-
def
|
|
81
|
-
""
|
|
82
|
-
|
|
81
|
+
def _detect_platform(input_data: dict) -> str | None:
|
|
82
|
+
if isinstance(input_data.get("cursor_version"), str):
|
|
83
|
+
return "cursor"
|
|
84
|
+
env_map = {
|
|
85
|
+
"CLAUDE_PROJECT_DIR": "claude",
|
|
86
|
+
"CURSOR_PROJECT_DIR": "cursor",
|
|
87
|
+
"CODEBUDDY_PROJECT_DIR": "codebuddy",
|
|
88
|
+
"FACTORY_PROJECT_DIR": "droid",
|
|
89
|
+
"GEMINI_PROJECT_DIR": "gemini",
|
|
90
|
+
"QODER_PROJECT_DIR": "qoder",
|
|
91
|
+
"KIRO_PROJECT_DIR": "kiro",
|
|
92
|
+
"COPILOT_PROJECT_DIR": "copilot",
|
|
93
|
+
}
|
|
94
|
+
for env_name, platform in env_map.items():
|
|
95
|
+
if os.environ.get(env_name):
|
|
96
|
+
return platform
|
|
97
|
+
script_parts = set(Path(sys.argv[0]).parts)
|
|
98
|
+
if ".claude" in script_parts:
|
|
99
|
+
return "claude"
|
|
100
|
+
if ".cursor" in script_parts:
|
|
101
|
+
return "cursor"
|
|
102
|
+
if ".gemini" in script_parts:
|
|
103
|
+
return "gemini"
|
|
104
|
+
if ".qoder" in script_parts:
|
|
105
|
+
return "qoder"
|
|
106
|
+
if ".codebuddy" in script_parts:
|
|
107
|
+
return "codebuddy"
|
|
108
|
+
if ".factory" in script_parts:
|
|
109
|
+
return "droid"
|
|
110
|
+
if ".kiro" in script_parts:
|
|
111
|
+
return "kiro"
|
|
112
|
+
return None
|
|
83
113
|
|
|
84
|
-
Returns:
|
|
85
|
-
Task directory relative path (relative to repo_root)
|
|
86
|
-
None if not set
|
|
87
|
-
"""
|
|
88
|
-
current_task_file = os.path.join(repo_root, DIR_WORKFLOW, FILE_CURRENT_TASK)
|
|
89
|
-
if not os.path.exists(current_task_file):
|
|
90
|
-
return None
|
|
91
114
|
|
|
115
|
+
def get_current_task(repo_root: str, input_data: dict) -> str | None:
|
|
116
|
+
"""Resolve current task directory through the unified active task resolver."""
|
|
117
|
+
scripts_dir = Path(repo_root) / DIR_WORKFLOW / "scripts"
|
|
118
|
+
if str(scripts_dir) not in sys.path:
|
|
119
|
+
sys.path.insert(0, str(scripts_dir))
|
|
92
120
|
try:
|
|
93
|
-
|
|
94
|
-
content = f.read().strip()
|
|
95
|
-
if not content:
|
|
96
|
-
return None
|
|
97
|
-
normalized = content.replace("\\", "/")
|
|
98
|
-
while normalized.startswith("./"):
|
|
99
|
-
normalized = normalized[2:]
|
|
100
|
-
if normalized.startswith("tasks/"):
|
|
101
|
-
normalized = f".trellis/{normalized}"
|
|
102
|
-
return normalized
|
|
121
|
+
from common.active_task import resolve_active_task # type: ignore[import-not-found]
|
|
103
122
|
except Exception:
|
|
104
123
|
return None
|
|
105
124
|
|
|
125
|
+
active = resolve_active_task(
|
|
126
|
+
Path(repo_root),
|
|
127
|
+
input_data,
|
|
128
|
+
platform=_detect_platform(input_data),
|
|
129
|
+
)
|
|
130
|
+
return active.task_path
|
|
131
|
+
|
|
106
132
|
|
|
107
133
|
def read_file_content(base_path: str, file_path: str) -> str | None:
|
|
108
134
|
"""Read file content, return None if file doesn't exist"""
|
|
@@ -167,15 +193,27 @@ def read_jsonl_entries(base_path: str, jsonl_path: str) -> list[tuple[str, str]]
|
|
|
167
193
|
Schema:
|
|
168
194
|
{"file": "path/to/file.md", "reason": "..."}
|
|
169
195
|
{"file": "path/to/dir/", "type": "directory", "reason": "..."}
|
|
196
|
+
{"_example": "..."} # seed row — skipped (no `file` field)
|
|
197
|
+
|
|
198
|
+
Rows without a ``file`` field (e.g. the self-describing seed line written
|
|
199
|
+
by ``task.py create`` before the agent has curated entries) are skipped
|
|
200
|
+
silently. If the resulting entry list is empty, a stderr warning is
|
|
201
|
+
emitted so the operator can debug missing context.
|
|
170
202
|
|
|
171
203
|
Returns:
|
|
172
204
|
[(path, content), ...]
|
|
173
205
|
"""
|
|
174
206
|
full_path = os.path.join(base_path, jsonl_path)
|
|
175
207
|
if not os.path.exists(full_path):
|
|
208
|
+
print(
|
|
209
|
+
f"[inject-subagent-context] WARN: {jsonl_path} not found — "
|
|
210
|
+
f"sub-agent will receive only prd.md",
|
|
211
|
+
file=sys.stderr,
|
|
212
|
+
)
|
|
176
213
|
return []
|
|
177
214
|
|
|
178
215
|
results = []
|
|
216
|
+
saw_real_entry = False
|
|
179
217
|
try:
|
|
180
218
|
with open(full_path, "r", encoding="utf-8") as f:
|
|
181
219
|
for line in f:
|
|
@@ -188,8 +226,10 @@ def read_jsonl_entries(base_path: str, jsonl_path: str) -> list[tuple[str, str]]
|
|
|
188
226
|
entry_type = item.get("type", "file")
|
|
189
227
|
|
|
190
228
|
if not file_path:
|
|
229
|
+
# Seed / comment row — skip silently
|
|
191
230
|
continue
|
|
192
231
|
|
|
232
|
+
saw_real_entry = True
|
|
193
233
|
if entry_type == "directory":
|
|
194
234
|
# Read all .md files in directory
|
|
195
235
|
dir_contents = read_directory_contents(base_path, file_path)
|
|
@@ -204,6 +244,14 @@ def read_jsonl_entries(base_path: str, jsonl_path: str) -> list[tuple[str, str]]
|
|
|
204
244
|
except Exception:
|
|
205
245
|
pass
|
|
206
246
|
|
|
247
|
+
if not saw_real_entry:
|
|
248
|
+
print(
|
|
249
|
+
f"[inject-subagent-context] WARN: {jsonl_path} has no curated "
|
|
250
|
+
f"entries (only seed / empty) — sub-agent will receive only "
|
|
251
|
+
f"prd.md. See workflow.md Phase 1.3 for curation guidance.",
|
|
252
|
+
file=sys.stderr,
|
|
253
|
+
)
|
|
254
|
+
|
|
207
255
|
return results
|
|
208
256
|
|
|
209
257
|
|
|
@@ -391,7 +439,11 @@ Finish checklist and requirements:
|
|
|
391
439
|
def get_research_context(repo_root: str, task_dir: str | None) -> str:
|
|
392
440
|
"""
|
|
393
441
|
Context for Research Agent — project structure overview for spec directories.
|
|
442
|
+
|
|
443
|
+
`task_dir` kept for signature parity with get_implement_context / get_check_context
|
|
444
|
+
so the dispatcher can call them uniformly.
|
|
394
445
|
"""
|
|
446
|
+
_ = task_dir
|
|
395
447
|
context_parts = []
|
|
396
448
|
|
|
397
449
|
# 1. Project structure overview (dynamically discover spec directories)
|
|
@@ -490,13 +542,90 @@ Provide structured search results including:
|
|
|
490
542
|
- External references (if any)"""
|
|
491
543
|
|
|
492
544
|
|
|
545
|
+
def _string_value(value: Any) -> str:
|
|
546
|
+
if isinstance(value, str):
|
|
547
|
+
stripped = value.strip()
|
|
548
|
+
return stripped
|
|
549
|
+
return ""
|
|
550
|
+
|
|
551
|
+
|
|
552
|
+
def _extract_subagent_name(value: Any) -> str:
|
|
553
|
+
"""Extract a sub-agent name from common platform encodings.
|
|
554
|
+
|
|
555
|
+
Cursor's native Task args encode custom sub-agents as a protobuf oneof,
|
|
556
|
+
which can appear in hook JSON as either ``{"custom": {"name": "..."}}``
|
|
557
|
+
or ``{"type": {"case": "custom", "value": {"name": "..."}}}``.
|
|
558
|
+
"""
|
|
559
|
+
direct = _string_value(value)
|
|
560
|
+
if direct:
|
|
561
|
+
return direct
|
|
562
|
+
|
|
563
|
+
if not isinstance(value, dict):
|
|
564
|
+
return ""
|
|
565
|
+
|
|
566
|
+
for key in ("name", "subagent_type_name", "subagentTypeName"):
|
|
567
|
+
direct = _string_value(value.get(key))
|
|
568
|
+
if direct:
|
|
569
|
+
return direct
|
|
570
|
+
|
|
571
|
+
custom = value.get("custom")
|
|
572
|
+
if isinstance(custom, dict):
|
|
573
|
+
custom_name = _string_value(custom.get("name"))
|
|
574
|
+
if custom_name:
|
|
575
|
+
return custom_name
|
|
576
|
+
|
|
577
|
+
oneof = value.get("type")
|
|
578
|
+
if isinstance(oneof, dict):
|
|
579
|
+
case_name = _string_value(oneof.get("case"))
|
|
580
|
+
if case_name == "custom":
|
|
581
|
+
nested_value = oneof.get("value")
|
|
582
|
+
if isinstance(nested_value, dict):
|
|
583
|
+
custom_name = _string_value(nested_value.get("name"))
|
|
584
|
+
if custom_name:
|
|
585
|
+
return custom_name
|
|
586
|
+
if case_name:
|
|
587
|
+
return case_name
|
|
588
|
+
|
|
589
|
+
case_name = _string_value(value.get("case"))
|
|
590
|
+
if case_name == "custom":
|
|
591
|
+
nested_value = value.get("value")
|
|
592
|
+
if isinstance(nested_value, dict):
|
|
593
|
+
custom_name = _string_value(nested_value.get("name"))
|
|
594
|
+
if custom_name:
|
|
595
|
+
return custom_name
|
|
596
|
+
if case_name:
|
|
597
|
+
return case_name
|
|
598
|
+
|
|
599
|
+
for agent_name in AGENTS_ALL:
|
|
600
|
+
if agent_name in value:
|
|
601
|
+
return agent_name
|
|
602
|
+
|
|
603
|
+
return ""
|
|
604
|
+
|
|
605
|
+
|
|
606
|
+
def _extract_subagent_type(tool_input: dict) -> str:
|
|
607
|
+
for key in (
|
|
608
|
+
"subagent_type",
|
|
609
|
+
"subagentType",
|
|
610
|
+
"subagent_type_name",
|
|
611
|
+
"subagentTypeName",
|
|
612
|
+
"agent_type",
|
|
613
|
+
"agentType",
|
|
614
|
+
"name",
|
|
615
|
+
):
|
|
616
|
+
agent_name = _extract_subagent_name(tool_input.get(key))
|
|
617
|
+
if agent_name:
|
|
618
|
+
return agent_name
|
|
619
|
+
return ""
|
|
620
|
+
|
|
621
|
+
|
|
493
622
|
def _parse_hook_input(input_data: dict) -> tuple[str, str, dict]:
|
|
494
623
|
"""Parse hook input across different platform formats.
|
|
495
624
|
|
|
496
625
|
Returns (subagent_type, original_prompt, tool_input).
|
|
497
626
|
Handles:
|
|
498
627
|
- Claude Code / Qoder / CodeBuddy / Droid: tool_name=Task|Agent, tool_input.subagent_type
|
|
499
|
-
- Cursor: tool_name=Task, tool_input.subagent_type
|
|
628
|
+
- Cursor: tool_name=Task|Subagent, tool_input.subagent_type
|
|
500
629
|
- Copilot CLI: toolName=task (camelCase key, lowercase value)
|
|
501
630
|
- Gemini CLI: tool_name IS the agent name (BeforeTool matcher already filtered)
|
|
502
631
|
- Kiro: agentSpawn hook, agent_name field at top level
|
|
@@ -505,9 +634,9 @@ def _parse_hook_input(input_data: dict) -> tuple[str, str, dict]:
|
|
|
505
634
|
|
|
506
635
|
# Standard format: Task/Agent tool with subagent_type
|
|
507
636
|
tool_name = input_data.get("tool_name", "") or input_data.get("toolName", "")
|
|
508
|
-
if tool_name.lower() in ("task", "agent"):
|
|
637
|
+
if tool_name.lower() in ("task", "agent", "subagent"):
|
|
509
638
|
return (
|
|
510
|
-
tool_input
|
|
639
|
+
_extract_subagent_type(tool_input),
|
|
511
640
|
tool_input.get("prompt", ""),
|
|
512
641
|
tool_input,
|
|
513
642
|
)
|
|
@@ -549,7 +678,7 @@ def main():
|
|
|
549
678
|
sys.exit(0)
|
|
550
679
|
|
|
551
680
|
# Get current task directory (research doesn't require it)
|
|
552
|
-
task_dir = get_current_task(repo_root)
|
|
681
|
+
task_dir = get_current_task(repo_root, input_data)
|
|
553
682
|
|
|
554
683
|
# implement/check need task directory
|
|
555
684
|
if subagent_type in AGENTS_REQUIRE_TASK:
|