workq-mcp 0.1.0 → 0.1.1

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 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;