loop-controller.js
3.72 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
/**
* DevLoop · LoopController (P0 最小骨架)
*
* 在 P0 阶段只暴露两个对外接口:
* - createProject({ company_id, mode, target_path }) — 调 path-resolver + gitVault.init
* - commitAfterAgent(pid, agentName, summary) — 每次 Agent 完成后 auto-commit
*
* 完整的 dispatch / unit fsm / retry 逻辑留待 P1 实现。
*
* 规格:
* - docs/prd/newfunction/_spec/sdd-process.md · P-10 GitCommit
* - docs/prd/newfunction/_spec/arch-git-versioning.md
* - docs/prd/newfunction/_spec/arch-project-root.md
*/
const fs = require('fs');
const path = require('path');
const pathResolver = require('../../../services/devloop/path-resolver');
const gitVault = require('../../../services/devloop/git-vault');
const timelineLogger = require('../../../services/devloop/timeline-logger');
/**
* 新建项目:
* 1. allocateProjectRoot (sandbox / linked-new)
* 2. 写 project.json (最小元数据)
* 3. gitVault.init + 写 .gitignore (gitVault 内置 DEFAULT_GITIGNORE)
* 4. 首次 commit "chore: init project"
*/
async function createProject({ company_id, mode = 'sandbox', target_path, idea = '' }) {
if (!company_id) throw new Error('company_id required');
const { pid, root_path } = pathResolver.allocateProjectRoot({
company_id, mode, target_path,
});
// project.json
const meta = {
pid,
company_id,
mode,
created_at: new Date().toISOString(),
idea_summary: (idea || '').slice(0, 200),
};
fs.writeFileSync(
pathResolver.resolveProjectJson(pid),
JSON.stringify(meta, null, 2)
);
// git init (内置首个 init commit) — gitVault.init 已自己做 commit
await gitVault.init(pid, {
summary: idea ? idea.slice(0, 80) : `init ${pid}`,
});
// timeline (写在 init 之后,作为"项目创建"事件)
try {
timelineLogger.append(pid, {
source: 'system',
kind: 'project.created',
ref: pid,
summary: `Project ${pid} created (${mode})`,
payload: { company_id, mode, root_path },
});
} catch (e) {
console.warn('[LoopController] timeline append failed:', e.message);
}
// 把 project.json + timeline.ndjson 一并提交进第二个 commit (工作树留干净)
const firstCommit = await gitVault.commit(pid, {
kind: 'chore',
summary: `chore: project metadata + first timeline entry for ${pid}`,
author: { name: 'devloop-system', email: 'devloop@local' },
});
return { pid, root_path, mode, first_commit: firstCommit };
}
/**
* Agent 结束后,把工作树新文件 auto-commit。
* 调用方一般在 stream-executor 完成或 agent-manager.post-task-success 时调。
*
* @param {string} pid
* @param {string} agentName (devloop-pm / devloop-architect / ...)
* @param {string} summary 一句话描述本次产出 (做 commit message 副标题)
* @returns {{ sha, files_changed }} 或 null (无变更时)
*/
async function commitAfterAgent(pid, agentName, summary = '') {
// commit kind 推断
const kindMap = {
'devloop-pm': 'prd',
'devloop-architect': 'sdd',
'devloop-coder': 'code',
'devloop-tester': 'code',
'devloop-integrator': 'integ',
'devloop-triager': 'triage',
};
const kind = kindMap[agentName] || 'chore';
const msg = `${kind}(${agentName}): ${summary || 'agent update'}`;
const commit = await gitVault.commit(pid, {
kind,
summary: msg,
author: { name: agentName, email: `${agentName}@devloop.local` },
});
if (commit.skipped) {
return null;
}
try {
timelineLogger.append(pid, {
source: agentName,
kind: 'git.commit',
ref: commit.sha,
summary: msg.slice(0, 200),
payload: { sha: commit.sha },
});
} catch { /* swallow */ }
return commit;
}
module.exports = {
createProject,
commitAfterAgent,
};