@locusai/sdk 0.13.0 → 0.13.3
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 +333 -15
- package/dist/ai/__tests__/factory.test.d.ts +2 -0
- package/dist/ai/__tests__/factory.test.d.ts.map +1 -0
- package/dist/ai/factory.d.ts.map +1 -1
- package/dist/core/config.d.ts +25 -2
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.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 +773 -108
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +37 -0
- package/package.json +3 -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();
|
|
@@ -521,6 +558,12 @@ var init_src = __esm(() => {
|
|
|
521
558
|
});
|
|
522
559
|
|
|
523
560
|
// src/core/config.ts
|
|
561
|
+
function isValidModelForProvider(provider, model) {
|
|
562
|
+
return PROVIDER_MODELS[provider].includes(model);
|
|
563
|
+
}
|
|
564
|
+
function getModelsForProvider(provider) {
|
|
565
|
+
return PROVIDER_MODELS[provider];
|
|
566
|
+
}
|
|
524
567
|
function getLocusPath(projectPath, fileName) {
|
|
525
568
|
return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG[fileName]);
|
|
526
569
|
}
|
|
@@ -528,16 +571,36 @@ function getAgentArtifactsPath(projectPath, agentId) {
|
|
|
528
571
|
const shortId = agentId.slice(-8);
|
|
529
572
|
return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.artifactsDir, shortId);
|
|
530
573
|
}
|
|
531
|
-
var import_node_path, PROVIDER, DEFAULT_MODEL, LOCUS_SCHEMA_BASE_URL = "https://locusai.dev/schemas", LOCUS_SCHEMAS, LOCUS_CONFIG, LOCUS_GITIGNORE_PATTERNS;
|
|
574
|
+
var import_node_path, PROVIDER, CLAUDE_MODELS, CODEX_MODELS, PROVIDER_MODELS, DEFAULT_MODEL, LOCUS_SCHEMA_BASE_URL = "https://locusai.dev/schemas", LOCUS_SCHEMAS, LOCUS_CONFIG, LOCUS_GITIGNORE_PATTERNS;
|
|
532
575
|
var init_config = __esm(() => {
|
|
533
576
|
import_node_path = require("node:path");
|
|
534
577
|
PROVIDER = {
|
|
535
578
|
CLAUDE: "claude",
|
|
536
579
|
CODEX: "codex"
|
|
537
580
|
};
|
|
581
|
+
CLAUDE_MODELS = {
|
|
582
|
+
OPUS: "opus",
|
|
583
|
+
SONNET: "sonnet",
|
|
584
|
+
HAIKU: "haiku",
|
|
585
|
+
OPUS_PLAN: "opusplan",
|
|
586
|
+
CLAUDE_OPUS_4_6: "claude-opus-4-6",
|
|
587
|
+
CLAUDE_SONNET_4_5: "claude-sonnet-4-5-20250929",
|
|
588
|
+
CLAUDE_SONNET_4_6: "claude-sonnet-4-6",
|
|
589
|
+
CLAUDE_HAIKU_4_5: "claude-haiku-4-5-20251001"
|
|
590
|
+
};
|
|
591
|
+
CODEX_MODELS = {
|
|
592
|
+
GPT_5_3_CODEX: "gpt-5.3-codex",
|
|
593
|
+
GPT_5_3_CODEX_SPARK: "gpt-5.3-codex-spark",
|
|
594
|
+
GPT_5_CODEX_MINI: "gpt-5-codex-mini",
|
|
595
|
+
GPT_5_2_CODEX: "gpt-5.2-codex"
|
|
596
|
+
};
|
|
597
|
+
PROVIDER_MODELS = {
|
|
598
|
+
[PROVIDER.CLAUDE]: Object.values(CLAUDE_MODELS),
|
|
599
|
+
[PROVIDER.CODEX]: Object.values(CODEX_MODELS)
|
|
600
|
+
};
|
|
538
601
|
DEFAULT_MODEL = {
|
|
539
|
-
[PROVIDER.CLAUDE]:
|
|
540
|
-
[PROVIDER.CODEX]:
|
|
602
|
+
[PROVIDER.CLAUDE]: CLAUDE_MODELS.OPUS,
|
|
603
|
+
[PROVIDER.CODEX]: CODEX_MODELS.GPT_5_3_CODEX
|
|
541
604
|
};
|
|
542
605
|
LOCUS_SCHEMAS = {
|
|
543
606
|
config: `${LOCUS_SCHEMA_BASE_URL}/config.schema.json`,
|
|
@@ -554,7 +617,8 @@ var init_config = __esm(() => {
|
|
|
554
617
|
documentsDir: "documents",
|
|
555
618
|
sessionsDir: "sessions",
|
|
556
619
|
reviewsDir: "reviews",
|
|
557
|
-
plansDir: "plans"
|
|
620
|
+
plansDir: "plans",
|
|
621
|
+
discussionsDir: "discussions"
|
|
558
622
|
};
|
|
559
623
|
LOCUS_GITIGNORE_PATTERNS = [
|
|
560
624
|
"# Locus AI - Session data (user-specific, can grow large)",
|
|
@@ -569,6 +633,9 @@ var init_config = __esm(() => {
|
|
|
569
633
|
"# Locus AI - Plans (generated per task)",
|
|
570
634
|
".locus/plans/",
|
|
571
635
|
"",
|
|
636
|
+
"# Locus AI - Discussions (AI discussion sessions)",
|
|
637
|
+
".locus/discussions/",
|
|
638
|
+
"",
|
|
572
639
|
"# Locus AI - Settings (contains API key, telegram config, etc.)",
|
|
573
640
|
".locus/settings.json",
|
|
574
641
|
"",
|
|
@@ -1517,6 +1584,10 @@ var init_codex_runner = __esm(() => {
|
|
|
1517
1584
|
function createAiRunner(provider, config) {
|
|
1518
1585
|
const resolvedProvider = provider ?? PROVIDER.CLAUDE;
|
|
1519
1586
|
const model = config.model ?? DEFAULT_MODEL[resolvedProvider];
|
|
1587
|
+
if (!isValidModelForProvider(resolvedProvider, model)) {
|
|
1588
|
+
const validModels = getModelsForProvider(resolvedProvider);
|
|
1589
|
+
throw new Error(`Model "${model}" is not valid for provider "${resolvedProvider}". ` + `Valid models: ${validModels.join(", ")}`);
|
|
1590
|
+
}
|
|
1520
1591
|
switch (resolvedProvider) {
|
|
1521
1592
|
case PROVIDER.CODEX:
|
|
1522
1593
|
return new CodexRunner(config.projectPath, model, config.log, config.reasoningEffort ?? "high", config.timeoutMs);
|
|
@@ -1871,6 +1942,187 @@ var init_git_workflow = __esm(() => {
|
|
|
1871
1942
|
import_node_child_process4 = require("node:child_process");
|
|
1872
1943
|
});
|
|
1873
1944
|
|
|
1945
|
+
// src/discussion/discussion-manager.ts
|
|
1946
|
+
class DiscussionManager {
|
|
1947
|
+
discussionsDir;
|
|
1948
|
+
constructor(projectPath) {
|
|
1949
|
+
this.discussionsDir = getLocusPath(projectPath, "discussionsDir");
|
|
1950
|
+
}
|
|
1951
|
+
create(topic, model, provider) {
|
|
1952
|
+
this.ensureDir();
|
|
1953
|
+
const now = new Date().toISOString();
|
|
1954
|
+
const id = `disc-${Date.now()}`;
|
|
1955
|
+
const discussion = {
|
|
1956
|
+
id,
|
|
1957
|
+
title: topic,
|
|
1958
|
+
topic,
|
|
1959
|
+
status: "active",
|
|
1960
|
+
messages: [],
|
|
1961
|
+
insights: [],
|
|
1962
|
+
createdAt: now,
|
|
1963
|
+
updatedAt: now,
|
|
1964
|
+
metadata: { model, provider }
|
|
1965
|
+
};
|
|
1966
|
+
this.save(discussion);
|
|
1967
|
+
return discussion;
|
|
1968
|
+
}
|
|
1969
|
+
save(discussion) {
|
|
1970
|
+
this.ensureDir();
|
|
1971
|
+
const jsonPath = import_node_path5.join(this.discussionsDir, `${discussion.id}.json`);
|
|
1972
|
+
const mdPath = import_node_path5.join(this.discussionsDir, `summary-${discussion.id}.md`);
|
|
1973
|
+
import_node_fs3.writeFileSync(jsonPath, JSON.stringify(discussion, null, 2), "utf-8");
|
|
1974
|
+
import_node_fs3.writeFileSync(mdPath, this.toMarkdown(discussion), "utf-8");
|
|
1975
|
+
}
|
|
1976
|
+
load(id) {
|
|
1977
|
+
this.ensureDir();
|
|
1978
|
+
const filePath = import_node_path5.join(this.discussionsDir, `${id}.json`);
|
|
1979
|
+
if (!import_node_fs3.existsSync(filePath)) {
|
|
1980
|
+
return null;
|
|
1981
|
+
}
|
|
1982
|
+
try {
|
|
1983
|
+
return JSON.parse(import_node_fs3.readFileSync(filePath, "utf-8"));
|
|
1984
|
+
} catch {
|
|
1985
|
+
return null;
|
|
1986
|
+
}
|
|
1987
|
+
}
|
|
1988
|
+
list(status) {
|
|
1989
|
+
this.ensureDir();
|
|
1990
|
+
const files = import_node_fs3.readdirSync(this.discussionsDir).filter((f) => f.endsWith(".json"));
|
|
1991
|
+
const discussions = [];
|
|
1992
|
+
for (const file of files) {
|
|
1993
|
+
try {
|
|
1994
|
+
const discussion = JSON.parse(import_node_fs3.readFileSync(import_node_path5.join(this.discussionsDir, file), "utf-8"));
|
|
1995
|
+
if (!status || discussion.status === status) {
|
|
1996
|
+
discussions.push(discussion);
|
|
1997
|
+
}
|
|
1998
|
+
} catch {}
|
|
1999
|
+
}
|
|
2000
|
+
discussions.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
2001
|
+
return discussions;
|
|
2002
|
+
}
|
|
2003
|
+
complete(id) {
|
|
2004
|
+
const discussion = this.load(id);
|
|
2005
|
+
if (!discussion) {
|
|
2006
|
+
throw new Error(`Discussion not found: ${id}`);
|
|
2007
|
+
}
|
|
2008
|
+
discussion.status = "completed";
|
|
2009
|
+
discussion.updatedAt = new Date().toISOString();
|
|
2010
|
+
this.save(discussion);
|
|
2011
|
+
return discussion;
|
|
2012
|
+
}
|
|
2013
|
+
archive(id) {
|
|
2014
|
+
const discussion = this.load(id);
|
|
2015
|
+
if (!discussion) {
|
|
2016
|
+
throw new Error(`Discussion not found: ${id}`);
|
|
2017
|
+
}
|
|
2018
|
+
discussion.status = "archived";
|
|
2019
|
+
discussion.updatedAt = new Date().toISOString();
|
|
2020
|
+
this.save(discussion);
|
|
2021
|
+
}
|
|
2022
|
+
delete(id) {
|
|
2023
|
+
this.ensureDir();
|
|
2024
|
+
const jsonPath = import_node_path5.join(this.discussionsDir, `${id}.json`);
|
|
2025
|
+
const mdPath = import_node_path5.join(this.discussionsDir, `summary-${id}.md`);
|
|
2026
|
+
if (import_node_fs3.existsSync(jsonPath)) {
|
|
2027
|
+
import_node_fs3.unlinkSync(jsonPath);
|
|
2028
|
+
}
|
|
2029
|
+
if (import_node_fs3.existsSync(mdPath)) {
|
|
2030
|
+
import_node_fs3.unlinkSync(mdPath);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
addMessage(id, role, content) {
|
|
2034
|
+
const discussion = this.load(id);
|
|
2035
|
+
if (!discussion) {
|
|
2036
|
+
throw new Error(`Discussion not found: ${id}`);
|
|
2037
|
+
}
|
|
2038
|
+
discussion.messages.push({
|
|
2039
|
+
role,
|
|
2040
|
+
content,
|
|
2041
|
+
timestamp: Date.now()
|
|
2042
|
+
});
|
|
2043
|
+
discussion.updatedAt = new Date().toISOString();
|
|
2044
|
+
this.save(discussion);
|
|
2045
|
+
return discussion;
|
|
2046
|
+
}
|
|
2047
|
+
addInsight(id, insight) {
|
|
2048
|
+
const discussion = this.load(id);
|
|
2049
|
+
if (!discussion) {
|
|
2050
|
+
throw new Error(`Discussion not found: ${id}`);
|
|
2051
|
+
}
|
|
2052
|
+
discussion.insights.push(insight);
|
|
2053
|
+
discussion.updatedAt = new Date().toISOString();
|
|
2054
|
+
this.save(discussion);
|
|
2055
|
+
return discussion;
|
|
2056
|
+
}
|
|
2057
|
+
getAllInsights() {
|
|
2058
|
+
const discussions = this.list("completed");
|
|
2059
|
+
const insights = [];
|
|
2060
|
+
for (const discussion of discussions) {
|
|
2061
|
+
insights.push(...discussion.insights);
|
|
2062
|
+
}
|
|
2063
|
+
insights.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
2064
|
+
return insights;
|
|
2065
|
+
}
|
|
2066
|
+
getMarkdown(id) {
|
|
2067
|
+
const discussion = this.load(id);
|
|
2068
|
+
if (!discussion)
|
|
2069
|
+
return null;
|
|
2070
|
+
return this.toMarkdown(discussion);
|
|
2071
|
+
}
|
|
2072
|
+
toMarkdown(discussion) {
|
|
2073
|
+
const lines = [];
|
|
2074
|
+
lines.push(`# Discussion: ${discussion.title}`);
|
|
2075
|
+
lines.push("");
|
|
2076
|
+
lines.push(`**Status:** ${discussion.status.toUpperCase()}`);
|
|
2077
|
+
lines.push(`**Topic:** ${discussion.topic}`);
|
|
2078
|
+
lines.push(`**Created:** ${discussion.createdAt}`);
|
|
2079
|
+
lines.push(`**Updated:** ${discussion.updatedAt}`);
|
|
2080
|
+
lines.push(`**Model:** ${discussion.metadata.model} (${discussion.metadata.provider})`);
|
|
2081
|
+
lines.push("");
|
|
2082
|
+
if (discussion.messages.length > 0) {
|
|
2083
|
+
lines.push(`## Messages (${discussion.messages.length})`);
|
|
2084
|
+
lines.push("");
|
|
2085
|
+
for (const msg of discussion.messages) {
|
|
2086
|
+
const time = new Date(msg.timestamp).toISOString();
|
|
2087
|
+
const roleLabel = msg.role === "user" ? "User" : "Assistant";
|
|
2088
|
+
lines.push(`### ${roleLabel} — ${time}`);
|
|
2089
|
+
lines.push("");
|
|
2090
|
+
lines.push(msg.content);
|
|
2091
|
+
lines.push("");
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
if (discussion.insights.length > 0) {
|
|
2095
|
+
lines.push(`## Insights (${discussion.insights.length})`);
|
|
2096
|
+
lines.push("");
|
|
2097
|
+
for (const insight of discussion.insights) {
|
|
2098
|
+
lines.push(`### [${insight.type.toUpperCase()}] ${insight.title}`);
|
|
2099
|
+
lines.push("");
|
|
2100
|
+
lines.push(insight.content);
|
|
2101
|
+
if (insight.tags.length > 0) {
|
|
2102
|
+
lines.push("");
|
|
2103
|
+
lines.push(`**Tags:** ${insight.tags.join(", ")}`);
|
|
2104
|
+
}
|
|
2105
|
+
lines.push("");
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
lines.push("---");
|
|
2109
|
+
lines.push(`*Discussion ID: ${discussion.id}*`);
|
|
2110
|
+
return lines.join(`
|
|
2111
|
+
`);
|
|
2112
|
+
}
|
|
2113
|
+
ensureDir() {
|
|
2114
|
+
if (!import_node_fs3.existsSync(this.discussionsDir)) {
|
|
2115
|
+
import_node_fs3.mkdirSync(this.discussionsDir, { recursive: true });
|
|
2116
|
+
}
|
|
2117
|
+
}
|
|
2118
|
+
}
|
|
2119
|
+
var import_node_fs3, import_node_path5;
|
|
2120
|
+
var init_discussion_manager = __esm(() => {
|
|
2121
|
+
init_config();
|
|
2122
|
+
import_node_fs3 = require("node:fs");
|
|
2123
|
+
import_node_path5 = require("node:path");
|
|
2124
|
+
});
|
|
2125
|
+
|
|
1874
2126
|
// src/core/prompt-builder.ts
|
|
1875
2127
|
class PromptBuilder {
|
|
1876
2128
|
projectPath;
|
|
@@ -1909,6 +2161,15 @@ ${knowledgeBase}
|
|
|
1909
2161
|
These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
|
|
1910
2162
|
${learnings}
|
|
1911
2163
|
</learnings>
|
|
2164
|
+
`;
|
|
2165
|
+
}
|
|
2166
|
+
const discussionInsights = this.getDiscussionInsightsContent();
|
|
2167
|
+
if (discussionInsights) {
|
|
2168
|
+
sections += `
|
|
2169
|
+
<discussion_insights>
|
|
2170
|
+
These are key decisions and insights from product discussions. Follow them to maintain product coherence:
|
|
2171
|
+
${discussionInsights}
|
|
2172
|
+
</discussion_insights>
|
|
1912
2173
|
`;
|
|
1913
2174
|
}
|
|
1914
2175
|
if (task.docs && task.docs.length > 0) {
|
|
@@ -1997,6 +2258,15 @@ ${knowledgeBase}
|
|
|
1997
2258
|
These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
|
|
1998
2259
|
${learnings}
|
|
1999
2260
|
</learnings>
|
|
2261
|
+
`;
|
|
2262
|
+
}
|
|
2263
|
+
const discussionInsights = this.getDiscussionInsightsContent();
|
|
2264
|
+
if (discussionInsights) {
|
|
2265
|
+
sections += `
|
|
2266
|
+
<discussion_insights>
|
|
2267
|
+
These are key decisions and insights from product discussions. Follow them to maintain product coherence:
|
|
2268
|
+
${discussionInsights}
|
|
2269
|
+
</discussion_insights>
|
|
2000
2270
|
`;
|
|
2001
2271
|
}
|
|
2002
2272
|
return `<direct_execution>
|
|
@@ -2011,9 +2281,9 @@ ${sections}
|
|
|
2011
2281
|
}
|
|
2012
2282
|
getProjectContext() {
|
|
2013
2283
|
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
2014
|
-
if (
|
|
2284
|
+
if (import_node_fs4.existsSync(contextPath)) {
|
|
2015
2285
|
try {
|
|
2016
|
-
const context =
|
|
2286
|
+
const context = import_node_fs4.readFileSync(contextPath, "utf-8");
|
|
2017
2287
|
if (context.trim().length > 20) {
|
|
2018
2288
|
return context;
|
|
2019
2289
|
}
|
|
@@ -2024,10 +2294,10 @@ ${sections}
|
|
|
2024
2294
|
return this.getFallbackContext() || null;
|
|
2025
2295
|
}
|
|
2026
2296
|
getFallbackContext() {
|
|
2027
|
-
const readmePath =
|
|
2028
|
-
if (
|
|
2297
|
+
const readmePath = import_node_path6.join(this.projectPath, "README.md");
|
|
2298
|
+
if (import_node_fs4.existsSync(readmePath)) {
|
|
2029
2299
|
try {
|
|
2030
|
-
const content =
|
|
2300
|
+
const content = import_node_fs4.readFileSync(readmePath, "utf-8");
|
|
2031
2301
|
const limit = 1000;
|
|
2032
2302
|
return content.slice(0, limit) + (content.length > limit ? `
|
|
2033
2303
|
...(truncated)...` : "");
|
|
@@ -2041,15 +2311,16 @@ ${sections}
|
|
|
2041
2311
|
return `You have access to the following documentation directories for context:
|
|
2042
2312
|
- Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
|
|
2043
2313
|
- Documents: \`.locus/documents\` (synced from cloud)
|
|
2314
|
+
- Discussions: \`.locus/discussions\` (product discussion insights and decisions)
|
|
2044
2315
|
If you need more information about the project strategies, plans, or architecture, read files in these directories.`;
|
|
2045
2316
|
}
|
|
2046
2317
|
getLearningsContent() {
|
|
2047
2318
|
const learningsPath = getLocusPath(this.projectPath, "learningsFile");
|
|
2048
|
-
if (!
|
|
2319
|
+
if (!import_node_fs4.existsSync(learningsPath)) {
|
|
2049
2320
|
return null;
|
|
2050
2321
|
}
|
|
2051
2322
|
try {
|
|
2052
|
-
const content =
|
|
2323
|
+
const content = import_node_fs4.readFileSync(learningsPath, "utf-8");
|
|
2053
2324
|
const lines = content.split(`
|
|
2054
2325
|
`).filter((l) => l.startsWith("- "));
|
|
2055
2326
|
if (lines.length === 0) {
|
|
@@ -2061,6 +2332,53 @@ If you need more information about the project strategies, plans, or architectur
|
|
|
2061
2332
|
return null;
|
|
2062
2333
|
}
|
|
2063
2334
|
}
|
|
2335
|
+
getDiscussionInsightsContent() {
|
|
2336
|
+
try {
|
|
2337
|
+
const manager = new DiscussionManager(this.projectPath);
|
|
2338
|
+
const insights = manager.getAllInsights();
|
|
2339
|
+
if (insights.length === 0) {
|
|
2340
|
+
return null;
|
|
2341
|
+
}
|
|
2342
|
+
const groups = {};
|
|
2343
|
+
for (const insight of insights) {
|
|
2344
|
+
const key = insight.type;
|
|
2345
|
+
if (!groups[key]) {
|
|
2346
|
+
groups[key] = [];
|
|
2347
|
+
}
|
|
2348
|
+
groups[key].push(insight);
|
|
2349
|
+
}
|
|
2350
|
+
const typeLabels = {
|
|
2351
|
+
decision: "Decisions",
|
|
2352
|
+
requirement: "Requirements",
|
|
2353
|
+
idea: "Ideas",
|
|
2354
|
+
concern: "Concerns",
|
|
2355
|
+
learning: "Learnings"
|
|
2356
|
+
};
|
|
2357
|
+
let output = "";
|
|
2358
|
+
for (const [type, label] of Object.entries(typeLabels)) {
|
|
2359
|
+
const items = groups[type];
|
|
2360
|
+
if (!items || items.length === 0)
|
|
2361
|
+
continue;
|
|
2362
|
+
output += `## ${label}
|
|
2363
|
+
`;
|
|
2364
|
+
for (const item of items) {
|
|
2365
|
+
output += `- [${item.title}]: ${item.content}
|
|
2366
|
+
`;
|
|
2367
|
+
}
|
|
2368
|
+
output += `
|
|
2369
|
+
`;
|
|
2370
|
+
}
|
|
2371
|
+
if (output.length === 0) {
|
|
2372
|
+
return null;
|
|
2373
|
+
}
|
|
2374
|
+
if (output.length > 2000) {
|
|
2375
|
+
output = `${output.slice(0, 1997)}...`;
|
|
2376
|
+
}
|
|
2377
|
+
return output.trimEnd();
|
|
2378
|
+
} catch {
|
|
2379
|
+
return null;
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2064
2382
|
roleToText(role) {
|
|
2065
2383
|
if (!role) {
|
|
2066
2384
|
return null;
|
|
@@ -2081,11 +2399,12 @@ If you need more information about the project strategies, plans, or architectur
|
|
|
2081
2399
|
}
|
|
2082
2400
|
}
|
|
2083
2401
|
}
|
|
2084
|
-
var
|
|
2402
|
+
var import_node_fs4, import_node_path6, import_shared2;
|
|
2085
2403
|
var init_prompt_builder = __esm(() => {
|
|
2404
|
+
init_discussion_manager();
|
|
2086
2405
|
init_config();
|
|
2087
|
-
|
|
2088
|
-
|
|
2406
|
+
import_node_fs4 = require("node:fs");
|
|
2407
|
+
import_node_path6 = require("node:path");
|
|
2089
2408
|
import_shared2 = require("@locusai/shared");
|
|
2090
2409
|
});
|
|
2091
2410
|
|
|
@@ -2408,7 +2727,6 @@ Branch: \`${result.branch}\`` : "";
|
|
|
2408
2727
|
this.log("All tasks done. Creating pull request...", "info");
|
|
2409
2728
|
const prResult = this.gitWorkflow.createPullRequest(this.completedTaskList, this.taskSummaries);
|
|
2410
2729
|
if (prResult.url) {
|
|
2411
|
-
this.log(`PR created: ${prResult.url}`, "success");
|
|
2412
2730
|
for (const task of this.completedTaskList) {
|
|
2413
2731
|
try {
|
|
2414
2732
|
await this.client.tasks.update(task.id, this.config.workspaceId, {
|
|
@@ -2458,7 +2776,9 @@ __export(exports_index_node, {
|
|
|
2458
2776
|
plannedTasksToCreatePayloads: () => plannedTasksToCreatePayloads,
|
|
2459
2777
|
parseSprintPlanFromAI: () => parseSprintPlanFromAI,
|
|
2460
2778
|
parseJsonWithSchema: () => parseJsonWithSchema,
|
|
2779
|
+
isValidModelForProvider: () => isValidModelForProvider,
|
|
2461
2780
|
getRemoteUrl: () => getRemoteUrl,
|
|
2781
|
+
getModelsForProvider: () => getModelsForProvider,
|
|
2462
2782
|
getLocusPath: () => getLocusPath,
|
|
2463
2783
|
getDefaultBranch: () => getDefaultBranch,
|
|
2464
2784
|
getCurrentBranch: () => getCurrentBranch,
|
|
@@ -2467,6 +2787,8 @@ __export(exports_index_node, {
|
|
|
2467
2787
|
detectRemoteProvider: () => detectRemoteProvider,
|
|
2468
2788
|
createAiRunner: () => createAiRunner,
|
|
2469
2789
|
c: () => c,
|
|
2790
|
+
buildSummaryPrompt: () => buildSummaryPrompt,
|
|
2791
|
+
buildFacilitatorPrompt: () => buildFacilitatorPrompt,
|
|
2470
2792
|
WorkspacesModule: () => WorkspacesModule,
|
|
2471
2793
|
TasksModule: () => TasksModule,
|
|
2472
2794
|
TaskExecutor: () => TaskExecutor,
|
|
@@ -2477,6 +2799,7 @@ __export(exports_index_node, {
|
|
|
2477
2799
|
PrService: () => PrService,
|
|
2478
2800
|
PlanningMeeting: () => PlanningMeeting,
|
|
2479
2801
|
PlanManager: () => PlanManager,
|
|
2802
|
+
PROVIDER_MODELS: () => PROVIDER_MODELS,
|
|
2480
2803
|
PROVIDER: () => PROVIDER,
|
|
2481
2804
|
OrganizationsModule: () => OrganizationsModule,
|
|
2482
2805
|
LocusEvent: () => LocusEvent,
|
|
@@ -2494,6 +2817,11 @@ __export(exports_index_node, {
|
|
|
2494
2817
|
ExecEventEmitter: () => ExecEventEmitter,
|
|
2495
2818
|
DocumentFetcher: () => DocumentFetcher,
|
|
2496
2819
|
DocsModule: () => DocsModule,
|
|
2820
|
+
DiscussionSchema: () => DiscussionSchema,
|
|
2821
|
+
DiscussionMessageSchema: () => DiscussionMessageSchema,
|
|
2822
|
+
DiscussionManager: () => DiscussionManager,
|
|
2823
|
+
DiscussionInsightSchema: () => DiscussionInsightSchema,
|
|
2824
|
+
DiscussionFacilitator: () => DiscussionFacilitator,
|
|
2497
2825
|
DEFAULT_MODEL: () => DEFAULT_MODEL,
|
|
2498
2826
|
ContextTracker: () => ContextTracker,
|
|
2499
2827
|
CodexRunner: () => CodexRunner,
|
|
@@ -2501,6 +2829,8 @@ __export(exports_index_node, {
|
|
|
2501
2829
|
CodebaseIndexer: () => CodebaseIndexer,
|
|
2502
2830
|
ClaudeRunner: () => ClaudeRunner,
|
|
2503
2831
|
CiModule: () => CiModule,
|
|
2832
|
+
CODEX_MODELS: () => CODEX_MODELS,
|
|
2833
|
+
CLAUDE_MODELS: () => CLAUDE_MODELS,
|
|
2504
2834
|
AuthModule: () => AuthModule,
|
|
2505
2835
|
AgentWorker: () => AgentWorker,
|
|
2506
2836
|
AgentOrchestrator: () => AgentOrchestrator
|
|
@@ -2509,8 +2839,8 @@ module.exports = __toCommonJS(exports_index_node);
|
|
|
2509
2839
|
|
|
2510
2840
|
// src/core/indexer.ts
|
|
2511
2841
|
var import_node_crypto2 = require("node:crypto");
|
|
2512
|
-
var
|
|
2513
|
-
var
|
|
2842
|
+
var import_node_fs5 = require("node:fs");
|
|
2843
|
+
var import_node_path7 = require("node:path");
|
|
2514
2844
|
var import_globby = require("globby");
|
|
2515
2845
|
|
|
2516
2846
|
class CodebaseIndexer {
|
|
@@ -2519,7 +2849,7 @@ class CodebaseIndexer {
|
|
|
2519
2849
|
fullReindexRatioThreshold = 0.2;
|
|
2520
2850
|
constructor(projectPath) {
|
|
2521
2851
|
this.projectPath = projectPath;
|
|
2522
|
-
this.indexPath =
|
|
2852
|
+
this.indexPath = import_node_path7.join(projectPath, ".locus", "codebase-index.json");
|
|
2523
2853
|
}
|
|
2524
2854
|
async index(onProgress, treeSummarizer, force = false) {
|
|
2525
2855
|
if (!treeSummarizer) {
|
|
@@ -2575,11 +2905,11 @@ class CodebaseIndexer {
|
|
|
2575
2905
|
}
|
|
2576
2906
|
}
|
|
2577
2907
|
async getFileTree() {
|
|
2578
|
-
const gitmodulesPath =
|
|
2908
|
+
const gitmodulesPath = import_node_path7.join(this.projectPath, ".gitmodules");
|
|
2579
2909
|
const submoduleIgnores = [];
|
|
2580
|
-
if (
|
|
2910
|
+
if (import_node_fs5.existsSync(gitmodulesPath)) {
|
|
2581
2911
|
try {
|
|
2582
|
-
const content =
|
|
2912
|
+
const content = import_node_fs5.readFileSync(gitmodulesPath, "utf-8");
|
|
2583
2913
|
const lines = content.split(`
|
|
2584
2914
|
`);
|
|
2585
2915
|
for (const line of lines) {
|
|
@@ -2635,9 +2965,9 @@ class CodebaseIndexer {
|
|
|
2635
2965
|
});
|
|
2636
2966
|
}
|
|
2637
2967
|
loadIndex() {
|
|
2638
|
-
if (
|
|
2968
|
+
if (import_node_fs5.existsSync(this.indexPath)) {
|
|
2639
2969
|
try {
|
|
2640
|
-
return JSON.parse(
|
|
2970
|
+
return JSON.parse(import_node_fs5.readFileSync(this.indexPath, "utf-8"));
|
|
2641
2971
|
} catch {
|
|
2642
2972
|
return null;
|
|
2643
2973
|
}
|
|
@@ -2645,11 +2975,11 @@ class CodebaseIndexer {
|
|
|
2645
2975
|
return null;
|
|
2646
2976
|
}
|
|
2647
2977
|
saveIndex(index) {
|
|
2648
|
-
const dir =
|
|
2649
|
-
if (!
|
|
2650
|
-
|
|
2978
|
+
const dir = import_node_path7.dirname(this.indexPath);
|
|
2979
|
+
if (!import_node_fs5.existsSync(dir)) {
|
|
2980
|
+
import_node_fs5.mkdirSync(dir, { recursive: true });
|
|
2651
2981
|
}
|
|
2652
|
-
|
|
2982
|
+
import_node_fs5.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
|
|
2653
2983
|
}
|
|
2654
2984
|
cloneIndex(index) {
|
|
2655
2985
|
return JSON.parse(JSON.stringify(index));
|
|
@@ -2665,7 +2995,7 @@ class CodebaseIndexer {
|
|
|
2665
2995
|
}
|
|
2666
2996
|
hashFile(filePath) {
|
|
2667
2997
|
try {
|
|
2668
|
-
const content =
|
|
2998
|
+
const content = import_node_fs5.readFileSync(import_node_path7.join(this.projectPath, filePath), "utf-8");
|
|
2669
2999
|
return import_node_crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
2670
3000
|
} catch {
|
|
2671
3001
|
return null;
|
|
@@ -2820,8 +3150,8 @@ Return ONLY valid JSON (no code fences, no markdown):
|
|
|
2820
3150
|
}
|
|
2821
3151
|
// src/agent/document-fetcher.ts
|
|
2822
3152
|
init_config();
|
|
2823
|
-
var
|
|
2824
|
-
var
|
|
3153
|
+
var import_node_fs6 = require("node:fs");
|
|
3154
|
+
var import_node_path8 = require("node:path");
|
|
2825
3155
|
|
|
2826
3156
|
class DocumentFetcher {
|
|
2827
3157
|
deps;
|
|
@@ -2830,8 +3160,8 @@ class DocumentFetcher {
|
|
|
2830
3160
|
}
|
|
2831
3161
|
async fetch() {
|
|
2832
3162
|
const documentsDir = getLocusPath(this.deps.projectPath, "documentsDir");
|
|
2833
|
-
if (!
|
|
2834
|
-
|
|
3163
|
+
if (!import_node_fs6.existsSync(documentsDir)) {
|
|
3164
|
+
import_node_fs6.mkdirSync(documentsDir, { recursive: true });
|
|
2835
3165
|
}
|
|
2836
3166
|
try {
|
|
2837
3167
|
const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
|
|
@@ -2844,14 +3174,14 @@ class DocumentFetcher {
|
|
|
2844
3174
|
continue;
|
|
2845
3175
|
}
|
|
2846
3176
|
const groupName = groupMap.get(doc.groupId || "") || "General";
|
|
2847
|
-
const groupDir =
|
|
2848
|
-
if (!
|
|
2849
|
-
|
|
3177
|
+
const groupDir = import_node_path8.join(documentsDir, groupName);
|
|
3178
|
+
if (!import_node_fs6.existsSync(groupDir)) {
|
|
3179
|
+
import_node_fs6.mkdirSync(groupDir, { recursive: true });
|
|
2850
3180
|
}
|
|
2851
3181
|
const fileName = `${doc.title}.md`;
|
|
2852
|
-
const filePath =
|
|
2853
|
-
if (!
|
|
2854
|
-
|
|
3182
|
+
const filePath = import_node_path8.join(groupDir, fileName);
|
|
3183
|
+
if (!import_node_fs6.existsSync(filePath) || import_node_fs6.readFileSync(filePath, "utf-8") !== doc.content) {
|
|
3184
|
+
import_node_fs6.writeFileSync(filePath, doc.content || "");
|
|
2855
3185
|
fetchedCount++;
|
|
2856
3186
|
}
|
|
2857
3187
|
}
|
|
@@ -3385,6 +3715,341 @@ init_prompt_builder();
|
|
|
3385
3715
|
// src/index-node.ts
|
|
3386
3716
|
init_prompt_builder();
|
|
3387
3717
|
|
|
3718
|
+
// src/discussion/agents/facilitator-prompt.ts
|
|
3719
|
+
function buildFacilitatorPrompt(input) {
|
|
3720
|
+
const {
|
|
3721
|
+
topic,
|
|
3722
|
+
projectContext,
|
|
3723
|
+
learnings,
|
|
3724
|
+
knowledgeBase,
|
|
3725
|
+
previousMessages,
|
|
3726
|
+
insights,
|
|
3727
|
+
isFirstMessage
|
|
3728
|
+
} = input;
|
|
3729
|
+
let sections = "";
|
|
3730
|
+
if (projectContext) {
|
|
3731
|
+
sections += `
|
|
3732
|
+
<project_context>
|
|
3733
|
+
${projectContext}
|
|
3734
|
+
</project_context>
|
|
3735
|
+
`;
|
|
3736
|
+
}
|
|
3737
|
+
sections += `
|
|
3738
|
+
<knowledge_base>
|
|
3739
|
+
${knowledgeBase}
|
|
3740
|
+
</knowledge_base>
|
|
3741
|
+
`;
|
|
3742
|
+
if (learnings) {
|
|
3743
|
+
sections += `
|
|
3744
|
+
<learnings>
|
|
3745
|
+
These are accumulated lessons from past work on this project. Use them to ask more informed questions:
|
|
3746
|
+
${learnings}
|
|
3747
|
+
</learnings>
|
|
3748
|
+
`;
|
|
3749
|
+
}
|
|
3750
|
+
if (previousMessages.length > 0) {
|
|
3751
|
+
let history = "";
|
|
3752
|
+
for (const msg of previousMessages) {
|
|
3753
|
+
const role = msg.role === "user" ? "User" : "Facilitator";
|
|
3754
|
+
history += `[${role}]: ${msg.content}
|
|
3755
|
+
|
|
3756
|
+
`;
|
|
3757
|
+
}
|
|
3758
|
+
sections += `
|
|
3759
|
+
<conversation_history>
|
|
3760
|
+
${history.trimEnd()}
|
|
3761
|
+
</conversation_history>
|
|
3762
|
+
`;
|
|
3763
|
+
}
|
|
3764
|
+
if (insights.length > 0) {
|
|
3765
|
+
let insightsText = "";
|
|
3766
|
+
for (const insight of insights) {
|
|
3767
|
+
insightsText += `- [${insight.type.toUpperCase()}] ${insight.title}: ${insight.content}
|
|
3768
|
+
`;
|
|
3769
|
+
}
|
|
3770
|
+
sections += `
|
|
3771
|
+
<extracted_insights>
|
|
3772
|
+
Insights identified so far in this discussion:
|
|
3773
|
+
${insightsText.trimEnd()}
|
|
3774
|
+
</extracted_insights>
|
|
3775
|
+
`;
|
|
3776
|
+
}
|
|
3777
|
+
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.`;
|
|
3778
|
+
return `<discussion_facilitator>
|
|
3779
|
+
You are a product strategy facilitator leading a structured discussion.
|
|
3780
|
+
|
|
3781
|
+
<topic>
|
|
3782
|
+
${topic}
|
|
3783
|
+
</topic>
|
|
3784
|
+
${sections}
|
|
3785
|
+
<role>
|
|
3786
|
+
You are an expert product strategy facilitator. Your job is to:
|
|
3787
|
+
1. Ask probing, specific questions about the topic — never generic or surface-level
|
|
3788
|
+
2. Build on previous answers to progressively deepen the conversation
|
|
3789
|
+
3. Identify and extract key decisions, requirements, ideas, concerns, and learnings
|
|
3790
|
+
4. Reference existing project context and learnings to ask informed questions
|
|
3791
|
+
5. When the topic feels fully explored, suggest wrapping up with a summary
|
|
3792
|
+
</role>
|
|
3793
|
+
|
|
3794
|
+
<rules>
|
|
3795
|
+
- ${firstMessageInstruction}
|
|
3796
|
+
- Ask ONE focused question at a time. Do not overwhelm with multiple questions.
|
|
3797
|
+
- Be conversational but purposeful — every question should drive toward actionable insights.
|
|
3798
|
+
- When you identify an insight from the user's response, include it as a structured XML block in your response.
|
|
3799
|
+
- Insight blocks use this format within your response text:
|
|
3800
|
+
|
|
3801
|
+
<insight>
|
|
3802
|
+
{"type": "decision|requirement|idea|concern|learning", "title": "short title", "content": "detailed description", "tags": ["relevant", "tags"]}
|
|
3803
|
+
</insight>
|
|
3804
|
+
|
|
3805
|
+
- You may include multiple <insight> blocks if the user's response contains several distinct insights.
|
|
3806
|
+
- The insight blocks will be parsed and removed from the displayed response, so write your conversational text as if they are not there.
|
|
3807
|
+
- Types explained:
|
|
3808
|
+
- **decision**: A choice or direction that has been made or agreed upon
|
|
3809
|
+
- **requirement**: A specific need, constraint, or must-have
|
|
3810
|
+
- **idea**: A suggestion, proposal, or possibility to explore
|
|
3811
|
+
- **concern**: A risk, worry, or potential problem identified
|
|
3812
|
+
- **learning**: A realization, lesson, or important context discovered
|
|
3813
|
+
- Keep responses concise. Aim for 2-4 sentences of conversation plus any insight blocks.
|
|
3814
|
+
- If the user's responses indicate the topic is well-explored, suggest summarizing and wrapping up.
|
|
3815
|
+
</rules>
|
|
3816
|
+
</discussion_facilitator>`;
|
|
3817
|
+
}
|
|
3818
|
+
function buildSummaryPrompt(topic, messages, insights) {
|
|
3819
|
+
let history = "";
|
|
3820
|
+
for (const msg of messages) {
|
|
3821
|
+
const role = msg.role === "user" ? "User" : "Facilitator";
|
|
3822
|
+
history += `[${role}]: ${msg.content}
|
|
3823
|
+
|
|
3824
|
+
`;
|
|
3825
|
+
}
|
|
3826
|
+
let insightsText = "";
|
|
3827
|
+
if (insights.length > 0) {
|
|
3828
|
+
for (const insight of insights) {
|
|
3829
|
+
insightsText += `- [${insight.type.toUpperCase()}] **${insight.title}**: ${insight.content}`;
|
|
3830
|
+
if (insight.tags.length > 0) {
|
|
3831
|
+
insightsText += ` (tags: ${insight.tags.join(", ")})`;
|
|
3832
|
+
}
|
|
3833
|
+
insightsText += `
|
|
3834
|
+
`;
|
|
3835
|
+
}
|
|
3836
|
+
}
|
|
3837
|
+
return `<discussion_summary>
|
|
3838
|
+
Create a final summary of this product discussion.
|
|
3839
|
+
|
|
3840
|
+
<topic>
|
|
3841
|
+
${topic}
|
|
3842
|
+
</topic>
|
|
3843
|
+
|
|
3844
|
+
<conversation>
|
|
3845
|
+
${history.trimEnd()}
|
|
3846
|
+
</conversation>
|
|
3847
|
+
|
|
3848
|
+
${insightsText ? `<insights>
|
|
3849
|
+
${insightsText.trimEnd()}
|
|
3850
|
+
</insights>
|
|
3851
|
+
` : ""}
|
|
3852
|
+
<rules>
|
|
3853
|
+
- Write a clear, structured summary of the entire discussion.
|
|
3854
|
+
- Organize by: Key Decisions, Requirements, Ideas to Explore, Concerns & Risks, and Learnings.
|
|
3855
|
+
- Only include sections that have relevant content — skip empty categories.
|
|
3856
|
+
- For each item, provide a brief but actionable description.
|
|
3857
|
+
- End with a "Next Steps" section listing concrete action items that emerged.
|
|
3858
|
+
- Be concise — this summary should be scannable and useful as a reference.
|
|
3859
|
+
- Do NOT include any <insight> XML blocks in the summary.
|
|
3860
|
+
</rules>
|
|
3861
|
+
</discussion_summary>`;
|
|
3862
|
+
}
|
|
3863
|
+
// src/discussion/discussion-facilitator.ts
|
|
3864
|
+
init_config();
|
|
3865
|
+
var import_node_fs7 = require("node:fs");
|
|
3866
|
+
class DiscussionFacilitator {
|
|
3867
|
+
projectPath;
|
|
3868
|
+
aiRunner;
|
|
3869
|
+
discussionManager;
|
|
3870
|
+
log;
|
|
3871
|
+
provider;
|
|
3872
|
+
model;
|
|
3873
|
+
constructor(config) {
|
|
3874
|
+
this.projectPath = config.projectPath;
|
|
3875
|
+
this.aiRunner = config.aiRunner;
|
|
3876
|
+
this.discussionManager = config.discussionManager;
|
|
3877
|
+
this.log = config.log ?? ((_msg) => {
|
|
3878
|
+
return;
|
|
3879
|
+
});
|
|
3880
|
+
this.provider = config.provider;
|
|
3881
|
+
this.model = config.model;
|
|
3882
|
+
}
|
|
3883
|
+
async startDiscussion(topic) {
|
|
3884
|
+
this.log("Starting new discussion...", "info");
|
|
3885
|
+
const discussion = this.discussionManager.create(topic, this.model, this.provider);
|
|
3886
|
+
const { projectContext, learnings, knowledgeBase } = this.buildContext();
|
|
3887
|
+
const prompt = buildFacilitatorPrompt({
|
|
3888
|
+
topic,
|
|
3889
|
+
projectContext,
|
|
3890
|
+
learnings,
|
|
3891
|
+
knowledgeBase,
|
|
3892
|
+
previousMessages: [],
|
|
3893
|
+
insights: [],
|
|
3894
|
+
isFirstMessage: true
|
|
3895
|
+
});
|
|
3896
|
+
const response = await this.aiRunner.run(prompt);
|
|
3897
|
+
const { cleanResponse } = this.parseInsights(response);
|
|
3898
|
+
this.discussionManager.addMessage(discussion.id, "assistant", cleanResponse);
|
|
3899
|
+
this.log("Discussion started", "success");
|
|
3900
|
+
const saved = this.discussionManager.load(discussion.id);
|
|
3901
|
+
if (!saved) {
|
|
3902
|
+
throw new Error(`Failed to load discussion after creation: ${discussion.id}`);
|
|
3903
|
+
}
|
|
3904
|
+
return {
|
|
3905
|
+
discussion: saved,
|
|
3906
|
+
message: cleanResponse
|
|
3907
|
+
};
|
|
3908
|
+
}
|
|
3909
|
+
async continueDiscussion(discussionId, userMessage) {
|
|
3910
|
+
const discussion = this.discussionManager.load(discussionId);
|
|
3911
|
+
if (!discussion) {
|
|
3912
|
+
throw new Error(`Discussion not found: ${discussionId}`);
|
|
3913
|
+
}
|
|
3914
|
+
const updated = this.discussionManager.addMessage(discussionId, "user", userMessage);
|
|
3915
|
+
const { projectContext, learnings, knowledgeBase } = this.buildContext();
|
|
3916
|
+
const prompt = buildFacilitatorPrompt({
|
|
3917
|
+
topic: updated.topic,
|
|
3918
|
+
projectContext,
|
|
3919
|
+
learnings,
|
|
3920
|
+
knowledgeBase,
|
|
3921
|
+
previousMessages: updated.messages,
|
|
3922
|
+
insights: updated.insights,
|
|
3923
|
+
isFirstMessage: false
|
|
3924
|
+
});
|
|
3925
|
+
const response = await this.aiRunner.run(prompt);
|
|
3926
|
+
const { cleanResponse, insights } = this.parseInsights(response);
|
|
3927
|
+
for (const insight of insights) {
|
|
3928
|
+
this.discussionManager.addInsight(discussionId, insight);
|
|
3929
|
+
}
|
|
3930
|
+
this.discussionManager.addMessage(discussionId, "assistant", cleanResponse);
|
|
3931
|
+
return { response: cleanResponse, insights };
|
|
3932
|
+
}
|
|
3933
|
+
async* continueDiscussionStream(discussionId, userMessage) {
|
|
3934
|
+
const discussion = this.discussionManager.load(discussionId);
|
|
3935
|
+
if (!discussion) {
|
|
3936
|
+
throw new Error(`Discussion not found: ${discussionId}`);
|
|
3937
|
+
}
|
|
3938
|
+
const updated = this.discussionManager.addMessage(discussionId, "user", userMessage);
|
|
3939
|
+
const { projectContext, learnings, knowledgeBase } = this.buildContext();
|
|
3940
|
+
const prompt = buildFacilitatorPrompt({
|
|
3941
|
+
topic: updated.topic,
|
|
3942
|
+
projectContext,
|
|
3943
|
+
learnings,
|
|
3944
|
+
knowledgeBase,
|
|
3945
|
+
previousMessages: updated.messages,
|
|
3946
|
+
insights: updated.insights,
|
|
3947
|
+
isFirstMessage: false
|
|
3948
|
+
});
|
|
3949
|
+
let fullResponse = "";
|
|
3950
|
+
const stream = this.aiRunner.runStream(prompt);
|
|
3951
|
+
for await (const chunk of stream) {
|
|
3952
|
+
yield chunk;
|
|
3953
|
+
if (chunk.type === "text_delta") {
|
|
3954
|
+
fullResponse += chunk.content;
|
|
3955
|
+
} else if (chunk.type === "result") {
|
|
3956
|
+
fullResponse = chunk.content;
|
|
3957
|
+
}
|
|
3958
|
+
}
|
|
3959
|
+
const { cleanResponse, insights } = this.parseInsights(fullResponse);
|
|
3960
|
+
for (const insight of insights) {
|
|
3961
|
+
this.discussionManager.addInsight(discussionId, insight);
|
|
3962
|
+
}
|
|
3963
|
+
this.discussionManager.addMessage(discussionId, "assistant", cleanResponse);
|
|
3964
|
+
return { response: cleanResponse, insights };
|
|
3965
|
+
}
|
|
3966
|
+
async summarizeDiscussion(discussionId) {
|
|
3967
|
+
const discussion = this.discussionManager.load(discussionId);
|
|
3968
|
+
if (!discussion) {
|
|
3969
|
+
throw new Error(`Discussion not found: ${discussionId}`);
|
|
3970
|
+
}
|
|
3971
|
+
this.log("Generating discussion summary...", "info");
|
|
3972
|
+
const prompt = buildSummaryPrompt(discussion.topic, discussion.messages, discussion.insights);
|
|
3973
|
+
const summary = await this.aiRunner.run(prompt);
|
|
3974
|
+
this.discussionManager.addMessage(discussionId, "assistant", summary);
|
|
3975
|
+
this.discussionManager.complete(discussionId);
|
|
3976
|
+
this.log("Discussion summarized and completed", "success");
|
|
3977
|
+
return summary;
|
|
3978
|
+
}
|
|
3979
|
+
parseInsights(response) {
|
|
3980
|
+
const insights = [];
|
|
3981
|
+
const insightRegex = /<insight>\s*([\s\S]*?)\s*<\/insight>/g;
|
|
3982
|
+
let match = insightRegex.exec(response);
|
|
3983
|
+
while (match !== null) {
|
|
3984
|
+
try {
|
|
3985
|
+
const parsed = JSON.parse(match[1]);
|
|
3986
|
+
const id = `ins-${Date.now()}-${Math.random().toString(36).substring(2, 7)}`;
|
|
3987
|
+
insights.push({
|
|
3988
|
+
id,
|
|
3989
|
+
type: parsed.type,
|
|
3990
|
+
title: parsed.title,
|
|
3991
|
+
content: parsed.content,
|
|
3992
|
+
tags: parsed.tags ?? [],
|
|
3993
|
+
createdAt: new Date().toISOString()
|
|
3994
|
+
});
|
|
3995
|
+
} catch {}
|
|
3996
|
+
match = insightRegex.exec(response);
|
|
3997
|
+
}
|
|
3998
|
+
const cleanResponse = response.replace(/<insight>\s*[\s\S]*?\s*<\/insight>/g, "").replace(/\n{3,}/g, `
|
|
3999
|
+
|
|
4000
|
+
`).trim();
|
|
4001
|
+
return { cleanResponse, insights };
|
|
4002
|
+
}
|
|
4003
|
+
buildContext() {
|
|
4004
|
+
return {
|
|
4005
|
+
projectContext: this.getProjectContext(),
|
|
4006
|
+
learnings: this.getLearningsContent(),
|
|
4007
|
+
knowledgeBase: this.getKnowledgeBaseSection()
|
|
4008
|
+
};
|
|
4009
|
+
}
|
|
4010
|
+
getProjectContext() {
|
|
4011
|
+
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
4012
|
+
if (import_node_fs7.existsSync(contextPath)) {
|
|
4013
|
+
try {
|
|
4014
|
+
const context = import_node_fs7.readFileSync(contextPath, "utf-8");
|
|
4015
|
+
if (context.trim().length > 20) {
|
|
4016
|
+
return context;
|
|
4017
|
+
}
|
|
4018
|
+
} catch {
|
|
4019
|
+
return null;
|
|
4020
|
+
}
|
|
4021
|
+
}
|
|
4022
|
+
return null;
|
|
4023
|
+
}
|
|
4024
|
+
getLearningsContent() {
|
|
4025
|
+
const learningsPath = getLocusPath(this.projectPath, "learningsFile");
|
|
4026
|
+
if (!import_node_fs7.existsSync(learningsPath)) {
|
|
4027
|
+
return null;
|
|
4028
|
+
}
|
|
4029
|
+
try {
|
|
4030
|
+
const content = import_node_fs7.readFileSync(learningsPath, "utf-8");
|
|
4031
|
+
const lines = content.split(`
|
|
4032
|
+
`).filter((l) => l.startsWith("- "));
|
|
4033
|
+
if (lines.length === 0) {
|
|
4034
|
+
return null;
|
|
4035
|
+
}
|
|
4036
|
+
return lines.join(`
|
|
4037
|
+
`);
|
|
4038
|
+
} catch {
|
|
4039
|
+
return null;
|
|
4040
|
+
}
|
|
4041
|
+
}
|
|
4042
|
+
getKnowledgeBaseSection() {
|
|
4043
|
+
return `You have access to the following documentation directories for context:
|
|
4044
|
+
- Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
|
|
4045
|
+
- Documents: \`.locus/documents\` (synced from cloud)
|
|
4046
|
+
If you need more information about the project strategies, plans, or architecture, read files in these directories.`;
|
|
4047
|
+
}
|
|
4048
|
+
}
|
|
4049
|
+
|
|
4050
|
+
// src/discussion/index.ts
|
|
4051
|
+
init_discussion_manager();
|
|
4052
|
+
init_discussion_types();
|
|
3388
4053
|
// src/exec/context-tracker.ts
|
|
3389
4054
|
var REFERENCE_ALIASES = {
|
|
3390
4055
|
plan: ["the plan", "sprint plan", "project plan", "implementation plan"],
|
|
@@ -3799,8 +4464,8 @@ class ExecEventEmitter {
|
|
|
3799
4464
|
}
|
|
3800
4465
|
// src/exec/history-manager.ts
|
|
3801
4466
|
init_config();
|
|
3802
|
-
var
|
|
3803
|
-
var
|
|
4467
|
+
var import_node_fs8 = require("node:fs");
|
|
4468
|
+
var import_node_path9 = require("node:path");
|
|
3804
4469
|
var DEFAULT_MAX_SESSIONS = 30;
|
|
3805
4470
|
function generateSessionId2() {
|
|
3806
4471
|
const timestamp = Date.now().toString(36);
|
|
@@ -3812,30 +4477,30 @@ class HistoryManager {
|
|
|
3812
4477
|
historyDir;
|
|
3813
4478
|
maxSessions;
|
|
3814
4479
|
constructor(projectPath, options) {
|
|
3815
|
-
this.historyDir = options?.historyDir ??
|
|
4480
|
+
this.historyDir = options?.historyDir ?? import_node_path9.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.sessionsDir);
|
|
3816
4481
|
this.maxSessions = options?.maxSessions ?? DEFAULT_MAX_SESSIONS;
|
|
3817
4482
|
this.ensureHistoryDir();
|
|
3818
4483
|
}
|
|
3819
4484
|
ensureHistoryDir() {
|
|
3820
|
-
if (!
|
|
3821
|
-
|
|
4485
|
+
if (!import_node_fs8.existsSync(this.historyDir)) {
|
|
4486
|
+
import_node_fs8.mkdirSync(this.historyDir, { recursive: true });
|
|
3822
4487
|
}
|
|
3823
4488
|
}
|
|
3824
4489
|
getSessionPath(sessionId) {
|
|
3825
|
-
return
|
|
4490
|
+
return import_node_path9.join(this.historyDir, `${sessionId}.json`);
|
|
3826
4491
|
}
|
|
3827
4492
|
saveSession(session) {
|
|
3828
4493
|
const filePath = this.getSessionPath(session.id);
|
|
3829
4494
|
session.updatedAt = Date.now();
|
|
3830
|
-
|
|
4495
|
+
import_node_fs8.writeFileSync(filePath, JSON.stringify(session, null, 2), "utf-8");
|
|
3831
4496
|
}
|
|
3832
4497
|
loadSession(sessionId) {
|
|
3833
4498
|
const filePath = this.getSessionPath(sessionId);
|
|
3834
|
-
if (!
|
|
4499
|
+
if (!import_node_fs8.existsSync(filePath)) {
|
|
3835
4500
|
return null;
|
|
3836
4501
|
}
|
|
3837
4502
|
try {
|
|
3838
|
-
const content =
|
|
4503
|
+
const content = import_node_fs8.readFileSync(filePath, "utf-8");
|
|
3839
4504
|
return JSON.parse(content);
|
|
3840
4505
|
} catch {
|
|
3841
4506
|
return null;
|
|
@@ -3843,18 +4508,18 @@ class HistoryManager {
|
|
|
3843
4508
|
}
|
|
3844
4509
|
deleteSession(sessionId) {
|
|
3845
4510
|
const filePath = this.getSessionPath(sessionId);
|
|
3846
|
-
if (!
|
|
4511
|
+
if (!import_node_fs8.existsSync(filePath)) {
|
|
3847
4512
|
return false;
|
|
3848
4513
|
}
|
|
3849
4514
|
try {
|
|
3850
|
-
|
|
4515
|
+
import_node_fs8.rmSync(filePath);
|
|
3851
4516
|
return true;
|
|
3852
4517
|
} catch {
|
|
3853
4518
|
return false;
|
|
3854
4519
|
}
|
|
3855
4520
|
}
|
|
3856
4521
|
listSessions(options) {
|
|
3857
|
-
const files =
|
|
4522
|
+
const files = import_node_fs8.readdirSync(this.historyDir);
|
|
3858
4523
|
let sessions = [];
|
|
3859
4524
|
for (const file of files) {
|
|
3860
4525
|
if (file.endsWith(".json")) {
|
|
@@ -3927,11 +4592,11 @@ class HistoryManager {
|
|
|
3927
4592
|
return deleted;
|
|
3928
4593
|
}
|
|
3929
4594
|
getSessionCount() {
|
|
3930
|
-
const files =
|
|
4595
|
+
const files = import_node_fs8.readdirSync(this.historyDir);
|
|
3931
4596
|
return files.filter((f) => f.endsWith(".json")).length;
|
|
3932
4597
|
}
|
|
3933
4598
|
sessionExists(sessionId) {
|
|
3934
|
-
return
|
|
4599
|
+
return import_node_fs8.existsSync(this.getSessionPath(sessionId));
|
|
3935
4600
|
}
|
|
3936
4601
|
findSessionByPartialId(partialId) {
|
|
3937
4602
|
const sessions = this.listSessions();
|
|
@@ -3945,12 +4610,12 @@ class HistoryManager {
|
|
|
3945
4610
|
return this.historyDir;
|
|
3946
4611
|
}
|
|
3947
4612
|
clearAllSessions() {
|
|
3948
|
-
const files =
|
|
4613
|
+
const files = import_node_fs8.readdirSync(this.historyDir);
|
|
3949
4614
|
let deleted = 0;
|
|
3950
4615
|
for (const file of files) {
|
|
3951
4616
|
if (file.endsWith(".json")) {
|
|
3952
4617
|
try {
|
|
3953
|
-
|
|
4618
|
+
import_node_fs8.rmSync(import_node_path9.join(this.historyDir, file));
|
|
3954
4619
|
deleted++;
|
|
3955
4620
|
} catch {}
|
|
3956
4621
|
}
|
|
@@ -4226,8 +4891,8 @@ init_src();
|
|
|
4226
4891
|
init_colors();
|
|
4227
4892
|
init_resolve_bin();
|
|
4228
4893
|
var import_node_child_process7 = require("node:child_process");
|
|
4229
|
-
var
|
|
4230
|
-
var
|
|
4894
|
+
var import_node_fs9 = require("node:fs");
|
|
4895
|
+
var import_node_path10 = require("node:path");
|
|
4231
4896
|
var import_node_url = require("node:url");
|
|
4232
4897
|
var import_shared4 = require("@locusai/shared");
|
|
4233
4898
|
var import_events4 = require("events");
|
|
@@ -4495,14 +5160,14 @@ ${agentId} finished (exit code: ${code})`);
|
|
|
4495
5160
|
}
|
|
4496
5161
|
resolveWorkerPath() {
|
|
4497
5162
|
const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator/index.ts");
|
|
4498
|
-
const currentModuleDir =
|
|
5163
|
+
const currentModuleDir = import_node_path10.dirname(currentModulePath);
|
|
4499
5164
|
const potentialPaths = [
|
|
4500
|
-
|
|
4501
|
-
|
|
4502
|
-
|
|
4503
|
-
|
|
5165
|
+
import_node_path10.join(currentModuleDir, "..", "agent", "worker.js"),
|
|
5166
|
+
import_node_path10.join(currentModuleDir, "agent", "worker.js"),
|
|
5167
|
+
import_node_path10.join(currentModuleDir, "worker.js"),
|
|
5168
|
+
import_node_path10.join(currentModuleDir, "..", "agent", "worker.ts")
|
|
4504
5169
|
];
|
|
4505
|
-
return potentialPaths.find((p) =>
|
|
5170
|
+
return potentialPaths.find((p) => import_node_fs9.existsSync(p));
|
|
4506
5171
|
}
|
|
4507
5172
|
}
|
|
4508
5173
|
function killProcessTree(proc) {
|
|
@@ -4521,12 +5186,12 @@ function sleep(ms) {
|
|
|
4521
5186
|
}
|
|
4522
5187
|
// src/planning/plan-manager.ts
|
|
4523
5188
|
init_config();
|
|
4524
|
-
var
|
|
4525
|
-
var
|
|
5189
|
+
var import_node_fs10 = require("node:fs");
|
|
5190
|
+
var import_node_path11 = require("node:path");
|
|
4526
5191
|
|
|
4527
5192
|
// src/planning/sprint-plan.ts
|
|
4528
5193
|
var import_shared5 = require("@locusai/shared");
|
|
4529
|
-
var
|
|
5194
|
+
var import_zod2 = require("zod");
|
|
4530
5195
|
|
|
4531
5196
|
// src/utils/structured-output.ts
|
|
4532
5197
|
function parseJsonWithSchema(raw, schema) {
|
|
@@ -4551,26 +5216,26 @@ Parsed JSON preview: ${JSON.stringify(parsed).slice(0, 300)}`);
|
|
|
4551
5216
|
}
|
|
4552
5217
|
|
|
4553
5218
|
// src/planning/sprint-plan.ts
|
|
4554
|
-
var PlannedTaskSchema =
|
|
4555
|
-
title:
|
|
4556
|
-
description:
|
|
4557
|
-
assigneeRole:
|
|
4558
|
-
priority:
|
|
4559
|
-
complexity:
|
|
4560
|
-
acceptanceCriteria:
|
|
4561
|
-
labels:
|
|
5219
|
+
var PlannedTaskSchema = import_zod2.z.object({
|
|
5220
|
+
title: import_zod2.z.string().default("Untitled Task"),
|
|
5221
|
+
description: import_zod2.z.string().default(""),
|
|
5222
|
+
assigneeRole: import_zod2.z.enum(["BACKEND", "FRONTEND", "QA", "PM", "DESIGN"]).default("BACKEND"),
|
|
5223
|
+
priority: import_zod2.z.enum(["CRITICAL", "HIGH", "MEDIUM", "LOW"]).default("MEDIUM"),
|
|
5224
|
+
complexity: import_zod2.z.number().min(1).max(5).default(3),
|
|
5225
|
+
acceptanceCriteria: import_zod2.z.array(import_zod2.z.string()).default([]),
|
|
5226
|
+
labels: import_zod2.z.array(import_zod2.z.string()).default([])
|
|
4562
5227
|
});
|
|
4563
|
-
var SprintPlanRiskSchema =
|
|
4564
|
-
description:
|
|
4565
|
-
mitigation:
|
|
4566
|
-
severity:
|
|
5228
|
+
var SprintPlanRiskSchema = import_zod2.z.object({
|
|
5229
|
+
description: import_zod2.z.string().default(""),
|
|
5230
|
+
mitigation: import_zod2.z.string().default(""),
|
|
5231
|
+
severity: import_zod2.z.enum(["low", "medium", "high"]).default("medium")
|
|
4567
5232
|
});
|
|
4568
|
-
var PlannerOutputSchema =
|
|
4569
|
-
name:
|
|
4570
|
-
goal:
|
|
4571
|
-
estimatedDays:
|
|
4572
|
-
tasks:
|
|
4573
|
-
risks:
|
|
5233
|
+
var PlannerOutputSchema = import_zod2.z.object({
|
|
5234
|
+
name: import_zod2.z.string().default("Unnamed Sprint"),
|
|
5235
|
+
goal: import_zod2.z.string().default(""),
|
|
5236
|
+
estimatedDays: import_zod2.z.number().default(1),
|
|
5237
|
+
tasks: import_zod2.z.array(PlannedTaskSchema).default([]),
|
|
5238
|
+
risks: import_zod2.z.array(SprintPlanRiskSchema).default([])
|
|
4574
5239
|
});
|
|
4575
5240
|
var SprintPlanAIOutputSchema = PlannerOutputSchema;
|
|
4576
5241
|
function sprintPlanToMarkdown(plan) {
|
|
@@ -4687,19 +5352,19 @@ class PlanManager {
|
|
|
4687
5352
|
save(plan) {
|
|
4688
5353
|
this.ensurePlansDir();
|
|
4689
5354
|
const slug = this.slugify(plan.name);
|
|
4690
|
-
const jsonPath =
|
|
4691
|
-
const mdPath =
|
|
4692
|
-
|
|
4693
|
-
|
|
5355
|
+
const jsonPath = import_node_path11.join(this.plansDir, `${slug}.json`);
|
|
5356
|
+
const mdPath = import_node_path11.join(this.plansDir, `sprint-${slug}.md`);
|
|
5357
|
+
import_node_fs10.writeFileSync(jsonPath, JSON.stringify(plan, null, 2), "utf-8");
|
|
5358
|
+
import_node_fs10.writeFileSync(mdPath, sprintPlanToMarkdown(plan), "utf-8");
|
|
4694
5359
|
return plan.id;
|
|
4695
5360
|
}
|
|
4696
5361
|
load(idOrSlug) {
|
|
4697
5362
|
this.ensurePlansDir();
|
|
4698
|
-
const files =
|
|
5363
|
+
const files = import_node_fs10.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
|
|
4699
5364
|
for (const file of files) {
|
|
4700
|
-
const filePath =
|
|
5365
|
+
const filePath = import_node_path11.join(this.plansDir, file);
|
|
4701
5366
|
try {
|
|
4702
|
-
const plan = JSON.parse(
|
|
5367
|
+
const plan = JSON.parse(import_node_fs10.readFileSync(filePath, "utf-8"));
|
|
4703
5368
|
if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
|
|
4704
5369
|
return plan;
|
|
4705
5370
|
}
|
|
@@ -4709,11 +5374,11 @@ class PlanManager {
|
|
|
4709
5374
|
}
|
|
4710
5375
|
list(status) {
|
|
4711
5376
|
this.ensurePlansDir();
|
|
4712
|
-
const files =
|
|
5377
|
+
const files = import_node_fs10.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
|
|
4713
5378
|
const plans = [];
|
|
4714
5379
|
for (const file of files) {
|
|
4715
5380
|
try {
|
|
4716
|
-
const plan = JSON.parse(
|
|
5381
|
+
const plan = JSON.parse(import_node_fs10.readFileSync(import_node_path11.join(this.plansDir, file), "utf-8"));
|
|
4717
5382
|
if (!status || plan.status === status) {
|
|
4718
5383
|
plans.push(plan);
|
|
4719
5384
|
}
|
|
@@ -4770,18 +5435,18 @@ class PlanManager {
|
|
|
4770
5435
|
}
|
|
4771
5436
|
delete(idOrSlug) {
|
|
4772
5437
|
this.ensurePlansDir();
|
|
4773
|
-
const files =
|
|
5438
|
+
const files = import_node_fs10.readdirSync(this.plansDir);
|
|
4774
5439
|
for (const file of files) {
|
|
4775
|
-
const filePath =
|
|
5440
|
+
const filePath = import_node_path11.join(this.plansDir, file);
|
|
4776
5441
|
if (!file.endsWith(".json"))
|
|
4777
5442
|
continue;
|
|
4778
5443
|
try {
|
|
4779
|
-
const plan = JSON.parse(
|
|
5444
|
+
const plan = JSON.parse(import_node_fs10.readFileSync(filePath, "utf-8"));
|
|
4780
5445
|
if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
|
|
4781
|
-
|
|
4782
|
-
const mdPath =
|
|
4783
|
-
if (
|
|
4784
|
-
|
|
5446
|
+
import_node_fs10.unlinkSync(filePath);
|
|
5447
|
+
const mdPath = import_node_path11.join(this.plansDir, `sprint-${this.slugify(plan.name)}.md`);
|
|
5448
|
+
if (import_node_fs10.existsSync(mdPath)) {
|
|
5449
|
+
import_node_fs10.unlinkSync(mdPath);
|
|
4785
5450
|
}
|
|
4786
5451
|
return;
|
|
4787
5452
|
}
|
|
@@ -4795,8 +5460,8 @@ class PlanManager {
|
|
|
4795
5460
|
return sprintPlanToMarkdown(plan);
|
|
4796
5461
|
}
|
|
4797
5462
|
ensurePlansDir() {
|
|
4798
|
-
if (!
|
|
4799
|
-
|
|
5463
|
+
if (!import_node_fs10.existsSync(this.plansDir)) {
|
|
5464
|
+
import_node_fs10.mkdirSync(this.plansDir, { recursive: true });
|
|
4800
5465
|
}
|
|
4801
5466
|
}
|
|
4802
5467
|
slugify(name) {
|
|
@@ -4805,8 +5470,8 @@ class PlanManager {
|
|
|
4805
5470
|
}
|
|
4806
5471
|
// src/planning/planning-meeting.ts
|
|
4807
5472
|
init_config();
|
|
4808
|
-
var
|
|
4809
|
-
var
|
|
5473
|
+
var import_node_fs11 = require("node:fs");
|
|
5474
|
+
var import_node_path12 = require("node:path");
|
|
4810
5475
|
|
|
4811
5476
|
// src/planning/agents/planner.ts
|
|
4812
5477
|
function buildPlannerPrompt(input) {
|
|
@@ -4892,8 +5557,8 @@ class PlanningMeeting {
|
|
|
4892
5557
|
async run(directive, feedback) {
|
|
4893
5558
|
this.log("Planning sprint...", "info");
|
|
4894
5559
|
const plansDir = getLocusPath(this.projectPath, "plansDir");
|
|
4895
|
-
if (!
|
|
4896
|
-
|
|
5560
|
+
if (!import_node_fs11.existsSync(plansDir)) {
|
|
5561
|
+
import_node_fs11.mkdirSync(plansDir, { recursive: true });
|
|
4897
5562
|
}
|
|
4898
5563
|
const ts = Date.now();
|
|
4899
5564
|
const planId = `plan-${ts}`;
|
|
@@ -4907,11 +5572,11 @@ class PlanningMeeting {
|
|
|
4907
5572
|
});
|
|
4908
5573
|
const response = await this.aiRunner.run(prompt);
|
|
4909
5574
|
this.log("Planning meeting complete.", "success");
|
|
4910
|
-
const expectedPath =
|
|
5575
|
+
const expectedPath = import_node_path12.join(plansDir, `${fileName}.json`);
|
|
4911
5576
|
let plan = null;
|
|
4912
|
-
if (
|
|
5577
|
+
if (import_node_fs11.existsSync(expectedPath)) {
|
|
4913
5578
|
try {
|
|
4914
|
-
plan = JSON.parse(
|
|
5579
|
+
plan = JSON.parse(import_node_fs11.readFileSync(expectedPath, "utf-8"));
|
|
4915
5580
|
} catch {}
|
|
4916
5581
|
}
|
|
4917
5582
|
if (!plan) {
|