@arcships/morula-runtime 0.1.0-alpha.1 → 0.1.0-alpha.3

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.
@@ -0,0 +1,42 @@
1
+ # SKILL 评估用例
2
+
3
+ > 用于验证 morula-cli SKILL 的激活准确性和输出质量。
4
+
5
+ ## 激活测试(should-trigger)
6
+
7
+ 以下输入应触发 morula-cli 加载:
8
+
9
+ | # | 用户输入 | 预期行为 |
10
+ |---|---------|---------|
11
+ | 1 | "查一下我的任务" | 加载 morula-cli → `task list` |
12
+ | 2 | "帮我创建一个高优先级任务" | 加载 morula-cli → 收集 spaceId + title → 确认 → `task create` |
13
+ | 3 | "把 TASK-042 改成已完成" | 加载 morula-cli → `task list` 获取 id → 确认 → `task update` |
14
+ | 4 | "我的空间有哪些" | 加载 morula-cli → `space list` |
15
+ | 5 | "看看产品研发空间" | 加载 morula-cli → `space list` 获取 id → `space get` |
16
+ | 6 | "登录 morula" | 加载 morula-cli → `auth login` |
17
+
18
+ ## 不激活测试(should-not-trigger)
19
+
20
+ 以下输入不应触发 morula-cli 加载:
21
+
22
+ | # | 用户输入 | 预期行为 |
23
+ |---|---------|---------|
24
+ | 1 | "任务管理系统怎么设计" | 不加载(架构讨论,非操作请求) |
25
+ | 2 | "帮我写个 Python 脚本" | 不加载(非 Morula 操作) |
26
+ | 3 | "morula 是什么" | 不加载(产品介绍,非操作请求) |
27
+
28
+ ## 输出测试(output eval)
29
+
30
+ 验证 Agent 在具体场景下的行为是否符合最佳实践:
31
+
32
+ | # | 场景 | 预期 Agent 行为 | 验证点 |
33
+ |---|------|----------------|--------|
34
+ | 1 | 用户说"创建任务 priority 为 p0" | Agent 拒绝:`--priority` 合法值为 `low medium high urgent` | 枚举校验 |
35
+ | 2 | `task update` 不传 id | Agent 提示需要 taskId | 必填检查 |
36
+ | 3 | `task create` 不传 spaceId | Agent 提示先 `space list` 获取 spaceId | 前置依赖 |
37
+ | 4 | 命令返回退出码 4(401) | Agent 执行 `morula auth login`,不向用户报错 | 自动恢复 |
38
+ | 5 | `task list` 返回 200 条 | Agent 展示前 20 条 + "第 1 页,共 200 条" | 数据量控制 |
39
+ | 7 | 创建任务成功 | Agent 展示 `code`(如 TASK-042)+ `title`,不展示完整 JSON | 输出格式化 |
40
+ | 8 | 命令返回退出码 7(5xx) | Agent 提示"服务暂时不可用,稍后重试",不重复调用 | 错误处理 |
41
+ | 9 | 用户未登录就要求创建任务 | Agent 先执行 `auth status` → 发现未登录 → 执行 `auth login` → 再 create | 认证前置 |
42
+ | 10 | `task create` 传 `--sort-order 5` | Agent 拒绝:`--sort-order` 仅 `task update` 支持 | 操作边界 |
@@ -0,0 +1,315 @@
1
+ ---
2
+ name: morula-working-on-issues
3
+ description: "Use when working on a Morula task after the daemon has provided the trigger context — to apply the product contracts the runtime brief does not encode: CLI-only platform interaction, PR linking vs close intent, how to read a linked PR's real state, task-number routing, status side effects, and final-comment delivery discipline."
4
+ user-invocable: false
5
+ allowed-tools: Bash(morula *), Bash(git *), Bash(gh *)
6
+ ---
7
+
8
+ > 本文件是随 CLI 插件分发的**文档副本**。daemon 执行任务时会按平台在任务工作区
9
+ > `.agents/skills/morula-working-on-issues/SKILL.md` 注入**权威版本**(GitLab 任务会附带 GitLab 平台说明段),
10
+ > 两者正文同源。命令细节以工作区内注入的那份与 `morula <resource> --help` 为准。
11
+
12
+ # Working on Morula issues(协作契约)
13
+
14
+ 本 SKILL 是 Morula 平台对本地智能体(daemon 执行的 AgentTask)的行为契约。
15
+ 每条契约都溯源到服务端实现,见 `references/working-on-issues-source-map.md`。
16
+ 在 daemon 派发的任务中,本 SKILL 会被自动注入到工作目录 `.agents/skills/`,由 Agent CLI 自动发现。
17
+
18
+ ## 0. 运行时上下文与任务详情获取(对应任务系统提示词,必须一一对应)
19
+
20
+ 任务系统提示词(发给你的首条消息)只承载:**你的身份**(阶段职责)、**你的目标**(「你的目标」段)、
21
+ **工作区与工具**(仓库清单、skill 指引)。**任务详情不进提示词**,一律用 CLI 获取:
22
+
23
+ - **当前任务详情**:`morula task get`(无参)——taskId 由执行环境提供(`MORULA_TASK_ID` env / `.morula/checkout.json`),无需也不应从别处猜。返回:`id`(本任务 AgentTask ID,UUID)、`code`(任务编号,如 `SPW299-59`)、`objectId`(关联业务任务对象)、`taskPrompt`(本次目标原文)、`contextSnapshot`、`status`。
24
+ - **业务任务详情**(标题/描述):`morula task get <objectId>`;**关键评论**(用户原始诉求/分析结论):`morula comment list --object <objectId>`。
25
+ - **「你的目标」**:系统提示「你的目标」段 = 本次任务的原始诉求,据此判断要做什么。
26
+
27
+ > **工作目录 = 任务工作区根**:根下放 `AGENTS.md`(空间项目说明,已作为 project.instructions 自动加载)、
28
+ > `.agents/`、`.agent_context/`、`.morula/`;**各仓库是工作区根下的子目录**(见 0.5)。
29
+ > 下文的 `AGENTS.md`/`.agents/`/`.agent_context/` 路径都相对这个工作区根。
30
+
31
+ > **任务上下文文件**:同一套上下文也写在工作目录 `.agent_context/task-context.md`(与首条系统提示同源)。
32
+ > 需要再次确认任务边界时**读取该文件**(`cat .agent_context/task-context.md`),不要凭记忆推断。
33
+
34
+ 详情见下文各节对 TaskID / 对外编号的具体用法。
35
+
36
+ ## 0.1 通用行为约束
37
+
38
+ 以下为所有任务(含 Chat 会话派发)恒常生效的行为约束,**无论任务是否有关联业务对象**:
39
+
40
+ 1. **直接回答用户的问题**。若用户要求查任务/了解项目(如「有哪些待处理任务」「XX任务详情」),请用 `morula` CLI(`morula task list`、`morula task get`、`morula space list` 等)或对应的挂载 skill(如 `task-management` 的 `search_objects` / `get_object_detail`)查询真实数据后再答;不要凭猜测或「我没有渠道」回绝。不要执行「验证性/自证性」命令来向用户证明链路是通的(如 `morula auth status` 等)。
41
+ 2. **本任务若没有关联的业务对象(objectId 为空,常见于 Chat 会话派发)**:只允许**读**业务 API;不要调用 `morula comment add/delete`(必然失败,也无需尝试),也不要主动改任务状态/指派(状态由服务端按 PR 事件自动流转)。若任务关联了业务对象,则按本 SKILL 的评论/状态纪律正常处理。
42
+ 3. **回复要简洁、结论先行**:用大白话直接回答用户问题;不要写「交付清单」「完成情况」「做了什么」式汇报,不要复述你调了什么命令。
43
+ 4. **仅当用户明确要求改代码/建分支/MR 时**,才按本 SKILL 的协作契约执行;否则本任务不涉及分支、PR、评论,回复里也不要出现这些章节。
44
+
45
+ ## 0.2 本任务编号
46
+
47
+ > **适用前提(PR/分支类契约)**:本节及后文 PR/分支/关单约定**仅当任务涉及代码改动时适用**。
48
+ > 纯问答、调研、分析、本地起服务类任务:**不建分支、不提 PR、最终评论不出现 PR 章节**,直接回答用户问题。
49
+
50
+ - 本任务的对外编号(在任务看板 URL / 列表可见)可能形如 `STR-101` 或 `AGENT-463963`。
51
+ - **PR 关联**:PR 的 title / branch / body 中出现任务编号 → 服务端自动把 PR 关联到任务。
52
+ - **关单**:只有 `Closes` / `Fixes` / `Resolves` 后**紧邻**任务编号(如 `Closes STR-101`)→ 该 PR merge 时服务端自动把任务流转到完成状态。
53
+ - **编号用对外编号(code,如 AGENT-463963 / STR-101),不要用 UUID**:MR/分支里的任务编号必须用对外编号——服务端的关单/关联扫描(`Closes` 通道)只识别对外编号,UUID 只用于业务对象(Object),agent 任务(AgentTask)不会被 UUID 关单识别(那会把 MR 关联失败)。
54
+ - 若任务详情只有 UUID 没有对外编号,把任务编号写进 PR 描述前先确认是否存在对外编号;确无再用 UUID 作为兜底。
55
+
56
+ ## 0.3 分支命名:由你(Agent)取名并上报
57
+
58
+ - **任务 ID 获取**:本任务的 AgentTask ID(UUID)用 `morula task get`(无参)返回的 `id` 取得——taskId 钥匙由执行环境提供,不进系统提示。`.agent_context/task-context.md` 开头也有同一 UUID。需要调用 `morula task branch` 时用它作为 taskId。
59
+ - **重要区分**:`morula task branch <taskId>` 的 taskId 用**本任务 UUID**(机器协议);但 **MR / 分支 / PR 里的任务编号用对外编号 code**(如 `AGENT-958201`,用于服务端关联/关单)——**不要用 UUID 标记 MR**(UUID 关单只认业务对象,AgentTask 不会被识别,会导致 MR 关联失败)。
60
+ - **只有需要改代码并推分支的任务才取名**;纯问答/只读调研/本地起服务不建分支(见第 3 条决策树)。
61
+ - **先检查当前分支**:若仓库已有 feature 分支(同任务/同一改动闭环的后续轮次复用),**直接继续用当前分支,不要新建**(在仓库目录内执行):
62
+ ```bash
63
+ cd <工作区根>/<仓库目录> && git branch --show-current # 若非空且非 main/master → 用这个分支继续改
64
+ ```
65
+ - 若当前为 detached HEAD(首次执行),且任务确实要改代码:
66
+ 1. 创建分支。分支名**严格由以下部分依次拼成,不多不少**:`<type>` + `/` + `<本任务对外编号>` + `-` + `<slug>`
67
+ - `<type>`:取下方「命名规范」清单里的一个(按任务性质选,如文档改动用 docs);
68
+ - `<本任务对外编号>`:见 0.2,**原样照抄**该编号到分支名里(大小写与内容都不改动,也不省略);
69
+ - `<slug>`:具体工作事情,小写、用 `-` 连接。
70
+ ```bash
71
+ git switch -c <type>/<本任务对外编号>-<slug>
72
+ ```
73
+ 例:任务编号为 SPW299-30、要做 meta build-info → 分支名 `feature/SPW299-30-meta-build-info`
74
+ 分支名**到 `<slug>` 为止就结束** —— 编号与 slug 之后不再接任何内容。
75
+ 2. **通过 CLI 把分支名上报服务端**(必须,服务端据此做 PR 关联/清理):
76
+ ```bash
77
+ morula task branch <taskId> --name <type>/<任务编号>-<slug>
78
+ ```
79
+ - **`morula task branch` 需要 `MORULA_RUNTIME_ID`**(daemon 注入的 env,用于校验你的归属)。若你的 exec 环境没有这个 env,**从主仓库目录内的 `.morula-worktree.json` 读取 `runtimeId` 字段**(完整路径 `<工作区根>/<仓库目录>/.morula-worktree.json`;工作区根下没有这个文件),用 `env MORULA_RUNTIME_ID=<该值> morula task branch ...` 或导出后再调用(不要从别处猜)。
80
+ - **命名规范**(分支名会出现在 PR、CI、历史里,必须规范):
81
+ - 前缀 type 选一:`feature`(新功能)、`fix`(缺陷修复)、`hotfix`(紧急/线上)、`docs`(文档)、`refactor`(重构)、`chore`(杂务)、`test`(测试)、`perf`(性能)、`style`(样式)、`build`(构建/依赖)、`ci`(CI)、`revert`(回滚);
82
+ - 任务编号 = 本任务的**对外编号**(见 0.2,如 `SPW299-30`),**原样大写**:服务端按 title/branch/body 扫描任务编号关联 MR,**匹配大小写敏感**(`[A-Z][A-Z0-9]{1,9}-数字`),写成小写会关联不到;
83
+ - slug = 具体工作事情,小写、用 `-` 连接(可用英文;**不要用中文**,中文分支名不被多数 CI/工具链支持);
84
+ - **只按本节规则拼,不要参考/模仿仓库里已有分支的命名**:历史分支是过往产物(可能含日期后缀、编号大小写不一、缺编号等),照着它们取名会沿用旧习惯;分支名完全由上面三段决定,与仓库现有分支无关;
85
+ - **分支名到 slug 就结束**:编号与 slug 之后**不再追加任何内容**——日期、时间戳、轮次号、随机串都不要;日期不表达语义,且会让「同一件事的后续轮次复用同分支」看起来像要新建分支;
86
+ - **禁止**用 `main`/`master`,禁止以 `-` 开头/结尾,长度 ≤ 255。
87
+ - 如果你不改代码,**不要**创建分支,也不要调用 `morula task branch`。
88
+
89
+ ## 0.4 开工前先确认工作现场
90
+
91
+ - **先确认自己站在哪个目录**:工作目录是**任务工作区根**,仓库在其下的子目录(见 0.5)。`git/gh` 命令必须在**仓库目录内**执行。
92
+ - **改代码前先看现场**(防止在错误的分支/目录上动手)——进入对应仓库目录后:
93
+ ```bash
94
+ cd <工作区根>/<仓库目录> # 主仓库目录 = 仓库全名把 / 换成 -(如 human-resources-xxx)
95
+ git status # 是否有未提交改动(复用前一轮成果时正常,但要知道有什么)
96
+ git branch --show-current # 当前分支;detached HEAD 表示本轮尚未取名
97
+ ```
98
+ - 若 `git status` 显示工作目录有**不属于本任务**的改动(如误入其他任务 worktree),**立即停止并报告**,不要继续编辑。
99
+ - **不要在工作区根直接跑 git/gh**:工作区根不是 git 仓库(除仓库子目录外还有 `AGENTS.md`/`.agents/`/`.agent_context/`/`.morula/`),在根目录跑 git 只会报 `not a git repository`。`morula` CLI 反之可从工作区根**或其任意子目录**执行(它从 cwd 向上查找 `.morula/checkout.json` 凭据)。
100
+
101
+ ## 0.5 仓库与工作目录
102
+
103
+ - **工作目录 = 任务工作区根**;仓库是它下面的**子目录**(清单里没有的仓库不在本地):
104
+
105
+ ```
106
+ <工作区根>/ ← 你的 cwd(pwd 就在这里)
107
+ ├── AGENTS.md ← 空间项目说明(已自动加载为 project.instructions)
108
+ ├── .agents/skills/ ← 协作契约 + 本任务自定义 skill
109
+ ├── .agent_context/task-context.md ← 任务上下文(见 0 节)
110
+ ├── .morula/checkout.json ← daemon 凭据(CLI 内部使用,不要手动读)
111
+ ├── <owner>-<repo>/ ← 主仓库(系统提示「仓库可用性」里的那个)
112
+ └── <owner>-<repo2>/ ← 次仓库 / 只读检出(按需物化,与主仓库同级)
113
+ ```
114
+
115
+ - **git / gh 命令一律在对应仓库目录内执行**(`cd <工作区根>/<仓库目录>`,或 `git -C <仓库目录> ...`):
116
+ 工作区根不是 git 仓库,在根目录跑 git 命令必然失败。`morula` CLI 可在工作区根或其任意子目录执行(见 0.4)。
117
+ - **主仓库**:系统提示给出的仓库已在工作区根下就绪(目录名 = 仓库全名把 `/` 换成 `-`,如 `human-resources/xxx` → `human-resources-xxx`);直接在该目录内建分支/提交/推分支/提 PR。
118
+ - **若任务需要了解仓库内容/代码/提交状态(读场景,含纯问答)**:用 `morula repo checkout <taskId> <repoFullName>`(本地 daemon 端点,**本轮立即物化**并返回路径,通常是工作区根下的 `<仓库目录>-readonly`),然后在该路径内只读查证:
119
+ - 读代码/提交/分支:`git -C <路径> log --oneline -20`、`git -C <路径> show <sha>`、`git -C <路径> branch -a`、`rg "关键词" <路径>`、读文件等(**只读**,不建分支、不改代码、不提 PR);
120
+ - 刷新远端最新:`git -C <路径> fetch origin --prune`(remote 已配置 SSH,免密)。
121
+ - **本地没有 ≠ 仓库不存在**:仓库由 `morula repo checkout` 物化,**不要**因为在 `repos/`、`worktrees/` 或常见目录搜索不到就断言「本地没有该仓库」——先执行 checkout(它会从远端拉取/物化),再基于物化结果回答。
122
+ - **若任务需要改代码**:用 `morula repo checkout <taskId> <repoFullName>` 拉取/切换仓库(认证已由执行环境注入),
123
+ 拿到返回的仓库路径后**chdir 进该仓库目录**,再按第 0.3 条建分支、第 3 条提 PR。可选 `--ref <branch|sha>` 指定检出分支。
124
+ - **若任务纯问答、或空间无可用仓库**:直接回答用户问题,不构建分支/PR(见第 3 条决策树)。
125
+ - **若任务涉及多个仓库(多仓库任务)**:涉及哪些仓库由**你**结合任务描述判断(系统提示「多仓库任务」段 + 任务描述「涉及仓库」段是管家的初始判断;执行中发现还需改动其他仓库时,自行 prepare 拉取),**不需要向用户逐个确认**:
126
+ 1. 主仓库:在它的子目录内直接 git 操作(工作区根不是仓库);
127
+ 2. 其他仓库用 `morula repo prepare <taskId> <repoFullName>` 物化到任务工作区根下(**与主仓库同级的子目录**,本地 daemon 端点,本轮立即物化并返回路径;git 协议拉取,不走平台 API);
128
+ 3. 在哪个仓库改动,就在哪个仓库内建分支(第 0.3 条命名规范)、推分支并建 PR/MR(title/description 带任务编号);每个仓库的 PR 服务端都会自动关联到本任务。**分支名逐仓上报**:主仓库(其子目录)用 `morula task branch <taskId> --name <分支名>`;次仓库(同级子目录)用 `morula task branch <taskId> --name <分支名> --repo <owner/name>` —— 服务端据此把分支名写到该仓库的工作上下文(平台侧逐仓建 MR 兜底、交付清单都依赖它;不报则一直停在占位分支名 `morula/tmp-*`,平台找不到你的分支);
129
+ 4. prepare 目录二次调用会 fetch 更新但**不动工作区**——跨轮复用时先 `git status` 确认现场。
130
+ - **安全边界**:只在本任务的任务工作区根(及其仓库子目录)内操作;禁止探索/修改工作区根之外的路径(尤其 daemon 根目录下其他任务的 `repos/`、`worktrees/`、`scratch/`)。
131
+ - **本地验证服务端口隔离**(issues/local-backend-watch-instability.md):若任务需要启动本地服务验证(如 `npm run dev` / `nest start` 等),**必须使用非平台占用端口**——绑定 `127.0.0.1` 的高位随机端口(如 127.0.0.1:18080+),**禁止**使用 `8080`(平台后端)及 `15173`(平台前端)等平台占用端口,也不要依赖终端会话存活(后台进程方式启动,用 `nohup`/`disown` 或 `&!` 脱离);启动后自测可用即可,不需要(也不应)让平台侧访问。云端沙箱化后由沙箱网络策略兜底。
132
+
133
+ ## 1. 与平台交互:只能通过 `morula` CLI
134
+
135
+ - **所有**平台资源访问(任务详情、评论、附件、状态、工作区)只通过 `morula` CLI(可从工作区根或其任意子目录执行,见 0.4/0.5)。
136
+ **`<taskId>` = `morula task get`(无参)返回的 `id`**(本任务 AgentTask UUID)——下文所有机器协议命令的 id 位都用它,不要用任务编号或别的 id 替代。
137
+ ```bash
138
+ morula task get # 本任务详情(**无参**;返回 id / code / objectId / taskPrompt / contextSnapshot / status)
139
+ morula task get <objectId> # 业务任务(Object)详情(标题/描述);注意这里要的是 objectId,不是本任务 UUID
140
+ morula task branch <taskId> --name <分支名> [--repo <owner/name>] # 上报你取的分支名(见 0.3;多仓库任务的次仓库加 --repo)
141
+ morula repo checkout <taskId> <repoFullName> [--ref <branch|sha>] # 拉取/切换仓库(返回物化路径,见 0.5)
142
+ morula repo prepare <taskId> <repoFullName> # 多仓库任务:物化其他仓库到任务工作区根下(见 0.5)
143
+ morula comment list --object <objectId> # 读评论(任务=objectId)
144
+ # 注:最终总结评论由服务端统一发送(作者=智能体),不要调用 comment add 自行置评。
145
+ ```
146
+ - **可用仓库清单**由系统提示直接给出(「可用仓库」/「多仓库任务」段),**不需要**自己查;只有在系统提示未给出、又确实需要枚举时才用
147
+ `morula repo list --space <spaceId>`(`<spaceId>` = `morula task get` 返回的 `contextSnapshot.spaceId`)。
148
+ - **每个命令的完整参数以 `morula <resource> --help` 为准**(如 `morula repo --help` 只列 repo 的用法);不确定某命令支持哪些旗标时先查它,不要凭记忆拼参数。
149
+ - **禁止** `curl` / `wget` 直连平台 REST API;禁止读取 `~/.morula-cli/config.yaml`、token 文件或环境变量中的密钥。
150
+ - **本地 `.morula/checkout.json` 是 daemon 管理文件**(`morula repo checkout` 内部读取端口与任务凭据),**不要手动读取/复制其中的字段**;凭据由 CLI 处理,你不需要也不应当看到它。
151
+ - CLI 的 `--token`/环境变量凭据已由 daemon 注入,无需(也不允许)自行登录或携带令牌。
152
+ - 如果某项操作 CLI 不支持,在最终评论里说明,**不要**用绕过方式硬做。
153
+
154
+ ## 1.1 生成文件必须回传平台(智能体 → 平台)
155
+
156
+ 若你的交付物是**文件**(报告、文档、图表格、xlsx、图片、压缩包等),且用户需要在线查看/下载,**必须**用 `morula attachment upload` 把本机文件回传到平台,而不是在消息里给一个本地相对路径/文件名(用户打不开):
157
+
158
+ - **纯 Chat 会话任务(推荐)**:本任务不关联业务对象(task-context.md 无 objectId),**必须带 `--task <AgentTaskID>`**(AgentTask ID = `morula task get`(无参)返回的 `id`(或 task-context.md 开头的 UUID)),服务端据此把文件挂到本 Chat 会话消息:
159
+ ```bash
160
+ morula attachment upload <file> --task <AgentTaskID>
161
+ ```
162
+ - **默认智能归并**(有绑定对象时):不传目标参数,服务端自动决定——有任务绑定对象挂对象:
163
+ ```bash
164
+ morula attachment upload <file>
165
+ ```
166
+ - **显式挂任务对象**(用户从任务/评论发起、任务有 objectId):
167
+ ```bash
168
+ morula attachment upload <file> --object <objectId>
169
+ ```
170
+ - **显式挂指定评论**:`morula attachment upload <file> --comment <commentId>`
171
+ - **显式挂 Chat 会话消息**:`morula attachment upload <file> --ag-message --task <AgentTaskID>`
172
+
173
+ **回传后**:命令输出 JSON,含 `downloadUrl` 与附件 `id`。在最终总结里**引用下载链接**(`http...` 或附件的可下载地址),并说明文件名即可——不要把本地绝对路径/相对文件名当交付物。<br>
174
+ **文件类型不限**,单文件上限 20MB;超限需拆分或说明,不要硬传失败。
175
+
176
+ ## 1.2 自动化的信息类产出:用 `morula report create`
177
+
178
+ 当本任务是**自动化(定时/触发)派发**的,且你的产出是给人看的**信息类结果**(巡检/扫描结论、摘要、分析),用报告承载:
179
+
180
+ ```bash
181
+ morula report create --title "<一句话标题>" --content "<Markdown 正文>" # 正文长时用 --content-file <path>
182
+ ```
183
+
184
+ - 报告落在**本次执行所属自动化的「报告」入口**(不在任务看板、不占任务编号),归属由平台自动绑定,不用传;
185
+ - 一次执行可产出多份(一份报告一个主题);**要建工单请用任务,不要把待办事项写进报告正文**;
186
+ - 报告落库后创建者与订阅者会收到轻通知(只含标题与跳转);
187
+ - 不产出也没关系:任务终态时平台会用你的最终输出兜底落一份报告(但兜底不如你主动组织的内容清晰,信息类产出**优先主动产出**)。
188
+
189
+ ## 2. PR 链接与关单意图是两套不同的契约
190
+
191
+ - **链接通道**:PR 的 title / branch / body 中出现任务编号 → 服务端自动关联 PR 到任务。
192
+ 推荐写法:title 以「STR-101: 描述」开头,或 branch 用 `str-101-xxx`,或 body 写「Closes STR-101」。
193
+ - **关单通道**:只有 `Closes` / `Fixes` / `Resolves` 后**紧邻**任务编号(如「Closes STR-101」)→ 该 PR
194
+ merge 时服务端自动把任务流转到完成状态。title 前缀或 branch 引用只会关联 PR,不会在 merge 时关单。
195
+ - body 中松散的提及(如「Related to STR-101」)会被标记为纯引用,不展示在任务的 PR 列表中。
196
+
197
+ ## 3. 什么时候推分支、提 PR(决策树)
198
+
199
+ 先判断**这次任务是否改了仓库里的代码**,再决定要不要分支/PR:
200
+
201
+ | 任务类型 | 是否建分支/上报分支名 | 是否推分支 + 建 PR |
202
+ |---|---|---|
203
+ | **改了代码**(改文件、修 bug、加功能、重构) | ✅ 取名 + `morula task branch`(第 0.3 条) | ✅ 推 feature 分支 + 建 PR/MR(命令见下方「执行要点」,按平台选) |
204
+ | **改了代码但被环境阻断无法建 PR**(权限/远端异常/无凭据等) | ✅ 已取名 | ⚠️ 最终总结里用文字报告阻塞原因,并带上约定关键词 **`PR_BLOCKED`**(**这不是 CLI 错误码**,只是给平台/人看的标记;daemon 也会回报交付告警) |
205
+ | **只回答问题 / 调研分析**(不改仓库文件) | ❌ 不建分支 | ❌ 不推、不建 PR,直接回答用户问题(不要声明「无需 PR」等无关补充) |
206
+ | **本地起服务 / 运行命令**(不落代码到仓库) | ❌ 不建分支 | ❌ 不推、不建 PR |
207
+ | **改了代码但用户明确只要本地** | ✅ 可建分支 | ❌ 不推 PR,总结说明 |
208
+
209
+ 执行要点:
210
+
211
+ - **以下命令都在对应仓库目录内执行**(`cd <工作区根>/<仓库目录>`;工作区根不是仓库);
212
+ - 修改了代码 → **推送到 feature 分支(禁止推 main/master)并确保有 PR**;
213
+ - 分支用你在第 0.3 条取的名(或复用的已有分支)。**创建/更新 PR(MR) 的命令按平台选择**(本文件末尾的「GitLab 平台说明」段会覆盖此处的 GitHub 写法):
214
+ - **GitHub 仓库**:先查该分支是否已有 PR:
215
+ ```bash
216
+ gh pr list --head <branch> --state open # 已有 → 直接 push 更新,不要重复创建
217
+ ```
218
+ - **已有 PR**(同一改动闭环的后续轮次)→ 只 `git push -u origin <branch>` 更新,**不要**再 `gh pr create`;
219
+ - **无 PR** → 创建:
220
+ ```bash
221
+ git push -u origin <branch>
222
+ gh pr create --base <默认分支> --head <branch> --title "<任务编号>: 描述" --body "Closes <任务编号>"
223
+ ```
224
+ - **GitLab 仓库**:推分支 + 建 MR 用一条平台命令(**不要**用 `gh`、不要手写 `git push -o merge_request.create`):
225
+ ```bash
226
+ morula repo mr create <repoFullName> --branch <branch> --base <默认分支> --task-code <任务编号>
227
+ ```
228
+ 该命令等价于「推分支 + 服务端建 MR」,MR 一定建在平台绑定的仓库上,详见末尾「GitLab 平台说明」段。
229
+ - **不确定用哪条**:本任务的工作区来自哪个平台由「GitLab 平台说明」段是否存在决定——**存在即 GitLab**,按 GitLab 走。
230
+ - 没有改代码(纯调研/问答/起服务)→ **直接回答用户问题**,不要声明「无需 PR」或「未改动任何代码」这类无关补充,也不要为了形式而建 PR;
231
+ - PR 创建被阻塞(权限/测试失败/远端状态异常)→ 在最终总结中**报告阻塞原因**,不要假装任务已完成;
232
+ - **不要**切回 `main` 或 `master` 直接提交。
233
+
234
+ ## 4. 状态变更由服务端驱动,不要手动翻状态
235
+
236
+ - 任务状态推进(如完成)由 PR merge 事件自动触发(配合第 2 条的关单写法)。
237
+ - 不要直接调 CLI 改任务状态;把状态流转交给服务端。
238
+
239
+ ## 4.1 分析任务:必须上报分析结论(verdict)
240
+
241
+ > **仅当本任务是「待分析」任务时适用**(任务停在「待分析」阶段、要求做需求分析与方案拆解、不写代码)。
242
+ > 其余任务(开发 / 评审 / 调研 / 问答)跳过本节,不要调用该命令。
243
+
244
+ - 分析结论分两部分,**都要给**:
245
+ 1. **人看的**:分析结论写进最终输出(需求是否清晰、怎么做、范围与风险、验收点)——服务端作为任务评论发布;
246
+ 2. **机器判定的**:执行 `morula task analysis-verdict <taskId> --verdict ready|blocked [--reason "…"]` 上报结论。
247
+ - `<taskId>` = `morula task get`(无参)返回的 `id`(本任务 AgentTask UUID)。
248
+ - **`ready`** = 需求清晰、可直接开发(`--reason` 可写一句话范围摘要);
249
+ **`blocked`** = 不可做 / 需澄清(**必须**在 `--reason` 或最终输出里写清阻塞点与需要澄清的问题)。
250
+ - **服务端据此自动推进,不需要任何人点确认**:`ready` → 任务自动进入「开发中」并派发开发任务;
251
+ `blocked` / 未上报 → 任务**停在「待分析」**,由用户补充需求后重派分析。
252
+ - **必须上报**:漏报会让任务静默停在「待分析」;同一任务只需上报一次(重复上报以首次为准,不会改写)。
253
+ - **不要手动流转本任务状态**(不要调 `morula task state`):分析任务的推进由服务端按 verdict 决定。
254
+
255
+ ## 4.2 评审任务:只读评审 + 必须回传结论(verdict)
256
+
257
+ > **仅当本任务是「Code Review 评审任务」时适用**(系统提示出现「本任务是 **Code Review 评审任务**」;要求对开发提交的改动做评审、不写代码)。
258
+ > 其余任务(开发 / 分析 / 调研 / 问答)跳过本节,不要调用该命令。
259
+
260
+ - 评审是**只读**的:物化仓库后查看 PR 分支相对目标分支的**实际改动**(`git diff`),**禁止修改代码、建分支、推分支、合并 MR**。
261
+ - 评审维度:正确性 / 安全性 / 代码风格 / 测试覆盖 / 是否符合原始需求;**必须读取真实 diff 后再下结论**,禁止凭空评审、编造结论。
262
+ - **评审所需信息全部用工具获取**(系统提示不注入):
263
+ - 评审对象:`morula task get`(无参)返回的 `objectId`(关联业务任务);
264
+ - 业务任务目标(标题/描述):`morula task get <objectId>`;关键评论:`morula comment list --object <objectId>`;
265
+ - PR 编号/链接:**GitLab** 用 `morula repo mr list <taskId> --state opened`(见末尾「GitLab 平台说明」段);**GitHub** 用 `gh pr list --head <branch>`;两者都可用业务对象详情里的 PR 关联兜底。
266
+ - **先答「目标达成度」**:对照业务任务目标逐条给出「达成 / 部分达成 / 未达成」;**未达成目标不得判 approved**;若开发改动与业务目标不匹配(做的是另一件事),verdict 必须为 `changes_requested`,不得以「描述残留、不影响验收」为由判通过。
267
+ - **必须回传结论**(机器协议),执行:
268
+ ```bash
269
+ morula review submit <objectId> --verdict approved|changes_requested|commented [--details <文本>] [--pr-number <PR编号>]
270
+ ```
271
+ - `<objectId>` = `morula task get`(无参)返回的 `objectId`;
272
+ - `--verdict`:`approved`(通过)/ `changes_requested`(不通过,**必须**在 `--details` 写明具体意见:文件/行/问题/建议,否则开发无法修复)/ `commented`(有意见但不阻塞);
273
+ - `--pr-number`:评审对象的 PR 编号(有则填)。
274
+ - **禁止评审自己开发的任务**:若待评审改动由你自己提交(同一智能体既开发又评审),不下结论,直接报告该配置问题。
275
+ - **不要手动流转本任务状态**(不要调 `morula task state`):评审结论由服务端据此推进。
276
+ - **必须上报**:漏报会让任务停在「待评审」,开发无法进入下一轮。
277
+
278
+ ## 5. 评论交付纪律
279
+
280
+ - **何时发评论**:**最终总结评论由服务端统一以智能体身份发送**(任务完成时自动发布到任务对象,作者=当前智能体)。**你不需要、也不应调用 `morula comment add`**——只要在最终输出里给出用户关心的结论即可,服务端会作为该智能体的完成评论发布。chat 会话无 objectId 的任务(纯对话派发)不回评论——最终交付由 daemon 回填 assistant 消息。
281
+ - **终端输出、思考过程、工具日志用户都看不到**——用户看到的只有服务端整合后发布的最终总结评论。
282
+ - **评论 @触发任务(本任务由评论触发,触发评论 ID 见系统提示)**:服务端会把最终总结自动作为**该触发评论的回复**发出(挂在被 @ 的评论下)。你无需、也不要自己调用 `morula comment add`/add_comment。
283
+ - **防重复(服务端保证)**:服务端对「同一个任务只发一条完成评论」做幂等,不会重复刷屏。你不需要、也不要去查/发多条。
284
+ - **最终输出 = 只留结论**(不设字数上限、不要求固定格式,自然组织即可):
285
+ - 必答:**做了什么、结果如何、用户需要知道的后续**。
286
+ - **PR 汇报仅当「任务涉及代码改动」或「用户明确问到 PR/合并」时给出**;纯问答/调研类任务不要出现 PR/分支章节,直接回答问题。
287
+ - **不要写**:思考过程、探索步骤、工具日志、终端输出、英文过程性叙述——用户只关心「做了什么、结果怎样」,不关心「怎么做的」。
288
+ - 内容较多时用小标题/列表组织,让结论先置顶。
289
+ - **过程文本必须加分隔符**:服务端取你的最终输出时按优先级截取「总结段」,**最高优先级是分隔符 `---FINAL---`**(独占一行、前后各空一行)。若你的输出里混入了中间过程(英文探索叙述、工具尝试记录、思考片段),**必须**在真正的总结之前插入该分隔符——服务端只保留它之后的内容:
290
+
291
+ ```
292
+ ...(前面的过程文本,会被服务端自动截掉)...
293
+
294
+ ---FINAL---
295
+
296
+ ## 总结
297
+
298
+ ...(真正的总结内容,会展示给用户)...
299
+ ```
300
+
301
+ 服务端同时兼容 `---SUMMARY---`。**不写分隔符时**,服务端只能退回「按总结类标题 / 水平线猜测」,猜不中就会把你的过程文本一起发给用户——所以混了过程就一定要加。
302
+ - 不要包含工具调试过程、内部日志、任何凭据或 token 信息。
303
+ - **不要把本地绝对路径当交付物**:工作目录路径只属于执行它的机器,读者打不开;生成的文件**必须先回传平台**(见 1.1 `morula attachment upload`),交付物用平台下载链接或仓库内相对路径。
304
+
305
+ ## 6. 禁止事项
306
+
307
+ - 禁止直接调用 morula REST API / 非 CLI 方式写评论、改状态、读数据;**不要调用 `morula comment add` 自行置评**——完成评论由服务端统一发送。
308
+ - 禁止读取 `~/.morula-cli`、凭据文件、环境变量中的密钥。
309
+ - 只在任务指定的工作目录与仓库内操作,不要探索工作目录之外的文件系统。
310
+ - 禁止把 token、安装 ID、内部 URL 写进评论或 commit。
311
+
312
+ ## 7. References
313
+
314
+ `references/working-on-issues-source-map.md` —— 每条契约对应的服务端/CLI 实现位置
315
+ (`file:line`),用于校准契约与实现一致。
@@ -0,0 +1,77 @@
1
+ # morula-working-on-issues — 契约到实现溯源(source map)
2
+ 每条契约都对应以下实现位置。合并/重构后行号可能漂移,先核对再依赖精确行号。
3
+
4
+ ## 1. PR 链接 vs 关单双通道
5
+
6
+ | 契约 | 服务端实现 | 位置 |
7
+ |------|-----------|------|
8
+ | 任务 key 匹配(`SPW299-30`) | `VcsLinkService.TASK_KEY_RE`(扫描 title/**branch**/body;正则 `[A-Z][A-Z0-9]{1,9}-数字`,**大小写敏感** → 分支名里的编号必须原样大写) | `src/modules/vcs-integration/link/vcs-link.service.ts:30,435` |
9
+ | Close intent 单独扫描(title/body,`Closes|Fixes|Resolves` 紧跟 key) | `CLOSE_INTENT_RE` | `vcs-link.service.ts:29` |
10
+ | reference-only 隐藏(松散 body 提及不入 PR 列表) | `resolveTargets` 与 closeIntent 分支 | `vcs-link.service.ts:60-80, 184-186` |
11
+ | merge 关单幂等 | merged → closeIntent 链接对应任务 | `vcs-link.service.ts:19-20`(文件头注释) |
12
+
13
+ ## 2. 与平台交互只能走 morula CLI
14
+
15
+ | 契约 | 实现 | 位置 |
16
+ |------|------|------|
17
+ | CLI 读评论 | `morula comment list --object <objectId>` | `apps/morula-cli/src/commands/comment.ts` |
18
+ | CLI 写评论(**契约禁止 agent 自行调用**:完成评论由服务端统一发送,见本文件第 5 条) | `morula comment add`(命令存在,但本契约要求不调用) | `apps/morula-cli/src/commands/comment.ts` |
19
+ | CLI 任务详情(**无参**取本任务;带参取业务对象) | `morula task get` → `GET /agent-tasks/:id`;`morula task get <objectId>` → `GET /objects/:id` | `apps/morula-cli/src/commands/task.ts` |
20
+ | daemon 注入凭据(agent 无需自行登录) | runner 注入 `env` 给 Provider 子进程 | `packages/runtime/src/daemon/runner.ts` |
21
+ | 系统提示注入(阶段职责 / 目标 / 仓库清单 / 工具指引);任务详情由 `morula task get`(无参)获取 | `buildTaskPromptWithContract`(`runner.ts`,唯一进 dim 的 messages[0]);CLI `apps/morula-cli/src/commands/task.ts`(resolveTaskId → `GET /agent-tasks/:id`) | `packages/runtime/src/daemon/runner.ts` |
22
+
23
+ ## 3. 改代码默认产 PR(分支纪律,Agent 取名)
24
+
25
+ | 契约 | 实现 | 位置 |
26
+ |------|------|------|
27
+ | 分支名由 Agent 取名并上报(格式 `<type>/<任务编号>-<slug>`,见契约 0.3) | workContext 初始占位符 `morula/tmp-<random6>`;`morula task branch <id> --name <name> [--repo <owner/name>]` 更新(多仓库任务的次仓库带 `--repo`) | `buildPlaceholderBranchName`(`collaboration-logic.ts`)、`POST /agent-tasks/:id/branch`(`agent-task.controller.ts`)、`commands/task.ts`。**服务端不生成分支名**(曾有的 `buildBranchName` 编码旧规范,已删除) |
28
+ | 独立 worktree 创建(detached,不建占位分支) | `prepareWorktree`(`git worktree add --detach`) | `packages/runtime/src/daemon/worktree-manager.ts` |
29
+ | 复用检测(允许 agent 改名,禁主干) | marker 身份校验不含 branchName;detached 残留自动保全重建(未提交改动 → stash `morula-rescue <tag>`、未推送提交 → `refs/morula-rescue/<tag>`),保全失败/主干 → `WORKTREE_BRANCH_DRIFT` | `worktree-manager.ts`、`workspace-marker.ts` |
30
+ | 任务编号补齐兜底 | `ensurePullRequestTaskCode` | `packages/runtime/src/daemon/runner.ts` |
31
+ | delivery 以本地实际分支为准 | `reportPrDelivery` 读 `git branch --show-current`,跳过占位/主干 | `packages/runtime/src/daemon/runner.ts` |
32
+ | 交付上报遍历本任务全部仓库 | 主仓库目录(工作区根下的仓库子目录)+ 其余同级仓库子目录(prepare 产物,磁盘事实;按 repoFullName 归属排除主仓库),逐仓上报 branch/pushState、逐仓独立成败;次仓库 `workContextId` 省略时服务端按 `(lastAgentTaskId, repoFullName)` 反查 | `reportPrDelivery`/`reportRepoTargets`(`runner.ts`)、`collectMaterializedRepoDeliveryTargets`(`repo-checkout-service.ts`) |
33
+
34
+ ## 4. 状态由服务端驱动(agent 不手动翻状态)
35
+
36
+ | 契约 | 实现 | 位置 |
37
+ |------|------|------|
38
+ | merge 自动关单(服务端驱动状态) | webhook merged → closeIntent → Object 完成 | `src/modules/vcs-integration/webhook/vcs-event.service.ts` |
39
+ | agent 不调 CLI 改状态 | 协作契约第 4 条。**注意:CLI 有 `morula task state <id> --state <code>`**(机器协议 `POST /agent-tasks/:id/state`,供平台/自流转使用)——是**契约要求 agent 不调用**它,不是「CLI 没有这个命令」 | `apps/morula-cli/src/commands/task.ts`(action `state`)、`src/modules/agent-task/agent-task.controller.ts`(端点) |
40
+
41
+ ## 4.1 评审任务结论回传(morula review submit)
42
+
43
+ | 契约 | 实现 | 位置 |
44
+ |------|------|------|
45
+ | 评审结论机器协议(approved / changes_requested / commented,changes_requested 必须带 details) | `morula review submit <objectId> --verdict ... [--details] [--pr-number]` → `POST /agent-collaborations/review/submit`(服务端校验调用者 = 评审任务后落库并驱动回环) | `apps/morula-cli/src/commands/review.ts`、`src/modules/agent-task/review-loop.service.ts` |
46
+ | 评审对象(objectId)经 AgentTask 本体下发(`morula task get` 无参返回) | AgentTask 记录 `objectId` 字段(enqueue 时写入) | `src/modules/agent-task/review-loop.service.ts` |
47
+
48
+ ## 5. 评论交付纪律(一条最终评论,结果上平台)
49
+
50
+ | 契约 | 实现 | 位置 |
51
+ |------|------|------|
52
+ | 评论是用户可见的唯一通道;**完成评论由服务端统一以智能体身份发送**(agent 不调 `comment add`) | 服务端 `finalOutput` 回填 + 聚合发布;评论 @触发任务的总结自动挂到触发评论下(hasAgentRepliedToTrigger 去重) | `src/modules/agent-task/task-lifecycle.service.ts`、本 SKILL 第 5 条 |
53
+ | 最终输出截取「总结段」(最高优先级 = `---FINAL---` 分隔符) | `cleanFinalOutput`:分隔符 → 总结类标题 → 水平线 → 原文兜底 | `src/modules/agent-task/task-lifecycle.service.ts` |
54
+ | **评论 @触发任务 → 最终总结由服务端自动回复到触发评论下**(agent 无需评论 ID,不注入提示词) | 结构性挂靠:`CommentService.create`(resolveCommentAuthor 返回 triggerCommentId → parentCommentId 默认值);生命周期兜底:`task-lifecycle.service.ts` hasAgentRepliedToTrigger / isMentionReply | LOCAL-AGENT-COMMENT-REPLY-001 |
55
+ | 禁止本地绝对路径作为交付物 | 契约第 5 条;`reportPrDelivery` 只回报仓库相对信息 | `packages/runtime/src/daemon/runner.ts` |
56
+ | 单条最终评论(不刷进度) | 契约第 5 条 | 本 SKILL |
57
+
58
+ ## 6. 禁止事项
59
+
60
+ | 禁止 | 依据 |
61
+ |------|------|
62
+ | 直连平台 REST API / curl / wget | 契约第 1、6 条;`allowed-tools` frontmatter |
63
+ | 读取 `~/.morula-cli` / token | 契约第 6 条;daemon 已注入凭据 |
64
+ | 探索工作目录外文件系统 | 契约第 6 条 |
65
+ | 推 main/master | 契约第 3 条;worktree-manager 分支漂移防护 |
66
+ | 在工作区根直接跑 git/gh(工作区根不是仓库) | 契约 0.4/0.5 |
67
+
68
+ ## 7. 任务工作区布局与 AGENTS.md(task-workspace-layout)
69
+
70
+ | 契约 | 实现 | 位置 |
71
+ |------|------|------|
72
+ | 工作区根 = `<workdir>/worktrees/<tenant>/<runtimeId8>/<dirName>`,仓库为其下的 `<safeDirName(repoFullName)>` 子目录 | `buildTaskRootPath` / `buildWorktreePath` / `prepareWorktree` | `packages/runtime/src/daemon/worktree-manager.ts` |
73
+ | cwd = 工作区根(ACP session cwd / `dimWorkDir` / `registerTask`) | `prepareTaskWorkspace` 返回 `taskRoot`,git 操作仍用 `repoDir` | `packages/runtime/src/daemon/runner.ts` |
74
+ | `.agents/`、`.agent_context/`、`AGENTS.md` 写在工作区根(不在仓库内,故不再写用户仓库 .gitignore) | `writeCustomSkills` / `writeCollaborationSkill` / `writeTaskContextBrief` / `writeProjectAgentsMd`;`.gitignore` 只注入 worktree marker | `packages/runtime/src/daemon/runner.ts`、`worktree-manager.ts` |
75
+ | 空间 AGENTS.md 自动生效(dim 从 cwd 向上查找为 `project.instructions`) | 服务端下发 `projectAgentsMd` → daemon 写工作区根 `AGENTS.md` | `runner.ts` `writeProjectAgentsMd`、`api.ts` `TaskDetail.projectAgentsMd` |
76
+ | 次仓库/只读检出物化到工作区根下同级子目录(`<repo>` / `<repo>-readonly`) | `resolveRepoTargetDir` / `materializeAdditionalRepo` / `materializeReadWorktree` | `packages/runtime/src/daemon/repo-checkout-service.ts` |
77
+ | cleanup 删整个工作区(含注入物与全部仓库目录),有未推送改动则拒绝;旧布局仍可安全清理 | `validateRemovable` / `findAdditionalRepoActivity` / `removeTaskWorkspace` | `packages/runtime/src/daemon/worktree-manager.ts` |
package/package.json CHANGED
@@ -1,14 +1,16 @@
1
1
  {
2
2
  "name": "@arcships/morula-runtime",
3
3
  "type": "module",
4
- "version": "0.1.0-alpha.1",
5
- "description": "Device-level CLI for the Morula local agent runtime (daemon/portal/models/auth), distributed via npm",
4
+ "version": "0.1.0-alpha.3",
5
+ "description": "Device-level CLI for the Morula local agent runtime (daemon/portal/models/auth) and the Morula business CLI (morula), distributed via npm",
6
6
  "bin": {
7
- "morula-runtime": "./bin/morula-runtime.js"
7
+ "morula-runtime": "./bin/morula-runtime.js",
8
+ "morula": "./bin/morula.js"
8
9
  },
9
10
  "files": [
10
11
  "dist",
11
- "bin"
12
+ "bin",
13
+ "scripts/postinstall.mjs"
12
14
  ],
13
15
  "engines": {
14
16
  "node": ">=20"
@@ -17,8 +19,11 @@
17
19
  "gen-build-env": "node scripts/gen-build-env.mjs",
18
20
  "build:env": "node scripts/gen-build-env.mjs",
19
21
  "build:dist": "node scripts/build-dist.mjs",
22
+ "gen:plugin-skill": "tsx scripts/gen-plugin-skill.mts",
20
23
  "prepublishOnly": "pnpm run gen-build-env && pnpm run build:dist",
24
+ "postinstall": "node scripts/postinstall.mjs",
21
25
  "dev": "tsx src/index.ts",
26
+ "dev:morula": "pnpm --filter @morula/morula-cli run dev",
22
27
  "lint:type": "tsc --noEmit -p tsconfig.json",
23
28
  "test": "tsx --test --test-concurrency=1 test/*.test.ts",
24
29
  "smoke": "tsx src/index.ts --help"
@@ -29,6 +34,7 @@
29
34
  },
30
35
  "devDependencies": {
31
36
  "@morula/morula-api-client": "workspace:*",
37
+ "@morula/morula-cli": "workspace:*",
32
38
  "@morula/morula-cli-core": "workspace:*",
33
39
  "@types/js-yaml": "^4.0.9",
34
40
  "@types/node": "^24.2.0",
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * postinstall:尽力把 Dim 插件目录物化到本机(docs/features/single-package-cli-distribution.md)。
4
+ *
5
+ * 为什么放在 postinstall:用户的目标是「装一个包就能用」。skills 与 agent 执行 PATH 上的
6
+ * `morula` 只能由 Dim 的插件根提供,所以安装时直接注册一次。
7
+ *
8
+ * 硬约束:**绝不阻断安装**。
9
+ * - 任何失败(资产缺失、权限、非交互环境、`--ignore-scripts` 之外的意外)只 warn + exit 0;
10
+ * - 失败后可随时手动补:`morula-runtime setup`;
11
+ * - 只跑本包的 `setup` 命令(注册逻辑单一来源),不在这里复制一份实现。
12
+ */
13
+ import { execFileSync } from 'node:child_process'
14
+ import { existsSync } from 'node:fs'
15
+ import { dirname, join } from 'node:path'
16
+ import { fileURLToPath } from 'node:url'
17
+
18
+ const root = dirname(dirname(fileURLToPath(import.meta.url)))
19
+ const entry = join(root, 'dist', 'index.js')
20
+
21
+ /** 静默跳过(源码 checkout 未构建 / 无 dist 产物):不是错误,不给噪音 */
22
+ if (!existsSync(entry)) {
23
+ process.exit(0)
24
+ }
25
+
26
+ try {
27
+ const output = execFileSync(process.execPath, [entry, 'setup'], { encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'] })
28
+ const result = JSON.parse(output)
29
+ const legacy = result.legacyRoot ? `;检测到旧插件目录 ${result.legacyRoot},建议删除(Dim 优先命中 ${result.pluginRoot})` : ''
30
+ console.error(`[morula-runtime] 插件已注册:${result.pluginRoot}(skills: ${(result.skills ?? []).join(', ')})${legacy}`)
31
+ console.error('[morula-runtime] Dim 会在**新 session** 加载插件;手动重注册:morula-runtime setup')
32
+ }
33
+ catch (error) {
34
+ const detail = error instanceof Error ? error.message.split('\n')[0] : String(error)
35
+ console.error(`[morula-runtime] 插件注册跳过(不影响安装):${detail}`)
36
+ console.error('[morula-runtime] 可随时手动重试:morula-runtime setup')
37
+ }
38
+ process.exit(0)