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.
- 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 +380 -136
- package/bin/cli.js +1880 -529
- package/bin/cm-ask.js +18 -13
- package/bin/cm-audit.js +124 -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 +683 -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 +675 -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 +113 -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 +294 -0
- package/lib/policy/enforce.js +125 -0
- package/lib/policy/ignore-files.js +169 -0
- package/lib/policy/index.js +52 -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/sources/FileDataSource.js +6 -6
- 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 +243 -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 +255 -0
- package/lib/wizards/project-detector.js +571 -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
|
@@ -10,316 +10,389 @@
|
|
|
10
10
|
* - JSON ↔ Markdown
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { decode as decodeToon } from '@toon-format/toon';
|
|
13
14
|
import FormatRegistry from '../formatters/format-registry.js';
|
|
14
15
|
import ToonFormatter from '../formatters/toon-formatter.js';
|
|
15
16
|
import fs from 'fs';
|
|
16
17
|
import path from 'path';
|
|
17
18
|
|
|
18
19
|
class FormatConverter {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
constructor() {
|
|
21
|
+
this.registry = new FormatRegistry();
|
|
22
|
+
this.toonFormatter = new ToonFormatter();
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Convert from one format to another
|
|
27
|
+
* @param {string} input - Input data as string
|
|
28
|
+
* @param {string} fromFormat - Source format
|
|
29
|
+
* @param {string} toFormat - Target format
|
|
30
|
+
* @returns {Object} Conversion result with output and metadata
|
|
31
|
+
*/
|
|
32
|
+
convert(input, fromFormat, toFormat) {
|
|
33
|
+
// Step 1: Parse input to JavaScript object
|
|
34
|
+
const data = this.parse(input, fromFormat);
|
|
35
|
+
|
|
36
|
+
// Step 2: Encode to target format
|
|
37
|
+
const output = this.encode(data, toFormat);
|
|
38
|
+
|
|
39
|
+
// Step 3: Calculate metadata
|
|
40
|
+
const inputSize = input.length;
|
|
41
|
+
const outputSize = output.length;
|
|
42
|
+
const savings = inputSize - outputSize;
|
|
43
|
+
const savingsPercentage = inputSize > 0 ? ((savings / inputSize) * 100).toFixed(1) : '0.0';
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
output,
|
|
47
|
+
metadata: {
|
|
48
|
+
inputSize,
|
|
49
|
+
outputSize,
|
|
50
|
+
savings,
|
|
51
|
+
savingsPercentage: parseFloat(savingsPercentage),
|
|
52
|
+
fromFormat,
|
|
53
|
+
toFormat,
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Parse string input to JavaScript object
|
|
60
|
+
*/
|
|
61
|
+
parse(input, format) {
|
|
62
|
+
switch (format.toLowerCase()) {
|
|
63
|
+
case 'json':
|
|
64
|
+
case 'json-compact':
|
|
65
|
+
return JSON.parse(input);
|
|
66
|
+
|
|
67
|
+
case 'yaml':
|
|
68
|
+
return this.parseYAML(input);
|
|
69
|
+
|
|
70
|
+
case 'toon':
|
|
71
|
+
return decodeToon(input);
|
|
72
|
+
|
|
73
|
+
case 'xml':
|
|
74
|
+
throw new Error('XML parsing not yet implemented. Use JSON as intermediate format.');
|
|
75
|
+
|
|
76
|
+
case 'csv':
|
|
77
|
+
return this.parseCSV(input);
|
|
78
|
+
|
|
79
|
+
case 'markdown':
|
|
80
|
+
throw new Error('Markdown parsing not yet implemented. Use JSON as intermediate format.');
|
|
81
|
+
|
|
82
|
+
default:
|
|
83
|
+
throw new Error(`Unknown format for parsing: ${format}`);
|
|
22
84
|
}
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const data = this.parse(input, fromFormat);
|
|
34
|
-
|
|
35
|
-
// Step 2: Encode to target format
|
|
36
|
-
const output = this.encode(data, toFormat);
|
|
37
|
-
|
|
38
|
-
// Step 3: Calculate metadata
|
|
39
|
-
const inputSize = input.length;
|
|
40
|
-
const outputSize = output.length;
|
|
41
|
-
const savings = inputSize - outputSize;
|
|
42
|
-
const savingsPercentage = inputSize > 0 ? ((savings / inputSize) * 100).toFixed(1) : '0.0';
|
|
43
|
-
|
|
44
|
-
return {
|
|
45
|
-
output,
|
|
46
|
-
metadata: {
|
|
47
|
-
inputSize,
|
|
48
|
-
outputSize,
|
|
49
|
-
savings,
|
|
50
|
-
savingsPercentage: parseFloat(savingsPercentage),
|
|
51
|
-
fromFormat,
|
|
52
|
-
toFormat
|
|
53
|
-
}
|
|
54
|
-
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Encode JavaScript object to format
|
|
89
|
+
*/
|
|
90
|
+
encode(data, format) {
|
|
91
|
+
try {
|
|
92
|
+
return this.registry.encode(format, data);
|
|
93
|
+
} catch (error) {
|
|
94
|
+
throw new Error(`Failed to encode to ${format}: ${error.message}`);
|
|
55
95
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Simple YAML parser for block-style mappings and sequences (what encodeYAML emits)
|
|
100
|
+
* Note: This is a basic implementation. Flow collections other than {} and [],
|
|
101
|
+
* compact "- key: value" items, anchors and block scalars are not supported.
|
|
102
|
+
*/
|
|
103
|
+
parseYAML(yamlString) {
|
|
104
|
+
const lines = yamlString
|
|
105
|
+
.split('\n')
|
|
106
|
+
.filter((line) => line.trim() && !line.trim().startsWith('#'))
|
|
107
|
+
.map((line) => ({ indent: line.length - line.trimStart().length, text: line.trim() }));
|
|
108
|
+
|
|
109
|
+
if (lines.length === 0) return {};
|
|
110
|
+
|
|
111
|
+
const state = { pos: 0 };
|
|
112
|
+
const first = lines[0];
|
|
113
|
+
return this.isYAMLSequenceItem(first.text)
|
|
114
|
+
? this.parseYAMLSequence(lines, state, first.indent)
|
|
115
|
+
: this.parseYAMLMapping(lines, state, first.indent);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Parse the mapping whose keys sit at `indent`, starting at state.pos
|
|
120
|
+
*/
|
|
121
|
+
parseYAMLMapping(lines, state, indent) {
|
|
122
|
+
const result = {};
|
|
123
|
+
|
|
124
|
+
while (state.pos < lines.length) {
|
|
125
|
+
const line = lines[state.pos];
|
|
126
|
+
if (line.indent < indent) break;
|
|
127
|
+
|
|
128
|
+
state.pos++;
|
|
129
|
+
// Lines that are not a key at this level are skipped, as before
|
|
130
|
+
const isKeyLine = line.indent === indent && !this.isYAMLSequenceItem(line.text);
|
|
131
|
+
const entry = isKeyLine ? this.splitYAMLKey(line.text) : null;
|
|
132
|
+
if (!entry) continue;
|
|
133
|
+
|
|
134
|
+
if (entry.value !== '') {
|
|
135
|
+
result[entry.key] = this.parseYAMLValue(entry.value);
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// An empty value introduces a nested block: deeper lines, or a sequence at the same indent
|
|
140
|
+
const next = lines[state.pos];
|
|
141
|
+
if (next && next.indent > indent) {
|
|
142
|
+
result[entry.key] = this.isYAMLSequenceItem(next.text)
|
|
143
|
+
? this.parseYAMLSequence(lines, state, next.indent)
|
|
144
|
+
: this.parseYAMLMapping(lines, state, next.indent);
|
|
145
|
+
} else if (next && next.indent === indent && this.isYAMLSequenceItem(next.text)) {
|
|
146
|
+
result[entry.key] = this.parseYAMLSequence(lines, state, indent);
|
|
147
|
+
} else {
|
|
148
|
+
result[entry.key] = null;
|
|
149
|
+
}
|
|
84
150
|
}
|
|
85
151
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Parse the sequence whose "-" markers sit at `indent`, starting at state.pos
|
|
157
|
+
*/
|
|
158
|
+
parseYAMLSequence(lines, state, indent) {
|
|
159
|
+
const result = [];
|
|
160
|
+
|
|
161
|
+
while (state.pos < lines.length) {
|
|
162
|
+
const line = lines[state.pos];
|
|
163
|
+
if (line.indent < indent) break;
|
|
164
|
+
if (line.indent > indent) {
|
|
165
|
+
state.pos++;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (!this.isYAMLSequenceItem(line.text)) break;
|
|
169
|
+
|
|
170
|
+
state.pos++;
|
|
171
|
+
const value = line.text.substring(1).trim();
|
|
172
|
+
if (value !== '') {
|
|
173
|
+
result.push(this.parseYAMLValue(value));
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// "-" alone: the item is the block on the following, deeper lines
|
|
178
|
+
const next = lines[state.pos];
|
|
179
|
+
if (next && next.indent > indent) {
|
|
180
|
+
result.push(
|
|
181
|
+
this.isYAMLSequenceItem(next.text)
|
|
182
|
+
? this.parseYAMLSequence(lines, state, next.indent)
|
|
183
|
+
: this.parseYAMLMapping(lines, state, next.indent)
|
|
184
|
+
);
|
|
185
|
+
} else {
|
|
186
|
+
result.push(null);
|
|
187
|
+
}
|
|
95
188
|
}
|
|
96
189
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
// Initialize array if needed
|
|
115
|
-
if (currentKey && (!result[currentKey] || !Array.isArray(result[currentKey]))) {
|
|
116
|
-
result[currentKey] = [];
|
|
117
|
-
currentArray = result[currentKey];
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (currentArray) {
|
|
121
|
-
currentArray.push(this.parseYAMLValue(value));
|
|
122
|
-
}
|
|
123
|
-
continue;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Key-value pair
|
|
127
|
-
const colonIndex = trimmed.indexOf(':');
|
|
128
|
-
if (colonIndex !== -1) {
|
|
129
|
-
const key = trimmed.substring(0, colonIndex).trim();
|
|
130
|
-
const value = trimmed.substring(colonIndex + 1).trim();
|
|
131
|
-
|
|
132
|
-
if (value === '') {
|
|
133
|
-
// Object or array follows
|
|
134
|
-
currentKey = key;
|
|
135
|
-
result[key] = {}; // Default to object, will be overwritten if array items found
|
|
136
|
-
currentArray = null; // Reset current array
|
|
137
|
-
} else {
|
|
138
|
-
result[key] = this.parseYAMLValue(value);
|
|
139
|
-
currentKey = null;
|
|
140
|
-
currentArray = null;
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
return result;
|
|
190
|
+
return result;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
isYAMLSequenceItem(text) {
|
|
194
|
+
return text === '-' || text.startsWith('- ');
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Split "key: value" into its parts; the key may be double-quoted
|
|
199
|
+
* @returns {{key: string, value: string}|null} null when the line has no key
|
|
200
|
+
*/
|
|
201
|
+
splitYAMLKey(text) {
|
|
202
|
+
// The value is trimmed below instead of matched as `\s+(.*)`, whose two quantifiers
|
|
203
|
+
// overlap on spaces (polynomial backtracking)
|
|
204
|
+
const quoted = text.match(/^("(?:[^"\\]|\\.)*")\s*:(.*)$/s);
|
|
205
|
+
if (quoted) {
|
|
206
|
+
return { key: JSON.parse(quoted[1]), value: quoted[2].trim() };
|
|
146
207
|
}
|
|
147
208
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
209
|
+
let colonIndex = text.search(/:(\s|$)/);
|
|
210
|
+
if (colonIndex === -1) colonIndex = text.indexOf(':');
|
|
211
|
+
if (colonIndex === -1) return null;
|
|
212
|
+
|
|
213
|
+
return {
|
|
214
|
+
key: text.substring(0, colonIndex).trim(),
|
|
215
|
+
value: text.substring(colonIndex + 1).trim(),
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Parse YAML value (string, number, boolean)
|
|
221
|
+
*/
|
|
222
|
+
parseYAMLValue(value) {
|
|
223
|
+
if (value === 'true') return true;
|
|
224
|
+
if (value === 'false') return false;
|
|
225
|
+
if (value === 'null') return null;
|
|
226
|
+
if (value === '{}') return {};
|
|
227
|
+
if (value === '[]') return [];
|
|
228
|
+
if (/^-?\d+$/.test(value)) return parseInt(value, 10);
|
|
229
|
+
if (/^-?\d+(\.\d+)?([eE][+-]?\d+)?$/.test(value)) return parseFloat(value);
|
|
230
|
+
if (value.startsWith('"') && value.endsWith('"')) {
|
|
231
|
+
try {
|
|
232
|
+
return JSON.parse(value);
|
|
233
|
+
} catch {
|
|
234
|
+
return value.substring(1, value.length - 1);
|
|
235
|
+
}
|
|
161
236
|
}
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
return { headers, rows };
|
|
237
|
+
return value;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* Simple CSV parser
|
|
242
|
+
*/
|
|
243
|
+
parseCSV(csvString) {
|
|
244
|
+
const lines = csvString.split('\n').filter((line) => line.trim());
|
|
245
|
+
if (lines.length === 0) return { rows: [] };
|
|
246
|
+
|
|
247
|
+
const headers = this.parseCSVLine(lines[0]);
|
|
248
|
+
const rows = [];
|
|
249
|
+
|
|
250
|
+
for (let i = 1; i < lines.length; i++) {
|
|
251
|
+
const values = this.parseCSVLine(lines[i]);
|
|
252
|
+
const row = {};
|
|
253
|
+
headers.forEach((header, index) => {
|
|
254
|
+
row[header] = values[index] || '';
|
|
255
|
+
});
|
|
256
|
+
rows.push(row);
|
|
183
257
|
}
|
|
184
258
|
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
*/
|
|
188
|
-
parseCSVLine(line) {
|
|
189
|
-
const values = [];
|
|
190
|
-
let current = '';
|
|
191
|
-
let inQuotes = false;
|
|
192
|
-
|
|
193
|
-
for (let i = 0; i < line.length; i++) {
|
|
194
|
-
const char = line[i];
|
|
195
|
-
|
|
196
|
-
if (char === '"') {
|
|
197
|
-
inQuotes = !inQuotes;
|
|
198
|
-
} else if (char === ',' && !inQuotes) {
|
|
199
|
-
values.push(current.trim());
|
|
200
|
-
current = '';
|
|
201
|
-
} else {
|
|
202
|
-
current += char;
|
|
203
|
-
}
|
|
204
|
-
}
|
|
259
|
+
return { headers, rows };
|
|
260
|
+
}
|
|
205
261
|
|
|
262
|
+
/**
|
|
263
|
+
* Parse CSV line (handles quoted values)
|
|
264
|
+
*/
|
|
265
|
+
parseCSVLine(line) {
|
|
266
|
+
const values = [];
|
|
267
|
+
let current = '';
|
|
268
|
+
let inQuotes = false;
|
|
269
|
+
|
|
270
|
+
for (let i = 0; i < line.length; i++) {
|
|
271
|
+
const char = line[i];
|
|
272
|
+
|
|
273
|
+
if (char === '"') {
|
|
274
|
+
inQuotes = !inQuotes;
|
|
275
|
+
} else if (char === ',' && !inQuotes) {
|
|
206
276
|
values.push(current.trim());
|
|
207
|
-
|
|
277
|
+
current = '';
|
|
278
|
+
} else {
|
|
279
|
+
current += char;
|
|
280
|
+
}
|
|
208
281
|
}
|
|
209
282
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
toFormat = this.extensionToFormat(ext);
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
// Read input file
|
|
230
|
-
const input = fs.readFileSync(inputFile, 'utf8');
|
|
231
|
-
|
|
232
|
-
// Convert
|
|
233
|
-
const result = this.convert(input, fromFormat, toFormat);
|
|
234
|
-
|
|
235
|
-
// Write output file
|
|
236
|
-
fs.writeFileSync(outputFile, result.output, 'utf8');
|
|
237
|
-
|
|
238
|
-
return {
|
|
239
|
-
inputFile,
|
|
240
|
-
outputFile,
|
|
241
|
-
fromFormat,
|
|
242
|
-
toFormat,
|
|
243
|
-
inputSize: input.length,
|
|
244
|
-
outputSize: result.output.length,
|
|
245
|
-
savings: input.length - result.output.length,
|
|
246
|
-
savingsPercent: result.metadata.savingsPercentage + '%'
|
|
247
|
-
};
|
|
283
|
+
values.push(current.trim());
|
|
284
|
+
return values;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/**
|
|
288
|
+
* Convert file from one format to another
|
|
289
|
+
* @param {string} inputFile - Input file path
|
|
290
|
+
* @param {string} outputFile - Output file path
|
|
291
|
+
* @param {string} fromFormat - Source format (auto-detect from extension if not provided)
|
|
292
|
+
* @param {string} toFormat - Target format (auto-detect from extension if not provided)
|
|
293
|
+
*/
|
|
294
|
+
convertFile(inputFile, outputFile, fromFormat = null, toFormat = null) {
|
|
295
|
+
// Auto-detect formats from file extensions
|
|
296
|
+
if (!fromFormat) {
|
|
297
|
+
const ext = path.extname(inputFile).substring(1);
|
|
298
|
+
fromFormat = this.extensionToFormat(ext);
|
|
248
299
|
}
|
|
249
300
|
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
extensionToFormat(ext) {
|
|
254
|
-
const map = {
|
|
255
|
-
'json': 'json',
|
|
256
|
-
'toon': 'toon',
|
|
257
|
-
'yaml': 'yaml',
|
|
258
|
-
'yml': 'yaml',
|
|
259
|
-
'csv': 'csv',
|
|
260
|
-
'xml': 'xml',
|
|
261
|
-
'md': 'markdown',
|
|
262
|
-
'txt': 'gitingest'
|
|
263
|
-
};
|
|
264
|
-
|
|
265
|
-
return map[ext.toLowerCase()] || 'json';
|
|
301
|
+
if (!toFormat) {
|
|
302
|
+
const ext = path.extname(outputFile).substring(1);
|
|
303
|
+
toFormat = this.extensionToFormat(ext);
|
|
266
304
|
}
|
|
267
305
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
306
|
+
// Read input file
|
|
307
|
+
const input = fs.readFileSync(inputFile, 'utf8');
|
|
308
|
+
|
|
309
|
+
// Convert
|
|
310
|
+
const result = this.convert(input, fromFormat, toFormat);
|
|
311
|
+
|
|
312
|
+
// Write output file
|
|
313
|
+
fs.writeFileSync(outputFile, result.output, 'utf8');
|
|
314
|
+
|
|
315
|
+
return {
|
|
316
|
+
inputFile,
|
|
317
|
+
outputFile,
|
|
318
|
+
fromFormat,
|
|
319
|
+
toFormat,
|
|
320
|
+
inputSize: input.length,
|
|
321
|
+
outputSize: result.output.length,
|
|
322
|
+
savings: input.length - result.output.length,
|
|
323
|
+
savingsPercent: result.metadata.savingsPercentage + '%',
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
/**
|
|
328
|
+
* Map file extension to format name
|
|
329
|
+
*/
|
|
330
|
+
extensionToFormat(ext) {
|
|
331
|
+
const map = {
|
|
332
|
+
json: 'json',
|
|
333
|
+
toon: 'toon',
|
|
334
|
+
yaml: 'yaml',
|
|
335
|
+
yml: 'yaml',
|
|
336
|
+
csv: 'csv',
|
|
337
|
+
xml: 'xml',
|
|
338
|
+
md: 'markdown',
|
|
339
|
+
txt: 'gitingest',
|
|
340
|
+
};
|
|
341
|
+
|
|
342
|
+
return map[ext.toLowerCase()] || 'json';
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Batch convert multiple files
|
|
347
|
+
* @param {Array} files - Array of {input, output, from, to}
|
|
348
|
+
* @returns {Array} Conversion results
|
|
349
|
+
*/
|
|
350
|
+
batchConvert(files) {
|
|
351
|
+
const results = [];
|
|
352
|
+
|
|
353
|
+
for (const file of files) {
|
|
354
|
+
try {
|
|
355
|
+
const result = this.convertFile(
|
|
356
|
+
file.input,
|
|
357
|
+
file.output,
|
|
358
|
+
file.from || null,
|
|
359
|
+
file.to || null
|
|
360
|
+
);
|
|
361
|
+
results.push({ success: true, ...result });
|
|
362
|
+
} catch (error) {
|
|
363
|
+
results.push({
|
|
364
|
+
success: false,
|
|
365
|
+
inputFile: file.input,
|
|
366
|
+
error: error.message,
|
|
367
|
+
});
|
|
368
|
+
}
|
|
295
369
|
}
|
|
296
370
|
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
}
|
|
371
|
+
return results;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
/**
|
|
375
|
+
* Get supported conversions
|
|
376
|
+
*/
|
|
377
|
+
getSupportedConversions() {
|
|
378
|
+
return {
|
|
379
|
+
fullySupported: [
|
|
380
|
+
'JSON → TOON',
|
|
381
|
+
'JSON → YAML',
|
|
382
|
+
'JSON → CSV',
|
|
383
|
+
'JSON → XML',
|
|
384
|
+
'JSON → Markdown',
|
|
385
|
+
'TOON → JSON',
|
|
386
|
+
'YAML → JSON (basic)',
|
|
387
|
+
'CSV → JSON',
|
|
388
|
+
],
|
|
389
|
+
partialSupport: [
|
|
390
|
+
'XML → JSON (parser not implemented)',
|
|
391
|
+
'Markdown → JSON (parser not implemented)',
|
|
392
|
+
],
|
|
393
|
+
planned: ['XML ↔ JSON (bidirectional)', 'Advanced YAML parsing'],
|
|
394
|
+
};
|
|
395
|
+
}
|
|
323
396
|
}
|
|
324
397
|
|
|
325
398
|
export default FormatConverter;
|