@programinglive/dev-workflow-mcp-server 1.13.2 → 1.13.4

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/exec.js CHANGED
@@ -52,8 +52,16 @@ function samePath(a, b) {
52
52
  * Menentukan direktori repo proyek tempat git boleh dijalankan.
53
53
  *
54
54
  * Urutan: eksplisit -> WORKFLOW_PROJECT_DIR/PROJECT_DIR -> kandidat.
55
- * Repo paket MCP selalu dilewati; kalau tidak ada pilihan lain, lebih baik
56
- * gagal dengan pesan jelas daripada commit ke repo yang salah.
55
+ *
56
+ * Repo paket MCP sengaja tidak dipilih sebagai cadangan: dulu server pernah
57
+ * menjalankan git di repo-nya sendiri saat cwd proyek gagal diresolusi, lalu
58
+ * melaporkan "No changes detected" sementara perubahan proyek tidak pernah
59
+ * ter-commit.
60
+ *
61
+ * Tetapi kandidat yang MEMANG berada di dalam repo paket berarti pemanggil
62
+ * sengaja bekerja di sana - misalnya saat mengembangkan server MCP ini sendiri.
63
+ * Membuangnya tanpa syarat membuat `commit_and_push` mustahil dipakai di repo
64
+ * paket, jadi kandidat seperti itu diterima. Repo proyek lain tetap menang.
57
65
  */
58
66
  export function resolveWorkingDirectory({ explicit = "", env = process.env, candidates = [] } = {}) {
59
67
  if (explicit) {
@@ -74,14 +82,22 @@ export function resolveWorkingDirectory({ explicit = "", env = process.env, cand
74
82
  }
75
83
 
76
84
  const found = candidates.map((candidate) => findRepoRoot(candidate)).filter(Boolean);
77
- const usable = found.find((dir) => !samePath(dir, PACKAGE_DIR));
78
- if (usable) {
79
- return usable;
85
+
86
+ const projectRepo = found.find((dir) => !samePath(dir, PACKAGE_DIR));
87
+ if (projectRepo) {
88
+ return projectRepo;
89
+ }
90
+
91
+ // Hanya repo paket yang ditemukan. Itu hanya mungkin bila salah satu kandidat
92
+ // benar-benar berada di dalamnya, jadi pemanggil memilih repo ini dengan sadar.
93
+ if (found.some((dir) => samePath(dir, PACKAGE_DIR))) {
94
+ return path.resolve(PACKAGE_DIR);
80
95
  }
81
96
 
82
97
  throw new Error(
83
- "Tidak bisa menentukan repo proyek: satu-satunya kandidat adalah repo paket server MCP. " +
84
- "Set WORKFLOW_PROJECT_DIR ke direktori proyek, atau jalankan server MCP dari direktori proyek.",
98
+ "Tidak bisa menentukan repo proyek: tidak ada repository git pada " +
99
+ `${candidates.filter(Boolean).map((candidate) => `"${candidate}"`).join(", ") || "direktori kerja"}. ` +
100
+ "Set WORKFLOW_PROJECT_DIR ke direktori proyek, atau jalankan server MCP dari dalam direktori proyek.",
85
101
  );
86
102
  }
87
103
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@programinglive/dev-workflow-mcp-server",
3
- "version": "1.13.2",
3
+ "version": "1.13.4",
4
4
  "description": "MCP server to enforce development workflow discipline",
5
5
  "homepage": "https://devworkflow.programinglive.com/",
6
6
  "mcpName": "io.github.programinglive/dev-workflow-mcp-server",
@@ -40,6 +40,40 @@ function getCliArguments(argv = process.argv) {
40
40
  return { releaseType: 'patch', extraArgs: [] };
41
41
  }
42
42
 
43
+ // `--release-as` selalu diambil sebagai tipe rilis dan DIBUANG dari extraArgs.
44
+ //
45
+ // Kalau dibiarkan, argumen itu sampai ke standard-version dua kali: sekali
46
+ // dari releaseType, sekali dari extraArgs. yargs lalu mengubahnya menjadi
47
+ // array, dan `semver.inc("1.13.2", ["patch","patch"])` mengembalikan null -
48
+ // rilis menulis `"version": null` dan tag `vnull`.
49
+ const releaseAsIndex = rawArgs.indexOf('--release-as');
50
+ if (releaseAsIndex !== -1) {
51
+ const value = rawArgs[releaseAsIndex + 1];
52
+ if (!value || value.startsWith('-')) {
53
+ throw new Error(
54
+ '--release-as memerlukan nilai tipe rilis (major, minor, patch, atau versi semver).'
55
+ );
56
+ }
57
+ return {
58
+ releaseType: value,
59
+ extraArgs: [...rawArgs.slice(0, releaseAsIndex), ...rawArgs.slice(releaseAsIndex + 2)]
60
+ };
61
+ }
62
+
63
+ const inline = rawArgs.find((arg) => arg.startsWith('--release-as='));
64
+ if (inline) {
65
+ const value = inline.slice('--release-as='.length);
66
+ if (!value) {
67
+ throw new Error(
68
+ '--release-as memerlukan nilai tipe rilis (major, minor, patch, atau versi semver).'
69
+ );
70
+ }
71
+ return {
72
+ releaseType: value,
73
+ extraArgs: rawArgs.filter((arg) => arg !== inline)
74
+ };
75
+ }
76
+
43
77
  if (rawArgs[0].startsWith('-')) {
44
78
  return { releaseType: 'patch', extraArgs: rawArgs };
45
79
  }
@@ -62,17 +96,43 @@ function getNpmRunArgument(env = process.env) {
62
96
  return undefined;
63
97
  }
64
98
 
99
+ /**
100
+ * Membuang setiap `--release-as` dari argumen tambahan.
101
+ *
102
+ * Pertahanan lapis kedua: tipe rilis sudah ditentukan lewat `releaseType`, jadi
103
+ * salinan apa pun di `extraArgs` hanya akan menggandakan opsi dan membuat yargs
104
+ * memberinya array - yang berakhir pada `semver.inc(...) === null`.
105
+ */
106
+ function stripReleaseAs(extraArgs) {
107
+ if (!Array.isArray(extraArgs)) {
108
+ return [];
109
+ }
110
+
111
+ const kept = [];
112
+ for (let index = 0; index < extraArgs.length; index += 1) {
113
+ const arg = extraArgs[index];
114
+ if (arg === '--release-as') {
115
+ index += 1; // lewati nilainya juga
116
+ continue;
117
+ }
118
+ if (typeof arg === 'string' && arg.startsWith('--release-as=')) {
119
+ continue;
120
+ }
121
+ kept.push(arg);
122
+ }
123
+ return kept;
124
+ }
125
+
65
126
  function buildStandardVersionArgs({ releaseType, extraArgs }) {
66
127
  const args = [];
67
-
128
+ const remainingArgs = stripReleaseAs(extraArgs);
129
+
68
130
  // Handle --first-release flag
69
- const isFirstRelease = Array.isArray(extraArgs) && extraArgs.includes('--first-release');
131
+ const isFirstRelease = remainingArgs.includes('--first-release');
70
132
  if (isFirstRelease) {
71
133
  args.push('--release-as', '0.0.1');
72
- // Remove --first-release from extraArgs to avoid passing it to standard-version
73
- extraArgs = extraArgs.filter(arg => arg !== '--first-release');
74
134
  } else if (releaseType) {
75
- const normalized = releaseType.trim();
135
+ const normalized = String(releaseType).trim();
76
136
  const isValid = VALID_RELEASE_TYPES.has(normalized) || SEMVER_REGEX.test(normalized);
77
137
  if (!isValid) {
78
138
  const allowed = Array.from(VALID_RELEASE_TYPES).join(', ');
@@ -81,8 +141,8 @@ function buildStandardVersionArgs({ releaseType, extraArgs }) {
81
141
  args.push('--release-as', normalized);
82
142
  }
83
143
 
84
- if (Array.isArray(extraArgs) && extraArgs.length > 0) {
85
- args.push(...extraArgs);
144
+ if (remainingArgs.length > 0) {
145
+ args.push(...remainingArgs.filter((arg) => arg !== '--first-release'));
86
146
  }
87
147
 
88
148
  return args;
@@ -98,6 +158,52 @@ function loadPackageJson(cwd = process.cwd()) {
98
158
  }
99
159
  }
100
160
 
161
+ /**
162
+ * Memastikan package.json berisi versi semver yang sah SETELAH rilis berjalan.
163
+ *
164
+ * MENGAPA ADA
165
+ * -----------
166
+ * 30 Oktober 2026 `perform_release` menulis `"version": null` ke package.json
167
+ * dan membuat tag `vnull`, lalu mem-push keduanya. Penyebabnya argumen
168
+ * `--release-as` terkirim dua kali sehingga `semver.inc()` mengembalikan null.
169
+ *
170
+ * `SEMVER_REGEX` sudah ada saat itu, tetapi hanya dipakai memvalidasi tipe
171
+ * rilis MASUKAN. Hasil rilis tidak pernah diperiksa, jadi kerusakan baru
172
+ * terlihat setelah tag keluar dari mesin.
173
+ *
174
+ * Fungsi ini menutup celah itu: standard-version dianggap belum berhasil
175
+ * sebelum package.json terbukti berisi semver yang sah.
176
+ */
177
+ function verifyReleaseVersion({ cwd = process.cwd(), readFileSync = fs.readFileSync } = {}) {
178
+ const packageJsonPath = path.join(cwd, 'package.json');
179
+
180
+ let contents;
181
+ try {
182
+ contents = readFileSync(packageJsonPath, 'utf8');
183
+ } catch (error) {
184
+ throw new Error(`Tidak bisa membaca ${packageJsonPath}; hasil rilis tidak dapat diverifikasi.`);
185
+ }
186
+
187
+ let parsed;
188
+ try {
189
+ parsed = JSON.parse(contents);
190
+ } catch (error) {
191
+ throw new Error(`${packageJsonPath} bukan JSON yang sah setelah rilis; hasilnya tidak dapat diverifikasi.`);
192
+ }
193
+
194
+ const version = parsed?.version;
195
+
196
+ if (typeof version !== 'string' || !SEMVER_REGEX.test(version.trim())) {
197
+ throw new Error(
198
+ `Rilis gagal: package.json berisi version ${JSON.stringify(version)}, bukan semver yang sah. ` +
199
+ 'Tag tidak boleh di-push. Periksa argumen --release-as, lalu pulihkan dengan ' +
200
+ `"git tag -d v${String(version)}" dan "git reset --hard HEAD~1" bila perlu.`
201
+ );
202
+ }
203
+
204
+ return version.trim();
205
+ }
206
+
101
207
  function detectPackageManager({ env = process.env, cwd = process.cwd() } = {}) {
102
208
  const userAgent = env.npm_config_user_agent || '';
103
209
  if (userAgent.startsWith('pnpm/')) return 'pnpm';
@@ -189,6 +295,18 @@ function runRelease({
189
295
  return { ...releaseResult, status: releaseResult?.status || 1 };
190
296
  }
191
297
 
298
+ // Rilis hanya dianggap berhasil setelah package.json terbukti berisi semver
299
+ // yang sah. Tanpa pemeriksaan ini, `version: null` dan tag `vnull` lolos
300
+ // sampai di-push (kejadian 30 Oktober 2026).
301
+ let releasedVersion;
302
+ try {
303
+ releasedVersion = verifyReleaseVersion({ cwd });
304
+ } catch (error) {
305
+ safeLog('error', error.message);
306
+ return { status: 1, error };
307
+ }
308
+ safeLog('info', `✅ Released ${releasedVersion}`);
309
+
192
310
  const releaseNotesPath = dependencies.releaseNotesPath || path.join('docs', 'release-notes', 'RELEASE_NOTES.md');
193
311
  const updateNotes = dependencies.updateReleaseNotes || ((options = {}) => updateReleaseNotes({ rootDir: cwd, ...options }));
194
312
 
@@ -224,6 +342,7 @@ module.exports = {
224
342
  getNpmRunArgument,
225
343
  buildStandardVersionArgs,
226
344
  loadPackageJson,
345
+ verifyReleaseVersion,
227
346
  detectPackageManager,
228
347
  runProjectTests,
229
348
  runRelease,