spacecode 2.8.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/repl.mjs CHANGED
@@ -3825,50 +3825,193 @@ var init_bash = __esm({
3825
3825
  }
3826
3826
  });
3827
3827
 
3828
- // ../agent/src/prompt.ts
3829
- function buildSystemPrompt(options) {
3830
- const workingDir = options.localCwd ? `Working directory: ${options.localCwd}` : options.repoName ? `Working directory: /home/user/${options.repoName}` : "Working directory: empty workspace";
3831
- const where = options.localCwd ? "on the user's machine" : "in the cloud";
3832
- const toolLines = [
3833
- "- read_file / write_file / edit_file / list_directory \u2014 instant file operations",
3834
- "- search \u2014 semantic code search by concept or keyword",
3835
- "- bash \u2014 shell commands (tests, builds, git, installs)"
3836
- ];
3837
- if (options.hasPreviewTool) {
3838
- toolLines.push("- preview \u2014 start or check a live preview of the app (front-end projects only)");
3839
- }
3840
- if (options.hasGitHubTools) {
3841
- toolLines.push("- create_pull_request \u2014 create a GitHub PR from the current branch");
3842
- }
3843
- if (options.hasSentryTools) {
3844
- toolLines.push("- sentry_list_issues / sentry_get_issue \u2014 view production errors (when connected)");
3845
- }
3846
- if (options.hasCodegenTools) {
3847
- toolLines.push("- build_feature \u2014 generate a complete feature with tests using the platform codegen service. Use for multi-file features or new modules. Files are written to workspace; run tests afterwards.");
3828
+ // ../shared/src/prompts/defaults-core.ts
3829
+ var DEFAULT_PROMPT_LIST;
3830
+ var init_defaults_core = __esm({
3831
+ "../shared/src/prompts/defaults-core.ts"() {
3832
+ "use strict";
3833
+ DEFAULT_PROMPT_LIST = [
3834
+ {
3835
+ key: "pipeline.decompose.system",
3836
+ description: "Splits a request into sub-tasks and enhances each (pipeline decomposer).",
3837
+ vars: [],
3838
+ body: `You are a task decomposition and prompt enhancement engine.
3839
+
3840
+ ## Decomposition Rules
3841
+ Analyze user requests and determine if they contain multiple independent tasks.
3842
+ - Numbered lists, bullet points, or requests joined by "and"/"also" are decomposable
3843
+ - Each sub-task should be independently executable
3844
+
3845
+ ## Prompt Enhancement Principles (MiniMax Best Practices)
3846
+
3847
+ 1. **Be Specific and Detailed**
3848
+ - Transform vague requests into rich, detailed specifications
3849
+ - Less: "Create a visualization website"
3850
+ - More: "Create an enterprise-grade data visualization website with interactive charts, filtering capabilities, export functionality, and responsive design"
3851
+
3852
+ 2. **Explain Intent ("Why")**
3853
+ - Add context about why this task matters
3854
+ - Example: "Your code will be reviewed for security vulnerabilities, so validate all inputs"
3855
+ - This helps the agent understand the purpose and provide more accurate results
3856
+
3857
+ 3. **Provide Examples**
3858
+ - Show good patterns to follow and bad patterns to avoid
3859
+ - Example: "Good: Handle errors with specific messages like 'User not found'. Bad: Use generic try/catch with no message."
3860
+
3861
+ 4. **Define Success Criteria**
3862
+ - What does "done" look like?
3863
+ - Example: "Complete when: tests pass, no linting errors, and manual verification in browser"
3864
+
3865
+ 5. **Specify Constraints**
3866
+ - What to avoid, what not to do
3867
+ - Example: "Do not use external dependencies beyond the existing package.json"
3868
+
3869
+ For each sub-task, enhance the description following these principles. Call the decompose_tasks function with your analysis.`
3870
+ },
3871
+ {
3872
+ key: "pipeline.enhance.system",
3873
+ description: "Rewrites a coding request into a richer prompt (pipeline optimizer).",
3874
+ vars: [],
3875
+ body: `You are a prompt enhancement engine. Transform coding requests into rich, detailed prompts following these principles:
3876
+
3877
+ ## Enhancement Principles (MiniMax Best Practices)
3878
+
3879
+ 1. **Be Specific and Detailed**
3880
+ - Transform vague requests into rich specifications
3881
+ - Less: "Create a visualization website"
3882
+ - More: "Create an enterprise-grade data visualization website with interactive charts, filtering capabilities, export functionality, and responsive design"
3883
+
3884
+ 2. **Explain Intent ("Why")**
3885
+ - Add context about why this task matters
3886
+ - Example: "Your code will be reviewed for security vulnerabilities, so validate all inputs"
3887
+
3888
+ 3. **Provide Examples**
3889
+ - Show good patterns to follow and bad patterns to avoid
3890
+ - Example: "Good: Handle errors with specific messages. Bad: Use generic try/catch"
3891
+
3892
+ 4. **Define Success Criteria**
3893
+ - What does "done" look like?
3894
+ - Example: "Complete when: tests pass, no linting errors"
3895
+
3896
+ 5. **Specify Constraints**
3897
+ - What to avoid
3898
+ - Example: "Do not use external dependencies beyond existing packages"
3899
+
3900
+ Call the enhance_prompt function with your analysis.`
3901
+ },
3902
+ {
3903
+ key: "context.summary",
3904
+ description: "Short conversation summary stored for context reuse.",
3905
+ vars: [],
3906
+ body: `Summarize this conversation concisely in 2-4 sentences. Focus on:
3907
+ - What the user asked for
3908
+ - Key decisions made
3909
+ - Current state of the work
3910
+ - Any important context (file names, technologies, error messages)
3911
+
3912
+ Do NOT include greetings or meta-commentary. Just the facts.`
3913
+ },
3914
+ {
3915
+ key: "context.reasoning_summary",
3916
+ description: "Detailed summary of the earlier conversation for the reasoning tier.",
3917
+ vars: [],
3918
+ body: `Summarize the earlier part of this conversation for a senior AI assistant that will continue the work. Be detailed and preserve:
3919
+ - Specific file paths, function names, and code snippets mentioned
3920
+ - Decisions made and their rationale
3921
+ - Error messages, stack traces, or debugging steps taken
3922
+ - Current state of the task (what's done, what remains)
3923
+ - User preferences or constraints expressed
3924
+
3925
+ Write 3-8 sentences. Be concrete and technical, not vague.`
3926
+ },
3927
+ {
3928
+ key: "profile.adapter.system",
3929
+ description: "Classifies imported instruction sections as keep / on_demand / drop.",
3930
+ vars: [],
3931
+ body: `You prepare a user's personal instruction file (written for the Claude Code CLI) to be used by SpaceCode, a different coding agent that runs locally with these tools: read_file, write_file, edit_file, list_directory, search, bash, todo_write, memory_read, instructions_read. SpaceCode does NOT have: Claude Code hooks, ToolSearch/deferred tools, subagents (Task/Agent), skills or slash commands (/ship etc.), MCP tools (mcp__*), plan mode or worktrees.
3932
+
3933
+ For EACH section decide:
3934
+ - "keep": a short, general standing rule that applies to nearly every task (always loaded). Keep this set small: the total of all kept sections must stay under about 5000 characters.
3935
+ - "on_demand": useful only when working in a specific area (a tool, vendor, workflow). Give a one-line "summary" saying when to read it.
3936
+ - "drop": (the text is kept and stored on demand anyway; this only flags it) only meaningful inside Claude Code (describes its hooks, skills, subagents, ToolSearch, MCP tool names or its own settings) or is obsolete for SpaceCode. Give a short "reason".
3937
+
3938
+ You never rewrite or shorten text; you only classify. When unsure between drop and on_demand choose on_demand. When unsure between keep and on_demand choose on_demand.
3939
+
3940
+ Reply with ONLY a JSON array, one object per input section, in the same order: {"slug": string, "decision": "keep"|"on_demand"|"drop", "reason"?: string, "summary"?: string}. No prose, no code fences.`
3941
+ },
3942
+ {
3943
+ key: "route.code.system",
3944
+ description: 'System prompt of the "code" dynamic route (pipeline helper calls).',
3945
+ vars: [],
3946
+ body: `You are an expert software engineer. Write clean, correct, well-structured code with proper error handling.`
3947
+ },
3948
+ {
3949
+ key: "route.code-review.system",
3950
+ description: 'System prompt of the "code-review" dynamic route (pipeline helper calls).',
3951
+ vars: [],
3952
+ body: `You are a senior code reviewer. Analyze code for bugs, security issues, performance problems, and maintainability. Be specific and actionable.`
3953
+ },
3954
+ {
3955
+ key: "route.debugging.system",
3956
+ description: 'System prompt of the "debugging" dynamic route (pipeline helper calls).',
3957
+ vars: [],
3958
+ body: `You are an expert debugger. Diagnose issues methodically: identify symptoms, trace root causes, and provide concrete fixes. Explain your reasoning step by step.`
3959
+ },
3960
+ {
3961
+ key: "route.architecture.system",
3962
+ description: 'System prompt of the "architecture" dynamic route (pipeline helper calls).',
3963
+ vars: [],
3964
+ body: `You are a software architect. Focus on system design, scalability, trade-offs, and infrastructure decisions. Be concrete and practical.`
3965
+ },
3966
+ {
3967
+ key: "route.planning.system",
3968
+ description: 'System prompt of the "planning" dynamic route (pipeline helper calls).',
3969
+ vars: [],
3970
+ body: `You are a technical project manager. Create structured plans, roadmaps, and specifications. Use clear headings and actionable items.`
3971
+ },
3972
+ {
3973
+ key: "agent.summarizer.system",
3974
+ description: "Extracts decisions and work items from a session transcript (resume summary, /compact).",
3975
+ vars: [],
3976
+ body: `You are extracting structured information from a coding-agent conversation transcript. Identify (a) concrete DECISIONS made during the conversation (architectural choices, approach selections, explicit tradeoffs accepted) and (b) WORK ITEMS \u2014 discrete pieces of work mentioned as done, in-progress, or still needed, each classified as "open" or "done" based on the transcript. Respond with ONLY a JSON object matching exactly this shape, no prose before or after, no markdown code fences: {"decisions": [{"description": string}], "workItems": [{"description": string, "status": "open"|"done"}]}`
3977
+ },
3978
+ {
3979
+ key: "agent.summarizer.user_suffix",
3980
+ description: "Reminder appended after the transcript for the summarizer.",
3981
+ vars: [],
3982
+ body: `Respond with ONLY the JSON object described above \u2014 no prose, no markdown code fences.`
3983
+ }
3984
+ ];
3848
3985
  }
3849
- return `You are SpaceCode Agent, a coding assistant running ${where}.
3986
+ });
3987
+
3988
+ // ../shared/src/prompts/defaults-agent.ts
3989
+ var AGENT_PROMPT_LIST;
3990
+ var init_defaults_agent = __esm({
3991
+ "../shared/src/prompts/defaults-agent.ts"() {
3992
+ "use strict";
3993
+ AGENT_PROMPT_LIST = [
3994
+ {
3995
+ key: "agent.system.core",
3996
+ description: "Core system prompt of the SpaceCode agent (REPL and cloud).",
3997
+ vars: ["where", "tool_lines", "orient_line", "finish_line", "codegen_section", "commit_section", "working_dir", "project_card_section", "resume_section"],
3998
+ body: `You are SpaceCode Agent, a coding assistant running {{where}}.
3850
3999
 
3851
4000
  ## Tools
3852
- ${toolLines.join("\n")}
4001
+ {{tool_lines}}
3853
4002
 
3854
4003
  ## Tool usage
3855
- ${options.projectCard ? "- A project card below already lists the layout, commands, docs and recent commits. Do not re-list directories or re-read files it covers; open only what the task needs." : "- Use list_directory to orient yourself in the codebase before diving in."}
4004
+ {{orient_line}}
3856
4005
  - Use search to find files by concept ("auth middleware", "billing webhook").
3857
4006
  - Use bash for running commands, not for reading files. Use read_file instead.
3858
4007
  - Prefer read_file + edit_file over write_file for existing files.
3859
- ${options.hasGitHubTools ? "- After completing work, create a PR with create_pull_request. Commit and push changes first." : "- After completing work, commit your changes with bash (git add + git commit)."}
4008
+ {{finish_line}}
3860
4009
 
3861
4010
  ## Editing
3862
4011
  - Always read_file before edit_file \u2014 you need the current content.
3863
4012
  - old_string must match exactly once. Include surrounding lines for uniqueness.
3864
4013
  - Make small, targeted edits. Avoid rewriting entire files with write_file.
3865
- ${options.hasCodegenTools ? `
3866
- ## Feature generation
3867
- - For multi-file features (3+ new files), prefer build_feature over writing files manually.
3868
- - build_feature leverages a component library \u2014 it reuses existing implementations when possible.
3869
- - After build_feature, always run tests and review the generated code before committing.
3870
- - For single-file changes, bug fixes, or config edits, use edit_file directly.
3871
- ` : ""}
4014
+ {{codegen_section}}
3872
4015
  ## Error recovery
3873
4016
  - If edit_file fails (old_string not found), re-read the file \u2014 it may have changed \u2014 then retry with corrected old_string.
3874
4017
  - If bash fails, read the error output and try a different approach. Never repeat the same failing command.
@@ -3879,24 +4022,125 @@ ${options.hasCodegenTools ? `
3879
4022
  - For an audit or plan request, finish with the audit or plan itself (findings, then ordered next steps). Do not end by asking what to work on next.
3880
4023
  - After changes, run relevant tests (bash: npm test, pytest, etc.) to verify.
3881
4024
  - Commit logical units of work with descriptive messages.
3882
- ${options.commitGuidance ? `
3883
- ## Committing
4025
+ {{commit_section}}
4026
+ {{working_dir}}
4027
+ {{project_card_section}}{{resume_section}}
4028
+ Complete the user's task, then stop. Be concise in responses.`
4029
+ },
4030
+ {
4031
+ key: "agent.fragment.orient_card",
4032
+ description: "Tool-usage line when a project card is present.",
4033
+ vars: [],
4034
+ body: `- A project card below already holds the layout, commands, doc titles and excerpts, recent commits, branch and uncommitted changes. Do not re-list directories, re-run git status/log/diff, or re-read a doc just to see what the card shows; open a file only to go beyond its excerpt.`
4035
+ },
4036
+ {
4037
+ key: "agent.fragment.orient_default",
4038
+ description: "Tool-usage line when there is no project card.",
4039
+ vars: [],
4040
+ body: `- Use list_directory to orient yourself in the codebase before diving in.`
4041
+ },
4042
+ {
4043
+ key: "agent.fragment.finish_github",
4044
+ description: "Closing instruction when GitHub tools are available.",
4045
+ vars: [],
4046
+ body: `- After completing work, create a PR with create_pull_request. Commit and push changes first.`
4047
+ },
4048
+ {
4049
+ key: "agent.fragment.finish_commit",
4050
+ description: "Closing instruction without GitHub tools.",
4051
+ vars: [],
4052
+ body: `- After completing work, commit your changes with bash (git add + git commit).`
4053
+ },
4054
+ {
4055
+ key: "agent.fragment.codegen",
4056
+ description: "Feature-generation section (only when build_feature is registered).",
4057
+ vars: [],
4058
+ body: `## Feature generation
4059
+ - For multi-file features (3+ new files), prefer build_feature over writing files manually.
4060
+ - build_feature leverages a component library \u2014 it reuses existing implementations when possible.
4061
+ - After build_feature, always run tests and review the generated code before committing.
4062
+ - For single-file changes, bug fixes, or config edits, use edit_file directly.`
4063
+ },
4064
+ {
4065
+ key: "agent.fragment.commit",
4066
+ description: "Committing section (only when commit guidance is on).",
4067
+ vars: [],
4068
+ body: `## Committing
3884
4069
  - After completing a logical unit of work, commit changes with a descriptive message using bash (git add + git commit).
3885
4070
  - Use conventional commit format: feat:, fix:, refactor:, test:, docs:
3886
- - Don't commit after every single file change \u2014 group related changes.
3887
- ` : ""}
3888
- ${workingDir}
3889
- ${options.projectCard ? `
3890
- ${options.projectCard}
3891
- ` : ""}${options.resumeSummary ? `
3892
- ## Previously in this session
3893
- ${options.resumeSummary}
3894
- ` : ""}
3895
- Complete the user's task, then stop. Be concise in responses.`;
4071
+ - Don't commit after every single file change \u2014 group related changes.`
4072
+ },
4073
+ {
4074
+ key: "agent.fragment.resume",
4075
+ description: "Prior-turn summary injected on resume.",
4076
+ vars: ["summary"],
4077
+ body: `## Previously in this session
4078
+ {{summary}}`
4079
+ }
4080
+ ];
4081
+ }
4082
+ });
4083
+
4084
+ // ../shared/src/prompts/index.ts
4085
+ function renderPrompt(body, vars = {}) {
4086
+ return body.replace(VAR, (_m, name) => {
4087
+ if (!(name in vars)) throw new Error(`prompt variable {{${name}}} was not provided`);
4088
+ return vars[name];
4089
+ });
4090
+ }
4091
+ var DEFAULT_PROMPTS, VAR;
4092
+ var init_prompts = __esm({
4093
+ "../shared/src/prompts/index.ts"() {
4094
+ init_defaults_core();
4095
+ init_defaults_agent();
4096
+ DEFAULT_PROMPTS = Object.freeze(
4097
+ Object.fromEntries([...DEFAULT_PROMPT_LIST, ...AGENT_PROMPT_LIST].map((p) => [p.key, p]))
4098
+ );
4099
+ VAR = /\{\{\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\}\}/g;
4100
+ }
4101
+ });
4102
+
4103
+ // ../agent/src/prompt.ts
4104
+ function buildSystemPrompt(options) {
4105
+ const workingDir = options.localCwd ? `Working directory: ${options.localCwd}` : options.repoName ? `Working directory: /home/user/${options.repoName}` : "Working directory: empty workspace";
4106
+ const where = options.localCwd ? "on the user's machine" : "in the cloud";
4107
+ const toolLines = [
4108
+ "- read_file / write_file / edit_file / list_directory \u2014 instant file operations",
4109
+ "- search \u2014 semantic code search by concept or keyword",
4110
+ "- bash \u2014 shell commands (tests, builds, git, installs)"
4111
+ ];
4112
+ if (options.hasPreviewTool) {
4113
+ toolLines.push("- preview \u2014 start or check a live preview of the app (front-end projects only)");
4114
+ }
4115
+ if (options.hasGitHubTools) {
4116
+ toolLines.push("- create_pull_request \u2014 create a GitHub PR from the current branch");
4117
+ }
4118
+ if (options.hasSentryTools) {
4119
+ toolLines.push("- sentry_list_issues / sentry_get_issue \u2014 view production errors (when connected)");
4120
+ }
4121
+ if (options.hasCodegenTools) {
4122
+ toolLines.push("- build_feature \u2014 generate a complete feature with tests using the platform codegen service. Use for multi-file features or new modules. Files are written to workspace; run tests afterwards.");
4123
+ }
4124
+ const t = (key) => options.templates?.[key] ?? DEFAULT_PROMPTS[key].body;
4125
+ const wrap = (body) => `
4126
+ ${body}
4127
+ `;
4128
+ return renderPrompt(t("agent.system.core"), {
4129
+ where,
4130
+ tool_lines: toolLines.join("\n"),
4131
+ orient_line: options.projectCard ? t("agent.fragment.orient_card") : t("agent.fragment.orient_default"),
4132
+ finish_line: options.hasGitHubTools ? t("agent.fragment.finish_github") : t("agent.fragment.finish_commit"),
4133
+ codegen_section: options.hasCodegenTools ? wrap(t("agent.fragment.codegen")) : "",
4134
+ commit_section: options.commitGuidance ? wrap(t("agent.fragment.commit")) : "",
4135
+ working_dir: workingDir,
4136
+ project_card_section: options.projectCard ? wrap(options.projectCard) : "",
4137
+ resume_section: options.resumeSummary ? wrap(renderPrompt(t("agent.fragment.resume"), { summary: options.resumeSummary })) : ""
4138
+ });
3896
4139
  }
3897
4140
  var init_prompt = __esm({
3898
4141
  "../agent/src/prompt.ts"() {
3899
4142
  "use strict";
4143
+ init_prompts();
3900
4144
  }
3901
4145
  });
3902
4146
 
@@ -3921,13 +4165,17 @@ function sections(i) {
3921
4165
  if (docs.length) out.push({ key: "docs", lines: docs });
3922
4166
  const commits = i.recentCommits.slice(0, 8).map((c) => `- ${c}`);
3923
4167
  if (commits.length) out.push({ key: "commits", lines: commits });
4168
+ const changeLines = [...i.changes?.vsMain ? [`- vs main: ${cut(i.changes.vsMain, 160)}`] : [], ...(i.changes?.files ?? []).slice(0, 15).map((f) => `- ${cut(f, 120)}`)];
4169
+ if (changeLines.length) out.push({ key: "changes", lines: changeLines });
4170
+ const digests = (i.digests ?? []).slice(0, 5).flatMap((d) => [`#### ${d.path}`, ...d.lines.slice(0, 6).map((l) => `- ${cut(l, 110)}`)]);
4171
+ if (digests.length) out.push({ key: "digests", lines: digests });
3924
4172
  const backlog = i.backlog.slice(0, 6).map((b2) => `- ${cut(b2, 160)}`);
3925
4173
  if (backlog.length) out.push({ key: "backlog", lines: backlog });
3926
4174
  return out;
3927
4175
  }
3928
- function renderProjectCard(input, budgetChars = 3500) {
4176
+ function renderProjectCard(input, budgetChars = 7e3) {
3929
4177
  let secs = sections(input);
3930
- for (const drop of ["backlog", "docs", "commits", "commands"]) {
4178
+ for (const drop of ["backlog", "digests", "changes", "docs", "commits", "commands"]) {
3931
4179
  if (render(secs).length <= budgetChars) return render(secs);
3932
4180
  secs = secs.filter((s) => s.key !== drop);
3933
4181
  }
@@ -3953,6 +4201,8 @@ var init_project_card = __esm({
3953
4201
  commands: "Commands",
3954
4202
  docs: "Docs",
3955
4203
  commits: "Recent commits",
4204
+ changes: "Uncommitted changes and branch vs main",
4205
+ digests: "Doc excerpts (opening lines; read the file for the rest)",
3956
4206
  backlog: "Open work noted in repo files (context, NOT tasks to start)"
3957
4207
  };
3958
4208
  render = (secs) => [HEADER, ...secs.map((s) => `### ${TITLES[s.key]}
@@ -4090,9 +4340,9 @@ function createFileTools(store) {
4090
4340
  const handlers = {
4091
4341
  read_file: async (input) => {
4092
4342
  const path12 = input.path;
4093
- const cached2 = readCache.get(path12);
4094
- if (cached2 !== void 0) {
4095
- return { output: cached2 };
4343
+ const cached3 = readCache.get(path12);
4344
+ if (cached3 !== void 0) {
4345
+ return { output: cached3 };
4096
4346
  }
4097
4347
  const content = await store.readFile(path12);
4098
4348
  if (content === null) {
@@ -4152,12 +4402,12 @@ Include more surrounding context to make old_string unique.`,
4152
4402
  },
4153
4403
  list_directory: async (input) => {
4154
4404
  const path12 = input.path;
4155
- const cached2 = listCache.get(path12);
4156
- if (cached2 !== void 0) {
4157
- if (cached2.length === 0) {
4405
+ const cached3 = listCache.get(path12);
4406
+ if (cached3 !== void 0) {
4407
+ if (cached3.length === 0) {
4158
4408
  return { output: `Empty or not found: ${path12}` };
4159
4409
  }
4160
- return { output: cached2.join("\n") };
4410
+ return { output: cached3.join("\n") };
4161
4411
  }
4162
4412
  const entries = await store.listDirectory(path12);
4163
4413
  listCache.set(path12, entries);
@@ -5050,13 +5300,13 @@ function createCodegenTools(fileStore, config) {
5050
5300
  if (!buildRes.ok) {
5051
5301
  throw new Error(`Failed to fetch build result: ${buildRes.status}`);
5052
5302
  }
5053
- const build = await buildRes.json();
5054
- if (!build.result) {
5303
+ const build2 = await buildRes.json();
5304
+ if (!build2.result) {
5055
5305
  throw new Error("Build completed but no files in result");
5056
5306
  }
5057
5307
  let files;
5058
5308
  try {
5059
- const parsed = JSON.parse(build.result);
5309
+ const parsed = JSON.parse(build2.result);
5060
5310
  files = parsed.files ?? [];
5061
5311
  } catch {
5062
5312
  throw new Error("Failed to parse build result");
@@ -5871,10 +6121,10 @@ async function summarizeConversation(messages, config) {
5871
6121
  return { decisions: [], workItems: [], summaryText: "", inputTokens: 0, outputTokens: 0 };
5872
6122
  }
5873
6123
  const transcript = buildTranscriptForSummarization(messages, MAX_TRANSCRIPT_CHARS);
5874
- const systemPrompt = 'You are extracting structured information from a coding-agent conversation transcript. Identify (a) concrete DECISIONS made during the conversation (architectural choices, approach selections, explicit tradeoffs accepted) and (b) WORK ITEMS \u2014 discrete pieces of work mentioned as done, in-progress, or still needed, each classified as "open" or "done" based on the transcript. Respond with ONLY a JSON object matching exactly this shape, no prose before or after, no markdown code fences: {"decisions": [{"description": string}], "workItems": [{"description": string, "status": "open"|"done"}]}';
6124
+ const systemPrompt = config.templates?.["agent.summarizer.system"] ?? DEFAULT_PROMPTS["agent.summarizer.system"].body;
5875
6125
  const userContent = `${transcript}
5876
6126
 
5877
- Respond with ONLY the JSON object described above \u2014 no prose, no markdown code fences.`;
6127
+ ${config.templates?.["agent.summarizer.user_suffix"] ?? DEFAULT_PROMPTS["agent.summarizer.user_suffix"].body}`;
5878
6128
  const response = await fetch(`${config.apiUrl}/v1/messages`, {
5879
6129
  method: "POST",
5880
6130
  headers: {
@@ -5968,6 +6218,7 @@ var DEFAULT_SUMMARIZER_MODEL, MAX_TRANSCRIPT_CHARS, MAX_TOKENS2;
5968
6218
  var init_summarizer = __esm({
5969
6219
  "../agent/src/summarizer.ts"() {
5970
6220
  "use strict";
6221
+ init_prompts();
5971
6222
  DEFAULT_SUMMARIZER_MODEL = "claude-haiku-4-5";
5972
6223
  MAX_TRANSCRIPT_CHARS = 4e4;
5973
6224
  MAX_TOKENS2 = 4096;
@@ -6644,17 +6895,17 @@ var init_memory_tool = __esm({
6644
6895
  // ../agent/src/import/profile-loader.ts
6645
6896
  async function loadProfile(opts) {
6646
6897
  const { client, cache: cache4, projectKeys } = opts;
6647
- let cached2 = null;
6898
+ let cached3 = null;
6648
6899
  let cacheError;
6649
6900
  try {
6650
- cached2 = await cache4.read();
6901
+ cached3 = await cache4.read();
6651
6902
  } catch (err) {
6652
6903
  cacheError = `profile cache unreadable: ${err instanceof Error ? err.message : String(err)}`;
6653
6904
  }
6654
- const sameProject = cached2 !== null && cached2.projectKey !== null && projectKeys.includes(cached2.projectKey);
6905
+ const sameProject = cached3 !== null && cached3.projectKey !== null && projectKeys.includes(cached3.projectKey);
6655
6906
  try {
6656
- const index = await client.index(sameProject ? cached2.version : void 0);
6657
- if (index === null && sameProject) return { profile: cached2, source: "live" };
6907
+ const index = await client.index(sameProject ? cached3.version : void 0);
6908
+ if (index === null && sameProject) return { profile: cached3, source: "live" };
6658
6909
  if (index === null) throw new Error("profile API answered 304 with no usable cached copy");
6659
6910
  const has2 = (kind, name) => index.assets.some((a) => a.kind === kind && a.name === name);
6660
6911
  const chosen = projectKeys.find((k2) => has2("memory", `${k2}.index`)) ?? null;
@@ -6676,8 +6927,8 @@ async function loadProfile(opts) {
6676
6927
  return { profile, source: "live", ...cacheError ? { error: cacheError } : {} };
6677
6928
  } catch (err) {
6678
6929
  const error = `${err instanceof Error ? err.message : String(err)}${cacheError ? `; ${cacheError}` : ""}`;
6679
- if (!cached2) return { profile: null, source: "none", error };
6680
- return { profile: sameProject ? cached2 : { ...cached2, memoryIndex: null, projectKey: null }, source: "cache", error };
6930
+ if (!cached3) return { profile: null, source: "none", error };
6931
+ return { profile: sameProject ? cached3 : { ...cached3, memoryIndex: null, projectKey: null }, source: "cache", error };
6681
6932
  }
6682
6933
  }
6683
6934
  var init_profile_loader = __esm({
@@ -9188,11 +9439,11 @@ var init_base = __esm({
9188
9439
  }
9189
9440
  const parts = os.release().split(".");
9190
9441
  const major = Number(parts[0]);
9191
- const build = Number(parts[2] ?? 0);
9442
+ const build2 = Number(parts[2] ?? 0);
9192
9443
  if (major < 10) {
9193
9444
  return true;
9194
9445
  }
9195
- if (major === 10 && build < 10586) {
9446
+ if (major === 10 && build2 < 10586) {
9196
9447
  return true;
9197
9448
  }
9198
9449
  return false;
@@ -9380,7 +9631,7 @@ var require_signal_exit = __commonJS({
9380
9631
  }
9381
9632
  assert.equal(typeof cb, "function", "a callback must be provided for exit handler");
9382
9633
  if (loaded === false) {
9383
- load();
9634
+ load2();
9384
9635
  }
9385
9636
  var ev = "exit";
9386
9637
  if (opts && opts.alwaysLast) {
@@ -9440,7 +9691,7 @@ var require_signal_exit = __commonJS({
9440
9691
  return signals;
9441
9692
  };
9442
9693
  loaded = false;
9443
- load = function load2() {
9694
+ load2 = function load3() {
9444
9695
  if (loaded || !processOk(global.process)) {
9445
9696
  return;
9446
9697
  }
@@ -9457,7 +9708,7 @@ var require_signal_exit = __commonJS({
9457
9708
  process13.emit = processEmit;
9458
9709
  process13.reallyExit = processReallyExit;
9459
9710
  };
9460
- module.exports.load = load;
9711
+ module.exports.load = load2;
9461
9712
  originalProcessReallyExit = process13.reallyExit;
9462
9713
  processReallyExit = function processReallyExit2(code) {
9463
9714
  if (!processOk(global.process)) {
@@ -9493,7 +9744,7 @@ var require_signal_exit = __commonJS({
9493
9744
  var emit;
9494
9745
  var sigListeners;
9495
9746
  var loaded;
9496
- var load;
9747
+ var load2;
9497
9748
  var originalProcessReallyExit;
9498
9749
  var processReallyExit;
9499
9750
  var originalProcessEmit;
@@ -42998,7 +43249,7 @@ var require_colors = __commonJS({
42998
43249
  }
42999
43250
  return str.replace(matchOperatorsRe, "\\$&");
43000
43251
  };
43001
- function build(_styles) {
43252
+ function build2(_styles) {
43002
43253
  var builder = function builder2() {
43003
43254
  return applyStyle2.apply(builder2, arguments);
43004
43255
  };
@@ -43013,7 +43264,7 @@ var require_colors = __commonJS({
43013
43264
  ansiStyles3[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles3[key].close), "g");
43014
43265
  ret[key] = {
43015
43266
  get: function() {
43016
- return build(this._styles.concat(key));
43267
+ return build2(this._styles.concat(key));
43017
43268
  }
43018
43269
  };
43019
43270
  });
@@ -43072,7 +43323,7 @@ var require_colors = __commonJS({
43072
43323
  Object.keys(styles5).forEach(function(name) {
43073
43324
  ret[name] = {
43074
43325
  get: function() {
43075
- return build([name]);
43326
+ return build2([name]);
43076
43327
  }
43077
43328
  };
43078
43329
  });
@@ -44076,7 +44327,7 @@ var require_core = __commonJS({
44076
44327
  return match && match.index === 0;
44077
44328
  }
44078
44329
  var BACKREF_RE = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./;
44079
- function join13(regexps, separator = "|") {
44330
+ function join14(regexps, separator = "|") {
44080
44331
  let numCaptures = 0;
44081
44332
  return regexps.map((regex2) => {
44082
44333
  numCaptures += 1;
@@ -44380,7 +44631,7 @@ var require_core = __commonJS({
44380
44631
  this.exec = () => null;
44381
44632
  }
44382
44633
  const terminators = this.regexes.map((el) => el[1]);
44383
- this.matcherRe = langRe(join13(terminators), true);
44634
+ this.matcherRe = langRe(join14(terminators), true);
44384
44635
  this.lastIndex = 0;
44385
44636
  }
44386
44637
  /** @param {string} s */
@@ -48157,7 +48408,7 @@ var require_bash = __commonJS({
48157
48408
  return joined;
48158
48409
  }
48159
48410
  function bash(hljs) {
48160
- const VAR = {};
48411
+ const VAR2 = {};
48161
48412
  const BRACED_VAR = {
48162
48413
  begin: /\$\{/,
48163
48414
  end: /\}/,
@@ -48165,12 +48416,12 @@ var require_bash = __commonJS({
48165
48416
  "self",
48166
48417
  {
48167
48418
  begin: /:-/,
48168
- contains: [VAR]
48419
+ contains: [VAR2]
48169
48420
  }
48170
48421
  // default values
48171
48422
  ]
48172
48423
  };
48173
- Object.assign(VAR, {
48424
+ Object.assign(VAR2, {
48174
48425
  className: "variable",
48175
48426
  variants: [
48176
48427
  { begin: concat(
@@ -48206,7 +48457,7 @@ var require_bash = __commonJS({
48206
48457
  end: /"/,
48207
48458
  contains: [
48208
48459
  hljs.BACKSLASH_ESCAPE,
48209
- VAR,
48460
+ VAR2,
48210
48461
  SUBST
48211
48462
  ]
48212
48463
  };
@@ -48226,7 +48477,7 @@ var require_bash = __commonJS({
48226
48477
  contains: [
48227
48478
  { begin: /\d+#[0-9a-f]+/, className: "number" },
48228
48479
  hljs.NUMBER_MODE,
48229
- VAR
48480
+ VAR2
48230
48481
  ]
48231
48482
  };
48232
48483
  const SH_LIKE_SHELLS = [
@@ -48276,7 +48527,7 @@ var require_bash = __commonJS({
48276
48527
  QUOTE_STRING,
48277
48528
  ESCAPED_QUOTE,
48278
48529
  APOS_STRING,
48279
- VAR
48530
+ VAR2
48280
48531
  ]
48281
48532
  };
48282
48533
  }
@@ -67899,7 +68150,7 @@ var require_perl = __commonJS({
67899
68150
  end: /\}/
67900
68151
  // contains defined later
67901
68152
  };
67902
- const VAR = {
68153
+ const VAR2 = {
67903
68154
  variants: [
67904
68155
  {
67905
68156
  begin: /\$\d/
@@ -67921,7 +68172,7 @@ var require_perl = __commonJS({
67921
68172
  const STRING_CONTAINS = [
67922
68173
  hljs.BACKSLASH_ESCAPE,
67923
68174
  SUBST,
67924
- VAR
68175
+ VAR2
67925
68176
  ];
67926
68177
  const REGEX_DELIMS = [
67927
68178
  /!/,
@@ -67956,7 +68207,7 @@ var require_perl = __commonJS({
67956
68207
  );
67957
68208
  };
67958
68209
  const PERL_DEFAULT_CONTAINS = [
67959
- VAR,
68210
+ VAR2,
67960
68211
  hljs.HASH_COMMENT_MODE,
67961
68212
  hljs.COMMENT(
67962
68213
  /^=\w/,
@@ -68423,7 +68674,7 @@ var require_n1ql = __commonJS({
68423
68674
  var require_nginx = __commonJS({
68424
68675
  "../../node_modules/highlight.js/lib/languages/nginx.js"(exports, module) {
68425
68676
  function nginx(hljs) {
68426
- const VAR = {
68677
+ const VAR2 = {
68427
68678
  className: "variable",
68428
68679
  variants: [
68429
68680
  {
@@ -68452,7 +68703,7 @@ var require_nginx = __commonJS({
68452
68703
  className: "string",
68453
68704
  contains: [
68454
68705
  hljs.BACKSLASH_ESCAPE,
68455
- VAR
68706
+ VAR2
68456
68707
  ],
68457
68708
  variants: [
68458
68709
  {
@@ -68471,13 +68722,13 @@ var require_nginx = __commonJS({
68471
68722
  end: "\\s",
68472
68723
  endsWithParent: true,
68473
68724
  excludeEnd: true,
68474
- contains: [VAR]
68725
+ contains: [VAR2]
68475
68726
  },
68476
68727
  {
68477
68728
  className: "regexp",
68478
68729
  contains: [
68479
68730
  hljs.BACKSLASH_ESCAPE,
68480
- VAR
68731
+ VAR2
68481
68732
  ],
68482
68733
  variants: [
68483
68734
  {
@@ -68512,7 +68763,7 @@ var require_nginx = __commonJS({
68512
68763
  begin: "\\b\\d+[kKmMgGdshdwy]*\\b",
68513
68764
  relevance: 0
68514
68765
  },
68515
- VAR
68766
+ VAR2
68516
68767
  ]
68517
68768
  };
68518
68769
  return {
@@ -69978,7 +70229,7 @@ var require_powershell = __commonJS({
69978
70229
  begin: "`[\\s\\S]",
69979
70230
  relevance: 0
69980
70231
  };
69981
- const VAR = {
70232
+ const VAR2 = {
69982
70233
  className: "variable",
69983
70234
  variants: [
69984
70235
  {
@@ -70011,7 +70262,7 @@ var require_powershell = __commonJS({
70011
70262
  ],
70012
70263
  contains: [
70013
70264
  BACKTICK_ESCAPE,
70014
- VAR,
70265
+ VAR2,
70015
70266
  {
70016
70267
  className: "variable",
70017
70268
  begin: /\$[A-z]/,
@@ -70102,7 +70353,7 @@ var require_powershell = __commonJS({
70102
70353
  end: /\)/,
70103
70354
  className: "params",
70104
70355
  relevance: 0,
70105
- contains: [VAR]
70356
+ contains: [VAR2]
70106
70357
  }
70107
70358
  // CMDLETS
70108
70359
  ]
@@ -70173,7 +70424,7 @@ var require_powershell = __commonJS({
70173
70424
  APOS_STRING,
70174
70425
  // PS_NEW_OBJECT_TYPE,
70175
70426
  CMDLETS,
70176
- VAR,
70427
+ VAR2,
70177
70428
  LITERAL,
70178
70429
  HASH_SIGNS
70179
70430
  ];
@@ -70295,7 +70546,7 @@ var require_prolog = __commonJS({
70295
70546
  begin: /[a-z][A-Za-z0-9_]*/,
70296
70547
  relevance: 0
70297
70548
  };
70298
- const VAR = {
70549
+ const VAR2 = {
70299
70550
  className: "symbol",
70300
70551
  variants: [
70301
70552
  {
@@ -70344,7 +70595,7 @@ var require_prolog = __commonJS({
70344
70595
  };
70345
70596
  const inner = [
70346
70597
  ATOM,
70347
- VAR,
70598
+ VAR2,
70348
70599
  PARENTED,
70349
70600
  PRED_OP,
70350
70601
  LIST,
@@ -71889,7 +72140,7 @@ var require_routeros = __commonJS({
71889
72140
  const COMMON_COMMANDS = "add remove enable disable set get print export edit find run debug error info warning";
71890
72141
  const LITERALS = "true false yes no nothing nil null";
71891
72142
  const OBJECTS = "traffic-flow traffic-generator firewall scheduler aaa accounting address-list address align area bandwidth-server bfd bgp bridge client clock community config connection console customer default dhcp-client dhcp-server discovery dns e-mail ethernet filter firmware gps graphing group hardware health hotspot identity igmp-proxy incoming instance interface ip ipsec ipv6 irq l2tp-server lcd ldp logging mac-server mac-winbox mangle manual mirror mme mpls nat nd neighbor network note ntp ospf ospf-v3 ovpn-server page peer pim ping policy pool port ppp pppoe-client pptp-server prefix profile proposal proxy queue radius resource rip ripng route routing screen script security-profiles server service service-port settings shares smb sms sniffer snmp snooper socks sstp-server system tool tracking type upgrade upnp user-manager users user vlan secret vrrp watchdog web-access wireless pptp pppoe lan wan layer7-protocol lease simple raw";
71892
- const VAR = {
72143
+ const VAR2 = {
71893
72144
  className: "variable",
71894
72145
  variants: [
71895
72146
  {
@@ -71906,7 +72157,7 @@ var require_routeros = __commonJS({
71906
72157
  end: /"/,
71907
72158
  contains: [
71908
72159
  hljs.BACKSLASH_ESCAPE,
71909
- VAR,
72160
+ VAR2,
71910
72161
  {
71911
72162
  className: "variable",
71912
72163
  begin: /\$\(/,
@@ -71956,7 +72207,7 @@ var require_routeros = __commonJS({
71956
72207
  hljs.COMMENT("^#", "$"),
71957
72208
  QUOTE_STRING,
71958
72209
  APOS_STRING,
71959
- VAR,
72210
+ VAR2,
71960
72211
  // attribute=value
71961
72212
  {
71962
72213
  // > is to avoid matches with => in other grammars
@@ -71975,7 +72226,7 @@ var require_routeros = __commonJS({
71975
72226
  contains: [
71976
72227
  QUOTE_STRING,
71977
72228
  APOS_STRING,
71978
- VAR,
72229
+ VAR2,
71979
72230
  {
71980
72231
  className: "literal",
71981
72232
  begin: "\\b(" + LITERALS.split(" ").join("|") + ")\\b"
@@ -78257,7 +78508,7 @@ var require_xquery = __commonJS({
78257
78508
  begin: /\bxquery version "[13]\.[01]"\s?(?:encoding ".+")?/,
78258
78509
  end: /;/
78259
78510
  };
78260
- const VAR = {
78511
+ const VAR2 = {
78261
78512
  className: "variable",
78262
78513
  begin: /[$][\w\-:]+/
78263
78514
  };
@@ -78326,7 +78577,7 @@ var require_xquery = __commonJS({
78326
78577
  ]
78327
78578
  };
78328
78579
  const CONTAINS = [
78329
- VAR,
78580
+ VAR2,
78330
78581
  BUILT_IN,
78331
78582
  STRING,
78332
78583
  NUMBER,
@@ -103698,6 +103949,9 @@ var init_local_toolset = __esm({
103698
103949
  import { execFile as execFile3 } from "node:child_process";
103699
103950
  import { readdir as readdir3, readFile as readFile4 } from "node:fs/promises";
103700
103951
  import { join as join7 } from "node:path";
103952
+ function digestLines(md) {
103953
+ return md.split("\n").map((l) => l.trim()).filter((l) => l && !/^#\s/.test(l) && !/^[|\s:-]+$/.test(l) && !/^\|\s*(area|task|priority|date|issue|id)\b/i.test(l)).slice(0, 12);
103954
+ }
103701
103955
  function firstHeading(md) {
103702
103956
  for (const line of md.split("\n", 40)) {
103703
103957
  const m2 = /^#{1,3}\s+(.+?)\s*$/.exec(line);
@@ -103755,10 +104009,19 @@ async function scanProject(cwd2, io = nodeScanIo) {
103755
104009
  const title = text ? firstHeading(text) : null;
103756
104010
  if (title) docs.push({ path: e.path, title });
103757
104011
  }
104012
+ const digests = [];
104013
+ for (const name of DIGEST_FILES.filter((n) => topLevel.some((t) => t.path === n))) {
104014
+ const text = await attempt(`read ${name}`, () => io.readFile(join7(cwd2, name)), null);
104015
+ const lines2 = text ? digestLines(text) : [];
104016
+ if (lines2.length) digests.push({ path: name, lines: lines2 });
104017
+ }
103758
104018
  const todo = await attempt("read TODO.md", () => io.readFile(join7(cwd2, "TODO.md")), null);
103759
104019
  const log = await io.git(cwd2, ["log", "--oneline", "-8"]);
103760
104020
  const status = await io.git(cwd2, ["status", "--porcelain"]);
103761
104021
  const branch = await io.git(cwd2, ["rev-parse", "--abbrev-ref", "HEAD"]);
104022
+ const onBranch = branch?.trim();
104023
+ const vsMainRaw = onBranch && onBranch !== "main" && onBranch !== "HEAD" ? await io.git(cwd2, ["diff", "--shortstat", "main...HEAD"]) : null;
104024
+ const vsMain = vsMainRaw?.trim() ? `${vsMainRaw.trim()} (branch ${onBranch})` : void 0;
103762
104025
  return {
103763
104026
  problems,
103764
104027
  input: {
@@ -103770,17 +104033,20 @@ async function scanProject(cwd2, io = nodeScanIo) {
103770
104033
  childDirs,
103771
104034
  recentCommits: (log ?? "").split("\n").filter(Boolean),
103772
104035
  docs,
103773
- backlog: todo ? backlogLines(todo) : []
104036
+ backlog: todo ? backlogLines(todo) : [],
104037
+ changes: { files: (status ?? "").split("\n").filter(Boolean), ...vsMain ? { vsMain } : {} },
104038
+ digests
103774
104039
  }
103775
104040
  };
103776
104041
  }
103777
- var SKIP, MAX_DOCS, MAX_BACKLOG, nodeScanIo;
104042
+ var SKIP, MAX_DOCS, MAX_BACKLOG, DIGEST_FILES, nodeScanIo;
103778
104043
  var init_project_card_scan = __esm({
103779
104044
  "src/project-card-scan.ts"() {
103780
104045
  "use strict";
103781
104046
  SKIP = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", "build", ".next", ".wrangler", ".turbo", "coverage", ".worktrees", ".superpowers", ".remember"]);
103782
104047
  MAX_DOCS = 15;
103783
104048
  MAX_BACKLOG = 6;
104049
+ DIGEST_FILES = ["CLAUDE.md", "TODO.md", "BUGS.md", "ISSUES.md", "CHANGELOG.md", "README.md"];
103784
104050
  nodeScanIo = {
103785
104051
  async readdir(path12) {
103786
104052
  const entries = await readdir3(path12, { withFileTypes: true });
@@ -103802,25 +104068,27 @@ var init_project_card_scan = __esm({
103802
104068
  });
103803
104069
 
103804
104070
  // src/project-card-runtime.ts
103805
- async function loadProjectCard(cwd2, deps = {}) {
103806
- const env3 = deps.env ?? process.env;
103807
- const now = deps.now ?? Date.now;
103808
- if (env3.SPACECODE_PROJECT_CARD === "off") return { text: "", statusLine: "project card: disabled (SPACECODE_PROJECT_CARD=off)" };
103809
- if (cached && cached.cwd === cwd2 && now() - cached.at < TTL_MS) return cached.state;
103810
- let state;
104071
+ async function build(cwd2, io) {
103811
104072
  try {
103812
- const { input, problems } = await scanProject(cwd2, deps.io ?? nodeScanIo);
104073
+ const { input, problems } = await scanProject(cwd2, io);
103813
104074
  const text = renderProjectCard(input);
103814
- state = {
104075
+ return {
103815
104076
  text,
103816
104077
  statusLine: `project card: ${text.length} chars, ${input.topLevel.length} top-level entries${problems.length ? `, ${problems.length} scan problem(s): ${problems.join("; ")}` : ""}`
103817
104078
  };
103818
104079
  } catch (err) {
103819
104080
  console.error("[project-card] scan failed; the REPL runs without a card", err instanceof Error ? err.message : String(err));
103820
- state = { text: "", statusLine: `project card: not built (${err instanceof Error ? err.message : String(err)})` };
104081
+ return { text: "", statusLine: `project card: not built (${err instanceof Error ? err.message : String(err)})` };
103821
104082
  }
103822
- cached = { cwd: cwd2, at: now(), state };
103823
- return state;
104083
+ }
104084
+ function loadProjectCard(cwd2, deps = {}) {
104085
+ const env3 = deps.env ?? process.env;
104086
+ const now = deps.now ?? Date.now;
104087
+ if (env3.SPACECODE_PROJECT_CARD === "off") return Promise.resolve({ text: "", statusLine: "project card: disabled (SPACECODE_PROJECT_CARD=off)" });
104088
+ if (cached && cached.cwd === cwd2 && now() - cached.at < TTL_MS) return cached.pending;
104089
+ const pending = build(cwd2, deps.io ?? nodeScanIo);
104090
+ cached = { cwd: cwd2, at: now(), pending };
104091
+ return pending;
103824
104092
  }
103825
104093
  var TTL_MS, cached;
103826
104094
  var init_project_card_runtime = __esm({
@@ -103832,6 +104100,65 @@ var init_project_card_runtime = __esm({
103832
104100
  }
103833
104101
  });
103834
104102
 
104103
+ // src/prompt-runtime.ts
104104
+ import { join as join8 } from "node:path";
104105
+ async function load(deps) {
104106
+ if (deps.env.SPACECODE_PROMPTS === "off") return { templates: {}, statusLine: "prompts: built-in defaults (SPACECODE_PROMPTS=off)" };
104107
+ const dir = deps.env.SPACECODE_CONFIG_DIR?.trim() || join8(deps.homedir(), ".spacecode");
104108
+ const file = join8(dir, "prompts-cache.json");
104109
+ const fromDisk = async (why) => {
104110
+ try {
104111
+ const raw = JSON.parse(await deps.readFile(file));
104112
+ const templates = raw.templates ?? {};
104113
+ return { templates, statusLine: `prompts: ${Object.keys(templates).length} tuned (STALE: served from cache; ${why})` };
104114
+ } catch {
104115
+ return { templates: {}, statusLine: `prompts: built-in defaults (${why})` };
104116
+ }
104117
+ };
104118
+ let apiKey;
104119
+ try {
104120
+ apiKey = await resolveApiKey({ env: deps.env, readFile: deps.readFile, homedir: deps.homedir });
104121
+ } catch (err) {
104122
+ return fromDisk(err instanceof Error ? err.message : String(err));
104123
+ }
104124
+ if (!apiKey) return fromDisk("not signed in");
104125
+ try {
104126
+ const res = await deps.fetch(`${backendUrl(deps.env)}/api/prompts`, {
104127
+ headers: { Authorization: `Bearer ${apiKey}`, "x-api-key": apiKey },
104128
+ signal: AbortSignal.timeout(3e3)
104129
+ });
104130
+ if (!res.ok) return fromDisk(`prompt API returned ${res.status}`);
104131
+ const { prompts } = await res.json();
104132
+ const templates = Object.fromEntries(prompts.filter((p) => p.source === "db").map((p) => [p.key, p.body]));
104133
+ try {
104134
+ await deps.mkdir(dir);
104135
+ await deps.writeFile(file, JSON.stringify({ fetchedAt: new Date((deps.now ?? Date.now)()).toISOString(), templates }));
104136
+ } catch (err) {
104137
+ console.error("[prompts] could not write the prompt cache", err instanceof Error ? err.message : String(err));
104138
+ }
104139
+ const n = Object.keys(templates).length;
104140
+ return { templates, statusLine: n ? `prompts: ${n} tuned override(s): ${Object.keys(templates).join(", ")}` : "prompts: built-in defaults (none tuned)" };
104141
+ } catch (err) {
104142
+ return fromDisk(`fetch failed: ${err instanceof Error ? err.message : String(err)}`);
104143
+ }
104144
+ }
104145
+ function loadPromptTemplates(deps) {
104146
+ const now = (deps.now ?? Date.now)();
104147
+ if (cached2 && now - cached2.at < TTL_MS2) return cached2.pending;
104148
+ const pending = load(deps);
104149
+ cached2 = { at: now, pending };
104150
+ return pending;
104151
+ }
104152
+ var TTL_MS2, cached2;
104153
+ var init_prompt_runtime = __esm({
104154
+ "src/prompt-runtime.ts"() {
104155
+ "use strict";
104156
+ init_api_key();
104157
+ init_profile_runtime();
104158
+ TTL_MS2 = 10 * 6e4;
104159
+ }
104160
+ });
104161
+
103835
104162
  // src/shell-turn.ts
103836
104163
  function shellCommandOf(prompt) {
103837
104164
  return prompt.startsWith("!") ? prompt.slice(1).trim() : null;
@@ -104256,7 +104583,7 @@ var init_recording_chunks = __esm({
104256
104583
  // src/recording/recorder.ts
104257
104584
  import { createHash as createHash4 } from "node:crypto";
104258
104585
  import { mkdir as mkdir4, readFile as readFile6, rename as rename4, rm as rm2, writeFile as writeFile4, readdir as readdir5, stat as stat3 } from "node:fs/promises";
104259
- import { join as join8 } from "node:path";
104586
+ import { join as join9 } from "node:path";
104260
104587
  function projectHash(root) {
104261
104588
  return createHash4("sha256").update(root).digest("hex");
104262
104589
  }
@@ -104267,24 +104594,24 @@ function createRecorder(deps) {
104267
104594
  let inflight = null;
104268
104595
  let retryAttempt = 0;
104269
104596
  let timerArmed = false;
104270
- const stateFile = (sessionId) => join8(deps.dir, `${sessionId}.json`);
104597
+ const stateFile = (sessionId) => join9(deps.dir, `${sessionId}.json`);
104271
104598
  async function save(sessionId, st) {
104272
104599
  await mkdir4(deps.dir, { recursive: true, mode: 448 });
104273
104600
  const tmp = `${stateFile(sessionId)}.${deps.randomId()}.tmp`;
104274
104601
  await writeFile4(tmp, JSON.stringify(st), { mode: 384 });
104275
104602
  await rename4(tmp, stateFile(sessionId));
104276
104603
  }
104277
- async function load(sessionId) {
104278
- const cached2 = states.get(sessionId);
104279
- if (cached2) return cached2;
104604
+ async function load2(sessionId) {
104605
+ const cached3 = states.get(sessionId);
104606
+ if (cached3) return cached3;
104280
104607
  let st;
104281
104608
  try {
104282
104609
  st = JSON.parse(await readFile6(stateFile(sessionId), "utf8"));
104283
104610
  } catch (e) {
104284
104611
  if (e.code !== "ENOENT") {
104285
- const bad = join8(deps.dir, "bad");
104612
+ const bad = join9(deps.dir, "bad");
104286
104613
  await mkdir4(bad, { recursive: true, mode: 448 });
104287
- await rename4(stateFile(sessionId), join8(bad, `${sessionId}.${deps.now()}.json`)).catch((err) => deps.log(`recording: could not quarantine ${sessionId}: ${String(err)}`));
104614
+ await rename4(stateFile(sessionId), join9(bad, `${sessionId}.${deps.now()}.json`)).catch((err) => deps.log(`recording: could not quarantine ${sessionId}: ${String(err)}`));
104288
104615
  lastError = `recording state for ${sessionId} was unreadable and was quarantined`;
104289
104616
  deps.log(`recording: ${lastError}`);
104290
104617
  }
@@ -104301,7 +104628,7 @@ function createRecorder(deps) {
104301
104628
  async function record(sessionId, messages, meta) {
104302
104629
  try {
104303
104630
  if (off2) return;
104304
- const st = await load(sessionId);
104631
+ const st = await load2(sessionId);
104305
104632
  if (st.stopped) return;
104306
104633
  if (st.hashes.length === 0 && st.nextSeq === 1 && meta.parentRecordingId) st.parentRecordingId = meta.parentRecordingId;
104307
104634
  st.meta = meta;
@@ -104454,7 +104781,7 @@ function createRecorder(deps) {
104454
104781
  for (const n of names) {
104455
104782
  if (!n.endsWith(".json")) continue;
104456
104783
  const id = n.slice(0, -5);
104457
- const st = await load(id);
104784
+ const st = await load2(id);
104458
104785
  if (st.queue.length === 0) states.delete(id);
104459
104786
  }
104460
104787
  if ([...states.values()].some((s) => s.queue.length > 0)) void flush();
@@ -104475,7 +104802,7 @@ var init_recorder = __esm({
104475
104802
 
104476
104803
  // src/recording/recording-runtime.ts
104477
104804
  import { mkdir as mkdir5, readFile as readFile7, writeFile as writeFile5, stat as stat4 } from "node:fs/promises";
104478
- import { join as join9, sep as sep2 } from "node:path";
104805
+ import { join as join10, sep as sep2 } from "node:path";
104479
104806
  function matchesExclude(pattern, root) {
104480
104807
  const re3 = pattern.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "\0").replace(/\*/g, `[^${sep2 === "\\" ? "\\\\" : "/"}]*`).replace(/\u0000/g, ".*");
104481
104808
  return new RegExp(`^${re3}(?:[/\\\\].*)?$`).test(root);
@@ -104490,8 +104817,8 @@ async function exists(path12) {
104490
104817
  }
104491
104818
  }
104492
104819
  function createRecordingRuntime(deps) {
104493
- const dir = join9(deps.configDir, "recordings");
104494
- const noticeFile = join9(dir, ".notice");
104820
+ const dir = join10(deps.configDir, "recordings");
104821
+ const noticeFile = join10(dir, ".notice");
104495
104822
  let recorder = null;
104496
104823
  let standingOff;
104497
104824
  let sessionOff = false;
@@ -104499,7 +104826,7 @@ function createRecordingRuntime(deps) {
104499
104826
  if (deps.env.SPACECODE_RECORD?.trim().toLowerCase() === "off") return "SPACECODE_RECORD=off";
104500
104827
  let cfg = {};
104501
104828
  try {
104502
- cfg = JSON.parse(await readFile7(join9(deps.configDir, "config.json"), "utf8"));
104829
+ cfg = JSON.parse(await readFile7(join10(deps.configDir, "config.json"), "utf8"));
104503
104830
  } catch (e) {
104504
104831
  if (e.code !== "ENOENT") return `~/.spacecode/config.json is unreadable (${e instanceof Error ? e.message : String(e)}); fix it to record`;
104505
104832
  }
@@ -104507,7 +104834,7 @@ function createRecordingRuntime(deps) {
104507
104834
  const excludes = Array.isArray(cfg.recording?.exclude) ? cfg.recording.exclude.filter((x2) => typeof x2 === "string") : [];
104508
104835
  const hit = excludes.find((p) => matchesExclude(p, deps.projectRoot));
104509
104836
  if (hit) return `this folder matches recording.exclude (${hit})`;
104510
- if (await exists(join9(deps.projectRoot, ".spacecode", "norecord"))) return "this repo has .spacecode/norecord";
104837
+ if (await exists(join10(deps.projectRoot, ".spacecode", "norecord"))) return "this repo has .spacecode/norecord";
104511
104838
  return null;
104512
104839
  }
104513
104840
  async function noticeShown() {
@@ -104649,7 +104976,7 @@ var init_skills_command = __esm({
104649
104976
 
104650
104977
  // src/permission-rules-loader.ts
104651
104978
  import { readFile as readFile8 } from "node:fs/promises";
104652
- import { join as join10 } from "node:path";
104979
+ import { join as join11 } from "node:path";
104653
104980
  async function readOrNull2(path12) {
104654
104981
  try {
104655
104982
  return await readFile8(path12, "utf8");
@@ -104660,7 +104987,7 @@ async function readOrNull2(path12) {
104660
104987
  }
104661
104988
  async function loadPermissionRules(deps) {
104662
104989
  const none = (line) => ({ rules: void 0, defaultMode: void 0, statusLines: [line] });
104663
- const configPath = join10(deps.configDir, "imports.json");
104990
+ const configPath = join11(deps.configDir, "imports.json");
104664
104991
  try {
104665
104992
  const loaded = parseImportsConfig(await readOrNull2(configPath), configPath);
104666
104993
  if (loaded.error) return none("permission rules: off (imports.json unreadable)");
@@ -104669,13 +104996,13 @@ async function loadPermissionRules(deps) {
104669
104996
  return none(`permission rules: off (imports.json not read: ${err instanceof Error ? err.message : String(err)})`);
104670
104997
  }
104671
104998
  const lines2 = [];
104672
- const files = [{ scope: "user", path: join10(deps.homeDir, ".claude", "settings.json") }];
104999
+ const files = [{ scope: "user", path: join11(deps.homeDir, ".claude", "settings.json") }];
104673
105000
  try {
104674
105001
  const sources2 = await readRepoSources(deps.cwd);
104675
105002
  if (sources2[".claude/settings.json"] !== void 0 || sources2[".claude/settings.local.json"] !== void 0) {
104676
105003
  const status = await createNodeTrustStore(deps.configDir).repoStatus(deps.cwd, sources2);
104677
105004
  if (status === "trusted" || status === "session") {
104678
- files.push({ scope: "repo", path: join10(deps.cwd, ".claude", "settings.json") }, { scope: "local", path: join10(deps.cwd, ".claude", "settings.local.json") });
105005
+ files.push({ scope: "repo", path: join11(deps.cwd, ".claude", "settings.json") }, { scope: "local", path: join11(deps.cwd, ".claude", "settings.local.json") });
104679
105006
  } else lines2.push(`permission rules: repo settings NOT loaded (${status === "never" ? "marked never trust" : "untrusted repo"})`);
104680
105007
  }
104681
105008
  } catch (err) {
@@ -104717,7 +105044,7 @@ var init_permission_rules_loader = __esm({
104717
105044
  });
104718
105045
 
104719
105046
  // src/command-loader.ts
104720
- import { join as join11 } from "node:path";
105047
+ import { join as join12 } from "node:path";
104721
105048
  async function userCommands(deps, lines2) {
104722
105049
  try {
104723
105050
  const apiKey = await resolveApiKey({ env: deps.env, readFile: deps.readFile, homedir: deps.homedir });
@@ -104772,7 +105099,7 @@ async function repoCommands(deps, lines2) {
104772
105099
  }
104773
105100
  }
104774
105101
  async function loadCommands(deps) {
104775
- const configPath = join11(deps.configDir, "imports.json");
105102
+ const configPath = join12(deps.configDir, "imports.json");
104776
105103
  try {
104777
105104
  const loaded = parseImportsConfig(await deps.readFile(configPath).catch((err) => {
104778
105105
  if (err.code === "ENOENT") return null;
@@ -104807,7 +105134,7 @@ var init_command_loader = __esm({
104807
105134
  // src/useReplController.ts
104808
105135
  import { readFile as readFile9, writeFile as writeFile6, mkdir as mkdir6 } from "node:fs/promises";
104809
105136
  import { homedir as homedir2 } from "node:os";
104810
- import { join as join12 } from "node:path";
105137
+ import { join as join13 } from "node:path";
104811
105138
  import { execFile as execFile4 } from "node:child_process";
104812
105139
  function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mentionsOpt, sessions, resumeLatest: resumeLatest2, onViewTurns, onViewResume, isEscOwned }) {
104813
105140
  const { exit } = use_app_default();
@@ -104829,6 +105156,14 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
104829
105156
  return () => clearInterval(timer);
104830
105157
  }, [turnStartedAt]);
104831
105158
  const projectRoot = (0, import_react30.useMemo)(() => process.cwd(), []);
105159
+ const promptTemplates = (0, import_react30.useCallback)(
105160
+ () => loadPromptTemplates({ env: process.env, readFile: (p) => readFile9(p, "utf-8"), writeFile: (p, d) => writeFile6(p, d, "utf-8"), mkdir: (p) => mkdir6(p, { recursive: true }).then(() => void 0), homedir: homedir2, fetch }),
105161
+ []
105162
+ );
105163
+ (0, import_react30.useEffect)(() => {
105164
+ void loadProjectCard(projectRoot);
105165
+ void promptTemplates();
105166
+ }, [projectRoot]);
104832
105167
  const sessionIdRef = (0, import_react30.useRef)(null);
104833
105168
  const sessionCreatedAtRef = (0, import_react30.useRef)(null);
104834
105169
  const sessionTokensRef = (0, import_react30.useRef)({ input: 0, output: 0 });
@@ -104838,7 +105173,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
104838
105173
  const recordingRef = (0, import_react30.useRef)(null);
104839
105174
  const recording = () => recordingRef.current ??= createRecordingRuntime({
104840
105175
  env: process.env,
104841
- configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join12(homedir2(), ".spacecode"),
105176
+ configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join13(homedir2(), ".spacecode"),
104842
105177
  projectRoot,
104843
105178
  fetch,
104844
105179
  baseUrl: backendUrl(process.env),
@@ -104865,7 +105200,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
104865
105200
  (0, import_react30.useEffect)(() => {
104866
105201
  let live2 = true;
104867
105202
  loadCommands({
104868
- configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join12(homedir2(), ".spacecode"),
105203
+ configDir: process.env.SPACECODE_CONFIG_DIR?.trim() || join13(homedir2(), ".spacecode"),
104869
105204
  cwd: process.cwd(),
104870
105205
  env: process.env,
104871
105206
  readFile: (p) => readFile9(p, "utf-8"),
@@ -105162,7 +105497,7 @@ function useReplController({ repoName: repoName2, yolo: yolo2, mentions: mention
105162
105497
  return;
105163
105498
  }
105164
105499
  notice("Compacting conversation\u2026");
105165
- const summary = await summarizeConversation(history, { apiUrl: apiUrl(), apiKey: apiKey2 });
105500
+ const summary = await summarizeConversation(history, { apiUrl: apiUrl(), apiKey: apiKey2, templates: (await promptTemplates()).templates });
105166
105501
  const compacted = buildCompactedHistory(summary.summaryText);
105167
105502
  const before = estimateTotalTokens("", history);
105168
105503
  const after = estimateTotalTokens("", compacted);
@@ -105282,7 +105617,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
105282
105617
  cwd: process.cwd(),
105283
105618
  gitRoot: gitTopLevel,
105284
105619
  importGate: async () => {
105285
- const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join12(homedir2(), ".spacecode");
105620
+ const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join13(homedir2(), ".spacecode");
105286
105621
  const gate = await importGateStatus({ configDir, cwd: process.cwd(), interactive: Boolean(process.stdin.isTTY) });
105287
105622
  const loaded = await loadPermissionRules({ configDir, homeDir: homedir2(), cwd: process.cwd() });
105288
105623
  rulesRef.current = loaded.rules;
@@ -105292,7 +105627,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
105292
105627
  });
105293
105628
  profileRef.current = await profilePromiseRef.current;
105294
105629
  if (mcpRef.current === null && (profileRef.current?.mcpDefs?.length ?? 0) > 0) {
105295
- const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join12(homedir2(), ".spacecode");
105630
+ const configDir = process.env.SPACECODE_CONFIG_DIR?.trim() || join13(homedir2(), ".spacecode");
105296
105631
  const trust = createNodeTrustStore(configDir);
105297
105632
  const manager = new McpManager({
105298
105633
  defs: profileRef.current.mcpDefs,
@@ -105302,7 +105637,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
105302
105637
  ...rulesRef.current ? { rules: rulesRef.current } : {},
105303
105638
  permissions,
105304
105639
  confirm: askUser,
105305
- audit: new FileAuditLog(join12(homedir2(), ".spacecode", "audit.jsonl")),
105640
+ audit: new FileAuditLog(join13(homedir2(), ".spacecode", "audit.jsonl")),
105306
105641
  clientVersion: process.env.SPACECODE_VERSION ?? "0"
105307
105642
  });
105308
105643
  mcpRef.current = manager;
@@ -105311,6 +105646,7 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
105311
105646
  const toolset = createToolset();
105312
105647
  toolsetRef.current = toolset;
105313
105648
  const projectCard = await loadProjectCard(projectRoot);
105649
+ const prompts = await promptTemplates();
105314
105650
  const config = {
105315
105651
  apiUrl: apiUrl(),
105316
105652
  apiKey,
@@ -105320,7 +105656,8 @@ ${list2.map((c) => ` /${c.name} (${c.scope})${c.description ? ` ${c.descripti
105320
105656
  localCwd: projectRoot,
105321
105657
  commitGuidance: true,
105322
105658
  hasGitHubTools: toolset.hasGitHubTools,
105323
- ...projectCard.text ? { projectCard: projectCard.text } : {}
105659
+ ...projectCard.text ? { projectCard: projectCard.text } : {},
105660
+ templates: prompts.templates
105324
105661
  }) + (profileRef.current?.promptSection ?? ""),
105325
105662
  tools: toolset.tools,
105326
105663
  maxTurns: 100,
@@ -105432,6 +105769,7 @@ var init_useReplController = __esm({
105432
105769
  init_session_commands();
105433
105770
  init_local_toolset();
105434
105771
  init_project_card_runtime();
105772
+ init_prompt_runtime();
105435
105773
  init_shell_turn();
105436
105774
  init_node_mentions();
105437
105775
  init_api_key();