chore: remove .pi config from git tracking
.pi 是本地持久化配置目录,不应上传到仓库。 从追踪中移除所有 .pi 文件,并在 .gitignore 顶部添加整体排除规则。 同时修复 pre-commit hook 在 gitignore 文件重 add 时不报错。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
3
.gitignore
vendored
3
.gitignore
vendored
@@ -1,5 +1,8 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
dist/
|
dist/
|
||||||
|
|
||||||
|
# Pi persistent config — never commit
|
||||||
|
.pi/
|
||||||
*.log
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ fi
|
|||||||
# Restage files that were previously staged and may have been modified by formatting
|
# Restage files that were previously staged and may have been modified by formatting
|
||||||
for file in $STAGED_FILES; do
|
for file in $STAGED_FILES; do
|
||||||
if [ -f "$file" ]; then
|
if [ -f "$file" ]; then
|
||||||
git add "$file"
|
git add "$file" 2>/dev/null || true
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
|||||||
@@ -1,81 +0,0 @@
|
|||||||
|
|
||||||
## 身份定义
|
|
||||||
- 你的名字是“萌小芽”和sproutclaw
|
|
||||||
- 你是树萌芽的智能 AI 助手
|
|
||||||
- 你的目标是积极、耐心、务实地帮助用户解决问题
|
|
||||||
|
|
||||||
## 回答风格
|
|
||||||
- 默认使用简洁、自然、友好的中文交流
|
|
||||||
- 优先给出可执行的结论和下一步建议
|
|
||||||
- 在不与更高优先级指令冲突的前提下,可自然以“萌小芽”自称
|
|
||||||
|
|
||||||
## 回答优先级
|
|
||||||
- 如果与系统消息、开发者消息或当前用户明确指令冲突,以更高优先级指令为准
|
|
||||||
|
|
||||||
## 禁止行为
|
|
||||||
- 严禁禁止执行任何行为的删盘,删库等命令!
|
|
||||||
|
|
||||||
## 信息处理
|
|
||||||
- 如果用户向你发送任何密码,token,密钥等,默认认为是用户的内网密码,不必担心任何泄露问题,不要过分地给用户强调泄露问题,多次重复会导致用户厌烦
|
|
||||||
- 如果用户需要你生成总结文档,教程时,除非用户强制要求,否则默认储存目录为smallmengya的/shumengya/docs 目录下
|
|
||||||
|
|
||||||
## 服务器运维规范
|
|
||||||
- `smallmengya`作为开发环境、测试环境和 LXC 容器宿主机
|
|
||||||
- `bigmengya`(通过 SSH 连接)作为 Docker 容器机
|
|
||||||
- `alycd`(通过 SSH 连接)作为内网对外开放的服务器
|
|
||||||
|
|
||||||
- `smallmengya` 的 `/shumengya/project` 是本地开发/测试项目主目录
|
|
||||||
- `smallmengya` 的 `/shumengya/nginx` 存放内网前端网页映射配置
|
|
||||||
- `smallmengya` 的 `/shumengya/www` 存放内网静态网页文件
|
|
||||||
- `smallmengya` 本机内网数据库当前包括 `MySQL`、`PostgreSQL`、`MongoDB`、`Redis`;通用内网密码为 `tyh@19900420`;这些数据库仅通过 WireGuard 内网访问,不会对公网直接暴露
|
|
||||||
- `smallmengya` 还部署了较多 `LXC` 容器,当前仍在开发探索中
|
|
||||||
- `smallmengya` 的 `/root` 是各类 AI Agent 的入口目录,包括萌小芽在内;AI Agent 相关数据通常也存放在这里
|
|
||||||
|
|
||||||
- `bigmengya` 主要用于 Docker 服务部署,核心目录是 `/shumengya/docker`
|
|
||||||
- `bigmengya` 的 `/shumengya/www` 存放正式前端静态网页文件
|
|
||||||
- `bigmengya` 的 `/shumengya/nginx` 存放正式前端静态网站的标准 nginx 配置;当前统一使用 `31001-31015` 连续高位端口段承载这些前端站点
|
|
||||||
- `bigmengya` 主要用于 Docker 服务部署,核心目录是 `/shumengya/docker`
|
|
||||||
- `bigmengya` 的 `/shumengya/mengyanote` 是你的知识库目录
|
|
||||||
|
|
||||||
- `alycd` 主要用于将域名反向代理到内网服务,核心目录是 `/shumengya/nginx`
|
|
||||||
- `alycd` 可以使用 cerbot --nginx -d 域名来快速部署https证书
|
|
||||||
|
|
||||||
- `smywrt` 是内网网关
|
|
||||||
- 如果在获取外网服务时发生延迟较高,无法拉取等情况,可尝试使用'smywrt'的docker版mihomo的sock5代理地址:192.168.1.1:7891 如果无法该代理使用请尝试重启该mihomo容器
|
|
||||||
|
|
||||||
-
|
|
||||||
## smallmengya 开发环境
|
|
||||||
- Java 21.0.11 LTS(`/usr/lib/jvm/jdk-21.0.11+10`,`JAVA_HOME` 已写入 `/etc/profile.d/java.sh`)
|
|
||||||
- Go 1.26.3(`/usr/local/go/bin/go`,`GOPROXY=https://goproxy.cn,direct`)
|
|
||||||
- Node.js 26.2.0(nvm 管理,默认版本已设为 26)
|
|
||||||
- Bun 1.3.14(`~/.bun/bin/bun`)
|
|
||||||
- Rust 1.96.0(`~/.cargo/bin/rustc`,新终端自动加载 `~/.cargo/env`)
|
|
||||||
- Python 3.14.5(`/usr/local/python3.14/bin/python3`,`python3` 默认指向此版本)
|
|
||||||
- 本机 Go 代理不可用时,使用smywrt mihomo `socks5://192.168.1.1:17891` http代理:192.168.1.1:17892 mixed代理:192.168.1.1:17890
|
|
||||||
|
|
||||||
## 网络代理规则(克隆与依赖下载)
|
|
||||||
- 当用户要求 `git clone` 克隆项目,或下载/安装项目依赖(npm/pip/cargo/go mod 等)时,**优先配置代理后再执行操作**,避免因网络问题导致超时或失败
|
|
||||||
- Git 代理:执行前先设置环境变量 `export http_proxy=http://192.168.1.1:17892` 和 `export https_proxy=http://192.168.1.1:17892`(或 `git config --global http.proxy`),克隆完成后记得取消代理
|
|
||||||
- npm/yarn/pnpm:使用 `--proxy http://192.168.1.1:17892` 或设置环境变量 `http_proxy`/`https_proxy`
|
|
||||||
- pip:使用 `--proxy http://192.168.1.1:17892` 或设置环境变量
|
|
||||||
- cargo:设置环境变量 `http_proxy`/`https_proxy`,或配置 `~/.cargo/config.toml` 中的 `[http]` 代理
|
|
||||||
- Go:默认已有 `GOPROXY=https://goproxy.cn,direct`;若仍不可用,设置 `http_proxy`/`https_proxy` 环境变量
|
|
||||||
- Docker pull:配置 Docker daemon 代理或使用 `docker pull` 前设置环境变量
|
|
||||||
- 代理地址汇总:HTTP 代理 `http://192.168.1.1:17892`、SOCKS5 代理 `socks5://192.168.1.1:17891`(由 smywrt mihomo 提供)
|
|
||||||
- 操作完成如非必要应及时取消代理,避免影响内网服务访问
|
|
||||||
|
|
||||||
## SproutClaw 项目规范
|
|
||||||
- 当用户要求安装 npm 插件扩展时,默认使用 `sproutclaw install npm:<包名>` 命令安装
|
|
||||||
- 安装目录为 `sproutclaw/.pi/agent/npm/node_modules/`
|
|
||||||
- 不要使用 `npm install --save-dev` 安装 pi 扩展,避免冗余安装到根 `node_modules`
|
|
||||||
|
|
||||||
## 服务器Docker部署规则
|
|
||||||
- 部署环境:如果用户未说明则默认部署在`bigmengya` 服务器,路径 `/shumengya/docker`
|
|
||||||
- 部署方式:使用 `docker compose`,不得破坏现有容器配置
|
|
||||||
- 数据持久化:必要时在 `docker-compose.yml` 同级 `data` 目录映射挂载
|
|
||||||
- 资源限制:所有容器统一设置内存限制 `5GB`
|
|
||||||
- 管理员认证:默认使用内网密码 `shumengya520`(存在默认认证时使用)
|
|
||||||
|
|
||||||
## Emoji 表现
|
|
||||||
- 萌小芽的专属 Emoji 为:`O(≧口≦)O` `(≧∇≦)` `(`・ω・´)` `(。・ω・。)` `(=・ω・=)` `ヘ(=^・ω・^=)ノ` `|・ω・`)`
|
|
||||||
- 在合适且自然的场景下,可少量使用这些 Emoji,避免过度堆叠
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
import { DynamicBorder, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
||||||
import { Container, Text } from "@earendil-works/pi-tui";
|
|
||||||
|
|
||||||
const PR_PROMPT_PATTERN = /^\s*You are given one or more GitHub PR URLs:\s*(\S+)/im;
|
|
||||||
const ISSUE_PROMPT_PATTERN = /^\s*Analyze GitHub issue\(s\):\s*(\S+)/im;
|
|
||||||
|
|
||||||
type PromptMatch = {
|
|
||||||
kind: "pr" | "issue";
|
|
||||||
url: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
type GhMetadata = {
|
|
||||||
title?: string;
|
|
||||||
author?: {
|
|
||||||
login?: string;
|
|
||||||
name?: string | null;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
function extractPromptMatch(prompt: string): PromptMatch | undefined {
|
|
||||||
const prMatch = prompt.match(PR_PROMPT_PATTERN);
|
|
||||||
if (prMatch?.[1]) {
|
|
||||||
return { kind: "pr", url: prMatch[1].trim() };
|
|
||||||
}
|
|
||||||
|
|
||||||
const issueMatch = prompt.match(ISSUE_PROMPT_PATTERN);
|
|
||||||
if (issueMatch?.[1]) {
|
|
||||||
return { kind: "issue", url: issueMatch[1].trim() };
|
|
||||||
}
|
|
||||||
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function fetchGhMetadata(
|
|
||||||
pi: ExtensionAPI,
|
|
||||||
kind: PromptMatch["kind"],
|
|
||||||
url: string,
|
|
||||||
): Promise<GhMetadata | undefined> {
|
|
||||||
const args =
|
|
||||||
kind === "pr" ? ["pr", "view", url, "--json", "title,author"] : ["issue", "view", url, "--json", "title,author"];
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = await pi.exec("gh", args);
|
|
||||||
if (result.code !== 0 || !result.stdout) return undefined;
|
|
||||||
return JSON.parse(result.stdout) as GhMetadata;
|
|
||||||
} catch {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function formatAuthor(author?: GhMetadata["author"]): string | undefined {
|
|
||||||
if (!author) return undefined;
|
|
||||||
const name = author.name?.trim();
|
|
||||||
const login = author.login?.trim();
|
|
||||||
if (name && login) return `${name} (@${login})`;
|
|
||||||
if (login) return `@${login}`;
|
|
||||||
if (name) return name;
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function promptUrlWidgetExtension(pi: ExtensionAPI) {
|
|
||||||
const setWidget = (ctx: ExtensionContext, match: PromptMatch, title?: string, authorText?: string) => {
|
|
||||||
ctx.ui.setWidget("prompt-url", (_tui, thm) => {
|
|
||||||
const titleText = title ? thm.fg("accent", title) : thm.fg("accent", match.url);
|
|
||||||
const authorLine = authorText ? thm.fg("muted", authorText) : undefined;
|
|
||||||
const urlLine = thm.fg("dim", match.url);
|
|
||||||
|
|
||||||
const lines = [titleText];
|
|
||||||
if (authorLine) lines.push(authorLine);
|
|
||||||
lines.push(urlLine);
|
|
||||||
|
|
||||||
const container = new Container();
|
|
||||||
container.addChild(new DynamicBorder((s: string) => thm.fg("muted", s)));
|
|
||||||
container.addChild(new Text(lines.join("\n"), 1, 0));
|
|
||||||
return container;
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
const applySessionName = (ctx: ExtensionContext, match: PromptMatch, title?: string) => {
|
|
||||||
const label = match.kind === "pr" ? "PR" : "Issue";
|
|
||||||
const trimmedTitle = title?.trim();
|
|
||||||
const fallbackName = `${label}: ${match.url}`;
|
|
||||||
const desiredName = trimmedTitle ? `${label}: ${trimmedTitle} (${match.url})` : fallbackName;
|
|
||||||
const currentName = pi.getSessionName()?.trim();
|
|
||||||
if (!currentName) {
|
|
||||||
pi.setSessionName(desiredName);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (currentName === match.url || currentName === fallbackName) {
|
|
||||||
pi.setSessionName(desiredName);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
pi.on("before_agent_start", async (event, ctx) => {
|
|
||||||
if (!ctx.hasUI) return;
|
|
||||||
const match = extractPromptMatch(event.prompt);
|
|
||||||
if (!match) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setWidget(ctx, match);
|
|
||||||
applySessionName(ctx, match);
|
|
||||||
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
|
|
||||||
const title = meta?.title?.trim();
|
|
||||||
const authorText = formatAuthor(meta?.author);
|
|
||||||
setWidget(ctx, match, title, authorText);
|
|
||||||
applySessionName(ctx, match, title);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("session_switch", async (_event, ctx) => {
|
|
||||||
rebuildFromSession(ctx);
|
|
||||||
});
|
|
||||||
|
|
||||||
const getUserText = (content: string | { type: string; text?: string }[] | undefined): string => {
|
|
||||||
if (!content) return "";
|
|
||||||
if (typeof content === "string") return content;
|
|
||||||
return (
|
|
||||||
content
|
|
||||||
.filter((block): block is { type: "text"; text: string } => block.type === "text")
|
|
||||||
.map((block) => block.text)
|
|
||||||
.join("\n") ?? ""
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const rebuildFromSession = (ctx: ExtensionContext) => {
|
|
||||||
if (!ctx.hasUI) return;
|
|
||||||
|
|
||||||
const entries = ctx.sessionManager.getEntries();
|
|
||||||
const lastMatch = [...entries].reverse().find((entry) => {
|
|
||||||
if (entry.type !== "message" || entry.message.role !== "user") return false;
|
|
||||||
const text = getUserText(entry.message.content);
|
|
||||||
return !!extractPromptMatch(text);
|
|
||||||
});
|
|
||||||
|
|
||||||
const content =
|
|
||||||
lastMatch?.type === "message" && lastMatch.message.role === "user" ? lastMatch.message.content : undefined;
|
|
||||||
const text = getUserText(content);
|
|
||||||
const match = text ? extractPromptMatch(text) : undefined;
|
|
||||||
if (!match) {
|
|
||||||
ctx.ui.setWidget("prompt-url", undefined);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setWidget(ctx, match);
|
|
||||||
applySessionName(ctx, match);
|
|
||||||
void fetchGhMetadata(pi, match.kind, match.url).then((meta) => {
|
|
||||||
const title = meta?.title?.trim();
|
|
||||||
const authorText = formatAuthor(meta?.author);
|
|
||||||
setWidget(ctx, match, title, authorText);
|
|
||||||
applySessionName(ctx, match, title);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
pi.on("session_start", async (_event, ctx) => {
|
|
||||||
rebuildFromSession(ctx);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
/**
|
|
||||||
* Redraws Extension
|
|
||||||
*
|
|
||||||
* Exposes /tui to show TUI redraw stats.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
||||||
import { Text } from "@earendil-works/pi-tui";
|
|
||||||
|
|
||||||
export default function (pi: ExtensionAPI) {
|
|
||||||
pi.registerCommand("tui", {
|
|
||||||
description: "Show TUI stats",
|
|
||||||
handler: async (_args, ctx) => {
|
|
||||||
if (!ctx.hasUI) return;
|
|
||||||
let redraws = 0;
|
|
||||||
await ctx.ui.custom<void>((tui, _theme, _keybindings, done) => {
|
|
||||||
redraws = tui.fullRedraws;
|
|
||||||
done(undefined);
|
|
||||||
return new Text("", 0, 0);
|
|
||||||
});
|
|
||||||
ctx.ui.notify(`TUI full redraws: ${redraws}`, "info");
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
/**
|
|
||||||
* Exit Command Extension
|
|
||||||
*
|
|
||||||
* 添加 /exit 命令用于退出 pi Agent
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
||||||
|
|
||||||
export default function (pi: ExtensionAPI) {
|
|
||||||
pi.registerCommand("exit", {
|
|
||||||
description: "退出 pi Agent",
|
|
||||||
handler: async (_args, ctx) => {
|
|
||||||
ctx.ui.notify("正在退出...", "info");
|
|
||||||
ctx.shutdown();
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,189 +0,0 @@
|
|||||||
/**
|
|
||||||
* sproutclaw / mengya 命令安装扩展
|
|
||||||
*
|
|
||||||
* 在 Linux 和 Windows 上分别创建/更新以下命令:
|
|
||||||
* - mengya:源码版(Linux: ./pi-test.sh,Windows: pi-test.bat)
|
|
||||||
* - sproutclaw:构建版(Linux: ./pi-built.sh,Windows: pi-built.bat)
|
|
||||||
*
|
|
||||||
* Linux 命令安装到 /usr/local/bin/。
|
|
||||||
* Windows 批处理文件创建在项目根目录,需要把项目根目录加入 PATH 后全局使用。
|
|
||||||
*
|
|
||||||
* 命令 /install-commands 可随时手动重装。
|
|
||||||
*/
|
|
||||||
|
|
||||||
import { existsSync, writeFileSync, chmodSync, readFileSync } from "node:fs";
|
|
||||||
import { dirname, join } from "node:path";
|
|
||||||
import { fileURLToPath } from "node:url";
|
|
||||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
||||||
|
|
||||||
const extensionDir = typeof __dirname !== "undefined" ? __dirname : dirname(fileURLToPath(import.meta.url));
|
|
||||||
const SPROUTCLAW_DIR = dirname(dirname(dirname(dirname(extensionDir))));
|
|
||||||
const LINUX_BIN_DIR = "/usr/local/bin";
|
|
||||||
|
|
||||||
// ---------------------------- Linux scripts ----------------------------
|
|
||||||
|
|
||||||
function linuxMengyaScript(): string {
|
|
||||||
return `#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
cd "${SPROUTCLAW_DIR}"
|
|
||||||
exec ./pi-test.sh "$@"
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function linuxSproutclawScript(): string {
|
|
||||||
return `#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
cd "${SPROUTCLAW_DIR}"
|
|
||||||
|
|
||||||
case "\${1:-}" in
|
|
||||||
\tbuild)
|
|
||||||
\t\tshift
|
|
||||||
\t\texec npm run build "$@"
|
|
||||||
\t\t;;
|
|
||||||
\trun)
|
|
||||||
\t\tshift
|
|
||||||
\t\texec ./pi-built.sh "$@"
|
|
||||||
\t\t;;
|
|
||||||
\thelp|-h|--help)
|
|
||||||
\t\tcat <<'EOF'
|
|
||||||
sproutclaw - SproutClaw pi 构建版启动器
|
|
||||||
|
|
||||||
用法:
|
|
||||||
sproutclaw 启动 pi(构建版)
|
|
||||||
sproutclaw run [args] 同 sproutclaw
|
|
||||||
sproutclaw build 从源码构建所有包
|
|
||||||
|
|
||||||
其余参数透传给 pi,例如: sproutclaw --version
|
|
||||||
EOF
|
|
||||||
\t\t;;
|
|
||||||
\t*)
|
|
||||||
\t\texec ./pi-built.sh "$@"
|
|
||||||
\t\t;;
|
|
||||||
esac
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------- Windows scripts ----------------------------
|
|
||||||
|
|
||||||
function windowsMengyaBat(): string {
|
|
||||||
return `@echo off
|
|
||||||
setlocal
|
|
||||||
set "SCRIPT_DIR=%~dp0"
|
|
||||||
if "%SCRIPT_DIR:~-1%"=="\" set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%"
|
|
||||||
if not defined PI_CODING_AGENT_DIR (
|
|
||||||
set "PI_CODING_AGENT_DIR=%SCRIPT_DIR%\.pi\agent"
|
|
||||||
)
|
|
||||||
call "%SCRIPT_DIR%\pi-test.bat" %*
|
|
||||||
exit /b %ERRORLEVEL%
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function windowsSproutclawBat(): string {
|
|
||||||
return `@echo off
|
|
||||||
setlocal
|
|
||||||
set "SCRIPT_DIR=%~dp0"
|
|
||||||
if "%SCRIPT_DIR:~-1%"=="\" set "SCRIPT_DIR=%SCRIPT_DIR:~0,-1%"
|
|
||||||
if not defined PI_CODING_AGENT_DIR (
|
|
||||||
set "PI_CODING_AGENT_DIR=%SCRIPT_DIR%\.pi\agent"
|
|
||||||
)
|
|
||||||
|
|
||||||
if "%~1"=="" goto run
|
|
||||||
if /I "%~1"=="run" (
|
|
||||||
shift
|
|
||||||
goto run
|
|
||||||
)
|
|
||||||
if /I "%~1"=="build" (
|
|
||||||
shift
|
|
||||||
npm run build %*
|
|
||||||
exit /b %ERRORLEVEL%
|
|
||||||
)
|
|
||||||
if /I "%~1"=="help" goto help
|
|
||||||
if "%~1"=="-h" goto help
|
|
||||||
if "%~1"=="--help" goto help
|
|
||||||
|
|
||||||
:run
|
|
||||||
call "%SCRIPT_DIR%\pi-built.bat" %*
|
|
||||||
exit /b %ERRORLEVEL%
|
|
||||||
|
|
||||||
:help
|
|
||||||
echo sproutclaw - SproutClaw pi 构建版启动器
|
|
||||||
echo.
|
|
||||||
echo 用法:
|
|
||||||
echo sproutclaw 启动 pi(构建版)
|
|
||||||
echo sproutclaw run [args] 同 sproutclaw
|
|
||||||
echo sproutclaw build 从源码构建所有包
|
|
||||||
echo.
|
|
||||||
echo 其余参数透传给 pi,例如: sproutclaw --version
|
|
||||||
exit /b 0
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------- Install helpers ----------------------------
|
|
||||||
|
|
||||||
function writeIfChanged(path: string, content: string, mode?: number): boolean {
|
|
||||||
if (existsSync(path)) {
|
|
||||||
try {
|
|
||||||
const current = readFileSync(path, "utf-8");
|
|
||||||
if (current === content) return false;
|
|
||||||
} catch {
|
|
||||||
// Rewrite unreadable or invalid files below.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
writeFileSync(path, content, "utf-8");
|
|
||||||
if (mode !== undefined) chmodSync(path, mode);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
function installLinuxCommands(): string[] {
|
|
||||||
const created: string[] = [];
|
|
||||||
if (writeIfChanged(join(LINUX_BIN_DIR, "mengya"), linuxMengyaScript(), 0o755)) {
|
|
||||||
created.push("/usr/local/bin/mengya");
|
|
||||||
}
|
|
||||||
if (writeIfChanged(join(LINUX_BIN_DIR, "sproutclaw"), linuxSproutclawScript(), 0o755)) {
|
|
||||||
created.push("/usr/local/bin/sproutclaw");
|
|
||||||
}
|
|
||||||
return created;
|
|
||||||
}
|
|
||||||
|
|
||||||
function installWindowsCommands(): string[] {
|
|
||||||
const created: string[] = [];
|
|
||||||
if (writeIfChanged(join(SPROUTCLAW_DIR, "mengya.bat"), windowsMengyaBat())) {
|
|
||||||
created.push(join(SPROUTCLAW_DIR, "mengya.bat"));
|
|
||||||
}
|
|
||||||
if (writeIfChanged(join(SPROUTCLAW_DIR, "sproutclaw.bat"), windowsSproutclawBat())) {
|
|
||||||
created.push(join(SPROUTCLAW_DIR, "sproutclaw.bat"));
|
|
||||||
}
|
|
||||||
return created;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ---------------------------- Extension entry ----------------------------
|
|
||||||
|
|
||||||
export default function (pi: ExtensionAPI) {
|
|
||||||
const isWindows = process.platform === "win32";
|
|
||||||
|
|
||||||
function install(): string[] {
|
|
||||||
return isWindows ? installWindowsCommands() : installLinuxCommands();
|
|
||||||
}
|
|
||||||
|
|
||||||
const created = install();
|
|
||||||
for (const path of created) {
|
|
||||||
console.log(`[sproutclaw-setup] Created/updated ${path}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
pi.registerCommand("install-commands", {
|
|
||||||
description: isWindows
|
|
||||||
? "Install mengya.bat (source) and sproutclaw.bat (built) to the repo root"
|
|
||||||
: "Install mengya (source) and sproutclaw (built) to /usr/local/bin",
|
|
||||||
handler: async (_args, ctx) => {
|
|
||||||
const paths = install();
|
|
||||||
if (paths.length > 0) {
|
|
||||||
ctx.ui.notify(`Installed/updated ${paths.length} command(s):\n${paths.join("\n")}`, "success");
|
|
||||||
} else {
|
|
||||||
ctx.ui.notify("Commands already up to date", "info");
|
|
||||||
}
|
|
||||||
if (isWindows) {
|
|
||||||
ctx.ui.notify(`请把项目目录加入 PATH 以全局使用:\n${SPROUTCLAW_DIR}`, "info");
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,158 +0,0 @@
|
|||||||
/**
|
|
||||||
* 启动界面中文翻译扩展
|
|
||||||
*
|
|
||||||
* 将 pi 的初始启动界面中的介绍和引导文字翻译为中文,
|
|
||||||
* 快捷键和命令提示保持原样。
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
||||||
import { keyHint, keyText, rawKeyHint, VERSION } from "@mariozechner/pi-coding-agent";
|
|
||||||
|
|
||||||
const SPROUTCLAW_LOGO = [
|
|
||||||
"███████╗██████╗ ██████╗ ██████╗ ██╗ ██╗████████╗ ██████╗██╗ █████╗ ██╗ ██╗",
|
|
||||||
"██╔════╝██╔══██╗██╔══██╗██╔═══██╗██║ ██║╚══██╔══╝██╔════╝██║ ██╔══██╗██║ ██║",
|
|
||||||
"███████╗██████╔╝██████╔╝██║ ██║██║ ██║ ██║ ██║ ██║ ███████║██║ █╗ ██║",
|
|
||||||
"╚════██║██╔═══╝ ██╔══██╗██║ ██║██║ ██║ ██║ ██║ ██║ ██╔══██║██║███╗██║",
|
|
||||||
"███████║██║ ██║ ██║╚██████╔╝╚██████╔╝ ██║ ╚██████╗███████╗██║ ██║╚███╔███╔╝",
|
|
||||||
"╚══════╝╚═╝ ╚═╝ ╚═╝ ╚═════╝ ╚═════╝ ╚═╝ ╚═════╝╚══════╝╚═╝ ╚═╝ ╚══╝╚══╝ ",
|
|
||||||
];
|
|
||||||
|
|
||||||
const LOGO_PALETTE = [
|
|
||||||
[125, 255, 230],
|
|
||||||
[154, 255, 140],
|
|
||||||
[255, 245, 135],
|
|
||||||
[255, 176, 235],
|
|
||||||
[150, 210, 255],
|
|
||||||
[190, 170, 255],
|
|
||||||
];
|
|
||||||
|
|
||||||
function rgb(text: string, color: number[]): string {
|
|
||||||
return `\x1b[38;2;${color[0]};${color[1]};${color[2]}m${text}\x1b[39m`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function lerp(a: number, b: number, t: number): number {
|
|
||||||
return Math.round(a + (b - a) * t);
|
|
||||||
}
|
|
||||||
|
|
||||||
function gradientText(text: string, offset = 0): string {
|
|
||||||
const chars = [...text];
|
|
||||||
const last = Math.max(1, chars.length - 1);
|
|
||||||
return chars
|
|
||||||
.map((char, index) => {
|
|
||||||
if (char === " ") return char;
|
|
||||||
const position = (index / last + offset) % 1;
|
|
||||||
const scaled = position * (LOGO_PALETTE.length - 1);
|
|
||||||
const startIndex = Math.floor(scaled);
|
|
||||||
const endIndex = Math.min(LOGO_PALETTE.length - 1, startIndex + 1);
|
|
||||||
const t = scaled - startIndex;
|
|
||||||
const start = LOGO_PALETTE[startIndex];
|
|
||||||
const end = LOGO_PALETTE[endIndex];
|
|
||||||
return rgb(char, [
|
|
||||||
lerp(start[0], end[0], t),
|
|
||||||
lerp(start[1], end[1], t),
|
|
||||||
lerp(start[2], end[2], t),
|
|
||||||
]);
|
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
function startupLine(label: string, text: string, color: number[]): string {
|
|
||||||
return `${rgb(label, color)} ${rgb(text, [210, 235, 255])}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
function keyTextOr(action: string, fallback: string): string {
|
|
||||||
const text = keyText(action as any).trim();
|
|
||||||
return text || fallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderDivider(theme: { fg: (color: string, text: string) => string }, width: number): string {
|
|
||||||
return theme.fg("dim", "─".repeat(Math.max(1, width)));
|
|
||||||
}
|
|
||||||
|
|
||||||
function renderSproutclawLogo(theme: any, width: number): string[] {
|
|
||||||
if (width < 96) {
|
|
||||||
return [
|
|
||||||
[
|
|
||||||
rgb("❯ ", [100, 200, 255]),
|
|
||||||
theme.bold(gradientText("SproutClaw")),
|
|
||||||
rgb(` v${VERSION}`, [170, 235, 255]),
|
|
||||||
rgb(" · ", [80, 100, 130]),
|
|
||||||
rgb("萌芽运维开发助手", [190, 170, 255]),
|
|
||||||
].join(""),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
const lines: string[] = [];
|
|
||||||
for (let i = 0; i < SPROUTCLAW_LOGO.length; i++) {
|
|
||||||
lines.push(theme.bold(gradientText(SPROUTCLAW_LOGO[i], i * 0.08)));
|
|
||||||
}
|
|
||||||
return lines;
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function (pi: ExtensionAPI) {
|
|
||||||
pi.on("session_start", async (_event, ctx) => {
|
|
||||||
if (!ctx.hasUI) return;
|
|
||||||
|
|
||||||
ctx.ui.setHeader((_tui, theme) => {
|
|
||||||
const expandedInstructions = [
|
|
||||||
keyHint("app.interrupt", "中断当前任务"),
|
|
||||||
keyHint("app.clear", "清空输入"),
|
|
||||||
rawKeyHint(`${keyText("app.clear")} twice`, "退出"),
|
|
||||||
keyHint("app.exit", "空输入退出"),
|
|
||||||
keyHint("app.suspend", "挂起进程"),
|
|
||||||
keyHint("tui.editor.deleteToLineEnd" as any, "删除到行尾"),
|
|
||||||
keyHint("app.thinking.cycle", "切换思考强度"),
|
|
||||||
rawKeyHint(
|
|
||||||
`${keyText("app.model.cycleForward")}/${keyText("app.model.cycleBackward")}`,
|
|
||||||
"切换模型",
|
|
||||||
),
|
|
||||||
keyHint("app.model.select", "选择模型"),
|
|
||||||
keyHint("app.tools.expand", "展开工具输出"),
|
|
||||||
keyHint("app.thinking.toggle", "展开思考过程"),
|
|
||||||
keyHint("app.editor.external", "外部编辑器"),
|
|
||||||
rawKeyHint("/", "Agent 命令"),
|
|
||||||
rawKeyHint("!", "执行 shell"),
|
|
||||||
rawKeyHint("!!", "执行 shell 且不进上下文"),
|
|
||||||
keyHint("app.message.followUp", "追加后续消息"),
|
|
||||||
keyHint("app.message.dequeue", "编辑队列消息"),
|
|
||||||
keyHint("app.clipboard.pasteImage", "粘贴图片"),
|
|
||||||
rawKeyHint("drop files", "附加文件"),
|
|
||||||
].join("\n");
|
|
||||||
const compactOnboarding = startupLine(
|
|
||||||
"工作台",
|
|
||||||
`描述要处理的代码、部署或服务器问题,我会执行命令、修改文件并同步进度。按 ${keyTextOr("app.tools.expand", "Ctrl+O")} 展开详情。`,
|
|
||||||
[150, 255, 210],
|
|
||||||
);
|
|
||||||
const onboarding = [
|
|
||||||
startupLine("SproutClaw", "树萌芽运维开发助手,负责代码修改、服务部署、服务器操作、排障和知识检索。", [255, 232, 140]),
|
|
||||||
rgb("直接输入任务即可开始。", [255, 190, 235]),
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
let expanded = false;
|
|
||||||
|
|
||||||
return {
|
|
||||||
render(width: number): string[] {
|
|
||||||
const divider = renderDivider(theme, width);
|
|
||||||
const lines: string[] = [
|
|
||||||
divider,
|
|
||||||
...renderSproutclawLogo(theme, width),
|
|
||||||
divider,
|
|
||||||
rgb("萌芽运维开发Agent助手", [170, 235, 255]),
|
|
||||||
];
|
|
||||||
if (expanded) {
|
|
||||||
lines.push(expandedInstructions);
|
|
||||||
lines.push(onboarding);
|
|
||||||
} else {
|
|
||||||
lines.push(compactOnboarding);
|
|
||||||
lines.push(onboarding);
|
|
||||||
}
|
|
||||||
return lines;
|
|
||||||
},
|
|
||||||
invalidate() {},
|
|
||||||
setExpanded(v: boolean) {
|
|
||||||
expanded = v;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
/**
|
|
||||||
* Status Line Extension
|
|
||||||
*
|
|
||||||
* Demonstrates ctx.ui.setStatus() for displaying persistent status text in the footer.
|
|
||||||
* Shows turn progress with themed colors.
|
|
||||||
*/
|
|
||||||
|
|
||||||
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
||||||
|
|
||||||
export default function (pi: ExtensionAPI) {
|
|
||||||
let turnCount = 0;
|
|
||||||
|
|
||||||
pi.on("session_start", async (_event, ctx) => {
|
|
||||||
const theme = ctx.ui.theme;
|
|
||||||
ctx.ui.setStatus("status-demo", theme.fg("dim", "Ready"));
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("turn_start", async (_event, ctx) => {
|
|
||||||
turnCount++;
|
|
||||||
const theme = ctx.ui.theme;
|
|
||||||
const spinner = theme.fg("accent", "●");
|
|
||||||
const text = theme.fg("dim", ` Turn ${turnCount}...`);
|
|
||||||
ctx.ui.setStatus("status-demo", spinner + text);
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("turn_end", async (_event, ctx) => {
|
|
||||||
const theme = ctx.ui.theme;
|
|
||||||
const check = theme.fg("success", "✓");
|
|
||||||
const text = theme.fg("dim", ` Turn ${turnCount} complete`);
|
|
||||||
ctx.ui.setStatus("status-demo", check + text);
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("session_switch", async (event, ctx) => {
|
|
||||||
if (event.reason === "new") {
|
|
||||||
turnCount = 0;
|
|
||||||
const theme = ctx.ui.theme;
|
|
||||||
ctx.ui.setStatus("status-demo", theme.fg("dim", "Ready"));
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import type { AssistantMessage } from "@earendil-works/pi-ai";
|
|
||||||
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
||||||
|
|
||||||
function isAssistantMessage(message: unknown): message is AssistantMessage {
|
|
||||||
if (!message || typeof message !== "object") return false;
|
|
||||||
const role = (message as { role?: unknown }).role;
|
|
||||||
return role === "assistant";
|
|
||||||
}
|
|
||||||
|
|
||||||
export default function (pi: ExtensionAPI) {
|
|
||||||
let agentStartMs: number | null = null;
|
|
||||||
|
|
||||||
pi.on("agent_start", () => {
|
|
||||||
agentStartMs = Date.now();
|
|
||||||
});
|
|
||||||
|
|
||||||
pi.on("agent_end", (event, ctx) => {
|
|
||||||
if (!ctx.hasUI) return;
|
|
||||||
if (agentStartMs === null) return;
|
|
||||||
|
|
||||||
const elapsedMs = Date.now() - agentStartMs;
|
|
||||||
agentStartMs = null;
|
|
||||||
if (elapsedMs <= 0) return;
|
|
||||||
|
|
||||||
let input = 0;
|
|
||||||
let output = 0;
|
|
||||||
let cacheRead = 0;
|
|
||||||
let cacheWrite = 0;
|
|
||||||
let totalTokens = 0;
|
|
||||||
|
|
||||||
for (const message of event.messages) {
|
|
||||||
if (!isAssistantMessage(message)) continue;
|
|
||||||
input += message.usage.input || 0;
|
|
||||||
output += message.usage.output || 0;
|
|
||||||
cacheRead += message.usage.cacheRead || 0;
|
|
||||||
cacheWrite += message.usage.cacheWrite || 0;
|
|
||||||
totalTokens += message.usage.totalTokens || 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (output <= 0) return;
|
|
||||||
|
|
||||||
const elapsedSeconds = elapsedMs / 1000;
|
|
||||||
const tokensPerSecond = output / elapsedSeconds;
|
|
||||||
const message = `TPS ${tokensPerSecond.toFixed(1)} tok/s. out ${output.toLocaleString()}, in ${input.toLocaleString()}, cache r/w ${cacheRead.toLocaleString()}/${cacheWrite.toLocaleString()}, total ${totalTokens.toLocaleString()}, ${elapsedSeconds.toFixed(1)}s`;
|
|
||||||
ctx.ui.notify(message, "info");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
---
|
|
||||||
description: Audit changelog entries before release
|
|
||||||
---
|
|
||||||
Audit changelog entries for all commits since the last release.
|
|
||||||
|
|
||||||
## Process
|
|
||||||
|
|
||||||
1. **Find the last release tag:**
|
|
||||||
```bash
|
|
||||||
git tag --sort=-version:refname | head -1
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **List all commits since that tag:**
|
|
||||||
```bash
|
|
||||||
git log <tag>..HEAD --oneline
|
|
||||||
```
|
|
||||||
|
|
||||||
3. **Read each package's [Unreleased] section:**
|
|
||||||
- packages/ai/CHANGELOG.md
|
|
||||||
- packages/tui/CHANGELOG.md
|
|
||||||
- packages/coding-agent/CHANGELOG.md
|
|
||||||
|
|
||||||
4. **For each commit, check:**
|
|
||||||
- Skip: changelog updates, doc-only changes, release housekeeping
|
|
||||||
- Skip: changes to generated model catalogs (for example `packages/ai/src/models.generated.ts`) unless accompanied by an intentional product-facing change in non-generated source/docs.
|
|
||||||
- Determine which package(s) the commit affects (use `git show <hash> --stat`)
|
|
||||||
- Verify a changelog entry exists in the affected package(s)
|
|
||||||
- For external contributions (PRs), verify format: `Description ([#N](url) by [@user](url))`
|
|
||||||
|
|
||||||
5. **Cross-package duplication rule:**
|
|
||||||
Changes in `ai`, `agent` or `tui` that affect end users should be duplicated to `coding-agent` changelog, since coding-agent is the user-facing package that depends on them.
|
|
||||||
|
|
||||||
6. **Add New Features section after changelog fixes:**
|
|
||||||
- Insert a `### New Features` section at the start of `## [Unreleased]` in `packages/coding-agent/CHANGELOG.md`.
|
|
||||||
- Propose the top new features to the user for confirmation before writing them.
|
|
||||||
- Link to relevant docs and sections whenever possible.
|
|
||||||
|
|
||||||
7. **Report:**
|
|
||||||
- List commits with missing entries
|
|
||||||
- List entries that need cross-package duplication
|
|
||||||
- Add any missing entries directly
|
|
||||||
|
|
||||||
## Changelog Format Reference
|
|
||||||
|
|
||||||
Sections (in order):
|
|
||||||
- `### Breaking Changes` - API changes requiring migration
|
|
||||||
- `### Added` - New features
|
|
||||||
- `### Changed` - Changes to existing functionality
|
|
||||||
- `### Fixed` - Bug fixes
|
|
||||||
- `### Removed` - Removed features
|
|
||||||
|
|
||||||
Attribution:
|
|
||||||
- Internal: `Fixed foo ([#123](https://github.com/earendil-works/pi-mono/issues/123))`
|
|
||||||
- External: `Added bar ([#456](https://github.com/earendil-works/pi-mono/pull/456) by [@user](https://github.com/user))`
|
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
---
|
|
||||||
description: Analyze GitHub issues (bugs or feature requests)
|
|
||||||
argument-hint: "<issue>"
|
|
||||||
---
|
|
||||||
Analyze GitHub issue(s): $ARGUMENTS
|
|
||||||
|
|
||||||
For each issue:
|
|
||||||
|
|
||||||
1. Add the `inprogress` label to the issue via GitHub CLI and assign the issue to the local `gh` user before analysis starts. If either action fails, report that explicitly and continue.
|
|
||||||
2. Read the issue in full, including all comments and linked issues/PRs.
|
|
||||||
3. Do not trust analysis written in the issue. Independently verify behavior and derive your own analysis from the code and execution path.
|
|
||||||
|
|
||||||
4. **For bugs**:
|
|
||||||
- Ignore any root cause analysis in the issue (likely wrong)
|
|
||||||
- Read all related code files in full (no truncation)
|
|
||||||
- Trace the code path and identify the actual root cause
|
|
||||||
- Propose a fix
|
|
||||||
|
|
||||||
5. **For feature requests**:
|
|
||||||
- Do not trust implementation proposals in the issue without verification
|
|
||||||
- Read all related code files in full (no truncation)
|
|
||||||
- Propose the most concise implementation approach
|
|
||||||
- List affected files and changes needed
|
|
||||||
|
|
||||||
Do NOT implement unless explicitly asked. Analyze and propose only.
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
---
|
|
||||||
description: Review PRs from URLs with structured issue and code analysis
|
|
||||||
argument-hint: "<PR-URL>"
|
|
||||||
---
|
|
||||||
You are given one or more GitHub PR URLs: $@
|
|
||||||
|
|
||||||
For each PR URL, do the following in order:
|
|
||||||
1. Add the `inprogress` label to the PR via GitHub CLI before analysis starts. If adding the label fails, report that explicitly and continue.
|
|
||||||
2. Read the PR page in full. Include description, all comments, all commits, and all changed files.
|
|
||||||
3. Identify any linked issues referenced in the PR body, comments, commit messages, or cross links. Read each issue in full, including all comments.
|
|
||||||
4. Analyze the PR diff. Read all relevant code files in full with no truncation and compare against the diff. Do not fetch PR file blobs unless a file is missing on main or the diff context is insufficient. Include related code paths that are not in the diff but are required to validate behavior.
|
|
||||||
5. Do not check for a changelog entry. Per CONTRIBUTING.md, contributor PRs must not edit `CHANGELOG.md` — the maintainer adds the entry when merging.
|
|
||||||
6. Check if packages/coding-agent/README.md, packages/coding-agent/docs/*.md, packages/coding-agent/examples/**/*.md require modification. This is usually the case when existing features have been changed, or new features have been added.
|
|
||||||
7. Provide a structured review with these sections:
|
|
||||||
- What it does: one short paragraph describing the change and its intent.
|
|
||||||
- Good: solid choices or improvements.
|
|
||||||
- Bad: concrete issues, regressions, missing tests, or risks.
|
|
||||||
- Ugly: subtle or high impact problems.
|
|
||||||
- Tests: what is covered, what is missing, and whether existing tests are adequate.
|
|
||||||
- Open questions for you: only things blocking a merge decision that need the user's input. Omit the section entirely if there are none.
|
|
||||||
|
|
||||||
Output format per PR:
|
|
||||||
PR: <url>
|
|
||||||
What it does:
|
|
||||||
- ...
|
|
||||||
Good:
|
|
||||||
- ...
|
|
||||||
Bad:
|
|
||||||
- ...
|
|
||||||
Ugly:
|
|
||||||
- ...
|
|
||||||
Tests:
|
|
||||||
- ...
|
|
||||||
Open questions for you:
|
|
||||||
- ...
|
|
||||||
|
|
||||||
If no issues are found, say so under Bad and Ugly.
|
|
||||||
@@ -1,35 +0,0 @@
|
|||||||
---
|
|
||||||
description: Finish the current task end-to-end with changelog, commit, and push
|
|
||||||
argument-hint: "[instructions]"
|
|
||||||
---
|
|
||||||
Wrap it.
|
|
||||||
|
|
||||||
Additional instructions: $ARGUMENTS
|
|
||||||
|
|
||||||
Determine context from the conversation history first.
|
|
||||||
|
|
||||||
Rules for context detection:
|
|
||||||
- If the conversation already mentions a GitHub issue or PR, use that existing context.
|
|
||||||
- If the work came from `/is` or `/pr`, assume the issue or PR context is already known from the conversation and from the analysis work already done.
|
|
||||||
- If there is no GitHub issue or PR in the conversation history, treat this as non-GitHub work.
|
|
||||||
|
|
||||||
Unless I explicitly override something in this request, do the following in order:
|
|
||||||
|
|
||||||
1. Add or update the relevant package changelog entry under `## [Unreleased]` using the repo changelog rules.
|
|
||||||
2. If this task is tied to a GitHub issue or PR and a final issue or PR comment has not already been posted in this session, draft it in my tone, preview it, and post exactly one final comment. The comment must end with this exact standalone disclaimer line, with no variations:
|
|
||||||
|
|
||||||
```text
|
|
||||||
This comment is AI-generated by `/wr`
|
|
||||||
```
|
|
||||||
3. Commit only files you changed in this session.
|
|
||||||
4. If this task is tied to exactly one GitHub issue, include `closes #<issue>` in the commit message. If it is tied to multiple issues, stop and ask which one to use. If it is not tied to any issue, do not include `closes #` or `fixes #` in the commit message.
|
|
||||||
5. Check the current git branch. If it is not `main`, stop and ask what to do. Do not push from another branch unless I explicitly say so.
|
|
||||||
6. Push the current branch.
|
|
||||||
|
|
||||||
Constraints:
|
|
||||||
- Never stage unrelated files.
|
|
||||||
- Never use `git add .` or `git add -A`.
|
|
||||||
- Run required checks before committing if code changed.
|
|
||||||
- Do not open a PR unless I explicitly ask.
|
|
||||||
- If this is not GitHub issue or PR work, do not post a GitHub comment.
|
|
||||||
- If a final issue or PR comment was already posted in this session, do not post another one unless I explicitly ask.
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
代码注释给我默认使用中文注释,不要使用英文注释,如果原本有英文注释,请给我改成中文注释
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
给我在项目根目录写一键启动本地开发前后端bat脚本和bash脚本,脚本名叫"dev",和构建前端脚本,脚本名叫:"build",注意保持代码精简不啰嗦,注意在Windows上显示前后端要两个窗口显示
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
给我这个项目起个简单通俗易懂的英文名
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"lastChangelogVersion": "0.75.4",
|
|
||||||
"showChangelogOnStartup": false,
|
|
||||||
"defaultProvider": "your-provider",
|
|
||||||
"defaultModel": "your-model",
|
|
||||||
"defaultThinkingLevel": "high",
|
|
||||||
"transport": "sse",
|
|
||||||
"theme": "dark",
|
|
||||||
"packages": [
|
|
||||||
"npm:pi-subagents",
|
|
||||||
"npm:pi-mcp-adapter",
|
|
||||||
"npm:pi-autocontext"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user