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
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
|
-
*
|
|
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
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
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
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
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
|
-
|
|
45
|
-
|
|
68
|
+
if (!fs.existsSync(reportPath)) {
|
|
69
|
+
console.error(`❌ Error: Report file not found: ${reportPath}`);
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
46
72
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
console.log(`✨ Generated from: ${path.basename(reportPath)}`);
|
|
73
|
+
try {
|
|
74
|
+
const report = JSON.parse(fs.readFileSync(reportPath, 'utf8'));
|
|
50
75
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
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
|
-
|
|
113
|
+
console.log('📄 Loading LLM context file...');
|
|
62
114
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
115
|
+
if (!fs.existsSync(contextPath)) {
|
|
116
|
+
console.error(`❌ Error: Context file not found: ${contextPath}`);
|
|
117
|
+
process.exit(1);
|
|
118
|
+
}
|
|
67
119
|
|
|
68
|
-
|
|
69
|
-
|
|
120
|
+
try {
|
|
121
|
+
const context = JSON.parse(fs.readFileSync(contextPath, 'utf8'));
|
|
70
122
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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
|
-
|
|
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
|
-
|
|
121
|
-
|
|
122
|
-
|
|
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
|
-
|
|
125
|
-
|
|
126
|
-
|
|
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
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
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
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
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
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
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
|
-
|
|
287
|
+
main();
|
|
229
288
|
}
|
|
230
289
|
|
|
231
|
-
export {
|
|
232
|
-
TokenCalculator,
|
|
233
|
-
generateDigestFromReport,
|
|
234
|
-
generateDigestFromContext,
|
|
235
|
-
main
|
|
236
|
-
};
|
|
290
|
+
export { TokenCalculator, generateDigestFromReport, generateDigestFromContext, main };
|
package/examples/README.md
CHANGED
|
@@ -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@v7
|
|
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": [
|
|
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"],
|