import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import type { CommitChecklistInput, PrepareTaskInput, TaskRecordInput, WorkflowContextInput } from '../types.js'; import { projectAliases } from '../core/constants.js'; import { detectRepoType } from '../core/project.js'; import { formatDateInShanghai, getRecentTaskRecords, readText, resolveInsideRoot, resolveProject, workspaceRoot } from '../core/workspace.js'; import { buildGuardrails } from './guardrails.js'; import { inspectProject } from './inspect.js'; import { scanTaskRisk } from './envRisk.js'; import { createPageTestCase } from './testing.js'; export async function prepareTask(input: PrepareTaskInput): Promise> { const project = resolveProject(input.project); const taskType = input.taskType || 'general'; const inspection = await inspectProject({ includeGit: input.includeGit !== false, project: input.project, }); const guardrails = buildGuardrails(input); const riskScan = await scanTaskRisk({ changedFiles: input.changedFiles || [], project: input.project, }); const pageTestCase = input.includePageTest || taskType === 'page' ? createPageTestCase({ entryUrl: input.entryUrl, project: input.project, title: input.userRequest || `${project.label} 页面级测试`, }) : null; return { guardrails, inspection, nextSteps: [ '阅读 workPacket.readFirst 中的上下文文件。', '按 guardrails.beforeEdit 做开工前检查。', '按最小范围修改。', '按 verification.commands 和 notes 做轻量验证。', '有文件改动时更新 TASKS.md。', '最终回复或提交前跑全局 review,并处理全部 findings。', ], pageTestCase, project, readFirst: [ 'AGENTS.md', 'SKILLS.md', 'TASKS.md', `${project.relativePath}/AGENTS.md`, `${project.relativePath}/package.json`, ], riskScan, taskRecordDraft: buildTaskRecord({ content: input.userRequest || '待补充', scope: [project.relativePath], testCase: pageTestCase ? pageTestCase.title : '本次无页面测试或待补充。', title: input.recordTitle || input.userRequest || `${project.label} 任务记录`, verification: guardrails.guardrails.verification.commands.join(';') || '待补充。', }), taskType, userRequest: input.userRequest || '', }; } export function buildTaskRecord(input: TaskRecordInput): string { const date = input.date || formatDateInShanghai(); const scope = (input.scope?.length ?? 0) > 0 ? input.scope!.join('、') : '待补充'; const content = input.content || '待补充'; const testCase = input.testCase || '本次无页面测试或待补充。'; const verification = input.verification || '待补充。'; return `### ${date}:${input.title}\n\n- 范围:${scope}\n- 内容:${content}\n- 页面测试用例:${testCase}\n- 验证:${verification}\n`; } export function appendTaskRecord(input: TaskRecordInput): Record { const record = buildTaskRecord(input); const tasksPath = resolveInsideRoot('TASKS.md'); const current = readFileSync(tasksPath, 'utf8'); const marker = '## 改动记录'; const markerIndex = current.indexOf(marker); if (markerIndex === -1) { throw new Error('TASKS.md does not contain ## 改动记录'); } const insertAt = current.indexOf('\n', markerIndex); const next = `${current.slice(0, insertAt + 1)}\n${record}\n${current.slice(insertAt + 1)}`; if (!input.dryRun) { writeFileSync(tasksPath, next, 'utf8'); } return { dryRun: input.dryRun, record, target: tasksPath, }; } export async function createCommitChecklist(input: CommitChecklistInput): Promise> { const inspection = await inspectProject({ includeGit: true, project: input.project, }); const message = input.message || ''; const messageOk = !message || /^(feat|fix|docs|style|refactor|test|chore|build|ci|perf|revert)(\([^)]+\))?: .*[一-龥]/u.test( message, ); return { checks: [ '确认只包含本次任务相关文件。', '先运行对应轻量验证命令。', '提交前已跑全局 review,且 findings 已修复或明确记录为非阻塞风险。', '每个独立 Git 仓库单独 commit。', '没有用户明确要求时不 push。', ], inspection, message, messageOk, messageRule: '英文 type 前缀 + 中文描述,例如 feat: 功能性提交', }; } export function readWorkflowContext(input: WorkflowContextInput): Record { const projects = Object.keys(projectAliases).map((key) => { const project = resolveProject(key); return { ...project, exists: existsSync(project.path), repoType: existsSync(project.path) ? detectRepoType(project.path) : 'missing', }; }); return { docs: { agents: readText('AGENTS.md'), recentTasks: getRecentTaskRecords(input.taskRecordCount), skills: readText('SKILLS.md'), }, projects, workspaceRoot, }; }