ctx-cc 1.0.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/README.md +144 -0
- package/agents/ctx-executor.md +139 -0
- package/agents/ctx-planner.md +141 -0
- package/agents/ctx-researcher.md +122 -0
- package/agents/ctx-verifier.md +182 -0
- package/bin/ctx.js +49 -0
- package/commands/do.md +130 -0
- package/commands/forget.md +58 -0
- package/commands/help.md +146 -0
- package/commands/init.md +132 -0
- package/commands/pause.md +104 -0
- package/commands/phase-add.md +53 -0
- package/commands/phase-list.md +46 -0
- package/commands/phase-next.md +67 -0
- package/commands/plan.md +139 -0
- package/commands/recall.md +72 -0
- package/commands/remember.md +68 -0
- package/commands/resume.md +108 -0
- package/commands/ship.md +119 -0
- package/commands/status.md +95 -0
- package/commands/update.md +117 -0
- package/commands/verify.md +151 -0
- package/package.json +39 -0
- package/src/install.js +173 -0
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ctx:update
|
|
3
|
+
description: Check for CTX updates and install if available
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<objective>
|
|
7
|
+
Check for CTX updates, show what's new, and install if user confirms.
|
|
8
|
+
</objective>
|
|
9
|
+
|
|
10
|
+
<process>
|
|
11
|
+
|
|
12
|
+
<step name="get_installed_version">
|
|
13
|
+
Read installed version:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
cat ~/.claude/ctx/VERSION 2>/dev/null
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
If VERSION file missing, treat as version 0.0.0.
|
|
20
|
+
</step>
|
|
21
|
+
|
|
22
|
+
<step name="check_latest_version">
|
|
23
|
+
Check npm for latest version:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npm view ctx-cc version 2>/dev/null
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
If npm check fails:
|
|
30
|
+
```
|
|
31
|
+
Couldn't check for updates (offline or npm unavailable).
|
|
32
|
+
|
|
33
|
+
To update manually: `npx ctx-cc --force`
|
|
34
|
+
```
|
|
35
|
+
</step>
|
|
36
|
+
|
|
37
|
+
<step name="compare_versions">
|
|
38
|
+
Compare installed vs latest:
|
|
39
|
+
|
|
40
|
+
**If installed == latest:**
|
|
41
|
+
```
|
|
42
|
+
## CTX Update
|
|
43
|
+
|
|
44
|
+
**Installed:** {version}
|
|
45
|
+
**Latest:** {version}
|
|
46
|
+
|
|
47
|
+
You're already on the latest version.
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
**If installed < latest:**
|
|
51
|
+
Continue to show changes and confirm.
|
|
52
|
+
</step>
|
|
53
|
+
|
|
54
|
+
<step name="show_changes">
|
|
55
|
+
Fetch changelog and show what's new:
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
## CTX Update Available
|
|
59
|
+
|
|
60
|
+
**Installed:** {old version}
|
|
61
|
+
**Latest:** {new version}
|
|
62
|
+
|
|
63
|
+
### What's New
|
|
64
|
+
────────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
{changelog entries between versions}
|
|
67
|
+
|
|
68
|
+
────────────────────────────────────────────────────────────
|
|
69
|
+
|
|
70
|
+
**Note:** The installer will replace:
|
|
71
|
+
- `~/.claude/commands/ctx/`
|
|
72
|
+
- `~/.claude/ctx/`
|
|
73
|
+
- `~/.claude/agents/ctx-*`
|
|
74
|
+
|
|
75
|
+
Your custom files are preserved.
|
|
76
|
+
```
|
|
77
|
+
</step>
|
|
78
|
+
|
|
79
|
+
<step name="confirm_update">
|
|
80
|
+
Ask user to confirm:
|
|
81
|
+
|
|
82
|
+
**Options:**
|
|
83
|
+
- "Yes, update now"
|
|
84
|
+
- "No, cancel"
|
|
85
|
+
</step>
|
|
86
|
+
|
|
87
|
+
<step name="run_update">
|
|
88
|
+
If confirmed:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
npx ctx-cc --force
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Clear any update cache.
|
|
95
|
+
</step>
|
|
96
|
+
|
|
97
|
+
<step name="complete">
|
|
98
|
+
```
|
|
99
|
+
╔═══════════════════════════════════════════════════════════╗
|
|
100
|
+
║ CTX Updated: v{old} → v{new} ║
|
|
101
|
+
╚═══════════════════════════════════════════════════════════╝
|
|
102
|
+
|
|
103
|
+
⚠️ Restart Claude Code to pick up the new commands.
|
|
104
|
+
|
|
105
|
+
View full changelog: https://github.com/jufjuf/CTX/blob/main/CHANGELOG.md
|
|
106
|
+
```
|
|
107
|
+
</step>
|
|
108
|
+
|
|
109
|
+
</process>
|
|
110
|
+
|
|
111
|
+
<success_criteria>
|
|
112
|
+
- [ ] Installed version detected
|
|
113
|
+
- [ ] Latest version checked
|
|
114
|
+
- [ ] Changelog shown for new versions
|
|
115
|
+
- [ ] User confirmation obtained
|
|
116
|
+
- [ ] Update executed successfully
|
|
117
|
+
</success_criteria>
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ctx:verify
|
|
3
|
+
description: Three-level verification of current phase
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
<objective>
|
|
7
|
+
Verify the current phase implementation using three-level verification plus anti-pattern scanning.
|
|
8
|
+
</objective>
|
|
9
|
+
|
|
10
|
+
<process>
|
|
11
|
+
|
|
12
|
+
<step name="load_phase">
|
|
13
|
+
Load current phase from `.ctx/ROADMAP.md`.
|
|
14
|
+
Load verification criteria from `.ctx/phases/{phase-id}/PLAN.md`.
|
|
15
|
+
|
|
16
|
+
If no phase:
|
|
17
|
+
```
|
|
18
|
+
No phase to verify. Run `/ctx:plan <goal>` first.
|
|
19
|
+
```
|
|
20
|
+
</step>
|
|
21
|
+
|
|
22
|
+
<step name="three_level_verification">
|
|
23
|
+
For each artifact in the phase:
|
|
24
|
+
|
|
25
|
+
### Level 1: EXISTS
|
|
26
|
+
```
|
|
27
|
+
Question: Is the file on disk?
|
|
28
|
+
Check: Glob for the file path
|
|
29
|
+
Pass: File exists
|
|
30
|
+
Fail: File missing
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
### Level 2: SUBSTANTIVE
|
|
34
|
+
```
|
|
35
|
+
Question: Is it real code, not a stub?
|
|
36
|
+
Check:
|
|
37
|
+
- No "// TODO" comments in new code
|
|
38
|
+
- No empty function bodies
|
|
39
|
+
- No placeholder returns (return null, return {})
|
|
40
|
+
- No "not implemented" comments
|
|
41
|
+
Pass: Real, complete implementation
|
|
42
|
+
Fail: Stub or placeholder detected
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### Level 3: WIRED
|
|
46
|
+
```
|
|
47
|
+
Question: Is it imported and used?
|
|
48
|
+
Check:
|
|
49
|
+
- Grep for imports of the file
|
|
50
|
+
- Trace call paths from entry points
|
|
51
|
+
- Verify the code is reachable
|
|
52
|
+
Pass: Connected to the application
|
|
53
|
+
Fail: Orphan code (exists but unused)
|
|
54
|
+
```
|
|
55
|
+
</step>
|
|
56
|
+
|
|
57
|
+
<step name="anti_pattern_scan">
|
|
58
|
+
Scan for common anti-patterns:
|
|
59
|
+
|
|
60
|
+
| Pattern | Search | Severity |
|
|
61
|
+
|---------|--------|----------|
|
|
62
|
+
| TODO comments | `// TODO`, `# TODO` | Warning |
|
|
63
|
+
| Empty catch | `catch.*\{\s*\}` | Error |
|
|
64
|
+
| Console-only errors | `console.error` without throw | Warning |
|
|
65
|
+
| Placeholder returns | `return null`, `return {}` | Error |
|
|
66
|
+
| Hardcoded secrets | API keys, passwords | Critical |
|
|
67
|
+
| Debug code | `console.log`, `debugger` | Warning |
|
|
68
|
+
</step>
|
|
69
|
+
|
|
70
|
+
<step name="goal_gap_analysis">
|
|
71
|
+
Check if phase goal is achieved:
|
|
72
|
+
|
|
73
|
+
1. Re-read the original goal
|
|
74
|
+
2. Compare against what was built
|
|
75
|
+
3. Identify any gaps
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
Goal: {original goal}
|
|
79
|
+
Built: {what was implemented}
|
|
80
|
+
Gaps: {missing pieces, if any}
|
|
81
|
+
```
|
|
82
|
+
</step>
|
|
83
|
+
|
|
84
|
+
<step name="generate_report">
|
|
85
|
+
Write `.ctx/phases/{phase-id}/VERIFY.md`:
|
|
86
|
+
|
|
87
|
+
```markdown
|
|
88
|
+
# Verification Report
|
|
89
|
+
|
|
90
|
+
## Phase: {name}
|
|
91
|
+
## Date: {timestamp}
|
|
92
|
+
|
|
93
|
+
## Three-Level Results
|
|
94
|
+
|
|
95
|
+
| Artifact | Exists | Substantive | Wired | Status |
|
|
96
|
+
|----------|--------|-------------|-------|--------|
|
|
97
|
+
| {file1} | ✓ | ✓ | ✓ | PASS |
|
|
98
|
+
| {file2} | ✓ | ✓ | ✗ | FAIL |
|
|
99
|
+
|
|
100
|
+
## Anti-Pattern Scan
|
|
101
|
+
|
|
102
|
+
| Pattern | Count | Files | Severity |
|
|
103
|
+
|---------|-------|-------|----------|
|
|
104
|
+
| TODO | 2 | auth.ts, login.ts | Warning |
|
|
105
|
+
|
|
106
|
+
## Goal Gap Analysis
|
|
107
|
+
|
|
108
|
+
**Goal:** {goal}
|
|
109
|
+
**Status:** {Achieved / Partially Achieved / Not Achieved}
|
|
110
|
+
**Gaps:** {list or "None"}
|
|
111
|
+
|
|
112
|
+
## Overall: {PASS / FAIL}
|
|
113
|
+
|
|
114
|
+
{If FAIL: List what needs to be fixed}
|
|
115
|
+
```
|
|
116
|
+
</step>
|
|
117
|
+
|
|
118
|
+
<step name="output_summary">
|
|
119
|
+
Display verification summary:
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
## Verification: {phase name}
|
|
123
|
+
|
|
124
|
+
**Three-Level Check:**
|
|
125
|
+
- Exists: {pass/fail count}
|
|
126
|
+
- Substantive: {pass/fail count}
|
|
127
|
+
- Wired: {pass/fail count}
|
|
128
|
+
|
|
129
|
+
**Anti-Patterns:** {count} found
|
|
130
|
+
**Goal Gaps:** {count or "None"}
|
|
131
|
+
|
|
132
|
+
**Overall:** {PASS ✓ / FAIL ✗}
|
|
133
|
+
|
|
134
|
+
{If FAIL:}
|
|
135
|
+
**Action Required:**
|
|
136
|
+
- {list of fixes needed}
|
|
137
|
+
|
|
138
|
+
{If PASS:}
|
|
139
|
+
**Next:** Run `/ctx:phase next` or `/ctx:ship`
|
|
140
|
+
```
|
|
141
|
+
</step>
|
|
142
|
+
|
|
143
|
+
</process>
|
|
144
|
+
|
|
145
|
+
<success_criteria>
|
|
146
|
+
- [ ] All artifacts checked at three levels
|
|
147
|
+
- [ ] Anti-pattern scan completed
|
|
148
|
+
- [ ] Goal gap analysis done
|
|
149
|
+
- [ ] VERIFY.md written
|
|
150
|
+
- [ ] Clear pass/fail determination
|
|
151
|
+
</success_criteria>
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ctx-cc",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "CTX - Smart Context Management for Claude Code. The GSD Killer.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude",
|
|
7
|
+
"claude-code",
|
|
8
|
+
"ai",
|
|
9
|
+
"context-management",
|
|
10
|
+
"workflow",
|
|
11
|
+
"development",
|
|
12
|
+
"productivity"
|
|
13
|
+
],
|
|
14
|
+
"author": "jufjuf",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"repository": {
|
|
17
|
+
"type": "git",
|
|
18
|
+
"url": "https://github.com/jufjuf/CTX.git"
|
|
19
|
+
},
|
|
20
|
+
"homepage": "https://github.com/jufjuf/CTX",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/jufjuf/CTX/issues"
|
|
23
|
+
},
|
|
24
|
+
"bin": {
|
|
25
|
+
"ctx-cc": "./bin/ctx.js"
|
|
26
|
+
},
|
|
27
|
+
"files": [
|
|
28
|
+
"bin/",
|
|
29
|
+
"src/",
|
|
30
|
+
"commands/",
|
|
31
|
+
"agents/",
|
|
32
|
+
"references/",
|
|
33
|
+
"templates/"
|
|
34
|
+
],
|
|
35
|
+
"engines": {
|
|
36
|
+
"node": ">=18.0.0"
|
|
37
|
+
},
|
|
38
|
+
"type": "module"
|
|
39
|
+
}
|
package/src/install.js
ADDED
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import readline from 'readline';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = path.dirname(__filename);
|
|
8
|
+
const packageRoot = path.resolve(__dirname, '..');
|
|
9
|
+
|
|
10
|
+
const VERSION = JSON.parse(
|
|
11
|
+
fs.readFileSync(path.join(packageRoot, 'package.json'), 'utf-8')
|
|
12
|
+
).version;
|
|
13
|
+
|
|
14
|
+
// ANSI colors
|
|
15
|
+
const cyan = (s) => `\x1b[36m${s}\x1b[0m`;
|
|
16
|
+
const green = (s) => `\x1b[32m${s}\x1b[0m`;
|
|
17
|
+
const yellow = (s) => `\x1b[33m${s}\x1b[0m`;
|
|
18
|
+
const red = (s) => `\x1b[31m${s}\x1b[0m`;
|
|
19
|
+
const dim = (s) => `\x1b[2m${s}\x1b[0m`;
|
|
20
|
+
const bold = (s) => `\x1b[1m${s}\x1b[0m`;
|
|
21
|
+
|
|
22
|
+
function printBanner() {
|
|
23
|
+
console.log(cyan(`
|
|
24
|
+
██████╗████████╗██╗ ██╗
|
|
25
|
+
██╔════╝╚══██╔══╝╚██╗██╔╝
|
|
26
|
+
██║ ██║ ╚███╔╝
|
|
27
|
+
██║ ██║ ██╔██╗
|
|
28
|
+
╚██████╗ ██║ ██╔╝ ██╗
|
|
29
|
+
╚═════╝ ╚═╝ ╚═╝ ╚═╝
|
|
30
|
+
`));
|
|
31
|
+
console.log(` ${bold('CTX')} ${dim(`v${VERSION}`)}`);
|
|
32
|
+
console.log(' Smart Context Management for Claude Code.');
|
|
33
|
+
console.log(' The GSD Killer. 12 commands, infinite power.\n');
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function copyDir(src, dest) {
|
|
37
|
+
if (!fs.existsSync(dest)) {
|
|
38
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
42
|
+
let count = 0;
|
|
43
|
+
|
|
44
|
+
for (const entry of entries) {
|
|
45
|
+
const srcPath = path.join(src, entry.name);
|
|
46
|
+
const destPath = path.join(dest, entry.name);
|
|
47
|
+
|
|
48
|
+
if (entry.isDirectory()) {
|
|
49
|
+
count += copyDir(srcPath, destPath);
|
|
50
|
+
} else {
|
|
51
|
+
fs.copyFileSync(srcPath, destPath);
|
|
52
|
+
count++;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return count;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function removeDir(dir) {
|
|
60
|
+
if (fs.existsSync(dir)) {
|
|
61
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export async function install(options) {
|
|
66
|
+
printBanner();
|
|
67
|
+
|
|
68
|
+
// Determine target directory
|
|
69
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE;
|
|
70
|
+
let targetBase;
|
|
71
|
+
|
|
72
|
+
if (options.project) {
|
|
73
|
+
targetBase = path.join(process.cwd(), '.claude');
|
|
74
|
+
console.log(` Installing for ${cyan('current project')} to ${cyan('.claude')}\n`);
|
|
75
|
+
} else {
|
|
76
|
+
targetBase = path.join(homeDir, '.claude');
|
|
77
|
+
console.log(` Installing ${cyan('globally')} to ${cyan('~/.claude')}\n`);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// Check for existing installation
|
|
81
|
+
const commandsDir = path.join(targetBase, 'commands', 'ctx');
|
|
82
|
+
const agentsDir = targetBase === path.join(homeDir, '.claude')
|
|
83
|
+
? path.join(targetBase, 'agents')
|
|
84
|
+
: path.join(targetBase, 'agents');
|
|
85
|
+
const ctxDir = path.join(targetBase, 'ctx');
|
|
86
|
+
|
|
87
|
+
if (fs.existsSync(commandsDir) && !options.force) {
|
|
88
|
+
const existingVersion = getExistingVersion(ctxDir);
|
|
89
|
+
if (existingVersion) {
|
|
90
|
+
console.log(yellow(` ⚠ CTX ${existingVersion} is already installed.`));
|
|
91
|
+
console.log(` Use ${cyan('--force')} to reinstall.\n`);
|
|
92
|
+
|
|
93
|
+
if (existingVersion === VERSION) {
|
|
94
|
+
console.log(green(' ✓ Already up to date.\n'));
|
|
95
|
+
process.exit(0);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// Clean install - remove existing
|
|
101
|
+
if (options.force || !fs.existsSync(commandsDir)) {
|
|
102
|
+
console.log(' Cleaning previous installation...');
|
|
103
|
+
removeDir(commandsDir);
|
|
104
|
+
removeDir(ctxDir);
|
|
105
|
+
// Only remove ctx-* agents, not all agents
|
|
106
|
+
if (fs.existsSync(agentsDir)) {
|
|
107
|
+
const agents = fs.readdirSync(agentsDir);
|
|
108
|
+
for (const agent of agents) {
|
|
109
|
+
if (agent.startsWith('ctx-')) {
|
|
110
|
+
fs.unlinkSync(path.join(agentsDir, agent));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Create directories
|
|
117
|
+
fs.mkdirSync(commandsDir, { recursive: true });
|
|
118
|
+
fs.mkdirSync(ctxDir, { recursive: true });
|
|
119
|
+
fs.mkdirSync(agentsDir, { recursive: true });
|
|
120
|
+
|
|
121
|
+
// Copy commands
|
|
122
|
+
const srcCommands = path.join(packageRoot, 'commands');
|
|
123
|
+
if (fs.existsSync(srcCommands)) {
|
|
124
|
+
const count = copyDir(srcCommands, commandsDir);
|
|
125
|
+
console.log(green(` ✓`) + ` Installed commands/ctx (${count} files)`);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Copy agents
|
|
129
|
+
const srcAgents = path.join(packageRoot, 'agents');
|
|
130
|
+
if (fs.existsSync(srcAgents)) {
|
|
131
|
+
const agents = fs.readdirSync(srcAgents);
|
|
132
|
+
for (const agent of agents) {
|
|
133
|
+
fs.copyFileSync(
|
|
134
|
+
path.join(srcAgents, agent),
|
|
135
|
+
path.join(agentsDir, agent)
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
console.log(green(` ✓`) + ` Installed agents (${agents.length} files)`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Copy references
|
|
142
|
+
const srcRefs = path.join(packageRoot, 'references');
|
|
143
|
+
const destRefs = path.join(ctxDir, 'references');
|
|
144
|
+
if (fs.existsSync(srcRefs)) {
|
|
145
|
+
const count = copyDir(srcRefs, destRefs);
|
|
146
|
+
console.log(green(` ✓`) + ` Installed references (${count} files)`);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Copy templates
|
|
150
|
+
const srcTemplates = path.join(packageRoot, 'templates');
|
|
151
|
+
const destTemplates = path.join(ctxDir, 'templates');
|
|
152
|
+
if (fs.existsSync(srcTemplates)) {
|
|
153
|
+
const count = copyDir(srcTemplates, destTemplates);
|
|
154
|
+
console.log(green(` ✓`) + ` Installed templates (${count} files)`);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// Write VERSION file
|
|
158
|
+
fs.writeFileSync(path.join(ctxDir, 'VERSION'), VERSION);
|
|
159
|
+
console.log(green(` ✓`) + ` Wrote VERSION (${VERSION})`);
|
|
160
|
+
|
|
161
|
+
// Success message
|
|
162
|
+
console.log(`\n ${green('Done!')} Launch Claude Code and run ${cyan('/ctx:help')}.`);
|
|
163
|
+
console.log(`\n ${dim('GitHub:')} https://github.com/jufjuf/CTX`);
|
|
164
|
+
console.log(` ${dim('Report issues:')} https://github.com/jufjuf/CTX/issues\n`);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function getExistingVersion(ctxDir) {
|
|
168
|
+
const versionFile = path.join(ctxDir, 'VERSION');
|
|
169
|
+
if (fs.existsSync(versionFile)) {
|
|
170
|
+
return fs.readFileSync(versionFile, 'utf-8').trim();
|
|
171
|
+
}
|
|
172
|
+
return null;
|
|
173
|
+
}
|