T3: collector 骨架——watch→轉檔→git commit/push(design §4)

Node 單檔服務(非 arcrun workflow,跑客戶端機器):chokidar watch 事件驅動、debounce
合併批次 commit、.md passthrough(非 md 格式誠實丟 NotImplemented 待 T4 Markitdown)、
刪檔→target repo 移除對應檔(deprecated 標記留給 ingest workflow,collector 只管檔案鏡像)。

已驗(雲端 sandbox scratch 環境,非真實客戶環境):node --check 全過;起 scratch watch dir
+ scratch git repo + bare remote,實測 add/change/delete 三種事件皆正確偵測、debounce 正確
合併成一次 commit、git push 真的送達 remote(git log 驗證)、非 md 格式正確跳過並警告。

端到端待本機/客戶環境驗(README.md 已列):真實 NAS/VM watch、真實 Gitea remote+憑證、
Gitea push webhook 真觸發 ingest、systemd 常駐穩定性。
This commit is contained in:
2026-07-07 21:08:21 +00:00
commit 4ca94c17ca
8 changed files with 483 additions and 0 deletions
+44
View File
@@ -0,0 +1,44 @@
const fs = require('fs');
const path = require('path');
class NotImplementedError extends Error {}
const MARKDOWN_EXT = new Set(['.md', '.markdown']);
/**
* 檔案 → md 轉換(T3 骨架只做 .md passthroughT4 補 Markitdown 邏輯)。
* srcPath: 來源檔案絕對路徑(客戶知識資料夾內)
* 回傳:{ relOutputPath, content }(相對 target repo 子目錄的輸出路徑 + md 內容)
*/
function transformFile(srcPath, watchDir) {
const ext = path.extname(srcPath).toLowerCase();
const relSrc = path.relative(watchDir, srcPath);
if (MARKDOWN_EXT.has(ext)) {
const raw = fs.readFileSync(srcPath, 'utf8');
const content = stampSourcePath(raw, relSrc);
const relOutputPath = relSrc;
return { relOutputPath, content };
}
// 非 mddocx/pptx/pdf...)— T4 待補 Markitdown 轉檔,這裡先誠實丟未實作,不假裝轉好。
throw new NotImplementedError(
`${relSrc}:非 .md 格式轉檔待 T4Markitdown adapter)補上,本骨架先跳過`,
);
}
/** md frontmatter 補 source_path(溯源用,design.md §4:「md frontmatter 記原檔路徑」)。 */
function stampSourcePath(raw, relSrc) {
const stamp = `source_path: "${relSrc}"`;
if (raw.startsWith('---\n')) {
const end = raw.indexOf('\n---', 4);
if (end !== -1) {
const fm = raw.slice(4, end);
if (fm.includes('source_path:')) return raw; // 已有就不重複塞
return `---\n${stamp}\n${fm}\n---${raw.slice(end + 4)}`;
}
}
return `---\n${stamp}\n---\n\n${raw}`;
}
module.exports = { transformFile, NotImplementedError };