feat(harness): 用戶 CC harness(acr install-harness)+ 公開 repo 只留對外

讓「用 arcrun 開發」的用戶,他的 CC 自動載入 arcrun 防護、不退回自寫 Python。

新增 user-cc-harness(SDD: .agents/specs/user-cc-harness,本機):
- acr install-harness:冪等裝進用戶當前專案(新/舊專案皆可),acr init 末尾也順便裝
  - CLAUDE.md arcrun 區塊(標記包夾,append 不破壞既有)
  - .claude/skills/arcrun-mindset(世界觀 + 資源去哪取 acr parts/auth-recipe)
  - .claude/commands/arcrun.md(/arcrun slash command)
  - .claude/hooks/arcrun-guard.sh(python→提醒不硬擋、暴露→exit 2、每條含正路)
  - settings.json 合併 hook(不覆蓋用戶既有 hooks/設定)
- llms.txt + README「給 AI」段:第一接觸點(用戶丟連結,CC 讀了知道第一步 install-harness)
  含 CF 憑證白話照抄式引導(不對用戶講 KV/Worker/R2 術語)
- harness 素材內嵌 npm 套件(cli/harness/,files 帶上),不依賴用戶有 arcrun repo
- 實測:空目錄/冪等/既有檔合併皆通過,tsc exit 0,npm pack 含 harness 5 檔

公開 repo 清理(richblack:用戶要用不要開發 arcrun):
- git rm --cached 移除開發痕跡 + 思考過程出公開 repo(本機保留供 richblack 開發):
  .claude/CLAUDE.md/AGENTS.md/.agents/docs/DECISIONS/BACKLOG/landing/.github
- .gitignore 防回流;補 MIT LICENSE

MCP(P7)納入 install-harness/update 的接點已設計,實作待 MCP 對齊(BACKLOG 另一條線)。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
uncle6me-web
2026-06-03 20:02:44 +08:00
parent 922a57fe34
commit 037cf9b6a4
139 changed files with 442 additions and 30768 deletions
+9
View File
@@ -18,6 +18,7 @@ import {
type DeployContext,
} from '../lib/deploy.js';
import { API_RECIPE_SEEDS } from '../lib/api-recipe-seeds.js';
import { cmdInstallHarness } from './install-harness.js';
const ARCRUN_REGISTER_URL = 'https://cypher.arcrun.dev/register';
@@ -42,6 +43,14 @@ export async function cmdInit(options: { local?: boolean; selfHosted?: boolean }
} finally {
rl.close();
}
// init 末尾順便裝 CC harness 進當前專案(SDD user-cc-harness §2init 裝 + 可單獨裝)。
// 失敗不影響 init 本身(harness 是加分,可事後 acr install-harness 補)。
try {
await cmdInstallHarness();
} catch (e) {
console.log(chalk.gray(` harness 安裝略過:${e instanceof Error ? e.message : e};可稍後跑 acr install-harness`));
}
}
async function initLocal(): Promise<void> {
+127
View File
@@ -0,0 +1,127 @@
/**
* acr install-harness — 把「用戶 CC harness」裝進當前專案。
*
* 對象:在「用 arcrun 開發」的專案裡工作的 CC + 使用者。讓使用者的 CC 自動載入 arcrun 防護:
* - CLAUDE.md 區塊(事前提醒:別自寫 Python)
* - .claude/skills/arcrun-mindset/(世界觀 + 資源去哪取)
* - .claude/commands/arcrun.md/arcrun slash command
* - .claude/hooks/arcrun-guard.sh + settings.json(做錯被糾正)
*
* 冪等:重裝不重複、不破壞使用者既有 CLAUDE.md / settings。
* SDD.agents/specs/user-cc-harness/design.md §2
*/
import { fileURLToPath } from 'node:url';
import {
readFileSync, writeFileSync, existsSync, mkdirSync, copyFileSync, chmodSync, readdirSync,
} from 'node:fs';
import { join, dirname } from 'node:path';
import chalk from 'chalk';
/** harness 素材根目錄(內嵌 npm 套件,SSOT=cli/harness/)。
* build 後此檔在 dist/commands/harness/ 在套件根 → ../../harness。 */
function harnessRoot(): string {
const here = dirname(fileURLToPath(import.meta.url)); // .../dist/commands
return join(here, '..', '..', 'harness'); // .../harness
}
const START = '<!-- arcrun-harness:start -->';
const END = '<!-- arcrun-harness:end -->';
export async function cmdInstallHarness(): Promise<void> {
const cwd = process.cwd();
const src = harnessRoot();
if (!existsSync(src)) {
console.error(chalk.red(`找不到 harness 素材(${src})。套件安裝可能不完整,請重裝 arcrun。`));
process.exit(1);
}
console.log(chalk.bold('\n 安裝 arcrun harness 到當前專案\n'));
// 1. CLAUDE.mdappend/取代 arcrun 區塊(標記包夾,冪等)
installClaudeBlock(cwd, src);
// 2. mindset Skill
copyTree(join(src, 'skills'), join(cwd, '.claude', 'skills'));
console.log(chalk.green(' ✓ .claude/skills/arcrun-mindset/'));
// 3. /arcrun command
copyTree(join(src, 'commands'), join(cwd, '.claude', 'commands'));
console.log(chalk.green(' ✓ .claude/commands/arcrun.md'));
// 4. hook
const hookDst = join(cwd, '.claude', 'hooks', 'arcrun-guard.sh');
mkdirSync(dirname(hookDst), { recursive: true });
copyFileSync(join(src, 'hooks', 'arcrun-guard.sh'), hookDst);
chmodSync(hookDst, 0o755);
console.log(chalk.green(' ✓ .claude/hooks/arcrun-guard.sh'));
// 5. settings.json:合併 hook 註冊(不覆蓋使用者既有設定)
mergeSettings(cwd, src);
console.log(chalk.green(' ✓ .claude/settings.json(已合併 arcrun guard hook'));
console.log(chalk.gray('\n 提示:'));
console.log(chalk.gray(' • 首次在此專案開 Claude Code 會要求「信任工作區」,按信任 hook 才生效。'));
console.log(chalk.gray(' • 之後跟 CC 說需求即可(或打 /arcrun <你的需求>)。'));
console.log(chalk.gray(' • CC 偏好 MCP?可另跑 acr update 連 arcrun MCPMCP 對齊中,optional)。\n'));
}
/** CLAUDE.md:無→建;有 arcrun 區塊→取代;有但無區塊→append。標記包夾,冪等。 */
function installClaudeBlock(cwd: string, src: string): void {
const block = readFileSync(join(src, 'CLAUDE.block.md'), 'utf8').trim();
const path = join(cwd, 'CLAUDE.md');
if (!existsSync(path)) {
writeFileSync(path, block + '\n', 'utf8');
console.log(chalk.green(' ✓ CLAUDE.md(已建立,含 arcrun 區塊)'));
return;
}
const cur = readFileSync(path, 'utf8');
if (cur.includes(START) && cur.includes(END)) {
// 取代既有區塊
const re = new RegExp(escapeRe(START) + '[\\s\\S]*?' + escapeRe(END));
writeFileSync(path, cur.replace(re, block), 'utf8');
console.log(chalk.green(' ✓ CLAUDE.md(已更新 arcrun 區塊)'));
} else {
writeFileSync(path, cur.replace(/\s*$/, '') + '\n\n' + block + '\n', 'utf8');
console.log(chalk.green(' ✓ CLAUDE.md(已附加 arcrun 區塊,未動既有內容)'));
}
}
/** 把 settings.fragment.json 的 hook 合併進專案 settings.json(不覆蓋使用者既有 hooks/設定)。 */
function mergeSettings(cwd: string, src: string): void {
const fragment = JSON.parse(readFileSync(join(src, 'settings.fragment.json'), 'utf8'));
const path = join(cwd, '.claude', 'settings.json');
mkdirSync(dirname(path), { recursive: true });
let settings: Record<string, unknown> = {};
if (existsSync(path)) {
try { settings = JSON.parse(readFileSync(path, 'utf8')); } catch { settings = {}; }
}
const hooks = (settings.hooks ?? {}) as Record<string, unknown[]>;
const fragHooks = fragment.hooks as Record<string, unknown[]>;
for (const [event, entries] of Object.entries(fragHooks)) {
const existing = Array.isArray(hooks[event]) ? hooks[event] : [];
// 去重:避免重裝重複加 arcrun-guard
const serialized = new Set(existing.map(e => JSON.stringify(e)));
for (const e of entries) {
if (!serialized.has(JSON.stringify(e))) existing.push(e);
}
hooks[event] = existing;
}
settings.hooks = hooks;
writeFileSync(path, JSON.stringify(settings, null, 2) + '\n', 'utf8');
}
/** 遞迴複製目錄樹(覆蓋同名檔)。 */
function copyTree(srcDir: string, dstDir: string): void {
if (!existsSync(srcDir)) return;
mkdirSync(dstDir, { recursive: true });
for (const name of readdirSync(srcDir, { withFileTypes: true })) {
const s = join(srcDir, name.name);
const d = join(dstDir, name.name);
if (name.isDirectory()) copyTree(s, d);
else copyFileSync(s, d);
}
}
function escapeRe(s: string): string {
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}
+7
View File
@@ -17,6 +17,7 @@ import { cmdRecipePush, cmdRecipeList, cmdRecipeDelete } from './commands/recipe
import { cmdList } from './commands/list.js';
import { cmdLogs } from './commands/logs.js';
import { cmdUpdate } from './commands/update.js';
import { cmdInstallHarness } from './commands/install-harness.js';
import { cmdAuthRecipeList, cmdAuthRecipeInfo, cmdAuthRecipeScaffold } from './commands/auth-recipe.js';
const program = new Command();
@@ -127,4 +128,10 @@ program
.description('self-hosted:拉新 release 並重新部署到你的 Cloudflare')
.action(() => cmdUpdate());
// acr install-harness(把 arcrun 的 CC harness 裝進當前專案)
program
.command('install-harness')
.description('把 arcrun 的 CC harnessmindset/提醒/防做歪 hook/指令)裝進當前專案')
.action(() => cmdInstallHarness());
program.parse(process.argv);