From 4492fcbdd684b4133ca94a509f64d9a6405a5b21 Mon Sep 17 00:00:00 2001 From: Jeffery Date: Thu, 14 May 2026 02:09:49 +0000 Subject: [PATCH] feat: sync triage skill files --- README.md | 4 ++++ TODO.md | 4 ++-- app/git.js | 26 ++++++++++++++++++++------ app/git.test.js | 45 +++++++++++++++++++++++++++++++++++++++++---- 4 files changed, 67 insertions(+), 12 deletions(-) diff --git a/README.md b/README.md index 1932fdb..62ba89b 100644 --- a/README.md +++ b/README.md @@ -224,3 +224,7 @@ Amazon Q:直接輸入 `triage-findings 問題原始檔(文字或截圖)` ### 適用情境 `triage-findings 問題原始檔(文字或截圖)` 用在 review 問題整併、排序、修正、排除誤判。 + +### 版本包含 + +提交時一併包含 `triage-findings` skill 與各平台入口檔。 diff --git a/TODO.md b/TODO.md index 23e1e63..c425d5c 100644 --- a/TODO.md +++ b/TODO.md @@ -38,8 +38,8 @@ - 已驗收:log 已明確顯示 `.gitea/ai-review/findings.json` 與 `.gitea/ai-review/exclusions.json` 都是 `JSON 格式正確`。 ## 階段八:記憶區 commit/push 與錯誤處理 -- 目標:記憶區能成功 commit/push,錯誤時有明確 log,流程結束有總結訊息。 -- 驗收:log 有「persisted findings」、「commit=...」、「push=...」等訊息,錯誤時有「Runner failed: ...」等明確錯誤說明。 +- 目標:記憶區能成功 commit/push,且一併包含 `triage-findings` skill 與各平台入口檔;錯誤時有明確 log,流程結束有總結訊息。 +- 驗收:log 有「persisted findings」、「commit=...」、「push=...」等訊息,且能看出 skill 相關檔案已一併提交;錯誤時有「Runner failed: ...」等明確錯誤說明。 - 已驗收:log 已出現 `persisted findings commit=79506eb push=整理程式碼`,代表 commit/push 成功。 ## 階段九:阻擋嚴重問題 PR(第 8 點) diff --git a/app/git.js b/app/git.js index 75041ef..ac38763 100644 --- a/app/git.js +++ b/app/git.js @@ -4,6 +4,16 @@ import path from 'path'; import { GITEA_SERVER_URL, GITEA_REPOSITORY, GITEA_TOKEN, PR_HEAD_BRANCH, FINDINGS_PATH } from './config.js'; const remoteUrl = `${GITEA_SERVER_URL.replace(/\/$/, '')}/${GITEA_REPOSITORY}.git`; +const SYNC_PATHS = [ + FINDINGS_PATH, + '.amazonq/rules/triage-findings.md', + '.claude/skills/triage-findings/SKILL.md', + '.gemini/skills/triage-findings/SKILL.md', + '.github/copilot-instructions.md', + '.github/skills/triage-findings/SKILL.md', + 'CLAUDE.md', + 'GEMINI.md', +]; function makeRunner(spawn) { return function run(args, cwd, env) { @@ -55,16 +65,20 @@ export async function commitAndPush(workspace, repoDir, _spawnSync = spawnSync) run(['config', 'user.email', 'ai-review[bot]@gitea'], repoDir); run(['config', 'user.name', 'AI Review Bot'], repoDir); - const srcFindings = path.join(workspace, FINDINGS_PATH); - const destFindings = path.join(repoDir, FINDINGS_PATH); - fs.mkdirSync(path.dirname(destFindings), { recursive: true }); - fs.copyFileSync(srcFindings, destFindings); + // Always copy source files over the repo copy so skill files stay in sync. + for (const relPath of SYNC_PATHS) { + const src = path.join(workspace, relPath); + const dest = path.join(repoDir, relPath); + if (!fs.existsSync(src)) continue; + fs.mkdirSync(path.dirname(dest), { recursive: true }); + fs.copyFileSync(src, dest); + } - run(['add', FINDINGS_PATH], repoDir); + run(['add', ...SYNC_PATHS], repoDir); const status = run(['status', '--porcelain'], repoDir); if (!status) { - console.log(' findings.json 無變更,跳過 commit'); + console.log(' sync files 無變更,跳過 commit'); return; } diff --git a/app/git.test.js b/app/git.test.js index bbf92e3..fb91869 100644 --- a/app/git.test.js +++ b/app/git.test.js @@ -10,10 +10,21 @@ function makeTmpWorkspace() { const ws = fs.mkdtempSync(path.join(os.tmpdir(), 'git-test-')); // Pre-create repo dir so clone branch is skipped fs.mkdirSync(path.join(ws, 'repo'), { recursive: true }); - // Create a findings.json to copy - const findingsDir = path.join(ws, '.gitea/ai-review'); - fs.mkdirSync(findingsDir, { recursive: true }); - fs.writeFileSync(path.join(findingsDir, 'findings.json'), '[]'); + const files = [ + '.gitea/ai-review/findings.json', + '.amazonq/rules/triage-findings.md', + '.claude/skills/triage-findings/SKILL.md', + '.gemini/skills/triage-findings/SKILL.md', + '.github/copilot-instructions.md', + '.github/skills/triage-findings/SKILL.md', + 'CLAUDE.md', + 'GEMINI.md', + ]; + for (const relPath of files) { + const fullPath = path.join(ws, relPath); + fs.mkdirSync(path.dirname(fullPath), { recursive: true }); + fs.writeFileSync(fullPath, relPath); + } return ws; } @@ -85,6 +96,32 @@ describe('commitAndPush', () => { assert.equal(commitCalled, false, 'commit should not run when there are no changes'); }); + it('adds skill and entry files together with findings', async () => { + const spawn = makeSpawn(); + await commitAndPush(workspace, path.join(workspace, 'repo'), spawn); + const addCall = spawn.calls.find(c => c.args[0] === 'add'); + assert.ok(addCall, 'expected git add to run'); + assert.ok(addCall.args.includes('.github/skills/triage-findings/SKILL.md')); + assert.ok(addCall.args.includes('.claude/skills/triage-findings/SKILL.md')); + assert.ok(addCall.args.includes('.gemini/skills/triage-findings/SKILL.md')); + assert.ok(addCall.args.includes('.github/copilot-instructions.md')); + assert.ok(addCall.args.includes('.amazonq/rules/triage-findings.md')); + assert.ok(addCall.args.includes('CLAUDE.md')); + assert.ok(addCall.args.includes('GEMINI.md')); + assert.ok(!addCall.args.includes('README.md')); + }); + + it('overwrites existing repo copies with workspace files', async () => { + const repoDir = path.join(workspace, 'repo'); + fs.writeFileSync(path.join(repoDir, '.github/skills/triage-findings/SKILL.md'), 'stale'); + fs.writeFileSync(path.join(repoDir, 'CLAUDE.md'), 'stale'); + + await commitAndPush(workspace, repoDir, makeSpawn()); + + assert.equal(fs.readFileSync(path.join(repoDir, '.github/skills/triage-findings/SKILL.md'), 'utf8'), '.github/skills/triage-findings/SKILL.md'); + assert.equal(fs.readFileSync(path.join(repoDir, 'CLAUDE.md'), 'utf8'), 'CLAUDE.md'); + }); + it('does not throw when git command fails', async () => { const failSpawn = () => ({ status: 1, stdout: '', stderr: 'fatal: error', error: null }); await assert.doesNotReject(() => commitAndPush(workspace, path.join(workspace, 'repo'), failSpawn));