ctxman 3.0.0 → 3.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.ctxman/llm-profiles.json +1067 -39
- package/.ctxman/profiles/backend-team.json +13 -0
- package/.ctxman/profiles/default.json +12 -0
- package/.ctxman/profiles/devops-team.json +21 -0
- package/.ctxman/profiles/frontend-team.json +20 -0
- package/.ctxman/templates/bug-fix.json +17 -0
- package/.ctxman/templates/code-review.json +9 -0
- package/.ctxman/templates/documentation.json +9 -0
- package/.ctxman/templates/feature.json +9 -0
- package/.ctxman/templates/full-context.json +9 -0
- package/.ctxman/templates/refactoring.json +18 -0
- package/.ctxman/templates/testing.json +9 -0
- package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
- package/LICENSE +21 -0
- package/README.md +381 -136
- package/bin/cli.js +1880 -529
- package/bin/cm-ask.js +18 -67
- package/bin/cm-audit.js +121 -0
- package/bin/cm-count.js +151 -0
- package/bin/cm-gitingest.js +128 -115
- package/bin/cm-map.js +242 -0
- package/bin/cm-mcp.js +293 -0
- package/bin/cm-pack.js +405 -0
- package/bin/cm-policy.js +170 -0
- package/bin/cm-pr.js +248 -0
- package/bin/cm-search.js +178 -0
- package/bin/cm-update.js +128 -128
- package/bin/cm-why.js +197 -0
- package/bin/mcp-server.js +8 -24
- package/ctxman.js +237 -183
- package/examples/README.md +19 -4
- package/examples/github-actions/ctxman-check.yml +49 -0
- package/examples/wizard-profiles/security-audit/profile.json +7 -1
- package/index.d.ts +1422 -0
- package/index.js +55 -31
- package/lib/agents/pr-failure-agent.js +48 -45
- package/lib/analyzers/ai-suggester.js +451 -0
- package/lib/analyzers/dependency-scanner.js +505 -0
- package/lib/analyzers/duplicate-detector.js +264 -0
- package/lib/analyzers/go-method-analyzer.js +151 -110
- package/lib/analyzers/import-tracker.js +431 -0
- package/lib/analyzers/method-analyzer.js +1055 -434
- package/lib/analyzers/performance-dashboard.js +687 -0
- package/lib/analyzers/token-calculator.js +895 -504
- package/lib/analyzers/unused-exports-detector.js +336 -0
- package/lib/api/mcp/install.js +424 -0
- package/lib/api/mcp/prompts.js +281 -281
- package/lib/api/mcp/resources.js +300 -241
- package/lib/api/mcp/sandbox.js +89 -0
- package/lib/api/mcp/server.js +772 -312
- package/lib/api/mcp/start.js +125 -0
- package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
- package/lib/api/rest/server.js +105 -28
- package/lib/cache/CacheManager.js +9 -17
- package/lib/core/Analyzer.js +16 -10
- package/lib/core/ContextBuilder.js +19 -28
- package/lib/core/Reporter.js +15 -14
- package/lib/core/Scanner.js +81 -12
- package/lib/count/index.js +100 -0
- package/lib/formatters/format-registry.js +394 -372
- package/lib/formatters/gitingest-formatter.js +656 -621
- package/lib/formatters/toon-formatter-v1.3.js +637 -632
- package/lib/formatters/toon-formatter.js +239 -240
- package/lib/integrations/git/BlameTracker.js +4 -4
- package/lib/integrations/git/DiffAnalyzer.js +38 -31
- package/lib/integrations/git/GitClient.js +71 -53
- package/lib/lsp/server.js +311 -0
- package/lib/map/graph.js +299 -0
- package/lib/map/imports.js +233 -0
- package/lib/map/index.js +33 -0
- package/lib/map/rank.js +253 -0
- package/lib/map/render.js +346 -0
- package/lib/map/resolver.js +574 -0
- package/lib/pack/budget.js +111 -0
- package/lib/pack/code-lexer.js +360 -0
- package/lib/pack/compress.js +414 -0
- package/lib/pack/index.js +354 -0
- package/lib/pack/renderers/common.js +134 -0
- package/lib/pack/renderers/index.js +24 -0
- package/lib/pack/renderers/json.js +47 -0
- package/lib/pack/renderers/markdown.js +54 -0
- package/lib/pack/renderers/plain.js +38 -0
- package/lib/pack/renderers/xml.js +85 -0
- package/lib/pack/select.js +177 -0
- package/lib/pack/split.js +182 -0
- package/lib/pack/tree.js +35 -0
- package/lib/parsers/gitignore-parser.js +116 -107
- package/lib/parsers/method-filter-parser.js +50 -33
- package/lib/plugins/ExporterPlugin.js +3 -3
- package/lib/plugins/LanguagePlugin.js +3 -3
- package/lib/plugins/PluginManager.js +7 -13
- package/lib/policy/audit.js +312 -0
- package/lib/policy/enforce.js +125 -0
- package/lib/policy/ignore-files.js +169 -0
- package/lib/policy/index.js +53 -0
- package/lib/policy/policy.js +489 -0
- package/lib/pr/diff-parser.js +267 -0
- package/lib/pr/method-mapper.js +148 -0
- package/lib/pr/pr-context.js +829 -0
- package/lib/pr/pr-git.js +149 -0
- package/lib/pr/pr-renderer.js +644 -0
- package/lib/pr/related-tests.js +296 -0
- package/lib/rag/DataSourcePlugin.js +5 -5
- package/lib/rag/EmbeddingProvider.js +19 -8
- package/lib/rag/Indexer.js +14 -5
- package/lib/rag/VectorStoreFactory.js +3 -3
- package/lib/rag/ask.js +122 -0
- package/lib/rag/sources/FileDataSource.js +12 -9
- package/lib/rag/stores/BaseVectorStore.js +5 -5
- package/lib/rag/stores/ChromaDBStore.js +10 -10
- package/lib/rag/stores/LanceDBStore.js +29 -19
- package/lib/rag/stores/PineconeStore.js +11 -9
- package/lib/search/bm25.js +433 -0
- package/lib/search/code-units.js +201 -0
- package/lib/search/symbols.js +189 -0
- package/lib/ui/dashboard.js +120 -89
- package/lib/ui/index.js +1 -6
- package/lib/ui/init-wizard-ui.js +550 -0
- package/lib/ui/progress-bar.js +94 -88
- package/lib/ui/select-input.js +40 -32
- package/lib/ui/template-selector.js +99 -0
- package/lib/ui/wizard.js +374 -329
- package/lib/utils/claude-token-counter.js +151 -0
- package/lib/utils/cli-output.js +17 -0
- package/lib/utils/clipboard-utils.js +49 -50
- package/lib/utils/config-utils.js +81 -66
- package/lib/utils/context-versioning.js +532 -0
- package/lib/utils/error-handler.js +112 -112
- package/lib/utils/file-utils.js +153 -69
- package/lib/utils/format-converter.js +356 -283
- package/lib/utils/git-utils.js +366 -294
- package/lib/utils/llm-cost-estimator.js +281 -0
- package/lib/utils/llm-detector.js +242 -78
- package/lib/utils/logger.js +246 -237
- package/lib/utils/messages.js +355 -0
- package/lib/utils/multi-repo-manager.js +364 -0
- package/lib/utils/profile-manager.js +380 -0
- package/lib/utils/safe-read.js +61 -0
- package/lib/utils/secret-redactor.js +61 -0
- package/lib/utils/snapshot-manager.js +679 -0
- package/lib/utils/template-manager.js +378 -0
- package/lib/utils/token-utils.js +201 -170
- package/lib/utils/tokenizer-adapter.js +421 -377
- package/lib/utils/updater.js +539 -506
- package/lib/watch/FileWatcher.js +12 -8
- package/lib/watch/IncrementalAnalyzer.js +7 -7
- package/lib/why/coupling.js +81 -0
- package/lib/why/errors.js +23 -0
- package/lib/why/git.js +169 -0
- package/lib/why/history.js +108 -0
- package/lib/why/index.js +720 -0
- package/lib/why/ownership.js +79 -0
- package/lib/why/paths.js +207 -0
- package/lib/why/references.js +259 -0
- package/lib/why/render.js +722 -0
- package/lib/why/search.js +358 -0
- package/lib/why/target.js +389 -0
- package/lib/wizards/config-generator.js +257 -0
- package/lib/wizards/project-detector.js +572 -0
- package/package.json +44 -10
- package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
- package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
- package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
|
@@ -0,0 +1,644 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render a pull-request context model as Markdown, XML or JSON.
|
|
3
|
+
*
|
|
4
|
+
* Sections follow the long-context layout: the long material (commit log, changed files,
|
|
5
|
+
* methods, diff, tests, file contents) first and the request (instructions, question) last.
|
|
6
|
+
* XML wraps file and method bodies in <documents><document index="n"><source>…</source>
|
|
7
|
+
* <document_content>…</document_content></document></documents>. Bodies are left as they
|
|
8
|
+
* are (not entity-escaped) so the model reads the code verbatim; the result is prompt
|
|
9
|
+
* markup, not a document for an XML parser.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import path from 'path';
|
|
13
|
+
|
|
14
|
+
export const SECTION_ORDER = [
|
|
15
|
+
'summary',
|
|
16
|
+
'commits',
|
|
17
|
+
'files',
|
|
18
|
+
'methods',
|
|
19
|
+
'diff',
|
|
20
|
+
'tests',
|
|
21
|
+
'contents',
|
|
22
|
+
'request',
|
|
23
|
+
];
|
|
24
|
+
|
|
25
|
+
export const SECTION_LABELS = {
|
|
26
|
+
summary: 'summary',
|
|
27
|
+
commits: 'commits',
|
|
28
|
+
files: 'files',
|
|
29
|
+
methods: 'methods',
|
|
30
|
+
diff: 'diff',
|
|
31
|
+
tests: 'tests',
|
|
32
|
+
contents: 'file contents',
|
|
33
|
+
request: 'request',
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
const STATUS_WORDS = {
|
|
37
|
+
A: 'added',
|
|
38
|
+
M: 'modified',
|
|
39
|
+
D: 'deleted',
|
|
40
|
+
R: 'renamed',
|
|
41
|
+
C: 'copied',
|
|
42
|
+
T: 'type changed',
|
|
43
|
+
U: 'unmerged',
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
const FENCE_LANGUAGES = {
|
|
47
|
+
'.js': 'javascript',
|
|
48
|
+
'.mjs': 'javascript',
|
|
49
|
+
'.cjs': 'javascript',
|
|
50
|
+
'.jsx': 'jsx',
|
|
51
|
+
'.ts': 'typescript',
|
|
52
|
+
'.mts': 'typescript',
|
|
53
|
+
'.cts': 'typescript',
|
|
54
|
+
'.tsx': 'tsx',
|
|
55
|
+
'.py': 'python',
|
|
56
|
+
'.go': 'go',
|
|
57
|
+
'.java': 'java',
|
|
58
|
+
'.rb': 'ruby',
|
|
59
|
+
'.php': 'php',
|
|
60
|
+
'.rs': 'rust',
|
|
61
|
+
'.cs': 'csharp',
|
|
62
|
+
'.kt': 'kotlin',
|
|
63
|
+
'.kts': 'kotlin',
|
|
64
|
+
'.swift': 'swift',
|
|
65
|
+
'.c': 'c',
|
|
66
|
+
'.h': 'c',
|
|
67
|
+
'.cpp': 'cpp',
|
|
68
|
+
'.cc': 'cpp',
|
|
69
|
+
'.hpp': 'cpp',
|
|
70
|
+
'.scala': 'scala',
|
|
71
|
+
'.json': 'json',
|
|
72
|
+
'.yml': 'yaml',
|
|
73
|
+
'.yaml': 'yaml',
|
|
74
|
+
'.md': 'markdown',
|
|
75
|
+
'.sh': 'bash',
|
|
76
|
+
'.bash': 'bash',
|
|
77
|
+
'.html': 'html',
|
|
78
|
+
'.css': 'css',
|
|
79
|
+
'.scss': 'scss',
|
|
80
|
+
'.sql': 'sql',
|
|
81
|
+
'.toml': 'toml',
|
|
82
|
+
'.xml': 'xml',
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const BUDGET_NOTE = 'omitted to fit the token budget';
|
|
86
|
+
|
|
87
|
+
/** Fence language for a path */
|
|
88
|
+
export function fenceLanguage(filePath) {
|
|
89
|
+
return FENCE_LANGUAGES[path.extname(filePath).toLowerCase()] || '';
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/** A Markdown code block whose fence is longer than any backtick run inside it */
|
|
93
|
+
function fence(content, language = '') {
|
|
94
|
+
const longest = Math.max(2, ...(content.match(/`+/g) || []).map((run) => run.length));
|
|
95
|
+
const ticks = '`'.repeat(longest + 1);
|
|
96
|
+
return `${ticks}${language}\n${content.replace(/\n$/, '')}\n${ticks}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Inline code span that survives backticks in the text */
|
|
100
|
+
function code(text) {
|
|
101
|
+
const value = String(text);
|
|
102
|
+
if (!value.includes('`')) return `\`${value}\``;
|
|
103
|
+
return `\`\` ${value} \`\``;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// The cells hold code spans, which show backslashes literally, so only the backslashes
|
|
107
|
+
// right before a pipe are doubled: enough to keep the pipe from ending the cell
|
|
108
|
+
const escapeCell = (text) =>
|
|
109
|
+
String(text)
|
|
110
|
+
.replace(/(\\*)\|/g, '$1$1\\|')
|
|
111
|
+
.replace(/\n/g, ' ');
|
|
112
|
+
|
|
113
|
+
const escapeAttr = (text) =>
|
|
114
|
+
String(text)
|
|
115
|
+
.replace(/&/g, '&')
|
|
116
|
+
.replace(/"/g, '"')
|
|
117
|
+
.replace(/</g, '<')
|
|
118
|
+
.replace(/>/g, '>')
|
|
119
|
+
.replace(/\n/g, ' ');
|
|
120
|
+
|
|
121
|
+
const formatNumber = (n) => Number(n).toLocaleString('en-US');
|
|
122
|
+
|
|
123
|
+
function statusWord(file) {
|
|
124
|
+
let word = STATUS_WORDS[file.status] || file.status;
|
|
125
|
+
if ((file.status === 'R' || file.status === 'C') && file.similarity != null) {
|
|
126
|
+
word += ` (${file.similarity}%)`;
|
|
127
|
+
}
|
|
128
|
+
if (file.untracked) word += ' (untracked)';
|
|
129
|
+
return word;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const lineRange = (item) =>
|
|
133
|
+
item.start === item.end ? `${item.start}` : `${item.start}-${item.end}`;
|
|
134
|
+
|
|
135
|
+
/** "line 4" or "lines 4-9" */
|
|
136
|
+
const linesLabel = (item) =>
|
|
137
|
+
item.start === item.end ? `line ${item.start}` : `lines ${item.start}-${item.end}`;
|
|
138
|
+
|
|
139
|
+
/** Plain-text summary facts shared by every style */
|
|
140
|
+
export function summaryFacts(model) {
|
|
141
|
+
const { stats } = model;
|
|
142
|
+
const facts = [];
|
|
143
|
+
if (model.mode === 'range') {
|
|
144
|
+
facts.push(['Base', `${model.base.ref} (${model.base.short})`]);
|
|
145
|
+
facts.push(['Head', `${model.head.ref} (${model.head.short})`]);
|
|
146
|
+
facts.push(['Merge base', model.mergeBase.short]);
|
|
147
|
+
} else {
|
|
148
|
+
const head =
|
|
149
|
+
model.mode === 'staged'
|
|
150
|
+
? 'staged changes (index)'
|
|
151
|
+
: 'working tree (uncommitted changes, untracked files included)';
|
|
152
|
+
facts.push(['Base', `${model.base.ref} (${model.base.short})`]);
|
|
153
|
+
if (model.mergeBase && model.mergeBase.sha !== model.base.sha) {
|
|
154
|
+
facts.push(['Merge base', model.mergeBase.short]);
|
|
155
|
+
}
|
|
156
|
+
facts.push(['Head', head]);
|
|
157
|
+
}
|
|
158
|
+
if (model.scope) facts.push(['Scope', model.scope]);
|
|
159
|
+
facts.push(['Commits', String(stats.commits)]);
|
|
160
|
+
|
|
161
|
+
const byStatus = Object.entries(stats.byStatus)
|
|
162
|
+
.map(([status, count]) => `${count} ${STATUS_WORDS[status] || status}`)
|
|
163
|
+
.join(', ');
|
|
164
|
+
facts.push([
|
|
165
|
+
'Files changed',
|
|
166
|
+
`${stats.files}${byStatus ? ` (${byStatus})` : ''}, +${formatNumber(stats.additions)} -${formatNumber(stats.deletions)} lines`,
|
|
167
|
+
]);
|
|
168
|
+
if (stats.excluded > 0) {
|
|
169
|
+
facts.push(['Excluded by context filters', `${stats.excluded} (listed, content omitted)`]);
|
|
170
|
+
}
|
|
171
|
+
facts.push([
|
|
172
|
+
'Changed methods',
|
|
173
|
+
`${stats.changedMethods} (${stats.addedMethods} added, ${stats.changedMethods - stats.addedMethods} modified); deleted methods: ${stats.deletedMethods}`,
|
|
174
|
+
]);
|
|
175
|
+
facts.push(['Related tests', String(stats.relatedTests)]);
|
|
176
|
+
if (model.impact) facts.push(['Impact (heuristic)', model.impact.level]);
|
|
177
|
+
if (model.tokens) {
|
|
178
|
+
const parts = SECTION_ORDER.filter((name) => model.tokens.sections[name] != null).map(
|
|
179
|
+
(name) => `${SECTION_LABELS[name]} ${formatNumber(model.tokens.sections[name])}`
|
|
180
|
+
);
|
|
181
|
+
facts.push(['Tokens', `~${formatNumber(model.tokens.total)} total; ${parts.join(', ')}`]);
|
|
182
|
+
}
|
|
183
|
+
if (!model.diffEnabled) facts.push(['Diff', 'not included (--no-diff)']);
|
|
184
|
+
if (model.budget) {
|
|
185
|
+
const dropped = model.budget.dropped.length
|
|
186
|
+
? `; dropped: ${model.budget.dropped.join('; ')}`
|
|
187
|
+
: '';
|
|
188
|
+
const over = model.budget.over ? ' (still over: the required sections alone exceed it)' : '';
|
|
189
|
+
facts.push(['Budget', `${formatNumber(model.budget.limit)} tokens${over}${dropped}`]);
|
|
190
|
+
}
|
|
191
|
+
return facts;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
// Markdown
|
|
196
|
+
// ---------------------------------------------------------------------------
|
|
197
|
+
|
|
198
|
+
const markdown = {
|
|
199
|
+
summary(model) {
|
|
200
|
+
const title =
|
|
201
|
+
model.mode === 'range'
|
|
202
|
+
? `# Pull request context: ${code(model.head.ref)} into ${code(model.base.ref)}`
|
|
203
|
+
: `# Change context: ${model.mode === 'staged' ? 'staged' : 'uncommitted'} changes`;
|
|
204
|
+
const lines = summaryFacts(model).map(([key, value]) => `- ${key}: ${value}`);
|
|
205
|
+
return `${title}\n\n## Summary\n\n${lines.join('\n')}`;
|
|
206
|
+
},
|
|
207
|
+
|
|
208
|
+
commit(commit, model) {
|
|
209
|
+
let text = `### ${code(commit.short)} ${commit.subject}\n${commit.author}, ${commit.date}`;
|
|
210
|
+
if (commit.body && !model.logBodiesDropped) {
|
|
211
|
+
text += `\n\n${commit.body
|
|
212
|
+
.split('\n')
|
|
213
|
+
.map((line) => (line ? `> ${line}` : '>'))
|
|
214
|
+
.join('\n')}`;
|
|
215
|
+
}
|
|
216
|
+
return text;
|
|
217
|
+
},
|
|
218
|
+
|
|
219
|
+
commits(model) {
|
|
220
|
+
if (model.commits.length === 0 || model.logDropped) return null;
|
|
221
|
+
const body = model.commits.map((commit) => markdown.commit(commit, model)).join('\n\n');
|
|
222
|
+
return `## Commits (${model.commits.length})\n\n${body}`;
|
|
223
|
+
},
|
|
224
|
+
|
|
225
|
+
files(model) {
|
|
226
|
+
if (model.filesDropped) return null;
|
|
227
|
+
const head = `## Changed files (${model.files.length})`;
|
|
228
|
+
if (model.files.length === 0) return `${head}\n\nNo changes.`;
|
|
229
|
+
const rows = model.files.map((file) => {
|
|
230
|
+
const name =
|
|
231
|
+
file.oldPath && file.oldPath !== file.path && (file.status === 'R' || file.status === 'C')
|
|
232
|
+
? `${code(file.oldPath)} → ${code(file.path)}`
|
|
233
|
+
: code(file.path);
|
|
234
|
+
const note = file.excluded ? ` (content excluded by ${file.excluded})` : '';
|
|
235
|
+
const added = file.binary ? 'binary' : file.additions;
|
|
236
|
+
const deleted = file.binary ? '' : file.deletions;
|
|
237
|
+
return `| ${statusWord(file)} | ${escapeCell(name + note)} | ${added} | ${deleted} |`;
|
|
238
|
+
});
|
|
239
|
+
return `${head}\n\n| Status | File | + | - |\n| --- | --- | ---: | ---: |\n${rows.join('\n')}`;
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
methodLabel(method) {
|
|
243
|
+
return `${code(method.file)}: ${code(method.name)} (${linesLabel(method)}, ${method.change})`;
|
|
244
|
+
},
|
|
245
|
+
|
|
246
|
+
method(method) {
|
|
247
|
+
return `### ${markdown.methodLabel(method)}\n\n${fence(method.source, fenceLanguage(method.file))}`;
|
|
248
|
+
},
|
|
249
|
+
|
|
250
|
+
methods(model) {
|
|
251
|
+
if (model.methodsDropped) return null;
|
|
252
|
+
if (model.methods.length === 0 && model.deletedMethods.length === 0) return null;
|
|
253
|
+
const parts = [`## Changed methods (${model.methods.length})`];
|
|
254
|
+
const withSource = model.methods.filter((m) => !m.sourceDropped);
|
|
255
|
+
const withoutSource = model.methods.filter((m) => m.sourceDropped);
|
|
256
|
+
if (withSource.length) parts.push(withSource.map(markdown.method).join('\n\n'));
|
|
257
|
+
if (withoutSource.length) {
|
|
258
|
+
const list = withoutSource.map((m) => `- ${markdown.methodLabel(m)}`).join('\n');
|
|
259
|
+
parts.push(`### Changed methods, source ${BUDGET_NOTE}\n\n${list}`);
|
|
260
|
+
}
|
|
261
|
+
if (model.deletedMethods.length) {
|
|
262
|
+
const list = model.deletedMethods
|
|
263
|
+
.map((m) => `- ${code(m.file)}: ${code(m.name)} (${linesLabel(m)} at base)`)
|
|
264
|
+
.join('\n');
|
|
265
|
+
parts.push(`### Deleted methods (${model.deletedMethods.length})\n\n${list}`);
|
|
266
|
+
}
|
|
267
|
+
return parts.join('\n\n');
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
diff(model) {
|
|
271
|
+
if (!model.diffEnabled) return null;
|
|
272
|
+
const shown = model.diffs.filter((d) => !d.dropped);
|
|
273
|
+
const excluded = model.files
|
|
274
|
+
.filter((f) => f.excluded)
|
|
275
|
+
.map((f) => `${code(f.path)} (${f.excluded})`);
|
|
276
|
+
if (shown.length === 0 && excluded.length === 0) return null;
|
|
277
|
+
const parts = ['## Diff'];
|
|
278
|
+
if (shown.length) parts.push(fence(shown.map((d) => d.text).join('\n'), 'diff'));
|
|
279
|
+
if (excluded.length) parts.push(`_Excluded by context filters: ${excluded.join(', ')}._`);
|
|
280
|
+
return parts.join('\n\n');
|
|
281
|
+
},
|
|
282
|
+
|
|
283
|
+
testLine(test) {
|
|
284
|
+
const note = test.excluded ? ` (content excluded by ${test.excluded})` : '';
|
|
285
|
+
return `- ${code(test.path)}: ${test.reasons.join('; ')}${note}`;
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
testContent(test) {
|
|
289
|
+
return `### ${code(test.path)}\n\n${fence(test.content, fenceLanguage(test.path))}`;
|
|
290
|
+
},
|
|
291
|
+
|
|
292
|
+
tests(model) {
|
|
293
|
+
const listed = model.tests.filter((t) => !t.dropped);
|
|
294
|
+
if (listed.length === 0) return null;
|
|
295
|
+
const parts = [`## Related tests (${listed.length})`, listed.map(markdown.testLine).join('\n')];
|
|
296
|
+
if (model.withTests) {
|
|
297
|
+
parts.push(...listed.filter(hasTestContent).map(markdown.testContent));
|
|
298
|
+
}
|
|
299
|
+
return parts.join('\n\n');
|
|
300
|
+
},
|
|
301
|
+
|
|
302
|
+
content(item) {
|
|
303
|
+
return `### ${code(item.path)}\n\n${fence(item.content, fenceLanguage(item.path))}`;
|
|
304
|
+
},
|
|
305
|
+
|
|
306
|
+
contents(model) {
|
|
307
|
+
if (!model.withFiles) return null;
|
|
308
|
+
const shown = model.contents.filter(hasContent);
|
|
309
|
+
const notes = model.contents.filter((c) => c.note).map((c) => `${code(c.path)} (${c.note})`);
|
|
310
|
+
if (shown.length === 0 && notes.length === 0) return null;
|
|
311
|
+
const parts = [`## Changed file contents (${shown.length})`, ...shown.map(markdown.content)];
|
|
312
|
+
if (notes.length) parts.push(`_Not included: ${notes.join(', ')}._`);
|
|
313
|
+
return parts.join('\n\n');
|
|
314
|
+
},
|
|
315
|
+
|
|
316
|
+
request(model) {
|
|
317
|
+
const parts = [];
|
|
318
|
+
if (model.instructions) parts.push(`## Instructions\n\n${model.instructions.trim()}`);
|
|
319
|
+
if (model.ask) parts.push(`## Question\n\n${model.ask.trim()}`);
|
|
320
|
+
return parts.length ? parts.join('\n\n') : null;
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
|
|
324
|
+
const hasContent = (item) => typeof item.content === 'string' && !item.dropped;
|
|
325
|
+
const hasTestContent = (test) => typeof test.content === 'string' && !test.contentDropped;
|
|
326
|
+
|
|
327
|
+
// ---------------------------------------------------------------------------
|
|
328
|
+
// XML
|
|
329
|
+
// ---------------------------------------------------------------------------
|
|
330
|
+
|
|
331
|
+
/** XML renderer with a running document index, unique across the whole output */
|
|
332
|
+
function createXml() {
|
|
333
|
+
let index = 0;
|
|
334
|
+
const document = (source, content) =>
|
|
335
|
+
`<document index="${++index}">\n<source>${source}</source>\n<document_content>\n${content.replace(/\n$/, '')}\n</document_content>\n</document>`;
|
|
336
|
+
const documents = (docs) => `<documents>\n${docs.join('\n')}\n</documents>`;
|
|
337
|
+
const methodAttrs = (m) =>
|
|
338
|
+
`file="${escapeAttr(m.file)}" name="${escapeAttr(m.name)}" lines="${lineRange(m)}" change="${m.change}"`;
|
|
339
|
+
|
|
340
|
+
const xml = {
|
|
341
|
+
summary(model) {
|
|
342
|
+
const lines = summaryFacts(model).map(([key, value]) => `${key}: ${value}`);
|
|
343
|
+
return `<summary>\n${lines.join('\n')}\n</summary>`;
|
|
344
|
+
},
|
|
345
|
+
|
|
346
|
+
commit(commit, model) {
|
|
347
|
+
const body = commit.body && !model.logBodiesDropped ? `\n\n${commit.body}` : '';
|
|
348
|
+
return `<commit sha="${commit.short}" author="${escapeAttr(commit.author)}" date="${commit.date}">\n${commit.subject}${body}\n</commit>`;
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
commits(model) {
|
|
352
|
+
if (model.commits.length === 0 || model.logDropped) return null;
|
|
353
|
+
const list = model.commits.map((c) => xml.commit(c, model)).join('\n');
|
|
354
|
+
return `<commits count="${model.commits.length}">\n${list}\n</commits>`;
|
|
355
|
+
},
|
|
356
|
+
|
|
357
|
+
files(model) {
|
|
358
|
+
if (model.filesDropped) return null;
|
|
359
|
+
const rows = model.files.map((file) => {
|
|
360
|
+
const attrs = [
|
|
361
|
+
`status="${escapeAttr(statusWord(file))}"`,
|
|
362
|
+
`path="${escapeAttr(file.path)}"`,
|
|
363
|
+
];
|
|
364
|
+
if (file.oldPath && file.oldPath !== file.path) {
|
|
365
|
+
attrs.push(`old_path="${escapeAttr(file.oldPath)}"`);
|
|
366
|
+
}
|
|
367
|
+
if (file.binary) attrs.push('binary="true"');
|
|
368
|
+
else attrs.push(`additions="${file.additions}"`, `deletions="${file.deletions}"`);
|
|
369
|
+
if (file.excluded) attrs.push(`content_excluded_by="${escapeAttr(file.excluded)}"`);
|
|
370
|
+
return `<file ${attrs.join(' ')}/>`;
|
|
371
|
+
});
|
|
372
|
+
return `<changed_files count="${model.files.length}">\n${rows.join('\n')}\n</changed_files>`;
|
|
373
|
+
},
|
|
374
|
+
|
|
375
|
+
method(method) {
|
|
376
|
+
const source = `${method.file}:${lineRange(method)} (${method.name}, ${method.change})`;
|
|
377
|
+
return document(escapeAttr(source), method.source);
|
|
378
|
+
},
|
|
379
|
+
|
|
380
|
+
methods(model) {
|
|
381
|
+
if (model.methodsDropped) return null;
|
|
382
|
+
if (model.methods.length === 0 && model.deletedMethods.length === 0) return null;
|
|
383
|
+
const parts = [`<changed_methods count="${model.methods.length}">`];
|
|
384
|
+
const withSource = model.methods.filter((m) => !m.sourceDropped);
|
|
385
|
+
const withoutSource = model.methods.filter((m) => m.sourceDropped);
|
|
386
|
+
if (withSource.length) parts.push(documents(withSource.map(xml.method)));
|
|
387
|
+
for (const method of withoutSource) {
|
|
388
|
+
parts.push(`<method ${methodAttrs(method)} source="${BUDGET_NOTE}"/>`);
|
|
389
|
+
}
|
|
390
|
+
if (model.deletedMethods.length) {
|
|
391
|
+
const rows = model.deletedMethods.map(
|
|
392
|
+
(m) =>
|
|
393
|
+
`<method file="${escapeAttr(m.file)}" name="${escapeAttr(m.name)}" base_lines="${lineRange(m)}"/>`
|
|
394
|
+
);
|
|
395
|
+
parts.push(
|
|
396
|
+
`<deleted_methods count="${rows.length}">\n${rows.join('\n')}\n</deleted_methods>`
|
|
397
|
+
);
|
|
398
|
+
}
|
|
399
|
+
parts.push('</changed_methods>');
|
|
400
|
+
return parts.join('\n');
|
|
401
|
+
},
|
|
402
|
+
|
|
403
|
+
diff(model) {
|
|
404
|
+
if (!model.diffEnabled) return null;
|
|
405
|
+
const shown = model.diffs.filter((d) => !d.dropped);
|
|
406
|
+
const excluded = model.files
|
|
407
|
+
.filter((f) => f.excluded)
|
|
408
|
+
.map((f) => `<excluded path="${escapeAttr(f.path)}" by="${escapeAttr(f.excluded)}"/>`);
|
|
409
|
+
if (shown.length === 0 && excluded.length === 0) return null;
|
|
410
|
+
const parts = ['<diff>'];
|
|
411
|
+
if (shown.length) parts.push(shown.map((d) => d.text).join('\n'));
|
|
412
|
+
parts.push(...excluded, '</diff>');
|
|
413
|
+
return parts.join('\n');
|
|
414
|
+
},
|
|
415
|
+
|
|
416
|
+
testLine(test) {
|
|
417
|
+
const excluded = test.excluded ? ` content_excluded_by="${escapeAttr(test.excluded)}"` : '';
|
|
418
|
+
return `<test path="${escapeAttr(test.path)}" reasons="${escapeAttr(test.reasons.join('; '))}"${excluded}/>`;
|
|
419
|
+
},
|
|
420
|
+
|
|
421
|
+
testContent(test) {
|
|
422
|
+
return document(escapeAttr(test.path), test.content);
|
|
423
|
+
},
|
|
424
|
+
|
|
425
|
+
tests(model) {
|
|
426
|
+
const listed = model.tests.filter((t) => !t.dropped);
|
|
427
|
+
if (listed.length === 0) return null;
|
|
428
|
+
const parts = [`<related_tests count="${listed.length}">`, ...listed.map(xml.testLine)];
|
|
429
|
+
const docs = model.withTests ? listed.filter(hasTestContent).map(xml.testContent) : [];
|
|
430
|
+
if (docs.length) parts.push(documents(docs));
|
|
431
|
+
parts.push('</related_tests>');
|
|
432
|
+
return parts.join('\n');
|
|
433
|
+
},
|
|
434
|
+
|
|
435
|
+
content(item) {
|
|
436
|
+
return document(escapeAttr(item.path), item.content);
|
|
437
|
+
},
|
|
438
|
+
|
|
439
|
+
contents(model) {
|
|
440
|
+
if (!model.withFiles) return null;
|
|
441
|
+
const shown = model.contents.filter(hasContent);
|
|
442
|
+
const notes = model.contents
|
|
443
|
+
.filter((c) => c.note)
|
|
444
|
+
.map((c) => `<omitted path="${escapeAttr(c.path)}" reason="${escapeAttr(c.note)}"/>`);
|
|
445
|
+
if (shown.length === 0 && notes.length === 0) return null;
|
|
446
|
+
const parts = ['<changed_file_contents>'];
|
|
447
|
+
if (shown.length) parts.push(documents(shown.map(xml.content)));
|
|
448
|
+
parts.push(...notes, '</changed_file_contents>');
|
|
449
|
+
return parts.join('\n');
|
|
450
|
+
},
|
|
451
|
+
|
|
452
|
+
request(model) {
|
|
453
|
+
const parts = [];
|
|
454
|
+
if (model.instructions) {
|
|
455
|
+
parts.push(`<instructions>\n${model.instructions.trim()}\n</instructions>`);
|
|
456
|
+
}
|
|
457
|
+
if (model.ask) parts.push(`<question>\n${model.ask.trim()}\n</question>`);
|
|
458
|
+
return parts.length ? parts.join('\n\n') : null;
|
|
459
|
+
},
|
|
460
|
+
};
|
|
461
|
+
return xml;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
// ---------------------------------------------------------------------------
|
|
465
|
+
// JSON
|
|
466
|
+
// ---------------------------------------------------------------------------
|
|
467
|
+
|
|
468
|
+
const json = {
|
|
469
|
+
summary(model) {
|
|
470
|
+
const summary = {
|
|
471
|
+
mode: model.mode,
|
|
472
|
+
base: model.base,
|
|
473
|
+
head: model.head,
|
|
474
|
+
mergeBase: model.mergeBase,
|
|
475
|
+
scope: model.scope || null,
|
|
476
|
+
stats: model.stats,
|
|
477
|
+
impact: model.impact,
|
|
478
|
+
diff: model.diffEnabled,
|
|
479
|
+
};
|
|
480
|
+
if (model.tokens) summary.tokens = model.tokens;
|
|
481
|
+
if (model.budget) summary.budget = model.budget;
|
|
482
|
+
return summary;
|
|
483
|
+
},
|
|
484
|
+
|
|
485
|
+
commit(commit, model) {
|
|
486
|
+
const { sha, short, author, date, subject, body } = commit;
|
|
487
|
+
return model.logBodiesDropped
|
|
488
|
+
? { sha, short, author, date, subject }
|
|
489
|
+
: { sha, short, author, date, subject, body };
|
|
490
|
+
},
|
|
491
|
+
|
|
492
|
+
commits(model) {
|
|
493
|
+
if (model.commits.length === 0 || model.logDropped) return null;
|
|
494
|
+
return model.commits.map((commit) => json.commit(commit, model));
|
|
495
|
+
},
|
|
496
|
+
|
|
497
|
+
files(model) {
|
|
498
|
+
if (model.filesDropped) return null;
|
|
499
|
+
return model.files.map((file) => ({
|
|
500
|
+
status: file.status,
|
|
501
|
+
path: file.path,
|
|
502
|
+
oldPath: file.oldPath,
|
|
503
|
+
similarity: file.similarity,
|
|
504
|
+
additions: file.additions,
|
|
505
|
+
deletions: file.deletions,
|
|
506
|
+
binary: file.binary,
|
|
507
|
+
untracked: file.untracked || undefined,
|
|
508
|
+
contentExcludedBy: file.excluded || undefined,
|
|
509
|
+
}));
|
|
510
|
+
},
|
|
511
|
+
|
|
512
|
+
method(method) {
|
|
513
|
+
const { file, name, start, end, change, changedLines } = method;
|
|
514
|
+
return {
|
|
515
|
+
file,
|
|
516
|
+
name,
|
|
517
|
+
start,
|
|
518
|
+
end,
|
|
519
|
+
change,
|
|
520
|
+
changedLines,
|
|
521
|
+
source: method.sourceDropped ? null : method.source,
|
|
522
|
+
};
|
|
523
|
+
},
|
|
524
|
+
|
|
525
|
+
methods(model) {
|
|
526
|
+
if (model.methodsDropped) return null;
|
|
527
|
+
if (model.methods.length === 0 && model.deletedMethods.length === 0) return null;
|
|
528
|
+
return {
|
|
529
|
+
changed: model.methods.map(json.method),
|
|
530
|
+
deleted: model.deletedMethods.map(({ file, name, start, end }) => ({
|
|
531
|
+
file,
|
|
532
|
+
name,
|
|
533
|
+
start,
|
|
534
|
+
end,
|
|
535
|
+
})),
|
|
536
|
+
};
|
|
537
|
+
},
|
|
538
|
+
|
|
539
|
+
diff(model) {
|
|
540
|
+
if (!model.diffEnabled) return null;
|
|
541
|
+
const files = [
|
|
542
|
+
...model.diffs.filter((d) => !d.dropped).map((d) => ({ path: d.path, diff: d.text })),
|
|
543
|
+
...model.files
|
|
544
|
+
.filter((f) => f.excluded)
|
|
545
|
+
.map((f) => ({ path: f.path, contentExcludedBy: f.excluded })),
|
|
546
|
+
];
|
|
547
|
+
return files.length ? files : null;
|
|
548
|
+
},
|
|
549
|
+
|
|
550
|
+
testLine(test) {
|
|
551
|
+
const entry = { path: test.path, reasons: test.reasons, changed: test.changed };
|
|
552
|
+
if (test.excluded) entry.contentExcludedBy = test.excluded;
|
|
553
|
+
return entry;
|
|
554
|
+
},
|
|
555
|
+
|
|
556
|
+
testContent(test) {
|
|
557
|
+
return { path: test.path, content: test.content };
|
|
558
|
+
},
|
|
559
|
+
|
|
560
|
+
tests(model) {
|
|
561
|
+
const listed = model.tests.filter((t) => !t.dropped);
|
|
562
|
+
if (listed.length === 0) return null;
|
|
563
|
+
return listed.map((test) => {
|
|
564
|
+
const entry = json.testLine(test);
|
|
565
|
+
if (model.withTests && hasTestContent(test)) entry.content = test.content;
|
|
566
|
+
return entry;
|
|
567
|
+
});
|
|
568
|
+
},
|
|
569
|
+
|
|
570
|
+
content(item) {
|
|
571
|
+
return { path: item.path, content: item.content };
|
|
572
|
+
},
|
|
573
|
+
|
|
574
|
+
contents(model) {
|
|
575
|
+
if (!model.withFiles) return null;
|
|
576
|
+
const list = model.contents
|
|
577
|
+
.filter((item) => hasContent(item) || item.note)
|
|
578
|
+
.map((item) => (item.note ? { path: item.path, omitted: item.note } : json.content(item)));
|
|
579
|
+
return list.length ? list : null;
|
|
580
|
+
},
|
|
581
|
+
|
|
582
|
+
request(model) {
|
|
583
|
+
if (!model.instructions && !model.ask) return null;
|
|
584
|
+
return { instructions: model.instructions || null, question: model.ask || null };
|
|
585
|
+
},
|
|
586
|
+
};
|
|
587
|
+
|
|
588
|
+
const JSON_KEYS = {
|
|
589
|
+
summary: 'summary',
|
|
590
|
+
commits: 'commits',
|
|
591
|
+
files: 'files',
|
|
592
|
+
methods: 'methods',
|
|
593
|
+
diff: 'diff',
|
|
594
|
+
tests: 'relatedTests',
|
|
595
|
+
contents: 'fileContents',
|
|
596
|
+
request: 'request',
|
|
597
|
+
};
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Render every section
|
|
601
|
+
* @param {object} model
|
|
602
|
+
* @param {'markdown'|'xml'|'json'} style
|
|
603
|
+
* @returns {{output: string, sections: Object<string, string>}} The output and each
|
|
604
|
+
* non-empty section's text (JSON: the section value serialized on its own)
|
|
605
|
+
*/
|
|
606
|
+
export function renderPrContext(model, style) {
|
|
607
|
+
if (style === 'json') {
|
|
608
|
+
const object = {};
|
|
609
|
+
const sections = {};
|
|
610
|
+
for (const name of SECTION_ORDER) {
|
|
611
|
+
const value = json[name](model);
|
|
612
|
+
if (value == null) continue;
|
|
613
|
+
object[JSON_KEYS[name]] = value;
|
|
614
|
+
sections[name] = JSON.stringify(value, null, 2);
|
|
615
|
+
}
|
|
616
|
+
return { output: `${JSON.stringify(object, null, 2)}\n`, sections };
|
|
617
|
+
}
|
|
618
|
+
|
|
619
|
+
const renderer = style === 'xml' ? createXml() : markdown;
|
|
620
|
+
const sections = {};
|
|
621
|
+
for (const name of SECTION_ORDER) {
|
|
622
|
+
const text = renderer[name](model);
|
|
623
|
+
if (text) sections[name] = text;
|
|
624
|
+
}
|
|
625
|
+
const body = SECTION_ORDER.filter((name) => sections[name])
|
|
626
|
+
.map((name) => sections[name])
|
|
627
|
+
.join('\n\n');
|
|
628
|
+
const output = style === 'xml' ? `<pr_context>\n${body}\n</pr_context>\n` : `${body}\n`;
|
|
629
|
+
return { output, sections };
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
/**
|
|
633
|
+
* Text one item contributes to the output, for estimating what dropping it saves
|
|
634
|
+
* @param {'content'|'testContent'|'testLine'|'method'|'commit'} kind
|
|
635
|
+
* @param {object} item
|
|
636
|
+
* @param {object} model
|
|
637
|
+
* @param {'markdown'|'xml'|'json'} style
|
|
638
|
+
* @returns {string}
|
|
639
|
+
*/
|
|
640
|
+
export function renderItem(kind, item, model, style) {
|
|
641
|
+
if (style === 'json') return JSON.stringify(json[kind](item, model), null, 2);
|
|
642
|
+
const renderer = style === 'xml' ? createXml() : markdown;
|
|
643
|
+
return renderer[kind](item, model);
|
|
644
|
+
}
|