@programinglive/dev-workflow-mcp-server 1.1.11 → 1.1.18
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/.github/workflows/npm-publish.yml +47 -0
- package/CHANGELOG.md +46 -0
- package/README.md +55 -1
- package/docs/product/PRD.md +64 -0
- package/docs/web-dashboard.md +163 -0
- package/git-helpers.js +25 -0
- package/index.js +69 -4
- package/package.json +5 -1
- package/release-wrapper.js +26 -1
- package/tests/exec.test.js +7 -4
- package/tests/handlers.test.js +217 -7
- package/tests/release-wrapper.test.js +11 -3
- package/tests/workflowState.test.js +54 -2
- package/tools/handlers.js +9 -2
- package/web/index.html +520 -80
- package/web/server.js +34 -1
- package/workflow-state.js +86 -23
package/tests/handlers.test.js
CHANGED
|
@@ -62,6 +62,7 @@ test("commit_and_push commits changes and pushes", async () => {
|
|
|
62
62
|
hasTestChanges: async () => true,
|
|
63
63
|
getStagedChanges: async () => [{ status: "M", path: "src/index.js" }],
|
|
64
64
|
getCurrentBranch: async () => "main",
|
|
65
|
+
getPrimaryBranch: async () => "main",
|
|
65
66
|
getLastCommitMessage: async () => "",
|
|
66
67
|
workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
|
|
67
68
|
};
|
|
@@ -129,6 +130,7 @@ test("run_full_workflow iterates until commit/release complete with clean tree",
|
|
|
129
130
|
hasTestChanges: async () => true,
|
|
130
131
|
getStagedChanges: async () => [],
|
|
131
132
|
getCurrentBranch: async () => "main",
|
|
133
|
+
getPrimaryBranch: async () => "main",
|
|
132
134
|
getLastCommitMessage: async () => "chore: existing",
|
|
133
135
|
workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
|
|
134
136
|
};
|
|
@@ -163,6 +165,172 @@ test("run_full_workflow iterates until commit/release complete with clean tree",
|
|
|
163
165
|
});
|
|
164
166
|
});
|
|
165
167
|
|
|
168
|
+
test("commit_and_push uses primary branch when no branch specified", async () => {
|
|
169
|
+
await withWorkflowState(async (workflowState) => {
|
|
170
|
+
workflowState.state.readyToCommit = true;
|
|
171
|
+
workflowState.state.readyCheckCompleted = true;
|
|
172
|
+
workflowState.state.testsSkipped = false;
|
|
173
|
+
workflowState.state.testsCreated = true;
|
|
174
|
+
workflowState.state.testsPassed = true;
|
|
175
|
+
workflowState.state.currentPhase = "commit";
|
|
176
|
+
await workflowState.save();
|
|
177
|
+
|
|
178
|
+
const commands = [];
|
|
179
|
+
const execStub = async (command) => {
|
|
180
|
+
commands.push(command);
|
|
181
|
+
return { stdout: "" };
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
const git = {
|
|
185
|
+
hasWorkingChanges: async () => true,
|
|
186
|
+
hasStagedChanges: async () => false,
|
|
187
|
+
hasTestChanges: async () => true,
|
|
188
|
+
getStagedChanges: async () => [{ status: "M", path: "src/index.js" }],
|
|
189
|
+
getCurrentBranch: async () => "feature/test",
|
|
190
|
+
getPrimaryBranch: async () => "main",
|
|
191
|
+
getLastCommitMessage: async () => "",
|
|
192
|
+
workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
|
|
193
|
+
};
|
|
194
|
+
|
|
195
|
+
const response = await handleToolCall({
|
|
196
|
+
request: createRequest("commit_and_push", { commitMessage: "feat: test" }),
|
|
197
|
+
normalizeRequestArgs,
|
|
198
|
+
workflowState,
|
|
199
|
+
exec: execStub,
|
|
200
|
+
git,
|
|
201
|
+
utils,
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
const pushCommand = commands.find((command) => command.startsWith("git push"));
|
|
205
|
+
assert.ok(pushCommand, "git push should be executed");
|
|
206
|
+
assert.ok(
|
|
207
|
+
pushCommand.includes("main"),
|
|
208
|
+
"push command should use detected primary branch (main)"
|
|
209
|
+
);
|
|
210
|
+
assert.ok(
|
|
211
|
+
response.content[0].text.includes("Commit and push completed!"),
|
|
212
|
+
"response should confirm commit and push"
|
|
213
|
+
);
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
test("commit_and_push falls back to master when main not found", async () => {
|
|
218
|
+
await withWorkflowState(async (workflowState) => {
|
|
219
|
+
workflowState.state.readyToCommit = true;
|
|
220
|
+
workflowState.state.readyCheckCompleted = true;
|
|
221
|
+
workflowState.state.testsSkipped = false;
|
|
222
|
+
workflowState.state.testsCreated = true;
|
|
223
|
+
workflowState.state.testsPassed = true;
|
|
224
|
+
workflowState.state.currentPhase = "commit";
|
|
225
|
+
await workflowState.save();
|
|
226
|
+
|
|
227
|
+
const commands = [];
|
|
228
|
+
const execStub = async (command) => {
|
|
229
|
+
commands.push(command);
|
|
230
|
+
return { stdout: "" };
|
|
231
|
+
};
|
|
232
|
+
|
|
233
|
+
const git = {
|
|
234
|
+
hasWorkingChanges: async () => true,
|
|
235
|
+
hasStagedChanges: async () => false,
|
|
236
|
+
hasTestChanges: async () => true,
|
|
237
|
+
getStagedChanges: async () => [{ status: "M", path: "src/index.js" }],
|
|
238
|
+
getCurrentBranch: async () => "feature/test",
|
|
239
|
+
getPrimaryBranch: async () => "master",
|
|
240
|
+
getLastCommitMessage: async () => "",
|
|
241
|
+
workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const response = await handleToolCall({
|
|
245
|
+
request: createRequest("commit_and_push", { commitMessage: "feat: test" }),
|
|
246
|
+
normalizeRequestArgs,
|
|
247
|
+
workflowState,
|
|
248
|
+
exec: execStub,
|
|
249
|
+
git,
|
|
250
|
+
utils,
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
const pushCommand = commands.find((command) => command.startsWith("git push"));
|
|
254
|
+
assert.ok(pushCommand, "git push should be executed");
|
|
255
|
+
assert.ok(
|
|
256
|
+
pushCommand.includes("master"),
|
|
257
|
+
"push command should use detected primary branch (master)"
|
|
258
|
+
);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("run_full_workflow uses provided branch for release push when commit is clean", async () => {
|
|
263
|
+
await withWorkflowState(async (workflowState) => {
|
|
264
|
+
workflowState.state.taskDescription = "Ship feature";
|
|
265
|
+
workflowState.state.taskType = "feature";
|
|
266
|
+
workflowState.state.currentPhase = "commit";
|
|
267
|
+
workflowState.state.bugFixed = true;
|
|
268
|
+
workflowState.state.testsCreated = true;
|
|
269
|
+
workflowState.state.testsPassed = true;
|
|
270
|
+
workflowState.state.documentationCreated = true;
|
|
271
|
+
workflowState.state.readyToCommit = true;
|
|
272
|
+
workflowState.state.readyCheckCompleted = true;
|
|
273
|
+
workflowState.state.commitAndPushCompleted = false;
|
|
274
|
+
workflowState.state.released = false;
|
|
275
|
+
workflowState.state.documentationType = "README";
|
|
276
|
+
workflowState.state.documentationSummary = "Docs";
|
|
277
|
+
workflowState.state.fixSummary = "Feature done";
|
|
278
|
+
await workflowState.save();
|
|
279
|
+
|
|
280
|
+
const commands = [];
|
|
281
|
+
const execStub = async (command) => {
|
|
282
|
+
commands.push(command);
|
|
283
|
+
if (command === "git status --porcelain") {
|
|
284
|
+
return { stdout: "" };
|
|
285
|
+
}
|
|
286
|
+
return { stdout: "" };
|
|
287
|
+
};
|
|
288
|
+
|
|
289
|
+
const git = {
|
|
290
|
+
hasWorkingChanges: async () => false,
|
|
291
|
+
hasStagedChanges: async () => false,
|
|
292
|
+
hasTestChanges: async () => true,
|
|
293
|
+
getStagedChanges: async () => [],
|
|
294
|
+
getCurrentBranch: async () => "feature/local",
|
|
295
|
+
getPrimaryBranch: async () => "main",
|
|
296
|
+
getLastCommitMessage: async () => "chore: existing",
|
|
297
|
+
workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
|
|
298
|
+
};
|
|
299
|
+
|
|
300
|
+
const branchName = "master";
|
|
301
|
+
const response = await handleToolCall({
|
|
302
|
+
request: createRequest("run_full_workflow", {
|
|
303
|
+
summary: "Feature done",
|
|
304
|
+
testCommand: "npm test",
|
|
305
|
+
documentationType: "README",
|
|
306
|
+
documentationSummary: "Docs",
|
|
307
|
+
commitMessage: "feat: ship feature",
|
|
308
|
+
branch: branchName,
|
|
309
|
+
releaseCommand: "npm run release:patch",
|
|
310
|
+
}),
|
|
311
|
+
normalizeRequestArgs,
|
|
312
|
+
workflowState,
|
|
313
|
+
exec: execStub,
|
|
314
|
+
git,
|
|
315
|
+
utils,
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
assert.ok(
|
|
319
|
+
response.content[0].text.includes("✅ Full workflow completed successfully"),
|
|
320
|
+
"run_full_workflow should finish successfully"
|
|
321
|
+
);
|
|
322
|
+
|
|
323
|
+
const tagPushCommand = commands.find((command) => command.startsWith("git push --follow-tags"));
|
|
324
|
+
assert.ok(tagPushCommand, "release should push tags");
|
|
325
|
+
assert.ok(
|
|
326
|
+
/origin\s+'?master'?/.test(tagPushCommand),
|
|
327
|
+
`release push should target the provided branch (${branchName})`
|
|
328
|
+
);
|
|
329
|
+
|
|
330
|
+
assert.equal(workflowState.state.currentPhase, "idle");
|
|
331
|
+
});
|
|
332
|
+
});
|
|
333
|
+
|
|
166
334
|
test("project_summary_data reads persisted file and falls back", async () => {
|
|
167
335
|
await withWorkflowState(async (workflowState) => {
|
|
168
336
|
// Seed some history
|
|
@@ -174,7 +342,16 @@ test("project_summary_data reads persisted file and falls back", async () => {
|
|
|
174
342
|
normalizeRequestArgs,
|
|
175
343
|
workflowState,
|
|
176
344
|
exec: async () => ({ stdout: "" }),
|
|
177
|
-
git: {
|
|
345
|
+
git: {
|
|
346
|
+
hasWorkingChanges: async () => false,
|
|
347
|
+
hasStagedChanges: async () => false,
|
|
348
|
+
hasTestChanges: async () => true,
|
|
349
|
+
getStagedChanges: async () => [],
|
|
350
|
+
getCurrentBranch: async () => "main",
|
|
351
|
+
getPrimaryBranch: async () => "main",
|
|
352
|
+
getLastCommitMessage: async () => "",
|
|
353
|
+
workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
|
|
354
|
+
},
|
|
178
355
|
utils,
|
|
179
356
|
});
|
|
180
357
|
|
|
@@ -199,7 +376,16 @@ test("project_summary aggregates task types and recent activity", async () => {
|
|
|
199
376
|
normalizeRequestArgs,
|
|
200
377
|
workflowState,
|
|
201
378
|
exec: async () => ({ stdout: "" }),
|
|
202
|
-
git: {
|
|
379
|
+
git: {
|
|
380
|
+
hasWorkingChanges: async () => false,
|
|
381
|
+
hasStagedChanges: async () => false,
|
|
382
|
+
hasTestChanges: async () => true,
|
|
383
|
+
getStagedChanges: async () => [],
|
|
384
|
+
getCurrentBranch: async () => "main",
|
|
385
|
+
getPrimaryBranch: async () => "main",
|
|
386
|
+
getLastCommitMessage: async () => "",
|
|
387
|
+
workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
|
|
388
|
+
},
|
|
203
389
|
utils,
|
|
204
390
|
});
|
|
205
391
|
|
|
@@ -239,6 +425,7 @@ test("run_full_workflow executes all steps successfully", async () => {
|
|
|
239
425
|
hasTestChanges: async () => true,
|
|
240
426
|
getStagedChanges: async () => [{ status: "M", path: "src/app.js" }],
|
|
241
427
|
getCurrentBranch: async () => "main",
|
|
428
|
+
getPrimaryBranch: async () => "main",
|
|
242
429
|
getLastCommitMessage: async () => "",
|
|
243
430
|
workingTreeSummary: () =>
|
|
244
431
|
workingChanges
|
|
@@ -315,6 +502,7 @@ test("run_full_workflow resumes from current phase when steps are already comple
|
|
|
315
502
|
hasTestChanges: async () => true,
|
|
316
503
|
getStagedChanges: async () => [],
|
|
317
504
|
getCurrentBranch: async () => "main",
|
|
505
|
+
getPrimaryBranch: async () => "main",
|
|
318
506
|
getLastCommitMessage: async () => "",
|
|
319
507
|
workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
|
|
320
508
|
};
|
|
@@ -373,7 +561,16 @@ test("run_full_workflow validates required arguments", async () => {
|
|
|
373
561
|
normalizeRequestArgs,
|
|
374
562
|
workflowState,
|
|
375
563
|
exec: async () => ({ stdout: "" }),
|
|
376
|
-
git: {
|
|
564
|
+
git: {
|
|
565
|
+
hasWorkingChanges: async () => false,
|
|
566
|
+
hasStagedChanges: async () => false,
|
|
567
|
+
hasTestChanges: async () => true,
|
|
568
|
+
getStagedChanges: async () => [],
|
|
569
|
+
getCurrentBranch: async () => "main",
|
|
570
|
+
getPrimaryBranch: async () => "main",
|
|
571
|
+
getLastCommitMessage: async () => "",
|
|
572
|
+
workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
|
|
573
|
+
},
|
|
377
574
|
utils,
|
|
378
575
|
});
|
|
379
576
|
|
|
@@ -400,7 +597,16 @@ test("force_complete_task records entry and resets state", async () => {
|
|
|
400
597
|
normalizeRequestArgs,
|
|
401
598
|
workflowState,
|
|
402
599
|
exec: async () => ({ stdout: "" }),
|
|
403
|
-
git: {
|
|
600
|
+
git: {
|
|
601
|
+
hasWorkingChanges: async () => false,
|
|
602
|
+
hasStagedChanges: async () => false,
|
|
603
|
+
hasTestChanges: async () => true,
|
|
604
|
+
getStagedChanges: async () => [],
|
|
605
|
+
getCurrentBranch: async () => "main",
|
|
606
|
+
getPrimaryBranch: async () => "main",
|
|
607
|
+
getLastCommitMessage: async () => "",
|
|
608
|
+
workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
|
|
609
|
+
},
|
|
404
610
|
utils,
|
|
405
611
|
});
|
|
406
612
|
|
|
@@ -436,7 +642,8 @@ test("perform_release blocks when new changes detected", async () => {
|
|
|
436
642
|
hasTestChanges: async () => true,
|
|
437
643
|
getStagedChanges: async () => [],
|
|
438
644
|
getCurrentBranch: async () => "main",
|
|
439
|
-
|
|
645
|
+
getPrimaryBranch: async () => "main",
|
|
646
|
+
getLastCommitMessage: async () => "",
|
|
440
647
|
workingTreeSummary: () => ({ hasChanges: true, lines: ["?? new-file.js"] }),
|
|
441
648
|
};
|
|
442
649
|
|
|
@@ -466,6 +673,7 @@ test("continue_workflow warns when workflow is idle", async () => {
|
|
|
466
673
|
hasTestChanges: async () => false,
|
|
467
674
|
getStagedChanges: async () => [],
|
|
468
675
|
getCurrentBranch: async () => "main",
|
|
676
|
+
getPrimaryBranch: async () => "main",
|
|
469
677
|
getLastCommitMessage: async () => "",
|
|
470
678
|
workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
|
|
471
679
|
};
|
|
@@ -499,8 +707,9 @@ test("continue_workflow resets to commit when new changes detected", async () =>
|
|
|
499
707
|
hasTestChanges: async () => true,
|
|
500
708
|
getStagedChanges: async () => [],
|
|
501
709
|
getCurrentBranch: async () => "main",
|
|
502
|
-
|
|
503
|
-
|
|
710
|
+
getPrimaryBranch: async () => "main",
|
|
711
|
+
getLastCommitMessage: async () => "",
|
|
712
|
+
workingTreeSummary: () => ({ hasChanges: true, lines: ["?? new-file.js"] }),
|
|
504
713
|
};
|
|
505
714
|
|
|
506
715
|
const response = await handleToolCall({
|
|
@@ -543,6 +752,7 @@ test("commit_and_push recognizes already committed work", async () => {
|
|
|
543
752
|
hasTestChanges: async () => true,
|
|
544
753
|
getStagedChanges: async () => [],
|
|
545
754
|
getCurrentBranch: async () => "main",
|
|
755
|
+
getPrimaryBranch: async () => "main",
|
|
546
756
|
getLastCommitMessage: async () => "fix: previous work",
|
|
547
757
|
workingTreeSummary: () => ({ hasChanges: false, lines: [] }),
|
|
548
758
|
};
|
|
@@ -3,10 +3,14 @@ import assert from "node:assert/strict";
|
|
|
3
3
|
import { mkdtemp, rm, writeFile, readFile, mkdir } from "node:fs/promises";
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
6
7
|
import { spawn } from "node:child_process";
|
|
7
8
|
|
|
8
|
-
const
|
|
9
|
-
const
|
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
10
|
+
const __dirname = path.dirname(__filename);
|
|
11
|
+
const releaseWrapperUrl = new URL("../release-wrapper.js", import.meta.url);
|
|
12
|
+
const RELEASE_WRAPPER = fileURLToPath(releaseWrapperUrl);
|
|
13
|
+
const PROJECT_ROOT = path.dirname(RELEASE_WRAPPER);
|
|
10
14
|
|
|
11
15
|
async function runWrapper({ state, args = ["patch"], env = {} }) {
|
|
12
16
|
const dir = await mkdtemp(path.join(tmpdir(), "release-wrapper-test-"));
|
|
@@ -22,14 +26,18 @@ async function runWrapper({ state, args = ["patch"], env = {} }) {
|
|
|
22
26
|
...env,
|
|
23
27
|
};
|
|
24
28
|
|
|
29
|
+
const nodeBinary = process.platform === "win32" ? "node" : process.execPath || "node";
|
|
30
|
+
const spawnShell = process.platform === "win32";
|
|
31
|
+
|
|
25
32
|
return await new Promise((resolve) => {
|
|
26
33
|
const child = spawn(
|
|
27
|
-
|
|
34
|
+
nodeBinary,
|
|
28
35
|
[RELEASE_WRAPPER, ...args],
|
|
29
36
|
{
|
|
30
37
|
cwd: PROJECT_ROOT,
|
|
31
38
|
env: childEnv,
|
|
32
39
|
stdio: ["ignore", "pipe", "pipe"],
|
|
40
|
+
shell: spawnShell,
|
|
33
41
|
}
|
|
34
42
|
);
|
|
35
43
|
|
|
@@ -36,6 +36,58 @@ async function withWorkflowState(callback) {
|
|
|
36
36
|
}
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
test("resolveStateFile prefers current working directory when multiple project markers exist", async () => {
|
|
40
|
+
const tempRoot = await mkdtemp(path.join(tmpdir(), "workflow-prefers-cwd-"));
|
|
41
|
+
const moduleDir = path.join(tempRoot, "module");
|
|
42
|
+
const projectA = path.join(tempRoot, "project-a");
|
|
43
|
+
const projectB = path.join(tempRoot, "project-b");
|
|
44
|
+
const projectBNested = path.join(projectB, "sub", "app");
|
|
45
|
+
|
|
46
|
+
await mkdir(moduleDir, { recursive: true });
|
|
47
|
+
await mkdir(projectA, { recursive: true });
|
|
48
|
+
await mkdir(projectBNested, { recursive: true });
|
|
49
|
+
|
|
50
|
+
const sourceModulePath = fileURLToPath(new URL("../workflow-state.js", import.meta.url));
|
|
51
|
+
await copyFile(sourceModulePath, path.join(moduleDir, "workflow-state.js"));
|
|
52
|
+
|
|
53
|
+
await writeFile(path.join(moduleDir, "package.json"), JSON.stringify({ name: "module" }));
|
|
54
|
+
await writeFile(path.join(projectA, "package.json"), JSON.stringify({ name: "project-a" }));
|
|
55
|
+
await writeFile(path.join(projectB, "package.json"), JSON.stringify({ name: "project-b" }));
|
|
56
|
+
|
|
57
|
+
const moduleUrl = pathToFileURL(path.join(moduleDir, "workflow-state.js"));
|
|
58
|
+
const { WorkflowState: TempWorkflowState } = await import(`${moduleUrl.href}?preferCwd=${Date.now()}`);
|
|
59
|
+
|
|
60
|
+
const originalCwd = process.cwd();
|
|
61
|
+
const initWasSet = Object.prototype.hasOwnProperty.call(process.env, "INIT_CWD");
|
|
62
|
+
const originalInitCwd = process.env.INIT_CWD;
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
process.chdir(projectBNested);
|
|
66
|
+
process.env.INIT_CWD = projectA;
|
|
67
|
+
|
|
68
|
+
const state = new TempWorkflowState();
|
|
69
|
+
const resolvedStateFile = state.stateFile;
|
|
70
|
+
|
|
71
|
+
assert.ok(
|
|
72
|
+
resolvedStateFile.startsWith(path.join(projectB, ".state", "users")),
|
|
73
|
+
"State file should resolve under the current working directory's project"
|
|
74
|
+
);
|
|
75
|
+
|
|
76
|
+
assert.ok(
|
|
77
|
+
!resolvedStateFile.startsWith(path.join(projectA, ".state")),
|
|
78
|
+
"State file should not use INIT_CWD project when cwd has its own markers"
|
|
79
|
+
);
|
|
80
|
+
} finally {
|
|
81
|
+
process.chdir(originalCwd);
|
|
82
|
+
if (initWasSet) {
|
|
83
|
+
process.env.INIT_CWD = originalInitCwd;
|
|
84
|
+
} else {
|
|
85
|
+
delete process.env.INIT_CWD;
|
|
86
|
+
}
|
|
87
|
+
await rm(tempRoot, { recursive: true, force: true });
|
|
88
|
+
}
|
|
89
|
+
});
|
|
90
|
+
|
|
39
91
|
test("WorkflowState mirrors state file to compatibility locations", async () => {
|
|
40
92
|
const tempRoot = await mkdtemp(path.join(tmpdir(), "workflow-compat-"));
|
|
41
93
|
const moduleDir = path.join(tempRoot, "module");
|
|
@@ -94,8 +146,8 @@ test("WorkflowState avoids filesystem root when no project markers found", async
|
|
|
94
146
|
const resolvedTempRoot = await realpath(tempRoot);
|
|
95
147
|
|
|
96
148
|
assert.ok(
|
|
97
|
-
state.stateFile.startsWith(
|
|
98
|
-
"state file should
|
|
149
|
+
state.stateFile.startsWith(resolvedTempRoot),
|
|
150
|
+
"state file should remain inside the temporary root when no project markers exist"
|
|
99
151
|
);
|
|
100
152
|
assert.ok(
|
|
101
153
|
!state.stateFile.startsWith(path.join(path.sep, ".state")),
|
package/tools/handlers.js
CHANGED
|
@@ -426,8 +426,9 @@ async function handleCommitAndPush(args, context) {
|
|
|
426
426
|
typeof args.commitMessage === "string" ? args.commitMessage.trim() : "";
|
|
427
427
|
|
|
428
428
|
const requestedBranch = typeof args.branch === "string" ? args.branch.trim() : "";
|
|
429
|
+
const primaryBranch = await git.getPrimaryBranch();
|
|
429
430
|
const currentBranch = await git.getCurrentBranch();
|
|
430
|
-
const branchForPush = requestedBranch || currentBranch || "";
|
|
431
|
+
const branchForPush = requestedBranch || primaryBranch || currentBranch || "";
|
|
431
432
|
|
|
432
433
|
const hasWorkingChanges = await git.hasWorkingChanges();
|
|
433
434
|
|
|
@@ -817,6 +818,12 @@ async function handleRunFullWorkflow(args, { workflowState, exec, git, utils })
|
|
|
817
818
|
|
|
818
819
|
await workflowState.ensurePrimaryFile();
|
|
819
820
|
|
|
821
|
+
const requestedBranch = typeof args.branch === "string" ? args.branch.trim() : "";
|
|
822
|
+
if (requestedBranch) {
|
|
823
|
+
workflowState.state.lastPushBranch = requestedBranch;
|
|
824
|
+
await workflowState.save();
|
|
825
|
+
}
|
|
826
|
+
|
|
820
827
|
const requiredStrings = [
|
|
821
828
|
{ key: "summary", message: "Provide a non-empty 'summary' for mark_bug_fixed." },
|
|
822
829
|
{ key: "testCommand", message: "Provide a non-empty 'testCommand' for run_tests." },
|
|
@@ -906,7 +913,7 @@ async function handleRunFullWorkflow(args, { workflowState, exec, git, utils })
|
|
|
906
913
|
const resp = await executeStep("commit_and_push", handleCommitAndPush, [
|
|
907
914
|
{
|
|
908
915
|
commitMessage: args.commitMessage,
|
|
909
|
-
branch:
|
|
916
|
+
branch: requestedBranch,
|
|
910
917
|
},
|
|
911
918
|
{ workflowState, exec, git, utils },
|
|
912
919
|
]);
|