Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletions .code-reducer.yaml
Original file line number Diff line number Diff line change
@@ -1,22 +1,52 @@
model_id: gemma4:26b-a4b-it-qat
model_id: ornith:9b
ollama_base_url: http://localhost:11434
ollama_num_ctx: 20000
docs_dir: wiki

# Reasoning output. ornith:9b is a reasoning model: with thinking enabled it
# charges its hidden thinking against every generation budget, so a one-line slot
# costs 400 tokens instead of 34 and the slot budgets below are no longer
# reachable. Keep this false.
think: false

# Client-wide generation cap. 0 lets Ollama decide, which only bounds the fact
# extraction calls: every documentation slot is capped by its own per-kind cap
# instead. A value lower than a per-kind cap caps that cap as well.
num_predict: 0

# Generation caps for the documentation prose slots. The page structure is written
# by the engine, so these are ceilings a well-behaved answer never reaches: a
# one-line slot measures 34 tokens and a paragraph slot 63. A clipped answer is
# salvaged to its last complete sentence instead of failing.
slot_num_predict: 192
paragraph_num_predict: 1024

# Characters per token, used to turn the context budget into a prompt payload
# budget, and the context tokens held back from every prompt for generation.
chars_per_token: 3.0
output_token_reserve: 1024

# Test files are excluded by default.
include_tests: false

system_prompt: |
You are Code-Reducer, an expert technical writer and code analyzer. Your job is to strictly follow instructions. You do not yap, you do not write filler.
DEFENSIVE RULES: 1. Do NOT use absolute terms ('always', 'never', 'zero') unless explicitly proven. 2. Do NOT guess downstream consequences or invent unhandled paths. If an error is swallowed, just say it is swallowed. 3. Do NOT name standard library packages unless explicitly stated in the source text. 4. Only report facts you are 100% sure about.

# Prose style for every slot of a module page. The engine owns the headings, their
# order and the component names, so this prompt must never ask for structure.
module_synthesis_prompt: |-
Task: Write a technical documentation page for a code module based on the provided list of its internal components.
Rule 1: Group related functions and classes under appropriate Markdown headings.
Rule 2: Explain the responsibility of the module and the data flow.
Rule 3: Keep it highly technical and dense.
Task: Write the prose for one slot of a module documentation page.
Rule 1: The headings, their order and the component names are already fixed by the tooling. Never emit headings, lists, code fences or any other structure.
Rule 2: Answer with the requested slot text only, dense and technical, and keep it as short as the slot instruction asks.
Rule 3: Use only the supplied facts, and say nothing about code you were not shown.

# Prose style for every section of the architecture and quickstart pages.
architecture_prompt: |-
Task: Write a global architecture or quickstart document based on the module summaries.
Rule 1: Explain the system boundaries and how the modules interact.
Rule 2: Provide a dense, developer-friendly overview.
Task: Write the prose for one slot of a project documentation page (architecture or quickstart).
Rule 1: The title, the headings and their order are already fixed by the tooling. Never emit headings, lists, code fences or any other structure.
Rule 2: Answer with the requested slot text only, dense and developer-friendly, and keep it as short as the slot instruction asks.
Rule 3: Use only the supplied module summaries, and never invent a module, a command or a workflow you were not shown.

file_fact_consolidation_prompt: |-
You are a specialized code documentation assistant.
Expand Down
140 changes: 103 additions & 37 deletions README.md

Large diffs are not rendered by default.

122 changes: 119 additions & 3 deletions cmd/cmd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,26 @@ func TestRootFlags(t *testing.T) {
// reset flags
modelIDFlag = ""
numCtxFlag = ""
thinkFlag = ""
numPredictFlag = ""
slotNumPredictFlag = ""
paragraphNumPredictFlag = ""
charsPerTokenFlag = ""
outputTokenReserveFlag = ""
includeTestsFlag = ""

RootCmd.SetArgs([]string{"--model-id", "test-model", "--num-ctx", "4096", "help"}) // run something harmless like help
RootCmd.SetArgs([]string{
"--model-id", "test-model",
"--num-ctx", "4096",
"--think", "true",
"--num-predict", "2048",
"--slot-num-predict", "96",
"--paragraph-num-predict", "768",
"--chars-per-token", "2.5",
"--output-token-reserve", "512",
"--include-tests", "true",
"help",
}) // run something harmless like help
_ = RootCmd.Execute()

if modelIDFlag != "test-model" {
Expand All @@ -26,6 +44,34 @@ func TestRootFlags(t *testing.T) {
if numCtxFlag != "4096" {
t.Errorf("Expected numCtxFlag to be '4096', got '%s'", numCtxFlag)
}

if thinkFlag != "true" {
t.Errorf("Expected thinkFlag to be 'true', got '%s'", thinkFlag)
}

if numPredictFlag != "2048" {
t.Errorf("Expected numPredictFlag to be '2048', got '%s'", numPredictFlag)
}

if slotNumPredictFlag != "96" {
t.Errorf("Expected slotNumPredictFlag to be '96', got '%s'", slotNumPredictFlag)
}

if paragraphNumPredictFlag != "768" {
t.Errorf("Expected paragraphNumPredictFlag to be '768', got '%s'", paragraphNumPredictFlag)
}

if charsPerTokenFlag != "2.5" {
t.Errorf("Expected charsPerTokenFlag to be '2.5', got '%s'", charsPerTokenFlag)
}

if outputTokenReserveFlag != "512" {
t.Errorf("Expected outputTokenReserveFlag to be '512', got '%s'", outputTokenReserveFlag)
}

if includeTestsFlag != "true" {
t.Errorf("Expected includeTestsFlag to be 'true', got '%s'", includeTestsFlag)
}
}

func TestCheckInitStatus(t *testing.T) {
Expand Down Expand Up @@ -169,10 +215,15 @@ func TestLoadInitialSetupConfig(t *testing.T) {
if cfg.ModelID == "" || cfg.OllamaNumCtx <= 0 {
t.Fatalf("expected non-empty defaults, got %+v", cfg)
}
if cfg.SlotNumPredict != config.SlotNumPredictDefault || cfg.ParagraphNumPredict != config.ParagraphNumPredictDefault {
t.Fatalf("expected the shipped slot bounds as defaults, got %+v", cfg)
}

testCfg := &config.Config{
ModelID: "custom-model",
OllamaNumCtx: 8192,
ModelID: "custom-model",
OllamaNumCtx: 8192,
SlotNumPredict: 96,
ParagraphNumPredict: 768,
}
if err := config.SaveConfig(tmp, testCfg); err != nil {
t.Fatalf("failed to save config: %v", err)
Expand All @@ -181,6 +232,9 @@ func TestLoadInitialSetupConfig(t *testing.T) {
if loaded.ModelID != "custom-model" || loaded.OllamaNumCtx != 8192 {
t.Fatalf("expected custom values, got %+v", loaded)
}
if loaded.SlotNumPredict != 96 || loaded.ParagraphNumPredict != 768 {
t.Fatalf("expected the custom slot bounds to survive, got %+v", loaded)
}
}

func TestRunSetupFlow(t *testing.T) {
Expand All @@ -200,3 +254,65 @@ func TestRunSetupFlow(t *testing.T) {
t.Errorf("expected default model ID, got %s", cfg.ModelID)
}
}

func TestRunSetupFlowKeepsIncludeTests(t *testing.T) {
tmp := t.TempDir()
if err := config.SaveConfig(tmp, &config.Config{ModelID: "custom-model", IncludeTests: true}); err != nil {
t.Fatalf("failed to save config: %v", err)
}

reader := bufio.NewReader(bytes.NewBufferString("\n\n\n\n\n"))
if err := runSetupFlowWithReader(reader, tmp); err != nil {
t.Fatalf("unexpected error running setup flow: %v", err)
}

cfg, err := config.LoadConfig(tmp)
if err != nil {
t.Fatalf("expected config to be saved: %v", err)
}
if !cfg.IncludeTests {
t.Error("expected the setup wizard to keep include_tests from the existing config")
}
}

func TestRunSetupFlowKeepsSlotBounds(t *testing.T) {
tmp := t.TempDir()
if err := config.SaveConfig(tmp, &config.Config{ModelID: "custom-model", SlotNumPredict: 96, ParagraphNumPredict: 768}); err != nil {
t.Fatalf("failed to save config: %v", err)
}

reader := bufio.NewReader(bytes.NewBufferString("\n\n\n\n\n"))
if err := runSetupFlowWithReader(reader, tmp); err != nil {
t.Fatalf("unexpected error running setup flow: %v", err)
}

cfg, err := config.LoadConfig(tmp)
if err != nil {
t.Fatalf("expected config to be saved: %v", err)
}
if cfg.SlotNumPredict != 96 {
t.Errorf("expected the setup wizard to keep slot_num_predict, got %d", cfg.SlotNumPredict)
}
if cfg.ParagraphNumPredict != 768 {
t.Errorf("expected the setup wizard to keep paragraph_num_predict, got %d", cfg.ParagraphNumPredict)
}
}

func TestRunSetupFlowWritesShippedSlotBounds(t *testing.T) {
tmp := t.TempDir()
reader := bufio.NewReader(bytes.NewBufferString("\n\n\n\n\n"))
if err := runSetupFlowWithReader(reader, tmp); err != nil {
t.Fatalf("unexpected error running setup flow: %v", err)
}

cfg, err := config.LoadConfig(tmp)
if err != nil {
t.Fatalf("expected config to be saved: %v", err)
}
if cfg.SlotNumPredict != config.SlotNumPredictDefault {
t.Errorf("expected slot_num_predict %d, got %d", config.SlotNumPredictDefault, cfg.SlotNumPredict)
}
if cfg.ParagraphNumPredict != config.ParagraphNumPredictDefault {
t.Errorf("expected paragraph_num_predict %d, got %d", config.ParagraphNumPredictDefault, cfg.ParagraphNumPredict)
}
}
30 changes: 27 additions & 3 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,15 @@ import (
)

var (
modelIDFlag string
numCtxFlag string
modelIDFlag string
numCtxFlag string
thinkFlag string
numPredictFlag string
slotNumPredictFlag string
paragraphNumPredictFlag string
charsPerTokenFlag string
outputTokenReserveFlag string
includeTestsFlag string
)

var RootCmd = &cobra.Command{
Expand All @@ -32,6 +39,13 @@ var RootCmd = &cobra.Command{
func init() {
RootCmd.PersistentFlags().StringVar(&modelIDFlag, "model-id", "", "Specify LLM model ID")
RootCmd.PersistentFlags().StringVar(&numCtxFlag, "num-ctx", "", "Specify Ollama context window size")
RootCmd.PersistentFlags().StringVar(&thinkFlag, "think", "", "Enable reasoning output on reasoning models (default false)")
RootCmd.PersistentFlags().StringVar(&numPredictFlag, "num-predict", "", "Cap the number of generated tokens per call (default 0, Ollama decides)")
RootCmd.PersistentFlags().StringVar(&slotNumPredictFlag, "slot-num-predict", "", "Cap the generated tokens for each one-line documentation slot (default 192)")
RootCmd.PersistentFlags().StringVar(&paragraphNumPredictFlag, "paragraph-num-predict", "", "Cap the generated tokens for each documentation paragraph slot (default 1024)")
RootCmd.PersistentFlags().StringVar(&charsPerTokenFlag, "chars-per-token", "", "Characters per token used to size prompt payloads (default 3.0)")
RootCmd.PersistentFlags().StringVar(&outputTokenReserveFlag, "output-token-reserve", "", "Context tokens held back from prompt payloads for generation (default 1024)")
RootCmd.PersistentFlags().StringVar(&includeTestsFlag, "include-tests", "", "Document test files as well (default false)")
}

func executeCommand(mode engine.Mode) error {
Expand All @@ -48,7 +62,17 @@ func executeCommand(mode engine.Mode) error {
return err
}

cfg, err := config.ResolveConfig(repoRoot, modelIDFlag, numCtxFlag)
cfg, err := config.ResolveConfig(repoRoot, config.Flags{
ModelID: modelIDFlag,
NumCtx: numCtxFlag,
Think: thinkFlag,
NumPredict: numPredictFlag,
SlotNumPredict: slotNumPredictFlag,
ParagraphNumPredict: paragraphNumPredictFlag,
CharsPerToken: charsPerTokenFlag,
OutputTokenReserve: outputTokenReserveFlag,
IncludeTests: includeTestsFlag,
})
if err != nil {
return err
}
Expand Down
26 changes: 26 additions & 0 deletions cmd/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,13 @@ func loadInitialSetupConfig(repoRoot string) *config.Config {
OllamaBaseURL: config.OllamaDefaultBaseURL,
OllamaNumCtx: config.OllamaDefaultNumCtx,
DocsDir: config.DefaultDocsDir,
Think: config.ThinkDefault,
NumPredict: config.NumPredictDefault,
SlotNumPredict: config.SlotNumPredictDefault,
ParagraphNumPredict: config.ParagraphNumPredictDefault,
CharsPerToken: config.CharsPerTokenDefault,
OutputTokenReserve: config.OutputTokenReserveDefault,
IncludeTests: config.IncludeTestsDefault,
ExtractionSteps: config.DefaultExtractionSteps,
SystemPrompt: config.DefaultSystemPrompt,
ModuleSynthesisPrompt: config.DefaultModuleSynthesisPrompt,
Expand All @@ -61,6 +68,18 @@ func loadInitialSetupConfig(repoRoot string) *config.Config {
if len(cfg.ExtractionSteps) == 0 {
cfg.ExtractionSteps = config.DefaultExtractionSteps
}
if cfg.SlotNumPredict <= 0 {
cfg.SlotNumPredict = config.SlotNumPredictDefault
}
if cfg.ParagraphNumPredict <= 0 {
cfg.ParagraphNumPredict = config.ParagraphNumPredictDefault
}
if cfg.CharsPerToken <= 0 {
cfg.CharsPerToken = config.CharsPerTokenDefault
}
if cfg.OutputTokenReserve <= 0 {
cfg.OutputTokenReserve = config.OutputTokenReserveDefault
}
if cfg.SystemPrompt == "" {
cfg.SystemPrompt = config.DefaultSystemPrompt
}
Expand Down Expand Up @@ -144,6 +163,13 @@ func runSetupFlowWithReader(reader *bufio.Reader, repoRoot string) error {
OllamaBaseURL: urlInput,
OllamaNumCtx: numCtx,
DocsDir: docsDirInput,
Think: current.Think,
NumPredict: current.NumPredict,
SlotNumPredict: current.SlotNumPredict,
ParagraphNumPredict: current.ParagraphNumPredict,
CharsPerToken: current.CharsPerToken,
OutputTokenReserve: current.OutputTokenReserve,
IncludeTests: current.IncludeTests,
ExtractionSteps: current.ExtractionSteps,
Ignore: ignores,
SystemPrompt: current.SystemPrompt,
Expand Down
Loading
Loading