@blxzer/cursor-trellis 0.3.3 → 0.3.4
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/CHANGELOG.md +6 -0
- package/README.zh-CN.md +1 -0
- package/dist/migrations/manifests/0.3.2.json +16 -0
- package/dist/migrations/manifests/0.3.3.json +9 -0
- package/dist/migrations/manifests/0.3.4.json +9 -0
- package/dist/templates/common/bundled-skills/cstl-meta/references/platform-files/hooks-and-settings.md +2 -1
- package/dist/templates/cursor/fixtures/expected-rules.d.ts.map +1 -1
- package/dist/templates/cursor/fixtures/expected-rules.js +5 -0
- package/dist/templates/cursor/fixtures/expected-rules.js.map +1 -1
- package/dist/templates/cursor/hooks.json +7 -0
- package/dist/templates/cursor/rules/cstl-session-rename.mdc +31 -0
- package/dist/templates/shared-hooks/index.d.ts +1 -1
- package/dist/templates/shared-hooks/index.d.ts.map +1 -1
- package/dist/templates/shared-hooks/index.js +1 -0
- package/dist/templates/shared-hooks/index.js.map +1 -1
- package/dist/templates/shared-hooks/rename-session-for-task.py +231 -0
- package/dist/templates/trellis/tasks/templates/release-execution/design.md +60 -60
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -11,6 +11,12 @@ SemVer: [semver.org](https://semver.org/spec/v2.0.0.html).
|
|
|
11
11
|
|
|
12
12
|
## [Unreleased]
|
|
13
13
|
|
|
14
|
+
## [0.3.4] - 2026-07-04
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
|
|
18
|
+
- **feat(cursor)**: auto-rename the **main** Agent chat tab to the cstl task **directory name** after `task.py select` or `task.py start-execution --approved` (`rename-session-for-task.py` `afterShellExecution` hook + `cstl-session-rename.mdc` rule). Uses `cursor-app-control` `rename_chat` when available; skips silently when MCP is missing. Does not rename on `task.py create`. Native and BYOK share the same path.
|
|
19
|
+
|
|
14
20
|
## [0.3.3] - 2026-07-04
|
|
15
21
|
|
|
16
22
|
Follow-up polish after 0.3.2 coexistence release: closes known limitations from the runtime-isolation task.
|
package/README.zh-CN.md
CHANGED
|
@@ -197,6 +197,7 @@ smart-search --version
|
|
|
197
197
|
| --- | --- |
|
|
198
198
|
| `pnpm build` | `tsc` + 拷贝模板 |
|
|
199
199
|
| `pnpm test` | Vitest |
|
|
200
|
+
| `pnpm mirror-check` | Dogfood `.cursor` / `.agents` vs templates |
|
|
200
201
|
| `pnpm run sync:smart-search` | 刷新 vendor |
|
|
201
202
|
|
|
202
203
|
Release 与 npm 发布流程**不在**公开 README 中;见内部维护文档。
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.3.2",
|
|
3
|
+
"description": "Reissue 0.3.1 runtime isolation with publishable core/cli pair (.cstl/)",
|
|
4
|
+
"breaking": true,
|
|
5
|
+
"recommendMigrate": true,
|
|
6
|
+
"changelog": "**@blxzer/cursor-trellis 0.3.2**\n\nReissues 0.3.1 content with a publishable `@blxzer/cursor-trellis-core` pair. Runtime root `.trellis/` → **`.cstl/`** for coexistence with upstream Trellis.\n\n```bash\nnpm install -g @blxzer/cursor-trellis@0.3.2\ncstl update --migrate\n```",
|
|
7
|
+
"migrations": [
|
|
8
|
+
{
|
|
9
|
+
"type": "rename-dir",
|
|
10
|
+
"from": ".trellis",
|
|
11
|
+
"to": ".cstl",
|
|
12
|
+
"description": "v0.3.2: cursor-trellis runtime dir isolation (.trellis -> .cstl)"
|
|
13
|
+
}
|
|
14
|
+
],
|
|
15
|
+
"notes": "0.3.1 was withdrawn from npm; use 0.3.2+ for the .cstl/ migration."
|
|
16
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.3.3",
|
|
3
|
+
"description": "Coexistence polish: AGENTS CSTL block uninstall, hash scope, publish CI",
|
|
4
|
+
"breaking": false,
|
|
5
|
+
"recommendMigrate": false,
|
|
6
|
+
"changelog": "**@blxzer/cursor-trellis 0.3.3**\n\n- feat(uninstall): strip CSTL managed block from AGENTS.md while preserving upstream TRELLIS block\n- feat(hash): AGENTS.md template hash tracks CSTL block only\n- docs(spec): cursor-trellis release coexistence guide\n- ci: publish workflow on cstl-v* tags",
|
|
7
|
+
"migrations": [],
|
|
8
|
+
"notes": "Run `cstl update` to sync templates. No `--migrate` required."
|
|
9
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "0.3.4",
|
|
3
|
+
"description": "Rename main Cursor chat to cstl task directory on select/start-execution",
|
|
4
|
+
"breaking": false,
|
|
5
|
+
"recommendMigrate": false,
|
|
6
|
+
"changelog": "**@blxzer/cursor-trellis 0.3.4**\n\n- feat(cursor): auto-rename main Agent chat tab to task directory name after `task.py select` or `start-execution --approved` (`rename-session-for-task.py` + `cstl-session-rename.mdc`). Uses `cursor-app-control` `rename_chat` when available; silent skip when MCP missing. No rename on `task.py create`.",
|
|
7
|
+
"migrations": [],
|
|
8
|
+
"notes": "Run `cstl update` to sync new hook, rule, and hooks.json wiring."
|
|
9
|
+
}
|
|
@@ -27,8 +27,9 @@ Whether legacy settings/config files exist in a project depends on which histori
|
|
|
27
27
|
| `inject-workflow-state.py` | Parses `[workflow-state:STATUS]` blocks in `.cstl/workflow.md` and emits the body matching the selected task status. Falls back to `Refer to workflow.md for current step.` when no matching block exists. |
|
|
28
28
|
| `inject-subagent-context.py` | Injects PRD, JSONL context, and related spec/research into sub-agents. |
|
|
29
29
|
| `inject-shell-session-context.py` | Lets shell commands inherit Trellis session identity. |
|
|
30
|
+
| `rename-session-for-task.py` | After successful `task.py select` / `start-execution --approved`, prompts main-session rename to task directory name (best-effort `rename_chat`). |
|
|
30
31
|
|
|
31
|
-
Cursor exposes sessionStart, preToolUse on Task/Subagent, beforeShellExecution, and stop events. Confirm against the current Cursor release before wiring a new event name; the available event surface has evolved over time and historical platform documentation may be stale.
|
|
32
|
+
Cursor exposes sessionStart, preToolUse on Task/Subagent, beforeShellExecution, afterShellExecution, and stop events. Confirm against the current Cursor release before wiring a new event name; the available event surface has evolved over time and historical platform documentation may be stale.
|
|
32
33
|
|
|
33
34
|
## Local Change Scenarios
|
|
34
35
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expected-rules.d.ts","sourceRoot":"","sources":["../../../../src/templates/cursor/fixtures/expected-rules.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,aAAa,EAAE,YAAY,
|
|
1
|
+
{"version":3,"file":"expected-rules.d.ts","sourceRoot":"","sources":["../../../../src/templates/cursor/fixtures/expected-rules.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,YAAY;IAC3B,gEAAgE;IAChE,QAAQ,EAAE,MAAM,CAAC;IACjB,gEAAgE;IAChE,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,qDAAqD;IACrD,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,eAAO,MAAM,aAAa,EAAE,YAAY,EAyBvC,CAAC"}
|
|
@@ -22,5 +22,10 @@ export const expectedRules = [
|
|
|
22
22
|
requiredSections: ["Native", "BYOK", "codegraph", "smart-search"],
|
|
23
23
|
minBytes: 600,
|
|
24
24
|
},
|
|
25
|
+
{
|
|
26
|
+
filename: "cstl-session-rename.mdc",
|
|
27
|
+
requiredSections: ["rename_chat", "select", "start-execution", "skip silently"],
|
|
28
|
+
minBytes: 300,
|
|
29
|
+
},
|
|
25
30
|
];
|
|
26
31
|
//# sourceMappingURL=expected-rules.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"expected-rules.js","sourceRoot":"","sources":["../../../../src/templates/cursor/fixtures/expected-rules.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,MAAM,CAAC,MAAM,aAAa,GAAmB;IAC3C;QACE,QAAQ,EAAE,iBAAiB;QAC3B,gBAAgB,EAAE;YAChB,eAAe;YACf,qBAAqB;YACrB,cAAc;SACf;QACD,QAAQ,EAAE,GAAG;KACd;IACD;QACE,QAAQ,EAAE,4BAA4B;QACtC,gBAAgB,EAAE,CAAC,SAAS,EAAE,0BAA0B,EAAE,KAAK,CAAC;QAChE,QAAQ,EAAE,GAAG;KACd;IACD;QACE,QAAQ,EAAE,uBAAuB;QACjC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,CAAC;QACjE,QAAQ,EAAE,GAAG;KACd;CACF,CAAC"}
|
|
1
|
+
{"version":3,"file":"expected-rules.js","sourceRoot":"","sources":["../../../../src/templates/cursor/fixtures/expected-rules.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAWH,MAAM,CAAC,MAAM,aAAa,GAAmB;IAC3C;QACE,QAAQ,EAAE,iBAAiB;QAC3B,gBAAgB,EAAE;YAChB,eAAe;YACf,qBAAqB;YACrB,cAAc;SACf;QACD,QAAQ,EAAE,GAAG;KACd;IACD;QACE,QAAQ,EAAE,4BAA4B;QACtC,gBAAgB,EAAE,CAAC,SAAS,EAAE,0BAA0B,EAAE,KAAK,CAAC;QAChE,QAAQ,EAAE,GAAG;KACd;IACD;QACE,QAAQ,EAAE,uBAAuB;QACjC,gBAAgB,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,WAAW,EAAE,cAAc,CAAC;QACjE,QAAQ,EAAE,GAAG;KACd;IACD;QACE,QAAQ,EAAE,yBAAyB;QACnC,gBAAgB,EAAE,CAAC,aAAa,EAAE,QAAQ,EAAE,iBAAiB,EAAE,eAAe,CAAC;QAC/E,QAAQ,EAAE,GAAG;KACd;CACF,CAAC"}
|
|
@@ -26,6 +26,13 @@
|
|
|
26
26
|
"timeout": 5
|
|
27
27
|
}
|
|
28
28
|
],
|
|
29
|
+
"afterShellExecution": [
|
|
30
|
+
{
|
|
31
|
+
"command": "{{PYTHON_CMD}} .cursor/hooks/rename-session-for-task.py",
|
|
32
|
+
"matcher": "task\\.py.*(select|start-execution)",
|
|
33
|
+
"timeout": 5
|
|
34
|
+
}
|
|
35
|
+
],
|
|
29
36
|
"stop": [
|
|
30
37
|
{
|
|
31
38
|
"command": "{{PYTHON_CMD}} .cursor/hooks/research-end-retrieval-pack.py",
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Rename Cursor chat to cstl task directory name after select or start-execution (cursor-app-control rename_chat)"
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Trellis session rename (Cursor main session)
|
|
7
|
+
|
|
8
|
+
Encourage **one task per main Agent chat**. Subagent / Task child windows are out of scope.
|
|
9
|
+
|
|
10
|
+
## When to rename
|
|
11
|
+
|
|
12
|
+
After **you** successfully run either command in the **main** session (same turn or immediately after shell output):
|
|
13
|
+
|
|
14
|
+
- `python ./.cstl/scripts/task.py select <task-dir>`
|
|
15
|
+
- `python ./.cstl/scripts/task.py start-execution <task-dir> --approved`
|
|
16
|
+
|
|
17
|
+
Do **not** rename for `task.py create` alone (create does not bind the session).
|
|
18
|
+
|
|
19
|
+
## Title
|
|
20
|
+
|
|
21
|
+
Use the task **directory name** only (e.g. `07-04-cstl-session-rename`), not `task.json` `title`.
|
|
22
|
+
|
|
23
|
+
## How
|
|
24
|
+
|
|
25
|
+
1. If `cursor-app-control` MCP `rename_chat` is available, call it once with `title` = directory name.
|
|
26
|
+
2. If the tool is **not** available (BYOK or other environments), **skip silently** — no user prompt to rename manually.
|
|
27
|
+
3. Do not rename repeatedly for the same task in the same session unless the user switched to a different task directory.
|
|
28
|
+
|
|
29
|
+
## Native vs BYOK
|
|
30
|
+
|
|
31
|
+
Same rule for both. BYOK availability of `rename_chat` is environment-dependent; never fail the task workflow when rename is unavailable.
|
|
@@ -11,7 +11,7 @@ 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-retrieval-plan.py" | "inject-workflow-state.py" | "inject-subagent-context.py" | "research-end-retrieval-pack.py";
|
|
14
|
+
export type SharedHookName = "session-start.py" | "inject-shell-session-context.py" | "rename-session-for-task.py" | "inject-retrieval-plan.py" | "inject-workflow-state.py" | "inject-subagent-context.py" | "research-end-retrieval-pack.py";
|
|
15
15
|
export type SharedHookPlatform = "claude" | "cursor" | "codex" | "gemini" | "qoder" | "copilot" | "codebuddy" | "droid" | "kiro";
|
|
16
16
|
/**
|
|
17
17
|
* Which shared hooks each platform actually invokes. Single source of truth
|
|
@@ -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,MAAM,MAAM,cAAc,GACtB,kBAAkB,GAClB,iCAAiC,GACjC,0BAA0B,GAC1B,0BAA0B,GAC1B,4BAA4B,GAC5B,gCAAgC,CAAC;AAErC,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,
|
|
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,4BAA4B,GAC5B,0BAA0B,GAC1B,0BAA0B,GAC1B,4BAA4B,GAC5B,gCAAgC,CAAC;AAErC,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,CAiC1B,CAAC;AAEF;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,UAAU,EAAE,CAWnD;AAED;;;;GAIG;AACH,wBAAgB,+BAA+B,CAC7C,QAAQ,EAAE,kBAAkB,GAC3B,UAAU,EAAE,CAGd"}
|
|
@@ -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;AA6BD;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,MAAM,CAAC,MAAM,wBAAwB,GAGjC;IACF,MAAM,EAAE;QACN,kBAAkB;QAClB,0BAA0B;QAC1B,4BAA4B;QAC5B,gCAAgC;KACjC;IACD,MAAM,EAAE;QACN,kBAAkB;QAClB,iCAAiC;QACjC,4BAA4B;QAC5B,0BAA0B;QAC1B,4BAA4B;QAC5B,gCAAgC;KACjC;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;QAC5B,gCAAgC;KACjC;IACD,KAAK,EAAE;QACL,kBAAkB;QAClB,0BAA0B;QAC1B,4BAA4B;QAC5B,gCAAgC;KACjC;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,231 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""Cursor afterShellExecution hook: prompt session rename after task bind/start.
|
|
3
|
+
|
|
4
|
+
When the agent successfully runs `task.py select` or
|
|
5
|
+
`task.py start-execution <task> --approved`, emit an agent_message (best-effort)
|
|
6
|
+
so the main session calls cursor-app-control `rename_chat` with the task
|
|
7
|
+
directory name. MCP unavailable → agent skips silently per cstl-session-rename rule.
|
|
8
|
+
"""
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import hashlib
|
|
12
|
+
import json
|
|
13
|
+
import os
|
|
14
|
+
import re
|
|
15
|
+
import shlex
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
from typing import Any
|
|
19
|
+
|
|
20
|
+
DIR_WORKFLOW = ".cstl"
|
|
21
|
+
DIR_RUNTIME = ".runtime"
|
|
22
|
+
DIR_SESSION_RENAME = "session-rename"
|
|
23
|
+
ANSI_ESCAPE = re.compile(r"\x1b\[[0-9;]*m")
|
|
24
|
+
CONTEXT_IDENTITY_KEYS = (
|
|
25
|
+
"session_id",
|
|
26
|
+
"sessionId",
|
|
27
|
+
"sessionID",
|
|
28
|
+
"conversation_id",
|
|
29
|
+
"conversationId",
|
|
30
|
+
"conversationID",
|
|
31
|
+
"transcript_path",
|
|
32
|
+
"transcriptPath",
|
|
33
|
+
"transcript",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def _string_value(value: Any) -> str | None:
|
|
38
|
+
if isinstance(value, str):
|
|
39
|
+
stripped = value.strip()
|
|
40
|
+
return stripped or None
|
|
41
|
+
return None
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def _strip_ansi(text: str) -> str:
|
|
45
|
+
return ANSI_ESCAPE.sub("", text)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _find_trellis_root(start: Path) -> Path | None:
|
|
49
|
+
current = start.resolve()
|
|
50
|
+
while True:
|
|
51
|
+
if (current / DIR_WORKFLOW).is_dir():
|
|
52
|
+
return current
|
|
53
|
+
if current == current.parent:
|
|
54
|
+
return None
|
|
55
|
+
current = current.parent
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _task_py_token_index(tokens: list[str]) -> int | None:
|
|
59
|
+
for index, token in enumerate(tokens):
|
|
60
|
+
if Path(token.strip("\"'")).name == "task.py":
|
|
61
|
+
return index
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def parse_rename_intent(command: str) -> dict[str, str] | None:
|
|
66
|
+
"""Return subcommand + task_ref when command should trigger rename."""
|
|
67
|
+
try:
|
|
68
|
+
tokens = shlex.split(command, posix=os.name != "nt")
|
|
69
|
+
except ValueError:
|
|
70
|
+
return None
|
|
71
|
+
|
|
72
|
+
task_index = _task_py_token_index(tokens)
|
|
73
|
+
if task_index is None or task_index + 1 >= len(tokens):
|
|
74
|
+
return None
|
|
75
|
+
|
|
76
|
+
subcommand = tokens[task_index + 1]
|
|
77
|
+
if subcommand == "select":
|
|
78
|
+
if task_index + 2 >= len(tokens):
|
|
79
|
+
return None
|
|
80
|
+
return {"subcommand": subcommand, "task_ref": tokens[task_index + 2]}
|
|
81
|
+
if subcommand == "start-execution":
|
|
82
|
+
if "--approved" not in tokens:
|
|
83
|
+
return None
|
|
84
|
+
if task_index + 2 >= len(tokens):
|
|
85
|
+
return None
|
|
86
|
+
task_ref = tokens[task_index + 2]
|
|
87
|
+
if task_ref.startswith("-"):
|
|
88
|
+
return None
|
|
89
|
+
return {"subcommand": subcommand, "task_ref": task_ref}
|
|
90
|
+
return None
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def task_directory_name(task_ref: str) -> str:
|
|
94
|
+
"""Use task folder basename as the chat title."""
|
|
95
|
+
cleaned = task_ref.strip().strip("\"'")
|
|
96
|
+
return Path(cleaned.replace("\\", "/")).name
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def shell_output_succeeded(subcommand: str, output: str) -> bool:
|
|
100
|
+
text = _strip_ansi(output or "")
|
|
101
|
+
if "Error:" in text or "error:" in text.lower():
|
|
102
|
+
return False
|
|
103
|
+
if subcommand == "select":
|
|
104
|
+
return "Selected task:" in text or "✓ Selected task" in text
|
|
105
|
+
if subcommand == "start-execution":
|
|
106
|
+
return "Execution approved" in text or "✓ Execution approved" in text
|
|
107
|
+
return False
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def _safe_context_filename(context_key: str) -> str:
|
|
111
|
+
digest = hashlib.sha256(context_key.encode("utf-8")).hexdigest()[:24]
|
|
112
|
+
return f"{digest}.json"
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def _rename_state_dir(root: Path) -> Path:
|
|
116
|
+
return root / DIR_WORKFLOW / DIR_RUNTIME / DIR_SESSION_RENAME
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _load_rename_state(path: Path) -> dict[str, Any]:
|
|
120
|
+
if not path.is_file():
|
|
121
|
+
return {}
|
|
122
|
+
try:
|
|
123
|
+
data = json.loads(path.read_text(encoding="utf-8"))
|
|
124
|
+
except (json.JSONDecodeError, OSError):
|
|
125
|
+
return {}
|
|
126
|
+
return data if isinstance(data, dict) else {}
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def _should_emit_rename(state: dict[str, Any], title: str) -> bool:
|
|
130
|
+
return state.get("last_title") != title
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def _write_rename_state(path: Path, context_key: str, title: str) -> None:
|
|
134
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
135
|
+
payload = {
|
|
136
|
+
"context_key": context_key,
|
|
137
|
+
"last_title": title,
|
|
138
|
+
}
|
|
139
|
+
path.write_text(
|
|
140
|
+
json.dumps(payload, indent=2, ensure_ascii=False) + "\n",
|
|
141
|
+
encoding="utf-8",
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
def _agent_rename_message(title: str) -> str:
|
|
146
|
+
return (
|
|
147
|
+
"Trellis session rename: call cursor-app-control MCP `rename_chat` once "
|
|
148
|
+
f'with title "{title}" (task directory name). '
|
|
149
|
+
"If rename_chat is not in your tool list, skip silently — do not ask the user "
|
|
150
|
+
"to rename manually."
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def _load_context_key(root: Path, hook_input: dict[str, Any]) -> str | None:
|
|
155
|
+
scripts_dir = root / DIR_WORKFLOW / "scripts"
|
|
156
|
+
if str(scripts_dir) not in sys.path:
|
|
157
|
+
sys.path.insert(0, str(scripts_dir))
|
|
158
|
+
from common.active_task import resolve_context_key # type: ignore[import-not-found]
|
|
159
|
+
|
|
160
|
+
if not any(_string_value(hook_input.get(key)) for key in CONTEXT_IDENTITY_KEYS):
|
|
161
|
+
return None
|
|
162
|
+
return resolve_context_key(hook_input, platform="cursor")
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
def main() -> int:
|
|
166
|
+
if os.environ.get("TRELLIS_HOOKS") == "0" or os.environ.get("TRELLIS_DISABLE_HOOKS") == "1":
|
|
167
|
+
print("{}")
|
|
168
|
+
return 0
|
|
169
|
+
|
|
170
|
+
try:
|
|
171
|
+
hook_input = json.loads(sys.stdin.read())
|
|
172
|
+
except (json.JSONDecodeError, ValueError):
|
|
173
|
+
hook_input = {}
|
|
174
|
+
if not isinstance(hook_input, dict):
|
|
175
|
+
hook_input = {}
|
|
176
|
+
|
|
177
|
+
event = _string_value(hook_input.get("hook_event_name")) or "afterShellExecution"
|
|
178
|
+
if event != "afterShellExecution":
|
|
179
|
+
print("{}")
|
|
180
|
+
return 0
|
|
181
|
+
|
|
182
|
+
command = _string_value(hook_input.get("command")) or ""
|
|
183
|
+
intent = parse_rename_intent(command)
|
|
184
|
+
if intent is None:
|
|
185
|
+
print("{}")
|
|
186
|
+
return 0
|
|
187
|
+
|
|
188
|
+
output = _string_value(hook_input.get("output")) or ""
|
|
189
|
+
if not shell_output_succeeded(intent["subcommand"], output):
|
|
190
|
+
print("{}")
|
|
191
|
+
return 0
|
|
192
|
+
|
|
193
|
+
title = task_directory_name(intent["task_ref"])
|
|
194
|
+
if not title:
|
|
195
|
+
print("{}")
|
|
196
|
+
return 0
|
|
197
|
+
|
|
198
|
+
cwd = Path(_string_value(hook_input.get("cwd")) or os.getcwd())
|
|
199
|
+
root = _find_trellis_root(cwd)
|
|
200
|
+
if root is None:
|
|
201
|
+
print("{}")
|
|
202
|
+
return 0
|
|
203
|
+
|
|
204
|
+
context_key = _load_context_key(root, hook_input)
|
|
205
|
+
if not context_key:
|
|
206
|
+
print("{}")
|
|
207
|
+
return 0
|
|
208
|
+
|
|
209
|
+
state_path = _rename_state_dir(root) / _safe_context_filename(context_key)
|
|
210
|
+
state = _load_rename_state(state_path)
|
|
211
|
+
if not _should_emit_rename(state, title):
|
|
212
|
+
print("{}")
|
|
213
|
+
return 0
|
|
214
|
+
|
|
215
|
+
try:
|
|
216
|
+
_write_rename_state(state_path, context_key, title)
|
|
217
|
+
except OSError:
|
|
218
|
+
print("{}")
|
|
219
|
+
return 0
|
|
220
|
+
|
|
221
|
+
print(
|
|
222
|
+
json.dumps(
|
|
223
|
+
{"agent_message": _agent_rename_message(title)},
|
|
224
|
+
ensure_ascii=False,
|
|
225
|
+
),
|
|
226
|
+
)
|
|
227
|
+
return 0
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
if __name__ == "__main__":
|
|
231
|
+
sys.exit(main())
|
|
@@ -1,61 +1,61 @@
|
|
|
1
|
-
# Design — Release execution
|
|
2
|
-
|
|
3
|
-
## Boundary
|
|
4
|
-
|
|
5
|
-
This task **may** mutate remotes **only** after explicit user approval captured in `verify.md` (quote or paraphrase the approval request/response).
|
|
6
|
-
|
|
7
|
-
## Release status model (this task)
|
|
8
|
-
|
|
9
|
-
| Status | When to use |
|
|
10
|
-
| --- | --- |
|
|
11
|
-
| **published** | npm and/or git remote actions completed for the approved version |
|
|
12
|
-
| **not published** | Execution aborted or only dry-run/preflight completed |
|
|
13
|
-
| **blocked** | Preflight failed; do not publish until fixed |
|
|
14
|
-
| **waived** | User approved proceeding despite a listed risk |
|
|
15
|
-
| **deferred** | User postponed publish after readiness |
|
|
16
|
-
|
|
17
|
-
Do not mark **ready to publish** here — that remains the readiness task outcome.
|
|
18
|
-
|
|
19
|
-
## Approval gate (mandatory)
|
|
20
|
-
|
|
21
|
-
Before any of the following, stop and obtain explicit user approval:
|
|
22
|
-
|
|
23
|
-
- `node scripts/release.js …` (bundles bump, commit, tag, push)
|
|
24
|
-
- `npm publish` / CI publish workflow dispatch
|
|
25
|
-
- `git tag` / `git push` (including `--tags`)
|
|
26
|
-
- GitHub **Create release** or promotion workflows
|
|
27
|
-
|
|
28
|
-
Document in `verify.md`:
|
|
29
|
-
|
|
30
|
-
```markdown
|
|
31
|
-
## Publish approval evidence
|
|
32
|
-
|
|
33
|
-
User explicitly approved: yes/no
|
|
34
|
-
Approved version: …
|
|
35
|
-
Approved dist-tag: …
|
|
36
|
-
Approved remote actions: tag / push / npm publish / …
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
## Evidence contract (`verify.md`)
|
|
40
|
-
|
|
41
|
-
1. **Readiness link** — path to readiness `handoff.md`.
|
|
42
|
-
2. **Publish approval evidence** — as above.
|
|
43
|
-
3. **Preflight results** — `check-versions`, `publish-plan`, `verify-packed-cli`.
|
|
44
|
-
4. **Execution log** — commands run (redact tokens); pass/fail.
|
|
45
|
-
5. **Post-publish smoke** — registry check, `
|
|
46
|
-
6. **Validation evidence** — tests run after publish if applicable.
|
|
47
|
-
|
|
48
|
-
## Trellis CLI reference sequence (after approval)
|
|
49
|
-
|
|
50
|
-
Typical human/CI sequence (do not automate without approval):
|
|
51
|
-
|
|
52
|
-
```bash
|
|
53
|
-
cd packages/cli
|
|
54
|
-
node scripts/release-preflight.js check-versions
|
|
55
|
-
node scripts/release-preflight.js publish-plan
|
|
56
|
-
# After approval:
|
|
57
|
-
pnpm --filter @blxzer/cursor-trellis release:beta # or patch/minor/major per policy
|
|
58
|
-
node scripts/release-preflight.js verify-npm --package all
|
|
59
|
-
```
|
|
60
|
-
|
|
1
|
+
# Design — Release execution
|
|
2
|
+
|
|
3
|
+
## Boundary
|
|
4
|
+
|
|
5
|
+
This task **may** mutate remotes **only** after explicit user approval captured in `verify.md` (quote or paraphrase the approval request/response).
|
|
6
|
+
|
|
7
|
+
## Release status model (this task)
|
|
8
|
+
|
|
9
|
+
| Status | When to use |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| **published** | npm and/or git remote actions completed for the approved version |
|
|
12
|
+
| **not published** | Execution aborted or only dry-run/preflight completed |
|
|
13
|
+
| **blocked** | Preflight failed; do not publish until fixed |
|
|
14
|
+
| **waived** | User approved proceeding despite a listed risk |
|
|
15
|
+
| **deferred** | User postponed publish after readiness |
|
|
16
|
+
|
|
17
|
+
Do not mark **ready to publish** here — that remains the readiness task outcome.
|
|
18
|
+
|
|
19
|
+
## Approval gate (mandatory)
|
|
20
|
+
|
|
21
|
+
Before any of the following, stop and obtain explicit user approval:
|
|
22
|
+
|
|
23
|
+
- `node scripts/release.js …` (bundles bump, commit, tag, push)
|
|
24
|
+
- `npm publish` / CI publish workflow dispatch
|
|
25
|
+
- `git tag` / `git push` (including `--tags`)
|
|
26
|
+
- GitHub **Create release** or promotion workflows
|
|
27
|
+
|
|
28
|
+
Document in `verify.md`:
|
|
29
|
+
|
|
30
|
+
```markdown
|
|
31
|
+
## Publish approval evidence
|
|
32
|
+
|
|
33
|
+
User explicitly approved: yes/no
|
|
34
|
+
Approved version: …
|
|
35
|
+
Approved dist-tag: …
|
|
36
|
+
Approved remote actions: tag / push / npm publish / …
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Evidence contract (`verify.md`)
|
|
40
|
+
|
|
41
|
+
1. **Readiness link** — path to readiness `handoff.md`.
|
|
42
|
+
2. **Publish approval evidence** — as above.
|
|
43
|
+
3. **Preflight results** — `check-versions`, `publish-plan`, `verify-packed-cli`.
|
|
44
|
+
4. **Execution log** — commands run (redact tokens); pass/fail.
|
|
45
|
+
5. **Post-publish smoke** — registry check, `cstl --version`, dogfood smoke commands.
|
|
46
|
+
6. **Validation evidence** — tests run after publish if applicable.
|
|
47
|
+
|
|
48
|
+
## Trellis CLI reference sequence (after approval)
|
|
49
|
+
|
|
50
|
+
Typical human/CI sequence (do not automate without approval):
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
cd packages/cli
|
|
54
|
+
node scripts/release-preflight.js check-versions
|
|
55
|
+
node scripts/release-preflight.js publish-plan
|
|
56
|
+
# After approval:
|
|
57
|
+
pnpm --filter @blxzer/cursor-trellis release:beta # or patch/minor/major per policy
|
|
58
|
+
node scripts/release-preflight.js verify-npm --package all
|
|
59
|
+
```
|
|
60
|
+
|
|
61
61
|
`release.js` performs commit, tag, and push — treat it as a single approved bundle or decompose manually per policy.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blxzer/cursor-trellis",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.4",
|
|
4
4
|
"description": "AI capabilities grow like ivy — Trellis provides the structure to guide them along a disciplined path",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"inquirer": "^9.3.7",
|
|
36
36
|
"undici": "^6.21.0",
|
|
37
37
|
"zod": "^4.4.2",
|
|
38
|
-
"@blxzer/cursor-trellis-core": "0.3.
|
|
38
|
+
"@blxzer/cursor-trellis-core": "0.3.4"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@eslint/js": "^9.18.0",
|