From c4fc7fde0144720b7e5ea95b3e4bbf95e4a61aa1 Mon Sep 17 00:00:00 2001 From: Markus Fritsche Date: Sat, 16 May 2026 22:08:54 +0000 Subject: [PATCH] context: [project] block plumbing (Phase 6 commit #1) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Foundation for Phase 6 — adds the field + composer + composition order with no callers yet. Nothing sets ctx.project; the meta hookup and startup auto-inject land in commit #2. Changes: - Context.new gains `project` (string, nil) and `_project_opts` (cached scan opts for `:tree refresh`; R7). - compose_project(text) helper mirrors compose_background / compose_summary. Returns "" for nil/empty; otherwise emits "\n\n[project]\n" + text. - to_messages inserts compose_project BETWEEN compose_background and compose_summary so the model reads memory facts -> project tree -> earlier conversation -> NORRIS suffix. - Same Norris-suppression guard as the other two dynamic blocks (R-C1 / R-C4 parity; planner stays on goal anchor). - Context:reset preserves ctx.project (R8 — matches the Phase 4 memory_items rule; startup-injected facts survive a user-driven context reset). Smoke verified (14/14 inline cases): - project nil -> no [project] block in sys_content - project set -> block present with contents - ordering: [background] < [project] < [earlier conversation summary] - norris_active suppresses all three; NORRIS suffix still appears - :reset clears turns/pending_exec_output/summary; preserves memory_items AND project Regression: test_safety 87/87, test_router_model 31/31, repl loads. Co-Authored-By: Claude Opus 4.7 (1M context) --- context.lua | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/context.lua b/context.lua index 2c14e91..391d413 100644 --- a/context.lua +++ b/context.lua @@ -56,6 +56,12 @@ function M.new(opts) summarize_fn = opts.summarize_fn, summary = nil, -- rolling summary string max_summary_chars = opts.max_summary_chars or 2000, + -- Phase 6 (#issue Phase 6 §6): project file-tree block, set by + -- repl.lua via :tree meta or the cfg.project.auto_tree startup + -- hook. nil = no block injected. Cached scan opts (depth / + -- max_chars overrides) live on _project_opts for :tree refresh. + project = nil, + _project_opts = nil, }, Context) end @@ -171,6 +177,15 @@ local function compose_summary(summary_text) return "\n\n[earlier conversation summary]\n" .. summary_text end +-- Phase 6: project file-tree composer. Inserted between [background] +-- and [earlier summary] so the reading order is memory facts → +-- project tree → earlier conversation → NORRIS suffix. Same Norris- +-- suppression rule (callers gate via self.norris_active). +local function compose_project(project_text) + if not project_text or project_text == "" then return "" end + return "\n\n[project]\n" .. project_text +end + -- Phase 3: NORRIS MODE suffix appended to the system prompt when -- self.norris_active. Carries self.norris_goal so eviction of the -- user's "[norris] goal: ..." turn doesn't lose the anchor. @@ -200,12 +215,13 @@ message if they declined.]] function Context:to_messages() local sys_content = self.system_prompt - -- Phase 4 [background] memory block + Phase 5 [earlier summary] - -- block. Both suppressed during Norris (R-C1 / R-C4 — avoid - -- redundant tokens per planning iteration; planner stays focused - -- on its goal anchor). + -- Phase 4 [background] memory block + Phase 6 [project] file-tree + -- block + Phase 5 [earlier summary] block. All suppressed during + -- Norris (R-C1 / R-C4 — avoid redundant tokens per planning + -- iteration; planner stays focused on its goal anchor). if not self.norris_active then sys_content = sys_content .. compose_background(self.memory_items) + sys_content = sys_content .. compose_project(self.project) sys_content = sys_content .. compose_summary(self.summary) end -- Phase 3 NORRIS MODE suffix. Last block so its instructions dominate.