flower-trellis 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +113 -0
- package/bin/flower-trellis.js +4 -0
- package/enhancements/0.5/.agents/skills/trellis-analyze-task/SKILL.md +142 -0
- package/enhancements/0.5/.agents/skills/trellis-check-all/SKILL.md +324 -0
- package/enhancements/0.5/.agents/skills/trellis-create-command/SKILL.md +258 -0
- package/enhancements/0.5/.agents/skills/trellis-create-prd/SKILL.md +197 -0
- package/enhancements/0.5/.agents/skills/trellis-draw-uml/SKILL.md +148 -0
- package/enhancements/0.5/.agents/skills/trellis-migrate-skill/SKILL.md +216 -0
- package/enhancements/0.5/.agents/skills/trellis-plan-version/SKILL.md +140 -0
- package/enhancements/0.5/.agents/skills/trellis-push/SKILL.md +240 -0
- package/enhancements/0.5/.agents/skills/trellis-re-implement/SKILL.md +166 -0
- package/enhancements/0.5/.agents/skills/trellis-route/SKILL.md +159 -0
- package/enhancements/0.5/.agents/skills/trellis-run-full-chain/SKILL.md +402 -0
- package/enhancements/0.5/.agents/skills/trellis-sync-prd/SKILL.md +150 -0
- package/enhancements/0.5/.agents/skills/trellis-verify-prd/SKILL.md +217 -0
- package/enhancements/0.5/.claude/skills/trellis-analyze-task/SKILL.md +142 -0
- package/enhancements/0.5/.claude/skills/trellis-check-all/SKILL.md +324 -0
- package/enhancements/0.5/.claude/skills/trellis-create-command/SKILL.md +258 -0
- package/enhancements/0.5/.claude/skills/trellis-create-prd/SKILL.md +197 -0
- package/enhancements/0.5/.claude/skills/trellis-draw-uml/SKILL.md +148 -0
- package/enhancements/0.5/.claude/skills/trellis-migrate-skill/SKILL.md +216 -0
- package/enhancements/0.5/.claude/skills/trellis-plan-version/SKILL.md +140 -0
- package/enhancements/0.5/.claude/skills/trellis-push/SKILL.md +240 -0
- package/enhancements/0.5/.claude/skills/trellis-re-implement/SKILL.md +166 -0
- package/enhancements/0.5/.claude/skills/trellis-route/SKILL.md +159 -0
- package/enhancements/0.5/.claude/skills/trellis-run-full-chain/SKILL.md +402 -0
- package/enhancements/0.5/.claude/skills/trellis-sync-prd/SKILL.md +150 -0
- package/enhancements/0.5/.claude/skills/trellis-verify-prd/SKILL.md +217 -0
- package/enhancements/0.5/overrides/trellis-route.md +52 -0
- package/enhancements/0.6/.agents/skills/trellis-check-all/SKILL.md +342 -0
- package/enhancements/0.6/.agents/skills/trellis-create-command/SKILL.md +293 -0
- package/enhancements/0.6/.agents/skills/trellis-draw-uml/SKILL.md +148 -0
- package/enhancements/0.6/.agents/skills/trellis-extract-prd/SKILL.md +197 -0
- package/enhancements/0.6/.agents/skills/trellis-plan-version/SKILL.md +140 -0
- package/enhancements/0.6/.agents/skills/trellis-push/SKILL.md +316 -0
- package/enhancements/0.6/.agents/skills/trellis-route/SKILL.md +159 -0
- package/enhancements/0.6/.agents/skills/trellis-run-full-chain/SKILL.md +402 -0
- package/enhancements/0.6/.agents/skills/trellis-verify-task/SKILL.md +360 -0
- package/enhancements/0.6/.claude/skills/trellis-check-all/SKILL.md +342 -0
- package/enhancements/0.6/.claude/skills/trellis-create-command/SKILL.md +293 -0
- package/enhancements/0.6/.claude/skills/trellis-draw-uml/SKILL.md +148 -0
- package/enhancements/0.6/.claude/skills/trellis-extract-prd/SKILL.md +197 -0
- package/enhancements/0.6/.claude/skills/trellis-plan-version/SKILL.md +140 -0
- package/enhancements/0.6/.claude/skills/trellis-push/SKILL.md +316 -0
- package/enhancements/0.6/.claude/skills/trellis-route/SKILL.md +159 -0
- package/enhancements/0.6/.claude/skills/trellis-run-full-chain/SKILL.md +402 -0
- package/enhancements/0.6/.claude/skills/trellis-verify-task/SKILL.md +360 -0
- package/enhancements/0.6/overrides/workflow-states/in_progress-inline.md +5 -0
- package/enhancements/0.6/overrides/workflow-states/in_progress.md +7 -0
- package/enhancements/0.6/overrides/workflow-states/no_task.md +6 -0
- package/enhancements/0.6/overrides/workflow-states/planning.md +6 -0
- package/enhancements/0.6/overrides/workflow.md +53 -0
- package/enhancements/MANIFEST.json +109 -0
- package/enhancements/old/.agents/skills/analyze-task/SKILL.md +143 -0
- package/enhancements/old/.agents/skills/check-all/SKILL.md +128 -0
- package/enhancements/old/.agents/skills/check-impl/SKILL.md +159 -0
- package/enhancements/old/.agents/skills/check-prd/SKILL.md +219 -0
- package/enhancements/old/.agents/skills/check-prd-impl/SKILL.md +190 -0
- package/enhancements/old/.agents/skills/create-prd/SKILL.md +154 -0
- package/enhancements/old/.agents/skills/draw-uml/SKILL.md +148 -0
- package/enhancements/old/.agents/skills/plan-version/SKILL.md +140 -0
- package/enhancements/old/.agents/skills/push/SKILL.md +191 -0
- package/enhancements/old/.agents/skills/re-implement/SKILL.md +166 -0
- package/enhancements/old/.agents/skills/sync-prd/SKILL.md +146 -0
- package/enhancements/old/.claude/commands/trellis/analyze-task.md +139 -0
- package/enhancements/old/.claude/commands/trellis/check-all.md +124 -0
- package/enhancements/old/.claude/commands/trellis/check-impl.md +154 -0
- package/enhancements/old/.claude/commands/trellis/check-prd-impl.md +186 -0
- package/enhancements/old/.claude/commands/trellis/check-prd.md +215 -0
- package/enhancements/old/.claude/commands/trellis/create-prd.md +150 -0
- package/enhancements/old/.claude/commands/trellis/draw-uml.md +144 -0
- package/enhancements/old/.claude/commands/trellis/plan-version.md +136 -0
- package/enhancements/old/.claude/commands/trellis/push.md +187 -0
- package/enhancements/old/.claude/commands/trellis/re-implement.md +162 -0
- package/enhancements/old/.claude/commands/trellis/sync-prd.md +142 -0
- package/package.json +39 -0
- package/src/cli.js +151 -0
- package/src/commands/init.js +66 -0
- package/src/commands/uninstall.js +85 -0
- package/src/commands/update.js +42 -0
- package/src/constants.js +50 -0
- package/src/lib/apply-enhancements.js +133 -0
- package/src/lib/banner.js +45 -0
- package/src/lib/codex-tweaks.js +112 -0
- package/src/lib/copy-skills.js +91 -0
- package/src/lib/fs-utils.js +60 -0
- package/src/lib/legacy-blocks.js +70 -0
- package/src/lib/manifest.js +32 -0
- package/src/lib/paths.js +16 -0
- package/src/lib/pick-platforms.js +57 -0
- package/src/lib/trellis-runner.js +190 -0
- package/src/lib/variant.js +40 -0
- package/src/lib/versions.js +30 -0
- package/src/lib/workflow-inject.js +193 -0
package/src/constants.js
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 全局常量集中地。
|
|
3
|
+
*
|
|
4
|
+
* 这里的名单从 skill-garden 的 install.sh 与 Trellis cli/index.ts 对齐而来,
|
|
5
|
+
* 改动时需与上游保持一致。
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** 强化包支持的三个版本变体目录名。 */
|
|
9
|
+
export const VARIANTS = ["old", "0.5", "0.6"];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Trellis init 支持的全部平台 flag。
|
|
13
|
+
*
|
|
14
|
+
* 用途:当用户未显式指定任何平台时,flower-trellis 默认补 `--claude`
|
|
15
|
+
* (对应「默认 Claude + agents」决策)。来源为 Trellis cli/index.ts 的 init 注册。
|
|
16
|
+
* 上游新增平台时此名单可滞后 —— 最坏后果仅是误补 `--claude`(与用户新平台共存),
|
|
17
|
+
* 不会致命。
|
|
18
|
+
*/
|
|
19
|
+
export const PLATFORM_FLAGS = [
|
|
20
|
+
"--cursor",
|
|
21
|
+
"--claude",
|
|
22
|
+
"--opencode",
|
|
23
|
+
"--codex",
|
|
24
|
+
"--kilo",
|
|
25
|
+
"--kiro",
|
|
26
|
+
"--gemini",
|
|
27
|
+
"--antigravity",
|
|
28
|
+
"--windsurf",
|
|
29
|
+
"--qoder",
|
|
30
|
+
"--codebuddy",
|
|
31
|
+
"--copilot",
|
|
32
|
+
"--droid",
|
|
33
|
+
"--pi",
|
|
34
|
+
"--reasonix",
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* flower-trellis 自有 flag —— 这些不能透传给 trellis,需在解析时剔除。
|
|
39
|
+
*
|
|
40
|
+
* 值含义:
|
|
41
|
+
* false → 布尔 flag(出现即生效,不带取值)
|
|
42
|
+
* true → 带取值 flag(其后紧跟一个取值 token,剔除时要连带跳过)
|
|
43
|
+
*/
|
|
44
|
+
export const OWN_FLAGS = {
|
|
45
|
+
"--no-enhance": false,
|
|
46
|
+
"--enhance-only": false,
|
|
47
|
+
"--skills": true,
|
|
48
|
+
"--variant": true,
|
|
49
|
+
"--target": true,
|
|
50
|
+
};
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { selectVariant } from "./variant.js";
|
|
4
|
+
import { ENHANCEMENTS_ROOT } from "./paths.js";
|
|
5
|
+
import { VARIANTS } from "../constants.js";
|
|
6
|
+
import { copySkills } from "./copy-skills.js";
|
|
7
|
+
import { injectWorkflow } from "./workflow-inject.js";
|
|
8
|
+
import { applyCodexTweaks } from "./codex-tweaks.js";
|
|
9
|
+
import { readManifest, writeManifest } from "./manifest.js";
|
|
10
|
+
import { rmrf } from "./fs-utils.js";
|
|
11
|
+
|
|
12
|
+
/** 清理升级后可能变空的强化目录(深 → 浅)。 */
|
|
13
|
+
function pruneEmptyDirs(target) {
|
|
14
|
+
for (const d of [
|
|
15
|
+
".agents/skills",
|
|
16
|
+
".agents",
|
|
17
|
+
".claude/commands/trellis",
|
|
18
|
+
".claude/commands",
|
|
19
|
+
]) {
|
|
20
|
+
const abs = path.join(target, ...d.split("/"));
|
|
21
|
+
try {
|
|
22
|
+
if (fs.readdirSync(abs).length === 0) fs.rmdirSync(abs);
|
|
23
|
+
} catch {
|
|
24
|
+
// 不存在或非空,忽略
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 叠加强化包 —— init / update 共享。
|
|
31
|
+
*
|
|
32
|
+
* 流程:校验是 Trellis 项目 → 选变体 → 铺 skill → 升级清理(删过期)→ 注入 workflow。
|
|
33
|
+
*
|
|
34
|
+
* 升级清理:用 flower manifest 记录上次全装铺过的精确路径,本次全装时删除
|
|
35
|
+
* 「上次有、这次变体不含」的过期项(覆盖 0.5/old → 0.6 升级)。仅全装(无 --skills)
|
|
36
|
+
* 时维护 manifest 与清理;带 --skills 为精细操作,不动 manifest、不清理。
|
|
37
|
+
*
|
|
38
|
+
* @param {string} target 目标项目根
|
|
39
|
+
* @param {object} [opts] { variant?, skills?[] }
|
|
40
|
+
* @returns {{variant: string, installed: string[]}}
|
|
41
|
+
*/
|
|
42
|
+
export function applyEnhancements(target, opts = {}) {
|
|
43
|
+
const trellisDir = path.join(target, ".trellis");
|
|
44
|
+
if (!fs.existsSync(trellisDir)) {
|
|
45
|
+
throw new Error(`目标不是 Trellis 项目(缺 .trellis/):${target}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 选变体:--variant 优先,否则读 .trellis/.version
|
|
49
|
+
let variant = opts.variant;
|
|
50
|
+
let version = "";
|
|
51
|
+
if (variant) {
|
|
52
|
+
if (!VARIANTS.includes(variant)) {
|
|
53
|
+
throw new Error(`非法 --variant:${variant}(可选 ${VARIANTS.join(" / ")})`);
|
|
54
|
+
}
|
|
55
|
+
} else {
|
|
56
|
+
({ variant, version } = selectVariant(target));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const variantDir = path.join(ENHANCEMENTS_ROOT, variant);
|
|
60
|
+
if (!fs.existsSync(variantDir)) {
|
|
61
|
+
throw new Error(`强化包快照缺变体 ${variant}/(请先在 flower-trellis 包内运行 npm run sync)`);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
console.log(
|
|
65
|
+
`\n强化包变体:${variant}${version ? `(项目 Trellis ${version})` : ""}`,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
const skills = opts.skills || [];
|
|
69
|
+
const { installed, paths: newPaths } = copySkills(
|
|
70
|
+
target,
|
|
71
|
+
variantDir,
|
|
72
|
+
variant,
|
|
73
|
+
skills,
|
|
74
|
+
);
|
|
75
|
+
const where = [];
|
|
76
|
+
if (newPaths.some((p) => p.startsWith(".claude/skills"))) where.push(".claude/skills");
|
|
77
|
+
if (newPaths.some((p) => p.startsWith(".agents/skills"))) where.push(".agents/skills");
|
|
78
|
+
if (newPaths.some((p) => p.startsWith(".claude/commands"))) where.push(".claude/commands/trellis");
|
|
79
|
+
console.log(
|
|
80
|
+
` ✓ 铺设 ${installed.length} 个强化技能 → ${where.join(" + ") || "(无平台目录)"}`,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// 升级清理 + manifest(仅全装时维护)
|
|
84
|
+
if (skills.length === 0) {
|
|
85
|
+
const old = readManifest(target);
|
|
86
|
+
if (old && Array.isArray(old.paths)) {
|
|
87
|
+
const keep = new Set(newPaths);
|
|
88
|
+
const stale = old.paths.filter((p) => !keep.has(p));
|
|
89
|
+
let removed = 0;
|
|
90
|
+
for (const rel of stale) {
|
|
91
|
+
const abs = path.join(target, ...rel.split("/"));
|
|
92
|
+
if (fs.existsSync(abs)) {
|
|
93
|
+
rmrf(abs);
|
|
94
|
+
removed++;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (removed > 0) {
|
|
98
|
+
console.log(
|
|
99
|
+
` ✓ 清理 ${removed} 个过期强化项(${old.variant || "?"} → ${variant})`,
|
|
100
|
+
);
|
|
101
|
+
pruneEmptyDirs(target);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
writeManifest(target, { variant, version, skills: installed, paths: newPaths });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// workflow 注入:无过滤名(全装)或显式指定 workflow-enhancement/finish-work-enhancement 时执行
|
|
108
|
+
const wantWorkflow =
|
|
109
|
+
skills.length === 0 ||
|
|
110
|
+
skills.includes("workflow-enhancement") ||
|
|
111
|
+
skills.includes("finish-work-enhancement");
|
|
112
|
+
if (wantWorkflow) {
|
|
113
|
+
const r = injectWorkflow(target, variantDir, variant);
|
|
114
|
+
if (r.skipped) {
|
|
115
|
+
console.log(` · workflow 注入跳过(${r.reason})`);
|
|
116
|
+
} else if (r.changed === false) {
|
|
117
|
+
console.log(` ✓ workflow.md 强化块已是最新${r.backupNote}`);
|
|
118
|
+
} else {
|
|
119
|
+
console.log(` ✓ workflow.md 已注入强化块(${r.action})${r.backupNote}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// codex 平台后处理:注释 config.toml 的 multi_agent_v2 + 挂上 SessionStart hook(仅当 .codex/ 存在)
|
|
124
|
+
const codex = applyCodexTweaks(target);
|
|
125
|
+
if (codex.applied) {
|
|
126
|
+
const seg = codex.tomlChanged
|
|
127
|
+
? "config.toml 已注释 multi_agent_v2"
|
|
128
|
+
: "config.toml(multi_agent_v2 已是注释态)";
|
|
129
|
+
console.log(` ✓ codex 调整:${seg};hooks.json = SessionStart + UserPromptSubmit`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return { variant, installed };
|
|
133
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import figlet from "figlet";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 打印 flower-trellis 品牌头部 —— ASCII logo + 副标题 + 开发者身份,
|
|
7
|
+
* 风格对齐 Trellis 页面。init 交互模式与 update 都会显示。
|
|
8
|
+
*
|
|
9
|
+
* @param {string|null} developer 开发者名(来自 -u/--user 或 git config)
|
|
10
|
+
*/
|
|
11
|
+
export function printBanner(developer) {
|
|
12
|
+
let art;
|
|
13
|
+
try {
|
|
14
|
+
art = figlet.textSync("Flower", { font: "ANSI Shadow" });
|
|
15
|
+
} catch {
|
|
16
|
+
art = "flower-trellis";
|
|
17
|
+
}
|
|
18
|
+
console.log("\n" + chalk.hex("#ff6fb5")(art));
|
|
19
|
+
console.log(
|
|
20
|
+
chalk.gray(" 一键装 Trellis 工程框架,并融合 skill-garden 强化包\n"),
|
|
21
|
+
);
|
|
22
|
+
if (developer) {
|
|
23
|
+
console.log(
|
|
24
|
+
`👤 ${chalk.magentaBright("Developer")}: ${chalk.bold(developer)}\n`,
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 解析开发者名:优先 -u/--user 的取值,否则回退到目标仓库的 git config user.name。 */
|
|
30
|
+
export function getDeveloper(passthrough, target) {
|
|
31
|
+
const i = passthrough.findIndex((a) => a === "-u" || a === "--user");
|
|
32
|
+
if (i >= 0) {
|
|
33
|
+
const v = passthrough[i + 1];
|
|
34
|
+
if (v && !v.startsWith("-")) return v;
|
|
35
|
+
}
|
|
36
|
+
try {
|
|
37
|
+
return (
|
|
38
|
+
execFileSync("git", ["-C", target, "config", "user.name"], {
|
|
39
|
+
encoding: "utf8",
|
|
40
|
+
}).trim() || null
|
|
41
|
+
);
|
|
42
|
+
} catch {
|
|
43
|
+
return null;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* flower-trellis 对 Trellis 生成的 codex 配置的定制后处理。
|
|
6
|
+
*
|
|
7
|
+
* 仅当目标项目已配置 codex 平台(存在 .codex/)时生效;在 init / update 叠加阶段调用,幂等。
|
|
8
|
+
* 做两件事:
|
|
9
|
+
* 1. 注释掉 .codex/config.toml 的 [features.multi_agent_v2] 段;
|
|
10
|
+
* 2. 用指定内容覆盖 .codex/hooks.json —— 在 Trellis 默认(仅 UserPromptSubmit)基础上挂 SessionStart。
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// 期望的 .codex/hooks.json。两个脚本均由 Trellis codex configurator 写入 .codex/hooks/。
|
|
14
|
+
const CODEX_HOOKS = {
|
|
15
|
+
hooks: {
|
|
16
|
+
SessionStart: [
|
|
17
|
+
{
|
|
18
|
+
hooks: [
|
|
19
|
+
{
|
|
20
|
+
type: "command",
|
|
21
|
+
command: "python3 .codex/hooks/session-start.py",
|
|
22
|
+
timeout: 5,
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
],
|
|
27
|
+
UserPromptSubmit: [
|
|
28
|
+
{
|
|
29
|
+
hooks: [
|
|
30
|
+
{
|
|
31
|
+
type: "command",
|
|
32
|
+
command: "python3 .codex/hooks/inject-workflow-state.py",
|
|
33
|
+
timeout: 5,
|
|
34
|
+
},
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
},
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 注释掉 config.toml 里的 [features.multi_agent_v2] 段(段头 + 段内键,直到下一个 section)。
|
|
43
|
+
* 幂等:段头已被注释则不再处理。
|
|
44
|
+
* @returns {boolean} 是否发生改动
|
|
45
|
+
*/
|
|
46
|
+
function commentMultiAgentV2(tomlPath) {
|
|
47
|
+
if (!fs.existsSync(tomlPath)) return false;
|
|
48
|
+
const lines = fs.readFileSync(tomlPath, "utf8").split("\n");
|
|
49
|
+
const out = [];
|
|
50
|
+
let inSection = false;
|
|
51
|
+
let changed = false;
|
|
52
|
+
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
const t = line.trim();
|
|
55
|
+
if (inSection) {
|
|
56
|
+
// 段结束:遇到下一个未注释的 section 头
|
|
57
|
+
if (t.startsWith("[") && !t.startsWith("#")) {
|
|
58
|
+
inSection = false;
|
|
59
|
+
out.push(line);
|
|
60
|
+
continue;
|
|
61
|
+
}
|
|
62
|
+
// 段内非空、未注释行 → 注释
|
|
63
|
+
if (t && !t.startsWith("#")) {
|
|
64
|
+
out.push("# " + line);
|
|
65
|
+
changed = true;
|
|
66
|
+
} else {
|
|
67
|
+
out.push(line);
|
|
68
|
+
}
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
if (t === "[features.multi_agent_v2]") {
|
|
72
|
+
out.push("# " + line);
|
|
73
|
+
inSection = true;
|
|
74
|
+
changed = true;
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
out.push(line);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
if (changed) fs.writeFileSync(tomlPath, out.join("\n"));
|
|
81
|
+
return changed;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 用 flower 指定内容覆盖 .codex/hooks.json。幂等:内容一致则不写。
|
|
86
|
+
* @returns {boolean} 是否写入
|
|
87
|
+
*/
|
|
88
|
+
function rewriteHooks(hooksPath) {
|
|
89
|
+
const desired = JSON.stringify(CODEX_HOOKS, null, 2) + "\n";
|
|
90
|
+
let current = null;
|
|
91
|
+
try {
|
|
92
|
+
current = fs.readFileSync(hooksPath, "utf8");
|
|
93
|
+
} catch {
|
|
94
|
+
// 文件不存在,视为需要写
|
|
95
|
+
}
|
|
96
|
+
if (current === desired) return false;
|
|
97
|
+
fs.writeFileSync(hooksPath, desired);
|
|
98
|
+
return true;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* codex 平台后处理入口。仅当 .codex/ 存在时执行。
|
|
103
|
+
* @param {string} target 目标项目根
|
|
104
|
+
* @returns {{applied: boolean, tomlChanged?: boolean, hooksWritten?: boolean}}
|
|
105
|
+
*/
|
|
106
|
+
export function applyCodexTweaks(target) {
|
|
107
|
+
const codexDir = path.join(target, ".codex");
|
|
108
|
+
if (!fs.existsSync(codexDir)) return { applied: false };
|
|
109
|
+
const tomlChanged = commentMultiAgentV2(path.join(codexDir, "config.toml"));
|
|
110
|
+
const hooksWritten = rewriteHooks(path.join(codexDir, "hooks.json"));
|
|
111
|
+
return { applied: true, tomlChanged, hooksWritten };
|
|
112
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { copyPath, listDirs, listFiles, ensureDir } from "./fs-utils.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 技能名过滤(移植 install.sh 的 should_install)。
|
|
7
|
+
* 空清单 → 全装;否则精确匹配,或「去掉 trellis- 前缀后」匹配
|
|
8
|
+
* (例:传 analyze-task 也命中 trellis-analyze-task)。
|
|
9
|
+
*/
|
|
10
|
+
function shouldInstall(name, skills) {
|
|
11
|
+
if (!skills || skills.length === 0) return true;
|
|
12
|
+
const stripped = name.replace(/^trellis-/, "");
|
|
13
|
+
return skills.some((f) => f === name || f === stripped);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 把指定变体的强化 skill / command 铺到目标项目,**跟随平台**。
|
|
18
|
+
*
|
|
19
|
+
* 平台 → 目录映射(对齐 Trellis 各 configurator 实际写入位置):
|
|
20
|
+
* - claude 平台 → .claude/skills(强化包 .claude/skills 源;old 用 .agents 镜像),
|
|
21
|
+
* command 仅 claude → .claude/commands/trellis
|
|
22
|
+
* - codex / gemini 等 → 共享层 .agents/skills(强化包 .agents/skills 源)
|
|
23
|
+
* (见 Trellis configureCodex:codex 的共享 skill 写在 .agents/skills,非 .codex/skills)
|
|
24
|
+
*
|
|
25
|
+
* 「跟随平台」靠检测目标项目已存在的 .claude/ 与 .agents/ 目录实现 —— 这两个目录由
|
|
26
|
+
* Trellis init 按所选平台创建(claude 建 .claude;codex/gemini 建 .agents),
|
|
27
|
+
* 因此 init / update / enhance-only 统一用此检测即可自动对齐用户实际选的平台。
|
|
28
|
+
*
|
|
29
|
+
* @returns {{ installed: string[], paths: string[] }}
|
|
30
|
+
*/
|
|
31
|
+
export function copySkills(target, variantDir, variant, skills) {
|
|
32
|
+
const installed = new Set();
|
|
33
|
+
const paths = [];
|
|
34
|
+
|
|
35
|
+
const agentsSrc = path.join(variantDir, ".agents", "skills");
|
|
36
|
+
const claudeSrc = path.join(variantDir, ".claude", "skills");
|
|
37
|
+
const cmdSrc = path.join(variantDir, ".claude", "commands", "trellis");
|
|
38
|
+
|
|
39
|
+
// 跟随平台:目标已有 .claude → claude 平台;已有 .agents → codex/gemini 等共享层
|
|
40
|
+
let needClaude = fs.existsSync(path.join(target, ".claude"));
|
|
41
|
+
let needAgents = fs.existsSync(path.join(target, ".agents"));
|
|
42
|
+
if (!needClaude && !needAgents) needClaude = true; // 兜底:至少铺 claude
|
|
43
|
+
|
|
44
|
+
// codex / gemini 等 → .agents/skills
|
|
45
|
+
if (needAgents) {
|
|
46
|
+
ensureDir(path.join(target, ".agents", "skills"));
|
|
47
|
+
for (const name of listDirs(agentsSrc)) {
|
|
48
|
+
if (!shouldInstall(name, skills)) continue;
|
|
49
|
+
copyPath(
|
|
50
|
+
path.join(agentsSrc, name),
|
|
51
|
+
path.join(target, ".agents", "skills", name),
|
|
52
|
+
);
|
|
53
|
+
installed.add(name);
|
|
54
|
+
paths.push(`.agents/skills/${name}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// claude → .claude/skills(0.5/0.6 源自带;old 源为空,用 .agents 镜像)
|
|
59
|
+
if (needClaude) {
|
|
60
|
+
ensureDir(path.join(target, ".claude", "skills"));
|
|
61
|
+
const claudeNames = listDirs(claudeSrc);
|
|
62
|
+
const claudeSource = claudeNames.length > 0 ? claudeSrc : agentsSrc;
|
|
63
|
+
for (const name of listDirs(claudeSource)) {
|
|
64
|
+
if (!shouldInstall(name, skills)) continue;
|
|
65
|
+
copyPath(
|
|
66
|
+
path.join(claudeSource, name),
|
|
67
|
+
path.join(target, ".claude", "skills", name),
|
|
68
|
+
);
|
|
69
|
+
installed.add(name);
|
|
70
|
+
paths.push(`.claude/skills/${name}`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// command 仅 claude(old 变体)→ .claude/commands/trellis
|
|
74
|
+
const cmds = listFiles(cmdSrc, ".md");
|
|
75
|
+
if (cmds.length > 0) {
|
|
76
|
+
ensureDir(path.join(target, ".claude", "commands", "trellis"));
|
|
77
|
+
for (const file of cmds) {
|
|
78
|
+
const name = file.replace(/\.md$/, "");
|
|
79
|
+
if (!shouldInstall(name, skills)) continue;
|
|
80
|
+
copyPath(
|
|
81
|
+
path.join(cmdSrc, file),
|
|
82
|
+
path.join(target, ".claude", "commands", "trellis", file),
|
|
83
|
+
);
|
|
84
|
+
installed.add(name);
|
|
85
|
+
paths.push(`.claude/commands/trellis/${file}`);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return { installed: [...installed], paths };
|
|
91
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/** 确保目录存在(等价 `mkdir -p`)。 */
|
|
5
|
+
export function ensureDir(dir) {
|
|
6
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/** 递归删除(等价 `rm -rf`,目标不存在不报错)。 */
|
|
10
|
+
export function rmrf(target) {
|
|
11
|
+
fs.rmSync(target, { recursive: true, force: true });
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 复制文件或目录到目标,已存在则无条件覆盖。
|
|
16
|
+
*
|
|
17
|
+
* 移植 skill-garden install.sh 的 install_one 语义:
|
|
18
|
+
* - 目标是软链时先删链,避免写穿到链接指向的真实文件;
|
|
19
|
+
* - 目标已存在时先整体 rm -rf 再复制,保证不残留被上游删掉的旧文件。
|
|
20
|
+
*
|
|
21
|
+
* @param {string} src 源文件/目录
|
|
22
|
+
* @param {string} dst 目标路径
|
|
23
|
+
*/
|
|
24
|
+
export function copyPath(src, dst) {
|
|
25
|
+
ensureDir(path.dirname(dst));
|
|
26
|
+
// lstat 不跟随软链:软链需先删,否则 cpSync 可能写穿
|
|
27
|
+
try {
|
|
28
|
+
if (fs.lstatSync(dst).isSymbolicLink()) {
|
|
29
|
+
fs.unlinkSync(dst);
|
|
30
|
+
}
|
|
31
|
+
} catch {
|
|
32
|
+
// 目标不存在,无需处理
|
|
33
|
+
}
|
|
34
|
+
rmrf(dst);
|
|
35
|
+
fs.cpSync(src, dst, { recursive: true });
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/** 列出目录下的直接子目录名;目录不存在返回空数组。 */
|
|
39
|
+
export function listDirs(dir) {
|
|
40
|
+
try {
|
|
41
|
+
return fs
|
|
42
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
43
|
+
.filter((e) => e.isDirectory())
|
|
44
|
+
.map((e) => e.name);
|
|
45
|
+
} catch {
|
|
46
|
+
return [];
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/** 列出目录下匹配后缀的文件名;目录不存在返回空数组。 */
|
|
51
|
+
export function listFiles(dir, ext) {
|
|
52
|
+
try {
|
|
53
|
+
return fs
|
|
54
|
+
.readdirSync(dir, { withFileTypes: true })
|
|
55
|
+
.filter((e) => e.isFile() && (!ext || e.name.endsWith(ext)))
|
|
56
|
+
.map((e) => e.name);
|
|
57
|
+
} catch {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 0.5 / old 变体的 workflow-state 注入文本常量。
|
|
3
|
+
*
|
|
4
|
+
* 这些块在 skill-garden 的 install.sh 里是内嵌 Python 的 `LEGACY_*` 字面量
|
|
5
|
+
* (0.6 变体的 state 文本改为从 overrides/workflow-states/*.md 读取,不走这里)。
|
|
6
|
+
* 此处逐字符移植,**包括每个块结尾的两个换行**(state 替换时直接拼接,依赖此尾部空行)。
|
|
7
|
+
* 改动需与上游 install.sh 保持一致。
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
export const LEGACY_NO_TASK_BLOCK = `<!-- BEGIN skill-garden workflow-state no-task-gate v0.5 -->
|
|
11
|
+
HIGHEST PRIORITY NO-TASK GUARD (skill-garden):
|
|
12
|
+
Creating/resuming a task ≠ permission to implement inline.
|
|
13
|
+
After PRD ready and task started, next impl action = \`trellis-route(implement)\`.
|
|
14
|
+
Don't infer opt-out from "small/urgent/unclear" — opt-out requires an explicit phrase in the current message (see C below).
|
|
15
|
+
<!-- END skill-garden workflow-state no-task-gate v0.5 -->
|
|
16
|
+
|
|
17
|
+
`;
|
|
18
|
+
|
|
19
|
+
export const LEGACY_PLANNING_BLOCK = `<!-- BEGIN skill-garden workflow-state planning-handoff v0.5 -->
|
|
20
|
+
HIGHEST PRIORITY PLANNING GUARD (skill-garden):
|
|
21
|
+
Planning is not implementation permission.
|
|
22
|
+
Complete prd.md + context first.
|
|
23
|
+
After in_progress, next action = \`trellis-route(implement)\`, not direct edits.
|
|
24
|
+
<!-- END skill-garden workflow-state planning-handoff v0.5 -->
|
|
25
|
+
|
|
26
|
+
`;
|
|
27
|
+
|
|
28
|
+
export const LEGACY_PUSH_PROGRESS_BLOCK = `<!-- BEGIN skill-garden workflow-state push-progress-recovery v0.6 -->
|
|
29
|
+
PUSH PROGRESS RECOVERY (skill-garden):
|
|
30
|
+
If you haven't already relayed recovery in this session, scan
|
|
31
|
+
\`.trellis/tasks/*/task.json\` for entries where status="in_progress" AND a
|
|
32
|
+
\`last_push_snapshot\` field is present (schema: snapshot_at / branch /
|
|
33
|
+
pushed_commits / completed_steps / partial_step / next_step / notes).
|
|
34
|
+
For each match, surface to the user:
|
|
35
|
+
「发现未完成任务 <title>:上次 push 完成到 <completed_steps>,下一步 <next_step>。要继续吗?」
|
|
36
|
+
If multiple match, list them with \`snapshot_at\` so the user can pick.
|
|
37
|
+
Then suggest \`python3 ./.trellis/scripts/task.py start <task_path>\` to
|
|
38
|
+
re-bind the active-task pointer before resuming work.
|
|
39
|
+
Skip this hint if (a) you've already relayed recovery this session, or
|
|
40
|
+
(b) no in_progress task carries \`last_push_snapshot\`.
|
|
41
|
+
<!-- END skill-garden workflow-state push-progress-recovery v0.6 -->
|
|
42
|
+
|
|
43
|
+
`;
|
|
44
|
+
|
|
45
|
+
export const LEGACY_IN_PROGRESS_BLOCK = `<!-- BEGIN skill-garden workflow-state trellis-route v0.5 -->
|
|
46
|
+
HIGHEST PRIORITY ROUTE GUARD (skill-garden):
|
|
47
|
+
This guard is intentionally appended after upstream in_progress breadcrumbs and overrides earlier direct-dispatch defaults in this same <workflow-state>.
|
|
48
|
+
At Phase 2.1/2.2/3.1, invoke \`trellis-route(implement|check)\` first, including every check / check-all path.
|
|
49
|
+
Codex \`dispatch_mode: sub-agent\` only makes subagent a selectable route outcome; it is not permission to bypass \`trellis-route\`.
|
|
50
|
+
Do NOT spawn \`trellis-implement\` / \`trellis-check\` / \`trellis-check-all\` directly from the main session unless \`trellis-route\` just selected a subagent mode.
|
|
51
|
+
If \`trellis-route\` selected inline mode, load \`trellis-before-dev\` / \`trellis-check\` / \`trellis-check-all\` as applicable and execute in the main session.
|
|
52
|
+
If \`trellis-route\` or its interactive helper is unavailable, present the same numbered route choices in normal chat and wait for the user's selection; do not record an inline/subagent choice yourself.
|
|
53
|
+
CHECK RULE: check never uses \`.trellis/.route-prefs.tmp\`; ask every time before \`trellis-check\`, \`trellis-check-all\`, or their subagents.
|
|
54
|
+
ANTI-DEFER: at phase boundaries, never ask meta questions ("X or Y?", "continue?", "what's next?") — invoke \`trellis-route(check)\` instead, or ask the numbered route choices if the helper is unavailable.
|
|
55
|
+
<!-- END skill-garden workflow-state trellis-route v0.5 -->
|
|
56
|
+
|
|
57
|
+
`;
|
|
58
|
+
|
|
59
|
+
export const LEGACY_PUSH_SNAPSHOT_BLOCK = `<!-- BEGIN skill-garden workflow-state in-progress-push-snapshot v0.6 -->
|
|
60
|
+
IN-PROGRESS PUSH SNAPSHOT (skill-garden):
|
|
61
|
+
The active task's task.json may carry a \`last_push_snapshot\` field (schema:
|
|
62
|
+
snapshot_at / branch / pushed_commits / completed_steps / partial_step /
|
|
63
|
+
next_step / notes). Before starting new work this turn, read that field; if
|
|
64
|
+
present, briefly relay \`partial_step\` + \`next_step\` so the user knows you
|
|
65
|
+
recognize the paused state instead of restarting from scratch. Skip if you
|
|
66
|
+
have already relayed this snapshot earlier in the session, or if the field
|
|
67
|
+
is absent.
|
|
68
|
+
<!-- END skill-garden workflow-state in-progress-push-snapshot v0.6 -->
|
|
69
|
+
|
|
70
|
+
`;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* flower-trellis 自己的安装清单。
|
|
6
|
+
*
|
|
7
|
+
* 记录「flower 上一次为该项目铺设了哪些强化文件」,使升级(如 0.5/old → 0.6)时
|
|
8
|
+
* 能精确删除当前变体不再包含的过期 skill / command —— 只删自己铺过的路径,
|
|
9
|
+
* 绝不误删用户或 Trellis 本体的文件。
|
|
10
|
+
*
|
|
11
|
+
* 放在 .trellis/ 下,随项目的 Trellis 生命周期存在(uninstall 删 .trellis 时一并消失)。
|
|
12
|
+
*/
|
|
13
|
+
const MANIFEST_REL = path.join(".trellis", ".flower-manifest.json");
|
|
14
|
+
|
|
15
|
+
/** manifest 文件的绝对路径。 */
|
|
16
|
+
export function manifestPath(target) {
|
|
17
|
+
return path.join(target, MANIFEST_REL);
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 读取 manifest;不存在或损坏时返回 null。 */
|
|
21
|
+
export function readManifest(target) {
|
|
22
|
+
try {
|
|
23
|
+
return JSON.parse(fs.readFileSync(manifestPath(target), "utf8"));
|
|
24
|
+
} catch {
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** 写入 manifest。 */
|
|
30
|
+
export function writeManifest(target, data) {
|
|
31
|
+
fs.writeFileSync(manifestPath(target), JSON.stringify(data, null, 2) + "\n");
|
|
32
|
+
}
|
package/src/lib/paths.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fileURLToPath } from "node:url";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 包内路径定位。
|
|
6
|
+
*
|
|
7
|
+
* ESM 没有 __dirname,统一用 import.meta.url 推导。本文件位于 <pkg>/src/lib/,
|
|
8
|
+
* 故包根是其上两级目录。
|
|
9
|
+
*/
|
|
10
|
+
const here = path.dirname(fileURLToPath(import.meta.url)); // .../src/lib
|
|
11
|
+
|
|
12
|
+
/** flower-trellis 包根目录。 */
|
|
13
|
+
export const PKG_ROOT = path.resolve(here, "..", "..");
|
|
14
|
+
|
|
15
|
+
/** 随包发布的强化包快照根目录(由 scripts/sync-enhancements.mjs 生成)。 */
|
|
16
|
+
export const ENHANCEMENTS_ROOT = path.join(PKG_ROOT, "enhancements");
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 平台多选清单 —— flower 自己的平台选择菜单。
|
|
5
|
+
*
|
|
6
|
+
* 为什么 flower 自己出菜单:Trellis 原生平台菜单的默认勾选写死为 Claude Code + Cursor
|
|
7
|
+
* (types/ai-tools.ts 的 defaultChecked),外部 wrapper 改不了。这里默认勾 Claude Code + Codex,
|
|
8
|
+
* 用户选完后把 value(= trellis init 的平台 flag)透传给 trellis。
|
|
9
|
+
*
|
|
10
|
+
* name 取自 Trellis types/ai-tools.ts;value 为对应 CLI flag。
|
|
11
|
+
*/
|
|
12
|
+
const PLATFORMS = [
|
|
13
|
+
{ name: "Claude Code", value: "--claude", checked: true },
|
|
14
|
+
{
|
|
15
|
+
name: "Codex(同时写 .agents/skills/ — Cursor / Gemini CLI / GitHub Copilot 等可读)",
|
|
16
|
+
value: "--codex",
|
|
17
|
+
checked: true,
|
|
18
|
+
},
|
|
19
|
+
{ name: "Cursor", value: "--cursor" },
|
|
20
|
+
{ name: "OpenCode", value: "--opencode" },
|
|
21
|
+
{ name: "Gemini CLI", value: "--gemini" },
|
|
22
|
+
{ name: "Kilo CLI", value: "--kilo" },
|
|
23
|
+
{ name: "Kiro Code", value: "--kiro" },
|
|
24
|
+
{ name: "Antigravity", value: "--antigravity" },
|
|
25
|
+
{ name: "Windsurf", value: "--windsurf" },
|
|
26
|
+
{ name: "Qoder", value: "--qoder" },
|
|
27
|
+
{ name: "CodeBuddy", value: "--codebuddy" },
|
|
28
|
+
{ name: "GitHub Copilot", value: "--copilot" },
|
|
29
|
+
{ name: "Factory Droid", value: "--droid" },
|
|
30
|
+
{ name: "Pi", value: "--pi" },
|
|
31
|
+
{ name: "Reasonix", value: "--reasonix" },
|
|
32
|
+
];
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 弹出平台多选菜单,返回选中的 trellis 平台 flag 数组(如 ["--claude","--codex"])。
|
|
36
|
+
* 非 TTY(无法交互)时回退到默认 codex + claude。
|
|
37
|
+
*
|
|
38
|
+
* @returns {Promise<string[]>}
|
|
39
|
+
*/
|
|
40
|
+
export async function pickPlatforms() {
|
|
41
|
+
if (!process.stdin.isTTY) return ["--codex", "--claude"];
|
|
42
|
+
const { tools } = await inquirer.prompt([
|
|
43
|
+
{
|
|
44
|
+
type: "checkbox",
|
|
45
|
+
name: "tools",
|
|
46
|
+
message:
|
|
47
|
+
"选择要配置的 AI 工具(空格勾选 / 回车确认,默认已勾 Claude Code + Codex):",
|
|
48
|
+
choices: PLATFORMS.map((p) => ({
|
|
49
|
+
name: p.name,
|
|
50
|
+
value: p.value,
|
|
51
|
+
checked: !!p.checked,
|
|
52
|
+
})),
|
|
53
|
+
loop: false,
|
|
54
|
+
},
|
|
55
|
+
]);
|
|
56
|
+
return tools;
|
|
57
|
+
}
|