@locusai/sdk 0.13.0 → 0.13.2
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.
- package/dist/agent/worker.d.ts.map +1 -1
- package/dist/agent/worker.js +300 -12
- package/dist/core/config.d.ts +2 -1
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/prompt-builder.d.ts +1 -0
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/discussion/agents/facilitator-prompt.d.ts +13 -0
- package/dist/discussion/agents/facilitator-prompt.d.ts.map +1 -0
- package/dist/discussion/discussion-facilitator.d.ts +67 -0
- package/dist/discussion/discussion-facilitator.d.ts.map +1 -0
- package/dist/discussion/discussion-manager.d.ts +59 -0
- package/dist/discussion/discussion-manager.d.ts.map +1 -0
- package/dist/discussion/discussion-types.d.ts +89 -0
- package/dist/discussion/discussion-types.d.ts.map +1 -0
- package/dist/discussion/index.d.ts +5 -0
- package/dist/discussion/index.d.ts.map +1 -0
- package/dist/index-node.d.ts +1 -0
- package/dist/index-node.d.ts.map +1 -1
- package/dist/index-node.js +735 -105
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -0
- package/package.json +2 -2
package/dist/index-node.js
CHANGED
|
@@ -391,6 +391,39 @@ var init_workspaces = __esm(() => {
|
|
|
391
391
|
};
|
|
392
392
|
});
|
|
393
393
|
|
|
394
|
+
// src/discussion/discussion-types.ts
|
|
395
|
+
var import_zod, DiscussionMessageSchema, DiscussionInsightSchema, DiscussionSchema;
|
|
396
|
+
var init_discussion_types = __esm(() => {
|
|
397
|
+
import_zod = require("zod");
|
|
398
|
+
DiscussionMessageSchema = import_zod.z.object({
|
|
399
|
+
role: import_zod.z.enum(["user", "assistant"]),
|
|
400
|
+
content: import_zod.z.string(),
|
|
401
|
+
timestamp: import_zod.z.number()
|
|
402
|
+
});
|
|
403
|
+
DiscussionInsightSchema = import_zod.z.object({
|
|
404
|
+
id: import_zod.z.string(),
|
|
405
|
+
type: import_zod.z.enum(["decision", "requirement", "idea", "concern", "learning"]),
|
|
406
|
+
title: import_zod.z.string(),
|
|
407
|
+
content: import_zod.z.string(),
|
|
408
|
+
tags: import_zod.z.array(import_zod.z.string()).default([]),
|
|
409
|
+
createdAt: import_zod.z.string()
|
|
410
|
+
});
|
|
411
|
+
DiscussionSchema = import_zod.z.object({
|
|
412
|
+
id: import_zod.z.string(),
|
|
413
|
+
title: import_zod.z.string(),
|
|
414
|
+
topic: import_zod.z.string(),
|
|
415
|
+
status: import_zod.z.enum(["active", "completed", "archived"]).default("active"),
|
|
416
|
+
messages: import_zod.z.array(DiscussionMessageSchema).default([]),
|
|
417
|
+
insights: import_zod.z.array(DiscussionInsightSchema).default([]),
|
|
418
|
+
createdAt: import_zod.z.string(),
|
|
419
|
+
updatedAt: import_zod.z.string(),
|
|
420
|
+
metadata: import_zod.z.object({
|
|
421
|
+
model: import_zod.z.string(),
|
|
422
|
+
provider: import_zod.z.string()
|
|
423
|
+
})
|
|
424
|
+
});
|
|
425
|
+
});
|
|
426
|
+
|
|
394
427
|
// src/index.ts
|
|
395
428
|
var exports_src = {};
|
|
396
429
|
__export(exports_src, {
|
|
@@ -403,6 +436,9 @@ __export(exports_src, {
|
|
|
403
436
|
LocusClient: () => LocusClient,
|
|
404
437
|
InvitationsModule: () => InvitationsModule,
|
|
405
438
|
DocsModule: () => DocsModule,
|
|
439
|
+
DiscussionSchema: () => DiscussionSchema,
|
|
440
|
+
DiscussionMessageSchema: () => DiscussionMessageSchema,
|
|
441
|
+
DiscussionInsightSchema: () => DiscussionInsightSchema,
|
|
406
442
|
CiModule: () => CiModule,
|
|
407
443
|
AuthModule: () => AuthModule
|
|
408
444
|
});
|
|
@@ -508,6 +544,7 @@ var init_src = __esm(() => {
|
|
|
508
544
|
init_sprints();
|
|
509
545
|
init_tasks();
|
|
510
546
|
init_workspaces();
|
|
547
|
+
init_discussion_types();
|
|
511
548
|
import_axios = __toESM(require("axios"));
|
|
512
549
|
init_events();
|
|
513
550
|
init_auth();
|
|
@@ -554,7 +591,8 @@ var init_config = __esm(() => {
|
|
|
554
591
|
documentsDir: "documents",
|
|
555
592
|
sessionsDir: "sessions",
|
|
556
593
|
reviewsDir: "reviews",
|
|
557
|
-
plansDir: "plans"
|
|
594
|
+
plansDir: "plans",
|
|
595
|
+
discussionsDir: "discussions"
|
|
558
596
|
};
|
|
559
597
|
LOCUS_GITIGNORE_PATTERNS = [
|
|
560
598
|
"# Locus AI - Session data (user-specific, can grow large)",
|
|
@@ -569,6 +607,9 @@ var init_config = __esm(() => {
|
|
|
569
607
|
"# Locus AI - Plans (generated per task)",
|
|
570
608
|
".locus/plans/",
|
|
571
609
|
"",
|
|
610
|
+
"# Locus AI - Discussions (AI discussion sessions)",
|
|
611
|
+
".locus/discussions/",
|
|
612
|
+
"",
|
|
572
613
|
"# Locus AI - Settings (contains API key, telegram config, etc.)",
|
|
573
614
|
".locus/settings.json",
|
|
574
615
|
"",
|
|
@@ -1871,6 +1912,187 @@ var init_git_workflow = __esm(() => {
|
|
|
1871
1912
|
import_node_child_process4 = require("node:child_process");
|
|
1872
1913
|
});
|
|
1873
1914
|
|
|
1915
|
+
// src/discussion/discussion-manager.ts
|
|
1916
|
+
class DiscussionManager {
|
|
1917
|
+
discussionsDir;
|
|
1918
|
+
constructor(projectPath) {
|
|
1919
|
+
this.discussionsDir = getLocusPath(projectPath, "discussionsDir");
|
|
1920
|
+
}
|
|
1921
|
+
create(topic, model, provider) {
|
|
1922
|
+
this.ensureDir();
|
|
1923
|
+
const now = new Date().toISOString();
|
|
1924
|
+
const id = `disc-${Date.now()}`;
|
|
1925
|
+
const discussion = {
|
|
1926
|
+
id,
|
|
1927
|
+
title: topic,
|
|
1928
|
+
topic,
|
|
1929
|
+
status: "active",
|
|
1930
|
+
messages: [],
|
|
1931
|
+
insights: [],
|
|
1932
|
+
createdAt: now,
|
|
1933
|
+
updatedAt: now,
|
|
1934
|
+
metadata: { model, provider }
|
|
1935
|
+
};
|
|
1936
|
+
this.save(discussion);
|
|
1937
|
+
return discussion;
|
|
1938
|
+
}
|
|
1939
|
+
save(discussion) {
|
|
1940
|
+
this.ensureDir();
|
|
1941
|
+
const jsonPath = import_node_path5.join(this.discussionsDir, `${discussion.id}.json`);
|
|
1942
|
+
const mdPath = import_node_path5.join(this.discussionsDir, `summary-${discussion.id}.md`);
|
|
1943
|
+
import_node_fs3.writeFileSync(jsonPath, JSON.stringify(discussion, null, 2), "utf-8");
|
|
1944
|
+
import_node_fs3.writeFileSync(mdPath, this.toMarkdown(discussion), "utf-8");
|
|
1945
|
+
}
|
|
1946
|
+
load(id) {
|
|
1947
|
+
this.ensureDir();
|
|
1948
|
+
const filePath = import_node_path5.join(this.discussionsDir, `${id}.json`);
|
|
1949
|
+
if (!import_node_fs3.existsSync(filePath)) {
|
|
1950
|
+
return null;
|
|
1951
|
+
}
|
|
1952
|
+
try {
|
|
1953
|
+
return JSON.parse(import_node_fs3.readFileSync(filePath, "utf-8"));
|
|
1954
|
+
} catch {
|
|
1955
|
+
return null;
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
list(status) {
|
|
1959
|
+
this.ensureDir();
|
|
1960
|
+
const files = import_node_fs3.readdirSync(this.discussionsDir).filter((f) => f.endsWith(".json"));
|
|
1961
|
+
const discussions = [];
|
|
1962
|
+
for (const file of files) {
|
|
1963
|
+
try {
|
|
1964
|
+
const discussion = JSON.parse(import_node_fs3.readFileSync(import_node_path5.join(this.discussionsDir, file), "utf-8"));
|
|
1965
|
+
if (!status || discussion.status === status) {
|
|
1966
|
+
discussions.push(discussion);
|
|
1967
|
+
}
|
|
1968
|
+
} catch {}
|
|
1969
|
+
}
|
|
1970
|
+
discussions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
1971
|
+
return discussions;
|
|
1972
|
+
}
|
|
1973
|
+
complete(id) {
|
|
1974
|
+
const discussion = this.load(id);
|
|
1975
|
+
if (!discussion) {
|
|
1976
|
+
throw new Error(`Discussion not found: ${id}`);
|
|
1977
|
+
}
|
|
1978
|
+
discussion.status = "completed";
|
|
1979
|
+
discussion.updatedAt = new Date().toISOString();
|
|
1980
|
+
this.save(discussion);
|
|
1981
|
+
return discussion;
|
|
1982
|
+
}
|
|
1983
|
+
archive(id) {
|
|
1984
|
+
const discussion = this.load(id);
|
|
1985
|
+
if (!discussion) {
|
|
1986
|
+
throw new Error(`Discussion not found: ${id}`);
|
|
1987
|
+
}
|
|
1988
|
+
discussion.status = "archived";
|
|
1989
|
+
discussion.updatedAt = new Date().toISOString();
|
|
1990
|
+
this.save(discussion);
|
|
1991
|
+
}
|
|
1992
|
+
delete(id) {
|
|
1993
|
+
this.ensureDir();
|
|
1994
|
+
const jsonPath = import_node_path5.join(this.discussionsDir, `${id}.json`);
|
|
1995
|
+
const mdPath = import_node_path5.join(this.discussionsDir, `summary-${id}.md`);
|
|
1996
|
+
if (import_node_fs3.existsSync(jsonPath)) {
|
|
1997
|
+
import_node_fs3.unlinkSync(jsonPath);
|
|
1998
|
+
}
|
|
1999
|
+
if (import_node_fs3.existsSync(mdPath)) {
|
|
2000
|
+
import_node_fs3.unlinkSync(mdPath);
|
|
2001
|
+
}
|
|
2002
|
+
}
|
|
2003
|
+
addMessage(id, role, content) {
|
|
2004
|
+
const discussion = this.load(id);
|
|
2005
|
+
if (!discussion) {
|
|
2006
|
+
throw new Error(`Discussion not found: ${id}`);
|
|
2007
|
+
}
|
|
2008
|
+
discussion.messages.push({
|
|
2009
|
+
role,
|
|
2010
|
+
content,
|
|
2011
|
+
timestamp: Date.now()
|
|
2012
|
+
});
|
|
2013
|
+
discussion.updatedAt = new Date().toISOString();
|
|
2014
|
+
this.save(discussion);
|
|
2015
|
+
return discussion;
|
|
2016
|
+
}
|
|
2017
|
+
addInsight(id, insight) {
|
|
2018
|
+
const discussion = this.load(id);
|
|
2019
|
+
if (!discussion) {
|
|
2020
|
+
throw new Error(`Discussion not found: ${id}`);
|
|
2021
|
+
}
|
|
2022
|
+
discussion.insights.push(insight);
|
|
2023
|
+
discussion.updatedAt = new Date().toISOString();
|
|
2024
|
+
this.save(discussion);
|
|
2025
|
+
return discussion;
|
|
2026
|
+
}
|
|
2027
|
+
getAllInsights() {
|
|
2028
|
+
const discussions = this.list("completed");
|
|
2029
|
+
const insights = [];
|
|
2030
|
+
for (const discussion of discussions) {
|
|
2031
|
+
insights.push(...discussion.insights);
|
|
2032
|
+
}
|
|
2033
|
+
insights.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
2034
|
+
return insights;
|
|
2035
|
+
}
|
|
2036
|
+
getMarkdown(id) {
|
|
2037
|
+
const discussion = this.load(id);
|
|
2038
|
+
if (!discussion)
|
|
2039
|
+
return null;
|
|
2040
|
+
return this.toMarkdown(discussion);
|
|
2041
|
+
}
|
|
2042
|
+
toMarkdown(discussion) {
|
|
2043
|
+
const lines = [];
|
|
2044
|
+
lines.push(`# Discussion: ${discussion.title}`);
|
|
2045
|
+
lines.push("");
|
|
2046
|
+
lines.push(`**Status:** ${discussion.status.toUpperCase()}`);
|
|
2047
|
+
lines.push(`**Topic:** ${discussion.topic}`);
|
|
2048
|
+
lines.push(`**Created:** ${discussion.createdAt}`);
|
|
2049
|
+
lines.push(`**Updated:** ${discussion.updatedAt}`);
|
|
2050
|
+
lines.push(`**Model:** ${discussion.metadata.model} (${discussion.metadata.provider})`);
|
|
2051
|
+
lines.push("");
|
|
2052
|
+
if (discussion.messages.length > 0) {
|
|
2053
|
+
lines.push(`## Messages (${discussion.messages.length})`);
|
|
2054
|
+
lines.push("");
|
|
2055
|
+
for (const msg of discussion.messages) {
|
|
2056
|
+
const time = new Date(msg.timestamp).toISOString();
|
|
2057
|
+
const roleLabel = msg.role === "user" ? "User" : "Assistant";
|
|
2058
|
+
lines.push(`### ${roleLabel} — ${time}`);
|
|
2059
|
+
lines.push("");
|
|
2060
|
+
lines.push(msg.content);
|
|
2061
|
+
lines.push("");
|
|
2062
|
+
}
|
|
2063
|
+
}
|
|
2064
|
+
if (discussion.insights.length > 0) {
|
|
2065
|
+
lines.push(`## Insights (${discussion.insights.length})`);
|
|
2066
|
+
lines.push("");
|
|
2067
|
+
for (const insight of discussion.insights) {
|
|
2068
|
+
lines.push(`### [${insight.type.toUpperCase()}] ${insight.title}`);
|
|
2069
|
+
lines.push("");
|
|
2070
|
+
lines.push(insight.content);
|
|
2071
|
+
if (insight.tags.length > 0) {
|
|
2072
|
+
lines.push("");
|
|
2073
|
+
lines.push(`**Tags:** ${insight.tags.join(", ")}`);
|
|
2074
|
+
}
|
|
2075
|
+
lines.push("");
|
|
2076
|
+
}
|
|
2077
|
+
}
|
|
2078
|
+
lines.push("---");
|
|
2079
|
+
lines.push(`*Discussion ID: ${discussion.id}*`);
|
|
2080
|
+
return lines.join(`
|
|
2081
|
+
`);
|
|
2082
|
+
}
|
|
2083
|
+
ensureDir() {
|
|
2084
|
+
if (!import_node_fs3.existsSync(this.discussionsDir)) {
|
|
2085
|
+
import_node_fs3.mkdirSync(this.discussionsDir, { recursive: true });
|
|
2086
|
+
}
|
|
2087
|
+
}
|
|
2088
|
+
}
|
|
2089
|
+
var import_node_fs3, import_node_path5;
|
|
2090
|
+
var init_discussion_manager = __esm(() => {
|
|
2091
|
+
init_config();
|
|
2092
|
+
import_node_fs3 = require("node:fs");
|
|
2093
|
+
import_node_path5 = require("node:path");
|
|
2094
|
+
});
|
|
2095
|
+
|
|
1874
2096
|
// src/core/prompt-builder.ts
|
|
1875
2097
|
class PromptBuilder {
|
|
1876
2098
|
projectPath;
|
|
@@ -1909,6 +2131,15 @@ ${knowledgeBase}
|
|
|
1909
2131
|
These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
|
|
1910
2132
|
${learnings}
|
|
1911
2133
|
</learnings>
|
|
2134
|
+
`;
|
|
2135
|
+
}
|
|
2136
|
+
const discussionInsights = this.getDiscussionInsightsContent();
|
|
2137
|
+
if (discussionInsights) {
|
|
2138
|
+
sections += `
|
|
2139
|
+
<discussion_insights>
|
|
2140
|
+
These are key decisions and insights from product discussions. Follow them to maintain product coherence:
|
|
2141
|
+
${discussionInsights}
|
|
2142
|
+
</discussion_insights>
|
|
1912
2143
|
`;
|
|
1913
2144
|
}
|
|
1914
2145
|
if (task.docs && task.docs.length > 0) {
|
|
@@ -1997,6 +2228,15 @@ ${knowledgeBase}
|
|
|
1997
2228
|
These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
|
|
1998
2229
|
${learnings}
|
|
1999
2230
|
</learnings>
|
|
2231
|
+
`;
|
|
2232
|
+
}
|
|
2233
|
+
const discussionInsights = this.getDiscussionInsightsContent();
|
|
2234
|
+
if (discussionInsights) {
|
|
2235
|
+
sections += `
|
|
2236
|
+
<discussion_insights>
|
|
2237
|
+
These are key decisions and insights from product discussions. Follow them to maintain product coherence:
|
|
2238
|
+
${discussionInsights}
|
|
2239
|
+
</discussion_insights>
|
|
2000
2240
|
`;
|
|
2001
2241
|
}
|
|
2002
2242
|
return `<direct_execution>
|
|
@@ -2011,9 +2251,9 @@ ${sections}
|
|
|
2011
2251
|
}
|
|
2012
2252
|
getProjectContext() {
|
|
2013
2253
|
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
2014
|
-
if (
|
|
2254
|
+
if (import_node_fs4.existsSync(contextPath)) {
|
|
2015
2255
|
try {
|
|
2016
|
-
const context =
|
|
2256
|
+
const context = import_node_fs4.readFileSync(contextPath, "utf-8");
|
|
2017
2257
|
if (context.trim().length > 20) {
|
|
2018
2258
|
return context;
|
|
2019
2259
|
}
|
|
@@ -2024,10 +2264,10 @@ ${sections}
|
|
|
2024
2264
|
return this.getFallbackContext() || null;
|
|
2025
2265
|
}
|
|
2026
2266
|
getFallbackContext() {
|
|
2027
|
-
const readmePath =
|
|
2028
|
-
if (
|
|
2267
|
+
const readmePath = import_node_path6.join(this.projectPath, "README.md");
|
|
2268
|
+
if (import_node_fs4.existsSync(readmePath)) {
|
|
2029
2269
|
try {
|
|
2030
|
-
const content =
|
|
2270
|
+
const content = import_node_fs4.readFileSync(readmePath, "utf-8");
|
|
2031
2271
|
const limit = 1000;
|
|
2032
2272
|
return content.slice(0, limit) + (content.length > limit ? `
|
|
2033
2273
|
...(truncated)...` : "");
|
|
@@ -2041,15 +2281,16 @@ ${sections}
|
|
|
2041
2281
|
return `You have access to the following documentation directories for context:
|
|
2042
2282
|
- Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
|
|
2043
2283
|
- Documents: \`.locus/documents\` (synced from cloud)
|
|
2284
|
+
- Discussions: \`.locus/discussions\` (product discussion insights and decisions)
|
|
2044
2285
|
If you need more information about the project strategies, plans, or architecture, read files in these directories.`;
|
|
2045
2286
|
}
|
|
2046
2287
|
getLearningsContent() {
|
|
2047
2288
|
const learningsPath = getLocusPath(this.projectPath, "learningsFile");
|
|
2048
|
-
if (!
|
|
2289
|
+
if (!import_node_fs4.existsSync(learningsPath)) {
|
|
2049
2290
|
return null;
|
|
2050
2291
|
}
|
|
2051
2292
|
try {
|
|
2052
|
-
const content =
|
|
2293
|
+
const content = import_node_fs4.readFileSync(learningsPath, "utf-8");
|
|
2053
2294
|
const lines = content.split(`
|
|
2054
2295
|
`).filter((l) => l.startsWith("- "));
|
|
2055
2296
|
if (lines.length === 0) {
|
|
@@ -2061,6 +2302,53 @@ If you need more information about the project strategies, plans, or architectur
|
|
|
2061
2302
|
return null;
|
|
2062
2303
|
}
|
|
2063
2304
|
}
|
|
2305
|
+
getDiscussionInsightsContent() {
|
|
2306
|
+
try {
|
|
2307
|
+
const manager = new DiscussionManager(this.projectPath);
|
|
2308
|
+
const insights = manager.getAllInsights();
|
|
2309
|
+
if (insights.length === 0) {
|
|
2310
|
+
return null;
|
|
2311
|
+
}
|
|
2312
|
+
const groups = {};
|
|
2313
|
+
for (const insight of insights) {
|
|
2314
|
+
const key = insight.type;
|
|
2315
|
+
if (!groups[key]) {
|
|
2316
|
+
groups[key] = [];
|
|
2317
|
+
}
|
|
2318
|
+
groups[key].push(insight);
|
|
2319
|
+
}
|
|
2320
|
+
const typeLabels = {
|
|
2321
|
+
decision: "Decisions",
|
|
2322
|
+
requirement: "Requirements",
|
|
2323
|
+
idea: "Ideas",
|
|
2324
|
+
concern: "Concerns",
|
|
2325
|
+
learning: "Learnings"
|
|
2326
|
+
};
|
|
2327
|
+
let output = "";
|
|
2328
|
+
for (const [type, label] of Object.entries(typeLabels)) {
|
|
2329
|
+
const items = groups[type];
|
|
2330
|
+
if (!items || items.length === 0)
|
|
2331
|
+
continue;
|
|
2332
|
+
output += `## ${label}
|
|
2333
|
+
`;
|
|
2334
|
+
for (const item of items) {
|
|
2335
|
+
output += `- [${item.title}]: ${item.content}
|
|
2336
|
+
`;
|
|
2337
|
+
}
|
|
2338
|
+
output += `
|
|
2339
|
+
`;
|
|
2340
|
+
}
|
|
2341
|
+
if (output.length === 0) {
|
|
2342
|
+
return null;
|
|
2343
|
+
}
|
|
2344
|
+
if (output.length > 2000) {
|
|
2345
|
+
output = `${output.slice(0, 1997)}...`;
|
|
2346
|
+
}
|
|
2347
|
+
return output.trimEnd();
|
|
2348
|
+
} catch {
|
|
2349
|
+
return null;
|
|
2350
|
+
}
|
|
2351
|
+
}
|
|
2064
2352
|
roleToText(role) {
|
|
2065
2353
|
if (!role) {
|
|
2066
2354
|
return null;
|
|
@@ -2081,11 +2369,12 @@ If you need more information about the project strategies, plans, or architectur
|
|
|
2081
2369
|
}
|
|
2082
2370
|
}
|
|
2083
2371
|
}
|
|
2084
|
-
var
|
|
2372
|
+
var import_node_fs4, import_node_path6, import_shared2;
|
|
2085
2373
|
var init_prompt_builder = __esm(() => {
|
|
2374
|
+
init_discussion_manager();
|
|
2086
2375
|
init_config();
|
|
2087
|
-
|
|
2088
|
-
|
|
2376
|
+
import_node_fs4 = require("node:fs");
|
|
2377
|
+
import_node_path6 = require("node:path");
|
|
2089
2378
|
import_shared2 = require("@locusai/shared");
|
|
2090
2379
|
});
|
|
2091
2380
|
|
|
@@ -2408,7 +2697,6 @@ Branch: \`${result.branch}\`` : "";
|
|
|
2408
2697
|
this.log("All tasks done. Creating pull request...", "info");
|
|
2409
2698
|
const prResult = this.gitWorkflow.createPullRequest(this.completedTaskList, this.taskSummaries);
|
|
2410
2699
|
if (prResult.url) {
|
|
2411
|
-
this.log(`PR created: ${prResult.url}`, "success");
|
|
2412
2700
|
for (const task of this.completedTaskList) {
|
|
2413
2701
|
try {
|
|
2414
2702
|
await this.client.tasks.update(task.id, this.config.workspaceId, {
|
|
@@ -2467,6 +2755,8 @@ __export(exports_index_node, {
|
|
|
2467
2755
|
detectRemoteProvider: () => detectRemoteProvider,
|
|
2468
2756
|
createAiRunner: () => createAiRunner,
|
|
2469
2757
|
c: () => c,
|
|
2758
|
+
buildSummaryPrompt: () => buildSummaryPrompt,
|
|
2759
|
+
buildFacilitatorPrompt: () => buildFacilitatorPrompt,
|
|
2470
2760
|
WorkspacesModule: () => WorkspacesModule,
|
|
2471
2761
|
TasksModule: () => TasksModule,
|
|
2472
2762
|
TaskExecutor: () => TaskExecutor,
|
|
@@ -2494,6 +2784,11 @@ __export(exports_index_node, {
|
|
|
2494
2784
|
ExecEventEmitter: () => ExecEventEmitter,
|
|
2495
2785
|
DocumentFetcher: () => DocumentFetcher,
|
|
2496
2786
|
DocsModule: () => DocsModule,
|
|
2787
|
+
DiscussionSchema: () => DiscussionSchema,
|
|
2788
|
+
DiscussionMessageSchema: () => DiscussionMessageSchema,
|
|
2789
|
+
DiscussionManager: () => DiscussionManager,
|
|
2790
|
+
DiscussionInsightSchema: () => DiscussionInsightSchema,
|
|
2791
|
+
DiscussionFacilitator: () => DiscussionFacilitator,
|
|
2497
2792
|
DEFAULT_MODEL: () => DEFAULT_MODEL,
|
|
2498
2793
|
ContextTracker: () => ContextTracker,
|
|
2499
2794
|
CodexRunner: () => CodexRunner,
|
|
@@ -2509,8 +2804,8 @@ module.exports = __toCommonJS(exports_index_node);
|
|
|
2509
2804
|
|
|
2510
2805
|
// src/core/indexer.ts
|
|
2511
2806
|
var import_node_crypto2 = require("node:crypto");
|
|
2512
|
-
var
|
|
2513
|
-
var
|
|
2807
|
+
var import_node_fs5 = require("node:fs");
|
|
2808
|
+
var import_node_path7 = require("node:path");
|
|
2514
2809
|
var import_globby = require("globby");
|
|
2515
2810
|
|
|
2516
2811
|
class CodebaseIndexer {
|
|
@@ -2519,7 +2814,7 @@ class CodebaseIndexer {
|
|
|
2519
2814
|
fullReindexRatioThreshold = 0.2;
|
|
2520
2815
|
constructor(projectPath) {
|
|
2521
2816
|
this.projectPath = projectPath;
|
|
2522
|
-
this.indexPath =
|
|
2817
|
+
this.indexPath = import_node_path7.join(projectPath, ".locus", "codebase-index.json");
|
|
2523
2818
|
}
|
|
2524
2819
|
async index(onProgress, treeSummarizer, force = false) {
|
|
2525
2820
|
if (!treeSummarizer) {
|
|
@@ -2575,11 +2870,11 @@ class CodebaseIndexer {
|
|
|
2575
2870
|
}
|
|
2576
2871
|
}
|
|
2577
2872
|
async getFileTree() {
|
|
2578
|
-
const gitmodulesPath =
|
|
2873
|
+
const gitmodulesPath = import_node_path7.join(this.projectPath, ".gitmodules");
|
|
2579
2874
|
const submoduleIgnores = [];
|
|
2580
|
-
if (
|
|
2875
|
+
if (import_node_fs5.existsSync(gitmodulesPath)) {
|
|
2581
2876
|
try {
|
|
2582
|
-
const content =
|
|
2877
|
+
const content = import_node_fs5.readFileSync(gitmodulesPath, "utf-8");
|
|
2583
2878
|
const lines = content.split(`
|
|
2584
2879
|
`);
|
|
2585
2880
|
for (const line of lines) {
|
|
@@ -2635,9 +2930,9 @@ class CodebaseIndexer {
|
|
|
2635
2930
|
});
|
|
2636
2931
|
}
|
|
2637
2932
|
loadIndex() {
|
|
2638
|
-
if (
|
|
2933
|
+
if (import_node_fs5.existsSync(this.indexPath)) {
|
|
2639
2934
|
try {
|
|
2640
|
-
return JSON.parse(
|
|
2935
|
+
return JSON.parse(import_node_fs5.readFileSync(this.indexPath, "utf-8"));
|
|
2641
2936
|
} catch {
|
|
2642
2937
|
return null;
|
|
2643
2938
|
}
|
|
@@ -2645,11 +2940,11 @@ class CodebaseIndexer {
|
|
|
2645
2940
|
return null;
|
|
2646
2941
|
}
|
|
2647
2942
|
saveIndex(index) {
|
|
2648
|
-
const dir =
|
|
2649
|
-
if (!
|
|
2650
|
-
|
|
2943
|
+
const dir = import_node_path7.dirname(this.indexPath);
|
|
2944
|
+
if (!import_node_fs5.existsSync(dir)) {
|
|
2945
|
+
import_node_fs5.mkdirSync(dir, { recursive: true });
|
|
2651
2946
|
}
|
|
2652
|
-
|
|
2947
|
+
import_node_fs5.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
|
|
2653
2948
|
}
|
|
2654
2949
|
cloneIndex(index) {
|
|
2655
2950
|
return JSON.parse(JSON.stringify(index));
|
|
@@ -2665,7 +2960,7 @@ class CodebaseIndexer {
|
|
|
2665
2960
|
}
|
|
2666
2961
|
hashFile(filePath) {
|
|
2667
2962
|
try {
|
|
2668
|
-
const content =
|
|
2963
|
+
const content = import_node_fs5.readFileSync(import_node_path7.join(this.projectPath, filePath), "utf-8");
|
|
2669
2964
|
return import_node_crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
2670
2965
|
} catch {
|
|
2671
2966
|
return null;
|
|
@@ -2820,8 +3115,8 @@ Return ONLY valid JSON (no code fences, no markdown):
|
|
|
2820
3115
|
}
|
|
2821
3116
|
// src/agent/document-fetcher.ts
|
|
2822
3117
|
init_config();
|
|
2823
|
-
var
|
|
2824
|
-
var
|
|
3118
|
+
var import_node_fs6 = require("node:fs");
|
|
3119
|
+
var import_node_path8 = require("node:path");
|
|
2825
3120
|
|
|
2826
3121
|
class DocumentFetcher {
|
|
2827
3122
|
deps;
|
|
@@ -2830,8 +3125,8 @@ class DocumentFetcher {
|
|
|
2830
3125
|
}
|
|
2831
3126
|
async fetch() {
|
|
2832
3127
|
const documentsDir = getLocusPath(this.deps.projectPath, "documentsDir");
|
|
2833
|
-
if (!
|
|
2834
|
-
|
|
3128
|
+
if (!import_node_fs6.existsSync(documentsDir)) {
|
|
3129
|
+
import_node_fs6.mkdirSync(documentsDir, { recursive: true });
|
|
2835
3130
|
}
|
|
2836
3131
|
try {
|
|
2837
3132
|
const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
|
|
@@ -2844,14 +3139,14 @@ class DocumentFetcher {
|
|
|
2844
3139
|
continue;
|
|
2845
3140
|
}
|
|
2846
3141
|
const groupName = groupMap.get(doc.groupId || "") || "General";
|
|
2847
|
-
const groupDir =
|
|
2848
|
-
if (!
|
|
2849
|
-
|
|
3142
|
+
const groupDir = import_node_path8.join(documentsDir, groupName);
|
|
3143
|
+
if (!import_node_fs6.existsSync(groupDir)) {
|
|
3144
|
+
import_node_fs6.mkdirSync(groupDir, { recursive: true });
|
|
2850
3145
|
}
|
|
2851
3146
|
const fileName = `${doc.title}.md`;
|
|
2852
|
-
const filePath =
|
|
2853
|
-
if (!
|
|
2854
|
-
|
|
3147
|
+
const filePath = import_node_path8.join(groupDir, fileName);
|
|
3148
|
+
if (!import_node_fs6.existsSync(filePath) || import_node_fs6.readFileSync(filePath, "utf-8") !== doc.content) {
|
|
3149
|
+
import_node_fs6.writeFileSync(filePath, doc.content || "");
|
|
2855
3150
|
fetchedCount++;
|
|
2856
3151
|
}
|
|
2857
3152
|
}
|
|
@@ -3385,6 +3680,341 @@ init_prompt_builder();
|
|
|
3385
3680
|
// src/index-node.ts
|
|
3386
3681
|
init_prompt_builder();
|
|
3387
3682
|
|
|
3683
|
+
// src/discussion/agents/facilitator-prompt.ts
|
|
3684
|
+
function buildFacilitatorPrompt(input) {
|
|
3685
|
+
const {
|
|
3686
|
+
topic,
|
|
3687
|
+
projectContext,
|
|
3688
|
+
learnings,
|
|
3689
|
+
knowledgeBase,
|
|
3690
|
+
previousMessages,
|
|
3691
|
+
insights,
|
|
3692
|
+
isFirstMessage
|
|
3693
|
+
} = input;
|
|
3694
|
+
let sections = "";
|
|
3695
|
+
if (projectContext) {
|
|
3696
|
+
sections += `
|
|
3697
|
+
<project_context>
|
|
3698
|
+
${projectContext}
|
|
3699
|
+
</project_context>
|
|
3700
|
+
`;
|
|
3701
|
+
}
|
|
3702
|
+
sections += `
|
|
3703
|
+
<knowledge_base>
|
|
3704
|
+
${knowledgeBase}
|
|
3705
|
+
</knowledge_base>
|
|
3706
|
+
`;
|
|
3707
|
+
if (learnings) {
|
|
3708
|
+
sections += `
|
|
3709
|
+
<learnings>
|
|
3710
|
+
These are accumulated lessons from past work on this project. Use them to ask more informed questions:
|
|
3711
|
+
${learnings}
|
|
3712
|
+
</learnings>
|
|
3713
|
+
`;
|
|
3714
|
+
}
|
|
3715
|
+
if (previousMessages.length > 0) {
|
|
3716
|
+
let history = "";
|
|
3717
|
+
for (const msg of previousMessages) {
|
|
3718
|
+
const role = msg.role === "user" ? "User" : "Facilitator";
|
|
3719
|
+
history += `[${role}]: ${msg.content}
|
|
3720
|
+
|
|
3721
|
+
`;
|
|
3722
|
+
}
|
|
3723
|
+
sections += `
|
|
3724
|
+
<conversation_history>
|
|
3725
|
+
${history.trimEnd()}
|
|
3726
|
+
</conversation_history>
|
|
3727
|
+
`;
|
|
3728
|
+
}
|
|
3729
|
+
if (insights.length > 0) {
|
|
3730
|
+
let insightsText = "";
|
|
3731
|
+
for (const insight of insights) {
|
|
3732
|
+
insightsText += `- [${insight.type.toUpperCase()}] ${insight.title}: ${insight.content}
|
|
3733
|
+
`;
|
|
3734
|
+
}
|
|
3735
|
+
sections += `
|
|
3736
|
+
<extracted_insights>
|
|
3737
|
+
Insights identified so far in this discussion:
|
|
3738
|
+
${insightsText.trimEnd()}
|
|
3739
|
+
</extracted_insights>
|
|
3740
|
+
`;
|
|
3741
|
+
}
|
|
3742
|
+
const firstMessageInstruction = isFirstMessage ? `This is the START of the discussion. Introduce yourself briefly, then ask your first probing question about the topic. Do NOT extract any insights yet — there is no user input to extract from.` : `Continue the discussion by responding to the user's latest message. Build on their answers to go deeper. After responding, extract any insights from their message.`;
|
|
3743
|
+
return `<discussion_facilitator>
|
|
3744
|
+
You are a product strategy facilitator leading a structured discussion.
|
|
3745
|
+
|
|
3746
|
+
<topic>
|
|
3747
|
+
${topic}
|
|
3748
|
+
</topic>
|
|
3749
|
+
${sections}
|
|
3750
|
+
<role>
|
|
3751
|
+
You are an expert product strategy facilitator. Your job is to:
|
|
3752
|
+
1. Ask probing, specific questions about the topic — never generic or surface-level
|
|
3753
|
+
2. Build on previous answers to progressively deepen the conversation
|
|
3754
|
+
3. Identify and extract key decisions, requirements, ideas, concerns, and learnings
|
|
3755
|
+
4. Reference existing project context and learnings to ask informed questions
|
|
3756
|
+
5. When the topic feels fully explored, suggest wrapping up with a summary
|
|
3757
|
+
</role>
|
|
3758
|
+
|
|
3759
|
+
<rules>
|
|
3760
|
+
- ${firstMessageInstruction}
|
|
3761
|
+
- Ask ONE focused question at a time. Do not overwhelm with multiple questions.
|
|
3762
|
+
- Be conversational but purposeful — every question should drive toward actionable insights.
|
|
3763
|
+
- When you identify an insight from the user's response, include it as a structured XML block in your response.
|
|
3764
|
+
- Insight blocks use this format within your response text:
|
|
3765
|
+
|
|
3766
|
+
<insight>
|
|
3767
|
+
{"type": "decision|requirement|idea|concern|learning", "title": "short title", "content": "detailed description", "tags": ["relevant", "tags"]}
|
|
3768
|
+
</insight>
|
|
3769
|
+
|
|
3770
|
+
- You may include multiple <insight> blocks if the user's response contains several distinct insights.
|
|
3771
|
+
- The insight blocks will be parsed and removed from the displayed response, so write your conversational text as if they are not there.
|
|
3772
|
+
- Types explained:
|
|
3773
|
+
- **decision**: A choice or direction that has been made or agreed upon
|
|
3774
|
+
- **requirement**: A specific need, constraint, or must-have
|
|
3775
|
+
- **idea**: A suggestion, proposal, or possibility to explore
|
|
3776
|
+
- **concern**: A risk, worry, or potential problem identified
|
|
3777
|
+
- **learning**: A realization, lesson, or important context discovered
|
|
3778
|
+
- Keep responses concise. Aim for 2-4 sentences of conversation plus any insight blocks.
|
|
3779
|
+
- If the user's responses indicate the topic is well-explored, suggest summarizing and wrapping up.
|
|
3780
|
+
</rules>
|
|
3781
|
+
</discussion_facilitator>`;
|
|
3782
|
+
}
|
|
3783
|
+
function buildSummaryPrompt(topic, messages, insights) {
|
|
3784
|
+
let history = "";
|
|
3785
|
+
for (const msg of messages) {
|
|
3786
|
+
const role = msg.role === "user" ? "User" : "Facilitator";
|
|
3787
|
+
history += `[${role}]: ${msg.content}
|
|
3788
|
+
|
|
3789
|
+
`;
|
|
3790
|
+
}
|
|
3791
|
+
let insightsText = "";
|
|
3792
|
+
if (insights.length > 0) {
|
|
3793
|
+
for (const insight of insights) {
|
|
3794
|
+
insightsText += `- [${insight.type.toUpperCase()}] **${insight.title}**: ${insight.content}`;
|
|
3795
|
+
if (insight.tags.length > 0) {
|
|
3796
|
+
insightsText += ` (tags: ${insight.tags.join(", ")})`;
|
|
3797
|
+
}
|
|
3798
|
+
insightsText += `
|
|
3799
|
+
`;
|
|
3800
|
+
}
|
|
3801
|
+
}
|
|
3802
|
+
return `<discussion_summary>
|
|
3803
|
+
Create a final summary of this product discussion.
|
|
3804
|
+
|
|
3805
|
+
<topic>
|
|
3806
|
+
${topic}
|
|
3807
|
+
</topic>
|
|
3808
|
+
|
|
3809
|
+
<conversation>
|
|
3810
|
+
${history.trimEnd()}
|
|
3811
|
+
</conversation>
|
|
3812
|
+
|
|
3813
|
+
${insightsText ? `<insights>
|
|
3814
|
+
${insightsText.trimEnd()}
|
|
3815
|
+
</insights>
|
|
3816
|
+
` : ""}
|
|
3817
|
+
<rules>
|
|
3818
|
+
- Write a clear, structured summary of the entire discussion.
|
|
3819
|
+
- Organize by: Key Decisions, Requirements, Ideas to Explore, Concerns & Risks, and Learnings.
|
|
3820
|
+
- Only include sections that have relevant content — skip empty categories.
|
|
3821
|
+
- For each item, provide a brief but actionable description.
|
|
3822
|
+
- End with a "Next Steps" section listing concrete action items that emerged.
|
|
3823
|
+
- Be concise — this summary should be scannable and useful as a reference.
|
|
3824
|
+
- Do NOT include any <insight> XML blocks in the summary.
|
|
3825
|
+
</rules>
|
|
3826
|
+
</discussion_summary>`;
|
|
3827
|
+
}
|
|
3828
|
+
// src/discussion/discussion-facilitator.ts
|
|
3829
|
+
init_config();
|
|
3830
|
+
var import_node_fs7 = require("node:fs");
|
|
3831
|
+
class DiscussionFacilitator {
|
|
3832
|
+
projectPath;
|
|
3833
|
+
aiRunner;
|
|
3834
|
+
discussionManager;
|
|
3835
|
+
log;
|
|
3836
|
+
provider;
|
|
3837
|
+
model;
|
|
3838
|
+
constructor(config) {
|
|
3839
|
+
this.projectPath = config.projectPath;
|
|
3840
|
+
this.aiRunner = config.aiRunner;
|
|
3841
|
+
this.discussionManager = config.discussionManager;
|
|
3842
|
+
this.log = config.log ?? ((_msg) => {
|
|
3843
|
+
return;
|
|
3844
|
+
});
|
|
3845
|
+
this.provider = config.provider;
|
|
3846
|
+
this.model = config.model;
|
|
3847
|
+
}
|
|
3848
|
+
async startDiscussion(topic) {
|
|
3849
|
+
this.log("Starting new discussion...", "info");
|
|
3850
|
+
const discussion = this.discussionManager.create(topic, this.model, this.provider);
|
|
3851
|
+
const { projectContext, learnings, knowledgeBase } = this.buildContext();
|
|
3852
|
+
const prompt = buildFacilitatorPrompt({
|
|
3853
|
+
topic,
|
|
3854
|
+
projectContext,
|
|
3855
|
+
learnings,
|
|
3856
|
+
knowledgeBase,
|
|
3857
|
+
previousMessages: [],
|
|
3858
|
+
insights: [],
|
|
3859
|
+
isFirstMessage: true
|
|
3860
|
+
});
|
|
3861
|
+
const response = await this.aiRunner.run(prompt);
|
|
3862
|
+
const { cleanResponse } = this.parseInsights(response);
|
|
3863
|
+
this.discussionManager.addMessage(discussion.id, "assistant", cleanResponse);
|
|
3864
|
+
this.log("Discussion started", "success");
|
|
3865
|
+
const saved = this.discussionManager.load(discussion.id);
|
|
3866
|
+
if (!saved) {
|
|
3867
|
+
throw new Error(`Failed to load discussion after creation: ${discussion.id}`);
|
|
3868
|
+
}
|
|
3869
|
+
return {
|
|
3870
|
+
discussion: saved,
|
|
3871
|
+
message: cleanResponse
|
|
3872
|
+
};
|
|
3873
|
+
}
|
|
3874
|
+
async continueDiscussion(discussionId, userMessage) {
|
|
3875
|
+
const discussion = this.discussionManager.load(discussionId);
|
|
3876
|
+
if (!discussion) {
|
|
3877
|
+
throw new Error(`Discussion not found: ${discussionId}`);
|
|
3878
|
+
}
|
|
3879
|
+
const updated = this.discussionManager.addMessage(discussionId, "user", userMessage);
|
|
3880
|
+
const { projectContext, learnings, knowledgeBase } = this.buildContext();
|
|
3881
|
+
const prompt = buildFacilitatorPrompt({
|
|
3882
|
+
topic: updated.topic,
|
|
3883
|
+
projectContext,
|
|
3884
|
+
learnings,
|
|
3885
|
+
knowledgeBase,
|
|
3886
|
+
previousMessages: updated.messages,
|
|
3887
|
+
insights: updated.insights,
|
|
3888
|
+
isFirstMessage: false
|
|
3889
|
+
});
|
|
3890
|
+
const response = await this.aiRunner.run(prompt);
|
|
3891
|
+
const { cleanResponse, insights } = this.parseInsights(response);
|
|
3892
|
+
for (const insight of insights) {
|
|
3893
|
+
this.discussionManager.addInsight(discussionId, insight);
|
|
3894
|
+
}
|
|
3895
|
+
this.discussionManager.addMessage(discussionId, "assistant", cleanResponse);
|
|
3896
|
+
return { response: cleanResponse, insights };
|
|
3897
|
+
}
|
|
3898
|
+
async* continueDiscussionStream(discussionId, userMessage) {
|
|
3899
|
+
const discussion = this.discussionManager.load(discussionId);
|
|
3900
|
+
if (!discussion) {
|
|
3901
|
+
throw new Error(`Discussion not found: ${discussionId}`);
|
|
3902
|
+
}
|
|
3903
|
+
const updated = this.discussionManager.addMessage(discussionId, "user", userMessage);
|
|
3904
|
+
const { projectContext, learnings, knowledgeBase } = this.buildContext();
|
|
3905
|
+
const prompt = buildFacilitatorPrompt({
|
|
3906
|
+
topic: updated.topic,
|
|
3907
|
+
projectContext,
|
|
3908
|
+
learnings,
|
|
3909
|
+
knowledgeBase,
|
|
3910
|
+
previousMessages: updated.messages,
|
|
3911
|
+
insights: updated.insights,
|
|
3912
|
+
isFirstMessage: false
|
|
3913
|
+
});
|
|
3914
|
+
let fullResponse = "";
|
|
3915
|
+
const stream = this.aiRunner.runStream(prompt);
|
|
3916
|
+
for await (const chunk of stream) {
|
|
3917
|
+
yield chunk;
|
|
3918
|
+
if (chunk.type === "text_delta") {
|
|
3919
|
+
fullResponse += chunk.content;
|
|
3920
|
+
} else if (chunk.type === "result") {
|
|
3921
|
+
fullResponse = chunk.content;
|
|
3922
|
+
}
|
|
3923
|
+
}
|
|
3924
|
+
const { cleanResponse, insights } = this.parseInsights(fullResponse);
|
|
3925
|
+
for (const insight of insights) {
|
|
3926
|
+
this.discussionManager.addInsight(discussionId, insight);
|
|
3927
|
+
}
|
|
3928
|
+
this.discussionManager.addMessage(discussionId, "assistant", cleanResponse);
|
|
3929
|
+
return { response: cleanResponse, insights };
|
|
3930
|
+
}
|
|
3931
|
+
async summarizeDiscussion(discussionId) {
|
|
3932
|
+
const discussion = this.discussionManager.load(discussionId);
|
|
3933
|
+
if (!discussion) {
|
|
3934
|
+
throw new Error(`Discussion not found: ${discussionId}`);
|
|
3935
|
+
}
|
|
3936
|
+
this.log("Generating discussion summary...", "info");
|
|
3937
|
+
const prompt = buildSummaryPrompt(discussion.topic, discussion.messages, discussion.insights);
|
|
3938
|
+
const summary = await this.aiRunner.run(prompt);
|
|
3939
|
+
this.discussionManager.addMessage(discussionId, "assistant", summary);
|
|
3940
|
+
this.discussionManager.complete(discussionId);
|
|
3941
|
+
this.log("Discussion summarized and completed", "success");
|
|
3942
|
+
return summary;
|
|
3943
|
+
}
|
|
3944
|
+
parseInsights(response) {
|
|
3945
|
+
const insights = [];
|
|
3946
|
+
const insightRegex = /<insight>\s*([\s\S]*?)\s*<\/insight>/g;
|
|
3947
|
+
let match = insightRegex.exec(response);
|
|
3948
|
+
while (match !== null) {
|
|
3949
|
+
try {
|
|
3950
|
+
const parsed = JSON.parse(match[1]);
|
|
3951
|
+
const id = `ins-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
|
|
3952
|
+
insights.push({
|
|
3953
|
+
id,
|
|
3954
|
+
type: parsed.type,
|
|
3955
|
+
title: parsed.title,
|
|
3956
|
+
content: parsed.content,
|
|
3957
|
+
tags: parsed.tags ?? [],
|
|
3958
|
+
createdAt: new Date().toISOString()
|
|
3959
|
+
});
|
|
3960
|
+
} catch {}
|
|
3961
|
+
match = insightRegex.exec(response);
|
|
3962
|
+
}
|
|
3963
|
+
const cleanResponse = response.replace(/<insight>\s*[\s\S]*?\s*<\/insight>/g, "").replace(/\n{3,}/g, `
|
|
3964
|
+
|
|
3965
|
+
`).trim();
|
|
3966
|
+
return { cleanResponse, insights };
|
|
3967
|
+
}
|
|
3968
|
+
buildContext() {
|
|
3969
|
+
return {
|
|
3970
|
+
projectContext: this.getProjectContext(),
|
|
3971
|
+
learnings: this.getLearningsContent(),
|
|
3972
|
+
knowledgeBase: this.getKnowledgeBaseSection()
|
|
3973
|
+
};
|
|
3974
|
+
}
|
|
3975
|
+
getProjectContext() {
|
|
3976
|
+
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
3977
|
+
if (import_node_fs7.existsSync(contextPath)) {
|
|
3978
|
+
try {
|
|
3979
|
+
const context = import_node_fs7.readFileSync(contextPath, "utf-8");
|
|
3980
|
+
if (context.trim().length > 20) {
|
|
3981
|
+
return context;
|
|
3982
|
+
}
|
|
3983
|
+
} catch {
|
|
3984
|
+
return null;
|
|
3985
|
+
}
|
|
3986
|
+
}
|
|
3987
|
+
return null;
|
|
3988
|
+
}
|
|
3989
|
+
getLearningsContent() {
|
|
3990
|
+
const learningsPath = getLocusPath(this.projectPath, "learningsFile");
|
|
3991
|
+
if (!import_node_fs7.existsSync(learningsPath)) {
|
|
3992
|
+
return null;
|
|
3993
|
+
}
|
|
3994
|
+
try {
|
|
3995
|
+
const content = import_node_fs7.readFileSync(learningsPath, "utf-8");
|
|
3996
|
+
const lines = content.split(`
|
|
3997
|
+
`).filter((l) => l.startsWith("- "));
|
|
3998
|
+
if (lines.length === 0) {
|
|
3999
|
+
return null;
|
|
4000
|
+
}
|
|
4001
|
+
return lines.join(`
|
|
4002
|
+
`);
|
|
4003
|
+
} catch {
|
|
4004
|
+
return null;
|
|
4005
|
+
}
|
|
4006
|
+
}
|
|
4007
|
+
getKnowledgeBaseSection() {
|
|
4008
|
+
return `You have access to the following documentation directories for context:
|
|
4009
|
+
- Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
|
|
4010
|
+
- Documents: \`.locus/documents\` (synced from cloud)
|
|
4011
|
+
If you need more information about the project strategies, plans, or architecture, read files in these directories.`;
|
|
4012
|
+
}
|
|
4013
|
+
}
|
|
4014
|
+
|
|
4015
|
+
// src/discussion/index.ts
|
|
4016
|
+
init_discussion_manager();
|
|
4017
|
+
init_discussion_types();
|
|
3388
4018
|
// src/exec/context-tracker.ts
|
|
3389
4019
|
var REFERENCE_ALIASES = {
|
|
3390
4020
|
plan: ["the plan", "sprint plan", "project plan", "implementation plan"],
|
|
@@ -3799,8 +4429,8 @@ class ExecEventEmitter {
|
|
|
3799
4429
|
}
|
|
3800
4430
|
// src/exec/history-manager.ts
|
|
3801
4431
|
init_config();
|
|
3802
|
-
var
|
|
3803
|
-
var
|
|
4432
|
+
var import_node_fs8 = require("node:fs");
|
|
4433
|
+
var import_node_path9 = require("node:path");
|
|
3804
4434
|
var DEFAULT_MAX_SESSIONS = 30;
|
|
3805
4435
|
function generateSessionId2() {
|
|
3806
4436
|
const timestamp = Date.now().toString(36);
|
|
@@ -3812,30 +4442,30 @@ class HistoryManager {
|
|
|
3812
4442
|
historyDir;
|
|
3813
4443
|
maxSessions;
|
|
3814
4444
|
constructor(projectPath, options) {
|
|
3815
|
-
this.historyDir = options?.historyDir ??
|
|
4445
|
+
this.historyDir = options?.historyDir ?? import_node_path9.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.sessionsDir);
|
|
3816
4446
|
this.maxSessions = options?.maxSessions ?? DEFAULT_MAX_SESSIONS;
|
|
3817
4447
|
this.ensureHistoryDir();
|
|
3818
4448
|
}
|
|
3819
4449
|
ensureHistoryDir() {
|
|
3820
|
-
if (!
|
|
3821
|
-
|
|
4450
|
+
if (!import_node_fs8.existsSync(this.historyDir)) {
|
|
4451
|
+
import_node_fs8.mkdirSync(this.historyDir, { recursive: true });
|
|
3822
4452
|
}
|
|
3823
4453
|
}
|
|
3824
4454
|
getSessionPath(sessionId) {
|
|
3825
|
-
return
|
|
4455
|
+
return import_node_path9.join(this.historyDir, `${sessionId}.json`);
|
|
3826
4456
|
}
|
|
3827
4457
|
saveSession(session) {
|
|
3828
4458
|
const filePath = this.getSessionPath(session.id);
|
|
3829
4459
|
session.updatedAt = Date.now();
|
|
3830
|
-
|
|
4460
|
+
import_node_fs8.writeFileSync(filePath, JSON.stringify(session, null, 2), "utf-8");
|
|
3831
4461
|
}
|
|
3832
4462
|
loadSession(sessionId) {
|
|
3833
4463
|
const filePath = this.getSessionPath(sessionId);
|
|
3834
|
-
if (!
|
|
4464
|
+
if (!import_node_fs8.existsSync(filePath)) {
|
|
3835
4465
|
return null;
|
|
3836
4466
|
}
|
|
3837
4467
|
try {
|
|
3838
|
-
const content =
|
|
4468
|
+
const content = import_node_fs8.readFileSync(filePath, "utf-8");
|
|
3839
4469
|
return JSON.parse(content);
|
|
3840
4470
|
} catch {
|
|
3841
4471
|
return null;
|
|
@@ -3843,18 +4473,18 @@ class HistoryManager {
|
|
|
3843
4473
|
}
|
|
3844
4474
|
deleteSession(sessionId) {
|
|
3845
4475
|
const filePath = this.getSessionPath(sessionId);
|
|
3846
|
-
if (!
|
|
4476
|
+
if (!import_node_fs8.existsSync(filePath)) {
|
|
3847
4477
|
return false;
|
|
3848
4478
|
}
|
|
3849
4479
|
try {
|
|
3850
|
-
|
|
4480
|
+
import_node_fs8.rmSync(filePath);
|
|
3851
4481
|
return true;
|
|
3852
4482
|
} catch {
|
|
3853
4483
|
return false;
|
|
3854
4484
|
}
|
|
3855
4485
|
}
|
|
3856
4486
|
listSessions(options) {
|
|
3857
|
-
const files =
|
|
4487
|
+
const files = import_node_fs8.readdirSync(this.historyDir);
|
|
3858
4488
|
let sessions = [];
|
|
3859
4489
|
for (const file of files) {
|
|
3860
4490
|
if (file.endsWith(".json")) {
|
|
@@ -3927,11 +4557,11 @@ class HistoryManager {
|
|
|
3927
4557
|
return deleted;
|
|
3928
4558
|
}
|
|
3929
4559
|
getSessionCount() {
|
|
3930
|
-
const files =
|
|
4560
|
+
const files = import_node_fs8.readdirSync(this.historyDir);
|
|
3931
4561
|
return files.filter((f) => f.endsWith(".json")).length;
|
|
3932
4562
|
}
|
|
3933
4563
|
sessionExists(sessionId) {
|
|
3934
|
-
return
|
|
4564
|
+
return import_node_fs8.existsSync(this.getSessionPath(sessionId));
|
|
3935
4565
|
}
|
|
3936
4566
|
findSessionByPartialId(partialId) {
|
|
3937
4567
|
const sessions = this.listSessions();
|
|
@@ -3945,12 +4575,12 @@ class HistoryManager {
|
|
|
3945
4575
|
return this.historyDir;
|
|
3946
4576
|
}
|
|
3947
4577
|
clearAllSessions() {
|
|
3948
|
-
const files =
|
|
4578
|
+
const files = import_node_fs8.readdirSync(this.historyDir);
|
|
3949
4579
|
let deleted = 0;
|
|
3950
4580
|
for (const file of files) {
|
|
3951
4581
|
if (file.endsWith(".json")) {
|
|
3952
4582
|
try {
|
|
3953
|
-
|
|
4583
|
+
import_node_fs8.rmSync(import_node_path9.join(this.historyDir, file));
|
|
3954
4584
|
deleted++;
|
|
3955
4585
|
} catch {}
|
|
3956
4586
|
}
|
|
@@ -4226,8 +4856,8 @@ init_src();
|
|
|
4226
4856
|
init_colors();
|
|
4227
4857
|
init_resolve_bin();
|
|
4228
4858
|
var import_node_child_process7 = require("node:child_process");
|
|
4229
|
-
var
|
|
4230
|
-
var
|
|
4859
|
+
var import_node_fs9 = require("node:fs");
|
|
4860
|
+
var import_node_path10 = require("node:path");
|
|
4231
4861
|
var import_node_url = require("node:url");
|
|
4232
4862
|
var import_shared4 = require("@locusai/shared");
|
|
4233
4863
|
var import_events4 = require("events");
|
|
@@ -4495,14 +5125,14 @@ ${agentId} finished (exit code: ${code})`);
|
|
|
4495
5125
|
}
|
|
4496
5126
|
resolveWorkerPath() {
|
|
4497
5127
|
const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator/index.ts");
|
|
4498
|
-
const currentModuleDir =
|
|
5128
|
+
const currentModuleDir = import_node_path10.dirname(currentModulePath);
|
|
4499
5129
|
const potentialPaths = [
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
5130
|
+
import_node_path10.join(currentModuleDir, "..", "agent", "worker.js"),
|
|
5131
|
+
import_node_path10.join(currentModuleDir, "agent", "worker.js"),
|
|
5132
|
+
import_node_path10.join(currentModuleDir, "worker.js"),
|
|
5133
|
+
import_node_path10.join(currentModuleDir, "..", "agent", "worker.ts")
|
|
4504
5134
|
];
|
|
4505
|
-
return potentialPaths.find((p) =>
|
|
5135
|
+
return potentialPaths.find((p) => import_node_fs9.existsSync(p));
|
|
4506
5136
|
}
|
|
4507
5137
|
}
|
|
4508
5138
|
function killProcessTree(proc) {
|
|
@@ -4521,12 +5151,12 @@ function sleep(ms) {
|
|
|
4521
5151
|
}
|
|
4522
5152
|
// src/planning/plan-manager.ts
|
|
4523
5153
|
init_config();
|
|
4524
|
-
var
|
|
4525
|
-
var
|
|
5154
|
+
var import_node_fs10 = require("node:fs");
|
|
5155
|
+
var import_node_path11 = require("node:path");
|
|
4526
5156
|
|
|
4527
5157
|
// src/planning/sprint-plan.ts
|
|
4528
5158
|
var import_shared5 = require("@locusai/shared");
|
|
4529
|
-
var
|
|
5159
|
+
var import_zod2 = require("zod");
|
|
4530
5160
|
|
|
4531
5161
|
// src/utils/structured-output.ts
|
|
4532
5162
|
function parseJsonWithSchema(raw, schema) {
|
|
@@ -4551,26 +5181,26 @@ Parsed JSON preview: ${JSON.stringify(parsed).slice(0, 300)}`);
|
|
|
4551
5181
|
}
|
|
4552
5182
|
|
|
4553
5183
|
// src/planning/sprint-plan.ts
|
|
4554
|
-
var PlannedTaskSchema =
|
|
4555
|
-
title:
|
|
4556
|
-
description:
|
|
4557
|
-
assigneeRole:
|
|
4558
|
-
priority:
|
|
4559
|
-
complexity:
|
|
4560
|
-
acceptanceCriteria:
|
|
4561
|
-
labels:
|
|
5184
|
+
var PlannedTaskSchema = import_zod2.z.object({
|
|
5185
|
+
title: import_zod2.z.string().default("Untitled Task"),
|
|
5186
|
+
description: import_zod2.z.string().default(""),
|
|
5187
|
+
assigneeRole: import_zod2.z.enum(["BACKEND", "FRONTEND", "QA", "PM", "DESIGN"]).default("BACKEND"),
|
|
5188
|
+
priority: import_zod2.z.enum(["CRITICAL", "HIGH", "MEDIUM", "LOW"]).default("MEDIUM"),
|
|
5189
|
+
complexity: import_zod2.z.number().min(1).max(5).default(3),
|
|
5190
|
+
acceptanceCriteria: import_zod2.z.array(import_zod2.z.string()).default([]),
|
|
5191
|
+
labels: import_zod2.z.array(import_zod2.z.string()).default([])
|
|
4562
5192
|
});
|
|
4563
|
-
var SprintPlanRiskSchema =
|
|
4564
|
-
description:
|
|
4565
|
-
mitigation:
|
|
4566
|
-
severity:
|
|
5193
|
+
var SprintPlanRiskSchema = import_zod2.z.object({
|
|
5194
|
+
description: import_zod2.z.string().default(""),
|
|
5195
|
+
mitigation: import_zod2.z.string().default(""),
|
|
5196
|
+
severity: import_zod2.z.enum(["low", "medium", "high"]).default("medium")
|
|
4567
5197
|
});
|
|
4568
|
-
var PlannerOutputSchema =
|
|
4569
|
-
name:
|
|
4570
|
-
goal:
|
|
4571
|
-
estimatedDays:
|
|
4572
|
-
tasks:
|
|
4573
|
-
risks:
|
|
5198
|
+
var PlannerOutputSchema = import_zod2.z.object({
|
|
5199
|
+
name: import_zod2.z.string().default("Unnamed Sprint"),
|
|
5200
|
+
goal: import_zod2.z.string().default(""),
|
|
5201
|
+
estimatedDays: import_zod2.z.number().default(1),
|
|
5202
|
+
tasks: import_zod2.z.array(PlannedTaskSchema).default([]),
|
|
5203
|
+
risks: import_zod2.z.array(SprintPlanRiskSchema).default([])
|
|
4574
5204
|
});
|
|
4575
5205
|
var SprintPlanAIOutputSchema = PlannerOutputSchema;
|
|
4576
5206
|
function sprintPlanToMarkdown(plan) {
|
|
@@ -4687,19 +5317,19 @@ class PlanManager {
|
|
|
4687
5317
|
save(plan) {
|
|
4688
5318
|
this.ensurePlansDir();
|
|
4689
5319
|
const slug = this.slugify(plan.name);
|
|
4690
|
-
const jsonPath =
|
|
4691
|
-
const mdPath =
|
|
4692
|
-
|
|
4693
|
-
|
|
5320
|
+
const jsonPath = import_node_path11.join(this.plansDir, `${slug}.json`);
|
|
5321
|
+
const mdPath = import_node_path11.join(this.plansDir, `sprint-${slug}.md`);
|
|
5322
|
+
import_node_fs10.writeFileSync(jsonPath, JSON.stringify(plan, null, 2), "utf-8");
|
|
5323
|
+
import_node_fs10.writeFileSync(mdPath, sprintPlanToMarkdown(plan), "utf-8");
|
|
4694
5324
|
return plan.id;
|
|
4695
5325
|
}
|
|
4696
5326
|
load(idOrSlug) {
|
|
4697
5327
|
this.ensurePlansDir();
|
|
4698
|
-
const files =
|
|
5328
|
+
const files = import_node_fs10.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
|
|
4699
5329
|
for (const file of files) {
|
|
4700
|
-
const filePath =
|
|
5330
|
+
const filePath = import_node_path11.join(this.plansDir, file);
|
|
4701
5331
|
try {
|
|
4702
|
-
const plan = JSON.parse(
|
|
5332
|
+
const plan = JSON.parse(import_node_fs10.readFileSync(filePath, "utf-8"));
|
|
4703
5333
|
if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
|
|
4704
5334
|
return plan;
|
|
4705
5335
|
}
|
|
@@ -4709,11 +5339,11 @@ class PlanManager {
|
|
|
4709
5339
|
}
|
|
4710
5340
|
list(status) {
|
|
4711
5341
|
this.ensurePlansDir();
|
|
4712
|
-
const files =
|
|
5342
|
+
const files = import_node_fs10.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
|
|
4713
5343
|
const plans = [];
|
|
4714
5344
|
for (const file of files) {
|
|
4715
5345
|
try {
|
|
4716
|
-
const plan = JSON.parse(
|
|
5346
|
+
const plan = JSON.parse(import_node_fs10.readFileSync(import_node_path11.join(this.plansDir, file), "utf-8"));
|
|
4717
5347
|
if (!status || plan.status === status) {
|
|
4718
5348
|
plans.push(plan);
|
|
4719
5349
|
}
|
|
@@ -4770,18 +5400,18 @@ class PlanManager {
|
|
|
4770
5400
|
}
|
|
4771
5401
|
delete(idOrSlug) {
|
|
4772
5402
|
this.ensurePlansDir();
|
|
4773
|
-
const files =
|
|
5403
|
+
const files = import_node_fs10.readdirSync(this.plansDir);
|
|
4774
5404
|
for (const file of files) {
|
|
4775
|
-
const filePath =
|
|
5405
|
+
const filePath = import_node_path11.join(this.plansDir, file);
|
|
4776
5406
|
if (!file.endsWith(".json"))
|
|
4777
5407
|
continue;
|
|
4778
5408
|
try {
|
|
4779
|
-
const plan = JSON.parse(
|
|
5409
|
+
const plan = JSON.parse(import_node_fs10.readFileSync(filePath, "utf-8"));
|
|
4780
5410
|
if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
|
|
4781
|
-
|
|
4782
|
-
const mdPath =
|
|
4783
|
-
if (
|
|
4784
|
-
|
|
5411
|
+
import_node_fs10.unlinkSync(filePath);
|
|
5412
|
+
const mdPath = import_node_path11.join(this.plansDir, `sprint-${this.slugify(plan.name)}.md`);
|
|
5413
|
+
if (import_node_fs10.existsSync(mdPath)) {
|
|
5414
|
+
import_node_fs10.unlinkSync(mdPath);
|
|
4785
5415
|
}
|
|
4786
5416
|
return;
|
|
4787
5417
|
}
|
|
@@ -4795,8 +5425,8 @@ class PlanManager {
|
|
|
4795
5425
|
return sprintPlanToMarkdown(plan);
|
|
4796
5426
|
}
|
|
4797
5427
|
ensurePlansDir() {
|
|
4798
|
-
if (!
|
|
4799
|
-
|
|
5428
|
+
if (!import_node_fs10.existsSync(this.plansDir)) {
|
|
5429
|
+
import_node_fs10.mkdirSync(this.plansDir, { recursive: true });
|
|
4800
5430
|
}
|
|
4801
5431
|
}
|
|
4802
5432
|
slugify(name) {
|
|
@@ -4805,8 +5435,8 @@ class PlanManager {
|
|
|
4805
5435
|
}
|
|
4806
5436
|
// src/planning/planning-meeting.ts
|
|
4807
5437
|
init_config();
|
|
4808
|
-
var
|
|
4809
|
-
var
|
|
5438
|
+
var import_node_fs11 = require("node:fs");
|
|
5439
|
+
var import_node_path12 = require("node:path");
|
|
4810
5440
|
|
|
4811
5441
|
// src/planning/agents/planner.ts
|
|
4812
5442
|
function buildPlannerPrompt(input) {
|
|
@@ -4892,8 +5522,8 @@ class PlanningMeeting {
|
|
|
4892
5522
|
async run(directive, feedback) {
|
|
4893
5523
|
this.log("Planning sprint...", "info");
|
|
4894
5524
|
const plansDir = getLocusPath(this.projectPath, "plansDir");
|
|
4895
|
-
if (!
|
|
4896
|
-
|
|
5525
|
+
if (!import_node_fs11.existsSync(plansDir)) {
|
|
5526
|
+
import_node_fs11.mkdirSync(plansDir, { recursive: true });
|
|
4897
5527
|
}
|
|
4898
5528
|
const ts = Date.now();
|
|
4899
5529
|
const planId = `plan-${ts}`;
|
|
@@ -4907,11 +5537,11 @@ class PlanningMeeting {
|
|
|
4907
5537
|
});
|
|
4908
5538
|
const response = await this.aiRunner.run(prompt);
|
|
4909
5539
|
this.log("Planning meeting complete.", "success");
|
|
4910
|
-
const expectedPath =
|
|
5540
|
+
const expectedPath = import_node_path12.join(plansDir, `${fileName}.json`);
|
|
4911
5541
|
let plan = null;
|
|
4912
|
-
if (
|
|
5542
|
+
if (import_node_fs11.existsSync(expectedPath)) {
|
|
4913
5543
|
try {
|
|
4914
|
-
plan = JSON.parse(
|
|
5544
|
+
plan = JSON.parse(import_node_fs11.readFileSync(expectedPath, "utf-8"));
|
|
4915
5545
|
} catch {}
|
|
4916
5546
|
}
|
|
4917
5547
|
if (!plan) {
|