@locusai/sdk 0.12.0 → 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 +135 -331
- 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 +396 -721
- 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 -5
- 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 -12
- 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,102 +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 parts = [];
|
|
1684
|
-
if (context.trim()) {
|
|
1685
|
-
parts.push(context.trim());
|
|
1686
|
-
}
|
|
1687
|
-
return parts.join(`
|
|
1688
|
-
|
|
1689
|
-
---
|
|
1690
|
-
|
|
1691
|
-
`);
|
|
1692
|
-
}
|
|
1693
|
-
initialize(info) {
|
|
1694
|
-
this.ensureDir(this.contextPath);
|
|
1695
|
-
this.ensureDir(this.progressPath);
|
|
1696
|
-
const techStackList = info.techStack.map((t) => `- ${t}`).join(`
|
|
1697
|
-
`);
|
|
1698
|
-
const contextContent = `# Project: ${info.name}
|
|
1699
|
-
|
|
1700
|
-
## Mission
|
|
1701
|
-
${info.mission}
|
|
1702
|
-
|
|
1703
|
-
## Tech Stack
|
|
1704
|
-
${techStackList}
|
|
1705
|
-
|
|
1706
|
-
## Architecture
|
|
1707
|
-
<!-- Describe your high-level architecture here -->
|
|
1708
|
-
|
|
1709
|
-
## Key Decisions
|
|
1710
|
-
<!-- Document important technical decisions and their rationale -->
|
|
1711
|
-
|
|
1712
|
-
## Feature Areas
|
|
1713
|
-
<!-- List your main feature areas and their status -->
|
|
1714
|
-
`;
|
|
1715
|
-
const progressContent = `# Conversation History
|
|
1716
|
-
`;
|
|
1717
|
-
import_node_fs3.writeFileSync(this.contextPath, contextContent);
|
|
1718
|
-
import_node_fs3.writeFileSync(this.progressPath, progressContent);
|
|
1719
|
-
}
|
|
1720
|
-
get exists() {
|
|
1721
|
-
return import_node_fs3.existsSync(this.contextPath) || import_node_fs3.existsSync(this.progressPath);
|
|
1722
|
-
}
|
|
1723
|
-
ensureDir(filePath) {
|
|
1724
|
-
const dir = import_node_path5.dirname(filePath);
|
|
1725
|
-
if (!import_node_fs3.existsSync(dir)) {
|
|
1726
|
-
import_node_fs3.mkdirSync(dir, { recursive: true });
|
|
1727
|
-
}
|
|
1728
|
-
}
|
|
1729
|
-
}
|
|
1730
|
-
var import_node_fs3, import_node_path5;
|
|
1731
|
-
var init_knowledge_base = __esm(() => {
|
|
1732
|
-
init_config();
|
|
1733
|
-
import_node_fs3 = require("node:fs");
|
|
1734
|
-
import_node_path5 = require("node:path");
|
|
1735
|
-
});
|
|
1736
|
-
|
|
1737
1629
|
// src/agent/git-workflow.ts
|
|
1738
1630
|
class GitWorkflow {
|
|
1739
1631
|
config;
|
|
@@ -1985,223 +1877,157 @@ class PromptBuilder {
|
|
|
1985
1877
|
constructor(projectPath) {
|
|
1986
1878
|
this.projectPath = projectPath;
|
|
1987
1879
|
}
|
|
1988
|
-
async build(task
|
|
1989
|
-
let prompt = `# Task: ${task.title}
|
|
1990
|
-
|
|
1991
|
-
`;
|
|
1880
|
+
async build(task) {
|
|
1992
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 = "";
|
|
1993
1887
|
if (roleText) {
|
|
1994
|
-
|
|
1888
|
+
sections += `
|
|
1889
|
+
<role>
|
|
1995
1890
|
You are acting as a ${roleText}.
|
|
1996
|
-
|
|
1891
|
+
</role>
|
|
1997
1892
|
`;
|
|
1998
1893
|
}
|
|
1999
|
-
|
|
2000
|
-
|
|
2001
|
-
|
|
2002
|
-
`;
|
|
2003
|
-
const projectConfig = this.getProjectConfig();
|
|
2004
|
-
if (projectConfig) {
|
|
2005
|
-
prompt += `## Project Metadata
|
|
2006
|
-
`;
|
|
2007
|
-
prompt += `- Version: ${projectConfig.version || "Unknown"}
|
|
2008
|
-
`;
|
|
2009
|
-
prompt += `- Created At: ${projectConfig.createdAt || "Unknown"}
|
|
2010
|
-
|
|
2011
|
-
`;
|
|
2012
|
-
}
|
|
2013
|
-
let serverContext = null;
|
|
2014
|
-
if (options.taskContext) {
|
|
2015
|
-
try {
|
|
2016
|
-
serverContext = JSON.parse(options.taskContext);
|
|
2017
|
-
} catch {
|
|
2018
|
-
serverContext = { context: options.taskContext };
|
|
2019
|
-
}
|
|
2020
|
-
}
|
|
2021
|
-
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
2022
|
-
let hasLocalContext = false;
|
|
2023
|
-
if (import_node_fs4.existsSync(contextPath)) {
|
|
2024
|
-
try {
|
|
2025
|
-
const context = import_node_fs4.readFileSync(contextPath, "utf-8");
|
|
2026
|
-
if (context.trim().length > 20) {
|
|
2027
|
-
prompt += `## Project Context (Local)
|
|
1894
|
+
if (context) {
|
|
1895
|
+
sections += `
|
|
1896
|
+
<project_context>
|
|
2028
1897
|
${context}
|
|
2029
|
-
|
|
1898
|
+
</project_context>
|
|
2030
1899
|
`;
|
|
2031
|
-
hasLocalContext = true;
|
|
2032
|
-
}
|
|
2033
|
-
} catch (err) {
|
|
2034
|
-
console.warn(`Warning: Could not read context file: ${err}`);
|
|
2035
|
-
}
|
|
2036
1900
|
}
|
|
2037
|
-
|
|
2038
|
-
|
|
2039
|
-
|
|
2040
|
-
|
|
2041
|
-
${fallback}
|
|
2042
|
-
|
|
1901
|
+
sections += `
|
|
1902
|
+
<knowledge_base>
|
|
1903
|
+
${knowledgeBase}
|
|
1904
|
+
</knowledge_base>
|
|
2043
1905
|
`;
|
|
2044
|
-
|
|
2045
|
-
|
|
2046
|
-
|
|
2047
|
-
|
|
2048
|
-
|
|
2049
|
-
|
|
2050
|
-
if (project) {
|
|
2051
|
-
prompt += `- Project: ${project.name || "Unknown"}
|
|
2052
|
-
`;
|
|
2053
|
-
if (!hasLocalContext && project.techStack?.length) {
|
|
2054
|
-
prompt += `- Tech Stack: ${project.techStack.join(", ")}
|
|
2055
|
-
`;
|
|
2056
|
-
}
|
|
2057
|
-
}
|
|
2058
|
-
if (serverContext.context) {
|
|
2059
|
-
prompt += `
|
|
2060
|
-
${serverContext.context}
|
|
2061
|
-
`;
|
|
2062
|
-
}
|
|
2063
|
-
prompt += `
|
|
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>
|
|
2064
1912
|
`;
|
|
2065
1913
|
}
|
|
2066
|
-
prompt += this.getProjectStructure();
|
|
2067
|
-
prompt += `## Project Knowledge Base
|
|
2068
|
-
`;
|
|
2069
|
-
prompt += `You have access to the following documentation directories for context:
|
|
2070
|
-
`;
|
|
2071
|
-
prompt += `- Artifacts: \`.locus/artifacts\`
|
|
2072
|
-
`;
|
|
2073
|
-
prompt += `- Documents: \`.locus/documents\`
|
|
2074
|
-
`;
|
|
2075
|
-
prompt += `If you need more information about the project strategies, plans, or architecture, please read files in these directories.
|
|
2076
|
-
|
|
2077
|
-
`;
|
|
2078
1914
|
if (task.docs && task.docs.length > 0) {
|
|
2079
|
-
|
|
2080
|
-
`;
|
|
2081
|
-
prompt += `> Full content available on server. Rely on Task Description for specific requirements.
|
|
2082
|
-
|
|
2083
|
-
`;
|
|
1915
|
+
let docsContent = "";
|
|
2084
1916
|
for (const doc of task.docs) {
|
|
2085
1917
|
const content = doc.content || "";
|
|
2086
1918
|
const limit = 800;
|
|
2087
1919
|
const preview = content.slice(0, limit);
|
|
2088
1920
|
const isTruncated = content.length > limit;
|
|
2089
|
-
|
|
1921
|
+
docsContent += `### ${doc.title}
|
|
2090
1922
|
${preview}${isTruncated ? `
|
|
2091
1923
|
...(truncated)...` : ""}
|
|
2092
1924
|
|
|
2093
1925
|
`;
|
|
2094
1926
|
}
|
|
1927
|
+
sections += `
|
|
1928
|
+
<documents>
|
|
1929
|
+
${docsContent.trimEnd()}
|
|
1930
|
+
</documents>
|
|
1931
|
+
`;
|
|
2095
1932
|
}
|
|
2096
1933
|
if (task.acceptanceChecklist && task.acceptanceChecklist.length > 0) {
|
|
2097
|
-
|
|
2098
|
-
`;
|
|
1934
|
+
let criteria = "";
|
|
2099
1935
|
for (const item of task.acceptanceChecklist) {
|
|
2100
|
-
|
|
1936
|
+
criteria += `- ${item.done ? "[x]" : "[ ]"} ${item.text}
|
|
2101
1937
|
`;
|
|
2102
1938
|
}
|
|
2103
|
-
|
|
1939
|
+
sections += `
|
|
1940
|
+
<acceptance_criteria>
|
|
1941
|
+
${criteria.trimEnd()}
|
|
1942
|
+
</acceptance_criteria>
|
|
2104
1943
|
`;
|
|
2105
1944
|
}
|
|
2106
1945
|
if (task.comments && task.comments.length > 0) {
|
|
2107
1946
|
const filteredComments = task.comments.filter((comment) => comment.author !== "system");
|
|
2108
1947
|
const comments = filteredComments.slice(0, 3);
|
|
2109
|
-
|
|
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}
|
|
2110
1953
|
`;
|
|
2111
|
-
|
|
2112
|
-
|
|
2113
|
-
|
|
1954
|
+
}
|
|
1955
|
+
sections += `
|
|
1956
|
+
<feedback>
|
|
1957
|
+
${commentsContent.trimEnd()}
|
|
1958
|
+
</feedback>
|
|
2114
1959
|
`;
|
|
2115
1960
|
}
|
|
2116
|
-
prompt += `
|
|
2117
|
-
`;
|
|
2118
1961
|
}
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
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>`;
|
|
2126
1976
|
}
|
|
2127
1977
|
async buildGenericPrompt(query) {
|
|
2128
|
-
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
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>
|
|
2138
1987
|
`;
|
|
2139
|
-
|
|
1988
|
+
}
|
|
1989
|
+
sections += `
|
|
1990
|
+
<knowledge_base>
|
|
1991
|
+
${knowledgeBase}
|
|
1992
|
+
</knowledge_base>
|
|
2140
1993
|
`;
|
|
2141
|
-
|
|
2142
|
-
|
|
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>
|
|
2143
2000
|
`;
|
|
2144
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() {
|
|
2145
2013
|
const contextPath = getLocusPath(this.projectPath, "contextFile");
|
|
2146
|
-
|
|
2147
|
-
if (import_node_fs4.existsSync(contextPath)) {
|
|
2014
|
+
if (import_node_fs3.existsSync(contextPath)) {
|
|
2148
2015
|
try {
|
|
2149
|
-
const context =
|
|
2016
|
+
const context = import_node_fs3.readFileSync(contextPath, "utf-8");
|
|
2150
2017
|
if (context.trim().length > 20) {
|
|
2151
|
-
|
|
2152
|
-
${context}
|
|
2153
|
-
|
|
2154
|
-
`;
|
|
2155
|
-
hasLocalContext = true;
|
|
2018
|
+
return context;
|
|
2156
2019
|
}
|
|
2157
2020
|
} catch (err) {
|
|
2158
2021
|
console.warn(`Warning: Could not read context file: ${err}`);
|
|
2159
2022
|
}
|
|
2160
2023
|
}
|
|
2161
|
-
|
|
2162
|
-
const fallback = this.getFallbackContext();
|
|
2163
|
-
if (fallback) {
|
|
2164
|
-
prompt += `## Project Context (README Fallback)
|
|
2165
|
-
${fallback}
|
|
2166
|
-
|
|
2167
|
-
`;
|
|
2168
|
-
}
|
|
2169
|
-
}
|
|
2170
|
-
prompt += this.getProjectStructure();
|
|
2171
|
-
prompt += `## Project Knowledge Base
|
|
2172
|
-
`;
|
|
2173
|
-
prompt += `You have access to the following documentation directories for context:
|
|
2174
|
-
`;
|
|
2175
|
-
prompt += `- Artifacts: \`.locus/artifacts\` (local-only, not synced to cloud)
|
|
2176
|
-
`;
|
|
2177
|
-
prompt += `- Documents: \`.locus/documents\` (synced from cloud)
|
|
2178
|
-
`;
|
|
2179
|
-
prompt += `If you need more information about the project strategies, plans, or architecture, please read files in these directories.
|
|
2180
|
-
|
|
2181
|
-
`;
|
|
2182
|
-
prompt += `## Instructions
|
|
2183
|
-
1. Execute the prompt based on the provided project context.
|
|
2184
|
-
2. **Paths**: Use relative paths from the project root at all times. Do NOT use absolute local paths (e.g., /Users/...).
|
|
2185
|
-
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.
|
|
2186
|
-
4. **Progress**: Do NOT modify \`.locus/project/progress.md\`. The system updates it automatically.`;
|
|
2187
|
-
return prompt;
|
|
2188
|
-
}
|
|
2189
|
-
getProjectConfig() {
|
|
2190
|
-
const configPath = getLocusPath(this.projectPath, "configFile");
|
|
2191
|
-
if (import_node_fs4.existsSync(configPath)) {
|
|
2192
|
-
try {
|
|
2193
|
-
return JSON.parse(import_node_fs4.readFileSync(configPath, "utf-8"));
|
|
2194
|
-
} catch {
|
|
2195
|
-
return null;
|
|
2196
|
-
}
|
|
2197
|
-
}
|
|
2198
|
-
return null;
|
|
2024
|
+
return this.getFallbackContext() || null;
|
|
2199
2025
|
}
|
|
2200
2026
|
getFallbackContext() {
|
|
2201
|
-
const readmePath =
|
|
2202
|
-
if (
|
|
2027
|
+
const readmePath = import_node_path5.join(this.projectPath, "README.md");
|
|
2028
|
+
if (import_node_fs3.existsSync(readmePath)) {
|
|
2203
2029
|
try {
|
|
2204
|
-
const content =
|
|
2030
|
+
const content = import_node_fs3.readFileSync(readmePath, "utf-8");
|
|
2205
2031
|
const limit = 1000;
|
|
2206
2032
|
return content.slice(0, limit) + (content.length > limit ? `
|
|
2207
2033
|
...(truncated)...` : "");
|
|
@@ -2211,32 +2037,28 @@ ${fallback}
|
|
|
2211
2037
|
}
|
|
2212
2038
|
return "";
|
|
2213
2039
|
}
|
|
2214
|
-
|
|
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
|
+
}
|
|
2215
2051
|
try {
|
|
2216
|
-
const
|
|
2217
|
-
const
|
|
2218
|
-
|
|
2219
|
-
|
|
2220
|
-
|
|
2221
|
-
return import_node_fs4.statSync(import_node_path6.join(this.projectPath, e)).isDirectory();
|
|
2222
|
-
} catch {
|
|
2223
|
-
return false;
|
|
2224
|
-
}
|
|
2225
|
-
});
|
|
2226
|
-
if (folders.length === 0)
|
|
2227
|
-
return "";
|
|
2228
|
-
let structure = `## Project Structure
|
|
2229
|
-
`;
|
|
2230
|
-
structure += `Key directories in this project:
|
|
2231
|
-
`;
|
|
2232
|
-
for (const folder of folders) {
|
|
2233
|
-
structure += `- \`${folder}/\`
|
|
2234
|
-
`;
|
|
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;
|
|
2235
2057
|
}
|
|
2236
|
-
return
|
|
2237
|
-
|
|
2058
|
+
return lines.join(`
|
|
2059
|
+
`);
|
|
2238
2060
|
} catch {
|
|
2239
|
-
return
|
|
2061
|
+
return null;
|
|
2240
2062
|
}
|
|
2241
2063
|
}
|
|
2242
2064
|
roleToText(role) {
|
|
@@ -2259,11 +2081,11 @@ ${fallback}
|
|
|
2259
2081
|
}
|
|
2260
2082
|
}
|
|
2261
2083
|
}
|
|
2262
|
-
var
|
|
2084
|
+
var import_node_fs3, import_node_path5, import_shared2;
|
|
2263
2085
|
var init_prompt_builder = __esm(() => {
|
|
2264
2086
|
init_config();
|
|
2265
|
-
|
|
2266
|
-
|
|
2087
|
+
import_node_fs3 = require("node:fs");
|
|
2088
|
+
import_node_path5 = require("node:path");
|
|
2267
2089
|
import_shared2 = require("@locusai/shared");
|
|
2268
2090
|
});
|
|
2269
2091
|
|
|
@@ -2381,7 +2203,6 @@ class AgentWorker {
|
|
|
2381
2203
|
client;
|
|
2382
2204
|
aiRunner;
|
|
2383
2205
|
taskExecutor;
|
|
2384
|
-
knowledgeBase;
|
|
2385
2206
|
gitWorkflow;
|
|
2386
2207
|
maxTasks = 50;
|
|
2387
2208
|
tasksCompleted = 0;
|
|
@@ -2421,7 +2242,6 @@ class AgentWorker {
|
|
|
2421
2242
|
projectPath,
|
|
2422
2243
|
log
|
|
2423
2244
|
});
|
|
2424
|
-
this.knowledgeBase = new KnowledgeBase(projectPath);
|
|
2425
2245
|
this.gitWorkflow = new GitWorkflow(config, log);
|
|
2426
2246
|
const providerLabel = provider === "codex" ? "Codex" : "Claude";
|
|
2427
2247
|
this.log(`Using ${providerLabel} CLI for all phases`, "info");
|
|
@@ -2495,20 +2315,6 @@ class AgentWorker {
|
|
|
2495
2315
|
};
|
|
2496
2316
|
}
|
|
2497
2317
|
}
|
|
2498
|
-
updateProgress(task, summary) {
|
|
2499
|
-
try {
|
|
2500
|
-
this.knowledgeBase.updateProgress({
|
|
2501
|
-
role: "user",
|
|
2502
|
-
content: task.title
|
|
2503
|
-
});
|
|
2504
|
-
this.knowledgeBase.updateProgress({
|
|
2505
|
-
role: "assistant",
|
|
2506
|
-
content: summary
|
|
2507
|
-
});
|
|
2508
|
-
} catch (err) {
|
|
2509
|
-
this.log(`Failed to update progress: ${err instanceof Error ? err.message : String(err)}`, "warn");
|
|
2510
|
-
}
|
|
2511
|
-
}
|
|
2512
2318
|
startHeartbeat() {
|
|
2513
2319
|
this.sendHeartbeat();
|
|
2514
2320
|
this.heartbeatInterval = setInterval(() => this.sendHeartbeat(), 60000);
|
|
@@ -2583,7 +2389,6 @@ Branch: \`${result.branch}\`` : "";
|
|
|
2583
2389
|
this.tasksCompleted++;
|
|
2584
2390
|
this.completedTaskList.push({ title: task.title, id: task.id });
|
|
2585
2391
|
this.taskSummaries.push(result.summary);
|
|
2586
|
-
this.updateProgress(task, result.summary);
|
|
2587
2392
|
}
|
|
2588
2393
|
} else {
|
|
2589
2394
|
this.log(`Failed: ${task.title} - ${result.summary}`, "error");
|
|
@@ -2628,7 +2433,6 @@ var init_worker = __esm(() => {
|
|
|
2628
2433
|
init_config();
|
|
2629
2434
|
init_git_utils();
|
|
2630
2435
|
init_src();
|
|
2631
|
-
init_knowledge_base();
|
|
2632
2436
|
init_colors();
|
|
2633
2437
|
init_git_workflow();
|
|
2634
2438
|
init_task_executor();
|
|
@@ -2653,6 +2457,7 @@ __export(exports_index_node, {
|
|
|
2653
2457
|
sprintPlanToMarkdown: () => sprintPlanToMarkdown,
|
|
2654
2458
|
plannedTasksToCreatePayloads: () => plannedTasksToCreatePayloads,
|
|
2655
2459
|
parseSprintPlanFromAI: () => parseSprintPlanFromAI,
|
|
2460
|
+
parseJsonWithSchema: () => parseJsonWithSchema,
|
|
2656
2461
|
getRemoteUrl: () => getRemoteUrl,
|
|
2657
2462
|
getLocusPath: () => getLocusPath,
|
|
2658
2463
|
getDefaultBranch: () => getDefaultBranch,
|
|
@@ -2681,7 +2486,6 @@ __export(exports_index_node, {
|
|
|
2681
2486
|
LOCUS_SCHEMAS: () => LOCUS_SCHEMAS,
|
|
2682
2487
|
LOCUS_GITIGNORE_PATTERNS: () => LOCUS_GITIGNORE_PATTERNS,
|
|
2683
2488
|
LOCUS_CONFIG: () => LOCUS_CONFIG,
|
|
2684
|
-
KnowledgeBase: () => KnowledgeBase,
|
|
2685
2489
|
InvitationsModule: () => InvitationsModule,
|
|
2686
2490
|
HistoryManager: () => HistoryManager,
|
|
2687
2491
|
GitWorkflow: () => GitWorkflow,
|
|
@@ -2705,8 +2509,8 @@ module.exports = __toCommonJS(exports_index_node);
|
|
|
2705
2509
|
|
|
2706
2510
|
// src/core/indexer.ts
|
|
2707
2511
|
var import_node_crypto2 = require("node:crypto");
|
|
2708
|
-
var
|
|
2709
|
-
var
|
|
2512
|
+
var import_node_fs4 = require("node:fs");
|
|
2513
|
+
var import_node_path6 = require("node:path");
|
|
2710
2514
|
var import_globby = require("globby");
|
|
2711
2515
|
|
|
2712
2516
|
class CodebaseIndexer {
|
|
@@ -2715,7 +2519,7 @@ class CodebaseIndexer {
|
|
|
2715
2519
|
fullReindexRatioThreshold = 0.2;
|
|
2716
2520
|
constructor(projectPath) {
|
|
2717
2521
|
this.projectPath = projectPath;
|
|
2718
|
-
this.indexPath =
|
|
2522
|
+
this.indexPath = import_node_path6.join(projectPath, ".locus", "codebase-index.json");
|
|
2719
2523
|
}
|
|
2720
2524
|
async index(onProgress, treeSummarizer, force = false) {
|
|
2721
2525
|
if (!treeSummarizer) {
|
|
@@ -2771,11 +2575,11 @@ class CodebaseIndexer {
|
|
|
2771
2575
|
}
|
|
2772
2576
|
}
|
|
2773
2577
|
async getFileTree() {
|
|
2774
|
-
const gitmodulesPath =
|
|
2578
|
+
const gitmodulesPath = import_node_path6.join(this.projectPath, ".gitmodules");
|
|
2775
2579
|
const submoduleIgnores = [];
|
|
2776
|
-
if (
|
|
2580
|
+
if (import_node_fs4.existsSync(gitmodulesPath)) {
|
|
2777
2581
|
try {
|
|
2778
|
-
const content =
|
|
2582
|
+
const content = import_node_fs4.readFileSync(gitmodulesPath, "utf-8");
|
|
2779
2583
|
const lines = content.split(`
|
|
2780
2584
|
`);
|
|
2781
2585
|
for (const line of lines) {
|
|
@@ -2831,9 +2635,9 @@ class CodebaseIndexer {
|
|
|
2831
2635
|
});
|
|
2832
2636
|
}
|
|
2833
2637
|
loadIndex() {
|
|
2834
|
-
if (
|
|
2638
|
+
if (import_node_fs4.existsSync(this.indexPath)) {
|
|
2835
2639
|
try {
|
|
2836
|
-
return JSON.parse(
|
|
2640
|
+
return JSON.parse(import_node_fs4.readFileSync(this.indexPath, "utf-8"));
|
|
2837
2641
|
} catch {
|
|
2838
2642
|
return null;
|
|
2839
2643
|
}
|
|
@@ -2841,11 +2645,11 @@ class CodebaseIndexer {
|
|
|
2841
2645
|
return null;
|
|
2842
2646
|
}
|
|
2843
2647
|
saveIndex(index) {
|
|
2844
|
-
const dir =
|
|
2845
|
-
if (!
|
|
2846
|
-
|
|
2648
|
+
const dir = import_node_path6.dirname(this.indexPath);
|
|
2649
|
+
if (!import_node_fs4.existsSync(dir)) {
|
|
2650
|
+
import_node_fs4.mkdirSync(dir, { recursive: true });
|
|
2847
2651
|
}
|
|
2848
|
-
|
|
2652
|
+
import_node_fs4.writeFileSync(this.indexPath, JSON.stringify(index, null, 2));
|
|
2849
2653
|
}
|
|
2850
2654
|
cloneIndex(index) {
|
|
2851
2655
|
return JSON.parse(JSON.stringify(index));
|
|
@@ -2861,7 +2665,7 @@ class CodebaseIndexer {
|
|
|
2861
2665
|
}
|
|
2862
2666
|
hashFile(filePath) {
|
|
2863
2667
|
try {
|
|
2864
|
-
const content =
|
|
2668
|
+
const content = import_node_fs4.readFileSync(import_node_path6.join(this.projectPath, filePath), "utf-8");
|
|
2865
2669
|
return import_node_crypto2.createHash("sha256").update(content).digest("hex").slice(0, 16);
|
|
2866
2670
|
} catch {
|
|
2867
2671
|
return null;
|
|
@@ -2925,43 +2729,42 @@ class CodebaseIndexer {
|
|
|
2925
2729
|
// src/utils/json-extractor.ts
|
|
2926
2730
|
function extractJsonFromLLMOutput(raw) {
|
|
2927
2731
|
const trimmed = raw.trim();
|
|
2928
|
-
const
|
|
2929
|
-
if (
|
|
2930
|
-
return
|
|
2931
|
-
}
|
|
2932
|
-
const
|
|
2933
|
-
if (
|
|
2934
|
-
|
|
2935
|
-
|
|
2936
|
-
|
|
2937
|
-
|
|
2938
|
-
|
|
2939
|
-
|
|
2940
|
-
|
|
2941
|
-
|
|
2942
|
-
|
|
2943
|
-
continue;
|
|
2944
|
-
}
|
|
2945
|
-
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
|
+
}
|
|
2946
2747
|
if (ch === "\\") {
|
|
2947
|
-
|
|
2948
|
-
|
|
2949
|
-
|
|
2748
|
+
isEscape = true;
|
|
2749
|
+
continue;
|
|
2750
|
+
}
|
|
2751
|
+
if (ch === '"') {
|
|
2752
|
+
inString = !inString;
|
|
2753
|
+
continue;
|
|
2950
2754
|
}
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
return trimmed.slice(startIdx, i + 1);
|
|
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
|
+
}
|
|
2961
2764
|
}
|
|
2962
2765
|
}
|
|
2963
2766
|
}
|
|
2964
|
-
return trimmed
|
|
2767
|
+
return trimmed;
|
|
2965
2768
|
}
|
|
2966
2769
|
|
|
2967
2770
|
// src/agent/codebase-indexer-service.ts
|
|
@@ -2975,22 +2778,30 @@ class CodebaseIndexerService {
|
|
|
2975
2778
|
async reindex(force = false) {
|
|
2976
2779
|
try {
|
|
2977
2780
|
const index = await this.indexer.index((msg) => this.deps.log(msg, "info"), async (tree) => {
|
|
2978
|
-
const prompt =
|
|
2979
|
-
|
|
2980
|
-
2. Responsibilities of each directory/file
|
|
2981
|
-
3. Overall project structure
|
|
2781
|
+
const prompt = `<codebase_analysis>
|
|
2782
|
+
Analyze this codebase file tree and extract key information.
|
|
2982
2783
|
|
|
2983
|
-
|
|
2984
|
-
- "symbols": object mapping symbol names to file paths (array)
|
|
2985
|
-
- "responsibilities": object mapping paths to brief descriptions
|
|
2986
|
-
|
|
2987
|
-
File tree:
|
|
2784
|
+
<file_tree>
|
|
2988
2785
|
${tree}
|
|
2786
|
+
</file_tree>
|
|
2787
|
+
|
|
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>
|
|
2989
2793
|
|
|
2990
|
-
|
|
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>`;
|
|
2991
2802
|
const response = await this.deps.aiRunner.run(prompt);
|
|
2992
|
-
const jsonStr = extractJsonFromLLMOutput(response);
|
|
2993
2803
|
try {
|
|
2804
|
+
const jsonStr = extractJsonFromLLMOutput(response);
|
|
2994
2805
|
return JSON.parse(jsonStr);
|
|
2995
2806
|
} catch {
|
|
2996
2807
|
return { symbols: {}, responsibilities: {}, lastIndexed: "" };
|
|
@@ -3009,8 +2820,8 @@ Return ONLY valid JSON, no markdown formatting.`;
|
|
|
3009
2820
|
}
|
|
3010
2821
|
// src/agent/document-fetcher.ts
|
|
3011
2822
|
init_config();
|
|
3012
|
-
var
|
|
3013
|
-
var
|
|
2823
|
+
var import_node_fs5 = require("node:fs");
|
|
2824
|
+
var import_node_path7 = require("node:path");
|
|
3014
2825
|
|
|
3015
2826
|
class DocumentFetcher {
|
|
3016
2827
|
deps;
|
|
@@ -3019,8 +2830,8 @@ class DocumentFetcher {
|
|
|
3019
2830
|
}
|
|
3020
2831
|
async fetch() {
|
|
3021
2832
|
const documentsDir = getLocusPath(this.deps.projectPath, "documentsDir");
|
|
3022
|
-
if (!
|
|
3023
|
-
|
|
2833
|
+
if (!import_node_fs5.existsSync(documentsDir)) {
|
|
2834
|
+
import_node_fs5.mkdirSync(documentsDir, { recursive: true });
|
|
3024
2835
|
}
|
|
3025
2836
|
try {
|
|
3026
2837
|
const groups = await this.deps.client.docs.listGroups(this.deps.workspaceId);
|
|
@@ -3033,14 +2844,14 @@ class DocumentFetcher {
|
|
|
3033
2844
|
continue;
|
|
3034
2845
|
}
|
|
3035
2846
|
const groupName = groupMap.get(doc.groupId || "") || "General";
|
|
3036
|
-
const groupDir =
|
|
3037
|
-
if (!
|
|
3038
|
-
|
|
2847
|
+
const groupDir = import_node_path7.join(documentsDir, groupName);
|
|
2848
|
+
if (!import_node_fs5.existsSync(groupDir)) {
|
|
2849
|
+
import_node_fs5.mkdirSync(groupDir, { recursive: true });
|
|
3039
2850
|
}
|
|
3040
2851
|
const fileName = `${doc.title}.md`;
|
|
3041
|
-
const filePath =
|
|
3042
|
-
if (!
|
|
3043
|
-
|
|
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 || "");
|
|
3044
2855
|
fetchedCount++;
|
|
3045
2856
|
}
|
|
3046
2857
|
}
|
|
@@ -3356,7 +3167,6 @@ class PrService {
|
|
|
3356
3167
|
|
|
3357
3168
|
// src/agent/reviewer-worker.ts
|
|
3358
3169
|
init_src();
|
|
3359
|
-
init_knowledge_base();
|
|
3360
3170
|
init_colors();
|
|
3361
3171
|
function resolveProvider2(value) {
|
|
3362
3172
|
if (!value || value.startsWith("--"))
|
|
@@ -3371,7 +3181,6 @@ class ReviewerWorker {
|
|
|
3371
3181
|
client;
|
|
3372
3182
|
aiRunner;
|
|
3373
3183
|
prService;
|
|
3374
|
-
knowledgeBase;
|
|
3375
3184
|
heartbeatInterval = null;
|
|
3376
3185
|
currentTaskId = null;
|
|
3377
3186
|
maxReviews = 50;
|
|
@@ -3397,7 +3206,6 @@ class ReviewerWorker {
|
|
|
3397
3206
|
log
|
|
3398
3207
|
});
|
|
3399
3208
|
this.prService = new PrService(projectPath, log);
|
|
3400
|
-
this.knowledgeBase = new KnowledgeBase(projectPath);
|
|
3401
3209
|
const providerLabel = provider === "codex" ? "Codex" : "Claude";
|
|
3402
3210
|
this.log(`Reviewer agent using ${providerLabel} CLI`, "info");
|
|
3403
3211
|
}
|
|
@@ -3513,17 +3321,6 @@ ${summary}`;
|
|
|
3513
3321
|
this.sendHeartbeat();
|
|
3514
3322
|
const result = await this.reviewPr(pr);
|
|
3515
3323
|
if (result.reviewed) {
|
|
3516
|
-
const status = result.approved ? "APPROVED" : "CHANGES REQUESTED";
|
|
3517
|
-
try {
|
|
3518
|
-
this.knowledgeBase.updateProgress({
|
|
3519
|
-
role: "user",
|
|
3520
|
-
content: `Review PR #${pr.number}: ${pr.title}`
|
|
3521
|
-
});
|
|
3522
|
-
this.knowledgeBase.updateProgress({
|
|
3523
|
-
role: "assistant",
|
|
3524
|
-
content: `${status}: ${result.summary}`
|
|
3525
|
-
});
|
|
3526
|
-
} catch {}
|
|
3527
3324
|
this.reviewsCompleted++;
|
|
3528
3325
|
} else {
|
|
3529
3326
|
this.log(`Review skipped: ${result.summary}`, "warn");
|
|
@@ -4002,8 +3799,8 @@ class ExecEventEmitter {
|
|
|
4002
3799
|
}
|
|
4003
3800
|
// src/exec/history-manager.ts
|
|
4004
3801
|
init_config();
|
|
4005
|
-
var
|
|
4006
|
-
var
|
|
3802
|
+
var import_node_fs6 = require("node:fs");
|
|
3803
|
+
var import_node_path8 = require("node:path");
|
|
4007
3804
|
var DEFAULT_MAX_SESSIONS = 30;
|
|
4008
3805
|
function generateSessionId2() {
|
|
4009
3806
|
const timestamp = Date.now().toString(36);
|
|
@@ -4015,30 +3812,30 @@ class HistoryManager {
|
|
|
4015
3812
|
historyDir;
|
|
4016
3813
|
maxSessions;
|
|
4017
3814
|
constructor(projectPath, options) {
|
|
4018
|
-
this.historyDir = options?.historyDir ??
|
|
3815
|
+
this.historyDir = options?.historyDir ?? import_node_path8.join(projectPath, LOCUS_CONFIG.dir, LOCUS_CONFIG.sessionsDir);
|
|
4019
3816
|
this.maxSessions = options?.maxSessions ?? DEFAULT_MAX_SESSIONS;
|
|
4020
3817
|
this.ensureHistoryDir();
|
|
4021
3818
|
}
|
|
4022
3819
|
ensureHistoryDir() {
|
|
4023
|
-
if (!
|
|
4024
|
-
|
|
3820
|
+
if (!import_node_fs6.existsSync(this.historyDir)) {
|
|
3821
|
+
import_node_fs6.mkdirSync(this.historyDir, { recursive: true });
|
|
4025
3822
|
}
|
|
4026
3823
|
}
|
|
4027
3824
|
getSessionPath(sessionId) {
|
|
4028
|
-
return
|
|
3825
|
+
return import_node_path8.join(this.historyDir, `${sessionId}.json`);
|
|
4029
3826
|
}
|
|
4030
3827
|
saveSession(session) {
|
|
4031
3828
|
const filePath = this.getSessionPath(session.id);
|
|
4032
3829
|
session.updatedAt = Date.now();
|
|
4033
|
-
|
|
3830
|
+
import_node_fs6.writeFileSync(filePath, JSON.stringify(session, null, 2), "utf-8");
|
|
4034
3831
|
}
|
|
4035
3832
|
loadSession(sessionId) {
|
|
4036
3833
|
const filePath = this.getSessionPath(sessionId);
|
|
4037
|
-
if (!
|
|
3834
|
+
if (!import_node_fs6.existsSync(filePath)) {
|
|
4038
3835
|
return null;
|
|
4039
3836
|
}
|
|
4040
3837
|
try {
|
|
4041
|
-
const content =
|
|
3838
|
+
const content = import_node_fs6.readFileSync(filePath, "utf-8");
|
|
4042
3839
|
return JSON.parse(content);
|
|
4043
3840
|
} catch {
|
|
4044
3841
|
return null;
|
|
@@ -4046,18 +3843,18 @@ class HistoryManager {
|
|
|
4046
3843
|
}
|
|
4047
3844
|
deleteSession(sessionId) {
|
|
4048
3845
|
const filePath = this.getSessionPath(sessionId);
|
|
4049
|
-
if (!
|
|
3846
|
+
if (!import_node_fs6.existsSync(filePath)) {
|
|
4050
3847
|
return false;
|
|
4051
3848
|
}
|
|
4052
3849
|
try {
|
|
4053
|
-
|
|
3850
|
+
import_node_fs6.rmSync(filePath);
|
|
4054
3851
|
return true;
|
|
4055
3852
|
} catch {
|
|
4056
3853
|
return false;
|
|
4057
3854
|
}
|
|
4058
3855
|
}
|
|
4059
3856
|
listSessions(options) {
|
|
4060
|
-
const files =
|
|
3857
|
+
const files = import_node_fs6.readdirSync(this.historyDir);
|
|
4061
3858
|
let sessions = [];
|
|
4062
3859
|
for (const file of files) {
|
|
4063
3860
|
if (file.endsWith(".json")) {
|
|
@@ -4130,11 +3927,11 @@ class HistoryManager {
|
|
|
4130
3927
|
return deleted;
|
|
4131
3928
|
}
|
|
4132
3929
|
getSessionCount() {
|
|
4133
|
-
const files =
|
|
3930
|
+
const files = import_node_fs6.readdirSync(this.historyDir);
|
|
4134
3931
|
return files.filter((f) => f.endsWith(".json")).length;
|
|
4135
3932
|
}
|
|
4136
3933
|
sessionExists(sessionId) {
|
|
4137
|
-
return
|
|
3934
|
+
return import_node_fs6.existsSync(this.getSessionPath(sessionId));
|
|
4138
3935
|
}
|
|
4139
3936
|
findSessionByPartialId(partialId) {
|
|
4140
3937
|
const sessions = this.listSessions();
|
|
@@ -4148,12 +3945,12 @@ class HistoryManager {
|
|
|
4148
3945
|
return this.historyDir;
|
|
4149
3946
|
}
|
|
4150
3947
|
clearAllSessions() {
|
|
4151
|
-
const files =
|
|
3948
|
+
const files = import_node_fs6.readdirSync(this.historyDir);
|
|
4152
3949
|
let deleted = 0;
|
|
4153
3950
|
for (const file of files) {
|
|
4154
3951
|
if (file.endsWith(".json")) {
|
|
4155
3952
|
try {
|
|
4156
|
-
|
|
3953
|
+
import_node_fs6.rmSync(import_node_path8.join(this.historyDir, file));
|
|
4157
3954
|
deleted++;
|
|
4158
3955
|
} catch {}
|
|
4159
3956
|
}
|
|
@@ -4429,8 +4226,8 @@ init_src();
|
|
|
4429
4226
|
init_colors();
|
|
4430
4227
|
init_resolve_bin();
|
|
4431
4228
|
var import_node_child_process7 = require("node:child_process");
|
|
4432
|
-
var
|
|
4433
|
-
var
|
|
4229
|
+
var import_node_fs7 = require("node:fs");
|
|
4230
|
+
var import_node_path9 = require("node:path");
|
|
4434
4231
|
var import_node_url = require("node:url");
|
|
4435
4232
|
var import_shared4 = require("@locusai/shared");
|
|
4436
4233
|
var import_events4 = require("events");
|
|
@@ -4698,14 +4495,14 @@ ${agentId} finished (exit code: ${code})`);
|
|
|
4698
4495
|
}
|
|
4699
4496
|
resolveWorkerPath() {
|
|
4700
4497
|
const currentModulePath = import_node_url.fileURLToPath("file:///home/runner/work/locusai/locusai/packages/sdk/src/orchestrator/index.ts");
|
|
4701
|
-
const currentModuleDir =
|
|
4498
|
+
const currentModuleDir = import_node_path9.dirname(currentModulePath);
|
|
4702
4499
|
const potentialPaths = [
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
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")
|
|
4707
4504
|
];
|
|
4708
|
-
return potentialPaths.find((p) =>
|
|
4505
|
+
return potentialPaths.find((p) => import_node_fs7.existsSync(p));
|
|
4709
4506
|
}
|
|
4710
4507
|
}
|
|
4711
4508
|
function killProcessTree(proc) {
|
|
@@ -4724,12 +4521,58 @@ function sleep(ms) {
|
|
|
4724
4521
|
}
|
|
4725
4522
|
// src/planning/plan-manager.ts
|
|
4726
4523
|
init_config();
|
|
4727
|
-
|
|
4728
|
-
var
|
|
4729
|
-
var import_node_path11 = require("node:path");
|
|
4524
|
+
var import_node_fs8 = require("node:fs");
|
|
4525
|
+
var import_node_path10 = require("node:path");
|
|
4730
4526
|
|
|
4731
4527
|
// src/planning/sprint-plan.ts
|
|
4732
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;
|
|
4733
4576
|
function sprintPlanToMarkdown(plan) {
|
|
4734
4577
|
const lines = [];
|
|
4735
4578
|
lines.push(`# Sprint Plan: ${plan.name}`);
|
|
@@ -4804,42 +4647,31 @@ function plannedTasksToCreatePayloads(plan, sprintId) {
|
|
|
4804
4647
|
}));
|
|
4805
4648
|
}
|
|
4806
4649
|
function parseSprintPlanFromAI(raw, directive) {
|
|
4807
|
-
const
|
|
4808
|
-
let parsed;
|
|
4809
|
-
try {
|
|
4810
|
-
parsed = JSON.parse(jsonStr);
|
|
4811
|
-
} catch (err) {
|
|
4812
|
-
const preview = jsonStr.slice(0, 200);
|
|
4813
|
-
throw new Error(`Failed to parse sprint plan JSON: ${err instanceof Error ? err.message : String(err)}
|
|
4814
|
-
Extracted JSON preview: ${preview}`);
|
|
4815
|
-
}
|
|
4816
|
-
if (parsed.revisedPlan) {
|
|
4817
|
-
parsed = parsed.revisedPlan;
|
|
4818
|
-
}
|
|
4650
|
+
const planData = parseJsonWithSchema(raw, SprintPlanAIOutputSchema);
|
|
4819
4651
|
const now = new Date().toISOString();
|
|
4820
4652
|
const id = `plan-${Date.now()}`;
|
|
4821
|
-
const tasks2 =
|
|
4653
|
+
const tasks2 = planData.tasks.map((t, i) => ({
|
|
4822
4654
|
index: i + 1,
|
|
4823
|
-
title: t.title
|
|
4824
|
-
description: t.description
|
|
4825
|
-
assigneeRole: t.assigneeRole
|
|
4826
|
-
priority: t.priority
|
|
4827
|
-
complexity: t.complexity
|
|
4828
|
-
acceptanceCriteria: t.acceptanceCriteria
|
|
4829
|
-
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
|
|
4830
4662
|
}));
|
|
4831
4663
|
return {
|
|
4832
4664
|
id,
|
|
4833
|
-
name:
|
|
4834
|
-
goal:
|
|
4665
|
+
name: planData.name,
|
|
4666
|
+
goal: planData.goal || directive,
|
|
4835
4667
|
directive,
|
|
4836
4668
|
tasks: tasks2,
|
|
4837
|
-
risks:
|
|
4838
|
-
description: r.description
|
|
4839
|
-
mitigation: r.mitigation
|
|
4840
|
-
severity: r.severity
|
|
4669
|
+
risks: planData.risks.map((r) => ({
|
|
4670
|
+
description: r.description,
|
|
4671
|
+
mitigation: r.mitigation,
|
|
4672
|
+
severity: r.severity
|
|
4841
4673
|
})),
|
|
4842
|
-
estimatedDays:
|
|
4674
|
+
estimatedDays: planData.estimatedDays,
|
|
4843
4675
|
status: "pending",
|
|
4844
4676
|
createdAt: now,
|
|
4845
4677
|
updatedAt: now
|
|
@@ -4848,28 +4680,26 @@ Extracted JSON preview: ${preview}`);
|
|
|
4848
4680
|
|
|
4849
4681
|
// src/planning/plan-manager.ts
|
|
4850
4682
|
class PlanManager {
|
|
4851
|
-
projectPath;
|
|
4852
4683
|
plansDir;
|
|
4853
4684
|
constructor(projectPath) {
|
|
4854
|
-
this.projectPath = projectPath;
|
|
4855
4685
|
this.plansDir = getLocusPath(projectPath, "plansDir");
|
|
4856
4686
|
}
|
|
4857
4687
|
save(plan) {
|
|
4858
4688
|
this.ensurePlansDir();
|
|
4859
4689
|
const slug = this.slugify(plan.name);
|
|
4860
|
-
const jsonPath =
|
|
4861
|
-
const mdPath =
|
|
4862
|
-
|
|
4863
|
-
|
|
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");
|
|
4864
4694
|
return plan.id;
|
|
4865
4695
|
}
|
|
4866
4696
|
load(idOrSlug) {
|
|
4867
4697
|
this.ensurePlansDir();
|
|
4868
|
-
const files =
|
|
4698
|
+
const files = import_node_fs8.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
|
|
4869
4699
|
for (const file of files) {
|
|
4870
|
-
const filePath =
|
|
4700
|
+
const filePath = import_node_path10.join(this.plansDir, file);
|
|
4871
4701
|
try {
|
|
4872
|
-
const plan = JSON.parse(
|
|
4702
|
+
const plan = JSON.parse(import_node_fs8.readFileSync(filePath, "utf-8"));
|
|
4873
4703
|
if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
|
|
4874
4704
|
return plan;
|
|
4875
4705
|
}
|
|
@@ -4879,11 +4709,11 @@ class PlanManager {
|
|
|
4879
4709
|
}
|
|
4880
4710
|
list(status) {
|
|
4881
4711
|
this.ensurePlansDir();
|
|
4882
|
-
const files =
|
|
4712
|
+
const files = import_node_fs8.readdirSync(this.plansDir).filter((f) => f.endsWith(".json"));
|
|
4883
4713
|
const plans = [];
|
|
4884
4714
|
for (const file of files) {
|
|
4885
4715
|
try {
|
|
4886
|
-
const plan = JSON.parse(
|
|
4716
|
+
const plan = JSON.parse(import_node_fs8.readFileSync(import_node_path10.join(this.plansDir, file), "utf-8"));
|
|
4887
4717
|
if (!status || plan.status === status) {
|
|
4888
4718
|
plans.push(plan);
|
|
4889
4719
|
}
|
|
@@ -4913,15 +4743,6 @@ class PlanManager {
|
|
|
4913
4743
|
plan.status = "approved";
|
|
4914
4744
|
plan.updatedAt = new Date().toISOString();
|
|
4915
4745
|
this.save(plan);
|
|
4916
|
-
const kb = new KnowledgeBase(this.projectPath);
|
|
4917
|
-
kb.updateProgress({
|
|
4918
|
-
role: "user",
|
|
4919
|
-
content: `Start sprint: ${plan.name}`
|
|
4920
|
-
});
|
|
4921
|
-
kb.updateProgress({
|
|
4922
|
-
role: "assistant",
|
|
4923
|
-
content: `Sprint started with ${tasks2.length} tasks. Goal: ${plan.goal}`
|
|
4924
|
-
});
|
|
4925
4746
|
return { sprint, tasks: tasks2 };
|
|
4926
4747
|
}
|
|
4927
4748
|
reject(idOrSlug, feedback) {
|
|
@@ -4949,18 +4770,18 @@ class PlanManager {
|
|
|
4949
4770
|
}
|
|
4950
4771
|
delete(idOrSlug) {
|
|
4951
4772
|
this.ensurePlansDir();
|
|
4952
|
-
const files =
|
|
4773
|
+
const files = import_node_fs8.readdirSync(this.plansDir);
|
|
4953
4774
|
for (const file of files) {
|
|
4954
|
-
const filePath =
|
|
4775
|
+
const filePath = import_node_path10.join(this.plansDir, file);
|
|
4955
4776
|
if (!file.endsWith(".json"))
|
|
4956
4777
|
continue;
|
|
4957
4778
|
try {
|
|
4958
|
-
const plan = JSON.parse(
|
|
4779
|
+
const plan = JSON.parse(import_node_fs8.readFileSync(filePath, "utf-8"));
|
|
4959
4780
|
if (plan.id === idOrSlug || this.slugify(plan.name) === idOrSlug) {
|
|
4960
|
-
|
|
4961
|
-
const mdPath =
|
|
4962
|
-
if (
|
|
4963
|
-
|
|
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);
|
|
4964
4785
|
}
|
|
4965
4786
|
return;
|
|
4966
4787
|
}
|
|
@@ -4974,8 +4795,8 @@ class PlanManager {
|
|
|
4974
4795
|
return sprintPlanToMarkdown(plan);
|
|
4975
4796
|
}
|
|
4976
4797
|
ensurePlansDir() {
|
|
4977
|
-
if (!
|
|
4978
|
-
|
|
4798
|
+
if (!import_node_fs8.existsSync(this.plansDir)) {
|
|
4799
|
+
import_node_fs8.mkdirSync(this.plansDir, { recursive: true });
|
|
4979
4800
|
}
|
|
4980
4801
|
}
|
|
4981
4802
|
slugify(name) {
|
|
@@ -4983,222 +4804,77 @@ class PlanManager {
|
|
|
4983
4804
|
}
|
|
4984
4805
|
}
|
|
4985
4806
|
// src/planning/planning-meeting.ts
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
function buildCrossTaskReviewerPrompt(input) {
|
|
4990
|
-
let prompt = `# Role: Cross-Task Reviewer (Architect + Engineer + Planner)
|
|
4991
|
-
|
|
4992
|
-
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.
|
|
4993
|
-
|
|
4994
|
-
## Context
|
|
4995
|
-
|
|
4996
|
-
In this system, tasks are executed SEQUENTIALLY by a single agent on ONE branch:
|
|
4997
|
-
- Tasks run one at a time, in the order they appear in the array
|
|
4998
|
-
- Each task's changes are committed before the next task starts
|
|
4999
|
-
- Later tasks can see and build on earlier tasks' work
|
|
5000
|
-
- The final result is a single branch with all changes, which becomes a pull request
|
|
5001
|
-
|
|
5002
|
-
This means:
|
|
5003
|
-
- Task ordering is critical — a task must NOT depend on a later task's output
|
|
5004
|
-
- Foundation work (config, schemas, shared code) must come first
|
|
5005
|
-
- Each task should be a focused, logical unit of work
|
|
5006
|
-
|
|
5007
|
-
## CEO Directive
|
|
5008
|
-
> ${input.directive}
|
|
5009
|
-
`;
|
|
5010
|
-
if (input.feedback) {
|
|
5011
|
-
prompt += `
|
|
5012
|
-
## CEO Feedback on Previous Plan
|
|
5013
|
-
> ${input.feedback}
|
|
5014
|
-
|
|
5015
|
-
IMPORTANT: Ensure the reviewed plan still addresses this feedback.
|
|
5016
|
-
`;
|
|
5017
|
-
}
|
|
5018
|
-
prompt += `
|
|
5019
|
-
## Project Context
|
|
5020
|
-
${input.projectContext || "No project context available."}
|
|
5021
|
-
|
|
5022
|
-
## Sprint Plan to Review
|
|
5023
|
-
${input.plannerOutput}
|
|
5024
|
-
|
|
5025
|
-
## Your Review Checklist
|
|
5026
|
-
|
|
5027
|
-
Go through EACH task and check for:
|
|
5028
|
-
|
|
5029
|
-
### 1. Ordering & Dependency Analysis
|
|
5030
|
-
For each task, verify:
|
|
5031
|
-
- Does it depend on any task that appears LATER in the list? If so, reorder.
|
|
5032
|
-
- Are foundational tasks (config, schemas, shared code) at the beginning?
|
|
5033
|
-
- Is the overall execution order logical?
|
|
5034
|
-
|
|
5035
|
-
### 2. Scope & Completeness
|
|
5036
|
-
For each task, verify:
|
|
5037
|
-
- Is the task well-scoped? Not too large, not too trivial?
|
|
5038
|
-
- Does it include ALL changes needed for its goal (given earlier tasks are done)?
|
|
5039
|
-
- Are there any missing tasks that should be added?
|
|
5040
|
-
|
|
5041
|
-
### 3. Description Quality Validation
|
|
5042
|
-
For each task, verify the description is a clear, actionable implementation guide. Each description must specify:
|
|
5043
|
-
- **What to do** — the specific goal and expected behavior/outcome
|
|
5044
|
-
- **Where to do it** — specific files, modules, or directories to modify or create
|
|
5045
|
-
- **How to do it** — implementation approach, patterns to follow, existing utilities to use
|
|
5046
|
-
- **Boundaries** — what is NOT in scope for this task
|
|
5047
|
-
|
|
5048
|
-
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.
|
|
5049
|
-
|
|
5050
|
-
### 4. Risk Assessment
|
|
5051
|
-
- Are there tasks that might fail or have unknowns?
|
|
5052
|
-
- Is the sprint scope realistic for sequential execution?
|
|
5053
|
-
|
|
5054
|
-
## Output Format
|
|
5055
|
-
|
|
5056
|
-
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:
|
|
5057
|
-
|
|
5058
|
-
{
|
|
5059
|
-
"hasIssues": true | false,
|
|
5060
|
-
"issues": [
|
|
5061
|
-
{
|
|
5062
|
-
"type": "wrong_order" | "missing_task" | "scope_issue" | "vague_description",
|
|
5063
|
-
"description": "string describing the specific issue",
|
|
5064
|
-
"affectedTasks": ["Task Title 1", "Task Title 2"],
|
|
5065
|
-
"resolution": "string describing how to fix it"
|
|
5066
|
-
}
|
|
5067
|
-
],
|
|
5068
|
-
"revisedPlan": {
|
|
5069
|
-
"name": "string (2-4 words)",
|
|
5070
|
-
"goal": "string (1 paragraph)",
|
|
5071
|
-
"estimatedDays": 3,
|
|
5072
|
-
"tasks": [
|
|
5073
|
-
{
|
|
5074
|
-
"title": "string",
|
|
5075
|
-
"description": "string (detailed implementation guide: what to do, where to do it, how to do it, and boundaries)",
|
|
5076
|
-
"assigneeRole": "BACKEND | FRONTEND | QA | PM | DESIGN",
|
|
5077
|
-
"priority": "CRITICAL | HIGH | MEDIUM | LOW",
|
|
5078
|
-
"labels": ["string"],
|
|
5079
|
-
"acceptanceCriteria": ["string"],
|
|
5080
|
-
"complexity": 3
|
|
5081
|
-
}
|
|
5082
|
-
],
|
|
5083
|
-
"risks": [
|
|
5084
|
-
{
|
|
5085
|
-
"description": "string",
|
|
5086
|
-
"mitigation": "string",
|
|
5087
|
-
"severity": "low | medium | high"
|
|
5088
|
-
}
|
|
5089
|
-
]
|
|
5090
|
-
}
|
|
5091
|
-
}
|
|
5092
|
-
|
|
5093
|
-
IMPORTANT:
|
|
5094
|
-
- If hasIssues is true, the revisedPlan MUST contain the corrected task list with issues resolved (reordered, descriptions rewritten, missing tasks added, etc.)
|
|
5095
|
-
- If hasIssues is false, the revisedPlan should be identical to the input plan (no changes needed)
|
|
5096
|
-
- The revisedPlan is ALWAYS required — it becomes the final plan
|
|
5097
|
-
- Ensure every task description is a detailed implementation guide (what, where, how, boundaries) — rewrite vague descriptions
|
|
5098
|
-
- Tasks execute sequentially — the array order IS the execution order`;
|
|
5099
|
-
return prompt;
|
|
5100
|
-
}
|
|
4807
|
+
init_config();
|
|
4808
|
+
var import_node_fs9 = require("node:fs");
|
|
4809
|
+
var import_node_path11 = require("node:path");
|
|
5101
4810
|
|
|
5102
4811
|
// src/planning/agents/planner.ts
|
|
5103
4812
|
function buildPlannerPrompt(input) {
|
|
5104
|
-
let
|
|
5105
|
-
|
|
5106
|
-
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.
|
|
5107
|
-
|
|
5108
|
-
## CEO Directive
|
|
5109
|
-
> ${input.directive}
|
|
5110
|
-
`;
|
|
4813
|
+
let feedbackSection = "";
|
|
5111
4814
|
if (input.feedback) {
|
|
5112
|
-
|
|
5113
|
-
|
|
5114
|
-
|
|
5115
|
-
|
|
5116
|
-
|
|
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>
|
|
5117
4820
|
`;
|
|
5118
4821
|
}
|
|
5119
|
-
|
|
5120
|
-
|
|
5121
|
-
|
|
5122
|
-
|
|
5123
|
-
|
|
5124
|
-
|
|
5125
|
-
|
|
5126
|
-
|
|
5127
|
-
|
|
5128
|
-
|
|
5129
|
-
|
|
5130
|
-
|
|
5131
|
-
|
|
5132
|
-
|
|
5133
|
-
|
|
5134
|
-
|
|
5135
|
-
|
|
5136
|
-
|
|
5137
|
-
|
|
5138
|
-
- **Assignee Role** — BACKEND, FRONTEND, QA, PM, or DESIGN
|
|
5139
|
-
- **Priority** — CRITICAL, HIGH, MEDIUM, or LOW
|
|
5140
|
-
- **Complexity** — 1 (trivial) to 5 (very complex)
|
|
5141
|
-
- **Labels** — Relevant tags (e.g., "api", "database", "ui", "auth")
|
|
5142
|
-
- **Acceptance Criteria** — Specific, testable conditions for completion
|
|
5143
|
-
|
|
5144
|
-
### CRITICAL: Task Description Requirements
|
|
5145
|
-
|
|
5146
|
-
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:
|
|
5147
|
-
|
|
5148
|
-
1. **What to do** — Clearly state the goal and expected behavior/outcome
|
|
5149
|
-
2. **Where to do it** — List specific files, modules, or directories to modify or create. Reference existing code paths when extending functionality
|
|
5150
|
-
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
|
|
5151
|
-
4. **Boundaries** — What is NOT in scope for this task to prevent overlap with other tasks
|
|
5152
|
-
|
|
5153
|
-
Bad example: "Add authentication to the API."
|
|
5154
|
-
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."
|
|
5155
|
-
|
|
5156
|
-
### CRITICAL: Task Ordering Rules
|
|
5157
|
-
|
|
5158
|
-
Tasks are executed SEQUENTIALLY by a single agent on ONE branch. The agent works through tasks in array order. Therefore:
|
|
5159
|
-
|
|
5160
|
-
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.
|
|
5161
|
-
2. **No forward dependencies.** A task must NOT depend on a task that appears later in the list.
|
|
5162
|
-
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.
|
|
5163
|
-
4. **Keep tasks focused.** Each task should do one logical unit of work. Avoid trivially small or overly large tasks.
|
|
5164
|
-
5. **Merge related trivial work.** If two pieces of work are trivially small and tightly related, combine them into one task.
|
|
5165
|
-
|
|
5166
|
-
### Sprint Scope Guidelines
|
|
5167
|
-
|
|
5168
|
-
- If the sprint would exceed 12 tasks, reduce scope or merge related tasks
|
|
5169
|
-
- Ensure acceptance criteria are specific and testable
|
|
5170
|
-
- Keep the sprint focused on the directive — avoid scope creep
|
|
5171
|
-
|
|
5172
|
-
## Output Format
|
|
5173
|
-
|
|
5174
|
-
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:
|
|
5175
|
-
|
|
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:
|
|
5176
4841
|
{
|
|
5177
|
-
"
|
|
5178
|
-
"
|
|
5179
|
-
"
|
|
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}",
|
|
5180
4850
|
"tasks": [
|
|
5181
4851
|
{
|
|
5182
|
-
"
|
|
5183
|
-
"
|
|
5184
|
-
"
|
|
5185
|
-
"
|
|
5186
|
-
"
|
|
5187
|
-
"
|
|
5188
|
-
"
|
|
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
|
|
5189
4860
|
}
|
|
5190
4861
|
],
|
|
5191
4862
|
"risks": [
|
|
5192
4863
|
{
|
|
5193
|
-
"description": "
|
|
5194
|
-
"mitigation": "
|
|
5195
|
-
"severity": "low
|
|
4864
|
+
"description": "What could go wrong",
|
|
4865
|
+
"mitigation": "How to handle it",
|
|
4866
|
+
"severity": "low|medium|high"
|
|
5196
4867
|
}
|
|
5197
4868
|
]
|
|
5198
4869
|
}
|
|
5199
4870
|
|
|
5200
|
-
IMPORTANT:
|
|
5201
|
-
|
|
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>`;
|
|
5202
4878
|
}
|
|
5203
4879
|
|
|
5204
4880
|
// src/planning/planning-meeting.ts
|
|
@@ -5214,42 +4890,41 @@ class PlanningMeeting {
|
|
|
5214
4890
|
});
|
|
5215
4891
|
}
|
|
5216
4892
|
async run(directive, feedback) {
|
|
5217
|
-
|
|
5218
|
-
|
|
5219
|
-
|
|
5220
|
-
|
|
5221
|
-
|
|
5222
|
-
|
|
5223
|
-
}
|
|
5224
|
-
|
|
5225
|
-
const
|
|
5226
|
-
this.log("Planner phase complete.", "success");
|
|
5227
|
-
this.log("Phase 2/2: Reviewer checking for conflicts and quality...", "info");
|
|
5228
|
-
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({
|
|
5229
4902
|
directive,
|
|
5230
|
-
|
|
5231
|
-
|
|
5232
|
-
|
|
4903
|
+
feedback,
|
|
4904
|
+
plansDir,
|
|
4905
|
+
planId,
|
|
4906
|
+
fileName
|
|
5233
4907
|
});
|
|
5234
|
-
const
|
|
5235
|
-
this.log("
|
|
5236
|
-
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
|
+
}
|
|
5237
4920
|
if (feedback) {
|
|
5238
4921
|
plan.feedback = feedback;
|
|
5239
4922
|
}
|
|
5240
4923
|
return {
|
|
5241
4924
|
plan,
|
|
5242
|
-
|
|
5243
|
-
planner: plannerOutput,
|
|
5244
|
-
review: reviewOutput
|
|
5245
|
-
}
|
|
4925
|
+
rawOutput: response
|
|
5246
4926
|
};
|
|
5247
4927
|
}
|
|
5248
|
-
getProjectContext() {
|
|
5249
|
-
const kb = new KnowledgeBase(this.projectPath);
|
|
5250
|
-
return kb.getFullContext();
|
|
5251
|
-
}
|
|
5252
4928
|
}
|
|
5253
4929
|
// src/index-node.ts
|
|
5254
|
-
init_knowledge_base();
|
|
5255
4930
|
init_colors();
|