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 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 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#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123pub struct Version {
124 pub task: Task,
125 pub version: u32,
126 pub text: String,
127 #[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 #[serde(default, skip_serializing_if = "Option::is_none")]
136 pub based_on: Option<String>,
137}