ctxman 3.0.0 → 3.1.0

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.
Files changed (223) hide show
  1. package/.ctxman/llm-profiles.json +1067 -39
  2. package/.ctxman/profiles/backend-team.json +13 -0
  3. package/.ctxman/profiles/default.json +12 -0
  4. package/.ctxman/profiles/devops-team.json +21 -0
  5. package/.ctxman/profiles/frontend-team.json +20 -0
  6. package/.ctxman/templates/bug-fix.json +17 -0
  7. package/.ctxman/templates/code-review.json +9 -0
  8. package/.ctxman/templates/documentation.json +9 -0
  9. package/.ctxman/templates/feature.json +9 -0
  10. package/.ctxman/templates/full-context.json +9 -0
  11. package/.ctxman/templates/refactoring.json +18 -0
  12. package/.ctxman/templates/testing.json +9 -0
  13. package/.ctxman/wizard-profiles/security-audit/profile.json +7 -1
  14. package/LICENSE +21 -0
  15. package/README.md +380 -136
  16. package/bin/cli.js +1880 -529
  17. package/bin/cm-ask.js +18 -13
  18. package/bin/cm-audit.js +124 -0
  19. package/bin/cm-count.js +151 -0
  20. package/bin/cm-gitingest.js +128 -115
  21. package/bin/cm-map.js +242 -0
  22. package/bin/cm-mcp.js +293 -0
  23. package/bin/cm-pack.js +405 -0
  24. package/bin/cm-policy.js +170 -0
  25. package/bin/cm-pr.js +248 -0
  26. package/bin/cm-search.js +178 -0
  27. package/bin/cm-update.js +128 -128
  28. package/bin/cm-why.js +197 -0
  29. package/bin/mcp-server.js +8 -24
  30. package/ctxman.js +237 -183
  31. package/examples/README.md +19 -4
  32. package/examples/github-actions/ctxman-check.yml +49 -0
  33. package/examples/wizard-profiles/security-audit/profile.json +7 -1
  34. package/index.d.ts +1422 -0
  35. package/index.js +55 -31
  36. package/lib/agents/pr-failure-agent.js +48 -45
  37. package/lib/analyzers/ai-suggester.js +451 -0
  38. package/lib/analyzers/dependency-scanner.js +505 -0
  39. package/lib/analyzers/duplicate-detector.js +264 -0
  40. package/lib/analyzers/go-method-analyzer.js +151 -110
  41. package/lib/analyzers/import-tracker.js +431 -0
  42. package/lib/analyzers/method-analyzer.js +1055 -434
  43. package/lib/analyzers/performance-dashboard.js +683 -0
  44. package/lib/analyzers/token-calculator.js +895 -504
  45. package/lib/analyzers/unused-exports-detector.js +336 -0
  46. package/lib/api/mcp/install.js +424 -0
  47. package/lib/api/mcp/prompts.js +281 -281
  48. package/lib/api/mcp/resources.js +300 -241
  49. package/lib/api/mcp/sandbox.js +89 -0
  50. package/lib/api/mcp/server.js +675 -312
  51. package/lib/api/mcp/start.js +125 -0
  52. package/lib/api/mcp/transports/HttpSseTransport.js +60 -52
  53. package/lib/api/rest/server.js +105 -28
  54. package/lib/cache/CacheManager.js +9 -17
  55. package/lib/core/Analyzer.js +16 -10
  56. package/lib/core/ContextBuilder.js +19 -28
  57. package/lib/core/Reporter.js +15 -14
  58. package/lib/core/Scanner.js +81 -12
  59. package/lib/count/index.js +100 -0
  60. package/lib/formatters/format-registry.js +394 -372
  61. package/lib/formatters/gitingest-formatter.js +656 -621
  62. package/lib/formatters/toon-formatter-v1.3.js +637 -632
  63. package/lib/formatters/toon-formatter.js +239 -240
  64. package/lib/integrations/git/BlameTracker.js +4 -4
  65. package/lib/integrations/git/DiffAnalyzer.js +38 -31
  66. package/lib/integrations/git/GitClient.js +71 -53
  67. package/lib/lsp/server.js +311 -0
  68. package/lib/map/graph.js +299 -0
  69. package/lib/map/imports.js +233 -0
  70. package/lib/map/index.js +33 -0
  71. package/lib/map/rank.js +253 -0
  72. package/lib/map/render.js +346 -0
  73. package/lib/map/resolver.js +574 -0
  74. package/lib/pack/budget.js +111 -0
  75. package/lib/pack/code-lexer.js +360 -0
  76. package/lib/pack/compress.js +414 -0
  77. package/lib/pack/index.js +354 -0
  78. package/lib/pack/renderers/common.js +134 -0
  79. package/lib/pack/renderers/index.js +24 -0
  80. package/lib/pack/renderers/json.js +47 -0
  81. package/lib/pack/renderers/markdown.js +54 -0
  82. package/lib/pack/renderers/plain.js +38 -0
  83. package/lib/pack/renderers/xml.js +85 -0
  84. package/lib/pack/select.js +177 -0
  85. package/lib/pack/split.js +182 -0
  86. package/lib/pack/tree.js +35 -0
  87. package/lib/parsers/gitignore-parser.js +113 -107
  88. package/lib/parsers/method-filter-parser.js +50 -33
  89. package/lib/plugins/ExporterPlugin.js +3 -3
  90. package/lib/plugins/LanguagePlugin.js +3 -3
  91. package/lib/plugins/PluginManager.js +7 -13
  92. package/lib/policy/audit.js +294 -0
  93. package/lib/policy/enforce.js +125 -0
  94. package/lib/policy/ignore-files.js +169 -0
  95. package/lib/policy/index.js +52 -0
  96. package/lib/policy/policy.js +489 -0
  97. package/lib/pr/diff-parser.js +267 -0
  98. package/lib/pr/method-mapper.js +148 -0
  99. package/lib/pr/pr-context.js +829 -0
  100. package/lib/pr/pr-git.js +149 -0
  101. package/lib/pr/pr-renderer.js +644 -0
  102. package/lib/pr/related-tests.js +296 -0
  103. package/lib/rag/DataSourcePlugin.js +5 -5
  104. package/lib/rag/EmbeddingProvider.js +19 -8
  105. package/lib/rag/Indexer.js +14 -5
  106. package/lib/rag/VectorStoreFactory.js +3 -3
  107. package/lib/rag/sources/FileDataSource.js +6 -6
  108. package/lib/rag/stores/BaseVectorStore.js +5 -5
  109. package/lib/rag/stores/ChromaDBStore.js +10 -10
  110. package/lib/rag/stores/LanceDBStore.js +29 -19
  111. package/lib/rag/stores/PineconeStore.js +11 -9
  112. package/lib/search/bm25.js +433 -0
  113. package/lib/search/code-units.js +201 -0
  114. package/lib/search/symbols.js +189 -0
  115. package/lib/ui/dashboard.js +120 -89
  116. package/lib/ui/index.js +1 -6
  117. package/lib/ui/init-wizard-ui.js +550 -0
  118. package/lib/ui/progress-bar.js +94 -88
  119. package/lib/ui/select-input.js +40 -32
  120. package/lib/ui/template-selector.js +99 -0
  121. package/lib/ui/wizard.js +374 -329
  122. package/lib/utils/claude-token-counter.js +151 -0
  123. package/lib/utils/cli-output.js +17 -0
  124. package/lib/utils/clipboard-utils.js +49 -50
  125. package/lib/utils/config-utils.js +81 -66
  126. package/lib/utils/context-versioning.js +532 -0
  127. package/lib/utils/error-handler.js +112 -112
  128. package/lib/utils/file-utils.js +153 -69
  129. package/lib/utils/format-converter.js +356 -283
  130. package/lib/utils/git-utils.js +366 -294
  131. package/lib/utils/llm-cost-estimator.js +281 -0
  132. package/lib/utils/llm-detector.js +242 -78
  133. package/lib/utils/logger.js +243 -237
  134. package/lib/utils/messages.js +355 -0
  135. package/lib/utils/multi-repo-manager.js +364 -0
  136. package/lib/utils/profile-manager.js +380 -0
  137. package/lib/utils/safe-read.js +61 -0
  138. package/lib/utils/secret-redactor.js +61 -0
  139. package/lib/utils/snapshot-manager.js +679 -0
  140. package/lib/utils/template-manager.js +378 -0
  141. package/lib/utils/token-utils.js +201 -170
  142. package/lib/utils/tokenizer-adapter.js +421 -377
  143. package/lib/utils/updater.js +539 -506
  144. package/lib/watch/FileWatcher.js +12 -8
  145. package/lib/watch/IncrementalAnalyzer.js +7 -7
  146. package/lib/why/coupling.js +81 -0
  147. package/lib/why/errors.js +23 -0
  148. package/lib/why/git.js +169 -0
  149. package/lib/why/history.js +108 -0
  150. package/lib/why/index.js +720 -0
  151. package/lib/why/ownership.js +79 -0
  152. package/lib/why/paths.js +207 -0
  153. package/lib/why/references.js +259 -0
  154. package/lib/why/render.js +722 -0
  155. package/lib/why/search.js +358 -0
  156. package/lib/why/target.js +389 -0
  157. package/lib/wizards/config-generator.js +255 -0
  158. package/lib/wizards/project-detector.js +571 -0
  159. package/package.json +44 -10
  160. package/.ctxman/logs/ctxman-2026-07-02.log +0 -156
  161. package/.ctxman/self-knowledge/documents.lance/_transactions/0-b3dd1558-f95b-44b9-a154-18e8c330ae7a.txn +0 -0
  162. package/.ctxman/self-knowledge/documents.lance/_transactions/1-44bff6d4-e552-4010-a769-e09ad822698d.txn +0 -0
  163. package/.ctxman/self-knowledge/documents.lance/_transactions/10-37260794-5947-4ecb-b4c8-d732f58f40f1.txn +0 -0
  164. package/.ctxman/self-knowledge/documents.lance/_transactions/11-0d52a883-5518-463c-9b6d-986c3af89dfd.txn +0 -0
  165. package/.ctxman/self-knowledge/documents.lance/_transactions/12-ae354646-52f2-49c9-90fb-2e6ed8932663.txn +0 -0
  166. package/.ctxman/self-knowledge/documents.lance/_transactions/13-5d7d5e57-27da-498b-b734-091b9d38da13.txn +0 -0
  167. package/.ctxman/self-knowledge/documents.lance/_transactions/14-558ce928-810b-4252-a364-fdd1ee6f8a25.txn +0 -0
  168. package/.ctxman/self-knowledge/documents.lance/_transactions/15-27e3a58f-8b64-43b8-a9f4-e3369446968b.txn +0 -0
  169. package/.ctxman/self-knowledge/documents.lance/_transactions/16-a110c390-e5c8-4464-b976-bc7f5195fa15.txn +0 -0
  170. package/.ctxman/self-knowledge/documents.lance/_transactions/17-97513225-53de-4216-b61a-6337bd0cc4ad.txn +0 -0
  171. package/.ctxman/self-knowledge/documents.lance/_transactions/18-5ba450f0-c527-459c-bf4f-6d79b5e4285f.txn +0 -0
  172. package/.ctxman/self-knowledge/documents.lance/_transactions/19-600d3a63-5225-49e9-b8b7-30e037b564ab.txn +0 -0
  173. package/.ctxman/self-knowledge/documents.lance/_transactions/2-48a61b4e-92cf-4342-8376-914c1b99a9f9.txn +0 -0
  174. package/.ctxman/self-knowledge/documents.lance/_transactions/20-121dd912-5410-410e-84ff-82c7a23604a0.txn +0 -0
  175. package/.ctxman/self-knowledge/documents.lance/_transactions/3-bc6844ab-7ea3-4bb3-9ee1-538ce5664a9c.txn +0 -0
  176. package/.ctxman/self-knowledge/documents.lance/_transactions/4-d5e1754f-dbb5-454e-a9be-2a7da8d7bcf9.txn +0 -0
  177. package/.ctxman/self-knowledge/documents.lance/_transactions/5-4fae93df-0568-4b6f-8acf-8bef4bba6047.txn +0 -0
  178. package/.ctxman/self-knowledge/documents.lance/_transactions/6-4b1b8b87-068e-49a3-ba2b-d8dae628b252.txn +0 -0
  179. package/.ctxman/self-knowledge/documents.lance/_transactions/7-63d040fb-d2fa-4c0f-bed0-53411918a7b0.txn +0 -0
  180. package/.ctxman/self-knowledge/documents.lance/_transactions/8-c5628cf6-264c-420f-aab8-bc96a9083591.txn +0 -0
  181. package/.ctxman/self-knowledge/documents.lance/_transactions/9-c94570aa-5e53-4fcb-971f-cecc1d1d5492.txn +0 -0
  182. package/.ctxman/self-knowledge/documents.lance/_versions/1.manifest +0 -0
  183. package/.ctxman/self-knowledge/documents.lance/_versions/10.manifest +0 -0
  184. package/.ctxman/self-knowledge/documents.lance/_versions/11.manifest +0 -0
  185. package/.ctxman/self-knowledge/documents.lance/_versions/12.manifest +0 -0
  186. package/.ctxman/self-knowledge/documents.lance/_versions/13.manifest +0 -0
  187. package/.ctxman/self-knowledge/documents.lance/_versions/14.manifest +0 -0
  188. package/.ctxman/self-knowledge/documents.lance/_versions/15.manifest +0 -0
  189. package/.ctxman/self-knowledge/documents.lance/_versions/16.manifest +0 -0
  190. package/.ctxman/self-knowledge/documents.lance/_versions/17.manifest +0 -0
  191. package/.ctxman/self-knowledge/documents.lance/_versions/18.manifest +0 -0
  192. package/.ctxman/self-knowledge/documents.lance/_versions/19.manifest +0 -0
  193. package/.ctxman/self-knowledge/documents.lance/_versions/2.manifest +0 -0
  194. package/.ctxman/self-knowledge/documents.lance/_versions/20.manifest +0 -0
  195. package/.ctxman/self-knowledge/documents.lance/_versions/21.manifest +0 -0
  196. package/.ctxman/self-knowledge/documents.lance/_versions/3.manifest +0 -0
  197. package/.ctxman/self-knowledge/documents.lance/_versions/4.manifest +0 -0
  198. package/.ctxman/self-knowledge/documents.lance/_versions/5.manifest +0 -0
  199. package/.ctxman/self-knowledge/documents.lance/_versions/6.manifest +0 -0
  200. package/.ctxman/self-knowledge/documents.lance/_versions/7.manifest +0 -0
  201. package/.ctxman/self-knowledge/documents.lance/_versions/8.manifest +0 -0
  202. package/.ctxman/self-knowledge/documents.lance/_versions/9.manifest +0 -0
  203. package/.ctxman/self-knowledge/documents.lance/data/000010010011011101000010de8621405d9d5cb06540dbf02c.lance +0 -0
  204. package/.ctxman/self-knowledge/documents.lance/data/0000101001011100011000003fb62a4aa8978974075c144f1c.lance +0 -0
  205. package/.ctxman/self-knowledge/documents.lance/data/000011010000100111001011c6cc824d628f3bb1f4e04cd5d0.lance +0 -0
  206. package/.ctxman/self-knowledge/documents.lance/data/001000001000011001011100d8f74349ada090b332cd1df669.lance +0 -0
  207. package/.ctxman/self-knowledge/documents.lance/data/01000001111010001000010045ab44467bb1e7e4d8ae06bd58.lance +0 -0
  208. package/.ctxman/self-knowledge/documents.lance/data/01000101101110111110011131ef904241a89e1ffb6ddaf8e3.lance +0 -0
  209. package/.ctxman/self-knowledge/documents.lance/data/010010110000010000011010fbfcc145c6a8a827b28057a07e.lance +0 -0
  210. package/.ctxman/self-knowledge/documents.lance/data/1000011000101010001110003fb4f045c3835982efa1405324.lance +0 -0
  211. package/.ctxman/self-knowledge/documents.lance/data/100111011101110111101011f4396b410fa06af3b028d204e6.lance +0 -0
  212. package/.ctxman/self-knowledge/documents.lance/data/100111100111101000001100596b174e00995d6cc2969cc8dc.lance +0 -0
  213. package/.ctxman/self-knowledge/documents.lance/data/1010001100011000110010102ea16b484d887c7c9725feffd6.lance +0 -0
  214. package/.ctxman/self-knowledge/documents.lance/data/101101100010101111110111c5230944d9b3af7301b6392dda.lance +0 -0
  215. package/.ctxman/self-knowledge/documents.lance/data/1011011100100111111110105892bf41b5a530fedd7cf516f1.lance +0 -0
  216. package/.ctxman/self-knowledge/documents.lance/data/10111110000010001011110164e8774742bade87b483c10f36.lance +0 -0
  217. package/.ctxman/self-knowledge/documents.lance/data/1100000100010110001110103ffaca40a2a42c85067ebaf9ae.lance +0 -0
  218. package/.ctxman/self-knowledge/documents.lance/data/110000010001110110101110664bde4c76a78dfc14b05e83b8.lance +0 -0
  219. package/.ctxman/self-knowledge/documents.lance/data/11001100111000010100110050645c4281b1f35b2022234f7f.lance +0 -0
  220. package/.ctxman/self-knowledge/documents.lance/data/1101111011001100110111017214924a368f5719eab2246619.lance +0 -0
  221. package/.ctxman/self-knowledge/documents.lance/data/1110000111001010001000002150c24dcab653d39d07032e63.lance +0 -0
  222. package/.ctxman/self-knowledge/documents.lance/data/1110100000000100000011014d20534967a18b5c87721659ff.lance +0 -0
  223. package/.ctxman/self-knowledge/documents.lance/data/11110101111010000101011135a0114ef2b35dc6f1297a3381.lance +0 -0
package/ctxman.js CHANGED
@@ -10,227 +10,281 @@ import path from 'path';
10
10
  import TokenCalculator from './lib/analyzers/token-calculator.js';
11
11
  import GitIngestFormatter from './lib/formatters/gitingest-formatter.js';
12
12
  import TokenUtils from './lib/utils/token-utils.js';
13
+ import { loadPolicy } from './lib/policy/policy.js';
14
+ import { enforceExport, PolicyViolationError } from './lib/policy/enforce.js';
15
+ import { recordExport } from './lib/policy/audit.js';
13
16
 
14
17
  /**
15
- * Generate GitIngest digest from token-analysis-report.json
18
+ * Apply the export policy of `projectRoot` to a digest built from a saved file list:
19
+ * denied files are dropped (the list may predate the policy) and the rest is checked
20
+ * @returns {{files: object[], audit: Function}} Remaining files, and a function that logs
21
+ * the written digest
16
22
  */
17
- function generateDigestFromReport(reportPath) {
18
- console.log('📄 Loading token analysis report...');
19
-
20
- if (!fs.existsSync(reportPath)) {
21
- console.error(`❌ Error: Report file not found: ${reportPath}`);
22
- process.exit(1);
23
+ function applyDigestPolicy(projectRoot, files, tokens, command) {
24
+ const policy = loadPolicy(projectRoot);
25
+ if (!policy.active) return { files, audit: () => {} };
26
+
27
+ const { kept, removed } = policy.filterFiles(files);
28
+ if (removed.length > 0) {
29
+ console.log(`🛡️ Removed ${removed.length} file(s) denied by the export policy`);
30
+ }
31
+ const event = {
32
+ command,
33
+ root: projectRoot,
34
+ format: 'gitingest',
35
+ files: kept,
36
+ tokens,
37
+ removedByPolicy: removed.length,
38
+ };
39
+ try {
40
+ enforceExport(policy, event);
41
+ } catch (error) {
42
+ if (error instanceof PolicyViolationError) {
43
+ recordExport(policy, { ...event, outcome: 'refused', violations: error.violations });
23
44
  }
45
+ throw error;
46
+ }
47
+
48
+ const audit = (digestPath, formatter) => {
49
+ if (!policy.audit.enabled) return;
50
+ const written = formatter.chunkFiles?.length ? formatter.chunkFiles : [digestPath];
51
+ recordExport(policy, {
52
+ ...event,
53
+ destination: digestPath,
54
+ outputFiles: written.length > 1 ? written : undefined,
55
+ redactions: formatter.redactions,
56
+ text: written.map((file) => fs.readFileSync(file)),
57
+ });
58
+ };
59
+ return { files: kept, audit };
60
+ }
24
61
 
25
- try {
26
- const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
27
-
28
- if (!report.metadata || !report.summary || !report.files) {
29
- console.error('❌ Error: Invalid report format. Missing required fields.');
30
- process.exit(1);
31
- }
32
-
33
- const projectRoot = report.metadata.projectRoot || process.cwd();
34
-
35
- console.log(`✅ Report loaded: ${report.files.length} files`);
36
- console.log('📄 Generating GitIngest digest from report...\n');
37
-
38
- const formatter = new GitIngestFormatter(
39
- projectRoot,
40
- report.summary,
41
- report.files
42
- );
62
+ /**
63
+ * Generate GitIngest digest from token-analysis-report.json
64
+ */
65
+ function generateDigestFromReport(reportPath) {
66
+ console.log('📄 Loading token analysis report...');
43
67
 
44
- const digestPath = path.join(process.cwd(), 'digest.txt');
45
- const digestSize = formatter.saveToFile(digestPath);
68
+ if (!fs.existsSync(reportPath)) {
69
+ console.error(`❌ Error: Report file not found: ${reportPath}`);
70
+ process.exit(1);
71
+ }
46
72
 
47
- console.log(`💾 GitIngest digest saved to: digest.txt`);
48
- console.log(`📊 Digest size: ${(digestSize / 1024).toFixed(1)} KB`);
49
- console.log(`✨ Generated from: ${path.basename(reportPath)}`);
73
+ try {
74
+ const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
50
75
 
51
- } catch (error) {
52
- console.error(`❌ Error processing report: ${error.message}`);
53
- process.exit(1);
76
+ if (!report.metadata || !report.summary || !report.files) {
77
+ console.error('❌ Error: Invalid report format. Missing required fields.');
78
+ process.exit(1);
54
79
  }
80
+
81
+ const projectRoot = report.metadata.projectRoot || process.cwd();
82
+
83
+ console.log(`✅ Report loaded: ${report.files.length} files`);
84
+ console.log('📄 Generating GitIngest digest from report...\n');
85
+
86
+ const reportTokens = report.files.reduce((sum, file) => sum + (Number(file.tokens) || 0), 0);
87
+ const guarded = applyDigestPolicy(
88
+ projectRoot,
89
+ report.files,
90
+ reportTokens,
91
+ 'cli --gitingest-from-report'
92
+ );
93
+ const formatter = new GitIngestFormatter(projectRoot, report.summary, guarded.files);
94
+
95
+ const digestPath = path.join(process.cwd(), 'digest.txt');
96
+ const digestSize = formatter.saveToFile(digestPath);
97
+ guarded.audit(digestPath, formatter);
98
+
99
+ console.log(`💾 GitIngest digest saved to: digest.txt`);
100
+ console.log(`📊 Digest size: ${(digestSize / 1024).toFixed(1)} KB`);
101
+ console.log(`✨ Generated from: ${path.basename(reportPath)}`);
102
+ } catch (error) {
103
+ console.error(`❌ Error processing report: ${error.message}`);
104
+ // 2: invalid policy file, 3: export refused by the policy
105
+ process.exit(error.exitCode ?? 1);
106
+ }
55
107
  }
56
108
 
57
109
  /**
58
110
  * Generate GitIngest digest from llm-context.json
59
111
  */
60
112
  function generateDigestFromContext(contextPath) {
61
- console.log('📄 Loading LLM context file...');
113
+ console.log('📄 Loading LLM context file...');
62
114
 
63
- if (!fs.existsSync(contextPath)) {
64
- console.error(`❌ Error: Context file not found: ${contextPath}`);
65
- process.exit(1);
66
- }
115
+ if (!fs.existsSync(contextPath)) {
116
+ console.error(`❌ Error: Context file not found: ${contextPath}`);
117
+ process.exit(1);
118
+ }
67
119
 
68
- try {
69
- const context = JSON.parse(fs.readFileSync(contextPath, 'utf8'));
120
+ try {
121
+ const context = JSON.parse(fs.readFileSync(contextPath, 'utf8'));
70
122
 
71
- if (!context.project || !context.paths) {
72
- console.error('❌ Error: Invalid context format. Missing required fields.');
73
- process.exit(1);
74
- }
123
+ if (!context.project || !context.paths) {
124
+ console.error('❌ Error: Invalid context format. Missing required fields.');
125
+ process.exit(1);
126
+ }
75
127
 
76
- console.log(`✅ Context loaded: ${context.project.totalFiles} files`);
77
- console.log('📄 Generating GitIngest digest from context...\n');
78
-
79
- // Extract files from context.paths structure
80
- const files = [];
81
- for (const [dirPath, fileNames] of Object.entries(context.paths)) {
82
- for (const fileName of fileNames) {
83
- const relativePath = dirPath === '/'
84
- ? fileName
85
- : `${dirPath}${fileName}`;
86
-
87
- const fullPath = path.join(process.cwd(), relativePath);
88
-
89
- if (fs.existsSync(fullPath)) {
90
- const stats = fs.statSync(fullPath);
91
- files.push({
92
- path: fullPath,
93
- relativePath: relativePath,
94
- sizeBytes: stats.size,
95
- tokens: 0,
96
- lines: 0
97
- });
98
- }
99
- }
128
+ console.log(`✅ Context loaded: ${context.project.totalFiles} files`);
129
+ console.log('📄 Generating GitIngest digest from context...\n');
130
+
131
+ // Extract files from context.paths structure
132
+ const files = [];
133
+ for (const [dirPath, fileNames] of Object.entries(context.paths)) {
134
+ for (const fileName of fileNames) {
135
+ const relativePath = dirPath === '/' ? fileName : `${dirPath}${fileName}`;
136
+
137
+ const fullPath = path.join(process.cwd(), relativePath);
138
+
139
+ if (fs.existsSync(fullPath)) {
140
+ const stats = fs.statSync(fullPath);
141
+ files.push({
142
+ path: fullPath,
143
+ relativePath: relativePath,
144
+ sizeBytes: stats.size,
145
+ tokens: 0,
146
+ lines: 0,
147
+ });
100
148
  }
149
+ }
150
+ }
101
151
 
102
- console.log(`📁 Found ${files.length} accessible files`);
103
-
104
- const stats = {
105
- totalFiles: files.length,
106
- totalTokens: context.project.totalTokens,
107
- totalBytes: files.reduce((sum, f) => sum + f.sizeBytes, 0),
108
- totalLines: 0
109
- };
110
-
111
- const formatter = new GitIngestFormatter(
112
- process.cwd(),
113
- stats,
114
- files
115
- );
116
-
117
- const digestPath = path.join(process.cwd(), 'digest.txt');
118
- const digestSize = formatter.saveToFile(digestPath);
152
+ console.log(`📁 Found ${files.length} accessible files`);
119
153
 
120
- console.log(`💾 GitIngest digest saved to: digest.txt`);
121
- console.log(`📊 Digest size: ${(digestSize / 1024).toFixed(1)} KB`);
122
- console.log(`✨ Generated from: ${path.basename(contextPath)}`);
154
+ const stats = {
155
+ totalFiles: files.length,
156
+ totalTokens: context.project.totalTokens,
157
+ totalBytes: files.reduce((sum, f) => sum + f.sizeBytes, 0),
158
+ totalLines: 0,
159
+ };
123
160
 
124
- } catch (error) {
125
- console.error(`❌ Error processing context: ${error.message}`);
126
- process.exit(1);
127
- }
161
+ const guarded = applyDigestPolicy(
162
+ process.cwd(),
163
+ files,
164
+ stats.totalTokens,
165
+ 'cli --gitingest-from-context'
166
+ );
167
+ const formatter = new GitIngestFormatter(process.cwd(), stats, guarded.files);
168
+
169
+ const digestPath = path.join(process.cwd(), 'digest.txt');
170
+ const digestSize = formatter.saveToFile(digestPath);
171
+ guarded.audit(digestPath, formatter);
172
+
173
+ console.log(`💾 GitIngest digest saved to: digest.txt`);
174
+ console.log(`📊 Digest size: ${(digestSize / 1024).toFixed(1)} KB`);
175
+ console.log(`✨ Generated from: ${path.basename(contextPath)}`);
176
+ } catch (error) {
177
+ console.error(`❌ Error processing context: ${error.message}`);
178
+ process.exit(error.exitCode ?? 1);
179
+ }
128
180
  }
129
181
 
130
182
  /**
131
183
  * Main execution
132
184
  */
133
185
  async function main(args = process.argv.slice(2)) {
134
- if (args.includes('--help') || args.includes('-h')) {
135
- printHelp();
136
- return;
137
- }
138
-
139
- // Handle --gitingest-from-report
140
- const reportFlagIndex = args.indexOf('--gitingest-from-report');
141
- if (reportFlagIndex !== -1) {
142
- const reportPath = args[reportFlagIndex + 1] || 'token-analysis-report.json';
143
- generateDigestFromReport(reportPath);
144
- return;
145
- }
146
-
147
- // Handle --gitingest-from-context
148
- const contextFlagIndex = args.indexOf('--gitingest-from-context');
149
- if (contextFlagIndex !== -1) {
150
- const contextPath = args[contextFlagIndex + 1] || 'llm-context.json';
151
- generateDigestFromContext(contextPath);
152
- return;
153
- }
154
-
155
- const options = {
156
- saveReport: args.includes('--save-report') || args.includes('-s'),
157
- verbose: args.includes('--verbose') || args.includes('-v'),
158
- contextExport: args.includes('--context-export'),
159
- contextToClipboard: args.includes('--context-clipboard'),
160
- methodLevel: args.includes('--method-level') || args.includes('-m'),
161
- gitingest: args.includes('--gitingest') || args.includes('-g')
162
- };
163
-
164
- printStartupInfo();
165
-
166
- const calculator = new TokenCalculator(process.cwd(), options);
167
- await calculator.run();
186
+ if (args.includes('--help') || args.includes('-h')) {
187
+ printHelp();
188
+ return;
189
+ }
190
+
191
+ // Handle --gitingest-from-report
192
+ const reportFlagIndex = args.indexOf('--gitingest-from-report');
193
+ if (reportFlagIndex !== -1) {
194
+ const reportPath = args[reportFlagIndex + 1] || 'token-analysis-report.json';
195
+ generateDigestFromReport(reportPath);
196
+ return;
197
+ }
198
+
199
+ // Handle --gitingest-from-context
200
+ const contextFlagIndex = args.indexOf('--gitingest-from-context');
201
+ if (contextFlagIndex !== -1) {
202
+ const contextPath = args[contextFlagIndex + 1] || 'llm-context.json';
203
+ generateDigestFromContext(contextPath);
204
+ return;
205
+ }
206
+
207
+ const options = {
208
+ saveReport: args.includes('--save-report') || args.includes('-s'),
209
+ verbose: args.includes('--verbose') || args.includes('-v'),
210
+ contextExport: args.includes('--context-export'),
211
+ contextToClipboard: args.includes('--context-clipboard'),
212
+ methodLevel: args.includes('--method-level') || args.includes('-m'),
213
+ gitingest: args.includes('--gitingest') || args.includes('-g'),
214
+ };
215
+
216
+ try {
217
+ options.policy = loadPolicy(process.cwd());
218
+ } catch (error) {
219
+ console.error(`❌ ${error.message}`);
220
+ process.exit(error.exitCode ?? 2);
221
+ }
222
+
223
+ printStartupInfo();
224
+
225
+ const calculator = new TokenCalculator(process.cwd(), options);
226
+ await calculator.run();
168
227
  }
169
228
 
170
229
  function printStartupInfo() {
171
- console.log('🚀 Code Analyzer by Hakkı Sağdıç');
172
- console.log('='.repeat(50));
173
- console.log('📋 Available options:');
174
- console.log(' --save-report, -s Save detailed JSON report');
175
- console.log(' --verbose, -v Show included files and directories');
176
- console.log(' --context-export Generate LLM context file list');
177
- console.log(' --context-clipboard Copy context to clipboard');
178
- console.log(' --method-level, -m Enable method-level analysis');
179
- console.log(' --gitingest, -g Generate GitIngest-style digest');
180
- console.log(' --help, -h Show this help message');
181
-
182
- if (!TokenUtils.hasExactCounting()) {
183
- console.log('\n💡 For exact token counts, install tiktoken: npm install tiktoken');
184
- }
185
- console.log();
230
+ console.log('🚀 Code Analyzer by Hakkı Sağdıç');
231
+ console.log('='.repeat(50));
232
+ console.log('📋 Available options:');
233
+ console.log(' --save-report, -s Save detailed JSON report');
234
+ console.log(' --verbose, -v Show included files and directories');
235
+ console.log(' --context-export Generate LLM context file list');
236
+ console.log(' --context-clipboard Copy context to clipboard');
237
+ console.log(' --method-level, -m Enable method-level analysis');
238
+ console.log(' --gitingest, -g Generate GitIngest-style digest');
239
+ console.log(' --help, -h Show this help message');
240
+
241
+ if (!TokenUtils.hasExactCounting()) {
242
+ console.log('\n💡 For exact token counts, install tiktoken: npm install tiktoken');
243
+ }
244
+ console.log();
186
245
  }
187
246
 
188
247
  function printHelp() {
189
- console.log('Ctxman - LLM context optimization with method-level filtering');
190
- console.log();
191
- console.log('Usage: ctxman [options]');
192
- console.log(' node ctxman.js [options] # Direct usage');
193
- console.log();
194
- console.log('Options:');
195
- console.log(' -s, --save-report Save detailed JSON report');
196
- console.log(' -v, --verbose Show all included files');
197
- console.log(' --context-export Generate LLM context file');
198
- console.log(' --context-clipboard Copy context to clipboard');
199
- console.log(' -m, --method-level Enable method-level analysis');
200
- console.log(' -g, --gitingest Generate GitIngest-style digest');
201
- console.log(' --gitingest-from-report <file> Generate digest from report JSON');
202
- console.log(' --gitingest-from-context <file> Generate digest from context JSON');
203
- console.log(' -h, --help Show this help');
204
- console.log();
205
- console.log('Method-level Configuration:');
206
- console.log(' .methodinclude Include only specified methods');
207
- console.log(' .methodignore Exclude specified methods');
208
- console.log();
209
- console.log('Examples:');
210
- console.log(' # Standard workflow');
211
- console.log(' ctxman # Interactive export selection');
212
- console.log(' ctxman --save-report # Save JSON report');
213
- console.log(' ctxman --gitingest # Generate digest.txt');
214
- console.log(' ctxman -g -s # Both digest + report');
215
- console.log();
216
- console.log(' # Generate digest from existing JSON files (fast, no re-scan)');
217
- console.log(' ctxman --gitingest-from-report token-analysis-report.json');
218
- console.log(' ctxman --gitingest-from-context llm-context.json');
219
- console.log();
220
- console.log(' # Two-step workflow');
221
- console.log(' ctxman -s # Step 1: Analyze and save report');
222
- console.log(' ctxman --gitingest-from-report token-analysis-report.json');
223
- console.log(' # Step 2: Generate digest (instant)');
248
+ console.log('Ctxman - LLM context optimization with method-level filtering');
249
+ console.log();
250
+ console.log('Usage: ctxman [options]');
251
+ console.log(' node ctxman.js [options] # Direct usage');
252
+ console.log();
253
+ console.log('Options:');
254
+ console.log(' -s, --save-report Save detailed JSON report');
255
+ console.log(' -v, --verbose Show all included files');
256
+ console.log(' --context-export Generate LLM context file');
257
+ console.log(' --context-clipboard Copy context to clipboard');
258
+ console.log(' -m, --method-level Enable method-level analysis');
259
+ console.log(' -g, --gitingest Generate GitIngest-style digest');
260
+ console.log(' --gitingest-from-report <file> Generate digest from report JSON');
261
+ console.log(' --gitingest-from-context <file> Generate digest from context JSON');
262
+ console.log(' -h, --help Show this help');
263
+ console.log();
264
+ console.log('Method-level Configuration:');
265
+ console.log(' .methodinclude Include only specified methods');
266
+ console.log(' .methodignore Exclude specified methods');
267
+ console.log();
268
+ console.log('Examples:');
269
+ console.log(' # Standard workflow');
270
+ console.log(' ctxman # Interactive export selection');
271
+ console.log(' ctxman --save-report # Save JSON report');
272
+ console.log(' ctxman --gitingest # Generate digest.txt');
273
+ console.log(' ctxman -g -s # Both digest + report');
274
+ console.log();
275
+ console.log(' # Generate digest from existing JSON files (fast, no re-scan)');
276
+ console.log(' ctxman --gitingest-from-report token-analysis-report.json');
277
+ console.log(' ctxman --gitingest-from-context llm-context.json');
278
+ console.log();
279
+ console.log(' # Two-step workflow');
280
+ console.log(' ctxman -s # Step 1: Analyze and save report');
281
+ console.log(' ctxman --gitingest-from-report token-analysis-report.json');
282
+ console.log(' # Step 2: Generate digest (instant)');
224
283
  }
225
284
 
226
285
  // ESM entry point check
227
286
  if (import.meta.url === `file://${process.argv[1]}`) {
228
- main();
287
+ main();
229
288
  }
230
289
 
231
- export {
232
- TokenCalculator,
233
- generateDigestFromReport,
234
- generateDigestFromContext,
235
- main
236
- };
290
+ export { TokenCalculator, generateDigestFromReport, generateDigestFromContext, main };
@@ -30,11 +30,13 @@ Wizard profiles are pre-configured analysis templates that optimize context gene
30
30
  ### Active vs Reference Profiles
31
31
 
32
32
  **Active Profiles:**
33
+
33
34
  - Location: `.ctxman/wizard-profiles/`
34
35
  - Used by the wizard during interactive setup
35
36
  - Can be modified or deleted by users
36
37
 
37
38
  **Reference Profiles (this directory):**
39
+
38
40
  - Location: `examples/wizard-profiles/`
39
41
  - Backup copies of default profiles
40
42
  - Use these to restore or create custom profiles
@@ -42,36 +44,42 @@ Wizard profiles are pre-configured analysis templates that optimize context gene
42
44
  ## 📋 Available Profiles
43
45
 
44
46
  ### 👀 Code Review
47
+
45
48
  **Purpose:** Reviewing code changes and PRs
46
49
  **Token Budget:** 20K-80K tokens
47
50
  **Includes:** Source code, tests, configurations
48
51
  **Best For:** Pull requests, architecture understanding
49
52
 
50
53
  ### 🔒 Security Audit
54
+
51
55
  **Purpose:** Security assessments and vulnerability analysis
52
56
  **Token Budget:** 15K-60K tokens
53
57
  **Includes:** Auth modules, API endpoints, database code
54
58
  **Best For:** Pre-deployment security reviews, compliance audits
55
59
 
56
60
  ### 💡 LLM Explain
61
+
57
62
  **Purpose:** Explaining code architecture to AI
58
63
  **Token Budget:** 25K-100K tokens
59
64
  **Includes:** Core source, entry points, documentation
60
65
  **Best For:** Understanding unfamiliar codebases, onboarding
61
66
 
62
67
  ### 📚 Documentation
68
+
63
69
  **Purpose:** Generating API docs and guides
64
70
  **Token Budget:** 18K-70K tokens
65
71
  **Includes:** Public APIs, type definitions, exports
66
72
  **Best For:** API documentation, user guides, README updates
67
73
 
68
74
  ### 🎯 Minimal
75
+
69
76
  **Purpose:** Quick queries with minimal token usage
70
77
  **Token Budget:** 5K-25K tokens
71
78
  **Includes:** Core source only
72
79
  **Best For:** Bug fixes, focused debugging, token-limited scenarios
73
80
 
74
81
  ### 📦 Full
82
+
75
83
  **Purpose:** Comprehensive codebase analysis
76
84
  **Token Budget:** 50K-500K+ tokens
77
85
  **Includes:** Everything (all source, tests, docs)
@@ -119,6 +127,7 @@ touch .ctxman/wizard-profiles/my-profile/.methodignore
119
127
  ```
120
128
 
121
129
  **profile.json structure:**
130
+
122
131
  ```json
123
132
  {
124
133
  "id": "my-profile",
@@ -138,10 +147,7 @@ touch .ctxman/wizard-profiles/my-profile/.methodignore
138
147
  "files": ["What files are excluded"],
139
148
  "methods": ["What methods are excluded"]
140
149
  },
141
- "bestFor": [
142
- "Use case 1",
143
- "Use case 2"
144
- ]
150
+ "bestFor": ["Use case 1", "Use case 2"]
145
151
  }
146
152
  ```
147
153
 
@@ -160,6 +166,7 @@ nano .ctxman/custom-profiles.json
160
166
  ```
161
167
 
162
168
  **Example custom LLM profile:**
169
+
163
170
  ```json
164
171
  {
165
172
  "models": [
@@ -215,6 +222,7 @@ init* # Initialization methods
215
222
  When you select a profile in the wizard:
216
223
 
217
224
  1. **Profile files are copied** with profile name suffix:
225
+
218
226
  ```
219
227
  .contextinclude-code-review
220
228
  .contextignore-code-review
@@ -223,6 +231,7 @@ When you select a profile in the wizard:
223
231
  ```
224
232
 
225
233
  2. **Active configs are created** (copies for current analysis):
234
+
226
235
  ```
227
236
  .contextinclude → copy of .contextinclude-code-review
228
237
  .contextignore → copy of .contextignore-code-review
@@ -241,16 +250,19 @@ When you select a profile in the wizard:
241
250
  ## 🎯 Best Practices
242
251
 
243
252
  ### Profile Naming
253
+
244
254
  - Use lowercase with hyphens: `my-custom-profile`
245
255
  - Choose descriptive names: `react-component-review`, `api-security-audit`
246
256
 
247
257
  ### Token Budgets
258
+
248
259
  - **Minimal:** 5K-25K tokens
249
260
  - **Focused:** 20K-80K tokens (code-review, security-audit)
250
261
  - **Balanced:** 50K-100K tokens (llm-explain, documentation)
251
262
  - **Comprehensive:** 100K-500K+ tokens (full)
252
263
 
253
264
  ### Filter Design
265
+
254
266
  - **Include mode** (`.contextinclude` / `.methodinclude`): Best for focused analysis
255
267
  - **Exclude mode** (`.contextignore` / `.methodignore`): Best for broad analysis with exceptions
256
268
  - **Combined mode**: Use both for fine-grained control
@@ -264,16 +276,19 @@ When you select a profile in the wizard:
264
276
  ## 🆘 Troubleshooting
265
277
 
266
278
  **Profile not appearing in wizard?**
279
+
267
280
  - Ensure `profile.json` exists and is valid JSON
268
281
  - Check that profile directory is in `.ctxman/wizard-profiles/`
269
282
  - Profile ID must match directory name
270
283
 
271
284
  **Filters not working?**
285
+
272
286
  - Check `.gitignore` first (it always takes precedence)
273
287
  - Verify pattern syntax (use `**` for recursive matching)
274
288
  - Test patterns with `ctxman --analyze`
275
289
 
276
290
  **Need to reset everything?**
291
+
277
292
  ```bash
278
293
  # Remove all active profiles and configs
279
294
  rm -rf .ctxman/wizard-profiles
@@ -0,0 +1,49 @@
1
+ # ctxman context check for pull requests
2
+ #
3
+ # Copy this file to .github/workflows/ctxman-check.yml in your repository. On every pull
4
+ # request it counts the tokens of the files the pull request changes, fails when they
5
+ # exceed the budget, reports whether they fit Claude Sonnet 4.5's context window in the
6
+ # job summary and uploads the TOON context file as an artifact.
7
+ #
8
+ # Pin the action to a release tag or a commit SHA instead of main for reproducible runs.
9
+ # Inputs, outputs and limits: https://github.com/hakkisagdic/ctxman/blob/main/docs/commands/ci.md
10
+
11
+ name: ctxman context check
12
+
13
+ on:
14
+ pull_request:
15
+
16
+ permissions:
17
+ contents: read
18
+
19
+ jobs:
20
+ context:
21
+ name: Context size
22
+ runs-on: ubuntu-latest
23
+ timeout-minutes: 10
24
+
25
+ steps:
26
+ - name: Checkout repository
27
+ uses: actions/checkout@v4
28
+ with:
29
+ persist-credentials: false
30
+ # changed-since needs the base commit of the pull request in the checkout
31
+ fetch-depth: 0
32
+
33
+ - name: ctxman context check
34
+ id: ctxman
35
+ uses: hakkisagdic/ctxman@main
36
+ with:
37
+ changed-since: ${{ github.event.pull_request.base.sha }}
38
+ max-tokens: 50000
39
+ target-model: claude-sonnet-5-5
40
+ export: toon
41
+ upload-artifact: true
42
+
43
+ - name: Use the outputs
44
+ if: ${{ !cancelled() }}
45
+ env:
46
+ TOTAL_TOKENS: ${{ steps.ctxman.outputs.total-tokens }}
47
+ TOTAL_FILES: ${{ steps.ctxman.outputs.total-files }}
48
+ FITS: ${{ steps.ctxman.outputs.fits }}
49
+ run: echo "The pull request changes $TOTAL_FILES files with $TOTAL_TOKENS tokens (fits Claude Sonnet 4.5 - $FITS)"
@@ -10,7 +10,13 @@
10
10
  },
11
11
  "includes": {
12
12
  "files": ["Auth modules", "API endpoints", "Middleware", "Database code", "Security configs"],
13
- "methods": ["Authentication", "Validation", "Cryptography", "Database queries", "File operations"]
13
+ "methods": [
14
+ "Authentication",
15
+ "Validation",
16
+ "Cryptography",
17
+ "Database queries",
18
+ "File operations"
19
+ ]
14
20
  },
15
21
  "excludes": {
16
22
  "files": ["Tests", "Documentation", "Build artifacts"],