workq-mcp 0.1.0 → 0.1.2
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 +53 -1
- package/bin/workq.mjs +174 -2
- package/lib/analyze-repo.mjs +1126 -0
- package/lib/natural-git.mjs +481 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -11,7 +11,7 @@ npm install -g workq-mcp
|
|
|
11
11
|
## Binaries
|
|
12
12
|
|
|
13
13
|
- `workq-mcp`: stdio MCP bridge that forwards JSON-RPC to `https://work-q.nado.ai.kr/api/mcp`.
|
|
14
|
-
- `workq`: helper CLI for one-time local repository connection.
|
|
14
|
+
- `workq`: helper CLI for one-time local repository connection and repository bootstrap.
|
|
15
15
|
|
|
16
16
|
## Environment
|
|
17
17
|
|
|
@@ -41,6 +41,58 @@ git rev-parse HEAD
|
|
|
41
41
|
|
|
42
42
|
Then it calls `workq_connect_local_project`.
|
|
43
43
|
|
|
44
|
+
By default, `workq connect` also reads local product docs, package scripts, routes, API/server files, tests, deployment config, and known integration folders. It then calls:
|
|
45
|
+
|
|
46
|
+
- `workq_update_project_context`
|
|
47
|
+
- `workq_connect_local_project`
|
|
48
|
+
- `workq_update_product_map`
|
|
49
|
+
|
|
50
|
+
This creates or updates the Work Q project context and an initial Product State Map from the local repository. Use `--no-bootstrap` only when you want to connect repository identity without updating the feature map.
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
workq connect --project-key WORKQ --no-bootstrap
|
|
54
|
+
workq bootstrap --project-key WORKQ
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
The bootstrap analyzer records observed code evidence, but unknown product intent remains `input_required` or `decision_needed`; it must not be treated as an approved bug-fix target until the owner decides.
|
|
58
|
+
|
|
59
|
+
## Import Natural-Git Specs
|
|
60
|
+
|
|
61
|
+
Work Q v2 cannot make the hosted web server read a desktop path like
|
|
62
|
+
`/Users/jin/code/lotto`. Generate the natural-language git documents on the
|
|
63
|
+
desktop and upload them through MCP:
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
cd /Users/jin/code/lotto
|
|
67
|
+
workq natural-git --project-key LOTTO
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
This command reads the local repository, creates `natural-git/README.md`,
|
|
71
|
+
`natural-git/code-index.md`, and one `natural-git/code/*.md` document per
|
|
72
|
+
code/config file, then imports those docs into the Work Q v2 natural-spec repo.
|
|
73
|
+
After that, open Work Q v2 and run `자연어 커밋 -> 명세 머지 -> 커밋 승인·Agent 큐`.
|
|
74
|
+
Work Q creates a queued MCP work packet from the accepted natural commit. Your
|
|
75
|
+
desktop Codex/Claude agent should claim that task, update the local repository,
|
|
76
|
+
run checks, create any PR/deploy from the local environment, and close the loop
|
|
77
|
+
with `workq_finish_task`.
|
|
78
|
+
|
|
79
|
+
For clean bootstrap verification, delete exactly one project with an explicit confirmation key:
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
workq reset --project-key WORKQ --confirm-project-key WORKQ
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Standard Completion Protocol
|
|
86
|
+
|
|
87
|
+
When a desktop agent finishes a Work Q task, it must close the loop in Work Q before saying the work is done.
|
|
88
|
+
|
|
89
|
+
1. Claim the task with `workq_claim_task`.
|
|
90
|
+
2. Post progress with `workq_post_progress` while reproducing, patching, and testing.
|
|
91
|
+
3. Finish completed work with `workq_finish_task`.
|
|
92
|
+
4. Include the returned `completion_receipt` in the final response.
|
|
93
|
+
|
|
94
|
+
`workq_finish_task` requires `test_summary`, `remaining_risk`, and at least one of `changed_node_keys` or `verified_node_keys`. If the task cannot be completed, use `workq_request_decision` or `workq_post_result` with `failed`/`decision_needed` instead of leaving the task in progress.
|
|
95
|
+
|
|
44
96
|
## Codex MCP Registration
|
|
45
97
|
|
|
46
98
|
Example:
|
package/bin/workq.mjs
CHANGED
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
import { execFileSync } from "node:child_process";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import os from "node:os";
|
|
6
|
+
import { analyzeRepository } from "../lib/analyze-repo.mjs";
|
|
7
|
+
import { generateNaturalGitDocs } from "../lib/natural-git.mjs";
|
|
6
8
|
|
|
7
9
|
const DEFAULT_ENDPOINT = "https://work-q.nado.ai.kr/api/mcp";
|
|
8
10
|
|
|
@@ -10,7 +12,10 @@ function usage() {
|
|
|
10
12
|
process.stdout.write(`Work Q CLI
|
|
11
13
|
|
|
12
14
|
Usage:
|
|
13
|
-
workq connect --project-key <KEY> [--device-label <LABEL>]
|
|
15
|
+
workq connect --project-key <KEY> [--device-label <LABEL>] [--node-display-language ko|en]
|
|
16
|
+
workq bootstrap --project-key <KEY> [--node-display-language ko|en]
|
|
17
|
+
workq natural-git --project-key <KEY>
|
|
18
|
+
workq reset --project-key <KEY> --confirm-project-key <KEY>
|
|
14
19
|
workq mcp
|
|
15
20
|
|
|
16
21
|
Environment:
|
|
@@ -20,6 +25,11 @@ Environment:
|
|
|
20
25
|
|
|
21
26
|
Examples:
|
|
22
27
|
workq connect --project-key WORKQ
|
|
28
|
+
workq connect --project-key WORKQ --node-display-language ko
|
|
29
|
+
workq connect --project-key WORKQ --no-bootstrap
|
|
30
|
+
workq bootstrap --project-key WORKQ
|
|
31
|
+
workq natural-git --project-key LOTTO
|
|
32
|
+
workq reset --project-key WORKQ --confirm-project-key WORKQ
|
|
23
33
|
WORKQ_MCP_TOKEN_FILE=~/.codex/workq-mcp-token workq connect --project-key WORKQ
|
|
24
34
|
`);
|
|
25
35
|
}
|
|
@@ -114,6 +124,27 @@ async function connect(args) {
|
|
|
114
124
|
const commitSha = git(["rev-parse", "HEAD"]);
|
|
115
125
|
const remote = parseRemote(gitRemoteUrl);
|
|
116
126
|
const deviceLabel = typeof args["device-label"] === "string" ? args["device-label"].trim() : `${os.hostname()} local`;
|
|
127
|
+
const shouldBootstrap = args.bootstrap !== false && args["no-bootstrap"] !== true;
|
|
128
|
+
const nodeDisplayLanguage = args["node-display-language"] || args.node_display_language || "";
|
|
129
|
+
|
|
130
|
+
let bootstrapResult = null;
|
|
131
|
+
let effectiveNodeDisplayLanguage = nodeDisplayLanguage;
|
|
132
|
+
if (shouldBootstrap) {
|
|
133
|
+
bootstrapResult = analyzeRepository({
|
|
134
|
+
localPath,
|
|
135
|
+
projectKey,
|
|
136
|
+
remote,
|
|
137
|
+
branch: gitBranch,
|
|
138
|
+
commitSha,
|
|
139
|
+
nodeDisplayLanguage
|
|
140
|
+
});
|
|
141
|
+
effectiveNodeDisplayLanguage = nodeDisplayLanguage || bootstrapResult.analysis.language;
|
|
142
|
+
|
|
143
|
+
await callMcp("workq_update_project_context", {
|
|
144
|
+
...bootstrapResult.projectContext,
|
|
145
|
+
node_display_language: effectiveNodeDisplayLanguage
|
|
146
|
+
});
|
|
147
|
+
}
|
|
117
148
|
|
|
118
149
|
const result = await callMcp("workq_connect_local_project", {
|
|
119
150
|
project_key: projectKey,
|
|
@@ -123,9 +154,20 @@ async function connect(args) {
|
|
|
123
154
|
repository_owner: remote.owner,
|
|
124
155
|
repository_repo: remote.repo,
|
|
125
156
|
git_branch: gitBranch,
|
|
126
|
-
commit_sha: commitSha
|
|
157
|
+
commit_sha: commitSha,
|
|
158
|
+
node_display_language: effectiveNodeDisplayLanguage
|
|
127
159
|
});
|
|
128
160
|
|
|
161
|
+
let productMapResult = null;
|
|
162
|
+
if (shouldBootstrap && bootstrapResult) {
|
|
163
|
+
productMapResult = await callMcp("workq_update_product_map", {
|
|
164
|
+
project_key: projectKey,
|
|
165
|
+
summary: bootstrapResult.productMap.summary,
|
|
166
|
+
nodes: bootstrapResult.productMap.nodes,
|
|
167
|
+
edges: bootstrapResult.productMap.edges
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
|
|
129
171
|
process.stdout.write(`Connected Work Q local repository binding.
|
|
130
172
|
project_key: ${result.service.project_key}
|
|
131
173
|
binding_id: ${result.binding.id}
|
|
@@ -133,6 +175,124 @@ status: ${result.binding.status}
|
|
|
133
175
|
repo: ${result.binding.repository_full_name}
|
|
134
176
|
local_path: ${result.binding.local_path}
|
|
135
177
|
branch: ${result.binding.git_branch ?? ""}
|
|
178
|
+
bootstrap: ${shouldBootstrap ? "updated project context and product map" : "skipped"}
|
|
179
|
+
${productMapResult ? `product_map_nodes: ${productMapResult.productMap?.nodes?.length ?? productMapResult.changedNodes?.length ?? 0}
|
|
180
|
+
product_map_edges: ${productMapResult.productMap?.edges?.length ?? productMapResult.changedEdges?.length ?? 0}
|
|
181
|
+
analysis_files: ${bootstrapResult.analysis.file_count}
|
|
182
|
+
analysis_docs: ${bootstrapResult.analysis.doc_files.join(", ") || "none"}
|
|
183
|
+
` : ""}
|
|
184
|
+
`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function bootstrap(args) {
|
|
188
|
+
const projectKey = required(args["project-key"] || args.project_key, "--project-key");
|
|
189
|
+
const localPath = git(["rev-parse", "--show-toplevel"]);
|
|
190
|
+
const gitRemoteUrl = git(["remote", "get-url", "origin"]);
|
|
191
|
+
const gitBranch = git(["branch", "--show-current"]);
|
|
192
|
+
const commitSha = git(["rev-parse", "HEAD"]);
|
|
193
|
+
const remote = parseRemote(gitRemoteUrl);
|
|
194
|
+
const nodeDisplayLanguage = args["node-display-language"] || args.node_display_language || "";
|
|
195
|
+
const result = analyzeRepository({
|
|
196
|
+
localPath,
|
|
197
|
+
projectKey,
|
|
198
|
+
remote,
|
|
199
|
+
branch: gitBranch,
|
|
200
|
+
commitSha,
|
|
201
|
+
nodeDisplayLanguage
|
|
202
|
+
});
|
|
203
|
+
const effectiveNodeDisplayLanguage = nodeDisplayLanguage || result.analysis.language;
|
|
204
|
+
|
|
205
|
+
await callMcp("workq_update_project_context", {
|
|
206
|
+
...result.projectContext,
|
|
207
|
+
node_display_language: effectiveNodeDisplayLanguage
|
|
208
|
+
});
|
|
209
|
+
const productMap = await callMcp("workq_update_product_map", {
|
|
210
|
+
project_key: projectKey,
|
|
211
|
+
summary: result.productMap.summary,
|
|
212
|
+
nodes: result.productMap.nodes,
|
|
213
|
+
edges: result.productMap.edges
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
process.stdout.write(`Bootstrapped Work Q project from local repository.
|
|
217
|
+
project_key: ${projectKey}
|
|
218
|
+
repo: ${remote.owner}/${remote.repo}
|
|
219
|
+
local_path: ${localPath}
|
|
220
|
+
branch: ${gitBranch}
|
|
221
|
+
nodes: ${productMap.productMap?.nodes?.length ?? result.productMap.nodes.length}
|
|
222
|
+
edges: ${productMap.productMap?.edges?.length ?? result.productMap.edges.length}
|
|
223
|
+
analysis_files: ${result.analysis.file_count}
|
|
224
|
+
analysis_docs: ${result.analysis.doc_files.join(", ") || "none"}
|
|
225
|
+
`);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function naturalGit(args) {
|
|
229
|
+
const projectKey = required(args["project-key"] || args.project_key, "--project-key");
|
|
230
|
+
const localPath = git(["rev-parse", "--show-toplevel"]);
|
|
231
|
+
const gitRemoteUrl = git(["remote", "get-url", "origin"]);
|
|
232
|
+
const gitBranch = git(["branch", "--show-current"]);
|
|
233
|
+
const commitSha = git(["rev-parse", "HEAD"]);
|
|
234
|
+
const remote = parseRemote(gitRemoteUrl);
|
|
235
|
+
const projectName = typeof args["project-name"] === "string" ? args["project-name"].trim() : projectKey;
|
|
236
|
+
|
|
237
|
+
await callMcp("workq_update_project_context", {
|
|
238
|
+
project_key: projectKey,
|
|
239
|
+
project_name: projectName,
|
|
240
|
+
local_path_hint: localPath,
|
|
241
|
+
repository_owner: remote.owner,
|
|
242
|
+
repository_repo: remote.repo,
|
|
243
|
+
default_branch: gitBranch || "main",
|
|
244
|
+
branch_prefix: "workq-v2/",
|
|
245
|
+
project_notes: `Natural-git import from ${localPath} at ${commitSha}.`
|
|
246
|
+
});
|
|
247
|
+
|
|
248
|
+
await callMcp("workq_connect_local_project", {
|
|
249
|
+
project_key: projectKey,
|
|
250
|
+
device_label: typeof args["device-label"] === "string" ? args["device-label"].trim() : `${os.hostname()} local`,
|
|
251
|
+
local_path: localPath,
|
|
252
|
+
git_remote_url: gitRemoteUrl,
|
|
253
|
+
repository_owner: remote.owner,
|
|
254
|
+
repository_repo: remote.repo,
|
|
255
|
+
git_branch: gitBranch,
|
|
256
|
+
commit_sha: commitSha
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
const generated = generateNaturalGitDocs({
|
|
260
|
+
localPath,
|
|
261
|
+
projectKey,
|
|
262
|
+
projectName,
|
|
263
|
+
repository: remote
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
const imported = await callMcp("workq_import_natural_git_specs", {
|
|
267
|
+
project_key: projectKey,
|
|
268
|
+
local_path: generated.root,
|
|
269
|
+
files_scanned: generated.files_scanned,
|
|
270
|
+
code_files: generated.code_files,
|
|
271
|
+
docs: generated.docs
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
process.stdout.write(`Imported natural-language git specs into Work Q.
|
|
275
|
+
project_key: ${imported.service.project_key}
|
|
276
|
+
repo: ${remote.owner}/${remote.repo}
|
|
277
|
+
local_path: ${generated.root}
|
|
278
|
+
files_scanned: ${generated.files_scanned}
|
|
279
|
+
code_files: ${generated.code_files}
|
|
280
|
+
natural_git_docs: ${generated.docs.length}
|
|
281
|
+
next_step: Open Work Q v2, confirm natural-spec repo docs, then run 자연어 커밋 -> 명세 머지 -> 커밋 승인·Agent 큐. The desktop agent should claim the queued task and finish it with workq_finish_task.
|
|
282
|
+
`);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async function reset(args) {
|
|
286
|
+
const projectKey = required(args["project-key"] || args.project_key, "--project-key");
|
|
287
|
+
const confirmProjectKey = required(args["confirm-project-key"] || args.confirm_project_key, "--confirm-project-key");
|
|
288
|
+
const result = await callMcp("workq_reset_project", {
|
|
289
|
+
project_key: projectKey,
|
|
290
|
+
confirm_project_key: confirmProjectKey
|
|
291
|
+
});
|
|
292
|
+
process.stdout.write(`Reset Work Q project.
|
|
293
|
+
project_key: ${result.project_key ?? projectKey}
|
|
294
|
+
deleted: ${Boolean(result.deleted)}
|
|
295
|
+
service_id: ${result.service_id ?? ""}
|
|
136
296
|
`);
|
|
137
297
|
}
|
|
138
298
|
|
|
@@ -147,6 +307,18 @@ async function main() {
|
|
|
147
307
|
await connect(args);
|
|
148
308
|
return;
|
|
149
309
|
}
|
|
310
|
+
if (command === "bootstrap") {
|
|
311
|
+
await bootstrap(args);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
if (command === "natural-git") {
|
|
315
|
+
await naturalGit(args);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
318
|
+
if (command === "reset") {
|
|
319
|
+
await reset(args);
|
|
320
|
+
return;
|
|
321
|
+
}
|
|
150
322
|
if (command === "mcp") {
|
|
151
323
|
await import("./workq-mcp.mjs");
|
|
152
324
|
return;
|