@locusai/sdk 0.11.8 → 0.12.1
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/codebase-indexer-service.d.ts.map +1 -1
- package/dist/agent/reviewer-worker.d.ts +0 -1
- package/dist/agent/reviewer-worker.d.ts.map +1 -1
- package/dist/agent/worker.d.ts +0 -2
- package/dist/agent/worker.d.ts.map +1 -1
- package/dist/agent/worker.js +140 -356
- package/dist/ai/claude-runner.d.ts +1 -0
- package/dist/ai/claude-runner.d.ts.map +1 -1
- package/dist/ai/codex-runner.d.ts +1 -1
- package/dist/ai/codex-runner.d.ts.map +1 -1
- package/dist/ai/factory.d.ts +2 -0
- package/dist/ai/factory.d.ts.map +1 -1
- package/dist/core/config.d.ts +2 -4
- package/dist/core/config.d.ts.map +1 -1
- package/dist/core/prompt-builder.d.ts +4 -5
- package/dist/core/prompt-builder.d.ts.map +1 -1
- package/dist/index-node.d.ts +1 -1
- package/dist/index-node.d.ts.map +1 -1
- package/dist/index-node.js +400 -776
- package/dist/planning/agents/cross-task-reviewer.d.ts +0 -14
- package/dist/planning/agents/cross-task-reviewer.d.ts.map +1 -1
- package/dist/planning/agents/planner.d.ts +11 -6
- package/dist/planning/agents/planner.d.ts.map +1 -1
- package/dist/planning/index.d.ts +1 -1
- package/dist/planning/index.d.ts.map +1 -1
- package/dist/planning/plan-manager.d.ts +0 -1
- package/dist/planning/plan-manager.d.ts.map +1 -1
- package/dist/planning/planning-meeting.d.ts +11 -13
- package/dist/planning/planning-meeting.d.ts.map +1 -1
- package/dist/planning/sprint-plan.d.ts +7 -3
- package/dist/planning/sprint-plan.d.ts.map +1 -1
- package/dist/utils/json-extractor.d.ts +6 -2
- package/dist/utils/json-extractor.d.ts.map +1 -1
- package/dist/utils/structured-output.d.ts +14 -0
- package/dist/utils/structured-output.d.ts.map +1 -0
- package/package.json +5 -4
- package/dist/project/knowledge-base.d.ts +0 -24
- package/dist/project/knowledge-base.d.ts.map +0 -1
- package/dist/worktree/index.d.ts +0 -2
- package/dist/worktree/index.d.ts.map +0 -1
- package/dist/worktree/worktree-config.d.ts +0 -2
- package/dist/worktree/worktree-config.d.ts.map +0 -1
- package/dist/worktree/worktree-manager.d.ts +0 -2
- package/dist/worktree/worktree-manager.d.ts.map +0 -1
package/dist/index-node.js
CHANGED
|
@@ -522,9 +522,6 @@ var init_src = __esm(() => {
|
|
|
522
522
|
|
|
523
523
|
// src/core/config.ts
|
|
524
524
|
function getLocusPath(projectPath, fileName) {
|
|
525
|
-
if (fileName === "projectContextFile" || fileName === "projectProgressFile") {
|
|
526
|
-
return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.projectDir, LOCUS_CONFIG[fileName]);
|
|
527
|
-
}
|
|
528
525
|
return import_node_path.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG[fileName]);
|
|
529
526
|
}
|
|
530
527
|
function getAgentArtifactsPath(projectPath, agentId) {
|
|
@@ -552,14 +549,12 @@ var init_config = __esm(() => {
|
|
|
552
549
|
settingsFile: "settings.json",
|
|
553
550
|
indexFile: "codebase-index.json",
|
|
554
551
|
contextFile: "LOCUS.md",
|
|
552
|
+
learningsFile: "LEARNINGS.md",
|
|
555
553
|
artifactsDir: "artifacts",
|
|
556
554
|
documentsDir: "documents",
|
|
557
555
|
sessionsDir: "sessions",
|
|
558
556
|
reviewsDir: "reviews",
|
|
559
|
-
plansDir: "plans"
|
|
560
|
-
projectDir: "project",
|
|
561
|
-
projectContextFile: "context.md",
|
|
562
|
-
projectProgressFile: "progress.md"
|
|
557
|
+
plansDir: "plans"
|
|
563
558
|
};
|
|
564
559
|
LOCUS_GITIGNORE_PATTERNS = [
|
|
565
560
|
"# Locus AI - Session data (user-specific, can grow large)",
|
|
@@ -577,11 +572,8 @@ var init_config = __esm(() => {
|
|
|
577
572
|
"# Locus AI - Settings (contains API key, telegram config, etc.)",
|
|
578
573
|
".locus/settings.json",
|
|
579
574
|
"",
|
|
580
|
-
"# Locus AI - Configuration (contains project context,
|
|
581
|
-
".locus/config.json"
|
|
582
|
-
"",
|
|
583
|
-
"# Locus AI - Project progress (contains project progress, etc.)",
|
|
584
|
-
".locus/project/progress.md"
|
|
575
|
+
"# Locus AI - Configuration (contains project context, etc.)",
|
|
576
|
+
".locus/config.json"
|
|
585
577
|
];
|
|
586
578
|
});
|
|
587
579
|
|
|
@@ -820,17 +812,22 @@ class ClaudeRunner {
|
|
|
820
812
|
});
|
|
821
813
|
});
|
|
822
814
|
}
|
|
823
|
-
|
|
815
|
+
buildCliArgs() {
|
|
824
816
|
const args = [
|
|
825
|
-
"--dangerously-skip-permissions",
|
|
826
817
|
"--print",
|
|
827
|
-
"--verbose",
|
|
828
818
|
"--output-format",
|
|
829
819
|
"stream-json",
|
|
820
|
+
"--verbose",
|
|
821
|
+
"--dangerously-skip-permissions",
|
|
822
|
+
"--no-session-persistence",
|
|
830
823
|
"--include-partial-messages",
|
|
831
824
|
"--model",
|
|
832
825
|
this.model
|
|
833
826
|
];
|
|
827
|
+
return args;
|
|
828
|
+
}
|
|
829
|
+
async* runStream(prompt) {
|
|
830
|
+
const args = this.buildCliArgs();
|
|
834
831
|
const env = getAugmentedEnv({
|
|
835
832
|
FORCE_COLOR: "1",
|
|
836
833
|
TERM: "xterm-256color"
|
|
@@ -1070,16 +1067,7 @@ class ClaudeRunner {
|
|
|
1070
1067
|
}
|
|
1071
1068
|
executeRun(prompt) {
|
|
1072
1069
|
return new Promise((resolve2, reject) => {
|
|
1073
|
-
const args =
|
|
1074
|
-
"--dangerously-skip-permissions",
|
|
1075
|
-
"--print",
|
|
1076
|
-
"--verbose",
|
|
1077
|
-
"--output-format",
|
|
1078
|
-
"stream-json",
|
|
1079
|
-
"--include-partial-messages",
|
|
1080
|
-
"--model",
|
|
1081
|
-
this.model
|
|
1082
|
-
];
|
|
1070
|
+
const args = this.buildCliArgs();
|
|
1083
1071
|
const env = getAugmentedEnv({
|
|
1084
1072
|
FORCE_COLOR: "1",
|
|
1085
1073
|
TERM: "xterm-256color"
|
|
@@ -1200,7 +1188,7 @@ class CodexRunner {
|
|
|
1200
1188
|
eventEmitter;
|
|
1201
1189
|
currentToolName;
|
|
1202
1190
|
timeoutMs;
|
|
1203
|
-
constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CODEX], log,
|
|
1191
|
+
constructor(projectPath, model = DEFAULT_MODEL[PROVIDER.CODEX], log, reasoningEffort, timeoutMs) {
|
|
1204
1192
|
this.projectPath = projectPath;
|
|
1205
1193
|
this.model = model;
|
|
1206
1194
|
this.log = log;
|
|
@@ -1531,7 +1519,7 @@ function createAiRunner(provider, config) {
|
|
|
1531
1519
|
const model = config.model ?? DEFAULT_MODEL[resolvedProvider];
|
|
1532
1520
|
switch (resolvedProvider) {
|
|
1533
1521
|
case PROVIDER.CODEX:
|
|
1534
|
-
return new CodexRunner(config.projectPath, model, config.log, config.
|
|
1522
|
+
return new CodexRunner(config.projectPath, model, config.log, config.reasoningEffort ?? "high", config.timeoutMs);
|
|
1535
1523
|
default:
|
|
1536
1524
|
return new ClaudeRunner(config.projectPath, model, config.log, config.timeoutMs);
|
|
1537
1525
|
}
|
|
@@ -1638,106 +1626,6 @@ var init_git_utils = __esm(() => {
|
|
|
1638
1626
|
import_node_child_process3 = require("node:child_process");
|
|
1639
1627
|
});
|
|
1640
1628
|
|
|
1641
|
-
// src/project/knowledge-base.ts
|
|
1642
|
-
class KnowledgeBase {
|
|
1643
|
-
contextPath;
|
|
1644
|
-
progressPath;
|
|
1645
|
-
constructor(projectPath) {
|
|
1646
|
-
this.contextPath = getLocusPath(projectPath, "projectContextFile");
|
|
1647
|
-
this.progressPath = getLocusPath(projectPath, "projectProgressFile");
|
|
1648
|
-
}
|
|
1649
|
-
readContext() {
|
|
1650
|
-
if (!import_node_fs3.existsSync(this.contextPath)) {
|
|
1651
|
-
return "";
|
|
1652
|
-
}
|
|
1653
|
-
return import_node_fs3.readFileSync(this.contextPath, "utf-8");
|
|
1654
|
-
}
|
|
1655
|
-
readProgress() {
|
|
1656
|
-
if (!import_node_fs3.existsSync(this.progressPath)) {
|
|
1657
|
-
return "";
|
|
1658
|
-
}
|
|
1659
|
-
return import_node_fs3.readFileSync(this.progressPath, "utf-8");
|
|
1660
|
-
}
|
|
1661
|
-
updateContext(content) {
|
|
1662
|
-
this.ensureDir(this.contextPath);
|
|
1663
|
-
import_node_fs3.writeFileSync(this.contextPath, content);
|
|
1664
|
-
}
|
|
1665
|
-
updateProgress(entry) {
|
|
1666
|
-
this.ensureDir(this.progressPath);
|
|
1667
|
-
const existing = this.readProgress();
|
|
1668
|
-
const timestamp = (entry.timestamp ?? new Date).toISOString();
|
|
1669
|
-
const label = entry.role === "user" ? "User" : "Assistant";
|
|
1670
|
-
const line = `**${label}** (${timestamp}):
|
|
1671
|
-
${entry.content}`;
|
|
1672
|
-
const updated = existing ? `${existing}
|
|
1673
|
-
|
|
1674
|
-
---
|
|
1675
|
-
|
|
1676
|
-
${line}` : `# Conversation History
|
|
1677
|
-
|
|
1678
|
-
${line}`;
|
|
1679
|
-
import_node_fs3.writeFileSync(this.progressPath, updated);
|
|
1680
|
-
}
|
|
1681
|
-
getFullContext() {
|
|
1682
|
-
const context = this.readContext();
|
|
1683
|
-
const progress = this.readProgress();
|
|
1684
|
-
const parts = [];
|
|
1685
|
-
if (context.trim()) {
|
|
1686
|
-
parts.push(context.trim());
|
|
1687
|
-
}
|
|
1688
|
-
if (progress.trim()) {
|
|
1689
|
-
parts.push(progress.trim());
|
|
1690
|
-
}
|
|
1691
|
-
return parts.join(`
|
|
1692
|
-
|
|
1693
|
-
---
|
|
1694
|
-
|
|
1695
|
-
`);
|
|
1696
|
-
}
|
|
1697
|
-
initialize(info) {
|
|
1698
|
-
this.ensureDir(this.contextPath);
|
|
1699
|
-
this.ensureDir(this.progressPath);
|
|
1700
|
-
const techStackList = info.techStack.map((t) => `- ${t}`).join(`
|
|
1701
|
-
`);
|
|
1702
|
-
const contextContent = `# Project: ${info.name}
|
|
1703
|
-
|
|
1704
|
-
## Mission
|
|
1705
|
-
${info.mission}
|
|
1706
|
-
|
|
1707
|
-
## Tech Stack
|
|
1708
|
-
${techStackList}
|
|
1709
|
-
|
|
1710
|
-
## Architecture
|
|
1711
|
-
<!-- Describe your high-level architecture here -->
|
|
1712
|
-
|
|
1713
|
-
## Key Decisions
|
|
1714
|
-
<!-- Document important technical decisions and their rationale -->
|
|
1715
|
-
|
|
1716
|
-
## Feature Areas
|
|
1717
|
-
<!-- List your main feature areas and their status -->
|
|
1718
|
-
`;
|
|
1719
|
-
const progressContent = `# Conversation History
|
|
1720
|
-
`;
|
|
1721
|
-
import_node_fs3.writeFileSync(this.contextPath, contextContent);
|
|
1722
|
-
import_node_fs3.writeFileSync(this.progressPath, progressContent);
|
|
1723
|
-
}
|
|
1724
|
-
get exists() {
|
|
1725
|
-
return import_node_fs3.existsSync(this.contextPath) || import_node_fs3.existsSync(this.progressPath);
|
|
1726
|
-
}
|
|
1727
|
-
ensureDir(filePath) {
|
|
1728
|
-
const dir = import_node_path5.dirname(filePath);
|
|
1729
|
-
if (!import_node_fs3.existsSync(dir)) {
|
|
1730
|
-
import_node_fs3.mkdirSync(dir, { recursive: true });
|
|
1731
|
-
}
|
|
1732
|
-
}
|
|
1733
|
-
}
|
|
1734
|
-
var import_node_fs3, import_node_path5;
|
|
1735
|
-
var init_knowledge_base = __esm(() => {
|
|
1736
|
-
init_config();
|
|
1737
|
-
import_node_fs3 = require("node:fs");
|
|
1738
|
-
import_node_path5 = require("node:path");
|
|
1739
|
-
});
|
|
1740
|
-
|
|
1741
1629
|
// src/agent/git-workflow.ts
|
|
1742
1630
|
class GitWorkflow {
|
|
1743
1631
|
config;
|
|
@@ -1989,239 +1877,157 @@ class PromptBuilder {
|
|
|
1989
1877
|
constructor(projectPath) {
|
|
1990
1878
|
this.projectPath = projectPath;
|
|
1991
1879
|
}
|
|
1992
|
-
async build(task
|
|
1993
|
-
let prompt = `# Task: ${task.title}
|
|
1994
|
-
|
|
1995
|
-
`;
|
|
1880
|
+
async build(task) {
|
|
1996
1881
|
const roleText = this.roleToText(task.assigneeRole);
|
|
1882
|
+
const description = task.description || "No description provided.";
|
|
1883
|
+
const context = this.getProjectContext();
|
|
1884
|
+
const learnings = this.getLearningsContent();
|
|
1885
|
+
const knowledgeBase = this.getKnowledgeBaseSection();
|
|
1886
|
+
let sections = "";
|
|
1997
1887
|
if (roleText) {
|
|
1998
|
-
|
|
1888
|
+
sections += `
|
|
1889
|
+
<role>
|
|
1999
1890
|
You are acting as a ${roleText}.
|
|
2000
|
-
|
|
1891
|
+
</role>
|
|
2001
1892
|
`;
|
|
2002
1893
|
}
|
|
2003
|
-
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
`;
|
|
2007
|
-
const projectConfig = this.getProjectConfig();
|
|
2008
|
-
if (projectConfig) {
|
|
2009
|
-
prompt += `## Project Metadata
|
|
2010
|
-
`;
|
|
2011
|
-
prompt += `- Version: ${projectConfig.version || "Unknown"}
|
|
2012
|
-
`;
|
|
2013
|
-
prompt += `- Created At: ${projectConfig.createdAt || "Unknown"}
|
|
2014
|
-
|
|
2015
|
-
`;
|
|
2016
|
-
}
|
|
2017
|
-
let serverContext = null;
|
|
2018
|
-
if (options.taskContext) {
|
|
2019
|
-
try {
|
|
2020
|
-
serverContext = JSON.parse(options.taskContext);
|
|
2021
|
-
} catch {
|
|
2022
|
-
serverContext = { context: options.taskContext };
|
|
2023
|
-
}
|
|
2024
|
-
}
|
|
2025
|
-
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
2026
|
-
let hasLocalContext = false;
|
|
2027
|
-
if (import_node_fs4.existsSync(contextPath)) {
|
|
2028
|
-
try {
|
|
2029
|
-
const context = import_node_fs4.readFileSync(contextPath, "utf-8");
|
|
2030
|
-
if (context.trim().length > 20) {
|
|
2031
|
-
prompt += `## Project Context (Local)
|
|
1894
|
+
if (context) {
|
|
1895
|
+
sections += `
|
|
1896
|
+
<project_context>
|
|
2032
1897
|
${context}
|
|
2033
|
-
|
|
1898
|
+
</project_context>
|
|
2034
1899
|
`;
|
|
2035
|
-
hasLocalContext = true;
|
|
2036
|
-
}
|
|
2037
|
-
} catch (err) {
|
|
2038
|
-
console.warn(`Warning: Could not read context file: ${err}`);
|
|
2039
|
-
}
|
|
2040
1900
|
}
|
|
2041
|
-
|
|
2042
|
-
|
|
2043
|
-
|
|
2044
|
-
|
|
2045
|
-
${fallback}
|
|
2046
|
-
|
|
2047
|
-
`;
|
|
2048
|
-
}
|
|
2049
|
-
}
|
|
2050
|
-
if (serverContext) {
|
|
2051
|
-
prompt += `## Project Context (Server)
|
|
2052
|
-
`;
|
|
2053
|
-
const project = serverContext.project;
|
|
2054
|
-
if (project) {
|
|
2055
|
-
prompt += `- Project: ${project.name || "Unknown"}
|
|
1901
|
+
sections += `
|
|
1902
|
+
<knowledge_base>
|
|
1903
|
+
${knowledgeBase}
|
|
1904
|
+
</knowledge_base>
|
|
2056
1905
|
`;
|
|
2057
|
-
|
|
2058
|
-
|
|
2059
|
-
|
|
2060
|
-
|
|
2061
|
-
|
|
2062
|
-
|
|
2063
|
-
prompt += `
|
|
2064
|
-
${serverContext.context}
|
|
2065
|
-
`;
|
|
2066
|
-
}
|
|
2067
|
-
prompt += `
|
|
2068
|
-
`;
|
|
2069
|
-
}
|
|
2070
|
-
prompt += this.getProjectStructure();
|
|
2071
|
-
prompt += `## Project Knowledge Base
|
|
2072
|
-
`;
|
|
2073
|
-
prompt += `You have access to the following documentation directories for context:
|
|
2074
|
-
`;
|
|
2075
|
-
prompt += `- Artifacts: \`.locus/artifacts\`
|
|
2076
|
-
`;
|
|
2077
|
-
prompt += `- Documents: \`.locus/documents\`
|
|
2078
|
-
`;
|
|
2079
|
-
prompt += `If you need more information about the project strategies, plans, or architecture, please read files in these directories.
|
|
2080
|
-
|
|
2081
|
-
`;
|
|
2082
|
-
const indexPath = getLocusPath(this.projectPath, "indexFile");
|
|
2083
|
-
if (import_node_fs4.existsSync(indexPath)) {
|
|
2084
|
-
prompt += `## Codebase Overview
|
|
2085
|
-
There is an index file in the .locus/codebase-index.json and if you need you can check it.
|
|
2086
|
-
|
|
1906
|
+
if (learnings) {
|
|
1907
|
+
sections += `
|
|
1908
|
+
<learnings>
|
|
1909
|
+
These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
|
|
1910
|
+
${learnings}
|
|
1911
|
+
</learnings>
|
|
2087
1912
|
`;
|
|
2088
1913
|
}
|
|
2089
1914
|
if (task.docs && task.docs.length > 0) {
|
|
2090
|
-
|
|
2091
|
-
`;
|
|
2092
|
-
prompt += `> Full content available on server. Rely on Task Description for specific requirements.
|
|
2093
|
-
|
|
2094
|
-
`;
|
|
1915
|
+
let docsContent = "";
|
|
2095
1916
|
for (const doc of task.docs) {
|
|
2096
1917
|
const content = doc.content || "";
|
|
2097
1918
|
const limit = 800;
|
|
2098
1919
|
const preview = content.slice(0, limit);
|
|
2099
1920
|
const isTruncated = content.length > limit;
|
|
2100
|
-
|
|
1921
|
+
docsContent += `### ${doc.title}
|
|
2101
1922
|
${preview}${isTruncated ? `
|
|
2102
1923
|
...(truncated)...` : ""}
|
|
2103
1924
|
|
|
2104
1925
|
`;
|
|
2105
1926
|
}
|
|
1927
|
+
sections += `
|
|
1928
|
+
<documents>
|
|
1929
|
+
${docsContent.trimEnd()}
|
|
1930
|
+
</documents>
|
|
1931
|
+
`;
|
|
2106
1932
|
}
|
|
2107
1933
|
if (task.acceptanceChecklist && task.acceptanceChecklist.length > 0) {
|
|
2108
|
-
|
|
2109
|
-
`;
|
|
1934
|
+
let criteria = "";
|
|
2110
1935
|
for (const item of task.acceptanceChecklist) {
|
|
2111
|
-
|
|
1936
|
+
criteria += `- ${item.done ? "[x]" : "[ ]"} ${item.text}
|
|
2112
1937
|
`;
|
|
2113
1938
|
}
|
|
2114
|
-
|
|
1939
|
+
sections += `
|
|
1940
|
+
<acceptance_criteria>
|
|
1941
|
+
${criteria.trimEnd()}
|
|
1942
|
+
</acceptance_criteria>
|
|
2115
1943
|
`;
|
|
2116
1944
|
}
|
|
2117
1945
|
if (task.comments && task.comments.length > 0) {
|
|
2118
|
-
const
|
|
2119
|
-
|
|
1946
|
+
const filteredComments = task.comments.filter((comment) => comment.author !== "system");
|
|
1947
|
+
const comments = filteredComments.slice(0, 3);
|
|
1948
|
+
if (comments.length > 0) {
|
|
1949
|
+
let commentsContent = "";
|
|
1950
|
+
for (const comment of comments) {
|
|
1951
|
+
const date = new Date(comment.createdAt).toLocaleString();
|
|
1952
|
+
commentsContent += `- ${comment.author} (${date}): ${comment.text}
|
|
2120
1953
|
`;
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
prompt += `### ${comment.author} (${date})
|
|
2127
|
-
${comment.text}
|
|
2128
|
-
|
|
1954
|
+
}
|
|
1955
|
+
sections += `
|
|
1956
|
+
<feedback>
|
|
1957
|
+
${commentsContent.trimEnd()}
|
|
1958
|
+
</feedback>
|
|
2129
1959
|
`;
|
|
2130
1960
|
}
|
|
2131
1961
|
}
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
1962
|
+
return `<task_execution>
|
|
1963
|
+
Complete this task: ${task.title}
|
|
1964
|
+
|
|
1965
|
+
<description>
|
|
1966
|
+
${description}
|
|
1967
|
+
</description>
|
|
1968
|
+
${sections}
|
|
1969
|
+
<rules>
|
|
1970
|
+
- Complete the task as described
|
|
1971
|
+
- Save any high-level documentation (PRDs, technical drafts, architecture docs) in \`.locus/artifacts/\`
|
|
1972
|
+
- Use relative paths from the project root at all times — no absolute local paths
|
|
1973
|
+
- Do NOT run \`git add\`, \`git commit\`, \`git push\`, or create branches — Locus handles git automatically
|
|
1974
|
+
</rules>
|
|
1975
|
+
</task_execution>`;
|
|
2139
1976
|
}
|
|
2140
1977
|
async buildGenericPrompt(query) {
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
2146
|
-
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
prompt += `## Project Metadata
|
|
1978
|
+
const context = this.getProjectContext();
|
|
1979
|
+
const learnings = this.getLearningsContent();
|
|
1980
|
+
const knowledgeBase = this.getKnowledgeBaseSection();
|
|
1981
|
+
let sections = "";
|
|
1982
|
+
if (context) {
|
|
1983
|
+
sections += `
|
|
1984
|
+
<project_context>
|
|
1985
|
+
${context}
|
|
1986
|
+
</project_context>
|
|
2151
1987
|
`;
|
|
2152
|
-
|
|
1988
|
+
}
|
|
1989
|
+
sections += `
|
|
1990
|
+
<knowledge_base>
|
|
1991
|
+
${knowledgeBase}
|
|
1992
|
+
</knowledge_base>
|
|
2153
1993
|
`;
|
|
2154
|
-
|
|
2155
|
-
|
|
1994
|
+
if (learnings) {
|
|
1995
|
+
sections += `
|
|
1996
|
+
<learnings>
|
|
1997
|
+
These are accumulated lessons from past tasks. Follow them to avoid repeating mistakes:
|
|
1998
|
+
${learnings}
|
|
1999
|
+
</learnings>
|
|
2156
2000
|
`;
|
|
2157
2001
|
}
|
|
2002
|
+
return `<direct_execution>
|
|
2003
|
+
Execute this prompt: ${query}
|
|
2004
|
+
${sections}
|
|
2005
|
+
<rules>
|
|
2006
|
+
- Execute the prompt based on the provided project context
|
|
2007
|
+
- Use relative paths from the project root at all times — no absolute local paths
|
|
2008
|
+
- Do NOT run \`git add\`, \`git commit\`, \`git push\`, or create branches — Locus handles git automatically
|
|
2009
|
+
</rules>
|
|
2010
|
+
</direct_execution>`;
|
|
2011
|
+
}
|
|
2012
|
+
getProjectContext() {
|
|
2158
2013
|
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
2159
|
-
|
|
2160
|
-
if (import_node_fs4.existsSync(contextPath)) {
|
|
2014
|
+
if (import_node_fs3.existsSync(contextPath)) {
|
|
2161
2015
|
try {
|
|
2162
|
-
const context =
|
|
2016
|
+
const context = import_node_fs3.readFileSync(contextPath, "utf-8");
|
|
2163
2017
|
if (context.trim().length > 20) {
|
|
2164
|
-
|
|
2165
|
-
${context}
|
|
2166
|
-
|
|
2167
|
-
`;
|
|
2168
|
-
hasLocalContext = true;
|
|
2018
|
+
return context;
|
|
2169
2019
|
}
|
|
2170
2020
|
} catch (err) {
|
|
2171
2021
|
console.warn(`Warning: Could not read context file: ${err}`);
|
|
2172
2022
|
}
|
|
2173
2023
|
}
|
|
2174
|
-
|
|
2175
|
-
const fallback = this.getFallbackContext();
|
|
2176
|
-
if (fallback) {
|
|
2177
|
-
prompt += `## Project Context (README Fallback)
|
|
2178
|
-
${fallback}
|
|
2179
|
-
|
|
2180
|
-
`;
|
|
2181
|
-
}
|
|
2182
|
-
}
|
|
2183
|
-
prompt += this.getProjectStructure();
|
|
2184
|
-
prompt += `## Project Knowledge Base
|
|
2185
|
-
`;
|
|
2186
|
-
prompt += `You have access to the following documentation directories for context:
|
|
2187
|
-
`;
|
|
2188
|
-
prompt += `- Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
|
|
2189
|
-
`;
|
|
2190
|
-
prompt += `- Documents: \`.locus/documents\` (synced from cloud)
|
|
2191
|
-
`;
|
|
2192
|
-
prompt += `If you need more information about the project strategies, plans, or architecture, please read files in these directories.
|
|
2193
|
-
|
|
2194
|
-
`;
|
|
2195
|
-
const indexPath = getLocusPath(this.projectPath, "indexFile");
|
|
2196
|
-
if (import_node_fs4.existsSync(indexPath)) {
|
|
2197
|
-
prompt += `## Codebase Overview
|
|
2198
|
-
There is an index file in the .locus/codebase-index.json and if you need you can check it.
|
|
2199
|
-
|
|
2200
|
-
`;
|
|
2201
|
-
}
|
|
2202
|
-
prompt += `## Instructions
|
|
2203
|
-
1. Execute the prompt based on the provided project context.
|
|
2204
|
-
2. **Paths**: Use relative paths from the project root at all times. Do NOT use absolute local paths (e.g., /Users/...).
|
|
2205
|
-
3. **Git**: Do NOT run \`git add\`, \`git commit\`, \`git push\`, or create branches. The Locus system handles all git operations automatically after your execution completes.
|
|
2206
|
-
4. **Progress**: Do NOT modify \`.locus/project/progress.md\`. The system updates it automatically.`;
|
|
2207
|
-
return prompt;
|
|
2208
|
-
}
|
|
2209
|
-
getProjectConfig() {
|
|
2210
|
-
const configPath = getLocusPath(this.projectPath, "configFile");
|
|
2211
|
-
if (import_node_fs4.existsSync(configPath)) {
|
|
2212
|
-
try {
|
|
2213
|
-
return JSON.parse(import_node_fs4.readFileSync(configPath, "utf-8"));
|
|
2214
|
-
} catch {
|
|
2215
|
-
return null;
|
|
2216
|
-
}
|
|
2217
|
-
}
|
|
2218
|
-
return null;
|
|
2024
|
+
return this.getFallbackContext() || null;
|
|
2219
2025
|
}
|
|
2220
2026
|
getFallbackContext() {
|
|
2221
|
-
const readmePath =
|
|
2222
|
-
if (
|
|
2027
|
+
const readmePath = import_node_path5.join(this.projectPath, "README.md");
|
|
2028
|
+
if (import_node_fs3.existsSync(readmePath)) {
|
|
2223
2029
|
try {
|
|
2224
|
-
const content =
|
|
2030
|
+
const content = import_node_fs3.readFileSync(readmePath, "utf-8");
|
|
2225
2031
|
const limit = 1000;
|
|
2226
2032
|
return content.slice(0, limit) + (content.length > limit ? `
|
|
2227
2033
|
...(truncated)...` : "");
|
|
@@ -2231,32 +2037,28 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
2231
2037
|
}
|
|
2232
2038
|
return "";
|
|
2233
2039
|
}
|
|
2234
|
-
|
|
2040
|
+
getKnowledgeBaseSection() {
|
|
2041
|
+
return `You have access to the following documentation directories for context:
|
|
2042
|
+
- Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
|
|
2043
|
+
- Documents: \`.locus/documents\` (synced from cloud)
|
|
2044
|
+
If you need more information about the project strategies, plans, or architecture, read files in these directories.`;
|
|
2045
|
+
}
|
|
2046
|
+
getLearningsContent() {
|
|
2047
|
+
const learningsPath = getLocusPath(this.projectPath, "learningsFile");
|
|
2048
|
+
if (!import_node_fs3.existsSync(learningsPath)) {
|
|
2049
|
+
return null;
|
|
2050
|
+
}
|
|
2235
2051
|
try {
|
|
2236
|
-
const
|
|
2237
|
-
const
|
|
2238
|
-
|
|
2239
|
-
|
|
2240
|
-
|
|
2241
|
-
return import_node_fs4.statSync(import_node_path6.join(this.projectPath, e)).isDirectory();
|
|
2242
|
-
} catch {
|
|
2243
|
-
return false;
|
|
2244
|
-
}
|
|
2245
|
-
});
|
|
2246
|
-
if (folders.length === 0)
|
|
2247
|
-
return "";
|
|
2248
|
-
let structure = `## Project Structure
|
|
2249
|
-
`;
|
|
2250
|
-
structure += `Key directories in this project:
|
|
2251
|
-
`;
|
|
2252
|
-
for (const folder of folders) {
|
|
2253
|
-
structure += `- \`${folder}/\`
|
|
2254
|
-
`;
|
|
2052
|
+
const content = import_node_fs3.readFileSync(learningsPath, "utf-8");
|
|
2053
|
+
const lines = content.split(`
|
|
2054
|
+
`).filter((l) => l.startsWith("- "));
|
|
2055
|
+
if (lines.length === 0) {
|
|
2056
|
+
return null;
|
|
2255
2057
|
}
|
|
2256
|
-
return
|
|
2257
|
-
|
|
2058
|
+
return lines.join(`
|
|
2059
|
+
`);
|
|
2258
2060
|
} catch {
|
|
2259
|
-
return
|
|
2061
|
+
return null;
|
|
2260
2062
|
}
|
|
2261
2063
|
}
|
|
2262
2064
|
roleToText(role) {
|
|
@@ -2279,11 +2081,11 @@ There is an index file in the .locus/codebase-index.json and if you need you can
|
|
|
2279
2081
|
}
|
|
2280
2082
|
}
|
|
2281
2083
|
}
|
|
2282
|
-
var
|
|
2084
|
+
var import_node_fs3, import_node_path5, import_shared2;
|
|
2283
2085
|
var init_prompt_builder = __esm(() => {
|
|
2284
2086
|
init_config();
|
|
2285
|
-
|
|
2286
|
-
|
|
2087
|
+
import_node_fs3 = require("node:fs");
|
|
2088
|
+
import_node_path5 = require("node:path");
|
|
2287
2089
|
import_shared2 = require("@locusai/shared");
|
|
2288
2090
|
});
|
|
2289
2091
|
|
|
@@ -2401,7 +2203,6 @@ class AgentWorker {
|
|
|
2401
2203
|
client;
|
|
2402
2204
|
aiRunner;
|
|
2403
2205
|
taskExecutor;
|
|
2404
|
-
knowledgeBase;
|
|
2405
2206
|
gitWorkflow;
|
|
2406
2207
|
maxTasks = 50;
|
|
2407
2208
|
tasksCompleted = 0;
|
|
@@ -2441,7 +2242,6 @@ class AgentWorker {
|
|
|
2441
2242
|
projectPath,
|
|
2442
2243
|
log
|
|
2443
2244
|
});
|
|
2444
|
-
this.knowledgeBase = new KnowledgeBase(projectPath);
|
|
2445
2245
|
this.gitWorkflow = new GitWorkflow(config, log);
|
|
2446
2246
|
const providerLabel = provider === "codex" ? "Codex" : "Claude";
|
|
2447
2247
|
this.log(`Using ${providerLabel} CLI for all phases`, "info");
|
|
@@ -2515,20 +2315,6 @@ class AgentWorker {
|
|
|
2515
2315
|
};
|
|
2516
2316
|
}
|
|
2517
2317
|
}
|
|
2518
|
-
updateProgress(task, summary) {
|
|
2519
|
-
try {
|
|
2520
|
-
this.knowledgeBase.updateProgress({
|
|
2521
|
-
role: "user",
|
|
2522
|
-
content: task.title
|
|
2523
|
-
});
|
|
2524
|
-
this.knowledgeBase.updateProgress({
|
|
2525
|
-
role: "assistant",
|
|
2526
|
-
content: summary
|
|
2527
|
-
});
|
|
2528
|
-
} catch (err) {
|
|
2529
|
-
this.log(`Failed to update progress: ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
2530
|
-
}
|
|
2531
|
-
}
|
|
2532
2318
|
startHeartbeat() {
|
|
2533
2319
|
this.sendHeartbeat();
|
|
2534
2320
|
this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), 60000);
|
|
@@ -2582,7 +2368,7 @@ class AgentWorker {
|
|
|
2582
2368
|
assignedTo: null
|
|
2583
2369
|
});
|
|
2584
2370
|
await this.client.tasks.addComment(task.id, this.config.workspaceId, {
|
|
2585
|
-
author:
|
|
2371
|
+
author: "system",
|
|
2586
2372
|
text: `⚠️ Agent execution finished with no file changes, so no commit was created.
|
|
2587
2373
|
|
|
2588
2374
|
${result.summary}`
|
|
@@ -2597,13 +2383,12 @@ ${result.summary}`
|
|
|
2597
2383
|
|
|
2598
2384
|
Branch: \`${result.branch}\`` : "";
|
|
2599
2385
|
await this.client.tasks.addComment(task.id, this.config.workspaceId, {
|
|
2600
|
-
author:
|
|
2386
|
+
author: "system",
|
|
2601
2387
|
text: `✅ ${result.summary}${branchInfo}`
|
|
2602
2388
|
});
|
|
2603
2389
|
this.tasksCompleted++;
|
|
2604
2390
|
this.completedTaskList.push({ title: task.title, id: task.id });
|
|
2605
2391
|
this.taskSummaries.push(result.summary);
|
|
2606
|
-
this.updateProgress(task, result.summary);
|
|
2607
2392
|
}
|
|
2608
2393
|
} else {
|
|
2609
2394
|
this.log(`Failed: ${task.title} - ${result.summary}`, "error");
|
|
@@ -2612,7 +2397,7 @@ Branch: \`${result.branch}\`` : "";
|
|
|
2612
2397
|
assignedTo: null
|
|
2613
2398
|
});
|
|
2614
2399
|
await this.client.tasks.addComment(task.id, this.config.workspaceId, {
|
|
2615
|
-
author:
|
|
2400
|
+
author: "system",
|
|
2616
2401
|
text: `❌ ${result.summary}`
|
|
2617
2402
|
});
|
|
2618
2403
|
}
|
|
@@ -2648,7 +2433,6 @@ var init_worker = __esm(() => {
|
|
|
2648
2433
|
init_config();
|
|
2649
2434
|
init_git_utils();
|
|
2650
2435
|
init_src();
|
|
2651
|
-
init_knowledge_base();
|
|
2652
2436
|
init_colors();
|
|
2653
2437
|
init_git_workflow();
|
|
2654
2438
|
init_task_executor();
|
|
@@ -2673,6 +2457,7 @@ __export(exports_index_node, {
|
|
|
2673
2457
|
sprintPlanToMarkdown: () => sprintPlanToMarkdown,
|
|
2674
2458
|
plannedTasksToCreatePayloads: () => plannedTasksToCreatePayloads,
|
|
2675
2459
|
parseSprintPlanFromAI: () => parseSprintPlanFromAI,
|
|
2460
|
+
parseJsonWithSchema: () => parseJsonWithSchema,
|
|
2676
2461
|
getRemoteUrl: () => getRemoteUrl,
|
|
2677
2462
|
getLocusPath: () => getLocusPath,
|
|
2678
2463
|
getDefaultBranch: () => getDefaultBranch,
|
|
@@ -2701,7 +2486,6 @@ __export(exports_index_node, {
|
|
|
2701
2486
|
LOCUS_SCHEMAS: () => LOCUS_SCHEMAS,
|
|
2702
2487
|
LOCUS_GITIGNORE_PATTERNS: () => LOCUS_GITIGNORE_PATTERNS,
|
|
2703
2488
|
LOCUS_CONFIG: () => LOCUS_CONFIG,
|
|
2704
|
-
KnowledgeBase: () => KnowledgeBase,
|
|
2705
2489
|
InvitationsModule: () => InvitationsModule,
|
|
2706
2490
|
HistoryManager: () => HistoryManager,
|
|
2707
2491
|
GitWorkflow: () => GitWorkflow,
|
|
@@ -2725,8 +2509,8 @@ module.exports = __toCommonJS(exports_index_node);
|
|
|
2725
2509
|
|
|
2726
2510
|
// src/core/indexer.ts
|
|
2727
2511
|
var import_node_crypto2 = require("node:crypto");
|
|
2728
|
-
var
|
|
2729
|
-
var
|
|
2512
|
+
var import_node_fs4 = require("node:fs");
|
|
2513
|
+
var import_node_path6 = require("node:path");
|
|
2730
2514
|
var import_globby = require("globby");
|
|
2731
2515
|
|
|
2732
2516
|
class CodebaseIndexer {
|
|
@@ -2735,7 +2519,7 @@ class CodebaseIndexer {
|
|
|
2735
2519
|
fullReindexRatioThreshold = 0.2;
|
|
2736
2520
|
constructor(projectPath) {
|
|
2737
2521
|
this.projectPath = projectPath;
|
|
2738
|
-
this.indexPath =
|
|
2522
|
+
this.indexPath = import_node_path6.join(projectPath, ".locus", "codebase-index.json");
|
|
2739
2523
|
}
|
|
2740
2524
|
async index(onProgress, treeSummarizer, force = false) {
|
|
2741
2525
|
if (!treeSummarizer) {
|
|
@@ -2791,11 +2575,11 @@ class CodebaseIndexer {
|
|
|
2791
2575
|
}
|
|
2792
2576
|
}
|
|
2793
2577
|
async getFileTree() {
|
|
2794
|
-
const gitmodulesPath =
|
|
2578
|
+
const gitmodulesPath = import_node_path6.join(this.projectPath, ".gitmodules");
|
|
2795
2579
|
const submoduleIgnores = [];
|
|
2796
|
-
if (
|
|
2580
|
+
if (import_node_fs4.existsSync(gitmodulesPath)) {
|
|
2797
2581
|
try {
|
|
2798
|
-
const content =
|
|
2582
|
+
const content = import_node_fs4.readFileSync(gitmodulesPath, "utf-8");
|
|
2799
2583
|
const lines = content.split(`
|
|
2800
2584
|
`);
|
|
2801
2585
|
for (const line of lines) {
|
|
@@ -2851,9 +2635,9 @@ class CodebaseIndexer {
|
|
|
2851
2635
|
});
|
|
2852
2636
|
}
|
|
2853
2637
|
loadIndex() {
|
|
2854
|
-
if (
|
|
2638
|
+
if (import_node_fs4.existsSync(this.indexPath)) {
|
|
2855
2639
|
try {
|
|
2856
|
-
return JSON.parse(
|
|
2640
|
+
return JSON.parse(import_node_fs4.readFileSync(this.indexPath, "utf-8"));
|
|
2857
2641
|
} catch {
|
|
2858
2642
|
return null;
|
|
2859
2643
|
}
|
|
@@ -2861,11 +2645,11 @@ class CodebaseIndexer {
|
|
|
2861
2645
|
return null;
|
|
2862
2646
|
}
|
|
2863
2647
|
saveIndex(index) {
|
|
2864
|
-
const dir =
|
|
2865
|
-
if (!
|
|
2866
|
-
|
|
2648
|
+
const dir = import_node_path6.dirname(this.indexPath);
|
|
2649
|
+
if (!import_node_fs4.existsSync(dir)) {
|
|
2650
|
+
import_node_fs4.mkdirSync(dir, { recursive: true });
|
|
2867
2651
|
}
|
|
2868
|
-
|
|
2652
|
+
import_node_fs4.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
|
|
2869
2653
|
}
|
|
2870
2654
|
cloneIndex(index) {
|
|
2871
2655
|
return JSON.parse(JSON.stringify(index));
|
|
@@ -2881,7 +2665,7 @@ class CodebaseIndexer {
|
|
|
2881
2665
|
}
|
|
2882
2666
|
hashFile(filePath) {
|
|
2883
2667
|
try {
|
|
2884
|
-
const content =
|
|
2668
|
+
const content = import_node_fs4.readFileSync(import_node_path6.join(this.projectPath, filePath), "utf-8");
|
|
2885
2669
|
return import_node_crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
2886
2670
|
} catch {
|
|
2887
2671
|
return null;
|
|
@@ -2945,43 +2729,42 @@ class CodebaseIndexer {
|
|
|
2945
2729
|
// src/utils/json-extractor.ts
|
|
2946
2730
|
function extractJsonFromLLMOutput(raw) {
|
|
2947
2731
|
const trimmed = raw.trim();
|
|
2948
|
-
const
|
|
2949
|
-
if (
|
|
2950
|
-
return
|
|
2951
|
-
}
|
|
2952
|
-
const
|
|
2953
|
-
if (
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
|
|
2961
|
-
|
|
2962
|
-
|
|
2963
|
-
continue;
|
|
2964
|
-
}
|
|
2965
|
-
if (inString) {
|
|
2732
|
+
const fenceMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)\n?\s*```/);
|
|
2733
|
+
if (fenceMatch) {
|
|
2734
|
+
return fenceMatch[1].trim();
|
|
2735
|
+
}
|
|
2736
|
+
const start = trimmed.indexOf("{");
|
|
2737
|
+
if (start !== -1) {
|
|
2738
|
+
let depth = 0;
|
|
2739
|
+
let inString = false;
|
|
2740
|
+
let isEscape = false;
|
|
2741
|
+
for (let i = start;i < trimmed.length; i++) {
|
|
2742
|
+
const ch = trimmed[i];
|
|
2743
|
+
if (isEscape) {
|
|
2744
|
+
isEscape = false;
|
|
2745
|
+
continue;
|
|
2746
|
+
}
|
|
2966
2747
|
if (ch === "\\") {
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
inString = false;
|
|
2748
|
+
isEscape = true;
|
|
2749
|
+
continue;
|
|
2970
2750
|
}
|
|
2971
|
-
|
|
2972
|
-
|
|
2973
|
-
|
|
2974
|
-
|
|
2975
|
-
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
if (
|
|
2980
|
-
|
|
2751
|
+
if (ch === '"') {
|
|
2752
|
+
inString = !inString;
|
|
2753
|
+
continue;
|
|
2754
|
+
}
|
|
2755
|
+
if (inString)
|
|
2756
|
+
continue;
|
|
2757
|
+
if (ch === "{")
|
|
2758
|
+
depth++;
|
|
2759
|
+
else if (ch === "}") {
|
|
2760
|
+
depth--;
|
|
2761
|
+
if (depth === 0) {
|
|
2762
|
+
return trimmed.slice(start, i + 1);
|
|
2763
|
+
}
|
|
2981
2764
|
}
|
|
2982
2765
|
}
|
|
2983
2766
|
}
|
|
2984
|
-
return trimmed
|
|
2767
|
+
return trimmed;
|
|
2985
2768
|
}
|
|
2986
2769
|
|
|
2987
2770
|
// src/agent/codebase-indexer-service.ts
|
|
@@ -2995,22 +2778,30 @@ class CodebaseIndexerService {
|
|
|
2995
2778
|
async reindex(force = false) {
|
|
2996
2779
|
try {
|
|
2997
2780
|
const index = await this.indexer.index((msg) => this.deps.log(msg, "info"), async (tree) => {
|
|
2998
|
-
const prompt =
|
|
2999
|
-
|
|
3000
|
-
2. Responsibilities of each directory/file
|
|
3001
|
-
3. Overall project structure
|
|
3002
|
-
|
|
3003
|
-
Analyze this file tree and provide a JSON response with:
|
|
3004
|
-
- "symbols": object mapping symbol names to file paths (array)
|
|
3005
|
-
- "responsibilities": object mapping paths to brief descriptions
|
|
2781
|
+
const prompt = `<codebase_analysis>
|
|
2782
|
+
Analyze this codebase file tree and extract key information.
|
|
3006
2783
|
|
|
3007
|
-
|
|
2784
|
+
<file_tree>
|
|
3008
2785
|
${tree}
|
|
2786
|
+
</file_tree>
|
|
3009
2787
|
|
|
3010
|
-
|
|
2788
|
+
<rules>
|
|
2789
|
+
- Extract key symbols (classes, functions, types) and their file locations
|
|
2790
|
+
- Identify responsibilities of each directory/file
|
|
2791
|
+
- Map overall project structure
|
|
2792
|
+
</rules>
|
|
2793
|
+
|
|
2794
|
+
<output>
|
|
2795
|
+
Return ONLY valid JSON (no code fences, no markdown):
|
|
2796
|
+
{
|
|
2797
|
+
"symbols": { "symbolName": ["file/path.ts"] },
|
|
2798
|
+
"responsibilities": { "path": "brief description" }
|
|
2799
|
+
}
|
|
2800
|
+
</output>
|
|
2801
|
+
</codebase_analysis>`;
|
|
3011
2802
|
const response = await this.deps.aiRunner.run(prompt);
|
|
3012
|
-
const jsonStr = extractJsonFromLLMOutput(response);
|
|
3013
2803
|
try {
|
|
2804
|
+
const jsonStr = extractJsonFromLLMOutput(response);
|
|
3014
2805
|
return JSON.parse(jsonStr);
|
|
3015
2806
|
} catch {
|
|
3016
2807
|
return { symbols: {}, responsibilities: {}, lastIndexed: "" };
|
|
@@ -3029,8 +2820,8 @@ Return ONLY valid JSON, no markdown formatting.`;
|
|
|
3029
2820
|
}
|
|
3030
2821
|
// src/agent/document-fetcher.ts
|
|
3031
2822
|
init_config();
|
|
3032
|
-
var
|
|
3033
|
-
var
|
|
2823
|
+
var import_node_fs5 = require("node:fs");
|
|
2824
|
+
var import_node_path7 = require("node:path");
|
|
3034
2825
|
|
|
3035
2826
|
class DocumentFetcher {
|
|
3036
2827
|
deps;
|
|
@@ -3039,8 +2830,8 @@ class DocumentFetcher {
|
|
|
3039
2830
|
}
|
|
3040
2831
|
async fetch() {
|
|
3041
2832
|
const documentsDir = getLocusPath(this.deps.projectPath, "documentsDir");
|
|
3042
|
-
if (!
|
|
3043
|
-
|
|
2833
|
+
if (!import_node_fs5.existsSync(documentsDir)) {
|
|
2834
|
+
import_node_fs5.mkdirSync(documentsDir, { recursive: true });
|
|
3044
2835
|
}
|
|
3045
2836
|
try {
|
|
3046
2837
|
const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
|
|
@@ -3053,14 +2844,14 @@ class DocumentFetcher {
|
|
|
3053
2844
|
continue;
|
|
3054
2845
|
}
|
|
3055
2846
|
const groupName = groupMap.get(doc.groupId || "") || "General";
|
|
3056
|
-
const groupDir =
|
|
3057
|
-
if (!
|
|
3058
|
-
|
|
2847
|
+
const groupDir = import_node_path7.join(documentsDir, groupName);
|
|
2848
|
+
if (!import_node_fs5.existsSync(groupDir)) {
|
|
2849
|
+
import_node_fs5.mkdirSync(groupDir, { recursive: true });
|
|
3059
2850
|
}
|
|
3060
2851
|
const fileName = `${doc.title}.md`;
|
|
3061
|
-
const filePath =
|
|
3062
|
-
if (!
|
|
3063
|
-
|
|
2852
|
+
const filePath = import_node_path7.join(groupDir, fileName);
|
|
2853
|
+
if (!import_node_fs5.existsSync(filePath) || import_node_fs5.readFileSync(filePath, "utf-8") !== doc.content) {
|
|
2854
|
+
import_node_fs5.writeFileSync(filePath, doc.content || "");
|
|
3064
2855
|
fetchedCount++;
|
|
3065
2856
|
}
|
|
3066
2857
|
}
|
|
@@ -3376,7 +3167,6 @@ class PrService {
|
|
|
3376
3167
|
|
|
3377
3168
|
// src/agent/reviewer-worker.ts
|
|
3378
3169
|
init_src();
|
|
3379
|
-
init_knowledge_base();
|
|
3380
3170
|
init_colors();
|
|
3381
3171
|
function resolveProvider2(value) {
|
|
3382
3172
|
if (!value || value.startsWith("--"))
|
|
@@ -3391,7 +3181,6 @@ class ReviewerWorker {
|
|
|
3391
3181
|
client;
|
|
3392
3182
|
aiRunner;
|
|
3393
3183
|
prService;
|
|
3394
|
-
knowledgeBase;
|
|
3395
3184
|
heartbeatInterval = null;
|
|
3396
3185
|
currentTaskId = null;
|
|
3397
3186
|
maxReviews = 50;
|
|
@@ -3417,7 +3206,6 @@ class ReviewerWorker {
|
|
|
3417
3206
|
log
|
|
3418
3207
|
});
|
|
3419
3208
|
this.prService = new PrService(projectPath, log);
|
|
3420
|
-
this.knowledgeBase = new KnowledgeBase(projectPath);
|
|
3421
3209
|
const providerLabel = provider === "codex" ? "Codex" : "Claude";
|
|
3422
3210
|
this.log(`Reviewer agent using ${providerLabel} CLI`, "info");
|
|
3423
3211
|
}
|
|
@@ -3533,17 +3321,6 @@ ${summary}`;
|
|
|
3533
3321
|
this.sendHeartbeat();
|
|
3534
3322
|
const result = await this.reviewPr(pr);
|
|
3535
3323
|
if (result.reviewed) {
|
|
3536
|
-
const status = result.approved ? "APPROVED" : "CHANGES REQUESTED";
|
|
3537
|
-
try {
|
|
3538
|
-
this.knowledgeBase.updateProgress({
|
|
3539
|
-
role: "user",
|
|
3540
|
-
content: `Review PR #${pr.number}: ${pr.title}`
|
|
3541
|
-
});
|
|
3542
|
-
this.knowledgeBase.updateProgress({
|
|
3543
|
-
role: "assistant",
|
|
3544
|
-
content: `${status}: ${result.summary}`
|
|
3545
|
-
});
|
|
3546
|
-
} catch {}
|
|
3547
3324
|
this.reviewsCompleted++;
|
|
3548
3325
|
} else {
|
|
3549
3326
|
this.log(`Review skipped: ${result.summary}`, "warn");
|
|
@@ -4022,8 +3799,8 @@ class ExecEventEmitter {
|
|
|
4022
3799
|
}
|
|
4023
3800
|
// src/exec/history-manager.ts
|
|
4024
3801
|
init_config();
|
|
4025
|
-
var
|
|
4026
|
-
var
|
|
3802
|
+
var import_node_fs6 = require("node:fs");
|
|
3803
|
+
var import_node_path8 = require("node:path");
|
|
4027
3804
|
var DEFAULT_MAX_SESSIONS = 30;
|
|
4028
3805
|
function generateSessionId2() {
|
|
4029
3806
|
const timestamp = Date.now().toString(36);
|
|
@@ -4035,30 +3812,30 @@ class HistoryManager {
|
|
|
4035
3812
|
historyDir;
|
|
4036
3813
|
maxSessions;
|
|
4037
3814
|
constructor(projectPath, options) {
|
|
4038
|
-
this.historyDir = options?.historyDir ??
|
|
3815
|
+
this.historyDir = options?.historyDir ?? import_node_path8.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.sessionsDir);
|
|
4039
3816
|
this.maxSessions = options?.maxSessions ?? DEFAULT_MAX_SESSIONS;
|
|
4040
3817
|
this.ensureHistoryDir();
|
|
4041
3818
|
}
|
|
4042
3819
|
ensureHistoryDir() {
|
|
4043
|
-
if (!
|
|
4044
|
-
|
|
3820
|
+
if (!import_node_fs6.existsSync(this.historyDir)) {
|
|
3821
|
+
import_node_fs6.mkdirSync(this.historyDir, { recursive: true });
|
|
4045
3822
|
}
|
|
4046
3823
|
}
|
|
4047
3824
|
getSessionPath(sessionId) {
|
|
4048
|
-
return
|
|
3825
|
+
return import_node_path8.join(this.historyDir, `${sessionId}.json`);
|
|
4049
3826
|
}
|
|
4050
3827
|
saveSession(session) {
|
|
4051
3828
|
const filePath = this.getSessionPath(session.id);
|
|
4052
3829
|
session.updatedAt = Date.now();
|
|
4053
|
-
|
|
3830
|
+
import_node_fs6.writeFileSync(filePath, JSON.stringify(session, null, 2), "utf-8");
|
|
4054
3831
|
}
|
|
4055
3832
|
loadSession(sessionId) {
|
|
4056
3833
|
const filePath = this.getSessionPath(sessionId);
|
|
4057
|
-
if (!
|
|
3834
|
+
if (!import_node_fs6.existsSync(filePath)) {
|
|
4058
3835
|
return null;
|
|
4059
3836
|
}
|
|
4060
3837
|
try {
|
|
4061
|
-
const content =
|
|
3838
|
+
const content = import_node_fs6.readFileSync(filePath, "utf-8");
|
|
4062
3839
|
return JSON.parse(content);
|
|
4063
3840
|
} catch {
|
|
4064
3841
|
return null;
|
|
@@ -4066,18 +3843,18 @@ class HistoryManager {
|
|
|
4066
3843
|
}
|
|
4067
3844
|
deleteSession(sessionId) {
|
|
4068
3845
|
const filePath = this.getSessionPath(sessionId);
|
|
4069
|
-
if (!
|
|
3846
|
+
if (!import_node_fs6.existsSync(filePath)) {
|
|
4070
3847
|
return false;
|
|
4071
3848
|
}
|
|
4072
3849
|
try {
|
|
4073
|
-
|
|
3850
|
+
import_node_fs6.rmSync(filePath);
|
|
4074
3851
|
return true;
|
|
4075
3852
|
} catch {
|
|
4076
3853
|
return false;
|
|
4077
3854
|
}
|
|
4078
3855
|
}
|
|
4079
3856
|
listSessions(options) {
|
|
4080
|
-
const files =
|
|
3857
|
+
const files = import_node_fs6.readdirSync(this.historyDir);
|
|
4081
3858
|
let sessions = [];
|
|
4082
3859
|
for (const file of files) {
|
|
4083
3860
|
if (file.endsWith(".json")) {
|
|
@@ -4150,11 +3927,11 @@ class HistoryManager {
|
|
|
4150
3927
|
return deleted;
|
|
4151
3928
|
}
|
|
4152
3929
|
getSessionCount() {
|
|
4153
|
-
const files =
|
|
3930
|
+
const files = import_node_fs6.readdirSync(this.historyDir);
|
|
4154
3931
|
return files.filter((f) => f.endsWith(".json")).length;
|
|
4155
3932
|
}
|
|
4156
3933
|
sessionExists(sessionId) {
|
|
4157
|
-
return
|
|
3934
|
+
return import_node_fs6.existsSync(this.getSessionPath(sessionId));
|
|
4158
3935
|
}
|
|
4159
3936
|
findSessionByPartialId(partialId) {
|
|
4160
3937
|
const sessions = this.listSessions();
|
|
@@ -4168,12 +3945,12 @@ class HistoryManager {
|
|
|
4168
3945
|
return this.historyDir;
|
|
4169
3946
|
}
|
|
4170
3947
|
clearAllSessions() {
|
|
4171
|
-
const files =
|
|
3948
|
+
const files = import_node_fs6.readdirSync(this.historyDir);
|
|
4172
3949
|
let deleted = 0;
|
|
4173
3950
|
for (const file of files) {
|
|
4174
3951
|
if (file.endsWith(".json")) {
|
|
4175
3952
|
try {
|
|
4176
|
-
|
|
3953
|
+
import_node_fs6.rmSync(import_node_path8.join(this.historyDir, file));
|
|
4177
3954
|
deleted++;
|
|
4178
3955
|
} catch {}
|
|
4179
3956
|
}
|
|
@@ -4449,8 +4226,8 @@ init_src();
|
|
|
4449
4226
|
init_colors();
|
|
4450
4227
|
init_resolve_bin();
|
|
4451
4228
|
var import_node_child_process7 = require("node:child_process");
|
|
4452
|
-
var
|
|
4453
|
-
var
|
|
4229
|
+
var import_node_fs7 = require("node:fs");
|
|
4230
|
+
var import_node_path9 = require("node:path");
|
|
4454
4231
|
var import_node_url = require("node:url");
|
|
4455
4232
|
var import_shared4 = require("@locusai/shared");
|
|
4456
4233
|
var import_events4 = require("events");
|
|
@@ -4718,14 +4495,14 @@ ${agentId} finished (exit code: ${code})`);
|
|
|
4718
4495
|
}
|
|
4719
4496
|
resolveWorkerPath() {
|
|
4720
4497
|
const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator/index.ts");
|
|
4721
|
-
const currentModuleDir =
|
|
4498
|
+
const currentModuleDir = import_node_path9.dirname(currentModulePath);
|
|
4722
4499
|
const potentialPaths = [
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4500
|
+
import_node_path9.join(currentModuleDir, "..", "agent", "worker.js"),
|
|
4501
|
+
import_node_path9.join(currentModuleDir, "agent", "worker.js"),
|
|
4502
|
+
import_node_path9.join(currentModuleDir, "worker.js"),
|
|
4503
|
+
import_node_path9.join(currentModuleDir, "..", "agent", "worker.ts")
|
|
4727
4504
|
];
|
|
4728
|
-
return potentialPaths.find((p) =>
|
|
4505
|
+
return potentialPaths.find((p) => import_node_fs7.existsSync(p));
|
|
4729
4506
|
}
|
|
4730
4507
|
}
|
|
4731
4508
|
function killProcessTree(proc) {
|
|
@@ -4744,12 +4521,58 @@ function sleep(ms) {
|
|
|
4744
4521
|
}
|
|
4745
4522
|
// src/planning/plan-manager.ts
|
|
4746
4523
|
init_config();
|
|
4747
|
-
|
|
4748
|
-
var
|
|
4749
|
-
var import_node_path11 = require("node:path");
|
|
4524
|
+
var import_node_fs8 = require("node:fs");
|
|
4525
|
+
var import_node_path10 = require("node:path");
|
|
4750
4526
|
|
|
4751
4527
|
// src/planning/sprint-plan.ts
|
|
4752
4528
|
var import_shared5 = require("@locusai/shared");
|
|
4529
|
+
var import_zod = require("zod");
|
|
4530
|
+
|
|
4531
|
+
// src/utils/structured-output.ts
|
|
4532
|
+
function parseJsonWithSchema(raw, schema) {
|
|
4533
|
+
const jsonStr = extractJsonFromLLMOutput(raw);
|
|
4534
|
+
let parsed;
|
|
4535
|
+
try {
|
|
4536
|
+
parsed = JSON.parse(jsonStr);
|
|
4537
|
+
} catch (err) {
|
|
4538
|
+
const preview = jsonStr.slice(0, 200);
|
|
4539
|
+
throw new Error(`Failed to parse JSON from LLM output: ${err instanceof Error ? err.message : String(err)}
|
|
4540
|
+
Extracted text preview: ${preview}`);
|
|
4541
|
+
}
|
|
4542
|
+
const result = schema.safeParse(parsed);
|
|
4543
|
+
if (!result.success) {
|
|
4544
|
+
const issues = result.error.issues.map((issue) => ` - ${issue.path.join(".")}: ${issue.message}`).join(`
|
|
4545
|
+
`);
|
|
4546
|
+
throw new Error(`LLM output failed schema validation:
|
|
4547
|
+
${issues}
|
|
4548
|
+
Parsed JSON preview: ${JSON.stringify(parsed).slice(0, 300)}`);
|
|
4549
|
+
}
|
|
4550
|
+
return result.data;
|
|
4551
|
+
}
|
|
4552
|
+
|
|
4553
|
+
// src/planning/sprint-plan.ts
|
|
4554
|
+
var PlannedTaskSchema = import_zod.z.object({
|
|
4555
|
+
title: import_zod.z.string().default("Untitled Task"),
|
|
4556
|
+
description: import_zod.z.string().default(""),
|
|
4557
|
+
assigneeRole: import_zod.z.enum(["BACKEND", "FRONTEND", "QA", "PM", "DESIGN"]).default("BACKEND"),
|
|
4558
|
+
priority: import_zod.z.enum(["CRITICAL", "HIGH", "MEDIUM", "LOW"]).default("MEDIUM"),
|
|
4559
|
+
complexity: import_zod.z.number().min(1).max(5).default(3),
|
|
4560
|
+
acceptanceCriteria: import_zod.z.array(import_zod.z.string()).default([]),
|
|
4561
|
+
labels: import_zod.z.array(import_zod.z.string()).default([])
|
|
4562
|
+
});
|
|
4563
|
+
var SprintPlanRiskSchema = import_zod.z.object({
|
|
4564
|
+
description: import_zod.z.string().default(""),
|
|
4565
|
+
mitigation: import_zod.z.string().default(""),
|
|
4566
|
+
severity: import_zod.z.enum(["low", "medium", "high"]).default("medium")
|
|
4567
|
+
});
|
|
4568
|
+
var PlannerOutputSchema = import_zod.z.object({
|
|
4569
|
+
name: import_zod.z.string().default("Unnamed Sprint"),
|
|
4570
|
+
goal: import_zod.z.string().default(""),
|
|
4571
|
+
estimatedDays: import_zod.z.number().default(1),
|
|
4572
|
+
tasks: import_zod.z.array(PlannedTaskSchema).default([]),
|
|
4573
|
+
risks: import_zod.z.array(SprintPlanRiskSchema).default([])
|
|
4574
|
+
});
|
|
4575
|
+
var SprintPlanAIOutputSchema = PlannerOutputSchema;
|
|
4753
4576
|
function sprintPlanToMarkdown(plan) {
|
|
4754
4577
|
const lines = [];
|
|
4755
4578
|
lines.push(`# Sprint Plan: ${plan.name}`);
|
|
@@ -4824,42 +4647,31 @@ function plannedTasksToCreatePayloads(plan, sprintId) {
|
|
|
4824
4647
|
}));
|
|
4825
4648
|
}
|
|
4826
4649
|
function parseSprintPlanFromAI(raw, directive) {
|
|
4827
|
-
const
|
|
4828
|
-
let parsed;
|
|
4829
|
-
try {
|
|
4830
|
-
parsed = JSON.parse(jsonStr);
|
|
4831
|
-
} catch (err) {
|
|
4832
|
-
const preview = jsonStr.slice(0, 200);
|
|
4833
|
-
throw new Error(`Failed to parse sprint plan JSON: ${err instanceof Error ? err.message : String(err)}
|
|
4834
|
-
Extracted JSON preview: ${preview}`);
|
|
4835
|
-
}
|
|
4836
|
-
if (parsed.revisedPlan) {
|
|
4837
|
-
parsed = parsed.revisedPlan;
|
|
4838
|
-
}
|
|
4650
|
+
const planData = parseJsonWithSchema(raw, SprintPlanAIOutputSchema);
|
|
4839
4651
|
const now = new Date().toISOString();
|
|
4840
4652
|
const id = `plan-${Date.now()}`;
|
|
4841
|
-
const tasks2 =
|
|
4653
|
+
const tasks2 = planData.tasks.map((t, i) => ({
|
|
4842
4654
|
index: i + 1,
|
|
4843
|
-
title: t.title
|
|
4844
|
-
description: t.description
|
|
4845
|
-
assigneeRole: t.assigneeRole
|
|
4846
|
-
priority: t.priority
|
|
4847
|
-
complexity: t.complexity
|
|
4848
|
-
acceptanceCriteria: t.acceptanceCriteria
|
|
4849
|
-
labels: t.labels
|
|
4655
|
+
title: t.title,
|
|
4656
|
+
description: t.description,
|
|
4657
|
+
assigneeRole: t.assigneeRole,
|
|
4658
|
+
priority: t.priority,
|
|
4659
|
+
complexity: t.complexity,
|
|
4660
|
+
acceptanceCriteria: t.acceptanceCriteria,
|
|
4661
|
+
labels: t.labels
|
|
4850
4662
|
}));
|
|
4851
4663
|
return {
|
|
4852
4664
|
id,
|
|
4853
|
-
name:
|
|
4854
|
-
goal:
|
|
4665
|
+
name: planData.name,
|
|
4666
|
+
goal: planData.goal || directive,
|
|
4855
4667
|
directive,
|
|
4856
4668
|
tasks: tasks2,
|
|
4857
|
-
risks:
|
|
4858
|
-
description: r.description
|
|
4859
|
-
mitigation: r.mitigation
|
|
4860
|
-
severity: r.severity
|
|
4669
|
+
risks: planData.risks.map((r) => ({
|
|
4670
|
+
description: r.description,
|
|
4671
|
+
mitigation: r.mitigation,
|
|
4672
|
+
severity: r.severity
|
|
4861
4673
|
})),
|
|
4862
|
-
estimatedDays:
|
|
4674
|
+
estimatedDays: planData.estimatedDays,
|
|
4863
4675
|
status: "pending",
|
|
4864
4676
|
createdAt: now,
|
|
4865
4677
|
updatedAt: now
|
|
@@ -4868,28 +4680,26 @@ Extracted JSON preview: ${preview}`);
|
|
|
4868
4680
|
|
|
4869
4681
|
// src/planning/plan-manager.ts
|
|
4870
4682
|
class PlanManager {
|
|
4871
|
-
projectPath;
|
|
4872
4683
|
plansDir;
|
|
4873
4684
|
constructor(projectPath) {
|
|
4874
|
-
this.projectPath = projectPath;
|
|
4875
4685
|
this.plansDir = getLocusPath(projectPath, "plansDir");
|
|
4876
4686
|
}
|
|
4877
4687
|
save(plan) {
|
|
4878
4688
|
this.ensurePlansDir();
|
|
4879
4689
|
const slug = this.slugify(plan.name);
|
|
4880
|
-
const jsonPath =
|
|
4881
|
-
const mdPath =
|
|
4882
|
-
|
|
4883
|
-
|
|
4690
|
+
const jsonPath = import_node_path10.join(this.plansDir, `${slug}.json`);
|
|
4691
|
+
const mdPath = import_node_path10.join(this.plansDir, `sprint-${slug}.md`);
|
|
4692
|
+
import_node_fs8.writeFileSync(jsonPath, JSON.stringify(plan, null, 2), "utf-8");
|
|
4693
|
+
import_node_fs8.writeFileSync(mdPath, sprintPlanToMarkdown(plan), "utf-8");
|
|
4884
4694
|
return plan.id;
|
|
4885
4695
|
}
|
|
4886
4696
|
load(idOrSlug) {
|
|
4887
4697
|
this.ensurePlansDir();
|
|
4888
|
-
const files =
|
|
4698
|
+
const files = import_node_fs8.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
|
|
4889
4699
|
for (const file of files) {
|
|
4890
|
-
const filePath =
|
|
4700
|
+
const filePath = import_node_path10.join(this.plansDir, file);
|
|
4891
4701
|
try {
|
|
4892
|
-
const plan = JSON.parse(
|
|
4702
|
+
const plan = JSON.parse(import_node_fs8.readFileSync(filePath, "utf-8"));
|
|
4893
4703
|
if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
|
|
4894
4704
|
return plan;
|
|
4895
4705
|
}
|
|
@@ -4899,11 +4709,11 @@ class PlanManager {
|
|
|
4899
4709
|
}
|
|
4900
4710
|
list(status) {
|
|
4901
4711
|
this.ensurePlansDir();
|
|
4902
|
-
const files =
|
|
4712
|
+
const files = import_node_fs8.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
|
|
4903
4713
|
const plans = [];
|
|
4904
4714
|
for (const file of files) {
|
|
4905
4715
|
try {
|
|
4906
|
-
const plan = JSON.parse(
|
|
4716
|
+
const plan = JSON.parse(import_node_fs8.readFileSync(import_node_path10.join(this.plansDir, file), "utf-8"));
|
|
4907
4717
|
if (!status || plan.status === status) {
|
|
4908
4718
|
plans.push(plan);
|
|
4909
4719
|
}
|
|
@@ -4933,15 +4743,6 @@ class PlanManager {
|
|
|
4933
4743
|
plan.status = "approved";
|
|
4934
4744
|
plan.updatedAt = new Date().toISOString();
|
|
4935
4745
|
this.save(plan);
|
|
4936
|
-
const kb = new KnowledgeBase(this.projectPath);
|
|
4937
|
-
kb.updateProgress({
|
|
4938
|
-
role: "user",
|
|
4939
|
-
content: `Start sprint: ${plan.name}`
|
|
4940
|
-
});
|
|
4941
|
-
kb.updateProgress({
|
|
4942
|
-
role: "assistant",
|
|
4943
|
-
content: `Sprint started with ${tasks2.length} tasks. Goal: ${plan.goal}`
|
|
4944
|
-
});
|
|
4945
4746
|
return { sprint, tasks: tasks2 };
|
|
4946
4747
|
}
|
|
4947
4748
|
reject(idOrSlug, feedback) {
|
|
@@ -4969,18 +4770,18 @@ class PlanManager {
|
|
|
4969
4770
|
}
|
|
4970
4771
|
delete(idOrSlug) {
|
|
4971
4772
|
this.ensurePlansDir();
|
|
4972
|
-
const files =
|
|
4773
|
+
const files = import_node_fs8.readdirSync(this.plansDir);
|
|
4973
4774
|
for (const file of files) {
|
|
4974
|
-
const filePath =
|
|
4775
|
+
const filePath = import_node_path10.join(this.plansDir, file);
|
|
4975
4776
|
if (!file.endsWith(".json"))
|
|
4976
4777
|
continue;
|
|
4977
4778
|
try {
|
|
4978
|
-
const plan = JSON.parse(
|
|
4779
|
+
const plan = JSON.parse(import_node_fs8.readFileSync(filePath, "utf-8"));
|
|
4979
4780
|
if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
|
|
4980
|
-
|
|
4981
|
-
const mdPath =
|
|
4982
|
-
if (
|
|
4983
|
-
|
|
4781
|
+
import_node_fs8.unlinkSync(filePath);
|
|
4782
|
+
const mdPath = import_node_path10.join(this.plansDir, `sprint-${this.slugify(plan.name)}.md`);
|
|
4783
|
+
if (import_node_fs8.existsSync(mdPath)) {
|
|
4784
|
+
import_node_fs8.unlinkSync(mdPath);
|
|
4984
4785
|
}
|
|
4985
4786
|
return;
|
|
4986
4787
|
}
|
|
@@ -4994,8 +4795,8 @@ class PlanManager {
|
|
|
4994
4795
|
return sprintPlanToMarkdown(plan);
|
|
4995
4796
|
}
|
|
4996
4797
|
ensurePlansDir() {
|
|
4997
|
-
if (!
|
|
4998
|
-
|
|
4798
|
+
if (!import_node_fs8.existsSync(this.plansDir)) {
|
|
4799
|
+
import_node_fs8.mkdirSync(this.plansDir, { recursive: true });
|
|
4999
4800
|
}
|
|
5000
4801
|
}
|
|
5001
4802
|
slugify(name) {
|
|
@@ -5004,226 +4805,76 @@ class PlanManager {
|
|
|
5004
4805
|
}
|
|
5005
4806
|
// src/planning/planning-meeting.ts
|
|
5006
4807
|
init_config();
|
|
5007
|
-
|
|
5008
|
-
var
|
|
5009
|
-
|
|
5010
|
-
// src/planning/agents/cross-task-reviewer.ts
|
|
5011
|
-
function buildCrossTaskReviewerPrompt(input) {
|
|
5012
|
-
let prompt = `# Role: Cross-Task Reviewer (Architect + Engineer + Planner)
|
|
5013
|
-
|
|
5014
|
-
You are a combined Architect, Senior Engineer, and Sprint Planner performing a FINAL review of a sprint plan. Your focus is ensuring that tasks are correctly ordered, well-scoped, and will execute successfully in sequence.
|
|
5015
|
-
|
|
5016
|
-
## Context
|
|
5017
|
-
|
|
5018
|
-
In this system, tasks are executed SEQUENTIALLY by a single agent on ONE branch:
|
|
5019
|
-
- Tasks run one at a time, in the order they appear in the array
|
|
5020
|
-
- Each task's changes are committed before the next task starts
|
|
5021
|
-
- Later tasks can see and build on earlier tasks' work
|
|
5022
|
-
- The final result is a single branch with all changes, which becomes a pull request
|
|
5023
|
-
|
|
5024
|
-
This means:
|
|
5025
|
-
- Task ordering is critical — a task must NOT depend on a later task's output
|
|
5026
|
-
- Foundation work (config, schemas, shared code) must come first
|
|
5027
|
-
- Each task should be a focused, logical unit of work
|
|
5028
|
-
|
|
5029
|
-
## CEO Directive
|
|
5030
|
-
> ${input.directive}
|
|
5031
|
-
`;
|
|
5032
|
-
if (input.feedback) {
|
|
5033
|
-
prompt += `
|
|
5034
|
-
## CEO Feedback on Previous Plan
|
|
5035
|
-
> ${input.feedback}
|
|
5036
|
-
|
|
5037
|
-
IMPORTANT: Ensure the reviewed plan still addresses this feedback.
|
|
5038
|
-
`;
|
|
5039
|
-
}
|
|
5040
|
-
prompt += `
|
|
5041
|
-
## Project Context
|
|
5042
|
-
${input.projectContext || "No project context available."}
|
|
5043
|
-
|
|
5044
|
-
## Sprint Plan to Review
|
|
5045
|
-
${input.plannerOutput}
|
|
5046
|
-
|
|
5047
|
-
## Your Review Checklist
|
|
5048
|
-
|
|
5049
|
-
Go through EACH task and check for:
|
|
5050
|
-
|
|
5051
|
-
### 1. Ordering & Dependency Analysis
|
|
5052
|
-
For each task, verify:
|
|
5053
|
-
- Does it depend on any task that appears LATER in the list? If so, reorder.
|
|
5054
|
-
- Are foundational tasks (config, schemas, shared code) at the beginning?
|
|
5055
|
-
- Is the overall execution order logical?
|
|
5056
|
-
|
|
5057
|
-
### 2. Scope & Completeness
|
|
5058
|
-
For each task, verify:
|
|
5059
|
-
- Is the task well-scoped? Not too large, not too trivial?
|
|
5060
|
-
- Does it include ALL changes needed for its goal (given earlier tasks are done)?
|
|
5061
|
-
- Are there any missing tasks that should be added?
|
|
5062
|
-
|
|
5063
|
-
### 3. Description Quality Validation
|
|
5064
|
-
For each task, verify the description is a clear, actionable implementation guide. Each description must specify:
|
|
5065
|
-
- **What to do** — the specific goal and expected behavior/outcome
|
|
5066
|
-
- **Where to do it** — specific files, modules, or directories to modify or create
|
|
5067
|
-
- **How to do it** — implementation approach, patterns to follow, existing utilities to use
|
|
5068
|
-
- **Boundaries** — what is NOT in scope for this task
|
|
5069
|
-
|
|
5070
|
-
If any description is vague (e.g., "Add authentication", "Update the API", "Fix the frontend"), rewrite it with concrete implementation details. The executing agent receives ONLY the task title, description, and acceptance criteria as its instructions.
|
|
5071
|
-
|
|
5072
|
-
### 4. Risk Assessment
|
|
5073
|
-
- Are there tasks that might fail or have unknowns?
|
|
5074
|
-
- Is the sprint scope realistic for sequential execution?
|
|
5075
|
-
|
|
5076
|
-
## Output Format
|
|
5077
|
-
|
|
5078
|
-
Your entire response must be a single JSON object — no text before it, no text after it, no markdown code blocks, no explanation. Start your response with the "{" character:
|
|
5079
|
-
|
|
5080
|
-
{
|
|
5081
|
-
"hasIssues": true | false,
|
|
5082
|
-
"issues": [
|
|
5083
|
-
{
|
|
5084
|
-
"type": "wrong_order" | "missing_task" | "scope_issue" | "vague_description",
|
|
5085
|
-
"description": "string describing the specific issue",
|
|
5086
|
-
"affectedTasks": ["Task Title 1", "Task Title 2"],
|
|
5087
|
-
"resolution": "string describing how to fix it"
|
|
5088
|
-
}
|
|
5089
|
-
],
|
|
5090
|
-
"revisedPlan": {
|
|
5091
|
-
"name": "string (2-4 words)",
|
|
5092
|
-
"goal": "string (1 paragraph)",
|
|
5093
|
-
"estimatedDays": 3,
|
|
5094
|
-
"tasks": [
|
|
5095
|
-
{
|
|
5096
|
-
"title": "string",
|
|
5097
|
-
"description": "string (detailed implementation guide: what to do, where to do it, how to do it, and boundaries)",
|
|
5098
|
-
"assigneeRole": "BACKEND | FRONTEND | QA | PM | DESIGN",
|
|
5099
|
-
"priority": "CRITICAL | HIGH | MEDIUM | LOW",
|
|
5100
|
-
"labels": ["string"],
|
|
5101
|
-
"acceptanceCriteria": ["string"],
|
|
5102
|
-
"complexity": 3
|
|
5103
|
-
}
|
|
5104
|
-
],
|
|
5105
|
-
"risks": [
|
|
5106
|
-
{
|
|
5107
|
-
"description": "string",
|
|
5108
|
-
"mitigation": "string",
|
|
5109
|
-
"severity": "low | medium | high"
|
|
5110
|
-
}
|
|
5111
|
-
]
|
|
5112
|
-
}
|
|
5113
|
-
}
|
|
5114
|
-
|
|
5115
|
-
IMPORTANT:
|
|
5116
|
-
- If hasIssues is true, the revisedPlan MUST contain the corrected task list with issues resolved (reordered, descriptions rewritten, missing tasks added, etc.)
|
|
5117
|
-
- If hasIssues is false, the revisedPlan should be identical to the input plan (no changes needed)
|
|
5118
|
-
- The revisedPlan is ALWAYS required — it becomes the final plan
|
|
5119
|
-
- Ensure every task description is a detailed implementation guide (what, where, how, boundaries) — rewrite vague descriptions
|
|
5120
|
-
- Tasks execute sequentially — the array order IS the execution order`;
|
|
5121
|
-
return prompt;
|
|
5122
|
-
}
|
|
4808
|
+
var import_node_fs9 = require("node:fs");
|
|
4809
|
+
var import_node_path11 = require("node:path");
|
|
5123
4810
|
|
|
5124
4811
|
// src/planning/agents/planner.ts
|
|
5125
4812
|
function buildPlannerPrompt(input) {
|
|
5126
|
-
let
|
|
5127
|
-
|
|
5128
|
-
You are a Sprint Planner — an expert engineer, architect, and project organizer rolled into one. Your job is to take a CEO directive and produce a complete, ready-to-execute sprint plan in a single pass.
|
|
5129
|
-
|
|
5130
|
-
## CEO Directive
|
|
5131
|
-
> ${input.directive}
|
|
5132
|
-
`;
|
|
4813
|
+
let feedbackSection = "";
|
|
5133
4814
|
if (input.feedback) {
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
|
|
4815
|
+
feedbackSection = `
|
|
4816
|
+
<ceo_feedback>
|
|
4817
|
+
The CEO has reviewed a previous plan and wants changes. Incorporate this feedback:
|
|
4818
|
+
${input.feedback}
|
|
4819
|
+
</ceo_feedback>
|
|
5139
4820
|
`;
|
|
5140
4821
|
}
|
|
5141
|
-
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
|
|
5145
|
-
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
5150
|
-
|
|
5151
|
-
|
|
5152
|
-
|
|
5153
|
-
|
|
5154
|
-
|
|
5155
|
-
|
|
5156
|
-
|
|
5157
|
-
|
|
5158
|
-
|
|
5159
|
-
|
|
5160
|
-
For each task, provide:
|
|
5161
|
-
- **Title** — Clear, action-oriented (e.g., "Implement user registration API endpoint")
|
|
5162
|
-
- **Description** — A detailed, actionable implementation guide (see below)
|
|
5163
|
-
- **Assignee Role** — BACKEND, FRONTEND, QA, PM, or DESIGN
|
|
5164
|
-
- **Priority** — CRITICAL, HIGH, MEDIUM, or LOW
|
|
5165
|
-
- **Complexity** — 1 (trivial) to 5 (very complex)
|
|
5166
|
-
- **Labels** — Relevant tags (e.g., "api", "database", "ui", "auth")
|
|
5167
|
-
- **Acceptance Criteria** — Specific, testable conditions for completion
|
|
5168
|
-
|
|
5169
|
-
### CRITICAL: Task Description Requirements
|
|
5170
|
-
|
|
5171
|
-
Each task description will be handed to an INDEPENDENT agent as its primary instruction. The agent will have access to the codebase but NO context about the planning meeting. Each description MUST include:
|
|
5172
|
-
|
|
5173
|
-
1. **What to do** — Clearly state the goal and expected behavior/outcome
|
|
5174
|
-
2. **Where to do it** — List specific files, modules, or directories to modify or create. Reference existing code paths when extending functionality
|
|
5175
|
-
3. **How to do it** — Key implementation details: which patterns to follow, which existing utilities or services to use, what the data flow looks like
|
|
5176
|
-
4. **Boundaries** — What is NOT in scope for this task to prevent overlap with other tasks
|
|
5177
|
-
|
|
5178
|
-
Bad example: "Add authentication to the API."
|
|
5179
|
-
Good example: "Implement JWT-based authentication middleware in src/middleware/auth.ts. Create a verifyToken middleware that extracts the Bearer token from the Authorization header, validates it using the existing JWT_SECRET from env config, and attaches the decoded user payload to req.user. Apply this middleware to all routes in src/routes/protected/. This task does NOT include user registration or password reset — those are handled separately."
|
|
5180
|
-
|
|
5181
|
-
### CRITICAL: Task Ordering Rules
|
|
5182
|
-
|
|
5183
|
-
Tasks are executed SEQUENTIALLY by a single agent on ONE branch. The agent works through tasks in array order. Therefore:
|
|
5184
|
-
|
|
5185
|
-
1. **Foundation first.** Place foundational tasks (schemas, config, shared code) at the beginning. Later tasks can build on earlier ones since they run in sequence.
|
|
5186
|
-
2. **No forward dependencies.** A task must NOT depend on a task that appears later in the list.
|
|
5187
|
-
3. **Each task is self-contained for its scope.** A task can depend on earlier tasks but must include all changes needed for its own goal.
|
|
5188
|
-
4. **Keep tasks focused.** Each task should do one logical unit of work. Avoid trivially small or overly large tasks.
|
|
5189
|
-
5. **Merge related trivial work.** If two pieces of work are trivially small and tightly related, combine them into one task.
|
|
5190
|
-
|
|
5191
|
-
### Sprint Scope Guidelines
|
|
5192
|
-
|
|
5193
|
-
- If the sprint would exceed 12 tasks, reduce scope or merge related tasks
|
|
5194
|
-
- Ensure acceptance criteria are specific and testable
|
|
5195
|
-
- Keep the sprint focused on the directive — avoid scope creep
|
|
5196
|
-
|
|
5197
|
-
## Output Format
|
|
5198
|
-
|
|
5199
|
-
Your entire response must be a single JSON object — no text before it, no text after it, no markdown code blocks, no explanation. Start your response with the "{" character:
|
|
5200
|
-
|
|
4822
|
+
const now = new Date().toISOString();
|
|
4823
|
+
return `<sprint_planning>
|
|
4824
|
+
Create a sprint plan for this directive: ${input.directive}
|
|
4825
|
+
${feedbackSection}
|
|
4826
|
+
<rules>
|
|
4827
|
+
- Tasks execute sequentially by one agent on one branch
|
|
4828
|
+
- Each task must be self-contained with clear What/Where/How
|
|
4829
|
+
- No forward dependencies (task N can't need task N+1)
|
|
4830
|
+
- Foundation first (shared code, types, schemas before features)
|
|
4831
|
+
- Be specific: exact file paths, function names, implementation details
|
|
4832
|
+
- Each task description is the ONLY instruction an independent agent receives — include all context it needs
|
|
4833
|
+
- Merge trivially small related work into one task
|
|
4834
|
+
- Assign appropriate roles: BACKEND, FRONTEND, QA, PM, or DESIGN
|
|
4835
|
+
</rules>
|
|
4836
|
+
|
|
4837
|
+
<output>
|
|
4838
|
+
Write the sprint plan as a JSON file to: ${input.plansDir}/${input.fileName}.json
|
|
4839
|
+
|
|
4840
|
+
The JSON file must contain this exact structure:
|
|
5201
4841
|
{
|
|
5202
|
-
"
|
|
5203
|
-
"
|
|
5204
|
-
"
|
|
4842
|
+
"id": "${input.planId}",
|
|
4843
|
+
"name": "2-4 words",
|
|
4844
|
+
"goal": "One paragraph of what this delivers",
|
|
4845
|
+
"directive": ${JSON.stringify(input.directive)},
|
|
4846
|
+
"estimatedDays": number,
|
|
4847
|
+
"status": "pending",
|
|
4848
|
+
"createdAt": "${now}",
|
|
4849
|
+
"updatedAt": "${now}",
|
|
5205
4850
|
"tasks": [
|
|
5206
4851
|
{
|
|
5207
|
-
"
|
|
5208
|
-
"
|
|
5209
|
-
"
|
|
5210
|
-
"
|
|
5211
|
-
"
|
|
5212
|
-
"
|
|
5213
|
-
"
|
|
4852
|
+
"index": 1,
|
|
4853
|
+
"title": "Action-oriented title",
|
|
4854
|
+
"description": "What: goal\\nWhere: files to modify\\nHow: implementation details\\nBoundaries: what's excluded",
|
|
4855
|
+
"assigneeRole": "BACKEND|FRONTEND|QA|PM|DESIGN",
|
|
4856
|
+
"priority": "CRITICAL|HIGH|MEDIUM|LOW",
|
|
4857
|
+
"labels": ["tags"],
|
|
4858
|
+
"acceptanceCriteria": ["testable conditions"],
|
|
4859
|
+
"complexity": 1-5
|
|
5214
4860
|
}
|
|
5215
4861
|
],
|
|
5216
4862
|
"risks": [
|
|
5217
4863
|
{
|
|
5218
|
-
"description": "
|
|
5219
|
-
"mitigation": "
|
|
5220
|
-
"severity": "low
|
|
4864
|
+
"description": "What could go wrong",
|
|
4865
|
+
"mitigation": "How to handle it",
|
|
4866
|
+
"severity": "low|medium|high"
|
|
5221
4867
|
}
|
|
5222
4868
|
]
|
|
5223
4869
|
}
|
|
5224
4870
|
|
|
5225
|
-
IMPORTANT:
|
|
5226
|
-
|
|
4871
|
+
IMPORTANT:
|
|
4872
|
+
- Write the file directly using your file writing tool. Do NOT output the JSON as text.
|
|
4873
|
+
- Tasks must have sequential "index" values starting at 1.
|
|
4874
|
+
- The file must be valid JSON with no comments or trailing commas.
|
|
4875
|
+
- Do not create any other files. Only create the single JSON file specified above.
|
|
4876
|
+
</output>
|
|
4877
|
+
</sprint_planning>`;
|
|
5227
4878
|
}
|
|
5228
4879
|
|
|
5229
4880
|
// src/planning/planning-meeting.ts
|
|
@@ -5239,68 +4890,41 @@ class PlanningMeeting {
|
|
|
5239
4890
|
});
|
|
5240
4891
|
}
|
|
5241
4892
|
async run(directive, feedback) {
|
|
5242
|
-
|
|
5243
|
-
const
|
|
5244
|
-
|
|
5245
|
-
|
|
5246
|
-
|
|
5247
|
-
|
|
5248
|
-
|
|
5249
|
-
|
|
5250
|
-
|
|
5251
|
-
const plannerOutput = await this.aiRunner.run(plannerPrompt);
|
|
5252
|
-
this.log("Planner phase complete.", "success");
|
|
5253
|
-
this.log("Phase 2/2: Reviewer checking for conflicts and quality...", "info");
|
|
5254
|
-
const crossTaskReviewerPrompt = buildCrossTaskReviewerPrompt({
|
|
4893
|
+
this.log("Planning sprint...", "info");
|
|
4894
|
+
const plansDir = getLocusPath(this.projectPath, "plansDir");
|
|
4895
|
+
if (!import_node_fs9.existsSync(plansDir)) {
|
|
4896
|
+
import_node_fs9.mkdirSync(plansDir, { recursive: true });
|
|
4897
|
+
}
|
|
4898
|
+
const ts = Date.now();
|
|
4899
|
+
const planId = `plan-${ts}`;
|
|
4900
|
+
const fileName = `plan-${ts}`;
|
|
4901
|
+
const prompt = buildPlannerPrompt({
|
|
5255
4902
|
directive,
|
|
5256
|
-
|
|
5257
|
-
|
|
5258
|
-
|
|
4903
|
+
feedback,
|
|
4904
|
+
plansDir,
|
|
4905
|
+
planId,
|
|
4906
|
+
fileName
|
|
5259
4907
|
});
|
|
5260
|
-
const
|
|
5261
|
-
this.log("
|
|
5262
|
-
const
|
|
4908
|
+
const response = await this.aiRunner.run(prompt);
|
|
4909
|
+
this.log("Planning meeting complete.", "success");
|
|
4910
|
+
const expectedPath = import_node_path11.join(plansDir, `${fileName}.json`);
|
|
4911
|
+
let plan = null;
|
|
4912
|
+
if (import_node_fs9.existsSync(expectedPath)) {
|
|
4913
|
+
try {
|
|
4914
|
+
plan = JSON.parse(import_node_fs9.readFileSync(expectedPath, "utf-8"));
|
|
4915
|
+
} catch {}
|
|
4916
|
+
}
|
|
4917
|
+
if (!plan) {
|
|
4918
|
+
throw new Error("Planning agent did not create the expected plan JSON file. " + "Check the agent output for errors.");
|
|
4919
|
+
}
|
|
5263
4920
|
if (feedback) {
|
|
5264
4921
|
plan.feedback = feedback;
|
|
5265
4922
|
}
|
|
5266
4923
|
return {
|
|
5267
4924
|
plan,
|
|
5268
|
-
|
|
5269
|
-
planner: plannerOutput,
|
|
5270
|
-
review: reviewOutput
|
|
5271
|
-
}
|
|
4925
|
+
rawOutput: response
|
|
5272
4926
|
};
|
|
5273
4927
|
}
|
|
5274
|
-
getProjectContext() {
|
|
5275
|
-
const kb = new KnowledgeBase(this.projectPath);
|
|
5276
|
-
return kb.getFullContext();
|
|
5277
|
-
}
|
|
5278
|
-
getCodebaseIndex() {
|
|
5279
|
-
const indexPath = getLocusPath(this.projectPath, "indexFile");
|
|
5280
|
-
if (!import_node_fs10.existsSync(indexPath)) {
|
|
5281
|
-
return "";
|
|
5282
|
-
}
|
|
5283
|
-
try {
|
|
5284
|
-
const raw = import_node_fs10.readFileSync(indexPath, "utf-8");
|
|
5285
|
-
const index = JSON.parse(raw);
|
|
5286
|
-
const parts = [];
|
|
5287
|
-
if (index.responsibilities) {
|
|
5288
|
-
parts.push("### File Responsibilities");
|
|
5289
|
-
const entries = Object.entries(index.responsibilities);
|
|
5290
|
-
for (const [file, summary] of entries.slice(0, 50)) {
|
|
5291
|
-
parts.push(`- \`${file}\`: ${summary}`);
|
|
5292
|
-
}
|
|
5293
|
-
if (entries.length > 50) {
|
|
5294
|
-
parts.push(`... and ${entries.length - 50} more files`);
|
|
5295
|
-
}
|
|
5296
|
-
}
|
|
5297
|
-
return parts.join(`
|
|
5298
|
-
`);
|
|
5299
|
-
} catch {
|
|
5300
|
-
return "";
|
|
5301
|
-
}
|
|
5302
|
-
}
|
|
5303
4928
|
}
|
|
5304
4929
|
// src/index-node.ts
|
|
5305
|
-
init_knowledge_base();
|
|
5306
4930
|
init_colors();
|