@boyingliu01/xp-gate 0.7.0 → 0.8.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/bin/xp-gate.js +4 -9
- package/lib/__tests__/audit-log.test.ts +107 -0
- package/lib/__tests__/detect-deps.test.js +212 -42
- package/lib/__tests__/doctor.test.js +1 -0
- package/lib/__tests__/gate-audit.test.ts +59 -0
- package/lib/__tests__/init.test.js +47 -1
- package/lib/__tests__/install-skill.test.js +1 -45
- package/lib/__tests__/ui-detector.test.ts +186 -26
- package/lib/__tests__/ui-review.test.ts +119 -0
- package/lib/__tests__/uninstall.test.js +1 -0
- package/lib/detect-deps.js +180 -37
- package/lib/doctor.js +7 -9
- package/lib/gate-audit.ts +26 -42
- package/lib/init.js +42 -13
- package/lib/install-skill.js +20 -48
- package/lib/rollback.js +1 -16
- package/lib/shared-paths.js +30 -0
- package/lib/shared-utils.js +25 -0
- package/lib/ui-detector.ts +20 -14
- package/lib/ui-review.ts +58 -53
- package/lib/uninstall.js +8 -9
- package/package.json +1 -1
- package/plugins/claude-code/.claude-plugin/plugin.json +1 -1
- package/plugins/claude-code/skills/admin-template-guidelines/SKILL.md +904 -0
- package/plugins/claude-code/skills/delphi-review/.delphi-config.json.example +45 -0
- package/plugins/claude-code/skills/delphi-review/AGENTS.md +66 -0
- package/plugins/claude-code/skills/delphi-review/INSTALL.md +152 -0
- package/plugins/claude-code/skills/delphi-review/SKILL.md +454 -0
- package/plugins/claude-code/skills/delphi-review/opencode.json.delphi.example +56 -0
- package/plugins/claude-code/skills/delphi-review/references/code-walkthrough.md +486 -0
- package/plugins/claude-code/skills/improve-codebase-architecture/SKILL.md +156 -0
- package/plugins/claude-code/skills/ralph-loop/SKILL.md +501 -0
- package/plugins/claude-code/skills/ralph-loop/references/components/memory.md +55 -0
- package/plugins/claude-code/skills/ralph-loop/references/components/middleware.md +54 -0
- package/plugins/claude-code/skills/ralph-loop/references/components/skill-invocations.md +39 -0
- package/plugins/claude-code/skills/ralph-loop/references/components/system-prompt.md +24 -0
- package/plugins/claude-code/skills/ralph-loop/references/components/tool-descriptions.md +32 -0
- package/plugins/claude-code/skills/ralph-loop/references/phase-2-build-ralph.md +89 -0
- package/plugins/claude-code/skills/ralph-loop/templates/progress-log.md +36 -0
- package/plugins/claude-code/skills/sprint-flow/AGENTS.md +68 -0
- package/plugins/claude-code/skills/sprint-flow/SKILL.md +1248 -0
- package/plugins/claude-code/skills/sprint-flow/references/components/memory.md +87 -0
- package/plugins/claude-code/skills/sprint-flow/references/components/middleware.md +72 -0
- package/plugins/claude-code/skills/sprint-flow/references/components/skill-invocations.md +104 -0
- package/plugins/claude-code/skills/sprint-flow/references/components/system-prompt.md +27 -0
- package/plugins/claude-code/skills/sprint-flow/references/components/tool-descriptions.md +96 -0
- package/plugins/claude-code/skills/sprint-flow/references/force-levels.md +203 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-0-think.md +115 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-1-plan.md +178 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-2-build.md +198 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-3-review.md +213 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-4-uat.md +125 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-5-feedback.md +100 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-6-ship.md +193 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-7-land.md +140 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-8-cleanup.md +192 -0
- package/plugins/claude-code/skills/sprint-flow/references/phase-minus-0-5-auto-estimate.md +257 -0
- package/plugins/claude-code/skills/sprint-flow/templates/auto-estimate-learning-log.md +136 -0
- package/plugins/claude-code/skills/sprint-flow/templates/auto-estimate-output-template.md +132 -0
- package/plugins/claude-code/skills/sprint-flow/templates/emergent-issues-template.md +120 -0
- package/plugins/claude-code/skills/sprint-flow/templates/pain-document-template.md +115 -0
- package/plugins/claude-code/skills/sprint-flow/templates/sprint-progress-template.md +151 -0
- package/plugins/claude-code/skills/sprint-flow/templates/sprint-summary-template.md +120 -0
- package/plugins/claude-code/skills/test-driven-development/SKILL.md +71 -0
- package/plugins/claude-code/skills/test-specification-alignment/AGENTS.md +56 -0
- package/plugins/claude-code/skills/test-specification-alignment/SKILL.md +702 -0
- package/plugins/claude-code/skills/test-specification-alignment/references/alignment-verification-algorithm.md +493 -0
- package/plugins/claude-code/skills/test-specification-alignment/references/phase2-constraint-enforcement.md +431 -0
- package/plugins/claude-code/skills/test-specification-alignment/references/specification-format.md +348 -0
- package/plugins/claude-code/skills/to-issues/SKILL.md +277 -0
- package/plugins/opencode/README.md +38 -0
- package/plugins/opencode/index.ts +85 -0
- package/plugins/opencode/package.json +18 -0
- package/plugins/opencode/skills/admin-template-guidelines/SKILL.md +904 -0
- package/plugins/opencode/skills/delphi-review/.delphi-config.json.example +45 -0
- package/plugins/opencode/skills/delphi-review/AGENTS.md +66 -0
- package/plugins/opencode/skills/delphi-review/INSTALL.md +152 -0
- package/plugins/opencode/skills/delphi-review/SKILL.md +454 -0
- package/plugins/opencode/skills/delphi-review/opencode.json.delphi.example +56 -0
- package/plugins/opencode/skills/delphi-review/references/code-walkthrough.md +486 -0
- package/plugins/opencode/skills/improve-codebase-architecture/SKILL.md +156 -0
- package/plugins/opencode/skills/ralph-loop/SKILL.md +501 -0
- package/plugins/opencode/skills/ralph-loop/references/components/memory.md +55 -0
- package/plugins/opencode/skills/ralph-loop/references/components/middleware.md +54 -0
- package/plugins/opencode/skills/ralph-loop/references/components/skill-invocations.md +39 -0
- package/plugins/opencode/skills/ralph-loop/references/components/system-prompt.md +24 -0
- package/plugins/opencode/skills/ralph-loop/references/components/tool-descriptions.md +32 -0
- package/plugins/opencode/skills/ralph-loop/references/phase-2-build-ralph.md +89 -0
- package/plugins/opencode/skills/ralph-loop/templates/progress-log.md +36 -0
- package/plugins/opencode/skills/sprint-flow/AGENTS.md +68 -0
- package/plugins/opencode/skills/sprint-flow/SKILL.md +1248 -0
- package/plugins/opencode/skills/sprint-flow/references/components/memory.md +87 -0
- package/plugins/opencode/skills/sprint-flow/references/components/middleware.md +72 -0
- package/plugins/opencode/skills/sprint-flow/references/components/skill-invocations.md +104 -0
- package/plugins/opencode/skills/sprint-flow/references/components/system-prompt.md +27 -0
- package/plugins/opencode/skills/sprint-flow/references/components/tool-descriptions.md +96 -0
- package/plugins/opencode/skills/sprint-flow/references/force-levels.md +203 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-0-think.md +115 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-1-plan.md +178 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-2-build.md +198 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-3-review.md +213 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-4-uat.md +125 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-5-feedback.md +100 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-6-ship.md +193 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-7-land.md +140 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-8-cleanup.md +192 -0
- package/plugins/opencode/skills/sprint-flow/references/phase-minus-0-5-auto-estimate.md +257 -0
- package/plugins/opencode/skills/sprint-flow/templates/auto-estimate-learning-log.md +136 -0
- package/plugins/opencode/skills/sprint-flow/templates/auto-estimate-output-template.md +132 -0
- package/plugins/opencode/skills/sprint-flow/templates/emergent-issues-template.md +120 -0
- package/plugins/opencode/skills/sprint-flow/templates/pain-document-template.md +115 -0
- package/plugins/opencode/skills/sprint-flow/templates/sprint-progress-template.md +151 -0
- package/plugins/opencode/skills/sprint-flow/templates/sprint-summary-template.md +120 -0
- package/plugins/opencode/skills/test-driven-development/SKILL.md +71 -0
- package/plugins/opencode/skills/test-specification-alignment/AGENTS.md +56 -0
- package/plugins/opencode/skills/test-specification-alignment/SKILL.md +702 -0
- package/plugins/opencode/skills/test-specification-alignment/references/alignment-verification-algorithm.md +493 -0
- package/plugins/opencode/skills/test-specification-alignment/references/phase2-constraint-enforcement.md +431 -0
- package/plugins/opencode/skills/test-specification-alignment/references/specification-format.md +348 -0
- package/plugins/opencode/skills/to-issues/SKILL.md +277 -0
- package/plugins/opencode/tsconfig.json +15 -0
- package/plugins/qoder/skills/admin-template-guidelines/SKILL.md +904 -0
- package/plugins/qoder/skills/delphi-review/.delphi-config.json.example +45 -0
- package/plugins/qoder/skills/delphi-review/AGENTS.md +66 -0
- package/plugins/qoder/skills/delphi-review/INSTALL.md +152 -0
- package/plugins/qoder/skills/delphi-review/SKILL.md +454 -0
- package/plugins/qoder/skills/delphi-review/opencode.json.delphi.example +56 -0
- package/plugins/qoder/skills/delphi-review/references/code-walkthrough.md +486 -0
- package/plugins/qoder/skills/improve-codebase-architecture/SKILL.md +156 -0
- package/plugins/qoder/skills/ralph-loop/SKILL.md +501 -0
- package/plugins/qoder/skills/ralph-loop/references/components/memory.md +55 -0
- package/plugins/qoder/skills/ralph-loop/references/components/middleware.md +54 -0
- package/plugins/qoder/skills/ralph-loop/references/components/skill-invocations.md +39 -0
- package/plugins/qoder/skills/ralph-loop/references/components/system-prompt.md +24 -0
- package/plugins/qoder/skills/ralph-loop/references/components/tool-descriptions.md +32 -0
- package/plugins/qoder/skills/ralph-loop/references/phase-2-build-ralph.md +89 -0
- package/plugins/qoder/skills/ralph-loop/templates/progress-log.md +36 -0
- package/plugins/qoder/skills/sprint-flow/AGENTS.md +68 -0
- package/plugins/qoder/skills/sprint-flow/SKILL.md +1248 -0
- package/plugins/qoder/skills/sprint-flow/references/components/memory.md +87 -0
- package/plugins/qoder/skills/sprint-flow/references/components/middleware.md +72 -0
- package/plugins/qoder/skills/sprint-flow/references/components/skill-invocations.md +104 -0
- package/plugins/qoder/skills/sprint-flow/references/components/system-prompt.md +27 -0
- package/plugins/qoder/skills/sprint-flow/references/components/tool-descriptions.md +96 -0
- package/plugins/qoder/skills/sprint-flow/references/force-levels.md +203 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-0-think.md +115 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-1-plan.md +178 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-2-build.md +198 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-3-review.md +213 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-4-uat.md +125 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-5-feedback.md +100 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-6-ship.md +193 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-7-land.md +140 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-8-cleanup.md +192 -0
- package/plugins/qoder/skills/sprint-flow/references/phase-minus-0-5-auto-estimate.md +257 -0
- package/plugins/qoder/skills/sprint-flow/templates/auto-estimate-learning-log.md +136 -0
- package/plugins/qoder/skills/sprint-flow/templates/auto-estimate-output-template.md +132 -0
- package/plugins/qoder/skills/sprint-flow/templates/emergent-issues-template.md +120 -0
- package/plugins/qoder/skills/sprint-flow/templates/pain-document-template.md +115 -0
- package/plugins/qoder/skills/sprint-flow/templates/sprint-progress-template.md +151 -0
- package/plugins/qoder/skills/sprint-flow/templates/sprint-summary-template.md +120 -0
- package/plugins/qoder/skills/test-driven-development/SKILL.md +71 -0
- package/plugins/qoder/skills/test-specification-alignment/AGENTS.md +56 -0
- package/plugins/qoder/skills/test-specification-alignment/SKILL.md +702 -0
- package/plugins/qoder/skills/test-specification-alignment/references/alignment-verification-algorithm.md +493 -0
- package/plugins/qoder/skills/test-specification-alignment/references/phase2-constraint-enforcement.md +431 -0
- package/plugins/qoder/skills/test-specification-alignment/references/specification-format.md +348 -0
- package/plugins/qoder/skills/to-issues/SKILL.md +277 -0
- package/skills/admin-template-guidelines/SKILL.md +904 -0
- package/skills/delphi-review/SKILL.md +112 -77
- package/skills/improve-codebase-architecture/SKILL.md +156 -0
- package/skills/ralph-loop/SKILL.md +165 -34
- package/skills/sprint-flow/SKILL.md +448 -107
- package/skills/sprint-flow/references/components/skill-invocations.md +1 -1
- package/skills/sprint-flow/references/components/tool-descriptions.md +1 -1
- package/skills/sprint-flow/references/force-levels.md +203 -0
- package/skills/sprint-flow/references/phase-1-plan.md +4 -4
- package/skills/sprint-flow/references/phase-minus-0-5-auto-estimate.md +20 -1
- package/skills/sprint-flow/templates/auto-estimate-output-template.md +7 -5
- package/skills/sprint-flow/templates/sprint-progress-template.md +151 -0
- package/skills/test-driven-development/SKILL.md +71 -0
- package/skills/test-specification-alignment/SKILL.md +98 -24
- package/skills/to-issues/SKILL.md +277 -0
- package/plugins/qoder/AGENTS.md +0 -93
- package/plugins/qoder/README.md +0 -87
- package/plugins/qoder/widgets/quality-report.html +0 -163
- package/plugins/qoder/widgets/sprint-dashboard.html +0 -172
- package/skills/delphi-review/evals/evals.json +0 -82
- package/skills/delphi-review/references/qoder-multi-model.md +0 -191
- package/skills/ralph-loop/evals/evals.json +0 -311
- package/skills/ralph-loop/evolution-history.json +0 -59
- package/skills/ralph-loop/evolution-log.md +0 -16
- package/skills/sprint-flow/evals/evals.json +0 -130
- package/skills/sprint-flow/evolution-history.json +0 -39
- package/skills/sprint-flow/evolution-log.md +0 -23
- package/skills/sprint-flow/references/qoder-adaptation.md +0 -173
- package/skills/test-specification-alignment/evals/evals.json +0 -75
package/lib/detect-deps.js
CHANGED
|
@@ -3,29 +3,73 @@ const path = require('path');
|
|
|
3
3
|
const os = require('os');
|
|
4
4
|
const { execSync } = require('child_process');
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
* Resolve the user's home directory cross-platform.
|
|
8
|
-
* Fallback chain: HOME → USERPROFILE → os.homedir()
|
|
9
|
-
* This covers all known Windows/Linux/macOS/edge cases.
|
|
10
|
-
*/
|
|
11
|
-
const HOME = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
12
|
-
|
|
13
|
-
const SKILLS_DIR = path.join(HOME, '.config', 'opencode', 'skills');
|
|
14
|
-
const OPENCODE_DIR = path.join(HOME, '.config', 'opencode');
|
|
6
|
+
const { HOME_DIR } = require('./shared-paths.js');
|
|
15
7
|
|
|
16
8
|
const REQUIRED_DEPS = [
|
|
17
|
-
{ name: 'superpowers', minVersion: '1.0.0' },
|
|
18
|
-
{ name: 'gstack', minVersion: '1.0.0' }
|
|
9
|
+
{ name: 'superpowers', repo: 'obra/superpowers', minVersion: '1.0.0' },
|
|
10
|
+
{ name: 'gstack', repo: 'garrytan/gstack', minVersion: '1.0.0' }
|
|
19
11
|
];
|
|
20
12
|
|
|
21
|
-
|
|
22
|
-
|
|
13
|
+
/**
|
|
14
|
+
* Platform profiles — each AI agent platform has its own skills directory.
|
|
15
|
+
* All platforms require the same dependencies (superpowers, gstack).
|
|
16
|
+
*
|
|
17
|
+
* @covers Issue #128 Bug 1 — Qoder platform was excluded from dependency checks
|
|
18
|
+
*/
|
|
23
19
|
const PLATFORM_PROFILES = {
|
|
24
|
-
opencode: {
|
|
25
|
-
|
|
26
|
-
|
|
20
|
+
opencode: {
|
|
21
|
+
skillsDirs: [
|
|
22
|
+
path.join(HOME_DIR, '.config', 'opencode', 'skills'),
|
|
23
|
+
path.join(HOME_DIR, '.config', 'opencode'),
|
|
24
|
+
],
|
|
25
|
+
requiredDeps: REQUIRED_DEPS,
|
|
26
|
+
},
|
|
27
|
+
'claude-code': {
|
|
28
|
+
skillsDirs: [
|
|
29
|
+
path.join(HOME_DIR, '.claude', 'skills'),
|
|
30
|
+
path.join(HOME_DIR, '.claude'),
|
|
31
|
+
],
|
|
32
|
+
requiredDeps: REQUIRED_DEPS,
|
|
33
|
+
},
|
|
34
|
+
qoder: {
|
|
35
|
+
skillsDirs: [
|
|
36
|
+
path.join(HOME_DIR, '.qoder', 'skills'),
|
|
37
|
+
path.join(HOME_DIR, '.qoder'),
|
|
38
|
+
],
|
|
39
|
+
requiredDeps: REQUIRED_DEPS,
|
|
40
|
+
},
|
|
27
41
|
};
|
|
28
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Detect which AI agent platform is currently in use.
|
|
45
|
+
* Checks for platform-specific directories in the user's home.
|
|
46
|
+
* Falls back to 'opencode' if no platform is detected.
|
|
47
|
+
*
|
|
48
|
+
* @returns {'opencode' | 'claude-code' | 'qoder'}
|
|
49
|
+
*/
|
|
50
|
+
function detectPlatform() {
|
|
51
|
+
// Check for Qoder-specific marker
|
|
52
|
+
if (fs.existsSync(path.join(HOME_DIR, '.qoder', 'skills'))) {
|
|
53
|
+
return 'qoder';
|
|
54
|
+
}
|
|
55
|
+
// Check for Claude Code-specific marker
|
|
56
|
+
if (fs.existsSync(path.join(HOME_DIR, '.claude', 'skills'))) {
|
|
57
|
+
return 'claude-code';
|
|
58
|
+
}
|
|
59
|
+
// Default to opencode (most common, backward compatible)
|
|
60
|
+
return 'opencode';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get the skills directories for a given platform.
|
|
65
|
+
* @param {string} platform - 'opencode' | 'claude-code' | 'qoder'
|
|
66
|
+
* @returns {string[]}
|
|
67
|
+
*/
|
|
68
|
+
function getSkillsDirs(platform) {
|
|
69
|
+
const profile = PLATFORM_PROFILES[platform] || PLATFORM_PROFILES.opencode;
|
|
70
|
+
return profile.skillsDirs;
|
|
71
|
+
}
|
|
72
|
+
|
|
29
73
|
/**
|
|
30
74
|
* Check if bash is available on the system.
|
|
31
75
|
* XP-Gate hooks are bash scripts — Windows users need Git Bash installed.
|
|
@@ -92,30 +136,28 @@ function checkBash() {
|
|
|
92
136
|
}
|
|
93
137
|
}
|
|
94
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Check if required dependencies are installed for the given platform.
|
|
141
|
+
* @param {string} [platform='opencode'] - AI agent platform
|
|
142
|
+
* @returns {Promise<{ok: boolean, missing?: string, versionMismatch?: object}>}
|
|
143
|
+
*/
|
|
95
144
|
async function checkDeps(platform = 'opencode') {
|
|
96
|
-
const
|
|
97
|
-
const { requiredDeps, skillsDirs } = profile;
|
|
98
|
-
|
|
99
|
-
// Qoder and other platforms with no hard dependencies pass immediately
|
|
100
|
-
if (requiredDeps.length === 0) {
|
|
101
|
-
return { ok: true, platform };
|
|
102
|
-
}
|
|
145
|
+
const skillsDirs = getSkillsDirs(platform);
|
|
103
146
|
|
|
104
|
-
for (const dep of
|
|
105
|
-
const possiblePaths = skillsDirs.map(dir => path.join(dir, dep.name));
|
|
106
|
-
|
|
147
|
+
for (const dep of REQUIRED_DEPS) {
|
|
107
148
|
let depDir = null;
|
|
108
|
-
for (const
|
|
109
|
-
|
|
110
|
-
|
|
149
|
+
for (const baseDir of skillsDirs) {
|
|
150
|
+
const candidate = path.join(baseDir, dep.name);
|
|
151
|
+
if (fs.existsSync(candidate)) {
|
|
152
|
+
depDir = candidate;
|
|
111
153
|
break;
|
|
112
154
|
}
|
|
113
155
|
}
|
|
114
|
-
|
|
156
|
+
|
|
115
157
|
if (!depDir) {
|
|
116
158
|
return { ok: false, missing: dep.name };
|
|
117
159
|
}
|
|
118
|
-
|
|
160
|
+
|
|
119
161
|
const version = await getSkillVersion(depDir);
|
|
120
162
|
if (version && compareVersions(version, dep.minVersion) < 0) {
|
|
121
163
|
return {
|
|
@@ -128,10 +170,103 @@ async function checkDeps(platform = 'opencode') {
|
|
|
128
170
|
};
|
|
129
171
|
}
|
|
130
172
|
}
|
|
131
|
-
|
|
173
|
+
|
|
132
174
|
return { ok: true };
|
|
133
175
|
}
|
|
134
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Auto-install missing dependencies by cloning from GitHub.
|
|
179
|
+
* @param {string} [platform='opencode'] - AI agent platform
|
|
180
|
+
* @returns {Promise<{ok: boolean, installed?: string[], errors?: Array<{name: string, message: string}>}>}
|
|
181
|
+
*/
|
|
182
|
+
function runGitClone(repoUrl, destPath) {
|
|
183
|
+
const cp = require('child_process');
|
|
184
|
+
const result = cp.spawnSync(
|
|
185
|
+
'git',
|
|
186
|
+
['clone', '--depth', '1', repoUrl, destPath],
|
|
187
|
+
{ stdio: ['ignore', 'pipe', 'pipe'], timeout: 120000, shell: false }
|
|
188
|
+
);
|
|
189
|
+
if (result.status === 0) return;
|
|
190
|
+
const stderr = String(result.stderr ?? '').trim();
|
|
191
|
+
throw new Error(stderr || `git clone exited with status ${result.status}`);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function ensureTargetDir(targetDir) {
|
|
195
|
+
if (fs.existsSync(targetDir)) return null;
|
|
196
|
+
try {
|
|
197
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
198
|
+
return null;
|
|
199
|
+
} catch (e) {
|
|
200
|
+
return { name: 'mkdir', message: `Cannot create skills directory: ${e.message}` };
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function versionMismatchError(versionMismatch) {
|
|
205
|
+
return {
|
|
206
|
+
name: versionMismatch.name,
|
|
207
|
+
message: `version mismatch: need ${versionMismatch.required}, found ${versionMismatch.found} (auto-install cannot upgrade)`,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function depExistsIn(skillsDirs, depName) {
|
|
212
|
+
for (const baseDir of skillsDirs) {
|
|
213
|
+
if (fs.existsSync(path.join(baseDir, depName))) return true;
|
|
214
|
+
}
|
|
215
|
+
return false;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function safeRemove(destPath) {
|
|
219
|
+
try {
|
|
220
|
+
if (fs.existsSync(destPath)) {
|
|
221
|
+
fs.rmSync(destPath, { recursive: true, force: true });
|
|
222
|
+
}
|
|
223
|
+
} catch {
|
|
224
|
+
// best-effort cleanup of partial clone
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
function installOneDep(dep, targetDir) {
|
|
229
|
+
const destPath = path.join(targetDir, dep.name);
|
|
230
|
+
const repoUrl = `https://github.com/${dep.repo}.git`;
|
|
231
|
+
try {
|
|
232
|
+
console.log(` ${dep.name}: not found → installing from ${repoUrl} ...`);
|
|
233
|
+
runGitClone(repoUrl, destPath);
|
|
234
|
+
console.log(` ${dep.name}: OK`);
|
|
235
|
+
return { ok: true };
|
|
236
|
+
} catch (e) {
|
|
237
|
+
const message = e.message || 'git clone failed';
|
|
238
|
+
console.warn(` ${dep.name}: FAILED (${message})`);
|
|
239
|
+
safeRemove(destPath);
|
|
240
|
+
return { ok: false, error: { name: dep.name, message } };
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
async function autoInstallDeps(platform = 'opencode') {
|
|
245
|
+
const skillsDirs = getSkillsDirs(platform);
|
|
246
|
+
const targetDir = skillsDirs[0];
|
|
247
|
+
|
|
248
|
+
const mkdirErr = ensureTargetDir(targetDir);
|
|
249
|
+
if (mkdirErr) return { ok: false, errors: [mkdirErr] };
|
|
250
|
+
|
|
251
|
+
const depCheck = await checkDeps(platform);
|
|
252
|
+
if (depCheck.ok) return { ok: true, installed: [] };
|
|
253
|
+
if (depCheck.versionMismatch) {
|
|
254
|
+
return { ok: false, installed: [], errors: [versionMismatchError(depCheck.versionMismatch)] };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
const errors = [];
|
|
258
|
+
const installed = [];
|
|
259
|
+
for (const dep of REQUIRED_DEPS) {
|
|
260
|
+
if (depExistsIn(skillsDirs, dep.name)) continue;
|
|
261
|
+
const result = installOneDep(dep, targetDir);
|
|
262
|
+
if (result.ok) installed.push(dep.name);
|
|
263
|
+
else errors.push(result.error);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (errors.length > 0) return { ok: false, installed, errors };
|
|
267
|
+
return { ok: true, installed };
|
|
268
|
+
}
|
|
269
|
+
|
|
135
270
|
async function getSkillVersion(skillDir) {
|
|
136
271
|
const pkgFile = path.join(skillDir, 'package.json');
|
|
137
272
|
if (fs.existsSync(pkgFile)) {
|
|
@@ -140,7 +275,7 @@ async function getSkillVersion(skillDir) {
|
|
|
140
275
|
return pkg.version;
|
|
141
276
|
} catch {}
|
|
142
277
|
}
|
|
143
|
-
|
|
278
|
+
|
|
144
279
|
const skillFile = path.join(skillDir, 'SKILL.md');
|
|
145
280
|
if (fs.existsSync(skillFile)) {
|
|
146
281
|
const content = fs.readFileSync(skillFile, 'utf8');
|
|
@@ -149,22 +284,30 @@ async function getSkillVersion(skillDir) {
|
|
|
149
284
|
return versionMatch[1];
|
|
150
285
|
}
|
|
151
286
|
}
|
|
152
|
-
|
|
287
|
+
|
|
153
288
|
return null;
|
|
154
289
|
}
|
|
155
290
|
|
|
156
291
|
function compareVersions(a, b) {
|
|
157
292
|
const partsA = a.split('.').map(Number);
|
|
158
293
|
const partsB = b.split('.').map(Number);
|
|
159
|
-
|
|
294
|
+
|
|
160
295
|
for (let i = 0; i < 3; i++) {
|
|
161
296
|
const partA = partsA[i] || 0;
|
|
162
297
|
const partB = partsB[i] || 0;
|
|
163
298
|
if (partA > partB) return 1;
|
|
164
299
|
if (partA < partB) return -1;
|
|
165
300
|
}
|
|
166
|
-
|
|
301
|
+
|
|
167
302
|
return 0;
|
|
168
303
|
}
|
|
169
304
|
|
|
170
|
-
module.exports = {
|
|
305
|
+
module.exports = {
|
|
306
|
+
checkDeps,
|
|
307
|
+
checkBash,
|
|
308
|
+
autoInstallDeps,
|
|
309
|
+
detectPlatform,
|
|
310
|
+
getSkillsDirs,
|
|
311
|
+
PLATFORM_PROFILES,
|
|
312
|
+
REQUIRED_DEPS,
|
|
313
|
+
};
|
package/lib/doctor.js
CHANGED
|
@@ -1,15 +1,13 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
3
|
const { execSync } = require('child_process');
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const GLOBAL_ADAPTERS_DIR = path.join(CONFIG_DIR, 'adapters');
|
|
4
|
+
const {
|
|
5
|
+
HOME_DIR,
|
|
6
|
+
CONFIG_DIR,
|
|
7
|
+
CONFIG_FILE,
|
|
8
|
+
GLOBAL_HOOKS_DIR,
|
|
9
|
+
GLOBAL_ADAPTERS_DIR,
|
|
10
|
+
} = require('./shared-paths.js');
|
|
13
11
|
|
|
14
12
|
// npm package source dir (template hooks/adapters)
|
|
15
13
|
const PKG_DIR = path.dirname(__dirname);
|
package/lib/gate-audit.ts
CHANGED
|
@@ -177,64 +177,44 @@ export function computeStats(
|
|
|
177
177
|
* Renames current file to .1, shifts existing archives.
|
|
178
178
|
* Keeps at most MAX_ARCHIVES archived files.
|
|
179
179
|
*/
|
|
180
|
-
|
|
181
|
-
if (!existsSync(logPath))
|
|
182
|
-
return;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
let stats;
|
|
180
|
+
function shouldRotate(logPath: string): boolean {
|
|
181
|
+
if (!existsSync(logPath)) return false;
|
|
186
182
|
try {
|
|
187
|
-
|
|
183
|
+
return statSync(logPath).size >= MAX_FILE_BYTES;
|
|
188
184
|
} catch {
|
|
189
|
-
return;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
if (stats.size < MAX_FILE_BYTES) {
|
|
193
|
-
return;
|
|
185
|
+
return false;
|
|
194
186
|
}
|
|
187
|
+
}
|
|
195
188
|
|
|
196
|
-
|
|
189
|
+
function shiftOldArchives(logPath: string): void {
|
|
197
190
|
for (let i = MAX_ARCHIVES; i >= 2; i--) {
|
|
198
191
|
const from = `${logPath}.${i - 1}`;
|
|
199
192
|
const to = `${logPath}.${i}`;
|
|
200
193
|
if (existsSync(from)) {
|
|
201
|
-
try {
|
|
202
|
-
renameSync(from, to);
|
|
203
|
-
} catch {
|
|
204
|
-
// Race condition or permission issue — skip
|
|
205
|
-
}
|
|
194
|
+
try { renameSync(from, to); } catch { /* skip */ }
|
|
206
195
|
}
|
|
207
196
|
}
|
|
197
|
+
}
|
|
208
198
|
|
|
209
|
-
|
|
210
|
-
const
|
|
211
|
-
if (existsSync(
|
|
212
|
-
try {
|
|
213
|
-
renameSync(firstArchive, `${logPath}.2`);
|
|
214
|
-
} catch {
|
|
215
|
-
// Skip on error
|
|
216
|
-
}
|
|
199
|
+
function archiveCurrent(logPath: string): void {
|
|
200
|
+
const first = `${logPath}.1`;
|
|
201
|
+
if (existsSync(first)) {
|
|
202
|
+
try { renameSync(first, `${logPath}.2`); } catch { /* skip */ }
|
|
217
203
|
}
|
|
218
|
-
|
|
219
204
|
try {
|
|
220
|
-
renameSync(logPath,
|
|
205
|
+
renameSync(logPath, first);
|
|
221
206
|
} catch {
|
|
222
|
-
|
|
223
|
-
try {
|
|
224
|
-
writeFileSync(logPath, '', 'utf8');
|
|
225
|
-
} catch {
|
|
226
|
-
console.error('[xp-gate audit] Failed to rotate or truncate log:', logPath);
|
|
227
|
-
}
|
|
207
|
+
try { writeFileSync(logPath, '', 'utf8'); } catch { /* skip */ }
|
|
228
208
|
}
|
|
229
209
|
}
|
|
230
210
|
|
|
231
|
-
|
|
211
|
+
export function rotateIfNeeded(logPath: string): void {
|
|
212
|
+
if (!shouldRotate(logPath)) return;
|
|
213
|
+
shiftOldArchives(logPath);
|
|
214
|
+
archiveCurrent(logPath);
|
|
215
|
+
}
|
|
232
216
|
|
|
233
|
-
|
|
234
|
-
* When called directly via `npx tsx gate-audit.ts record --gate-id ...`,
|
|
235
|
-
* this block handles CLI argument parsing and appends the entry.
|
|
236
|
-
*/
|
|
237
|
-
if (require.main === module) {
|
|
217
|
+
function runCli(): void {
|
|
238
218
|
const args = process.argv.slice(2);
|
|
239
219
|
const command = args[0];
|
|
240
220
|
|
|
@@ -258,7 +238,11 @@ if (require.main === module) {
|
|
|
258
238
|
}
|
|
259
239
|
}
|
|
260
240
|
|
|
261
|
-
|
|
241
|
+
if (require.main === module) {
|
|
242
|
+
runCli();
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
export function parseCliOptions(args: string[]): Record<string, string> {
|
|
262
246
|
const opts: Record<string, string> = {};
|
|
263
247
|
for (let i = 0; i < args.length; i++) {
|
|
264
248
|
if (args[i].startsWith('--') && i + 1 < args.length) {
|
|
@@ -269,7 +253,7 @@ function parseCliOptions(args: string[]): Record<string, string> {
|
|
|
269
253
|
return opts;
|
|
270
254
|
}
|
|
271
255
|
|
|
272
|
-
function getCommitHash(): string {
|
|
256
|
+
export function getCommitHash(): string {
|
|
273
257
|
try {
|
|
274
258
|
const { execSync } = require('child_process');
|
|
275
259
|
return execSync('git rev-parse HEAD', { encoding: 'utf8', stdio: ['pipe', 'pipe', 'pipe'] }).trim();
|
package/lib/init.js
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
3
|
const crypto = require('crypto');
|
|
5
|
-
const { checkDeps } = require('./detect-deps.js');
|
|
6
|
-
const {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
const GLOBAL_HOOKS_DIR = path.join(CONFIG_DIR, 'hooks');
|
|
15
|
-
const GLOBAL_ADAPTERS_DIR = path.join(CONFIG_DIR, 'adapters');
|
|
4
|
+
const { checkDeps, checkBash, autoInstallDeps, detectPlatform } = require('./detect-deps.js');
|
|
5
|
+
const {
|
|
6
|
+
HOME_DIR,
|
|
7
|
+
CONFIG_DIR,
|
|
8
|
+
CONFIG_FILE,
|
|
9
|
+
TEMPLATE_DIR,
|
|
10
|
+
GLOBAL_HOOKS_DIR,
|
|
11
|
+
GLOBAL_ADAPTERS_DIR,
|
|
12
|
+
} = require('./shared-paths.js');
|
|
16
13
|
|
|
17
14
|
function copyHooks(srcDir, destDir) {
|
|
18
15
|
['pre-commit', 'pre-push'].forEach(hook => {
|
|
@@ -54,6 +51,35 @@ function logDeps(depCheck) {
|
|
|
54
51
|
}
|
|
55
52
|
}
|
|
56
53
|
|
|
54
|
+
/**
|
|
55
|
+
* Detect which AI agent platform is in use and check/auto-install deps.
|
|
56
|
+
* @param {string} platform - 'opencode' | 'claude-code' | 'qoder'
|
|
57
|
+
*/
|
|
58
|
+
async function checkAndInstallDeps(platform) {
|
|
59
|
+
const depCheck = await checkDeps(platform);
|
|
60
|
+
if (depCheck.ok) {
|
|
61
|
+
logDeps(depCheck);
|
|
62
|
+
return depCheck;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// Auto-install missing deps
|
|
66
|
+
console.log(`Checking dependencies (platform: ${platform})...`);
|
|
67
|
+
const installResult = await autoInstallDeps(platform);
|
|
68
|
+
if (installResult.ok) {
|
|
69
|
+
console.log('Dependencies: OK (auto-installed)\n');
|
|
70
|
+
return { ok: true };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// Auto-install failed — report and continue with warning
|
|
74
|
+
logDeps(depCheck);
|
|
75
|
+
if (installResult.errors) {
|
|
76
|
+
for (const err of installResult.errors) {
|
|
77
|
+
console.warn(` Auto-install failed for ${err.name}: ${err.message}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return depCheck;
|
|
81
|
+
}
|
|
82
|
+
|
|
57
83
|
function printUsage() {
|
|
58
84
|
console.log('Choose installation mode:');
|
|
59
85
|
console.log(' 1) Global — all git projects use the same hooks (recommended)');
|
|
@@ -228,7 +254,10 @@ async function init(args) {
|
|
|
228
254
|
console.warn(` ${bashCheck.message}\n`);
|
|
229
255
|
}
|
|
230
256
|
|
|
231
|
-
|
|
257
|
+
// Detect platform and check/auto-install dependencies
|
|
258
|
+
const platform = detectPlatform();
|
|
259
|
+
console.log(`Platform: ${platform}\n`);
|
|
260
|
+
await checkAndInstallDeps(platform);
|
|
232
261
|
|
|
233
262
|
const installMode = args.includes('--global') ? 'global' :
|
|
234
263
|
args.includes('--core-only') ? 'local' :
|
package/lib/install-skill.js
CHANGED
|
@@ -1,25 +1,14 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const os = require('os');
|
|
4
3
|
const https = require('https');
|
|
5
4
|
const http = require('http');
|
|
6
5
|
const { execSync } = require('child_process');
|
|
7
|
-
const { checkDeps } = require('./detect-deps.js');
|
|
6
|
+
const { checkDeps, detectPlatform } = require('./detect-deps.js');
|
|
8
7
|
const { downloadFromGitHub } = require('./download-skill.js');
|
|
9
8
|
const { rollback } = require('./rollback.js');
|
|
9
|
+
const { HOME_DIR, CONFIG_DIR } = require('./shared-paths.js');
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
const HOME = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
13
|
-
|
|
14
|
-
const CONFIG_DIR = path.join(HOME, '.config', 'xp-gate');
|
|
15
|
-
const SKILLS_DIR = path.join(HOME, '.config', 'opencode', 'skills');
|
|
16
|
-
const QODER_SKILLS_DIR = path.join(HOME, '.qoder', 'skills');
|
|
17
|
-
|
|
18
|
-
// Platform-specific skill directories
|
|
19
|
-
const PLATFORM_SKILLS_DIRS = {
|
|
20
|
-
opencode: SKILLS_DIR,
|
|
21
|
-
qoder: QODER_SKILLS_DIR,
|
|
22
|
-
};
|
|
11
|
+
const SKILLS_DIR = path.join(HOME_DIR, '.config', 'opencode', 'skills');
|
|
23
12
|
|
|
24
13
|
const SKILLS_REGISTRY = {
|
|
25
14
|
'sprint-flow': { repo: 'boyingliu01/xp-gate', path: 'skills/sprint-flow' },
|
|
@@ -29,23 +18,21 @@ const SKILLS_REGISTRY = {
|
|
|
29
18
|
};
|
|
30
19
|
|
|
31
20
|
async function installSkill(name, options = {}) {
|
|
32
|
-
const { offline = false, verbose = false, force = false
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
if (
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return 1;
|
|
48
|
-
}
|
|
21
|
+
const { offline = false, verbose = false, force = false } = options;
|
|
22
|
+
|
|
23
|
+
const platform = detectPlatform();
|
|
24
|
+
const depCheck = await checkDeps(platform);
|
|
25
|
+
if (!depCheck.ok) {
|
|
26
|
+
if (depCheck.missing) {
|
|
27
|
+
console.error(`Error: ${depCheck.missing} is required but not installed`);
|
|
28
|
+
console.error('Please install superpowers and gstack first');
|
|
29
|
+
console.error('See: https://github.com/boyingliu01/superpowers');
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
if (depCheck.versionMismatch) {
|
|
33
|
+
console.error(`Error: ${depCheck.versionMismatch.name} version too old`);
|
|
34
|
+
console.error(`Need: ${depCheck.versionMismatch.required}, Found: ${depCheck.versionMismatch.found}`);
|
|
35
|
+
return 1;
|
|
49
36
|
}
|
|
50
37
|
}
|
|
51
38
|
|
|
@@ -56,8 +43,7 @@ async function installSkill(name, options = {}) {
|
|
|
56
43
|
return 1;
|
|
57
44
|
}
|
|
58
45
|
|
|
59
|
-
const
|
|
60
|
-
const targetDir = path.join(targetSkillsDir, name);
|
|
46
|
+
const targetDir = path.join(SKILLS_DIR, name);
|
|
61
47
|
if (fs.existsSync(targetDir) && !force) {
|
|
62
48
|
console.error(`Error: ${name} is already installed`);
|
|
63
49
|
console.error('Use --force to overwrite');
|
|
@@ -155,21 +141,7 @@ async function downloadFile(url, dest, verbose) {
|
|
|
155
141
|
});
|
|
156
142
|
}
|
|
157
143
|
|
|
158
|
-
|
|
159
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
160
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
161
|
-
|
|
162
|
-
for (const entry of entries) {
|
|
163
|
-
const srcPath = path.join(src, entry.name);
|
|
164
|
-
const destPath = path.join(dest, entry.name);
|
|
165
|
-
|
|
166
|
-
if (entry.isDirectory()) {
|
|
167
|
-
copyDirRecursive(srcPath, destPath);
|
|
168
|
-
} else {
|
|
169
|
-
fs.copyFileSync(srcPath, destPath);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
}
|
|
144
|
+
const { copyDirRecursive } = require('./shared-utils');
|
|
173
145
|
|
|
174
146
|
function ensureConfigDir() {
|
|
175
147
|
fs.mkdirSync(CONFIG_DIR, { recursive: true });
|
package/lib/rollback.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
const fs = require('fs');
|
|
2
2
|
const path = require('path');
|
|
3
3
|
const os = require('os');
|
|
4
|
+
const { copyDirRecursive } = require('./shared-utils');
|
|
4
5
|
|
|
5
6
|
// Cross-platform home directory resolution
|
|
6
7
|
const HOME = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
@@ -51,22 +52,6 @@ async function createBackup(installId, skillName) {
|
|
|
51
52
|
return backupDir;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
function copyDirRecursive(src, dest) {
|
|
55
|
-
fs.mkdirSync(dest, { recursive: true });
|
|
56
|
-
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
57
|
-
|
|
58
|
-
for (const entry of entries) {
|
|
59
|
-
const srcPath = path.join(src, entry.name);
|
|
60
|
-
const destPath = path.join(dest, entry.name);
|
|
61
|
-
|
|
62
|
-
if (entry.isDirectory()) {
|
|
63
|
-
copyDirRecursive(srcPath, destPath);
|
|
64
|
-
} else {
|
|
65
|
-
fs.copyFileSync(srcPath, destPath);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
55
|
function cleanupBackup(installId) {
|
|
71
56
|
const backupDir = path.join(BACKUP_DIR, installId);
|
|
72
57
|
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared filesystem path constants used across CLI modules.
|
|
3
|
+
* Cross-platform home directory resolution with os.homedir() fallback.
|
|
4
|
+
*
|
|
5
|
+
* @intent Eliminate duplicate path constants in init.js / uninstall.js / detect-deps.js
|
|
6
|
+
* @covers Issue #107 — duplicate code between init.js and uninstall.js
|
|
7
|
+
*/
|
|
8
|
+
const path = require('path');
|
|
9
|
+
const os = require('os');
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Resolve the user's home directory cross-platform.
|
|
13
|
+
* Fallback chain: HOME → USERPROFILE → os.homedir()
|
|
14
|
+
*/
|
|
15
|
+
const HOME_DIR = process.env.HOME || process.env.USERPROFILE || os.homedir();
|
|
16
|
+
|
|
17
|
+
const CONFIG_DIR = path.join(HOME_DIR, '.config', 'xp-gate');
|
|
18
|
+
const CONFIG_FILE = path.join(CONFIG_DIR, 'xp-gate.json');
|
|
19
|
+
const TEMPLATE_DIR = path.join(HOME_DIR, '.config', 'opencode', 'git-hooks-template');
|
|
20
|
+
const GLOBAL_HOOKS_DIR = path.join(CONFIG_DIR, 'hooks');
|
|
21
|
+
const GLOBAL_ADAPTERS_DIR = path.join(CONFIG_DIR, 'adapters');
|
|
22
|
+
|
|
23
|
+
module.exports = {
|
|
24
|
+
HOME_DIR,
|
|
25
|
+
CONFIG_DIR,
|
|
26
|
+
CONFIG_FILE,
|
|
27
|
+
TEMPLATE_DIR,
|
|
28
|
+
GLOBAL_HOOKS_DIR,
|
|
29
|
+
GLOBAL_ADAPTERS_DIR,
|
|
30
|
+
};
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure utility functions shared by CLI modules.
|
|
3
|
+
* No module-level state — safe for tests that mock fs/path/os.
|
|
4
|
+
*/
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Recursively copy a directory.
|
|
9
|
+
* Pure function: only uses fs/path params, no global config.
|
|
10
|
+
*/
|
|
11
|
+
function copyDirRecursive(src, dest) {
|
|
12
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
13
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
14
|
+
for (const entry of entries) {
|
|
15
|
+
const srcPath = require('path').join(src, entry.name);
|
|
16
|
+
const destPath = require('path').join(dest, entry.name);
|
|
17
|
+
if (entry.isDirectory()) {
|
|
18
|
+
copyDirRecursive(srcPath, destPath);
|
|
19
|
+
} else {
|
|
20
|
+
fs.copyFileSync(srcPath, destPath);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
module.exports = { copyDirRecursive };
|