147 lines
5.1 KiB
TypeScript
147 lines
5.1 KiB
TypeScript
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<Record<string, unknown>> {
|
||
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<string, unknown> {
|
||
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<Record<string, unknown>> {
|
||
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<string, unknown> {
|
||
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,
|
||
};
|
||
}
|