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/bin/cm-pack.js
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* ctxman pack - the contents of a project's files in one LLM-ready document
|
|
4
|
+
*
|
|
5
|
+
* stdout carries only the document (unless -o/--copy); the summary, notices and errors go
|
|
6
|
+
* to stderr. Exit codes: 0 done, 1 error, 2 the document is larger than --max-tokens.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
import { getLogger } from '../lib/utils/logger.js';
|
|
12
|
+
|
|
13
|
+
const STYLES = ['xml', 'markdown', 'plain', 'json'];
|
|
14
|
+
// Exit code when the document is larger than --max-tokens
|
|
15
|
+
const EXIT_TOO_LARGE = 2;
|
|
16
|
+
|
|
17
|
+
function printHelp() {
|
|
18
|
+
process.stdout.write(`ctxman pack - file contents in one LLM-ready document
|
|
19
|
+
|
|
20
|
+
Usage: ctxman pack [path] [options]
|
|
21
|
+
|
|
22
|
+
Packs the files of a project (default: the current directory) into one document: a
|
|
23
|
+
directory tree, then every file, then your instructions last. .gitignore, .contextignore
|
|
24
|
+
and .contextinclude decide which files are included; binary files and files over 1 MiB
|
|
25
|
+
are skipped. Secrets (API keys, tokens, private keys) are masked.
|
|
26
|
+
|
|
27
|
+
Selection:
|
|
28
|
+
--include GLOB Only files matching GLOB (repeatable or comma-separated)
|
|
29
|
+
--exclude GLOB Leave out files matching GLOB (repeatable or comma-separated)
|
|
30
|
+
--stdin Read the file list from stdin, one path per line, instead of
|
|
31
|
+
scanning; paths must lie inside the project, ignore files apply
|
|
32
|
+
-0, --null With --stdin: paths are separated by NUL bytes
|
|
33
|
+
(find -print0, git ls-files -z)
|
|
34
|
+
--max-file-size SIZE Skip larger files (default 1m; bytes, or with a k/m suffix)
|
|
35
|
+
|
|
36
|
+
Document:
|
|
37
|
+
--style STYLE xml (default; Anthropic's <documents> format), markdown, plain, json
|
|
38
|
+
--json Same as --style json
|
|
39
|
+
--no-tree Leave out the directory tree
|
|
40
|
+
--header TEXT Text at the top
|
|
41
|
+
--instructions FILE Append the text of FILE at the end, after the files
|
|
42
|
+
--line-numbers Number every line (original numbers, also with --compress)
|
|
43
|
+
--compress Keep signatures, replace function bodies with a one-line
|
|
44
|
+
placeholder (languages of the method analyzers)
|
|
45
|
+
--no-redact Do not mask secrets
|
|
46
|
+
|
|
47
|
+
Size:
|
|
48
|
+
--budget TOKENS Keep the document within TOKENS (e.g. 50000 or 50k): the
|
|
49
|
+
highest-ranked files first (ranking of ctxman map), the files
|
|
50
|
+
left out are listed with their token counts
|
|
51
|
+
--max-tokens TOKENS Exit with code 2, writing nothing, when the document is larger
|
|
52
|
+
--split TOKENS With -o: write numbered parts of at most TOKENS each
|
|
53
|
+
(FILE.1.xml, FILE.2.xml, ...)
|
|
54
|
+
--target-model ID Count with this model's tokenizer encoding and report how the
|
|
55
|
+
document fits its context window (see ctxman --list-llms)
|
|
56
|
+
|
|
57
|
+
Output:
|
|
58
|
+
-o, --output FILE Write to FILE instead of stdout
|
|
59
|
+
--copy Copy to the clipboard instead of stdout
|
|
60
|
+
-q, --quiet No summary on stderr
|
|
61
|
+
-h, --help Show this help
|
|
62
|
+
|
|
63
|
+
Examples:
|
|
64
|
+
ctxman pack -o context.xml
|
|
65
|
+
ctxman pack src --include "**/*.ts" --exclude "**/*.test.ts" --style markdown
|
|
66
|
+
ctxman pack --compress --budget 100k --instructions task.md --copy
|
|
67
|
+
git ls-files -z -- '*.py' | ctxman pack --stdin -0 --line-numbers
|
|
68
|
+
ctxman pack --target-model gpt-4o --max-tokens 120k -q -o context.xml
|
|
69
|
+
ctxman pack --split 150k -o context.xml # context.1.xml, context.2.xml, ...
|
|
70
|
+
`);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** "8000", "8k", "1.5k" or "2m" → 8000 / 1500 / 2000000 */
|
|
74
|
+
function parseTokens(text, flag) {
|
|
75
|
+
const match = /^(\d+(?:\.\d+)?)([km])?$/i.exec(String(text).trim());
|
|
76
|
+
const scale = { k: 1e3, m: 1e6 }[match?.[2]?.toLowerCase()] ?? 1;
|
|
77
|
+
const value = match ? Math.round(Number(match[1]) * scale) : NaN;
|
|
78
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
79
|
+
throw new Error(`${flag} needs a positive number of tokens (e.g. 50000 or 50k), got ${text}`);
|
|
80
|
+
}
|
|
81
|
+
return value;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** "4096", "512k" or "2m" (KiB/MiB) → bytes */
|
|
85
|
+
function parseSize(text, flag) {
|
|
86
|
+
const match = /^(\d+)([km])?$/i.exec(String(text).trim());
|
|
87
|
+
const scale = { k: 1024, m: 1024 * 1024 }[match?.[2]?.toLowerCase()] ?? 1;
|
|
88
|
+
const value = match ? Number(match[1]) * scale : NaN;
|
|
89
|
+
if (!Number.isInteger(value) || value <= 0) {
|
|
90
|
+
throw new Error(`${flag} needs a size in bytes (e.g. 500000, 512k or 2m), got ${text}`);
|
|
91
|
+
}
|
|
92
|
+
return value;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const VALUE_OPTIONS = {
|
|
96
|
+
'--include': 'include',
|
|
97
|
+
'--exclude': 'exclude',
|
|
98
|
+
'--max-file-size': 'maxFileSize',
|
|
99
|
+
'--style': 'style',
|
|
100
|
+
'--header': 'header',
|
|
101
|
+
'--instructions': 'instructions',
|
|
102
|
+
'--budget': 'budget',
|
|
103
|
+
'--max-tokens': 'maxTokens',
|
|
104
|
+
'--split': 'split',
|
|
105
|
+
'--target-model': 'targetModel',
|
|
106
|
+
'--output': 'output',
|
|
107
|
+
'-o': 'output',
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const FLAG_OPTIONS = {
|
|
111
|
+
'--stdin': ['stdin', true],
|
|
112
|
+
'--null': ['nul', true],
|
|
113
|
+
'-0': ['nul', true],
|
|
114
|
+
'--json': ['style', 'json'],
|
|
115
|
+
'--tree': ['tree', true],
|
|
116
|
+
'--no-tree': ['tree', false],
|
|
117
|
+
'--line-numbers': ['lineNumbers', true],
|
|
118
|
+
'--compress': ['compress', true],
|
|
119
|
+
'--redact': ['redact', true],
|
|
120
|
+
'--no-redact': ['redact', false],
|
|
121
|
+
'--copy': ['copy', true],
|
|
122
|
+
'--quiet': ['quiet', true],
|
|
123
|
+
'-q': ['quiet', true],
|
|
124
|
+
'--help': ['help', true],
|
|
125
|
+
'-h': ['help', true],
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Parse the command line
|
|
130
|
+
* @param {string[]} argv
|
|
131
|
+
* @returns {object}
|
|
132
|
+
*/
|
|
133
|
+
function parseArgs(argv) {
|
|
134
|
+
const options = { include: [], exclude: [], tree: true, redact: true, paths: [] };
|
|
135
|
+
for (let i = 0; i < argv.length; i++) {
|
|
136
|
+
const arg = argv[i];
|
|
137
|
+
if (arg === '--') {
|
|
138
|
+
options.paths.push(...argv.slice(i + 1));
|
|
139
|
+
break;
|
|
140
|
+
}
|
|
141
|
+
const eq = arg.startsWith('--') ? arg.indexOf('=') : -1;
|
|
142
|
+
const name = eq > 0 ? arg.slice(0, eq) : arg;
|
|
143
|
+
if (VALUE_OPTIONS[name]) {
|
|
144
|
+
let value;
|
|
145
|
+
if (eq > 0) value = arg.slice(eq + 1);
|
|
146
|
+
else if (i + 1 < argv.length) value = argv[++i];
|
|
147
|
+
else throw new Error(`${name} needs a value`);
|
|
148
|
+
const key = VALUE_OPTIONS[name];
|
|
149
|
+
if (key === 'include' || key === 'exclude') {
|
|
150
|
+
options[key].push(...value.split(',').filter((glob) => glob.trim()));
|
|
151
|
+
} else {
|
|
152
|
+
options[key] = value;
|
|
153
|
+
}
|
|
154
|
+
} else if (FLAG_OPTIONS[name] && eq < 0) {
|
|
155
|
+
const [key, value] = FLAG_OPTIONS[name];
|
|
156
|
+
options[key] = value;
|
|
157
|
+
} else if (arg.startsWith('-') && arg !== '-') {
|
|
158
|
+
throw new Error(`Unknown option: ${arg}`);
|
|
159
|
+
} else {
|
|
160
|
+
options.paths.push(arg);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (options.paths.length > 1) {
|
|
165
|
+
throw new Error(`Expected one project path, got ${options.paths.length}`);
|
|
166
|
+
}
|
|
167
|
+
if (options.style !== undefined && !STYLES.includes(options.style)) {
|
|
168
|
+
throw new Error(`--style must be one of ${STYLES.join(', ')}`);
|
|
169
|
+
}
|
|
170
|
+
for (const [key, flag] of [
|
|
171
|
+
['budget', '--budget'],
|
|
172
|
+
['maxTokens', '--max-tokens'],
|
|
173
|
+
['split', '--split'],
|
|
174
|
+
]) {
|
|
175
|
+
if (options[key] !== undefined) options[key] = parseTokens(options[key], flag);
|
|
176
|
+
}
|
|
177
|
+
if (options.maxFileSize !== undefined) {
|
|
178
|
+
options.maxFileSize = parseSize(options.maxFileSize, '--max-file-size');
|
|
179
|
+
}
|
|
180
|
+
if (options.nul && !options.stdin) throw new Error('-0/--null only applies to --stdin');
|
|
181
|
+
if (options.split && !options.output) {
|
|
182
|
+
throw new Error('--split writes numbered files: give the first name with -o/--output');
|
|
183
|
+
}
|
|
184
|
+
if (options.split && options.copy) throw new Error('--split cannot be combined with --copy');
|
|
185
|
+
return options;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/** Paths of the parts of a split output: context.xml → context.1.xml, context.2.xml, ... */
|
|
189
|
+
function partPath(output, index, count) {
|
|
190
|
+
const extension = path.extname(output);
|
|
191
|
+
const base = output.slice(0, output.length - extension.length);
|
|
192
|
+
return `${base}.${String(index).padStart(String(count).length, '0')}${extension}`;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** Existing files that look like parts of `output` (left out of the pack, like the output) */
|
|
196
|
+
function existingParts(output) {
|
|
197
|
+
const directory = path.dirname(output);
|
|
198
|
+
const extension = path.extname(output);
|
|
199
|
+
const base = path.basename(output, extension);
|
|
200
|
+
const escape = (text) => text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
201
|
+
const pattern = new RegExp(`^${escape(base)}\\.\\d+${escape(extension)}$`);
|
|
202
|
+
try {
|
|
203
|
+
return fs
|
|
204
|
+
.readdirSync(directory)
|
|
205
|
+
.filter((name) => pattern.test(name))
|
|
206
|
+
.map((name) => path.join(directory, name));
|
|
207
|
+
} catch (_error) {
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
const formatNumber = (n) => Number(n).toLocaleString('en-US');
|
|
213
|
+
|
|
214
|
+
/** A path as the user would type it: relative to the current directory when below it */
|
|
215
|
+
function display(file) {
|
|
216
|
+
const relative = path.relative(process.cwd(), file);
|
|
217
|
+
return relative && !relative.startsWith('..') && !path.isAbsolute(relative) ? relative : file;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
function printSummary(result, extras) {
|
|
221
|
+
const lines = [];
|
|
222
|
+
const style = result.style;
|
|
223
|
+
lines.push(
|
|
224
|
+
`ctxman pack: ${formatNumber(result.files.length)} file(s), ` +
|
|
225
|
+
`${formatNumber(result.totalTokens)} tokens (${style}, ${result.encoding})`
|
|
226
|
+
);
|
|
227
|
+
if (result.budget) {
|
|
228
|
+
const omitted = result.omitted.length;
|
|
229
|
+
const omittedTokens = result.omitted.reduce((sum, file) => sum + file.tokens, 0);
|
|
230
|
+
lines.push(
|
|
231
|
+
` Budget ${formatNumber(result.budget.limit)}: ${formatNumber(result.budget.used)} used` +
|
|
232
|
+
(omitted
|
|
233
|
+
? `; ${formatNumber(omitted)} file(s) left out (${formatNumber(omittedTokens)} tokens)`
|
|
234
|
+
: '; every file fits')
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
if (result.compression) {
|
|
238
|
+
const { files, bodies, originalTokens, savedTokens } = result.compression;
|
|
239
|
+
const percent = originalTokens ? Math.round((savedTokens / originalTokens) * 100) : 0;
|
|
240
|
+
lines.push(
|
|
241
|
+
` Compressed ${formatNumber(bodies)} function ${bodies === 1 ? 'body' : 'bodies'} in ` +
|
|
242
|
+
`${formatNumber(files)} file(s): ` +
|
|
243
|
+
`${formatNumber(savedTokens)} tokens saved (${percent}%)`
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
if (result.redactions.total) {
|
|
247
|
+
const types = Object.entries(result.redactions.byType)
|
|
248
|
+
.map(([type, count]) => `${count} ${type}`)
|
|
249
|
+
.join(', ');
|
|
250
|
+
lines.push(` Redacted ${formatNumber(result.redactions.total)} secret(s): ${types}`);
|
|
251
|
+
}
|
|
252
|
+
if (result.skipped.length) {
|
|
253
|
+
const reasons = {};
|
|
254
|
+
for (const file of result.skipped) reasons[file.reason] = (reasons[file.reason] ?? 0) + 1;
|
|
255
|
+
const text = Object.entries(reasons)
|
|
256
|
+
.map(([reason, count]) => `${count} ${reason}`)
|
|
257
|
+
.join(', ');
|
|
258
|
+
lines.push(` Skipped ${formatNumber(result.skipped.length)} file(s): ${text}`);
|
|
259
|
+
}
|
|
260
|
+
if (!result.files.length && !result.omitted.length) {
|
|
261
|
+
lines.push(
|
|
262
|
+
' Warning: no files to pack (check the path, --include/--exclude and the ignore files)'
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
for (const file of result.splitFiles) {
|
|
266
|
+
lines.push(` ${file.path} alone exceeds a part: split by lines into ${file.pieces} pieces`);
|
|
267
|
+
}
|
|
268
|
+
if (result.model) {
|
|
269
|
+
const model = result.model;
|
|
270
|
+
const how = model.exact ? 'exact' : 'approximate';
|
|
271
|
+
lines.push(
|
|
272
|
+
` ${model.name} (${model.id}): ${model.percentOfWindow.toFixed(1)}% of the ` +
|
|
273
|
+
`${formatNumber(model.contextWindow)}-token window, ` +
|
|
274
|
+
`${model.fits ? 'fits' : "doesn't fit"} (${how} count, ${model.encoding})`
|
|
275
|
+
);
|
|
276
|
+
if (model.fits && model.maxRecommendedInput && result.totalTokens > model.maxRecommendedInput) {
|
|
277
|
+
lines.push(
|
|
278
|
+
` Note: above the recommended ${formatNumber(model.maxRecommendedInput)} input tokens ` +
|
|
279
|
+
'for this model; consider --budget or --compress'
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
lines.push(...extras.map((line) => ` ${line}`));
|
|
284
|
+
process.stderr.write(`${lines.join('\n')}\n`);
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
/** The largest packed files, to say what to leave out when --max-tokens is exceeded */
|
|
288
|
+
function largestFiles(result, count = 5) {
|
|
289
|
+
return [...result.files]
|
|
290
|
+
.sort((a, b) => b.tokens - a.tokens)
|
|
291
|
+
.slice(0, count)
|
|
292
|
+
.map((file) => ` ${formatNumber(file.tokens).padStart(9)} ${file.path}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
async function main() {
|
|
296
|
+
let args;
|
|
297
|
+
try {
|
|
298
|
+
args = parseArgs(process.argv.slice(2));
|
|
299
|
+
} catch (error) {
|
|
300
|
+
process.stderr.write(`Error: ${error.message}\nRun "ctxman pack --help" for usage.\n`);
|
|
301
|
+
process.exitCode = 1;
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
if (args.help) {
|
|
305
|
+
printHelp();
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// stdout carries only the document: logger lines and ignore-file notices (console.log in
|
|
310
|
+
// shared modules) go to stderr, and pack writes no log files into the project
|
|
311
|
+
getLogger({ level: process.env.LOG_LEVEL || 'warn', logToFile: false });
|
|
312
|
+
console.log = console.info = console.debug = console.error;
|
|
313
|
+
|
|
314
|
+
try {
|
|
315
|
+
const { packContext } = await import('../lib/pack/index.js');
|
|
316
|
+
const root = path.resolve(args.paths[0] ?? '.');
|
|
317
|
+
const output = args.output ? path.resolve(args.output) : null;
|
|
318
|
+
|
|
319
|
+
const options = {
|
|
320
|
+
style: args.style,
|
|
321
|
+
include: args.include,
|
|
322
|
+
exclude: args.exclude,
|
|
323
|
+
maxFileSize: args.maxFileSize,
|
|
324
|
+
tree: args.tree,
|
|
325
|
+
header: args.header,
|
|
326
|
+
lineNumbers: args.lineNumbers,
|
|
327
|
+
compress: args.compress,
|
|
328
|
+
redact: args.redact,
|
|
329
|
+
budget: args.budget,
|
|
330
|
+
split: args.split,
|
|
331
|
+
targetModel: args.targetModel,
|
|
332
|
+
// A previous output inside the project is not packed again
|
|
333
|
+
excludeFiles: output ? [output, ...(args.split ? existingParts(output) : [])] : [],
|
|
334
|
+
};
|
|
335
|
+
if (args.instructions !== undefined) {
|
|
336
|
+
options.instructions = fs.readFileSync(path.resolve(args.instructions), 'utf8');
|
|
337
|
+
}
|
|
338
|
+
if (args.stdin) {
|
|
339
|
+
const input = fs.readFileSync(0, 'utf8');
|
|
340
|
+
options.files = input
|
|
341
|
+
.split(args.nul ? '\0' : '\n')
|
|
342
|
+
.map((line) => (args.nul ? line : line.replace(/\r$/, '')))
|
|
343
|
+
.filter((line) => line.trim() !== '');
|
|
344
|
+
options.cwd = process.cwd();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
const result = await packContext(root, options);
|
|
348
|
+
const extras = [];
|
|
349
|
+
|
|
350
|
+
if (args.maxTokens && result.totalTokens > args.maxTokens) {
|
|
351
|
+
if (!args.quiet) printSummary(result, extras);
|
|
352
|
+
process.stderr.write(
|
|
353
|
+
`Error: the document has ${formatNumber(result.totalTokens)} tokens, more than ` +
|
|
354
|
+
`--max-tokens ${formatNumber(args.maxTokens)}; nothing was written. Largest files:\n` +
|
|
355
|
+
`${largestFiles(result).join('\n')}\n`
|
|
356
|
+
);
|
|
357
|
+
process.exitCode = EXIT_TOO_LARGE;
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
let toStdout = !output && !args.copy;
|
|
362
|
+
if (output && result.parts) {
|
|
363
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
364
|
+
const written = new Set();
|
|
365
|
+
for (const part of result.parts) {
|
|
366
|
+
const file = partPath(output, part.index, result.parts.length);
|
|
367
|
+
fs.writeFileSync(file, part.text);
|
|
368
|
+
written.add(file);
|
|
369
|
+
extras.push(`Part ${part.index}: ${display(file)} (${formatNumber(part.tokens)} tokens)`);
|
|
370
|
+
}
|
|
371
|
+
// Parts of an earlier, longer run are left alone, but they are not part of this output
|
|
372
|
+
const stale = existingParts(output).filter((file) => !written.has(file));
|
|
373
|
+
if (stale.length) {
|
|
374
|
+
extras.push(
|
|
375
|
+
`Note: ${stale.map(display).join(', ')} ${stale.length === 1 ? 'is' : 'are'} ` +
|
|
376
|
+
'from an earlier run, not part of this output'
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
} else if (output) {
|
|
380
|
+
fs.mkdirSync(path.dirname(output), { recursive: true });
|
|
381
|
+
fs.writeFileSync(output, result.text);
|
|
382
|
+
extras.push(`Written to ${display(output)}`);
|
|
383
|
+
}
|
|
384
|
+
if (args.copy) {
|
|
385
|
+
const { default: ClipboardUtils } = await import('../lib/utils/clipboard-utils.js');
|
|
386
|
+
if (!ClipboardUtils.copy(result.text) && !output) {
|
|
387
|
+
extras.push('Clipboard unavailable; writing to stdout instead');
|
|
388
|
+
toStdout = true;
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
if (toStdout) process.stdout.write(result.text);
|
|
392
|
+
if (!args.quiet) printSummary(result, extras);
|
|
393
|
+
} catch (error) {
|
|
394
|
+
process.stderr.write(`Error: ${error.message}\n`);
|
|
395
|
+
process.exitCode = 1;
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// `ctxman pack | head` closes the pipe early: stop quietly instead of crashing on EPIPE
|
|
400
|
+
process.stdout.on('error', (error) => {
|
|
401
|
+
if (error.code !== 'EPIPE') throw error;
|
|
402
|
+
process.exit(process.exitCode ?? 0);
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
main();
|
package/bin/cm-policy.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Policy Command
|
|
4
|
+
* Validate and show the export policy (`ctxman policy check`), or write a starter policy
|
|
5
|
+
* (`ctxman policy init`)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import fs from 'fs';
|
|
9
|
+
import path from 'path';
|
|
10
|
+
import {
|
|
11
|
+
loadPolicy,
|
|
12
|
+
createStarterPolicy,
|
|
13
|
+
loadIgnoreFileRules,
|
|
14
|
+
POLICY_FILE,
|
|
15
|
+
POLICY_ENV,
|
|
16
|
+
POLICY_DOCS_URL,
|
|
17
|
+
PolicyConfigError,
|
|
18
|
+
} from '../lib/policy/index.js';
|
|
19
|
+
|
|
20
|
+
function main() {
|
|
21
|
+
const args = process.argv.slice(2);
|
|
22
|
+
|
|
23
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
24
|
+
printHelp();
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const command = args[0] && !args[0].startsWith('-') ? args[0] : 'check';
|
|
29
|
+
|
|
30
|
+
switch (command) {
|
|
31
|
+
case 'check':
|
|
32
|
+
check(args.includes('--json'));
|
|
33
|
+
break;
|
|
34
|
+
case 'init':
|
|
35
|
+
init(args.includes('--force') || args.includes('-f'));
|
|
36
|
+
break;
|
|
37
|
+
default:
|
|
38
|
+
console.error(`❌ Unknown policy command: ${command}`);
|
|
39
|
+
console.error(' Usage: ctxman policy <check|init> (see ctxman policy --help)');
|
|
40
|
+
process.exitCode = 2;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function check(json) {
|
|
45
|
+
const root = process.cwd();
|
|
46
|
+
let policy;
|
|
47
|
+
try {
|
|
48
|
+
policy = loadPolicy(root);
|
|
49
|
+
} catch (error) {
|
|
50
|
+
if (!(error instanceof PolicyConfigError)) throw error;
|
|
51
|
+
if (json) {
|
|
52
|
+
console.log(
|
|
53
|
+
JSON.stringify({ valid: false, file: error.file, errors: error.errors }, null, 2)
|
|
54
|
+
);
|
|
55
|
+
} else {
|
|
56
|
+
console.error(`❌ ${error.message}`);
|
|
57
|
+
}
|
|
58
|
+
process.exitCode = error.exitCode;
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (json) {
|
|
63
|
+
console.log(JSON.stringify({ valid: true, ...policy.toJSON() }, null, 2));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!policy.active) {
|
|
68
|
+
console.log(`No export policy: there is no ${POLICY_FILE} and ${POLICY_ENV} is not set.`);
|
|
69
|
+
console.log('Exports are unrestricted. Create a policy with: ctxman policy init');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const display = (file) => (file.startsWith(root + path.sep) ? path.relative(root, file) : file);
|
|
74
|
+
const label = (text) => `${text}:`.padEnd(24);
|
|
75
|
+
|
|
76
|
+
console.log('🛡️ ctxman export policy');
|
|
77
|
+
console.log(`Project root: ${root}`);
|
|
78
|
+
console.log();
|
|
79
|
+
console.log('Policy files:');
|
|
80
|
+
for (const { scope, file } of policy.sources) {
|
|
81
|
+
console.log(` ${scope.padEnd(13)} ${display(file)}`);
|
|
82
|
+
}
|
|
83
|
+
console.log();
|
|
84
|
+
|
|
85
|
+
console.log('Deny rules (never exported):');
|
|
86
|
+
if (policy.denyRules.length === 0) console.log(' (none)');
|
|
87
|
+
for (const rule of policy.denyRules) {
|
|
88
|
+
console.log(` [${rule.scope}] ${rule.patterns.join(' ')}`);
|
|
89
|
+
}
|
|
90
|
+
console.log(
|
|
91
|
+
label('Require redaction') + (policy.requireRedaction ? 'yes (--no-redact is refused)' : 'no')
|
|
92
|
+
);
|
|
93
|
+
console.log(
|
|
94
|
+
label('Max export tokens') +
|
|
95
|
+
(policy.maxExportTokens === null
|
|
96
|
+
? 'no limit'
|
|
97
|
+
: policy.maxExportTokens.toLocaleString('en-US'))
|
|
98
|
+
);
|
|
99
|
+
console.log(
|
|
100
|
+
label('Allowed target models') +
|
|
101
|
+
(policy.modelRules.length === 0
|
|
102
|
+
? 'any'
|
|
103
|
+
: policy.modelRules
|
|
104
|
+
.map((rule) => `${rule.patterns.join(', ')} [${rule.scope}]`)
|
|
105
|
+
.join(' AND '))
|
|
106
|
+
);
|
|
107
|
+
|
|
108
|
+
if (policy.ignoreFiles.length === 0) {
|
|
109
|
+
console.log(label('AI-tool ignore files') + 'none');
|
|
110
|
+
} else {
|
|
111
|
+
console.log(
|
|
112
|
+
label('AI-tool ignore files') + loadIgnoreFileRules(root, policy.ignoreFiles).describe()
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
console.log(
|
|
117
|
+
label('Audit log') + (policy.audit.enabled ? policy.audit.paths.map(display).join(', ') : 'off')
|
|
118
|
+
);
|
|
119
|
+
console.log();
|
|
120
|
+
console.log('✅ Policy is valid.');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function init(force) {
|
|
124
|
+
const file = path.join(process.cwd(), POLICY_FILE);
|
|
125
|
+
|
|
126
|
+
fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
127
|
+
try {
|
|
128
|
+
// 'wx' fails when the file exists, without a separate check that could go stale
|
|
129
|
+
fs.writeFileSync(file, `${JSON.stringify(createStarterPolicy(), null, 2)}\n`, {
|
|
130
|
+
flag: force ? 'w' : 'wx',
|
|
131
|
+
});
|
|
132
|
+
} catch (error) {
|
|
133
|
+
if (error.code !== 'EEXIST') throw error;
|
|
134
|
+
console.error(`❌ ${POLICY_FILE} already exists. Use --force to overwrite it.`);
|
|
135
|
+
process.exitCode = 1;
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
console.log(`✅ Wrote ${POLICY_FILE}`);
|
|
140
|
+
console.log();
|
|
141
|
+
console.log('JSON has no comments, so the "$comment" field points to the reference:');
|
|
142
|
+
console.log(` ${POLICY_DOCS_URL}`);
|
|
143
|
+
console.log();
|
|
144
|
+
console.log('Next steps:');
|
|
145
|
+
console.log(' 1. Edit the "deny" list (.gitignore syntax) and the limits.');
|
|
146
|
+
console.log(' 2. Run `ctxman policy check` to validate it.');
|
|
147
|
+
console.log(' 3. Add .ctxman/audit.log to .gitignore; commit .ctxman/policy.json.');
|
|
148
|
+
console.log(` Organisation-wide rules: point ${POLICY_ENV} at a shared policy file.`);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
function printHelp() {
|
|
152
|
+
console.log('ctxman policy - export policy for this repository\n');
|
|
153
|
+
console.log('Usage: ctxman policy [check|init] [options]\n');
|
|
154
|
+
console.log('Commands:');
|
|
155
|
+
console.log(' check Validate the policy files and print the effective policy');
|
|
156
|
+
console.log(' (default)');
|
|
157
|
+
console.log(` init Write a starter ${POLICY_FILE}\n`);
|
|
158
|
+
console.log('Options:');
|
|
159
|
+
console.log(' --json check: print the effective policy as JSON');
|
|
160
|
+
console.log(' -f, --force init: overwrite an existing policy file');
|
|
161
|
+
console.log(' -h, --help Show this help\n');
|
|
162
|
+
console.log('Policy files:');
|
|
163
|
+
console.log(` ${POLICY_FILE} Project policy, committed with the repository`);
|
|
164
|
+
console.log(` $${POLICY_ENV} Organisation policy file; wins on conflicts (deny lists`);
|
|
165
|
+
console.log(' are combined, the stricter limit applies)\n');
|
|
166
|
+
console.log('Exit codes: 0 valid or no policy, 2 invalid policy file');
|
|
167
|
+
console.log(`Reference: ${POLICY_DOCS_URL}`);
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
main();
|