@programinglive/dev-workflow-mcp-server 1.13.10 → 1.13.12
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/git-facade.js +4 -0
- package/git-helpers.js +23 -0
- package/package.json +1 -1
- package/scripts/verify-push-branch.mjs +111 -0
- package/tools/handler-modules/commit-handlers.js +86 -4
- package/workflow-state.js +71 -0
package/git-facade.js
CHANGED
|
@@ -10,8 +10,10 @@
|
|
|
10
10
|
|
|
11
11
|
import {
|
|
12
12
|
getCurrentBranch,
|
|
13
|
+
getHeadSha,
|
|
13
14
|
getLastCommitMessage,
|
|
14
15
|
getPrimaryBranch,
|
|
16
|
+
getRemoteBranchSha,
|
|
15
17
|
getStagedChanges,
|
|
16
18
|
getStatusOutput,
|
|
17
19
|
hasStagedChanges,
|
|
@@ -27,7 +29,9 @@ export function createGitFacade() {
|
|
|
27
29
|
hasStagedChanges,
|
|
28
30
|
getStagedChanges,
|
|
29
31
|
getCurrentBranch,
|
|
32
|
+
getHeadSha,
|
|
30
33
|
getPrimaryBranch,
|
|
34
|
+
getRemoteBranchSha,
|
|
31
35
|
getLastCommitMessage,
|
|
32
36
|
getStatusOutput,
|
|
33
37
|
workingTreeSummary,
|
package/git-helpers.js
CHANGED
|
@@ -145,6 +145,29 @@ export async function getCurrentBranch() {
|
|
|
145
145
|
return stdout.trim();
|
|
146
146
|
}
|
|
147
147
|
|
|
148
|
+
/**
|
|
149
|
+
* SHA commit yang sedang di HEAD.
|
|
150
|
+
*
|
|
151
|
+
* Dipakai untuk membuktikan bahwa push benar-benar mengirim commit ini, bukan
|
|
152
|
+
* hanya "git push" yang keluar 0 tanpa memindahkan apa pun.
|
|
153
|
+
*/
|
|
154
|
+
export async function getHeadSha() {
|
|
155
|
+
const { stdout } = await exec("git rev-parse HEAD");
|
|
156
|
+
return stdout.trim();
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* SHA yang tercatat pada remote-tracking ref `origin/<branch>`.
|
|
161
|
+
* Melempar bila branch itu belum ada di remote.
|
|
162
|
+
*/
|
|
163
|
+
export async function getRemoteBranchSha(branch) {
|
|
164
|
+
const { stdout } = await exec("git rev-parse", {
|
|
165
|
+
executable: "git",
|
|
166
|
+
arguments: ["rev-parse", `origin/${branch}`],
|
|
167
|
+
});
|
|
168
|
+
return stdout.trim();
|
|
169
|
+
}
|
|
170
|
+
|
|
148
171
|
export async function getStatusOutput() {
|
|
149
172
|
const { stdout } = await exec("git status --porcelain");
|
|
150
173
|
return stdout;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@programinglive/dev-workflow-mcp-server",
|
|
3
|
-
"version": "1.13.
|
|
3
|
+
"version": "1.13.12",
|
|
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",
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
// Verifikasi end-to-end commit_and_push: harus mem-push branch AKTIF.
|
|
2
|
+
// Memanggil handler asli (bukan mock) terhadap repo sementara + remote bare.
|
|
3
|
+
import { execFileSync } from "node:child_process";
|
|
4
|
+
import { mkdtempSync, writeFileSync, rmSync } from "node:fs";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
|
|
8
|
+
import { handleToolCall } from "../tools/handlers.js";
|
|
9
|
+
import { WorkflowState } from "../workflow-state.js";
|
|
10
|
+
import { shellEscape, normalizeRequestArgs } from "../utils.js";
|
|
11
|
+
import { createCommitMessageParts, determineReleaseTypeFromCommit } from "../commit-helpers.js";
|
|
12
|
+
import { createGitFacade } from "../git-facade.js";
|
|
13
|
+
import { exec } from "../exec.js";
|
|
14
|
+
|
|
15
|
+
const git = (cwd, ...args) =>
|
|
16
|
+
execFileSync("git", args, { cwd, encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
|
|
17
|
+
|
|
18
|
+
const root = mkdtempSync(path.join(tmpdir(), "push-verify-"));
|
|
19
|
+
const remote = path.join(root, "remote.git");
|
|
20
|
+
const work = path.join(root, "work");
|
|
21
|
+
|
|
22
|
+
try {
|
|
23
|
+
git(root, "init", "--bare", "-q", remote);
|
|
24
|
+
git(root, "clone", "-q", remote, work);
|
|
25
|
+
git(work, "config", "user.email", "test@example.test");
|
|
26
|
+
git(work, "config", "user.name", "Test");
|
|
27
|
+
|
|
28
|
+
writeFileSync(path.join(work, "base.txt"), "awal\n");
|
|
29
|
+
git(work, "add", ".");
|
|
30
|
+
git(work, "commit", "-q", "-m", "chore: initial");
|
|
31
|
+
git(work, "branch", "-M", "main");
|
|
32
|
+
git(work, "push", "-q", "origin", "main");
|
|
33
|
+
|
|
34
|
+
// Branch fitur dengan commit yang BELUM di-push.
|
|
35
|
+
git(work, "checkout", "-q", "-b", "feat/verifikasi");
|
|
36
|
+
writeFileSync(path.join(work, "fitur.txt"), "fitur\n");
|
|
37
|
+
git(work, "add", ".");
|
|
38
|
+
git(work, "commit", "-q", "-m", "feat: pekerjaan di branch fitur");
|
|
39
|
+
|
|
40
|
+
const headBefore = git(work, "rev-parse", "HEAD");
|
|
41
|
+
const mainBefore = git(work, "rev-parse", "main");
|
|
42
|
+
|
|
43
|
+
// Perubahan yang BELUM di-commit, supaya tool menempuh jalur commit + push
|
|
44
|
+
// (bukan jalur "no changes detected").
|
|
45
|
+
writeFileSync(path.join(work, "belum-commit.txt"), "perubahan\n");
|
|
46
|
+
|
|
47
|
+
console.log("branch aktif :", git(work, "branch", "--show-current"));
|
|
48
|
+
console.log("HEAD awal :", headBefore.slice(0, 8));
|
|
49
|
+
let remoteFeatBefore = "(belum ada)";
|
|
50
|
+
try {
|
|
51
|
+
remoteFeatBefore = git(work, "rev-parse", "--verify", "-q", "origin/feat/verifikasi").slice(0, 8);
|
|
52
|
+
} catch { /* belum ada */ }
|
|
53
|
+
console.log("origin/feat :", remoteFeatBefore);
|
|
54
|
+
|
|
55
|
+
const stateFile = path.join(root, "state.json");
|
|
56
|
+
const workflowState = new WorkflowState(stateFile);
|
|
57
|
+
await workflowState.load();
|
|
58
|
+
Object.assign(workflowState.state, {
|
|
59
|
+
currentPhase: "commit",
|
|
60
|
+
taskDescription: "verifikasi push branch",
|
|
61
|
+
taskType: "other",
|
|
62
|
+
featureFlowCreated: true,
|
|
63
|
+
bugFixed: true,
|
|
64
|
+
testsCreated: true,
|
|
65
|
+
testsPassed: true,
|
|
66
|
+
testsSkipped: true,
|
|
67
|
+
documentationCreated: true,
|
|
68
|
+
readyToCommit: true,
|
|
69
|
+
readyCheckCompleted: true,
|
|
70
|
+
commitAndPushCompleted: false,
|
|
71
|
+
projectDir: work,
|
|
72
|
+
history: [],
|
|
73
|
+
});
|
|
74
|
+
await workflowState.save();
|
|
75
|
+
|
|
76
|
+
const response = await handleToolCall({
|
|
77
|
+
request: {
|
|
78
|
+
params: {
|
|
79
|
+
name: "commit_and_push",
|
|
80
|
+
arguments: { commitMessage: "feat: uji push", projectDir: work },
|
|
81
|
+
},
|
|
82
|
+
},
|
|
83
|
+
normalizeRequestArgs,
|
|
84
|
+
workflowState,
|
|
85
|
+
exec,
|
|
86
|
+
git: createGitFacade(),
|
|
87
|
+
utils: { shellEscape, determineReleaseTypeFromCommit, createCommitMessageParts },
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
console.log("\n=== output tool ===");
|
|
91
|
+
console.log(response.content[0].text);
|
|
92
|
+
|
|
93
|
+
console.log("\n=== hasil di remote ===");
|
|
94
|
+
let remoteFeat = "";
|
|
95
|
+
try {
|
|
96
|
+
remoteFeat = git(work, "rev-parse", "--verify", "-q", "origin/feat/verifikasi");
|
|
97
|
+
} catch { /* tidak ada */ }
|
|
98
|
+
const remoteMain = git(work, "rev-parse", "main");
|
|
99
|
+
console.log("origin/feat/verifikasi :", remoteFeat ? remoteFeat.slice(0, 8) : "(tidak ada)");
|
|
100
|
+
console.log("origin/main :", remoteMain.slice(0, 8));
|
|
101
|
+
|
|
102
|
+
const headAfter = git(work, "rev-parse", "HEAD");
|
|
103
|
+
const okFeat = remoteFeat === headAfter;
|
|
104
|
+
const okMain = remoteMain === mainBefore;
|
|
105
|
+
console.log("");
|
|
106
|
+
console.log(okFeat ? "BENAR: remote menerima commit di branch fitur" : "SALAH: remote/feat tidak menerima HEAD");
|
|
107
|
+
console.log(okMain ? "BENAR: main tidak tersentuh" : "SALAH: main ikut berubah");
|
|
108
|
+
process.exitCode = okFeat && okMain ? 0 : 1;
|
|
109
|
+
} finally {
|
|
110
|
+
rmSync(root, { recursive: true, force: true });
|
|
111
|
+
}
|
|
@@ -43,6 +43,62 @@ function applyProjectDirOverride(args) {
|
|
|
43
43
|
}
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
+
/**
|
|
47
|
+
* Branch yang benar-benar dipakai untuk push.
|
|
48
|
+
*
|
|
49
|
+
* Urutan: `branch` eksplisit -> branch aktif -> primary branch.
|
|
50
|
+
*
|
|
51
|
+
* Sebelum 31 Okt 2026 urutannya primaryBranch lebih dulu, dan `getPrimaryBranch()`
|
|
52
|
+
* selalu menjawab "main" bila `origin/main` ada - tanpa melihat branch aktif.
|
|
53
|
+
* Akibatnya di worktree yang sedang di `feat/...`, tool menjalankan
|
|
54
|
+
* `git push origin main`. Karena `main` lokal sudah sama dengan `origin/main`,
|
|
55
|
+
* perintah itu keluar 0 tanpa mengirim apa pun, lalu tool melaporkan
|
|
56
|
+
* "Commit and push completed". Dua commit nyata tidak pernah ter-push.
|
|
57
|
+
*
|
|
58
|
+
* HEAD yang detached ("HEAD") bukan branch, jadi tidak bisa dipakai.
|
|
59
|
+
*/
|
|
60
|
+
function pickPushBranch({ requestedBranch, currentBranch, primaryBranch }) {
|
|
61
|
+
if (requestedBranch) return requestedBranch;
|
|
62
|
+
if (currentBranch && currentBranch !== "HEAD") return currentBranch;
|
|
63
|
+
return primaryBranch || currentBranch || "";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Pastikan remote benar-benar menerima HEAD.
|
|
68
|
+
*
|
|
69
|
+
* `git push` bisa keluar 0 tanpa memindahkan apa pun - itulah yang menyamarkan
|
|
70
|
+
* bug di atas. Satu-satunya bukti yang bisa dipercaya adalah membandingkan SHA.
|
|
71
|
+
*/
|
|
72
|
+
async function verifyPushed(git, branch) {
|
|
73
|
+
let head;
|
|
74
|
+
try {
|
|
75
|
+
head = await git.getHeadSha();
|
|
76
|
+
} catch (error) {
|
|
77
|
+
return { ok: false, detail: `tidak bisa membaca HEAD: ${error.message}` };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
let remote;
|
|
81
|
+
try {
|
|
82
|
+
remote = await git.getRemoteBranchSha(branch);
|
|
83
|
+
} catch {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
detail: `branch origin/${branch} tidak ditemukan di remote setelah push`,
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (remote !== head) {
|
|
91
|
+
return {
|
|
92
|
+
ok: false,
|
|
93
|
+
detail:
|
|
94
|
+
`origin/${branch} berada di ${remote.slice(0, 8)}, ` +
|
|
95
|
+
`sedangkan HEAD di ${head.slice(0, 8)}`,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return { ok: true, head };
|
|
100
|
+
}
|
|
101
|
+
|
|
46
102
|
export async function handleCommitAndPush(args, context) {
|
|
47
103
|
const { workflowState, exec, git, utils } = context;
|
|
48
104
|
|
|
@@ -84,12 +140,25 @@ export async function handleCommitAndPush(args, context) {
|
|
|
84
140
|
);
|
|
85
141
|
}
|
|
86
142
|
|
|
87
|
-
const branchForPush = requestedBranch
|
|
143
|
+
const branchForPush = pickPushBranch({ requestedBranch, currentBranch, primaryBranch });
|
|
88
144
|
|
|
89
145
|
if (!hasWorkingChanges) {
|
|
90
146
|
const lastCommitMessage = await git.getLastCommitMessage();
|
|
91
147
|
const effectiveCommitMessage = lastCommitMessage || providedCommitMessage;
|
|
92
148
|
|
|
149
|
+
// "Tidak ada perubahan" bukan berarti "sudah ter-push". HEAD bisa saja
|
|
150
|
+
// berupa commit yang belum pernah dikirim. Memeriksanya mencegah tool
|
|
151
|
+
// menandai selesai padahal remote masih tertinggal.
|
|
152
|
+
const pushed = await verifyPushed(git, branchForPush);
|
|
153
|
+
if (!pushed.ok) {
|
|
154
|
+
return textResponse(
|
|
155
|
+
`⚠️ Tidak ada perubahan untuk di-commit, tetapi HEAD BELUM ada di remote.\n` +
|
|
156
|
+
`Repo: ${repoLabel()}\nBranch: ${branchForPush || "(tidak diketahui)"}\n` +
|
|
157
|
+
`Sebab: ${pushed.detail}\n\n` +
|
|
158
|
+
`Jalankan 'commit_and_push' lagi dengan 'branch' yang benar, atau push manual.`
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
93
162
|
workflowState.state.commitAndPushCompleted = true;
|
|
94
163
|
workflowState.state.lastCommitMessage = effectiveCommitMessage || "";
|
|
95
164
|
workflowState.state.lastPushBranch = branchForPush;
|
|
@@ -97,7 +166,7 @@ export async function handleCommitAndPush(args, context) {
|
|
|
97
166
|
await workflowState.save();
|
|
98
167
|
|
|
99
168
|
return textResponse(
|
|
100
|
-
`ℹ️ No changes detected. Assuming commit and push already completed.\nRepo: ${repoLabel()}\nLast commit: ${effectiveCommitMessage || "(unavailable)"
|
|
169
|
+
`ℹ️ No changes detected. Assuming commit and push already completed.\nRepo: ${repoLabel()}\nBranch: ${branchForPush} (verified on remote)\nLast commit: ${effectiveCommitMessage || "(unavailable)"
|
|
101
170
|
}\n\nNext: Run 'perform_release' to handle the release and push tags.`
|
|
102
171
|
);
|
|
103
172
|
}
|
|
@@ -152,6 +221,20 @@ export async function handleCommitAndPush(args, context) {
|
|
|
152
221
|
);
|
|
153
222
|
}
|
|
154
223
|
|
|
224
|
+
// Exit code 0 dari `git push` BUKAN bukti bahwa commit ini terkirim. Kalau
|
|
225
|
+
// branch yang dipilih sudah sinkron, git tidak melakukan apa pun dan tetap
|
|
226
|
+
// keluar 0. Satu-satunya bukti yang bisa dipercaya: SHA HEAD ada di remote.
|
|
227
|
+
const pushed = await verifyPushed(git, branchForPush);
|
|
228
|
+
if (!pushed.ok) {
|
|
229
|
+
return textResponse(
|
|
230
|
+
`❌ Commit dibuat, tetapi TIDAK ter-push.\n` +
|
|
231
|
+
`Repo: ${repoLabel()}\nBranch: ${branchForPush || "(tidak diketahui)"}\n` +
|
|
232
|
+
`Sebab: ${pushed.detail}\n\n` +
|
|
233
|
+
`Commit lokal: ${generatedSummary}\n\n` +
|
|
234
|
+
`Jalankan 'commit_and_push' lagi dengan 'branch' yang benar, atau push manual.`
|
|
235
|
+
);
|
|
236
|
+
}
|
|
237
|
+
|
|
155
238
|
workflowState.state.commitAndPushCompleted = true;
|
|
156
239
|
workflowState.state.lastCommitMessage = generatedSummary;
|
|
157
240
|
workflowState.state.lastPushBranch = branchForPush;
|
|
@@ -159,8 +242,7 @@ export async function handleCommitAndPush(args, context) {
|
|
|
159
242
|
await workflowState.save();
|
|
160
243
|
|
|
161
244
|
return textResponse(
|
|
162
|
-
`✅ Commit and push completed!\nRepo: ${repoLabel()}\n\nCommit message: ${generatedSummary}\nPushed to: ${branchForPush
|
|
163
|
-
}\n\nNext: Run 'perform_release' to handle the release and push tags.`
|
|
245
|
+
`✅ Commit and push completed!\nRepo: ${repoLabel()}\n\nCommit message: ${generatedSummary}\nPushed to: ${branchForPush} (verified at ${pushed.head.slice(0, 8)})\n\nNext: Run 'perform_release' to handle the release and push tags.`
|
|
164
246
|
);
|
|
165
247
|
}
|
|
166
248
|
|
package/workflow-state.js
CHANGED
|
@@ -33,6 +33,22 @@ let cachedUserStateDir = null;
|
|
|
33
33
|
*/
|
|
34
34
|
const PROCESS_TOKEN = `proc-${process.pid}-${Math.random().toString(16).slice(2, 10)}`;
|
|
35
35
|
|
|
36
|
+
/**
|
|
37
|
+
* Langkah workflow yang hanya boleh MAJU dalam satu task.
|
|
38
|
+
*
|
|
39
|
+
* Hanya flag PENCAPAIAN. Gerbang kualitas (`readyToCommit`,
|
|
40
|
+
* `readyCheckCompleted`, `commitAndPushCompleted`, `released`) SENGAJA tidak
|
|
41
|
+
* masuk: keempatnya direset ketika langkah sebelumnya diulang, dan memaksa
|
|
42
|
+
* nilainya tetap true akan melemahkan gerbang yang justru ingin ditegakkan.
|
|
43
|
+
*/
|
|
44
|
+
const MONOTONIC_STEP_FIELDS = [
|
|
45
|
+
"featureFlowCreated",
|
|
46
|
+
"bugFixed",
|
|
47
|
+
"testsCreated",
|
|
48
|
+
"testsPassed",
|
|
49
|
+
"documentationCreated",
|
|
50
|
+
];
|
|
51
|
+
|
|
36
52
|
function migrateOwnerMetadata(value) {
|
|
37
53
|
if(value?.owner && !value.owner.processMarker && /^proc-[A-Za-z0-9-]+$/.test(value.owner.token || '')) {
|
|
38
54
|
value.owner.processMarker=value.owner.token;
|
|
@@ -820,6 +836,44 @@ export class WorkflowState {
|
|
|
820
836
|
return created;
|
|
821
837
|
}
|
|
822
838
|
|
|
839
|
+
/**
|
|
840
|
+
* Gabungkan kemajuan langkah dari disk bila task-nya sama.
|
|
841
|
+
*
|
|
842
|
+
* Hanya menyentuh flag yang MONOTON dalam satu task. Field yang bisa turun
|
|
843
|
+
* dengan sengaja (mis. `readyCheckCompleted` yang direset `create_tests`)
|
|
844
|
+
* tidak digabung, karena itu bagian dari alur normal satu proses.
|
|
845
|
+
*
|
|
846
|
+
* `reconciled` sudah memuat state disk terbaru; `this.state` adalah milik
|
|
847
|
+
* proses ini. Kalau task berbeda atau disk idle, tidak ada yang digabung:
|
|
848
|
+
* itu task lain, atau task baru yang memang harus mulai dari nol.
|
|
849
|
+
*/
|
|
850
|
+
mergeMonotonicProgress(reconciled) {
|
|
851
|
+
const diskTask = (reconciled?.diskTask || "").trim();
|
|
852
|
+
const ourTask = (this.state.taskDescription || "").trim();
|
|
853
|
+
if (!diskTask || !ourTask || diskTask !== ourTask) {
|
|
854
|
+
return;
|
|
855
|
+
}
|
|
856
|
+
if ((reconciled?.diskPhase || "idle") === "idle") {
|
|
857
|
+
return;
|
|
858
|
+
}
|
|
859
|
+
|
|
860
|
+
// Harus dari `reconciled` yang BARU dibaca. `lastReconcile` adalah hasil
|
|
861
|
+
// penyimpanan sebelumnya, sehingga bisa tertinggal satu putaran - dan
|
|
862
|
+
// justru itu yang membuat penggabungan gagal menangkap langkah terbaru.
|
|
863
|
+
const disk = reconciled?.diskState;
|
|
864
|
+
if (!disk) {
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
|
|
868
|
+
// Kemajuan hanya boleh maju. Sekali sebuah langkah tercapai di proses mana
|
|
869
|
+
// pun, langkah itu tetap tercapai.
|
|
870
|
+
for (const field of MONOTONIC_STEP_FIELDS) {
|
|
871
|
+
if (disk[field] === true && this.state[field] !== true) {
|
|
872
|
+
this.state[field] = true;
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
|
|
823
877
|
async save(options = {}) {
|
|
824
878
|
assertSafeKnowledge(this.state);
|
|
825
879
|
const reconciled = await this.reconcileWithDisk();
|
|
@@ -839,6 +893,19 @@ export class WorkflowState {
|
|
|
839
893
|
}
|
|
840
894
|
this.lastConflict = null;
|
|
841
895
|
|
|
896
|
+
// Dua proses MCP bisa melayani sesi yang SAMA (OpenCode men-spawn lebih dari
|
|
897
|
+
// satu instance), dan keduanya menulis berkas state yang sama. Tanpa ini,
|
|
898
|
+
// proses yang memegang state basi menimpa langkah yang sudah dicapai proses
|
|
899
|
+
// lain: `mark_bug_fixed` menjawab sukses, lalu `create_tests` menjawab
|
|
900
|
+
// "mark your feature/bug as fixed first". Gejalanya intermiten, sehingga
|
|
901
|
+
// mudah disalahkan pada hal lain.
|
|
902
|
+
//
|
|
903
|
+
// Langkah workflow bersifat monoton dalam satu task: sekali tercapai, tidak
|
|
904
|
+
// boleh mundur. Karena itu digabung dengan OR - tetapi HANYA bila task di
|
|
905
|
+
// disk memang task yang sama. `start_task` mengganti task, dan pada saat itu
|
|
906
|
+
// flag harus benar-benar direset, bukan dihidupkan kembali.
|
|
907
|
+
this.mergeMonotonicProgress(reconciled);
|
|
908
|
+
|
|
842
909
|
this.state.owner = {
|
|
843
910
|
processMarker: PROCESS_TOKEN,
|
|
844
911
|
pid: process.pid,
|
|
@@ -988,11 +1055,15 @@ export class WorkflowState {
|
|
|
988
1055
|
foreignToken: null,
|
|
989
1056
|
diskTask: "",
|
|
990
1057
|
diskPhase: "idle",
|
|
1058
|
+
// Dipakai mergeMonotonicProgress: state disk apa adanya, supaya flag
|
|
1059
|
+
// langkah yang sudah dicapai proses lain bisa digabung.
|
|
1060
|
+
diskState: null,
|
|
991
1061
|
};
|
|
992
1062
|
const disk = await this.readDiskState();
|
|
993
1063
|
if (!disk) {
|
|
994
1064
|
return result;
|
|
995
1065
|
}
|
|
1066
|
+
result.diskState = disk;
|
|
996
1067
|
|
|
997
1068
|
result.diskTask = disk.taskDescription || "";
|
|
998
1069
|
result.diskPhase = disk.currentPhase || "idle";
|