u-foo 1.7.5 → 1.8.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.
Files changed (41) hide show
  1. package/README.md +9 -1
  2. package/README.zh-CN.md +9 -1
  3. package/bin/ufoo.js +4 -2
  4. package/package.json +1 -1
  5. package/src/agent/cliRunner.js +3 -2
  6. package/src/agent/ucodeBootstrap.js +5 -3
  7. package/src/agent/ufooAgent.js +184 -5
  8. package/src/assistant/constants.js +1 -1
  9. package/src/chat/commandExecutor.js +98 -3
  10. package/src/chat/commands.js +7 -0
  11. package/src/chat/completionController.js +40 -0
  12. package/src/chat/daemonMessageRouter.js +21 -1
  13. package/src/chat/dashboardKeyController.js +55 -3
  14. package/src/chat/dashboardView.js +31 -5
  15. package/src/chat/index.js +152 -36
  16. package/src/chat/inputListenerController.js +14 -0
  17. package/src/chat/inputSubmitHandler.js +9 -5
  18. package/src/chat/transientAgentState.js +64 -0
  19. package/src/cli/groupCoreCommands.js +21 -12
  20. package/src/cli.js +23 -1
  21. package/src/daemon/groupOrchestrator.js +581 -97
  22. package/src/daemon/index.js +418 -3
  23. package/src/daemon/ops.js +25 -7
  24. package/src/daemon/promptLoop.js +16 -0
  25. package/src/daemon/promptRequest.js +126 -2
  26. package/src/daemon/reporting.js +18 -0
  27. package/src/daemon/soloBootstrap.js +435 -0
  28. package/src/daemon/status.js +5 -1
  29. package/src/globalMode.js +33 -0
  30. package/src/group/bootstrap.js +157 -0
  31. package/src/group/promptProfiles.js +646 -0
  32. package/src/group/templateValidation.js +99 -0
  33. package/src/group/validateTemplate.js +36 -5
  34. package/src/init/index.js +13 -7
  35. package/src/report/store.js +6 -0
  36. package/src/shared/eventContract.js +1 -0
  37. package/templates/groups/{dev-basic.json → build-lane.json} +38 -34
  38. package/templates/groups/product-discovery.json +79 -0
  39. package/templates/groups/ui-polish.json +87 -0
  40. package/templates/groups/verify-ship.json +79 -0
  41. package/templates/groups/research-quick.json +0 -49
@@ -0,0 +1,99 @@
1
+ "use strict";
2
+
3
+ const { loadPromptProfileRegistry } = require("./promptProfiles");
4
+ const { resolveTemplateReference } = require("./templates");
5
+ const { validateTemplate } = require("./validateTemplate");
6
+
7
+ function asTrimmedString(value) {
8
+ if (typeof value !== "string") return "";
9
+ return value.trim();
10
+ }
11
+
12
+ function formatResolveErrors(errors = []) {
13
+ if (!Array.isArray(errors) || errors.length === 0) return "";
14
+ return errors
15
+ .map((item) => `${item.filePath}: ${item.error || item.message || "unknown error"}`)
16
+ .join("; ");
17
+ }
18
+
19
+ function resolveTemplateTarget(projectRoot, target, options = {}) {
20
+ const rawTarget = asTrimmedString(target);
21
+ if (!rawTarget) {
22
+ return {
23
+ ok: false,
24
+ error: "template target is required",
25
+ entry: null,
26
+ resolveErrors: [],
27
+ };
28
+ }
29
+
30
+ const resolved = resolveTemplateReference(projectRoot, rawTarget, {
31
+ allowPath: options.allowPath !== false,
32
+ cwd: options.cwd || projectRoot,
33
+ ...(options.templatesOptions || {}),
34
+ });
35
+ if (!resolved.entry) {
36
+ const details = formatResolveErrors(resolved.errors || []);
37
+ return {
38
+ ok: false,
39
+ error: details
40
+ ? `failed to load template "${rawTarget}": ${details}`
41
+ : `template not found: ${rawTarget}`,
42
+ entry: null,
43
+ resolveErrors: resolved.errors || [],
44
+ };
45
+ }
46
+
47
+ return {
48
+ ok: true,
49
+ error: "",
50
+ entry: resolved.entry,
51
+ resolveErrors: resolved.errors || [],
52
+ };
53
+ }
54
+
55
+ function validateTemplateEntry(projectRoot, entry, options = {}) {
56
+ const promptRegistry = loadPromptProfileRegistry(projectRoot, options.promptProfilesOptions || {});
57
+ if (promptRegistry.errors.length > 0) {
58
+ return {
59
+ ok: false,
60
+ error: "prompt profile registry invalid",
61
+ errors: promptRegistry.errors.slice(),
62
+ entry,
63
+ promptRegistry,
64
+ promptProfiles: [],
65
+ };
66
+ }
67
+
68
+ const result = validateTemplate(entry.data, { promptProfileRegistry: promptRegistry });
69
+ return {
70
+ ok: result.ok,
71
+ error: result.ok ? "" : "template validation failed",
72
+ errors: result.errors || [],
73
+ entry,
74
+ promptRegistry,
75
+ promptProfiles: result.promptProfiles || [],
76
+ };
77
+ }
78
+
79
+ function validateTemplateTarget(projectRoot, target, options = {}) {
80
+ const resolved = resolveTemplateTarget(projectRoot, target, options);
81
+ if (!resolved.ok || !resolved.entry) {
82
+ return {
83
+ ok: false,
84
+ error: resolved.error,
85
+ errors: resolved.resolveErrors || [],
86
+ entry: null,
87
+ promptRegistry: null,
88
+ promptProfiles: [],
89
+ };
90
+ }
91
+ return validateTemplateEntry(projectRoot, resolved.entry, options);
92
+ }
93
+
94
+ module.exports = {
95
+ formatResolveErrors,
96
+ resolveTemplateTarget,
97
+ validateTemplateEntry,
98
+ validateTemplateTarget,
99
+ };
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
 
3
- const ALLOWED_AGENT_TYPES = new Set(["codex", "claude", "ucode"]);
3
+ const ALLOWED_AGENT_TYPES = new Set(["auto", "codex", "claude", "ucode"]);
4
+ const { resolvePromptProfileReference } = require("./promptProfiles");
4
5
 
5
6
  function isPlainObject(value) {
6
7
  return Boolean(value) && typeof value === "object" && !Array.isArray(value);
@@ -93,12 +94,14 @@ function detectDependsCycle(agents = []) {
93
94
  return null;
94
95
  }
95
96
 
96
- function validateTemplate(doc) {
97
+ function validateTemplate(doc, options = {}) {
97
98
  const errors = [];
99
+ const promptProfiles = [];
100
+ const promptProfileRegistry = options.promptProfileRegistry || null;
98
101
 
99
102
  if (!isPlainObject(doc)) {
100
103
  addError(errors, "$", "template document must be a JSON object");
101
- return { ok: false, errors };
104
+ return { ok: false, errors, promptProfiles };
102
105
  }
103
106
 
104
107
  if (!Number.isInteger(doc.schema_version) || doc.schema_version < 1) {
@@ -121,7 +124,7 @@ function validateTemplate(doc) {
121
124
 
122
125
  if (!Array.isArray(doc.agents) || doc.agents.length === 0) {
123
126
  addError(errors, "agents", "agents must be a non-empty array");
124
- return { ok: false, errors };
127
+ return { ok: false, errors, promptProfiles };
125
128
  }
126
129
 
127
130
  const knownNicknames = new Set();
@@ -156,6 +159,34 @@ function validateTemplate(doc) {
156
159
  if (!Number.isInteger(agent.startup_order) || agent.startup_order < 0) {
157
160
  addError(errors, `${basePath}.startup_order`, "startup_order must be an integer >= 0");
158
161
  }
162
+
163
+ if (agent.prompt_profile !== undefined) {
164
+ const requestedProfile = asTrimmedString(agent.prompt_profile);
165
+ if (!requestedProfile) {
166
+ addError(errors, `${basePath}.prompt_profile`, "prompt_profile must be a non-empty string");
167
+ } else if (promptProfileRegistry) {
168
+ const resolvedProfile = resolvePromptProfileReference(promptProfileRegistry, requestedProfile);
169
+ if (!resolvedProfile) {
170
+ addError(
171
+ errors,
172
+ `${basePath}.prompt_profile`,
173
+ `unknown prompt_profile "${requestedProfile}"`
174
+ );
175
+ } else {
176
+ promptProfiles.push({
177
+ index: i,
178
+ agent_id: asTrimmedString(agent.id),
179
+ nickname,
180
+ requested_profile: requestedProfile,
181
+ resolved_profile: resolvedProfile.id,
182
+ display_name: resolvedProfile.display_name || resolvedProfile.id,
183
+ short_name: resolvedProfile.short_name || "",
184
+ profile_source: resolvedProfile.source || "",
185
+ deprecated: resolvedProfile.deprecated === true,
186
+ });
187
+ }
188
+ }
189
+ }
159
190
  }
160
191
 
161
192
  for (let i = 0; i < doc.agents.length; i += 1) {
@@ -225,7 +256,7 @@ function validateTemplate(doc) {
225
256
  );
226
257
  }
227
258
 
228
- return { ok: errors.length === 0, errors };
259
+ return { ok: errors.length === 0, errors, promptProfiles };
229
260
  }
230
261
 
231
262
  module.exports = {
package/src/init/index.js CHANGED
@@ -19,20 +19,25 @@ class UfooInit {
19
19
  async init(options = {}) {
20
20
  const modules = (options.modules || "context").split(",");
21
21
  const project = options.project || process.cwd();
22
+ const controllerMode = options.controllerMode === true;
22
23
 
23
24
  console.log("=== ufoo init ===");
24
25
  console.log(`Project directory: ${project}`);
25
26
  console.log(`Modules: ${modules.join(", ")}`);
26
27
  console.log();
27
28
 
28
- // 确保 AGENTS.md 和 CLAUDE.md 存在
29
- this.ensureAgentsFiles(project);
29
+ if (!controllerMode) {
30
+ // 确保 AGENTS.md 和 CLAUDE.md 存在
31
+ this.ensureAgentsFiles(project);
32
+ }
30
33
 
31
34
  // 初始化核心
32
- this.initCore(project);
35
+ this.initCore(project, { controllerMode });
33
36
 
34
- // 初始化 AGENTS.md 模板
35
- this.injectAgentsTemplate(project);
37
+ if (!controllerMode) {
38
+ // 初始化 AGENTS.md 模板
39
+ this.injectAgentsTemplate(project);
40
+ }
36
41
 
37
42
  // 初始化各模块
38
43
  for (const module of modules) {
@@ -82,8 +87,9 @@ class UfooInit {
82
87
  /**
83
88
  * 初始化核心 .ufoo 目录
84
89
  */
85
- initCore(project) {
90
+ initCore(project, options = {}) {
86
91
  console.log("[core] Initializing .ufoo core...");
92
+ const controllerMode = options.controllerMode === true;
87
93
 
88
94
  const ufooDir = path.join(project, ".ufoo");
89
95
  if (!fs.existsSync(ufooDir)) {
@@ -94,7 +100,7 @@ class UfooInit {
94
100
  const docsLink = path.join(ufooDir, "docs");
95
101
  const projectDocs = path.join(project, "docs");
96
102
 
97
- if (fs.existsSync(projectDocs)) {
103
+ if (!controllerMode && fs.existsSync(projectDocs)) {
98
104
  const linkStat = this.safeLstat(docsLink);
99
105
  if (linkStat) {
100
106
  fs.unlinkSync(docsLink);
@@ -216,6 +216,11 @@ function listControllerInboxEntries(projectRoot, controllerId = "ufoo-agent", op
216
216
  return rows.slice(rows.length - num);
217
217
  }
218
218
 
219
+ function countControllerInboxEntries(projectRoot, controllerId = "ufoo-agent") {
220
+ const file = getControllerInboxFile(projectRoot, controllerId);
221
+ return parseJsonLines(file).length;
222
+ }
223
+
219
224
  function clearControllerInbox(projectRoot, controllerId = "ufoo-agent") {
220
225
  const file = getControllerInboxFile(projectRoot, controllerId);
221
226
  try {
@@ -326,6 +331,7 @@ module.exports = {
326
331
  getControllerInboxFile,
327
332
  appendControllerInboxEntry,
328
333
  listControllerInboxEntries,
334
+ countControllerInboxEntries,
329
335
  clearControllerInbox,
330
336
  consumeControllerInboxEntries,
331
337
  };
@@ -17,6 +17,7 @@ const IPC_REQUEST_TYPES = {
17
17
  REGISTER_AGENT: "register_agent",
18
18
  AGENT_READY: "agent_ready",
19
19
  AGENT_REPORT: "agent_report",
20
+ ASSIGN_ROLE: "assign_role",
20
21
  };
21
22
 
22
23
  const IPC_RESPONSE_TYPES = {
@@ -1,9 +1,9 @@
1
1
  {
2
2
  "schema_version": 1,
3
3
  "template": {
4
- "id": "software-dev-basic",
5
- "alias": "dev-basic",
6
- "name": "Software Dev Basic"
4
+ "id": "build-lane",
5
+ "alias": "build-lane",
6
+ "name": "Build Lane"
7
7
  },
8
8
  "defaults": {
9
9
  "launch_mode": "auto",
@@ -11,68 +11,72 @@
11
11
  },
12
12
  "agents": [
13
13
  {
14
- "id": "pm",
15
- "nickname": "pm",
16
- "type": "codex",
17
- "role": "task coordinator",
18
- "prompt_profile": "task-breakdown",
14
+ "id": "architect",
15
+ "nickname": "architect",
16
+ "type": "auto",
17
+ "role": "define slices and architecture constraints",
18
+ "prompt_profile": "system-architect",
19
19
  "accept_from": [],
20
- "report_to": [],
20
+ "report_to": [
21
+ "builder",
22
+ "reviewer"
23
+ ],
21
24
  "startup_order": 1,
22
25
  "depends_on": []
23
26
  },
24
27
  {
25
- "id": "architect",
26
- "nickname": "architect",
27
- "type": "claude",
28
- "role": "system architect",
29
- "prompt_profile": "architecture-review",
28
+ "id": "builder",
29
+ "nickname": "builder",
30
+ "type": "auto",
31
+ "role": "implement approved slices",
32
+ "prompt_profile": "implementation-lead",
30
33
  "accept_from": [
31
- "pm"
34
+ "architect",
35
+ "reviewer"
32
36
  ],
33
37
  "report_to": [
34
- "pm"
38
+ "reviewer"
35
39
  ],
36
40
  "startup_order": 2,
37
41
  "depends_on": [
38
- "pm"
42
+ "architect"
39
43
  ]
40
44
  },
41
45
  {
42
- "id": "builder",
43
- "nickname": "builder",
44
- "type": "codex",
45
- "role": "implementation engineer",
46
- "prompt_profile": "code-implement",
46
+ "id": "reviewer",
47
+ "nickname": "reviewer",
48
+ "type": "auto",
49
+ "role": "review correctness and risk",
50
+ "prompt_profile": "review-critic",
47
51
  "accept_from": [
48
- "pm",
49
- "architect"
52
+ "architect",
53
+ "builder"
50
54
  ],
51
55
  "report_to": [
52
- "pm"
56
+ "builder"
53
57
  ],
54
58
  "startup_order": 3,
55
59
  "depends_on": [
56
- "pm",
57
- "architect"
60
+ "architect",
61
+ "builder"
58
62
  ]
59
63
  }
60
64
  ],
61
65
  "edges": [
62
66
  {
63
- "from": "pm",
64
- "to": "architect",
67
+ "from": "architect",
68
+ "to": "builder",
65
69
  "kind": "task"
66
70
  },
67
71
  {
68
- "from": "pm",
69
- "to": "builder",
70
- "kind": "task"
72
+ "from": "builder",
73
+ "to": "reviewer",
74
+ "kind": "review"
71
75
  },
72
76
  {
73
- "from": "architect",
77
+ "from": "reviewer",
74
78
  "to": "builder",
75
- "kind": "review"
79
+ "kind": "task"
76
80
  }
77
81
  ]
78
82
  }
@@ -0,0 +1,79 @@
1
+ {
2
+ "schema_version": 1,
3
+ "template": {
4
+ "id": "product-discovery",
5
+ "alias": "product-discovery",
6
+ "name": "Product Discovery"
7
+ },
8
+ "defaults": {
9
+ "launch_mode": "auto",
10
+ "start_timeout_ms": 15000
11
+ },
12
+ "agents": [
13
+ {
14
+ "id": "facilitator",
15
+ "nickname": "facilitator",
16
+ "type": "auto",
17
+ "role": "clarify the real problem and define a narrow wedge",
18
+ "prompt_profile": "discovery-facilitator",
19
+ "accept_from": [],
20
+ "report_to": [
21
+ "challenger",
22
+ "architect"
23
+ ],
24
+ "startup_order": 1,
25
+ "depends_on": []
26
+ },
27
+ {
28
+ "id": "challenger",
29
+ "nickname": "challenger",
30
+ "type": "auto",
31
+ "role": "challenge scope and leverage",
32
+ "prompt_profile": "scope-challenger",
33
+ "accept_from": [
34
+ "facilitator"
35
+ ],
36
+ "report_to": [
37
+ "architect"
38
+ ],
39
+ "startup_order": 2,
40
+ "depends_on": [
41
+ "facilitator"
42
+ ]
43
+ },
44
+ {
45
+ "id": "architect",
46
+ "nickname": "architect",
47
+ "type": "auto",
48
+ "role": "translate approved scope into a technical plan",
49
+ "prompt_profile": "system-architect",
50
+ "accept_from": [
51
+ "facilitator",
52
+ "challenger"
53
+ ],
54
+ "report_to": [],
55
+ "startup_order": 3,
56
+ "depends_on": [
57
+ "facilitator",
58
+ "challenger"
59
+ ]
60
+ }
61
+ ],
62
+ "edges": [
63
+ {
64
+ "from": "facilitator",
65
+ "to": "challenger",
66
+ "kind": "task"
67
+ },
68
+ {
69
+ "from": "facilitator",
70
+ "to": "architect",
71
+ "kind": "task"
72
+ },
73
+ {
74
+ "from": "challenger",
75
+ "to": "architect",
76
+ "kind": "review"
77
+ }
78
+ ]
79
+ }
@@ -0,0 +1,87 @@
1
+ {
2
+ "schema_version": 1,
3
+ "template": {
4
+ "id": "ui-polish",
5
+ "alias": "ui-polish",
6
+ "name": "UI Polish"
7
+ },
8
+ "defaults": {
9
+ "launch_mode": "auto",
10
+ "start_timeout_ms": 15000
11
+ },
12
+ "agents": [
13
+ {
14
+ "id": "designer",
15
+ "nickname": "designer",
16
+ "type": "auto",
17
+ "role": "audit the interface and rank design polish issues",
18
+ "prompt_profile": "design-critic",
19
+ "accept_from": [],
20
+ "report_to": [
21
+ "refiner",
22
+ "qa"
23
+ ],
24
+ "startup_order": 1,
25
+ "depends_on": []
26
+ },
27
+ {
28
+ "id": "refiner",
29
+ "nickname": "refiner",
30
+ "type": "auto",
31
+ "role": "implement focused UI and interaction refinements",
32
+ "prompt_profile": "frontend-refiner",
33
+ "accept_from": [
34
+ "designer",
35
+ "qa"
36
+ ],
37
+ "report_to": [
38
+ "qa"
39
+ ],
40
+ "startup_order": 2,
41
+ "depends_on": [
42
+ "designer"
43
+ ]
44
+ },
45
+ {
46
+ "id": "qa",
47
+ "nickname": "qa",
48
+ "type": "auto",
49
+ "role": "validate polish changes and catch UX regressions",
50
+ "prompt_profile": "qa-driver",
51
+ "accept_from": [
52
+ "designer",
53
+ "refiner"
54
+ ],
55
+ "report_to": [
56
+ "refiner"
57
+ ],
58
+ "startup_order": 3,
59
+ "depends_on": [
60
+ "designer",
61
+ "refiner"
62
+ ]
63
+ }
64
+ ],
65
+ "edges": [
66
+ {
67
+ "from": "designer",
68
+ "to": "refiner",
69
+ "kind": "task"
70
+ },
71
+ {
72
+ "from": "designer",
73
+ "to": "qa",
74
+ "kind": "review"
75
+ },
76
+ {
77
+ "from": "refiner",
78
+ "to": "qa",
79
+ "kind": "task"
80
+ },
81
+ {
82
+ "from": "qa",
83
+ "to": "refiner",
84
+ "kind": "review"
85
+ }
86
+ ]
87
+ }
@@ -0,0 +1,79 @@
1
+ {
2
+ "schema_version": 1,
3
+ "template": {
4
+ "id": "verify-ship",
5
+ "alias": "verify-ship",
6
+ "name": "Verify and Ship"
7
+ },
8
+ "defaults": {
9
+ "launch_mode": "auto",
10
+ "start_timeout_ms": 15000
11
+ },
12
+ "agents": [
13
+ {
14
+ "id": "qa",
15
+ "nickname": "qa",
16
+ "type": "auto",
17
+ "role": "validate user flows and capture defects",
18
+ "prompt_profile": "qa-driver",
19
+ "accept_from": [],
20
+ "report_to": [
21
+ "debugger",
22
+ "release"
23
+ ],
24
+ "startup_order": 1,
25
+ "depends_on": []
26
+ },
27
+ {
28
+ "id": "debugger",
29
+ "nickname": "debugger",
30
+ "type": "auto",
31
+ "role": "identify root cause for hard failures",
32
+ "prompt_profile": "debug-investigator",
33
+ "accept_from": [
34
+ "qa"
35
+ ],
36
+ "report_to": [
37
+ "release"
38
+ ],
39
+ "startup_order": 2,
40
+ "depends_on": [
41
+ "qa"
42
+ ]
43
+ },
44
+ {
45
+ "id": "release",
46
+ "nickname": "release",
47
+ "type": "auto",
48
+ "role": "judge release readiness and blockers",
49
+ "prompt_profile": "release-coordinator",
50
+ "accept_from": [
51
+ "qa",
52
+ "debugger"
53
+ ],
54
+ "report_to": [],
55
+ "startup_order": 3,
56
+ "depends_on": [
57
+ "qa",
58
+ "debugger"
59
+ ]
60
+ }
61
+ ],
62
+ "edges": [
63
+ {
64
+ "from": "qa",
65
+ "to": "debugger",
66
+ "kind": "task"
67
+ },
68
+ {
69
+ "from": "qa",
70
+ "to": "release",
71
+ "kind": "task"
72
+ },
73
+ {
74
+ "from": "debugger",
75
+ "to": "release",
76
+ "kind": "review"
77
+ }
78
+ ]
79
+ }
@@ -1,49 +0,0 @@
1
- {
2
- "schema_version": 1,
3
- "template": {
4
- "id": "research-quick",
5
- "alias": "research-quick",
6
- "name": "Research Quick"
7
- },
8
- "defaults": {
9
- "launch_mode": "auto",
10
- "start_timeout_ms": 10000
11
- },
12
- "agents": [
13
- {
14
- "id": "researcher",
15
- "nickname": "researcher",
16
- "type": "claude",
17
- "role": "collect references and summarize findings",
18
- "prompt_profile": "research-scan",
19
- "accept_from": [],
20
- "report_to": [],
21
- "startup_order": 1,
22
- "depends_on": []
23
- },
24
- {
25
- "id": "coder",
26
- "nickname": "coder",
27
- "type": "codex",
28
- "role": "prototype and verify implementation",
29
- "prompt_profile": "rapid-prototype",
30
- "accept_from": [
31
- "researcher"
32
- ],
33
- "report_to": [
34
- "researcher"
35
- ],
36
- "startup_order": 2,
37
- "depends_on": [
38
- "researcher"
39
- ]
40
- }
41
- ],
42
- "edges": [
43
- {
44
- "from": "researcher",
45
- "to": "coder",
46
- "kind": "task"
47
- }
48
- ]
49
- }