@programinglive/dev-workflow-mcp-server 1.13.9 ā 1.13.11
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 +21 -2
- package/exec.js +105 -31
- package/git-facade.js +4 -0
- package/git-helpers.js +23 -0
- package/index.js +5 -1
- package/package.json +1 -1
- package/scripts/verify-push-branch.mjs +111 -0
- package/tools/definitions.js +19 -0
- package/tools/handler-modules/commit-handlers.js +137 -5
- package/tools/handler-modules/release-handlers.js +14 -1
- package/tools/handler-modules/workflow-handlers.js +69 -1
- package/workflow-state.js +12 -0
package/README.md
CHANGED
|
@@ -610,14 +610,33 @@ then reinstall.
|
|
|
610
610
|
changes** - git ran in the wrong repository. The server used to fall back to its
|
|
611
611
|
own package directory, which is also a git repo, so commits landed there (or
|
|
612
612
|
nowhere) instead of in your project. It now refuses to run git in the package
|
|
613
|
-
repo and asks for the project explicitly
|
|
613
|
+
repo and asks for the project explicitly.
|
|
614
|
+
|
|
615
|
+
Since 30 October 2026 the reliable fix is **per task**, not global. Pass
|
|
616
|
+
`projectDir` to `start_task`; the repo is pinned for that task and every later
|
|
617
|
+
git operation uses it:
|
|
618
|
+
|
|
619
|
+
```json
|
|
620
|
+
{ "description": "Fix login redirect", "type": "bugfix", "projectDir": "D:\\code\\clinic" }
|
|
621
|
+
```
|
|
622
|
+
|
|
623
|
+
`start_task` answers with `š Repo proyek: <path> (<source>)`, so the repo in use
|
|
624
|
+
is never a guess. `commit_and_push` repeats it on every response.
|
|
625
|
+
|
|
626
|
+
`WORKFLOW_PROJECT_DIR` still works as an operator-wide default and still wins
|
|
627
|
+
over the session directory, but it is one value for the whole server process.
|
|
628
|
+
When the session directory points at a *different* repo, `start_task` now
|
|
629
|
+
reports `ā ļø KONFLIK` and tells you to pass `projectDir` ā previously that
|
|
630
|
+
mismatch was silent, which is how a commit landed in the wrong repo. Prefer
|
|
631
|
+
`projectDir`; keep the env var only for machines with a single project.
|
|
614
632
|
|
|
615
633
|
```bash
|
|
616
634
|
export WORKFLOW_PROJECT_DIR=/path/to/your/project
|
|
617
635
|
```
|
|
618
636
|
|
|
619
637
|
Point it at any directory inside the project; the repo root is discovered
|
|
620
|
-
upward. Non-git commands are unaffected.
|
|
638
|
+
upward. Non-git commands are unaffected. Full incident write-up:
|
|
639
|
+
[`docs/2026-10-30-project-dir-misrouting.md`](docs/2026-10-30-project-dir-misrouting.md).
|
|
621
640
|
|
|
622
641
|
**A task jumped straight to `done` after reporting `review`** - it was reported
|
|
623
642
|
twice. Factory treats the second report as approval. Current versions block that
|
package/exec.js
CHANGED
|
@@ -49,64 +49,138 @@ function samePath(a, b) {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
*
|
|
52
|
+
* Direktori repo proyek yang aktif untuk pemanggilan ini.
|
|
53
53
|
*
|
|
54
|
-
*
|
|
54
|
+
* Kenapa ada: `WORKFLOW_PROJECT_DIR` adalah satu nilai global untuk seluruh
|
|
55
|
+
* proses MCP. Di mesin ini nilainya `D:\code\clinic`, sehingga sesi yang
|
|
56
|
+
* sebenarnya bekerja di repo lain tetap menjalankan git di clinic ā dan
|
|
57
|
+
* `commit_and_push` melaporkan "No changes detected" seolah sudah selesai.
|
|
55
58
|
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
58
|
-
*
|
|
59
|
-
|
|
59
|
+
* Direktori aktif diisi dari state sesi (`projectDir`), yang ditentukan saat
|
|
60
|
+
* `start_task` atau saat `commit_and_push`/`perform_release` dipanggil dengan
|
|
61
|
+
* argumen `projectDir` eksplisit.
|
|
62
|
+
*/
|
|
63
|
+
let activeProjectDir = "";
|
|
64
|
+
|
|
65
|
+
export function setActiveProjectDir(dir) {
|
|
66
|
+
activeProjectDir = dir ? path.resolve(dir) : "";
|
|
67
|
+
return activeProjectDir;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
export function getActiveProjectDir() {
|
|
71
|
+
return activeProjectDir;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function repoRootOrThrow(dir, message) {
|
|
75
|
+
const root = findRepoRoot(dir);
|
|
76
|
+
if (!root) {
|
|
77
|
+
throw new Error(message);
|
|
78
|
+
}
|
|
79
|
+
return root;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Menentukan direktori repo proyek, sekaligus melaporkan DARI MANA asalnya.
|
|
60
84
|
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
64
|
-
*
|
|
85
|
+
* Urutan (per 30 Oktober 2026):
|
|
86
|
+
* 1. explicit - argumen `projectDir` pada panggilan tool
|
|
87
|
+
* 2. pinned - `projectDir` yang dicatat `start_task` untuk sesi ini
|
|
88
|
+
* 3. env - WORKFLOW_PROJECT_DIR / PROJECT_DIR (setelan operator, global)
|
|
89
|
+
* 4. cwd - direktori kerja sesi (kandidat)
|
|
90
|
+
*
|
|
91
|
+
* `env` tetap di atas `cwd` karena ia setelan operator yang disengaja: kalau
|
|
92
|
+
* seseorang menunjuk repo lewat env, ia bermaksud demikian.
|
|
93
|
+
*
|
|
94
|
+
* Yang berubah: env sekarang bisa DIKALAHKAN per sesi lewat `projectDir`, dan
|
|
95
|
+
* bila env dipakai sementara direktori sesi menunjuk repo lain, konfliknya
|
|
96
|
+
* dilaporkan (`conflict`). Sebelumnya tidak ada jalan keluar sama sekali:
|
|
97
|
+
* satu nilai global membajak semua sesi, `git status` di repo yang salah
|
|
98
|
+
* menjawab "bersih", dan workflow melaporkan sukses palsu.
|
|
99
|
+
*
|
|
100
|
+
* `source` dan `conflict` dipakai tool untuk memberi tahu pengguna repo mana
|
|
101
|
+
* yang dipakai - dan kapan ia kemungkinan salah.
|
|
65
102
|
*/
|
|
66
|
-
export function
|
|
103
|
+
export function resolveProjectDir({ explicit = "", pinned = "", env = process.env, candidates = [] } = {}) {
|
|
67
104
|
if (explicit) {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
105
|
+
return {
|
|
106
|
+
dir: repoRootOrThrow(explicit, `Direktori "${explicit}" bukan repo git (tidak ada .git).`),
|
|
107
|
+
source: "explicit",
|
|
108
|
+
conflict: null,
|
|
109
|
+
};
|
|
73
110
|
}
|
|
74
111
|
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
112
|
+
if (pinned) {
|
|
113
|
+
return {
|
|
114
|
+
dir: repoRootOrThrow(
|
|
115
|
+
pinned,
|
|
116
|
+
`projectDir sesi "${pinned}" bukan repo git (tidak ada .git). ` +
|
|
117
|
+
"Perbaiki dengan memanggil 'start_task' lagi dengan projectDir yang benar.",
|
|
118
|
+
),
|
|
119
|
+
source: "pinned",
|
|
120
|
+
conflict: null,
|
|
121
|
+
};
|
|
82
122
|
}
|
|
83
123
|
|
|
84
124
|
const found = candidates.map((candidate) => findRepoRoot(candidate)).filter(Boolean);
|
|
125
|
+
const cwdRepo = found.find((dir) => !samePath(dir, PACKAGE_DIR)) || null;
|
|
126
|
+
const packageCandidate = found.some((dir) => samePath(dir, PACKAGE_DIR));
|
|
127
|
+
|
|
128
|
+
const envDir = (env.WORKFLOW_PROJECT_DIR || env.PROJECT_DIR || "").trim();
|
|
129
|
+
if (envDir) {
|
|
130
|
+
const envRepo = repoRootOrThrow(envDir, `WORKFLOW_PROJECT_DIR "${envDir}" bukan repo git (tidak ada .git).`);
|
|
131
|
+
return {
|
|
132
|
+
dir: envRepo,
|
|
133
|
+
source: "env",
|
|
134
|
+
// Direktori sesi menunjuk repo lain: kemungkinan besar env ini milik
|
|
135
|
+
// proyek lain, dan git akan berjalan di repo yang salah.
|
|
136
|
+
conflict: cwdRepo && !samePath(cwdRepo, envRepo) ? { envDir: envRepo, cwdDir: cwdRepo } : null,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
85
139
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
return projectRepo;
|
|
140
|
+
if (cwdRepo) {
|
|
141
|
+
return { dir: cwdRepo, source: "cwd", conflict: null };
|
|
89
142
|
}
|
|
90
143
|
|
|
91
144
|
// Hanya repo paket yang ditemukan. Itu hanya mungkin bila salah satu kandidat
|
|
92
145
|
// benar-benar berada di dalamnya, jadi pemanggil memilih repo ini dengan sadar.
|
|
93
|
-
if (
|
|
94
|
-
return path.resolve(PACKAGE_DIR);
|
|
146
|
+
if (packageCandidate) {
|
|
147
|
+
return { dir: path.resolve(PACKAGE_DIR), source: "cwd", conflict: null };
|
|
95
148
|
}
|
|
96
149
|
|
|
97
150
|
throw new Error(
|
|
98
151
|
"Tidak bisa menentukan repo proyek: tidak ada repository git pada " +
|
|
99
152
|
`${candidates.filter(Boolean).map((candidate) => `"${candidate}"`).join(", ") || "direktori kerja"}. ` +
|
|
100
|
-
"
|
|
153
|
+
"Sebutkan 'projectDir' pada 'start_task' (disarankan), atau set WORKFLOW_PROJECT_DIR, " +
|
|
154
|
+
"atau jalankan server MCP dari dalam direktori proyek.",
|
|
101
155
|
);
|
|
102
156
|
}
|
|
103
157
|
|
|
158
|
+
/**
|
|
159
|
+
* Penentu direktori repo proyek tempat git boleh dijalankan.
|
|
160
|
+
*
|
|
161
|
+
* Urutan: eksplisit -> WORKFLOW_PROJECT_DIR/PROJECT_DIR -> kandidat.
|
|
162
|
+
*
|
|
163
|
+
* Repo paket MCP sengaja tidak dipilih sebagai cadangan: dulu server pernah
|
|
164
|
+
* menjalankan git di repo-nya sendiri saat cwd proyek gagal diresolusi, lalu
|
|
165
|
+
* melaporkan "No changes detected" sementara perubahan proyek tidak pernah
|
|
166
|
+
* ter-commit.
|
|
167
|
+
*
|
|
168
|
+
* Tetapi kandidat yang MEMANG berada di dalam repo paket berarti pemanggil
|
|
169
|
+
* sengaja bekerja di sana - misalnya saat mengembangkan server MCP ini sendiri.
|
|
170
|
+
* Membuangnya tanpa syarat membuat `commit_and_push` mustahil dipakai di repo
|
|
171
|
+
* paket, jadi kandidat seperti itu diterima. Repo proyek lain tetap menang.
|
|
172
|
+
*/
|
|
173
|
+
export function resolveWorkingDirectory(options = {}) {
|
|
174
|
+
return resolveProjectDir(options).dir;
|
|
175
|
+
}
|
|
176
|
+
|
|
104
177
|
function getDefaultCwd(command) {
|
|
105
178
|
try {
|
|
106
|
-
return
|
|
179
|
+
return resolveProjectDir({
|
|
180
|
+
explicit: activeProjectDir,
|
|
107
181
|
env: process.env,
|
|
108
182
|
candidates: [process.cwd(), process.env.INIT_CWD],
|
|
109
|
-
});
|
|
183
|
+
}).dir;
|
|
110
184
|
} catch (error) {
|
|
111
185
|
// Perintah non-git (echo, npm run release, ...) tidak butuh repo proyek.
|
|
112
186
|
// Yang berbahaya hanya git: di sana cwd menentukan repo mana yang diubah.
|
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/index.js
CHANGED
|
@@ -13,7 +13,7 @@ import {
|
|
|
13
13
|
GetPromptRequestSchema,
|
|
14
14
|
} from "@modelcontextprotocol/sdk/types.js";
|
|
15
15
|
import { fileURLToPath } from "url";
|
|
16
|
-
import { exec } from "./exec.js";
|
|
16
|
+
import { exec, setActiveProjectDir } from "./exec.js";
|
|
17
17
|
import { normalizeRequestArgs, shellEscape } from "./utils.js";
|
|
18
18
|
import { createGitFacade } from "./git-facade.js";
|
|
19
19
|
import {
|
|
@@ -60,6 +60,9 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
60
60
|
const sessionId = sessionKeyFromRequest(request);
|
|
61
61
|
if (sessionId) await workflowState.useSession(sessionId);
|
|
62
62
|
await workflowState.load();
|
|
63
|
+
// Repo proyek mengikuti sesi, bukan satu nilai global. Tanpa ini, sesi yang
|
|
64
|
+
// bekerja di repo lain tetap menjalankan git di WORKFLOW_PROJECT_DIR.
|
|
65
|
+
setActiveProjectDir(workflowState.state.projectDir || "");
|
|
63
66
|
}
|
|
64
67
|
return securityOutputFilter(await handleToolCall({
|
|
65
68
|
request,
|
|
@@ -234,6 +237,7 @@ async function cliMain(argv) {
|
|
|
234
237
|
workflowState = new WorkflowState();
|
|
235
238
|
await workflowState.load();
|
|
236
239
|
await workflowState.ensurePrimaryFile();
|
|
240
|
+
setActiveProjectDir(workflowState.state.projectDir || "");
|
|
237
241
|
|
|
238
242
|
const request = {
|
|
239
243
|
params: {
|
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.11",
|
|
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
|
+
}
|
package/tools/definitions.js
CHANGED
|
@@ -25,6 +25,14 @@ export function getToolList() {
|
|
|
25
25
|
enum: ["feature", "bugfix", "refactor", "other"],
|
|
26
26
|
description: "Type of task",
|
|
27
27
|
},
|
|
28
|
+
projectDir: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description:
|
|
31
|
+
"Absolute path to the repository this task works on (e.g. D:\\code\\clinic). " +
|
|
32
|
+
"Pinned for the whole task; every git operation afterwards runs there. " +
|
|
33
|
+
"Pass it whenever the session directory is not itself a git repository, " +
|
|
34
|
+
"otherwise the server may fall back to WORKFLOW_PROJECT_DIR and commit to the wrong repo.",
|
|
35
|
+
},
|
|
28
36
|
},
|
|
29
37
|
required: ["description", "type"],
|
|
30
38
|
},
|
|
@@ -153,6 +161,12 @@ export function getToolList() {
|
|
|
153
161
|
type: "string",
|
|
154
162
|
description: "Optional branch name to push to (defaults to current branch)",
|
|
155
163
|
},
|
|
164
|
+
projectDir: {
|
|
165
|
+
type: "string",
|
|
166
|
+
description:
|
|
167
|
+
"Absolute path to the repository to commit in. Overrides the repo pinned by 'start_task' " +
|
|
168
|
+
"for this call. Use it to fix a misdirected commit without restarting the task.",
|
|
169
|
+
},
|
|
156
170
|
},
|
|
157
171
|
required: [],
|
|
158
172
|
},
|
|
@@ -172,6 +186,11 @@ export function getToolList() {
|
|
|
172
186
|
type: "string",
|
|
173
187
|
description: "Optional release notes",
|
|
174
188
|
},
|
|
189
|
+
projectDir: {
|
|
190
|
+
type: "string",
|
|
191
|
+
description:
|
|
192
|
+
"Absolute path to the repository to release. Overrides the repo pinned by 'start_task' for this call.",
|
|
193
|
+
},
|
|
175
194
|
},
|
|
176
195
|
required: ["command"],
|
|
177
196
|
},
|
|
@@ -1,9 +1,114 @@
|
|
|
1
1
|
import { textResponse, isReleaseSatisfied, formatChecklist } from "./shared.js";
|
|
2
2
|
import { handleReadyCheck } from "./status-handlers.js";
|
|
3
3
|
import { containsTestFilesInStatus } from "../../git-helpers.js";
|
|
4
|
+
import { resolveProjectDir, setActiveProjectDir, getActiveProjectDir } from "../../exec.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Label repo yang benar-benar dipakai, untuk ditampilkan di setiap jawaban.
|
|
8
|
+
*
|
|
9
|
+
* Tanpa ini, "No changes detected" tidak bisa dibedakan antara "sudah commit"
|
|
10
|
+
* dan "git berjalan di repo yang salah". Itu persis yang menyamarkan commit
|
|
11
|
+
* salah arah pada 30 Oktober 2026.
|
|
12
|
+
*/
|
|
13
|
+
function repoLabel() {
|
|
14
|
+
try {
|
|
15
|
+
const resolved = resolveProjectDir({
|
|
16
|
+
explicit: getActiveProjectDir(),
|
|
17
|
+
env: process.env,
|
|
18
|
+
candidates: [process.cwd(), process.env.INIT_CWD],
|
|
19
|
+
});
|
|
20
|
+
const origin = resolved.source === "env" ? " ā dari WORKFLOW_PROJECT_DIR (global)" : "";
|
|
21
|
+
return resolved.dir + origin;
|
|
22
|
+
} catch {
|
|
23
|
+
return "(tidak bisa ditentukan)";
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Terapkan `projectDir` dari argumen tool bila ada.
|
|
29
|
+
* Mengembalikan pesan kesalahan, atau null bila tidak ada masalah.
|
|
30
|
+
*/
|
|
31
|
+
function applyProjectDirOverride(args) {
|
|
32
|
+
const requested = typeof args.projectDir === "string" ? args.projectDir.trim() : "";
|
|
33
|
+
if (!requested) {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const resolved = resolveProjectDir({ explicit: requested });
|
|
39
|
+
setActiveProjectDir(resolved.dir);
|
|
40
|
+
return null;
|
|
41
|
+
} catch (error) {
|
|
42
|
+
return `ā projectDir tidak valid: ${error.message}`;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
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
|
+
}
|
|
4
101
|
|
|
5
102
|
export async function handleCommitAndPush(args, context) {
|
|
6
103
|
const { workflowState, exec, git, utils } = context;
|
|
104
|
+
|
|
105
|
+
const projectDirError = applyProjectDirOverride(args);
|
|
106
|
+
if (projectDirError) {
|
|
107
|
+
return textResponse(projectDirError);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
workflowState.state.projectDir = getActiveProjectDir() || workflowState.state.projectDir || "";
|
|
111
|
+
|
|
7
112
|
if (!workflowState.state.readyToCommit || !workflowState.state.readyCheckCompleted) {
|
|
8
113
|
const readyResponse = await handleReadyCheck(workflowState);
|
|
9
114
|
if (workflowState.state.currentPhase !== "commit") {
|
|
@@ -29,17 +134,31 @@ export async function handleCommitAndPush(args, context) {
|
|
|
29
134
|
} catch (error) {
|
|
30
135
|
return textResponse(
|
|
31
136
|
`ā Tidak bisa memeriksa repository Git, jadi tidak ada yang di-commit.\n\n` +
|
|
137
|
+
`Repo: ${repoLabel()}\n` +
|
|
32
138
|
`Penyebab: ${error.message}\n\n` +
|
|
33
|
-
`
|
|
139
|
+
`Sebutkan 'projectDir' pada 'start_task' atau 'commit_and_push' untuk menunjuk repo yang benar.`
|
|
34
140
|
);
|
|
35
141
|
}
|
|
36
142
|
|
|
37
|
-
const branchForPush = requestedBranch
|
|
143
|
+
const branchForPush = pickPushBranch({ requestedBranch, currentBranch, primaryBranch });
|
|
38
144
|
|
|
39
145
|
if (!hasWorkingChanges) {
|
|
40
146
|
const lastCommitMessage = await git.getLastCommitMessage();
|
|
41
147
|
const effectiveCommitMessage = lastCommitMessage || providedCommitMessage;
|
|
42
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
|
+
|
|
43
162
|
workflowState.state.commitAndPushCompleted = true;
|
|
44
163
|
workflowState.state.lastCommitMessage = effectiveCommitMessage || "";
|
|
45
164
|
workflowState.state.lastPushBranch = branchForPush;
|
|
@@ -47,7 +166,7 @@ export async function handleCommitAndPush(args, context) {
|
|
|
47
166
|
await workflowState.save();
|
|
48
167
|
|
|
49
168
|
return textResponse(
|
|
50
|
-
`ā¹ļø No changes detected. Assuming commit and push already completed.\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)"
|
|
51
170
|
}\n\nNext: Run 'perform_release' to handle the release and push tags.`
|
|
52
171
|
);
|
|
53
172
|
}
|
|
@@ -102,6 +221,20 @@ export async function handleCommitAndPush(args, context) {
|
|
|
102
221
|
);
|
|
103
222
|
}
|
|
104
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
|
+
|
|
105
238
|
workflowState.state.commitAndPushCompleted = true;
|
|
106
239
|
workflowState.state.lastCommitMessage = generatedSummary;
|
|
107
240
|
workflowState.state.lastPushBranch = branchForPush;
|
|
@@ -109,8 +242,7 @@ export async function handleCommitAndPush(args, context) {
|
|
|
109
242
|
await workflowState.save();
|
|
110
243
|
|
|
111
244
|
return textResponse(
|
|
112
|
-
`ā
Commit and push completed!\n\nCommit message: ${generatedSummary}\nPushed to: ${branchForPush
|
|
113
|
-
}\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.`
|
|
114
246
|
);
|
|
115
247
|
}
|
|
116
248
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { textResponse, isReleaseSatisfied, resetToCommitIfWorkingChanges, summarizeHistory } from "./shared.js";
|
|
2
|
+
import { resolveProjectDir, setActiveProjectDir, getActiveProjectDir } from "../../exec.js";
|
|
2
3
|
|
|
3
4
|
export async function handleSkipRelease(args, workflowState) {
|
|
4
5
|
if (workflowState.state.currentPhase === "idle") {
|
|
@@ -50,6 +51,18 @@ export async function handlePerformRelease(args, context) {
|
|
|
50
51
|
return textResponse("ā ļø Please start a task first using 'start_task'!");
|
|
51
52
|
}
|
|
52
53
|
|
|
54
|
+
// Sama seperti commit_and_push: projectDir eksplisit menang atas repo sesi.
|
|
55
|
+
const requestedProjectDir =
|
|
56
|
+
typeof args.projectDir === "string" ? args.projectDir.trim() : "";
|
|
57
|
+
if (requestedProjectDir) {
|
|
58
|
+
try {
|
|
59
|
+
setActiveProjectDir(resolveProjectDir({ explicit: requestedProjectDir }).dir);
|
|
60
|
+
} catch (error) {
|
|
61
|
+
return textResponse(`ā projectDir tidak valid: ${error.message}`);
|
|
62
|
+
}
|
|
63
|
+
workflowState.state.projectDir = getActiveProjectDir();
|
|
64
|
+
}
|
|
65
|
+
|
|
53
66
|
let hasNewChanges;
|
|
54
67
|
try {
|
|
55
68
|
hasNewChanges = await resetToCommitIfWorkingChanges(workflowState, git);
|
|
@@ -81,7 +94,7 @@ export async function handlePerformRelease(args, context) {
|
|
|
81
94
|
return textResponse(
|
|
82
95
|
`ā Tidak bisa memeriksa repository Git, jadi status rilis tidak dapat diverifikasi.\n\n` +
|
|
83
96
|
`Penyebab: ${error.message}\n\n` +
|
|
84
|
-
`
|
|
97
|
+
`Sebutkan 'projectDir' pada 'perform_release' untuk menunjuk repo yang benar.`
|
|
85
98
|
);
|
|
86
99
|
}
|
|
87
100
|
|
|
@@ -1,5 +1,71 @@
|
|
|
1
1
|
import { classify } from '../../security/policy.js';
|
|
2
2
|
import { textResponse, isReleaseSatisfied, isErrorResponse, ensurePrdExists } from "./shared.js";
|
|
3
|
+
import { resolveProjectDir, setActiveProjectDir } from "../../exec.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Catat repo proyek untuk sesi ini.
|
|
7
|
+
*
|
|
8
|
+
* Tanpa ini, satu-satunya penentu adalah WORKFLOW_PROJECT_DIR yang global.
|
|
9
|
+
* Di mesin ini nilainya `D:\code\clinic`, jadi task yang sebenarnya bekerja di
|
|
10
|
+
* repo lain tetap menjalankan git di clinic dan `commit_and_push` menjawab
|
|
11
|
+
* "No changes detected" seolah sudah selesai (30 Oktober 2026).
|
|
12
|
+
*
|
|
13
|
+
* Best-effort: kalau repo belum bisa ditentukan, task tetap boleh mulai, tapi
|
|
14
|
+
* pengguna diberi tahu supaya tidak terkejut saat commit.
|
|
15
|
+
*/
|
|
16
|
+
function pinProjectDir(args, workflowState) {
|
|
17
|
+
const requested = typeof args.projectDir === "string" ? args.projectDir.trim() : "";
|
|
18
|
+
|
|
19
|
+
let resolved = null;
|
|
20
|
+
let error = null;
|
|
21
|
+
try {
|
|
22
|
+
resolved = resolveProjectDir({
|
|
23
|
+
explicit: requested,
|
|
24
|
+
env: process.env,
|
|
25
|
+
candidates: [process.cwd(), process.env.INIT_CWD],
|
|
26
|
+
});
|
|
27
|
+
} catch (resolveError) {
|
|
28
|
+
error = resolveError;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!resolved) {
|
|
32
|
+
workflowState.state.projectDir = "";
|
|
33
|
+
setActiveProjectDir("");
|
|
34
|
+
return {
|
|
35
|
+
message:
|
|
36
|
+
`ā ļø Repo proyek belum ditentukan: ${error.message}\n` +
|
|
37
|
+
` Sebutkan 'projectDir' pada 'start_task' supaya commit tidak salah arah.`,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
workflowState.state.projectDir = resolved.dir;
|
|
42
|
+
setActiveProjectDir(resolved.dir);
|
|
43
|
+
|
|
44
|
+
const sourceNote = {
|
|
45
|
+
explicit: "dari argumen projectDir",
|
|
46
|
+
cwd: "dari direktori kerja sesi",
|
|
47
|
+
env: "dari WORKFLOW_PROJECT_DIR (nilai global)",
|
|
48
|
+
pinned: "dari projectDir sesi",
|
|
49
|
+
}[resolved.source] || resolved.source;
|
|
50
|
+
|
|
51
|
+
let message = `š Repo proyek: ${resolved.dir} (${sourceNote})`;
|
|
52
|
+
|
|
53
|
+
// Sumber env berarti tidak ada sinyal per-sesi sama sekali. Kalau direktori
|
|
54
|
+
// sesi ternyata menunjuk repo LAIN, hampir pasti env ini milik proyek lain -
|
|
55
|
+
// dan itulah yang dulu membuat `git add .` berjalan di repo yang salah.
|
|
56
|
+
if (resolved.conflict) {
|
|
57
|
+
message +=
|
|
58
|
+
`\n ā ļø KONFLIK: direktori sesi menunjuk repo lain, ${resolved.conflict.cwdDir}.` +
|
|
59
|
+
`\n Repo dipilih dari WORKFLOW_PROJECT_DIR. Kalau task ini untuk repo sesi,` +
|
|
60
|
+
`\n ulangi 'start_task' dengan projectDir: "${resolved.conflict.cwdDir}".`;
|
|
61
|
+
} else if (resolved.source === "env") {
|
|
62
|
+
message +=
|
|
63
|
+
`\n ā ļø Repo ini berasal dari WORKFLOW_PROJECT_DIR, bukan dari sesi.` +
|
|
64
|
+
`\n Kalau task ini bekerja di repo lain, ulangi 'start_task' dengan projectDir yang benar.`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return { message };
|
|
68
|
+
}
|
|
3
69
|
|
|
4
70
|
export async function handleStartTask(args, workflowState) {
|
|
5
71
|
// Selamatkan dulu pekerjaan yang mungkin tertimpa. Ini menangani dua kasus:
|
|
@@ -17,6 +83,8 @@ export async function handleStartTask(args, workflowState) {
|
|
|
17
83
|
workflowState.state.commitAndPushCompleted = false;
|
|
18
84
|
workflowState.state.lastCommitMessage = "";
|
|
19
85
|
workflowState.state.lastPushBranch = "";
|
|
86
|
+
// Setelah reset(), karena reset mengosongkan projectDir.
|
|
87
|
+
const project = pinProjectDir(args, workflowState);
|
|
20
88
|
// start_task memang bermaksud MENGGANTI task, jadi ia boleh menang atas
|
|
21
89
|
// task berjalan. Task yang diganti tetap diarsipkan oleh reconcileWithDisk,
|
|
22
90
|
// bukan dihapus.
|
|
@@ -47,7 +115,7 @@ export async function handleStartTask(args, workflowState) {
|
|
|
47
115
|
const warningBlock = warnings.length > 0 ? `\n\n${warnings.join("\n\n")}` : "";
|
|
48
116
|
|
|
49
117
|
return textResponse(
|
|
50
|
-
`ā
Task Started: ${args.description}${warningBlock}\n\nšÆ Be conscious about what you're coding!\n\nWorkflow Steps:\n1. ā Start task (current)\n2. ā³ Describe feature flow with Mermaid\n3. ā³ Fix/implement the feature\n4. ā³ Create tests\n5. ā³ Run tests (must pass!)\n6. ā³ Create documentation\n7. ā³ Run 'check_ready_to_commit'\n8. ā³ Run 'commit_and_push' (commits and pushes)\n9. ā³ Run 'perform_release' (handles versioning and tags) or 'skip_release' (when no release is needed)\n10. ā³ Complete task\n\nReminder: Focus on writing clean, maintainable code!`
|
|
118
|
+
`ā
Task Started: ${args.description}\n\n${project.message}${warningBlock}\n\nšÆ Be conscious about what you're coding!\n\nWorkflow Steps:\n1. ā Start task (current)\n2. ā³ Describe feature flow with Mermaid\n3. ā³ Fix/implement the feature\n4. ā³ Create tests\n5. ā³ Run tests (must pass!)\n6. ā³ Create documentation\n7. ā³ Run 'check_ready_to_commit'\n8. ā³ Run 'commit_and_push' (commits and pushes)\n9. ā³ Run 'perform_release' (handles versioning and tags) or 'skip_release' (when no release is needed)\n10. ā³ Complete task\n\nReminder: Focus on writing clean, maintainable code!`
|
|
51
119
|
);
|
|
52
120
|
}
|
|
53
121
|
|
package/workflow-state.js
CHANGED
|
@@ -624,6 +624,9 @@ export class WorkflowState {
|
|
|
624
624
|
commitAndPushCompleted: false,
|
|
625
625
|
lastCommitMessage: "",
|
|
626
626
|
lastPushBranch: "",
|
|
627
|
+
// Repo proyek untuk sesi ini. Ditentukan saat start_task, dipakai semua
|
|
628
|
+
// operasi git berikutnya. Kosong berarti ikuti urutan resolusi di exec.js.
|
|
629
|
+
projectDir: "",
|
|
627
630
|
featureFlowCreated: false,
|
|
628
631
|
featureFlowDescription: "",
|
|
629
632
|
history: [],
|
|
@@ -665,6 +668,9 @@ export class WorkflowState {
|
|
|
665
668
|
commitAndPushCompleted: false,
|
|
666
669
|
lastCommitMessage: "",
|
|
667
670
|
lastPushBranch: "",
|
|
671
|
+
// Repo proyek untuk sesi ini. Ditentukan saat start_task, dipakai semua
|
|
672
|
+
// operasi git berikutnya. Kosong berarti ikuti urutan resolusi di exec.js.
|
|
673
|
+
projectDir: "",
|
|
668
674
|
featureFlowCreated: false,
|
|
669
675
|
featureFlowDescription: "",
|
|
670
676
|
history: [],
|
|
@@ -724,6 +730,9 @@ export class WorkflowState {
|
|
|
724
730
|
if (typeof this.state.lastPushBranch !== "string") {
|
|
725
731
|
this.state.lastPushBranch = "";
|
|
726
732
|
}
|
|
733
|
+
if (typeof this.state.projectDir !== "string") {
|
|
734
|
+
this.state.projectDir = "";
|
|
735
|
+
}
|
|
727
736
|
if (typeof this.state.featureFlowCreated !== "boolean") {
|
|
728
737
|
this.state.featureFlowCreated = false;
|
|
729
738
|
}
|
|
@@ -1174,6 +1183,9 @@ export class WorkflowState {
|
|
|
1174
1183
|
commitAndPushCompleted: false,
|
|
1175
1184
|
lastCommitMessage: "",
|
|
1176
1185
|
lastPushBranch: "",
|
|
1186
|
+
// Repo proyek untuk sesi ini. Ditentukan saat start_task, dipakai semua
|
|
1187
|
+
// operasi git berikutnya. Kosong berarti ikuti urutan resolusi di exec.js.
|
|
1188
|
+
projectDir: "",
|
|
1177
1189
|
featureFlowCreated: false,
|
|
1178
1190
|
featureFlowDescription: "",
|
|
1179
1191
|
history,
|