Skip to content

feat(cli): inject system prompt, AGENTS.md, and environment into TUI - #531

Merged
Astro-Han merged 6 commits into
mainfrom
feat/cli-system-prompt
Jul 5, 2026
Merged

feat(cli): inject system prompt, AGENTS.md, and environment into TUI#531
Astro-Han merged 6 commits into
mainfrom
feat/cli-system-prompt

Conversation

@Astro-Han

@Astro-Han Astro-Han commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Summary

  • Sink the read-only system-prompt fragment builders (workspace-instructions / AGENTS.md, personalization, project-context, session-environment) from apps/desktop/src/main into packages/runtime/src/system-prompt, shared by the desktop app and the CLI/TUI.
  • Inject a systemPrompt (personalization + gated workspace instructions) and a per-turn turnTailPrompt (cwd/git/platform/date) into the CLI/TUI's AiSdkBackend. Previously the TUI agent ran with no system message at all.

Why

The CLI created its AiSdkBackend without a systemPrompt, so the agent had no AGENTS.md project instructions, no personalization, and no session environment — it ran "naked". Desktop already built these fragments in apps/desktop/src/main, but packages/cli cannot import from apps/desktop, so the logic had to be shared at the runtime layer.

Scope

4 commits, each independently revertible (dependency chain below):

  1. refactor(runtime,desktop): sink workspace-instructions prompt builder — runtime file + index export + desktop workspace-instructions.ts (re-export read-only builder + keep open/create management surface)
  2. refactor(runtime,desktop): sink personalization prompt builder — runtime file + index export + delete desktop personalization-prompt.ts + system-prompt-main/settings-ipc-helpers/tests import from @maka/runtime
  3. refactor(runtime,desktop): sink project-context and session-environment builders — sunk together (session-environment imports ProjectGitInfo from project-context, must move together) + system-prompt-main/main/2 tests import changed
  4. feat(cli): inject system prompt and per-turn environment into TUI backendcli-system-prompt.ts + tests + runtime-bootstrap wiring

Dependencies and revert:

  • feat (4) depends on all three refactor commits (imports the runtime fragments). Reverting any refactor must revert feat too.
  • Reverting feat alone is safe (fragment sinking stays, desktop unaffected).
  • Commits 1 and 2 are independent of each other; commit 3 binds project-context + session-environment together.

Not included (intentional, follow-up PRs):

  • skills catalog + Skill tool for the CLI (needs buildSkillAgentTool wiring; another contributor is working on the skills module)
  • LocalMemoryService for the CLI (stateful, desktop-only; sunk as an injected fragment in a later PR)
  • shared aggregate buildSystemPrompt (after skills + memory are also sunk, desktop/CLI share one assembler)

Verification

  • TDD: cli-system-prompt.test.ts written first (RED: stub threw not implemented), then implemented (GREEN: 6 tests pass). Covers the settings gate (enabled/disabled), AGENTS.md injection, personalization, the empty case, joining, and the turn-tail environment.
  • Per-commit: commits 1/2 runtime build green; commit 3 runtime + desktop build:main green; commit 4 cli test 74 pass.
  • Full suite: desktop test 1892 pass, runtime test 812 pass, cli test 74 pass, full typecheck green.
  • Gap: commits 1/2 did not run desktop build:main (the working-tree main.ts is in final state and would falsely break, but the staged state is self-consistent); commit 3's desktop build:main green covers the final state. Per-commit empirical verification can be backfilled with git stash --keep-index if wanted.

User-facing impact

The TUI agent now receives the cwd's AGENTS.md/CLAUDE.md/GEMINI.md (gated by settings.workspaceInstructions.enabled), the user's personalization preferences, and a per-turn environment block (cwd/git/branch/platform/date). No desktop behavior change. Skills and local memory are not yet available in the TUI (follow-up PRs).

Reviewer notes

  • The settings gate is preserved: the CLI reads the same workspace settings.json, so disabling workspaceInstructions in desktop also disables it in the TUI.
  • session-environment stays in turnTailPrompt (per-turn), not in the durable system prompt, matching desktop, to avoid churning the system-prefix hash when date/branch change.
  • @maka/runtime does not import @maka/storage: settings are read by the CLI caller and injected into buildCliSystemPrompt (per layering review).
  • workspace-instructions sinking moves only the read-only builder; desktop keeps the open/create management surface (workspace-instructions.ts re-exports read-only + keeps management).

Astro-Han added 6 commits July 5, 2026 03:13
Move the read-only workspace-instructions scan + prompt builder into packages/runtime/src/system-prompt so the CLI/TUI can reuse it. Desktop keeps the workspace-instruction file-management surface (open/create/template) and re-exports the read-only builders; behavior unchanged.
Move the personalization prompt fragment (display name + assistant tone sanitizer + warning collector) into packages/runtime/src/system-prompt. Desktop imports it from @maka/runtime; behavior unchanged.
…nt builders

Move resolveProjectGitInfo/resolveProjectRoot and the per-turn session-environment prompt builder into packages/runtime/src/system-prompt. They go together because session-environment imports the ProjectGitInfo type from project-context. Desktop imports them from @maka/runtime; behavior unchanged.
…kend

The CLI AiSdkBackend was created without systemPrompt, so the TUI agent ran with no system message, no AGENTS.md project instructions, and no session environment. Wire buildCliSystemPrompt (personalization + gated workspace instructions) as the durable system prompt and buildCliTurnTailPrompt (cwd/git/platform/date) as the per-turn tail. Skills and local memory are intentionally out of scope here. Driven by cli-system-prompt.test.ts (RED -> GREEN).
…ructions

isInside() only checked for `..` escape, but path.relative returns the target unchanged (absolute) when root and target are on different Windows drives, so a symlink/junction to another drive's file was treated as inside the workspace and could leak its content into the system prompt. Extract isPathInside(root, target, pathApi) which rejects an absolute relative() result (cross-drive / different root) before the `..` check; the runtime scan and the desktop open/create management surface both use it. Driven by workspace-instructions.test.ts (RED -> GREEN) covering cross-drive, same-drive-outside, parent escape, and under-root cases.
…apes in isPathInside

isPathInside rejected any relative() result starting with "..", but "..rules" (a legitimate directory name starting with two dots) is not a parent reference — it was wrongly blocked, so an internal symlink pointing to .../..rules/AGENTS.md would not be injected. Narrow the check to reject only the exact ".." segment or a path starting with `..${sep}`, and drop the redundant startsWith("..") / includes() combination (path.relative normalizes intermediate ".." away, so the includes() check was dead). Driven by a RED -> GREEN test for POSIX /repo/..rules/AGENTS.md and Windows C:\repo\..rules\AGENTS.md.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant