Skip to main content

comindly_api/
prompts.rs

1use std::collections::BTreeMap;
2
3use serde::{Deserialize, Serialize};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
6#[serde(rename_all = "lowercase")]
7pub enum Task {
8    Summary,
9    Guide,
10    Flow,
11}
12
13impl Task {
14    pub const ALL: [Task; 3] = [Task::Summary, Task::Guide, Task::Flow];
15
16    pub fn name(self) -> &'static str {
17        match self {
18            Task::Summary => "summary",
19            Task::Guide => "guide",
20            Task::Flow => "flow",
21        }
22    }
23
24    pub fn parse(s: &str) -> Option<Task> {
25        Task::ALL.into_iter().find(|t| t.name() == s)
26    }
27
28    /// The built-in instructions, version 0.
29    pub fn builtin(self) -> &'static str {
30        match self {
31            Task::Summary => {
32                "You write the summaries in a code index that developers and coding agents search \
33                across all of a team's repositories. An agent reads your summary to decide, without \
34                opening the file, whether this is the code it must change or call, so be specific \
35                and exact.\n\
36                You get one symbol: its name, kind, repository and file, the class it belongs to, its \
37                signature, docstring and code, what it calls, what calls it, and the service \
38                contracts (HTTP routes, queues, topics, tables, buckets) it serves, publishes or uses.\n\
39                The summary:\n\
40                - Says what the code does and what for, in the domain's own words, in one sentence of \
41                at most 30 words, present tense. Never start with the symbol's name, \"This \
42                function\" or \"This class\".\n\
43                - Names the effects the code shows: what it reads or writes (table, cache, file, \
44                bucket), the HTTP calls it makes and to which route, the messages it publishes or \
45                consumes, the errors it raises on purpose, and what it returns.\n\
46                - Leads with the contract when it serves one: \"Serves GET /orders/{id}: ...\".\n\
47                - For a class, says what it stands for and what it is used for, not its methods.\n\
48                - States only what the code, docstring, signature or neighbours show. When the code \
49                is not shown, describe what the signature and neighbours show and stop; never invent \
50                behaviour, and never judge the code or give advice.\n\
51                The questions:\n\
52                - Three questions someone would type to find this code before knowing its name: \
53                \"where do we ...\", \"how does ... get ...\", \"what happens when ...\", \
54                \"which service ...\".\n\
55                - Use the domain's words (entities, events, routes, tables), not the code's \
56                identifiers, and make each question ask something different."
57            }
58            Task::Guide => {
59                "You are a senior engineer documenting ONE section of a codebase's house-style \
60                guide so an AI coding agent writes code INDISTINGUISHABLE from this repo's. You are \
61                given REAL evidence (signatures, code excerpts, dependency facts, measured stats) for \
62                this section only. Make the repo's tacit conventions explicit.\n\
63                Hard rules:\n\
64                - Cite a real FILE PATH (and symbol/signature) for every non-trivial claim; quote a \
65                short real snippet as the example. Where useful, show a correct vs incorrect example.\n\
66                - Prefer the repo's OWN wrapper/abstraction over a raw library when the evidence \
67                shows one; name it exactly.\n\
68                - NEVER output generic advice that would apply to any codebase ('use meaningful \
69                names', 'write tests', 'handle errors gracefully'). Delete such lines.\n\
70                - Strength tags: **MUST**/**NEVER** for patterns across ≥3 files or enforced by \
71                config; 'prefer' for weaker signals. Give a one-line reason where non-obvious.\n\
72                - At most 8 rules: keep the ones an agent is most likely to get wrong.\n\
73                - Skip what a formatter or linter in the evidence already enforces; name the tool \
74                and its setting once instead."
75            }
76            Task::Flow => {
77                "You write the walkthroughs in a code index, for a developer or coding agent new to \
78                this code who must change or debug the flow. You get the entry point's signature and \
79                code and the trace of what it reaches: each line gives the depth, how the step was \
80                reached (calls, serves, publishes, subscribes, reads, writes), the repository and \
81                symbol, its location, and a one-line summary of the step when one is known.\n\
82                The walkthrough tells the story of one request or message:\n\
83                - Start with what triggers it (an HTTP route, a message, a command, a schedule) and \
84                what it is for.\n\
85                - Follow the steps that matter, in order, naming each symbol in backticks and what \
86                it does. Skip trivial helpers, getters, logging and validation of arguments.\n\
87                - Say plainly where it leaves the service: the route it calls or the topic or queue \
88                it publishes to, and which repository's code picks it up. Name the repositories.\n\
89                - Name the side effects: what is stored, sent or changed, and where.\n\
90                - End with the outcome: what the caller gets back or what state the system is left \
91                in, and where it can fail or branch when the code or trace shows it.\n\
92                - Write 3 to 6 short sentences, at most 120 words; leave out detail the reader can \
93                find in a step's own summary. Use only what the trace, the code and the summaries \
94                show; never invent a step.\n\
95                The questions: three questions this flow answers, in the domain's words: \
96                \"what happens when ...\", \"where does ... end up\", \"which service ...\"."
97            }
98        }
99    }
100
101    /// What the reply holds; the same shape goes along as a JSON schema, and is never editable.
102    pub fn format(self) -> &'static str {
103        match self {
104            Task::Summary => {
105                "Reply as JSON: \"summary\" is the one-sentence summary; \"queries\" are the 3 \
106                questions."
107            }
108            Task::Guide => {
109                "Reply as JSON: \"rules\" holds this section's rules, one terse markdown rule per \
110                item, with no heading, preamble or closing note; \"observed\" is false, with no \
111                rules, when the evidence shows no consistent convention."
112            }
113            Task::Flow => {
114                "Reply as JSON: \"narration\" is the walkthrough in markdown, symbols in \
115                backticks; \"queries\" are the 3 questions."
116            }
117        }
118    }
119}
120
121/// One saved version of a task's instructions.
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123pub struct Version {
124    pub task: Task,
125    pub version: u32,
126    pub text: String,
127    /// Style guide only: what to cover in a section, by title, in place of the built-in guidance.
128    #[serde(default, skip_serializing_if = "BTreeMap::is_empty")]
129    pub sections: BTreeMap<String, String>,
130    #[serde(default, skip_serializing_if = "Option::is_none")]
131    pub note: Option<String>,
132    pub author: String,
133    pub created_at: String,
134    /// The built-in prompt when this version was written, to tell when a release changed it.
135    #[serde(default, skip_serializing_if = "Option::is_none")]
136    pub based_on: Option<String>,
137}