team-anya 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of team-anya might be problematic. Click here for more details.
- package/README.md +38 -0
- package/anya/prompts/execution-guides/git-delivery.md +38 -0
- package/anya/prompts/execution-guides/testing-and-self-heal.md +28 -0
- package/anya/prompts/protocols/brief-assembly.md +55 -0
- package/anya/prompts/protocols/report.md +175 -0
- package/anya/prompts/protocols/review.md +90 -0
- package/anya/prompts/task-claude-md.template.md +32 -0
- package/apps/server/dist/broker/cc-broker.js +257 -0
- package/apps/server/dist/cli.js +296 -0
- package/apps/server/dist/config.js +76 -0
- package/apps/server/dist/daemon.js +51 -0
- package/apps/server/dist/gateway/chat-sync.js +135 -0
- package/apps/server/dist/gateway/command-router.js +114 -0
- package/apps/server/dist/gateway/commands/cancel.js +32 -0
- package/apps/server/dist/gateway/commands/help.js +16 -0
- package/apps/server/dist/gateway/commands/index.js +26 -0
- package/apps/server/dist/gateway/commands/restart.js +34 -0
- package/apps/server/dist/gateway/commands/status.js +34 -0
- package/apps/server/dist/gateway/commands/tasks.js +33 -0
- package/apps/server/dist/gateway/feishu-sender.js +346 -0
- package/apps/server/dist/gateway/feishu-ws.js +254 -0
- package/apps/server/dist/gateway/http.js +994 -0
- package/apps/server/dist/gateway/media-downloader.js +149 -0
- package/apps/server/dist/gateway/message-events.js +10 -0
- package/apps/server/dist/gateway/message-intake.js +50 -0
- package/apps/server/dist/gateway/message-queue.js +104 -0
- package/apps/server/dist/gateway/session-reader.js +142 -0
- package/apps/server/dist/gateway/ws-push.js +115 -0
- package/apps/server/dist/loid/brain.js +104 -0
- package/apps/server/dist/loid/brief-assembler.js +156 -0
- package/apps/server/dist/loid/clarifier.js +162 -0
- package/apps/server/dist/loid/commitment-tracker.js +236 -0
- package/apps/server/dist/loid/context-builder.js +413 -0
- package/apps/server/dist/loid/dispatcher.js +544 -0
- package/apps/server/dist/loid/intent-classifier.js +158 -0
- package/apps/server/dist/loid/mcp-server.js +104 -0
- package/apps/server/dist/loid/memory-settler.js +189 -0
- package/apps/server/dist/loid/opportunity-manager.js +148 -0
- package/apps/server/dist/loid/process-manager.js +186 -0
- package/apps/server/dist/loid/profile-updater.js +179 -0
- package/apps/server/dist/loid/reporter.js +148 -0
- package/apps/server/dist/loid/schemas.js +117 -0
- package/apps/server/dist/loid/self-calibrator.js +314 -0
- package/apps/server/dist/loid/session-manager.js +217 -0
- package/apps/server/dist/loid/session.js +271 -0
- package/apps/server/dist/loid/worktree-manager.js +191 -0
- package/apps/server/dist/main.js +337 -0
- package/apps/server/dist/tracing/index.js +2 -0
- package/apps/server/dist/tracing/trace-context.js +92 -0
- package/apps/server/dist/types/message.js +2 -0
- package/apps/server/dist/yor/yor-mcp-server.js +104 -0
- package/apps/server/dist/yor/yor-orchestrator.js +233 -0
- package/apps/web/dist/assets/index-CHIT0Dya.css +1 -0
- package/apps/web/dist/assets/index-CJzAjoVH.js +798 -0
- package/apps/web/dist/index.html +13 -0
- package/package.json +42 -0
- package/packages/cc-client/dist/claude-code-backend.js +664 -0
- package/packages/cc-client/dist/index.js +2 -0
- package/packages/cc-client/package.json +11 -0
- package/packages/core/dist/constants.js +59 -0
- package/packages/core/dist/errors.js +35 -0
- package/packages/core/dist/index.js +7 -0
- package/packages/core/dist/office-init.js +97 -0
- package/packages/core/dist/scope/checker.js +114 -0
- package/packages/core/dist/scope/defaults.js +40 -0
- package/packages/core/dist/scope/index.js +3 -0
- package/packages/core/dist/state-machine.js +85 -0
- package/packages/core/dist/types/audit.js +12 -0
- package/packages/core/dist/types/backend.js +2 -0
- package/packages/core/dist/types/commitment.js +17 -0
- package/packages/core/dist/types/communication.js +18 -0
- package/packages/core/dist/types/index.js +8 -0
- package/packages/core/dist/types/opportunity.js +27 -0
- package/packages/core/dist/types/org.js +26 -0
- package/packages/core/dist/types/task.js +46 -0
- package/packages/core/package.json +10 -0
- package/packages/db/dist/client.js +69 -0
- package/packages/db/dist/index.js +603 -0
- package/packages/db/dist/schema/audit-events.js +13 -0
- package/packages/db/dist/schema/cc-sessions.js +14 -0
- package/packages/db/dist/schema/chats.js +33 -0
- package/packages/db/dist/schema/commitments.js +18 -0
- package/packages/db/dist/schema/communication-events.js +14 -0
- package/packages/db/dist/schema/index.js +12 -0
- package/packages/db/dist/schema/message-log.js +20 -0
- package/packages/db/dist/schema/opportunities.js +23 -0
- package/packages/db/dist/schema/org.js +36 -0
- package/packages/db/dist/schema/projects.js +23 -0
- package/packages/db/dist/schema/tasks.js +46 -0
- package/packages/db/dist/schema/trace-spans.js +19 -0
- package/packages/db/package.json +12 -0
- package/packages/db/src/migrations/0000_simple_magneto.sql +148 -0
- package/packages/db/src/migrations/0001_nifty_morph.sql +42 -0
- package/packages/db/src/migrations/0002_common_joshua_kane.sql +20 -0
- package/packages/db/src/migrations/0003_add_cc_sessions.sql +13 -0
- package/packages/db/src/migrations/0004_jittery_triathlon.sql +1 -0
- package/packages/db/src/migrations/meta/0000_snapshot.json +987 -0
- package/packages/db/src/migrations/meta/0001_snapshot.json +1280 -0
- package/packages/db/src/migrations/meta/0002_snapshot.json +1417 -0
- package/packages/db/src/migrations/meta/0004_snapshot.json +1505 -0
- package/packages/db/src/migrations/meta/_journal.json +41 -0
- package/packages/mcp-tools/dist/index.js +41 -0
- package/packages/mcp-tools/dist/layer1/audit-append.js +38 -0
- package/packages/mcp-tools/dist/layer1/audit-query.js +51 -0
- package/packages/mcp-tools/dist/layer1/memory-brief.js +168 -0
- package/packages/mcp-tools/dist/layer1/memory-context.js +124 -0
- package/packages/mcp-tools/dist/layer1/memory-digest.js +126 -0
- package/packages/mcp-tools/dist/layer1/memory-forget.js +108 -0
- package/packages/mcp-tools/dist/layer1/memory-learn.js +63 -0
- package/packages/mcp-tools/dist/layer1/memory-recall.js +287 -0
- package/packages/mcp-tools/dist/layer1/memory-reflect.js +80 -0
- package/packages/mcp-tools/dist/layer1/memory-remember.js +119 -0
- package/packages/mcp-tools/dist/layer1/memory-search.js +263 -0
- package/packages/mcp-tools/dist/layer1/memory-write.js +21 -0
- package/packages/mcp-tools/dist/layer1/org-lookup.js +47 -0
- package/packages/mcp-tools/dist/layer1/project-get.js +28 -0
- package/packages/mcp-tools/dist/layer1/project-list.js +20 -0
- package/packages/mcp-tools/dist/layer1/report-daily.js +68 -0
- package/packages/mcp-tools/dist/layer1/task-get.js +29 -0
- package/packages/mcp-tools/dist/layer1/task-update.js +34 -0
- package/packages/mcp-tools/dist/layer2/loid/decision-log.js +15 -0
- package/packages/mcp-tools/dist/layer2/loid/decision-no-action.js +15 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-create-pr.js +30 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-share.js +12 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-submit.js +77 -0
- package/packages/mcp-tools/dist/layer2/loid/delivery-upload.js +18 -0
- package/packages/mcp-tools/dist/layer2/loid/project-remove.js +16 -0
- package/packages/mcp-tools/dist/layer2/loid/project-upsert.js +33 -0
- package/packages/mcp-tools/dist/layer2/loid/task-dispatch.js +177 -0
- package/packages/mcp-tools/dist/layer2/loid/task-lookup.js +38 -0
- package/packages/mcp-tools/dist/layer2/loid/task-review.js +151 -0
- package/packages/mcp-tools/dist/layer2/loid/workspace-cleanup.js +7 -0
- package/packages/mcp-tools/dist/layer2/loid/workspace-info.js +31 -0
- package/packages/mcp-tools/dist/layer2/loid/workspace-prepare.js +12 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-approve.js +8 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-kill.js +7 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-rework.js +7 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-spawn.js +15 -0
- package/packages/mcp-tools/dist/layer2/loid/yor-status.js +8 -0
- package/packages/mcp-tools/dist/layer2/yor/code-lint.js +47 -0
- package/packages/mcp-tools/dist/layer2/yor/code-test.js +52 -0
- package/packages/mcp-tools/dist/layer2/yor/git-add.js +24 -0
- package/packages/mcp-tools/dist/layer2/yor/git-commit.js +24 -0
- package/packages/mcp-tools/dist/layer2/yor/git-push.js +64 -0
- package/packages/mcp-tools/dist/layer2/yor/task-block.js +11 -0
- package/packages/mcp-tools/dist/layer2/yor/task-deliver.js +35 -0
- package/packages/mcp-tools/dist/layer2/yor/task-progress.js +21 -0
- package/packages/mcp-tools/dist/layer3/adapters/feishu-adapter.js +191 -0
- package/packages/mcp-tools/dist/layer3/adapters/types.js +28 -0
- package/packages/mcp-tools/dist/layer3/channel-receive.js +11 -0
- package/packages/mcp-tools/dist/layer3/channel-send.js +90 -0
- package/packages/mcp-tools/dist/layer3/file-upload.js +44 -0
- package/packages/mcp-tools/dist/registry.js +779 -0
- package/packages/mcp-tools/package.json +13 -0
- package/workspace/.claude/settings.local.json +9 -0
- package/workspace/.mcp.json +12 -0
- package/workspace/CHARTER.md +73 -0
- package/workspace/CLAUDE.md +49 -0
- package/workspace/PROTOCOL.md +126 -0
- package/workspace/TOOLS.md +464 -0
- package/workspace/audit/.gitkeep +0 -0
- package/workspace/loid/CLAUDE.md +12 -0
- package/workspace/loid/PLAYBOOK.md +198 -0
- package/workspace/loid/PROFILE.md +78 -0
- package/workspace/memory/commitments/.gitkeep +0 -0
- package/workspace/memory/execution/.gitkeep +0 -0
- package/workspace/memory/people/.gitkeep +0 -0
- package/workspace/memory/projects/.gitkeep +0 -0
- package/workspace/memory/self/.gitkeep +0 -0
- package/workspace/reference/identity/.gitkeep +0 -0
- package/workspace/reference/org/escalation.yaml +24 -0
- package/workspace/reference/org/ownership.yaml +28 -0
- package/workspace/reports/.gitkeep +0 -0
- package/workspace/yor/CLAUDE.md +22 -0
- package/workspace/yor/PLAYBOOK.md +73 -0
- package/workspace/yor/PROFILE.md +52 -0
- package/workspace/yor/SELF-HEAL.md +39 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { fileUpload } from '../../layer3/file-upload.js';
|
|
2
|
+
/**
|
|
3
|
+
* delivery.upload - 上传文件产物
|
|
4
|
+
*
|
|
5
|
+
* 委托给 file.upload 实现。target 参数暂不使用(预留给后续上传到指定位置的场景)。
|
|
6
|
+
*/
|
|
7
|
+
export async function deliveryUpload(deps, input) {
|
|
8
|
+
const result = await fileUpload(deps, {
|
|
9
|
+
file_path: input.file_path,
|
|
10
|
+
});
|
|
11
|
+
return {
|
|
12
|
+
status: 'uploaded',
|
|
13
|
+
url: result.url,
|
|
14
|
+
file_name: result.file_name,
|
|
15
|
+
file_size: result.file_size,
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=delivery-upload.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { getProject, softDeleteProject, insertAuditEvent } from '@team-anya/db';
|
|
2
|
+
export async function projectRemove(db, _workspacePath, input) {
|
|
3
|
+
const existing = getProject(db, input.project_id);
|
|
4
|
+
if (!existing) {
|
|
5
|
+
return { removed: false, message: `Project ${input.project_id} not found` };
|
|
6
|
+
}
|
|
7
|
+
softDeleteProject(db, input.project_id);
|
|
8
|
+
insertAuditEvent(db, {
|
|
9
|
+
event_type: 'project_removed',
|
|
10
|
+
actor: 'loid',
|
|
11
|
+
summary: `Project ${input.project_id} soft-deleted: ${input.reason}`,
|
|
12
|
+
detail: JSON.stringify({ project_id: input.project_id, reason: input.reason }),
|
|
13
|
+
});
|
|
14
|
+
return { removed: true, message: `Project ${input.project_id} archived` };
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=project-remove.js.map
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { getProject, upsertProject, syncProjectRepos, insertAuditEvent } from '@team-anya/db';
|
|
2
|
+
export async function projectUpsert(db, _workspacePath, input) {
|
|
3
|
+
const existing = getProject(db, input.project_id);
|
|
4
|
+
const action = existing ? 'updated' : 'created';
|
|
5
|
+
upsertProject(db, {
|
|
6
|
+
project_id: input.project_id,
|
|
7
|
+
name: input.name,
|
|
8
|
+
description: input.description ?? null,
|
|
9
|
+
platform: input.platform ?? 'github',
|
|
10
|
+
claude_md: input.claude_md ?? null,
|
|
11
|
+
});
|
|
12
|
+
const repos = input.repos ?? [];
|
|
13
|
+
if (repos.length > 0) {
|
|
14
|
+
syncProjectRepos(db, input.project_id, repos.map(r => ({
|
|
15
|
+
name: r.name,
|
|
16
|
+
git_url: r.git_url,
|
|
17
|
+
repo_path: r.repo_path,
|
|
18
|
+
default_branch: r.default_branch ?? 'main',
|
|
19
|
+
})));
|
|
20
|
+
}
|
|
21
|
+
insertAuditEvent(db, {
|
|
22
|
+
event_type: `project_${action}`,
|
|
23
|
+
actor: 'loid',
|
|
24
|
+
summary: `Project ${input.project_id} ${action}`,
|
|
25
|
+
detail: JSON.stringify({
|
|
26
|
+
project_id: input.project_id,
|
|
27
|
+
platform: input.platform ?? 'github',
|
|
28
|
+
repo_count: repos.length,
|
|
29
|
+
}),
|
|
30
|
+
});
|
|
31
|
+
return { project_id: input.project_id, action };
|
|
32
|
+
}
|
|
33
|
+
//# sourceMappingURL=project-upsert.js.map
|
|
@@ -0,0 +1,177 @@
|
|
|
1
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { execFile as execFileCb } from 'node:child_process';
|
|
3
|
+
import { join } from 'node:path';
|
|
4
|
+
import { promisify } from 'node:util';
|
|
5
|
+
import { createTask, updateTask, insertAuditEvent, getTodayMaxSequence, } from '@team-anya/db';
|
|
6
|
+
import { TaskStatus, generateTaskId, assertTransition } from '@team-anya/core';
|
|
7
|
+
const execFile = promisify(execFileCb);
|
|
8
|
+
/**
|
|
9
|
+
* 创建任务、写 brief、自动准备工作区
|
|
10
|
+
*
|
|
11
|
+
* 派工两步走:task.dispatch(MCP) → yor.spawn(MCP)
|
|
12
|
+
* dispatch 内部自动根据 project mode 准备隔离工作区(git worktree / mkdir)。
|
|
13
|
+
* 工作区准备失败时标记 BLOCKED 并返回 error,不抛异常。
|
|
14
|
+
*/
|
|
15
|
+
export async function taskDispatch(deps, input) {
|
|
16
|
+
const { db, workspacePath, logger } = deps;
|
|
17
|
+
const seq = getTodayMaxSequence(db) + 1;
|
|
18
|
+
const taskId = generateTaskId(seq);
|
|
19
|
+
// 1. 创建任务
|
|
20
|
+
createTask(db, {
|
|
21
|
+
task_id: taskId,
|
|
22
|
+
title: input.title,
|
|
23
|
+
status: TaskStatus.NEW,
|
|
24
|
+
source_type: 'feishu',
|
|
25
|
+
source_ref: input.source_message_id ?? null,
|
|
26
|
+
objective: input.objective,
|
|
27
|
+
acceptance_criteria: input.acceptance_criteria ? JSON.stringify(input.acceptance_criteria) : undefined,
|
|
28
|
+
context: input.context,
|
|
29
|
+
project_id: input.project_id,
|
|
30
|
+
created_by: input.created_by ?? null,
|
|
31
|
+
source_chat_id: input.source_chat_id ?? null,
|
|
32
|
+
});
|
|
33
|
+
// 2. 写 brief 到 workspace
|
|
34
|
+
const taskDir = join(workspacePath, 'yor', 'tasks', taskId);
|
|
35
|
+
await mkdir(taskDir, { recursive: true });
|
|
36
|
+
const briefPath = join(taskDir, 'brief.md');
|
|
37
|
+
await writeFile(briefPath, input.brief, 'utf-8');
|
|
38
|
+
// 3. 审计日志
|
|
39
|
+
insertAuditEvent(db, {
|
|
40
|
+
event_type: 'task_dispatched',
|
|
41
|
+
actor: 'loid',
|
|
42
|
+
task_id: taskId,
|
|
43
|
+
summary: `Loid 派工: ${input.title}`,
|
|
44
|
+
detail: JSON.stringify({ brief_length: input.brief.length, project: input.project_id }),
|
|
45
|
+
});
|
|
46
|
+
// 4. 获取项目配置
|
|
47
|
+
const project = deps.getProjectConfig
|
|
48
|
+
? await deps.getProjectConfig(input.project_id)
|
|
49
|
+
: undefined;
|
|
50
|
+
logger?.info(`[anya:pipeline] [Loid] 创建任务 ${taskId} "${input.title}"${input.project_id ? ` | project=${input.project_id} mode=${project?.mode}` : ''}`);
|
|
51
|
+
// 5. 自动准备工作区
|
|
52
|
+
const workspaceResult = await prepareWorkspace(taskId, taskDir, project, logger);
|
|
53
|
+
if (!workspaceResult.ok) {
|
|
54
|
+
// 工作区准备失败 → NEW → BLOCKED(经状态机校验)
|
|
55
|
+
assertTransition(TaskStatus.NEW, TaskStatus.BLOCKED);
|
|
56
|
+
updateTask(db, taskId, { status: TaskStatus.BLOCKED });
|
|
57
|
+
insertAuditEvent(db, {
|
|
58
|
+
event_type: 'task_status_changed',
|
|
59
|
+
actor: 'loid',
|
|
60
|
+
task_id: taskId,
|
|
61
|
+
summary: `NEW → BLOCKED: 工作区准备失败: ${workspaceResult.error}`,
|
|
62
|
+
detail: JSON.stringify({ from: 'NEW', to: 'BLOCKED', reason: workspaceResult.error }),
|
|
63
|
+
});
|
|
64
|
+
return { task_id: taskId, status: 'blocked', brief_path: briefPath, error: workspaceResult.error };
|
|
65
|
+
}
|
|
66
|
+
// 6. 工作区就绪 → NEW → READY,同时回写工作区信息
|
|
67
|
+
assertTransition(TaskStatus.NEW, TaskStatus.READY);
|
|
68
|
+
const branchName = project?.mode === 'project' ? `feat/anya-${taskId}` : undefined;
|
|
69
|
+
updateTask(db, taskId, {
|
|
70
|
+
status: TaskStatus.READY,
|
|
71
|
+
workspace_path: workspaceResult.workingDir,
|
|
72
|
+
assignee: 'yor',
|
|
73
|
+
...(branchName ? { branch: branchName } : {}),
|
|
74
|
+
});
|
|
75
|
+
insertAuditEvent(db, {
|
|
76
|
+
event_type: 'task_status_changed',
|
|
77
|
+
actor: 'loid',
|
|
78
|
+
task_id: taskId,
|
|
79
|
+
summary: 'NEW → READY: 工作区准备完成',
|
|
80
|
+
detail: JSON.stringify({ from: 'NEW', to: 'READY', workspace_path: workspaceResult.workingDir, branch: branchName }),
|
|
81
|
+
});
|
|
82
|
+
return { task_id: taskId, status: 'dispatched', brief_path: briefPath, working_dir: workspaceResult.workingDir };
|
|
83
|
+
}
|
|
84
|
+
async function prepareWorkspace(taskId, taskDir, project, logger) {
|
|
85
|
+
const mode = project?.mode ?? 'adhoc';
|
|
86
|
+
try {
|
|
87
|
+
switch (mode) {
|
|
88
|
+
case 'project': {
|
|
89
|
+
const repos = project.repos;
|
|
90
|
+
const workingDir = taskDir;
|
|
91
|
+
const createdWorktrees = [];
|
|
92
|
+
try {
|
|
93
|
+
for (const repo of repos) {
|
|
94
|
+
const defaultBranch = repo.default_branch ?? 'main';
|
|
95
|
+
const wtPath = join(workingDir, repo.name);
|
|
96
|
+
await execFile('git', ['-C', repo.repo_path, 'fetch', 'origin']);
|
|
97
|
+
await execFile('git', ['-C', repo.repo_path, 'worktree', 'add', wtPath, '-b', `feat/anya-${taskId}`, `origin/${defaultBranch}`]);
|
|
98
|
+
createdWorktrees.push({ repoPath: repo.repo_path, wtPath });
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
// 回滚已创建的 worktree
|
|
103
|
+
for (const wt of createdWorktrees) {
|
|
104
|
+
try {
|
|
105
|
+
await execFile('git', ['-C', wt.repoPath, 'worktree', 'remove', wt.wtPath, '--force']);
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// 回滚失败忽略
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
112
|
+
return { ok: false, error: `worktree 准备失败: ${msg}` };
|
|
113
|
+
}
|
|
114
|
+
// git init 为 CC 提供项目边界(防止全局搜索)
|
|
115
|
+
await execFile('git', ['init', workingDir]);
|
|
116
|
+
// .gitignore 忽略所有 worktree 子目录(它们各自有独立的 .git)
|
|
117
|
+
const ignoreContent = repos.map(r => `/${r.name}/`).join('\n') + '\n';
|
|
118
|
+
await writeFile(join(workingDir, '.gitignore'), ignoreContent, 'utf-8');
|
|
119
|
+
// 写入 CLAUDE.md 帮助 CC 理解工作区结构
|
|
120
|
+
const claudeMd = buildProjectClaudeMd(taskId, repos, project.claudeMd);
|
|
121
|
+
await writeFile(join(workingDir, 'CLAUDE.md'), claudeMd, 'utf-8');
|
|
122
|
+
// 提交到 git,否则 CC 不会加载 untracked 的 CLAUDE.md
|
|
123
|
+
await execFile('git', ['-C', workingDir, 'add', 'CLAUDE.md', '.gitignore']);
|
|
124
|
+
await execFile('git', ['-C', workingDir, 'commit', '-m', 'init: workspace config']);
|
|
125
|
+
logger?.info(`[anya:pipeline] [Loid] worktrees 已创建 (${repos.length} repos): ${workingDir}`);
|
|
126
|
+
return { ok: true, workingDir };
|
|
127
|
+
}
|
|
128
|
+
case 'adhoc':
|
|
129
|
+
default: {
|
|
130
|
+
const workingDir = taskDir;
|
|
131
|
+
await mkdir(join(workingDir, 'adhoc'), { recursive: true });
|
|
132
|
+
// 在任务级目录 git init,版本管理整个任务目录
|
|
133
|
+
await execFile('git', ['init', workingDir]);
|
|
134
|
+
await writeFile(join(workingDir, '.gitignore'), '*.tmp\nnode_modules/\n.DS_Store\n', 'utf-8');
|
|
135
|
+
await execFile('git', ['-C', workingDir, 'add', '.']);
|
|
136
|
+
await execFile('git', ['-C', workingDir, 'commit', '-m', `chore: init task ${taskId}`]);
|
|
137
|
+
logger?.info(`[anya:pipeline] [Loid] adhoc 目录已创建并 git init: ${workingDir}`);
|
|
138
|
+
return { ok: true, workingDir };
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
144
|
+
return { ok: false, error: `工作区准备失败 (${mode}): ${msg}` };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
// ── 工作区 CLAUDE.md 生成 ──
|
|
148
|
+
function buildProjectClaudeMd(taskId, repos, projectClaudeMd) {
|
|
149
|
+
const lines = [
|
|
150
|
+
`# 任务工作区 ${taskId}`,
|
|
151
|
+
'',
|
|
152
|
+
'本目录是任务工作区根目录。每个子目录是一个独立的 git worktree。',
|
|
153
|
+
'',
|
|
154
|
+
'## 仓库列表',
|
|
155
|
+
'',
|
|
156
|
+
'| 目录 | 分支 |',
|
|
157
|
+
'|------|------|',
|
|
158
|
+
];
|
|
159
|
+
for (const repo of repos) {
|
|
160
|
+
lines.push(`| \`${repo.name}/\` | \`feat/anya-${taskId}\` (基于 ${repo.default_branch}) |`);
|
|
161
|
+
}
|
|
162
|
+
lines.push('');
|
|
163
|
+
lines.push('## 重要');
|
|
164
|
+
lines.push('');
|
|
165
|
+
lines.push('- 每个子目录是独立的 git 仓库(worktree),搜索代码时请进入对应子目录');
|
|
166
|
+
lines.push('- git 操作(add/commit/push)必须在对应子目录内执行');
|
|
167
|
+
lines.push('- 不要在根目录执行 git commit');
|
|
168
|
+
lines.push('- 任务产物请读取 `brief.md` 了解详情');
|
|
169
|
+
if (projectClaudeMd) {
|
|
170
|
+
lines.push('');
|
|
171
|
+
lines.push('## 项目技术栈');
|
|
172
|
+
lines.push('');
|
|
173
|
+
lines.push(projectClaudeMd);
|
|
174
|
+
}
|
|
175
|
+
return lines.join('\n') + '\n';
|
|
176
|
+
}
|
|
177
|
+
//# sourceMappingURL=task-dispatch.js.map
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { getTask, getTasksByStatus } from '@team-anya/db';
|
|
2
|
+
import { TaskStatus } from '@team-anya/core';
|
|
3
|
+
/**
|
|
4
|
+
* 查询任务状态和历史
|
|
5
|
+
*
|
|
6
|
+
* 可按 task_id 或 status 过滤。不传参数时返回所有活跃任务。
|
|
7
|
+
*/
|
|
8
|
+
export async function taskLookup(db, input) {
|
|
9
|
+
let tasks;
|
|
10
|
+
if (input.task_id) {
|
|
11
|
+
const task = getTask(db, input.task_id);
|
|
12
|
+
tasks = task ? [task] : [];
|
|
13
|
+
}
|
|
14
|
+
else if (input.status) {
|
|
15
|
+
tasks = getTasksByStatus(db, input.status);
|
|
16
|
+
}
|
|
17
|
+
else {
|
|
18
|
+
// 默认返回活跃任务
|
|
19
|
+
tasks = [
|
|
20
|
+
...getTasksByStatus(db, TaskStatus.IN_PROGRESS),
|
|
21
|
+
...getTasksByStatus(db, TaskStatus.READY),
|
|
22
|
+
...getTasksByStatus(db, TaskStatus.NEED_CLARIFICATION),
|
|
23
|
+
...getTasksByStatus(db, TaskStatus.DELIVERING),
|
|
24
|
+
...getTasksByStatus(db, TaskStatus.BLOCKED),
|
|
25
|
+
];
|
|
26
|
+
}
|
|
27
|
+
return {
|
|
28
|
+
tasks: tasks.map(t => ({
|
|
29
|
+
task_id: t.task_id,
|
|
30
|
+
title: t.title,
|
|
31
|
+
status: t.status,
|
|
32
|
+
assignee: t.assignee,
|
|
33
|
+
created_at: t.created_at,
|
|
34
|
+
pr_url: t.pr_url ?? null,
|
|
35
|
+
})),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=task-lookup.js.map
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getTask, updateTask, insertAuditEvent, getMessageLogBySourceRef, } from '@team-anya/db';
|
|
4
|
+
import { TaskStatus } from '@team-anya/core';
|
|
5
|
+
/**
|
|
6
|
+
* 验收审核(合并 approve_delivery + reject_delivery)
|
|
7
|
+
*
|
|
8
|
+
* 支持三级判断:
|
|
9
|
+
* - PASS: 验收通过 → DONE + 通知人类
|
|
10
|
+
* - REVISE: 打回返工 → retry_count++ + feedback + 重派
|
|
11
|
+
* - ESCALATE: 升级 → BLOCKED + 通知人类
|
|
12
|
+
*/
|
|
13
|
+
export async function taskReview(deps, input) {
|
|
14
|
+
const { db, workspacePath, dispatcher, logger } = deps;
|
|
15
|
+
const task = getTask(db, input.task_id);
|
|
16
|
+
if (!task) {
|
|
17
|
+
return {
|
|
18
|
+
task_id: input.task_id,
|
|
19
|
+
judgment: input.judgment,
|
|
20
|
+
success: false,
|
|
21
|
+
message: '任务不存在',
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
switch (input.judgment) {
|
|
25
|
+
case 'PASS': {
|
|
26
|
+
updateTask(db, input.task_id, { status: TaskStatus.DONE });
|
|
27
|
+
insertAuditEvent(db, {
|
|
28
|
+
event_type: 'delivery_approved',
|
|
29
|
+
actor: 'loid',
|
|
30
|
+
task_id: input.task_id,
|
|
31
|
+
summary: `Loid 验收通过: ${input.summary ?? '无评语'}`,
|
|
32
|
+
});
|
|
33
|
+
// 通知人类
|
|
34
|
+
let notified = false;
|
|
35
|
+
if (input.notify_human !== false && deps.channelSend) {
|
|
36
|
+
try {
|
|
37
|
+
let chatId = null;
|
|
38
|
+
if (task.source_ref) {
|
|
39
|
+
const sourceMsg = getMessageLogBySourceRef(db, task.source_ref);
|
|
40
|
+
chatId = sourceMsg?.chat_id ?? null;
|
|
41
|
+
}
|
|
42
|
+
if (chatId) {
|
|
43
|
+
const prPart = task.pr_url ? `,PR: ${task.pr_url}` : '';
|
|
44
|
+
await deps.channelSend(`feishu://${chatId}`, `${input.task_id} 搞定了——${input.summary ?? task.title}${prPart}`, { task_id: input.task_id });
|
|
45
|
+
notified = true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
logger?.error(`[anya:pipeline] [Loid] 任务完成通知发送失败 (${input.task_id}):`, err);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
logger?.info(`[anya:pipeline] [Loid] 验收通过 ${input.task_id}${notified ? ' (已通知)' : ''}`);
|
|
53
|
+
return { task_id: input.task_id, judgment: 'PASS', success: true, notified };
|
|
54
|
+
}
|
|
55
|
+
case 'REVISE': {
|
|
56
|
+
const retryCount = (task.retry_count ?? 0) + 1;
|
|
57
|
+
const maxRetries = task.max_retries ?? 3;
|
|
58
|
+
if (retryCount > maxRetries) {
|
|
59
|
+
// 超过最大重试次数,标记为 BLOCKED
|
|
60
|
+
updateTask(db, input.task_id, {
|
|
61
|
+
status: TaskStatus.BLOCKED,
|
|
62
|
+
blocked_reason: `超过最大返工次数 (${maxRetries}): ${input.feedback ?? '未说明'}`,
|
|
63
|
+
blocked_since: new Date().toISOString(),
|
|
64
|
+
retry_count: retryCount,
|
|
65
|
+
});
|
|
66
|
+
insertAuditEvent(db, {
|
|
67
|
+
event_type: 'delivery_rejected_max_retries',
|
|
68
|
+
actor: 'loid',
|
|
69
|
+
task_id: input.task_id,
|
|
70
|
+
summary: `Loid 打回但已达上限 (${retryCount}/${maxRetries}): ${input.feedback ?? ''}`,
|
|
71
|
+
});
|
|
72
|
+
logger?.info(`[anya:pipeline] [Loid] 打回 ${input.task_id} (已达上限 ${retryCount}/${maxRetries}, BLOCKED)`);
|
|
73
|
+
return {
|
|
74
|
+
task_id: input.task_id,
|
|
75
|
+
judgment: 'REVISE',
|
|
76
|
+
success: true,
|
|
77
|
+
retry_count: retryCount,
|
|
78
|
+
max_retries: maxRetries,
|
|
79
|
+
blocked: true,
|
|
80
|
+
message: `已达最大返工次数 ${maxRetries},任务已阻塞`,
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
// 更新任务状态和反馈
|
|
84
|
+
updateTask(db, input.task_id, {
|
|
85
|
+
status: TaskStatus.READY,
|
|
86
|
+
retry_count: retryCount,
|
|
87
|
+
blocked_reason: null,
|
|
88
|
+
blocked_since: null,
|
|
89
|
+
});
|
|
90
|
+
// 写返工反馈
|
|
91
|
+
const taskDir = join(workspacePath, 'yor', input.task_id);
|
|
92
|
+
await mkdir(taskDir, { recursive: true });
|
|
93
|
+
const feedbackContent = `# 返工反馈 (第 ${retryCount} 次)\n\n## 打回原因\n${input.feedback ?? '未说明'}\n\n## 修改意见\n${input.feedback ?? '无'}\n`;
|
|
94
|
+
await writeFile(join(taskDir, `feedback-${retryCount}.md`), feedbackContent, 'utf-8');
|
|
95
|
+
insertAuditEvent(db, {
|
|
96
|
+
event_type: 'delivery_rejected',
|
|
97
|
+
actor: 'loid',
|
|
98
|
+
task_id: input.task_id,
|
|
99
|
+
summary: `Loid 打回 (${retryCount}/${maxRetries}): ${input.feedback ?? ''}`,
|
|
100
|
+
detail: JSON.stringify({ feedback: input.feedback }),
|
|
101
|
+
});
|
|
102
|
+
// 重新派工
|
|
103
|
+
if (dispatcher) {
|
|
104
|
+
await dispatcher.dispatch(input.task_id);
|
|
105
|
+
}
|
|
106
|
+
logger?.info(`[anya:pipeline] [Loid] 打回 ${input.task_id} → 返工 (${retryCount}/${maxRetries})`);
|
|
107
|
+
return {
|
|
108
|
+
task_id: input.task_id,
|
|
109
|
+
judgment: 'REVISE',
|
|
110
|
+
success: true,
|
|
111
|
+
retry_count: retryCount,
|
|
112
|
+
max_retries: maxRetries,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
case 'ESCALATE': {
|
|
116
|
+
updateTask(db, input.task_id, {
|
|
117
|
+
status: TaskStatus.BLOCKED,
|
|
118
|
+
blocked_reason: `需人类决策: ${input.escalation_note ?? '未说明'}`,
|
|
119
|
+
blocked_since: new Date().toISOString(),
|
|
120
|
+
});
|
|
121
|
+
insertAuditEvent(db, {
|
|
122
|
+
event_type: 'delivery_escalated',
|
|
123
|
+
actor: 'loid',
|
|
124
|
+
task_id: input.task_id,
|
|
125
|
+
summary: `Loid 升级: ${input.escalation_note ?? '需人类介入'}`,
|
|
126
|
+
detail: JSON.stringify({ escalation_note: input.escalation_note }),
|
|
127
|
+
});
|
|
128
|
+
// 通过通道向人类发升级通知
|
|
129
|
+
let notified = false;
|
|
130
|
+
if (deps.channelSend) {
|
|
131
|
+
try {
|
|
132
|
+
let chatId = null;
|
|
133
|
+
if (task.source_ref) {
|
|
134
|
+
const sourceMsg = getMessageLogBySourceRef(db, task.source_ref);
|
|
135
|
+
chatId = sourceMsg?.chat_id ?? null;
|
|
136
|
+
}
|
|
137
|
+
if (chatId) {
|
|
138
|
+
await deps.channelSend(`feishu://${chatId}`, `${input.task_id} 需要你来决定——${input.escalation_note ?? '详见任务'}`, { task_id: input.task_id, intent: 'escalation' });
|
|
139
|
+
notified = true;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
catch (err) {
|
|
143
|
+
logger?.error(`[anya:pipeline] [Loid] 升级通知发送失败 (${input.task_id}):`, err);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
logger?.info(`[anya:pipeline] [Loid] 升级 ${input.task_id}: ${input.escalation_note ?? '需人类决策'}${notified ? ' (已通知)' : ''}`);
|
|
147
|
+
return { task_id: input.task_id, judgment: 'ESCALATE', success: true, notified };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
//# sourceMappingURL=task-review.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export async function workspaceCleanup(deps, input) {
|
|
2
|
+
const { worktreeManager, logger } = deps;
|
|
3
|
+
logger?.info(`[anya:pipeline] [Loid] workspace.cleanup ${input.task_id}`);
|
|
4
|
+
await worktreeManager.cleanup(input.task_id, input.project_id);
|
|
5
|
+
return { task_id: input.task_id, cleaned: true };
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=workspace-cleanup.js.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
export async function workspaceInfo(deps, input) {
|
|
2
|
+
const { worktreeManager } = deps;
|
|
3
|
+
await worktreeManager.loadConfig();
|
|
4
|
+
const product = worktreeManager.findProduct(input.project_id);
|
|
5
|
+
if (product) {
|
|
6
|
+
return {
|
|
7
|
+
type: 'product',
|
|
8
|
+
project_id: product.productId,
|
|
9
|
+
name: product.name,
|
|
10
|
+
description: product.description,
|
|
11
|
+
repos: product.repos.map(r => ({
|
|
12
|
+
name: r.name,
|
|
13
|
+
gitUrl: r.gitUrl,
|
|
14
|
+
repoPath: worktreeManager.resolveRepoPath(r.repoPath, r.name),
|
|
15
|
+
defaultBranch: r.defaultBranch,
|
|
16
|
+
})),
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
const project = worktreeManager.findProject(input.project_id);
|
|
20
|
+
if (project) {
|
|
21
|
+
return {
|
|
22
|
+
type: 'project',
|
|
23
|
+
project_id: project.projectId,
|
|
24
|
+
gitUrl: project.gitUrl,
|
|
25
|
+
repoPath: worktreeManager.resolveRepoPath(project.repoPath, project.projectId),
|
|
26
|
+
defaultBranch: project.defaultBranch,
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
return { type: 'not_found', project_id: input.project_id };
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=workspace-info.js.map
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export async function workspacePrepare(deps, input) {
|
|
2
|
+
const { worktreeManager, logger } = deps;
|
|
3
|
+
logger?.info(`[anya:pipeline] [Loid] workspace.prepare ${input.task_id} | project=${input.project_id ?? 'none'}`);
|
|
4
|
+
const result = await worktreeManager.prepare(input.task_id, input.project_id);
|
|
5
|
+
return {
|
|
6
|
+
path: result.workingDir,
|
|
7
|
+
branch: result.branch,
|
|
8
|
+
type: result.mode,
|
|
9
|
+
repos: result.repos,
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
//# sourceMappingURL=workspace-prepare.js.map
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export function yorApprove(deps, input) {
|
|
2
|
+
const { yorOrchestrator, logger } = deps;
|
|
3
|
+
logger?.info(`[anya:pipeline] [Loid] yor.approve task=${input.task_id}`);
|
|
4
|
+
const message = input.message ?? '审核通过,任务完成。你可以做最后的清理工作。';
|
|
5
|
+
yorOrchestrator.approveAndShutdown(input.task_id, message);
|
|
6
|
+
return { task_id: input.task_id, approved: true };
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=yor-approve.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export async function yorKill(deps, input) {
|
|
2
|
+
const { yorOrchestrator, logger } = deps;
|
|
3
|
+
logger?.info(`[anya:pipeline] [Loid] yor.kill task=${input.task_id}`);
|
|
4
|
+
const result = await yorOrchestrator.kill(input.task_id);
|
|
5
|
+
return { task_id: input.task_id, killed: result.success };
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=yor-kill.js.map
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export function yorRework(deps, input) {
|
|
2
|
+
const { yorOrchestrator, logger } = deps;
|
|
3
|
+
logger?.info(`[anya:pipeline] [Loid] yor.rework task=${input.task_id}`);
|
|
4
|
+
yorOrchestrator.sendRework(input.task_id, input.feedback);
|
|
5
|
+
return { task_id: input.task_id, sent: true };
|
|
6
|
+
}
|
|
7
|
+
//# sourceMappingURL=yor-rework.js.map
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export async function yorSpawn(deps, input) {
|
|
2
|
+
const { yorOrchestrator, logger } = deps;
|
|
3
|
+
logger?.info(`[anya:pipeline] [Loid] yor.spawn ${input.task_id} | dir=${input.working_dir}`);
|
|
4
|
+
const result = await yorOrchestrator.spawnAndExecute({
|
|
5
|
+
taskId: input.task_id,
|
|
6
|
+
workingDir: input.working_dir,
|
|
7
|
+
briefPath: input.brief_path,
|
|
8
|
+
env: input.env,
|
|
9
|
+
});
|
|
10
|
+
return {
|
|
11
|
+
instance_id: result.instanceId,
|
|
12
|
+
task_id: input.task_id,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=yor-spawn.js.map
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
/**
|
|
7
|
+
* code.lint - 运行 lint 检查
|
|
8
|
+
*
|
|
9
|
+
* 自动检测包管理器,运行 lint 脚本。
|
|
10
|
+
* 返回 lint 结果。
|
|
11
|
+
*/
|
|
12
|
+
export async function codeLint(input) {
|
|
13
|
+
const { working_dir } = input;
|
|
14
|
+
let command;
|
|
15
|
+
if (existsSync(join(working_dir, 'pnpm-lock.yaml')) || existsSync(join(working_dir, 'pnpm-workspace.yaml'))) {
|
|
16
|
+
command = 'pnpm lint';
|
|
17
|
+
}
|
|
18
|
+
else if (existsSync(join(working_dir, 'yarn.lock'))) {
|
|
19
|
+
command = 'yarn lint';
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
command = 'npm run lint';
|
|
23
|
+
}
|
|
24
|
+
try {
|
|
25
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
26
|
+
cwd: working_dir,
|
|
27
|
+
timeout: 3 * 60 * 1000, // 3 分钟超时
|
|
28
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
29
|
+
});
|
|
30
|
+
const output = [stdout, stderr].filter(Boolean).join('\n');
|
|
31
|
+
return { success: true, passed: true, output };
|
|
32
|
+
}
|
|
33
|
+
catch (err) {
|
|
34
|
+
const output = [err.stdout, err.stderr].filter(Boolean).join('\n');
|
|
35
|
+
const isLintFailure = err.code !== undefined && err.code !== null;
|
|
36
|
+
if (isLintFailure) {
|
|
37
|
+
return { success: true, passed: false, output };
|
|
38
|
+
}
|
|
39
|
+
return {
|
|
40
|
+
success: false,
|
|
41
|
+
passed: false,
|
|
42
|
+
output: output || '',
|
|
43
|
+
error: err.message,
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=code-lint.js.map
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { exec } from 'node:child_process';
|
|
2
|
+
import { promisify } from 'node:util';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
/**
|
|
7
|
+
* code.test - 运行测试
|
|
8
|
+
*
|
|
9
|
+
* 默认运行 pnpm test 或 npm test,可指定自定义测试命令。
|
|
10
|
+
* 返回测试输出和通过/失败状态。
|
|
11
|
+
*/
|
|
12
|
+
export async function codeTest(input) {
|
|
13
|
+
const { working_dir } = input;
|
|
14
|
+
let command = input.command;
|
|
15
|
+
if (!command) {
|
|
16
|
+
// 自动检测包管理器
|
|
17
|
+
if (existsSync(join(working_dir, 'pnpm-lock.yaml')) || existsSync(join(working_dir, 'pnpm-workspace.yaml'))) {
|
|
18
|
+
command = 'pnpm test';
|
|
19
|
+
}
|
|
20
|
+
else if (existsSync(join(working_dir, 'yarn.lock'))) {
|
|
21
|
+
command = 'yarn test';
|
|
22
|
+
}
|
|
23
|
+
else {
|
|
24
|
+
command = 'npm test';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
try {
|
|
28
|
+
const { stdout, stderr } = await execAsync(command, {
|
|
29
|
+
cwd: working_dir,
|
|
30
|
+
timeout: 5 * 60 * 1000, // 5 分钟超时
|
|
31
|
+
maxBuffer: 10 * 1024 * 1024, // 10MB
|
|
32
|
+
});
|
|
33
|
+
const output = [stdout, stderr].filter(Boolean).join('\n');
|
|
34
|
+
return { success: true, passed: true, output };
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
// 测试失败但命令执行了(exit code !== 0)
|
|
38
|
+
const output = [err.stdout, err.stderr].filter(Boolean).join('\n');
|
|
39
|
+
const isTestFailure = err.code !== undefined && err.code !== null;
|
|
40
|
+
if (isTestFailure) {
|
|
41
|
+
return { success: true, passed: false, output };
|
|
42
|
+
}
|
|
43
|
+
// 命令本身执行失败
|
|
44
|
+
return {
|
|
45
|
+
success: false,
|
|
46
|
+
passed: false,
|
|
47
|
+
output: output || '',
|
|
48
|
+
error: err.message,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
//# sourceMappingURL=code-test.js.map
|