Step 385: add context bundle format

This commit is contained in:
Bill
2026-02-16 12:54:39 -07:00
parent 667cf3a7ec
commit 1f15e82401
4 changed files with 408 additions and 0 deletions

View File

@@ -2344,4 +2344,13 @@ target_link_libraries(step384_test PRIVATE
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
add_executable(step385_test tests/step385_test.cpp)
target_include_directories(step385_test PRIVATE src)
target_link_libraries(step385_test PRIVATE
nlohmann_json::nlohmann_json
unofficial::tree-sitter::tree-sitter
tree_sitter_python tree_sitter_cpp tree_sitter_elisp
tree_sitter_javascript tree_sitter_typescript
tree_sitter_java tree_sitter_rust tree_sitter_go)
# Step 12: Dear ImGui shell scaffolding created (main.cpp exists but not built due to dependencies)

168
editor/src/ContextBundle.h Normal file
View File

@@ -0,0 +1,168 @@
#pragma once
// Step 385: Standard context bundle format for external model invocation.
#include "RoutingEngine.h"
#include "WorkerRegistry.h"
#include <sstream>
#include <string>
#include <vector>
struct AttemptSummary {
std::string workerType;
std::string generatedCode;
std::string feedback;
json toJson() const {
return {
{"workerType", workerType},
{"generatedCode", generatedCode},
{"feedback", feedback}
};
}
static AttemptSummary fromJson(const json& j) {
AttemptSummary a;
a.workerType = j.value("workerType", "");
a.generatedCode = j.value("generatedCode", "");
a.feedback = j.value("feedback", "");
return a;
}
};
struct ContextBundle {
std::string taskDescription;
std::string skeletonCode;
std::string intent;
std::vector<std::string> constraints;
std::string contextCode;
std::string projectSummary;
std::string existingTests;
std::vector<AttemptSummary> previousAttempts;
int tokenBudget = 0;
std::string outputFormat; // "code-only" | "code-with-explanation"
json toJson() const {
json attempts = json::array();
for (const auto& a : previousAttempts) attempts.push_back(a.toJson());
return {
{"taskDescription", taskDescription},
{"skeletonCode", skeletonCode},
{"intent", intent},
{"constraints", constraints},
{"contextCode", contextCode},
{"projectSummary", projectSummary},
{"existingTests", existingTests},
{"previousAttempts", attempts},
{"tokenBudget", tokenBudget},
{"outputFormat", outputFormat}
};
}
static ContextBundle fromJson(const json& j) {
ContextBundle b;
b.taskDescription = j.value("taskDescription", "");
b.skeletonCode = j.value("skeletonCode", "");
b.intent = j.value("intent", "");
if (j.contains("constraints") && j["constraints"].is_array()) {
b.constraints = j["constraints"].get<std::vector<std::string>>();
}
b.contextCode = j.value("contextCode", "");
b.projectSummary = j.value("projectSummary", "");
b.existingTests = j.value("existingTests", "");
if (j.contains("previousAttempts") && j["previousAttempts"].is_array()) {
for (const auto& a : j["previousAttempts"]) {
b.previousAttempts.push_back(AttemptSummary::fromJson(a));
}
}
b.tokenBudget = j.value("tokenBudget", 0);
b.outputFormat = j.value("outputFormat", "code-with-explanation");
return b;
}
};
inline std::vector<std::string> collectBundleConstraints(const WorkerContext& context,
const WorkItem& item) {
std::vector<std::string> out;
for (const auto& anno : context.annotations) {
std::string type = anno.value("type", "");
std::string name = anno.value("name", "");
std::string value = anno.value("value", "");
if (!type.empty() || !name.empty() || !value.empty()) {
std::string line = type;
if (!name.empty()) line += ":" + name;
if (!value.empty()) line += "=" + value;
out.push_back(line);
}
}
if (item.reviewRequired) out.push_back("requires-review");
if (out.empty()) out.push_back("none");
return out;
}
inline ContextBundle buildBundle(const WorkItem& item,
const WorkerContext& context,
const RoutingDecision& routingDecision) {
ContextBundle bundle;
bundle.taskDescription = "Implement " + item.nodeType + " '" + item.nodeName + "'";
bundle.skeletonCode = context.nodeAst.is_null() ? "" : context.nodeAst.dump(2);
bundle.intent = context.skeletonIntent;
if (bundle.intent.empty() && !item.rejectionFeedback.empty()) {
bundle.intent = item.rejectionFeedback;
}
bundle.constraints = collectBundleConstraints(context, item);
bundle.contextCode = context.bufferContent;
bundle.projectSummary = context.projectSummary.is_null() ? "" :
context.projectSummary.dump(2);
bundle.existingTests = "";
for (const auto& attempt : item.rejectionHistory) {
bundle.previousAttempts.push_back({
attempt.workerType,
attempt.result.generatedCode,
attempt.feedback
});
}
bundle.tokenBudget = routingDecision.contextBudgetTokens > 0
? routingDecision.contextBudgetTokens
: estimateContextBudget(routingDecision.contextWidth);
bundle.outputFormat = (routingDecision.workerType == "deterministic" ||
routingDecision.workerType == "template")
? "code-only"
: "code-with-explanation";
return bundle;
}
inline std::string bundleToPrompt(const ContextBundle& bundle) {
std::ostringstream oss;
oss << "Task:\n" << bundle.taskDescription << "\n\n";
oss << "Intent:\n" << (bundle.intent.empty() ? "(none)" : bundle.intent) << "\n\n";
oss << "Constraints:\n";
for (const auto& c : bundle.constraints) {
oss << "- " << c << "\n";
}
oss << "\nSkeleton:\n" << (bundle.skeletonCode.empty() ? "(none)" : bundle.skeletonCode) << "\n\n";
oss << "Context Code:\n" << (bundle.contextCode.empty() ? "(none)" : bundle.contextCode) << "\n\n";
oss << "Project Summary:\n" << (bundle.projectSummary.empty() ? "(none)" : bundle.projectSummary) << "\n\n";
oss << "Previous Attempts:\n";
if (bundle.previousAttempts.empty()) {
oss << "(none)\n";
} else {
for (const auto& a : bundle.previousAttempts) {
oss << "- worker=" << a.workerType << "\n";
if (!a.generatedCode.empty()) oss << " code:\n" << a.generatedCode << "\n";
if (!a.feedback.empty()) oss << " feedback: " << a.feedback << "\n";
}
}
oss << "\nToken Budget: " << bundle.tokenBudget << "\n";
oss << "Output Format: " << bundle.outputFormat << "\n";
return oss.str();
}
inline json bundleToJson(const ContextBundle& bundle) {
return bundle.toJson();
}
inline int estimateBundleTokens(const ContextBundle& bundle) {
return static_cast<int>(bundle.toJson().dump().size() / 4);
}

View File

@@ -0,0 +1,192 @@
// Step 385: Context bundle format (12 tests)
#include <cassert>
#include <iostream>
#include <string>
#include "ContextBundle.h"
static WorkItem makeItem(const std::string& id = "w1") {
WorkItem item;
item.id = id;
item.nodeId = id + "_node";
item.nodeName = "buildThing";
item.nodeType = "Function";
item.bufferId = "main.py";
item.contextWidth = "project";
item.workerType = "llm";
item.reviewRequired = true;
item.priority = "high";
item.status = WI_READY;
item.createdAt = workItemTimestamp();
return item;
}
static WorkerContext makeContext() {
WorkerContext ctx;
ctx.nodeAst = json{{"id", "w1_node"}, {"type", "Function"}, {"name", "buildThing"}};
ctx.bufferContent = "def helper():\n return 1\n";
ctx.projectSummary = json{{"files", 4}, {"keyModules", json::array({"core", "api"})}};
ctx.annotations = {
json{{"type", "Contract"}, {"name", "precondition"}, {"value", "input non-empty"}},
json{{"type", "Risk"}, {"name", "level"}, {"value", "medium"}}
};
ctx.skeletonIntent = "Build normalized result map";
return ctx;
}
static RoutingDecision makeDecision() {
RoutingDecision d;
d.workerType = "llm";
d.contextWidth = "project";
d.contextBudgetTokens = 8000;
d.reviewRequired = true;
d.confidence = 0.7f;
d.reasoning = "Project context";
d.agentRole = "generator";
return d;
}
int main() {
int passed = 0;
// Test 1: bundle contains core fields
{
auto b = buildBundle(makeItem(), makeContext(), makeDecision());
assert(!b.taskDescription.empty());
assert(!b.skeletonCode.empty());
assert(!b.contextCode.empty());
assert(b.tokenBudget == 8000);
std::cout << "Test 1 PASSED: bundle contains required fields\n";
passed++;
}
// Test 2: bundleToPrompt produces structured readable output
{
auto b = buildBundle(makeItem(), makeContext(), makeDecision());
std::string prompt = bundleToPrompt(b);
assert(prompt.find("Task:") != std::string::npos);
assert(prompt.find("Constraints:") != std::string::npos);
assert(prompt.find("Output Format:") != std::string::npos);
std::cout << "Test 2 PASSED: prompt rendering structured\n";
passed++;
}
// Test 3: rejection history included in previous attempts
{
WorkItem item = makeItem();
RejectionAttempt a;
a.workerType = "slm";
a.result.generatedCode = "return tmp";
a.feedback = "missed edge case";
item.rejectionHistory.push_back(a);
auto b = buildBundle(item, makeContext(), makeDecision());
assert(b.previousAttempts.size() == 1);
assert(b.previousAttempts[0].feedback == "missed edge case");
std::cout << "Test 3 PASSED: rejection history carried into bundle\n";
passed++;
}
// Test 4: token estimate is positive and size-correlated
{
auto b = buildBundle(makeItem(), makeContext(), makeDecision());
int estimate = estimateBundleTokens(b);
assert(estimate > 0);
assert(std::abs(estimate - (int)(b.toJson().dump().size() / 4)) <= 1);
std::cout << "Test 4 PASSED: token estimate reasonable\n";
passed++;
}
// Test 5: budget value preserved from routing decision
{
RoutingDecision d = makeDecision();
d.contextBudgetTokens = 1234;
auto b = buildBundle(makeItem(), makeContext(), d);
assert(b.tokenBudget == 1234);
std::cout << "Test 5 PASSED: budget respected from routing decision\n";
passed++;
}
// Test 6: annotation constraints included
{
auto b = buildBundle(makeItem(), makeContext(), makeDecision());
assert(!b.constraints.empty());
bool hasContract = false;
for (const auto& c : b.constraints) {
if (c.find("Contract") != std::string::npos) hasContract = true;
}
assert(hasContract);
std::cout << "Test 6 PASSED: constraints include annotations\n";
passed++;
}
// Test 7: intent comes from worker context
{
auto b = buildBundle(makeItem(), makeContext(), makeDecision());
assert(b.intent == "Build normalized result map");
std::cout << "Test 7 PASSED: intent carried from context\n";
passed++;
}
// Test 8: empty context fields handled gracefully
{
WorkItem item = makeItem();
WorkerContext ctx;
RoutingDecision d = makeDecision();
auto b = buildBundle(item, ctx, d);
std::string prompt = bundleToPrompt(b);
assert(!prompt.empty());
assert(b.constraints.size() == 1); // default "none"
std::cout << "Test 8 PASSED: empty fields handled gracefully\n";
passed++;
}
// Test 9: bundle JSON roundtrip
{
auto b = buildBundle(makeItem(), makeContext(), makeDecision());
auto round = ContextBundle::fromJson(bundleToJson(b));
assert(round.taskDescription == b.taskDescription);
assert(round.tokenBudget == b.tokenBudget);
assert(round.outputFormat == b.outputFormat);
std::cout << "Test 9 PASSED: JSON roundtrip works\n";
passed++;
}
// Test 10: prompt format remains model-agnostic
{
auto b = buildBundle(makeItem(), makeContext(), makeDecision());
std::string prompt = bundleToPrompt(b);
assert(prompt.find("Claude") == std::string::npos);
assert(prompt.find("OpenAI") == std::string::npos);
std::cout << "Test 10 PASSED: prompt format model-agnostic\n";
passed++;
}
// Test 11: deterministic/template defaults to code-only output format
{
RoutingDecision d = makeDecision();
d.workerType = "template";
auto b = buildBundle(makeItem(), makeContext(), d);
assert(b.outputFormat == "code-only");
std::cout << "Test 11 PASSED: code-only format for deterministic/template\n";
passed++;
}
// Test 12: previous attempts preserve insertion order
{
WorkItem item = makeItem();
RejectionAttempt a1; a1.workerType = "slm"; a1.feedback = "first";
RejectionAttempt a2; a2.workerType = "llm"; a2.feedback = "second";
item.rejectionHistory.push_back(a1);
item.rejectionHistory.push_back(a2);
auto b = buildBundle(item, makeContext(), makeDecision());
assert(b.previousAttempts.size() == 2);
assert(b.previousAttempts[0].feedback == "first");
assert(b.previousAttempts[1].feedback == "second");
std::cout << "Test 12 PASSED: previous-attempt order preserved\n";
passed++;
}
std::cout << "\nResults: " << passed << "/12\n";
assert(passed == 12);
return 0;
}

View File

@@ -3063,6 +3063,45 @@ and next-action guidance for clients.
- `editor/src/WorkflowProtocol.h` within header-size limit (`185` <= `600`)
- `editor/tests/step384_test.cpp` within test-file size guidance (`177` lines)
### Step 385: Context Bundle Format
**Status:** PASS (12/12 tests)
Standardized the external-model context contract with a structured bundle,
prompt rendering, JSON transport format, and token estimation utilities.
**Files created:**
- `editor/src/ContextBundle.h` — context bundle model:
- `ContextBundle` + `AttemptSummary`
- `buildBundle(workItem, workerContext, routingDecision)`
- `bundleToPrompt(bundle)` (model-agnostic prompt rendering)
- `bundleToJson(bundle)` / `ContextBundle::fromJson(...)`
- `estimateBundleTokens(bundle)`
- `editor/tests/step385_test.cpp` — 12 tests covering:
1. required bundle field population
2. prompt structure readability
3. rejection-history inclusion
4. token-estimate reasonableness
5. routing budget propagation
6. annotation-constraint extraction
7. intent propagation
8. empty-field handling
9. JSON roundtrip
10. model-agnostic prompt format
11. output-format policy (`code-only` vs `code-with-explanation`)
12. previous-attempt ordering
**Files modified:**
- `editor/CMakeLists.txt``step385_test` target
**Verification run:**
- `step385_test` — PASS (12/12) new step coverage
- `step384_test` — PASS (12/12) regression coverage
- `step383_test` — PASS (8/8) regression coverage
**Architecture gate check:**
- `editor/src/ContextBundle.h` within header-size limit (`168` <= `600`)
- `editor/tests/step385_test.cpp` within test-file size guidance (`192` lines)
# Roadmap Planning — Sprints 12-25+
## Status: Planning Complete (Sprints 12-19 detailed, 20-25 in roadmap.md)