ai-project-manage-cli 4.0.22 → 5.0.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/dist/index.js CHANGED
@@ -40,12 +40,6 @@ function defaultApmConfig() {
40
40
  email: ""
41
41
  };
42
42
  }
43
- function httpBaseToWsOrigin(httpBase) {
44
- const u = httpBase.trim().replace(/\/+$/, "");
45
- if (u.startsWith("https://")) return "wss://" + u.slice("https://".length);
46
- if (u.startsWith("http://")) return "ws://" + u.slice("http://".length);
47
- throw new Error("baseUrl \u5FC5\u987B\u4EE5 http:// \u6216 https:// \u5F00\u5934");
48
- }
49
43
  async function ensureApmConfig() {
50
44
  const existing = await readApmConfig();
51
45
  if (existing) return existing;
@@ -65,172 +59,47 @@ async function writeApmConfig(cfg) {
65
59
  mode: 384
66
60
  });
67
61
  }
68
- function buildAgentWsUrl(httpBase, token) {
69
- const origin = httpBaseToWsOrigin(httpBase);
70
- const q = new URLSearchParams({ token });
71
- return `${origin}/ws/agent?${q.toString()}`;
72
- }
73
-
74
- // src/commands/comment.ts
75
- import { readFileSync as readFileSync2 } from "fs";
76
-
77
- // src/api/client.ts
78
- import { createApiClient } from "listpage-http";
79
62
 
80
- // src/api/request-config.ts
81
- import { defineEndpoint } from "listpage-http";
82
- var requestConfig = {
83
- auth: {
84
- login: defineEndpoint({
85
- method: "POST",
86
- path: "/auth/login",
87
- authRequired: false
88
- })
89
- },
90
- cliRequirements: {
91
- pull: defineEndpoint({
92
- method: "GET",
93
- path: "/cli/requirements/pull"
94
- }),
95
- branchBaseline: defineEndpoint({
96
- method: "GET",
97
- path: "/cli/requirements/branch-baseline"
98
- }),
99
- comment: defineEndpoint({
100
- method: "POST",
101
- path: "/cli/requirements/comment"
102
- }),
103
- commentStructured: defineEndpoint({
104
- method: "POST",
105
- path: "/cli/requirements/comment-structured"
106
- }),
107
- refine: defineEndpoint({
108
- method: "POST",
109
- path: "/cli/requirements/refine"
110
- }),
111
- updateStatus: defineEndpoint({
112
- method: "POST",
113
- path: "/cli/requirements/update-status"
114
- }),
115
- updateDevStatus: defineEndpoint({
116
- method: "POST",
117
- path: "/cli/requirements/update-dev-status"
118
- })
119
- },
120
- requirementAttachment: {
121
- list: defineEndpoint({
122
- method: "GET",
123
- path: "/requirement-attachments/list"
124
- }),
125
- download: defineEndpoint({
126
- method: "GET",
127
- path: "/requirement-attachments/download",
128
- mode: "download"
129
- })
130
- },
131
- requirementArtifact: {
132
- list: defineEndpoint({
133
- method: "GET",
134
- path: "/requirement-artifacts/list"
135
- }),
136
- get: defineEndpoint({
137
- method: "GET",
138
- path: "/requirement-artifacts/get"
139
- }),
140
- create: defineEndpoint({
141
- method: "POST",
142
- path: "/requirement-artifacts/create"
143
- }),
144
- upsert: defineEndpoint({
145
- method: "POST",
146
- path: "/requirement-artifacts/upsert"
147
- }),
148
- update: defineEndpoint({
149
- method: "POST",
150
- path: "/requirement-artifacts/update"
151
- }),
152
- delete: defineEndpoint({
153
- method: "POST",
154
- path: "/requirement-artifacts/delete"
155
- })
156
- },
157
- theaterSessionArtifact: {
158
- upsert: defineEndpoint({
159
- method: "POST",
160
- path: "/theater-session-artifacts/upsert"
161
- }),
162
- create: defineEndpoint({
163
- method: "POST",
164
- path: "/theater-session-artifacts/create"
165
- }),
166
- update: defineEndpoint({
167
- method: "POST",
168
- path: "/theater-session-artifacts/update"
169
- }),
170
- list: defineEndpoint({
171
- method: "GET",
172
- path: "/theater-session-artifacts/list"
173
- }),
174
- get: defineEndpoint({
175
- method: "GET",
176
- path: "/theater-session-artifacts/get"
177
- })
178
- },
179
- theater: {
180
- reportMemberAgentProgress: defineEndpoint({
181
- method: "POST",
182
- path: "/theater/sessions/report-member-agent-progress"
183
- }),
184
- submitMemberResponse: defineEndpoint({
185
- method: "POST",
186
- path: "/theater/sessions/submit-member-response"
187
- })
188
- },
189
- spaceWorkflow: {
190
- aiStartNode: defineEndpoint({
191
- method: "POST",
192
- path: "/space-workflows/ai-start-node"
193
- }),
194
- aiConfirmNode: defineEndpoint(
195
- {
196
- method: "POST",
197
- path: "/space-workflows/ai-confirm-node"
198
- }
199
- ),
200
- aiFailNode: defineEndpoint({
201
- method: "POST",
202
- path: "/space-workflows/ai-fail-node"
203
- })
204
- }
205
- };
206
-
207
- // src/api/client.ts
208
- function createApmApiClient(cfg) {
209
- const baseURL = `${cfg.baseUrl.trim().replace(/\/+$/, "")}/api/v1`;
210
- return createApiClient(requestConfig, {
211
- baseURL,
212
- getToken: () => cfg.token || void 0,
213
- successCodes: [200, 201],
214
- unauthorizedCodes: [401]
215
- });
216
- }
63
+ // src/commands/init.ts
64
+ import { join as join3 } from "path";
65
+ import { readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
217
66
 
218
67
  // src/command-utils.ts
219
68
  import { cpSync, existsSync, mkdirSync as mkdirSync2, readdirSync, statSync } from "fs";
220
- import { dirname, join as join2, resolve } from "path";
69
+ import { basename, dirname, extname, join as join2, resolve } from "path";
221
70
  import { fileURLToPath } from "url";
222
71
  var __dirname = dirname(fileURLToPath(import.meta.url));
223
72
  var CLI_TEMPLATE_DIR = resolve(__dirname, "../template");
224
- var WORKSPACE_APM_DIR = resolve(process.cwd(), ".apm");
225
- function requirementWorkitemsDir(requirementId, workspaceDir = WORKSPACE_APM_DIR) {
226
- return join2(workspaceDir, "workitems", requirementId);
73
+ function workspaceApmDir(cwd = process.cwd()) {
74
+ return resolve(cwd, ".apm");
75
+ }
76
+ var SESSIONS_SUBDIR = "sessions";
77
+ var SESSION_DOCS_SUBDIR = "docs";
78
+ var SESSION_ATTACHMENTS_SUBDIR = "attachments";
79
+ function sessionDir(sessionId, apmRoot) {
80
+ return join2(apmRoot ?? workspaceApmDir(), SESSIONS_SUBDIR, sessionId);
81
+ }
82
+ function sessionDocsDir(sessionId, apmRoot) {
83
+ return join2(sessionDir(sessionId, apmRoot), SESSION_DOCS_SUBDIR);
84
+ }
85
+ function sessionRulePath(sessionId, apmRoot) {
86
+ return join2(sessionDir(sessionId, apmRoot), "RULE.md");
227
87
  }
228
- function theaterSessionDir(sessionId, apmRoot = WORKSPACE_APM_DIR) {
229
- return join2(apmRoot, "theater-sessions", sessionId);
88
+ function sessionYamlPath(sessionId, apmRoot) {
89
+ return join2(sessionDir(sessionId, apmRoot), "session.yaml");
230
90
  }
231
- function theaterSessionDocumentPath(sessionId, relativePath = "docs/prd.md", apmRoot = WORKSPACE_APM_DIR) {
232
- const normalized = relativePath.replace(/\\/g, "/").replace(/^\/+/, "");
233
- return join2(theaterSessionDir(sessionId, apmRoot), normalized);
91
+ function documentLocalFileName(platformName) {
92
+ const trimmed = platformName.trim();
93
+ if (!trimmed) return "document.md";
94
+ if (extname(trimmed).toLowerCase() === ".md") return trimmed;
95
+ return `${trimmed}.md`;
96
+ }
97
+ function documentPlatformName(filePath) {
98
+ const base = basename(filePath.trim().replace(/\\/g, "/"));
99
+ if (base.toLowerCase().endsWith(".md")) {
100
+ return base.slice(0, -3);
101
+ }
102
+ return base;
234
103
  }
235
104
  async function ensureLoggedConfig() {
236
105
  const cfg = await ensureApmConfig();
@@ -246,7 +115,7 @@ async function ensureDirExists(dir) {
246
115
  mkdirSync2(dir, { recursive: true });
247
116
  }
248
117
  async function ensureWorkspaceApmDirForInit() {
249
- const dir = WORKSPACE_APM_DIR;
118
+ const dir = workspaceApmDir();
250
119
  if (!existsSync(dir)) {
251
120
  mkdirSync2(dir, { recursive: true });
252
121
  return;
@@ -277,994 +146,92 @@ async function copyTemplateFiles(targetDir) {
277
146
  })
278
147
  );
279
148
  }
280
- function xmlEscape(v) {
281
- return v.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;").replaceAll('"', "&quot;").replaceAll("'", "&apos;");
282
- }
283
149
  function resolveCwdPath(file) {
284
150
  return resolve(process.cwd(), file);
285
151
  }
286
152
 
287
- // src/structured-review-yaml.ts
288
- import { parse } from "yaml";
289
- var KIND_MAP = {
290
- clarify: "CLARIFY",
291
- difficulty: "DIFFICULTY",
292
- business: "BUSINESS",
293
- CLARIFY: "CLARIFY",
294
- DIFFICULTY: "DIFFICULTY",
295
- BUSINESS: "BUSINESS"
296
- };
297
- var STANCE_VALUES = /* @__PURE__ */ new Set(["frontend", "backend", "fullstack"]);
298
- function asRecord(value) {
299
- if (value && typeof value === "object" && !Array.isArray(value)) {
300
- return value;
301
- }
302
- throw new Error("YAML \u7ED3\u6784\u65E0\u6548\uFF1A\u9700\u8981\u5BF9\u8C61\u6839\u8282\u70B9");
303
- }
304
- function parseAnchor(anchor, index) {
305
- const rec = asRecord(anchor);
306
- const start = Number(rec.start ?? rec.startLine);
307
- const end = Number(rec.end ?? rec.endLine);
308
- if (!Number.isInteger(start) || !Number.isInteger(end) || start < 1 || end < start) {
309
- throw new Error(`items[${index}].anchor \u884C\u53F7\u65E0\u6548\uFF08\u9700 1 \u2264 start \u2264 end\uFF09`);
310
- }
311
- return { start, end };
312
- }
313
- function parseKind(raw, index) {
314
- const key = String(raw ?? "").trim();
315
- const kind = KIND_MAP[key] ?? KIND_MAP[key.toLowerCase()];
316
- if (!kind) {
317
- if (key.toLowerCase() === "coordination" || key === "COORDINATION") {
318
- throw new Error(
319
- `items[${index}].kind \u4E0D\u518D\u652F\u6301 coordination\uFF08\u8054\u8C03\u4F9D\u8D56\uFF09\uFF1B\u8BF7\u52FF\u8F93\u51FA\u63A5\u53E3/\u8054\u8C03\u7C7B\u8BC4\u5BA1\u6761\u76EE`
320
- );
321
- }
322
- throw new Error(
323
- `items[${index}].kind \u65E0\u6548\uFF0C\u5E94\u4E3A clarify | difficulty | business`
324
- );
325
- }
326
- return kind;
327
- }
328
- function parseStructuredReviewYaml(raw, cliModel) {
329
- let doc;
330
- try {
331
- doc = parse(raw);
332
- } catch (e) {
333
- const msg = e instanceof Error ? e.message : String(e);
334
- throw new Error(`YAML \u89E3\u6790\u5931\u8D25\uFF1A${msg}`);
335
- }
336
- const root = asRecord(doc);
337
- const reviewer = asRecord(root.reviewer ?? {});
338
- const stance = String(reviewer.stance ?? root.stance ?? "").trim();
339
- if (!STANCE_VALUES.has(stance)) {
340
- throw new Error("reviewer.stance \u5FC5\u987B\u4E3A frontend\u3001backend \u6216 fullstack");
341
- }
342
- const modelFromYaml = reviewer.model != null ? String(reviewer.model).trim() : "";
343
- const model = cliModel?.trim() || modelFromYaml || null;
344
- const itemsRaw = root.items;
345
- if (!Array.isArray(itemsRaw) || itemsRaw.length === 0) {
346
- throw new Error("items \u4E0D\u80FD\u4E3A\u7A7A");
347
- }
348
- const items = itemsRaw.map((entry, index) => {
349
- const item = asRecord(entry);
350
- const anchor = parseAnchor(item.anchor ?? item, index);
351
- const kind = parseKind(item.kind, index);
352
- const body = typeof item.body === "string" ? item.body.trim() : String(item.body ?? "").trim();
353
- if (!body) {
354
- throw new Error(`items[${index}].body \u4E0D\u80FD\u4E3A\u7A7A`);
355
- }
356
- return {
357
- startLine: anchor.start,
358
- endLine: anchor.end,
359
- kind,
360
- body
361
- };
362
- });
363
- return { stance, model, items };
364
- }
365
- function inferCommentFormat(filePath, explicit) {
366
- if (explicit === "structured" || explicit === "legacy") {
367
- return explicit;
368
- }
369
- const lower = filePath.toLowerCase();
370
- if (lower.endsWith(".yaml") || lower.endsWith(".yml")) {
371
- return "structured";
372
- }
373
- return "legacy";
374
- }
375
-
376
- // src/commands/comment.ts
377
- async function runComment(requirementId, file, options) {
378
- const cfg = await ensureLoggedConfig();
379
- const resolvedPath = resolveCwdPath(file);
380
- const raw = readFileSync2(resolvedPath, "utf8");
381
- const format = inferCommentFormat(resolvedPath, options?.format);
382
- const api = createApmApiClient(cfg);
383
- if (format === "structured") {
384
- const parsed = parseStructuredReviewYaml(raw, options?.model);
385
- const data2 = await api.cliRequirements.commentStructured({
386
- requirementId,
387
- stance: parsed.stance,
388
- model: parsed.model,
389
- items: parsed.items
390
- });
391
- console.log(JSON.stringify(data2, null, 2));
392
- return;
393
- }
394
- if (!raw.trim()) {
395
- throw new Error("\u8BC4\u8BBA\u6B63\u6587\u4E0D\u80FD\u4E3A\u7A7A");
153
+ // src/commands/init.ts
154
+ async function runInit(name) {
155
+ await ensureWorkspaceApmDirForInit();
156
+ const apmDir = workspaceApmDir();
157
+ await copyTemplateFiles(apmDir);
158
+ if (name) {
159
+ const apmConfigPath = join3(apmDir, "apm.config.json");
160
+ const config = readFileSync2(apmConfigPath, "utf8");
161
+ const configJson = JSON.parse(config);
162
+ configJson.name = name;
163
+ writeFileSync2(apmConfigPath, JSON.stringify(configJson, null, 2), "utf8");
396
164
  }
397
- const data = await api.cliRequirements.comment({
398
- requirementId,
399
- content: raw,
400
- model: options?.model
401
- });
402
- console.log(JSON.stringify(data, null, 2));
403
165
  }
404
166
 
405
- // src/commands/connect.ts
406
- import { randomUUID as randomUUID2 } from "crypto";
407
- import WebSocket from "ws";
408
- import { Agent } from "@cursor/sdk";
409
- import { join as join7 } from "path";
410
-
411
- // src/session-utils.ts
412
- import { appendFileSync } from "fs";
413
- import { resolve as resolve2 } from "path";
414
- var EventSession = class {
415
- events = [];
416
- constructor(prompt) {
417
- this.events.push({
418
- type: "input",
419
- content: prompt
420
- });
421
- }
422
- addEvent(event) {
423
- const latestEvent = this.events[this.events.length - 1];
424
- const formatedEvent = this.formatEvent(event);
425
- if (!formatedEvent) {
426
- return;
427
- }
428
- if (formatedEvent.type === "tool_call") {
429
- const existingToolCall = this.events.find(
430
- (e) => e.type === "tool_call" && e.call_id === formatedEvent.call_id
431
- );
432
- if (existingToolCall) {
433
- existingToolCall.args = formatedEvent.args;
434
- existingToolCall.result = formatedEvent.result;
435
- existingToolCall.status = formatedEvent.status;
436
- return;
437
- }
438
- this.events.push(formatedEvent);
439
- return;
440
- }
441
- if (formatedEvent.type === "status") {
442
- return;
443
- }
444
- if (formatedEvent.type === "request") {
445
- this.events.push(formatedEvent);
446
- return;
447
- }
448
- if (latestEvent?.type === formatedEvent.type) {
449
- switch (formatedEvent.type) {
450
- case "assistant":
451
- latestEvent.content += formatedEvent.content;
452
- break;
453
- case "thinking":
454
- latestEvent.content += formatedEvent.content;
455
- break;
456
- case "task":
457
- latestEvent.status = formatedEvent.status;
458
- latestEvent.text = formatedEvent.text;
459
- break;
460
- }
461
- return;
462
- }
463
- this.events.push(formatedEvent);
464
- }
465
- formatEvent(event) {
466
- switch (event.type) {
467
- case "assistant":
468
- return {
469
- type: "assistant",
470
- content: event.message.content[0].text || event.content || ""
471
- };
472
- case "thinking":
473
- return {
474
- type: "thinking",
475
- content: event.text || event.content || ""
476
- };
477
- case "tool_call":
478
- return {
479
- type: "tool_call",
480
- args: event.args,
481
- result: event.result,
482
- status: event.status,
483
- call_id: event.call_id,
484
- name: event.name
485
- };
486
- case "task":
487
- return {
488
- type: "task",
489
- status: event.status,
490
- text: event.text
491
- };
492
- case "request":
493
- return {
494
- ...event,
495
- type: "request"
496
- };
497
- case "status":
498
- return { type: "status", status: event.status, message: event.message };
499
- }
500
- }
501
- /** 合并所有 assistant 片段,供剧场成员回传等场景使用 */
502
- getAssistantText() {
503
- return this.events.filter((e) => e.type === "assistant").map((e) => String(e.content ?? "")).join("\n").trim();
504
- }
505
- writeToFile(cwd, requirementId, agentId) {
506
- const sessionsDir = resolve2(
507
- cwd,
508
- `.apm/workitems/${requirementId}/sessions`
509
- );
510
- ensureDirExists(sessionsDir);
511
- const sessionFile = resolve2(sessionsDir, `${agentId}.md`);
512
- this.events.forEach((event) => {
513
- const type = event.type;
514
- const content = (function(type2) {
515
- if (type2 === "input") {
516
- return `## \u7528\u6237\u8F93\u5165
517
-
518
- ${event.content}
519
- `;
520
- }
521
- if (type2 === "assistant") {
522
- return `## \u6A21\u578B\u8F93\u51FA
523
-
524
- ${event.content}
525
- `;
526
- }
527
- if (type2 === "thinking") {
528
- return `## \u6A21\u578B\u601D\u8003
167
+ // src/commands/login.ts
168
+ import { ApiError } from "listpage-http";
529
169
 
530
- ${event.content}
531
- `;
532
- }
533
- if (type2 === "tool_call") {
534
- return "````toolcall\n" + JSON.stringify(event, null, 2) + "\n````\n";
535
- }
536
- return `## \u672A\u77E5\u4E8B\u4EF6\uFF1A${type2}
170
+ // src/api/client.ts
171
+ import { createApiClient } from "listpage-http";
537
172
 
538
- \`\`\`json
539
- ${JSON.stringify(event, null, 2)}
540
- \`\`\``;
541
- })(type);
542
- appendFileSync(sessionFile, content + "\n");
543
- });
173
+ // src/api/request-config.ts
174
+ import { defineEndpoint } from "listpage-http";
175
+ var requestConfig = {
176
+ auth: {
177
+ login: defineEndpoint({
178
+ method: "POST",
179
+ path: "/auth/login",
180
+ authRequired: false
181
+ })
182
+ },
183
+ cli: {
184
+ sessionDetail: defineEndpoint({
185
+ method: "GET",
186
+ path: "/cli/sessions/detail"
187
+ }),
188
+ sessionMembers: defineEndpoint({
189
+ method: "GET",
190
+ path: "/cli/sessions/members"
191
+ }),
192
+ listDocuments: defineEndpoint({
193
+ method: "GET",
194
+ path: "/cli/documents"
195
+ }),
196
+ listAttachments: defineEndpoint({
197
+ method: "GET",
198
+ path: "/cli/attachments"
199
+ }),
200
+ upsertDocument: defineEndpoint({
201
+ method: "PUT",
202
+ path: "/cli/documents/upsert"
203
+ }),
204
+ appendMessageContent: defineEndpoint({
205
+ method: "PUT",
206
+ path: "/cli/messages/content"
207
+ }),
208
+ updateMessageStatus: defineEndpoint({
209
+ method: "PUT",
210
+ path: "/cli/messages/status"
211
+ }),
212
+ branchBaseline: defineEndpoint({
213
+ method: "GET",
214
+ path: "/cli/requirements/branch-baseline"
215
+ }),
216
+ listSkills: defineEndpoint({
217
+ method: "GET",
218
+ path: "/cli/skills"
219
+ })
544
220
  }
545
221
  };
546
222
 
547
- // src/commands/upload-artifact.ts
548
- import { existsSync as existsSync3, readFileSync as readFileSync3, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
549
- import { join as join4, relative, sep } from "path";
550
-
551
- // src/commands/sync-requirement-attachments.ts
552
- import { existsSync as existsSync2, writeFileSync as writeFileSync2 } from "fs";
553
- import { join as join3 } from "path";
554
- var REQUIREMENT_ATTACHMENTS_SUBDIR = "attachments";
555
- async function syncRequirementAttachments(api, requirementId, workitemsDir) {
556
- const dir = join3(workitemsDir, REQUIREMENT_ATTACHMENTS_SUBDIR);
557
- await ensureDirExists(dir);
558
- const { items } = await api.requirementAttachment.list({ requirementId });
559
- if (items.length === 0) return;
560
- for (const item of items) {
561
- const dest = join3(dir, item.fileName);
562
- if (existsSync2(dest)) continue;
563
- const { blob } = await api.requirementAttachment.download({
564
- attachmentId: item.id
565
- });
566
- const buffer = Buffer.from(await blob.arrayBuffer());
567
- writeFileSync2(dest, buffer);
568
- console.log(
569
- `[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${REQUIREMENT_ATTACHMENTS_SUBDIR}/${item.fileName}`
570
- );
571
- }
572
- }
573
-
574
- // src/commands/upload-artifact.ts
575
- var EXCLUDED_RELATIVE_PATHS = /* @__PURE__ */ new Set([
576
- "defect.xml",
577
- "prd.md",
578
- "requirement-status.yaml",
579
- "reviews.xml",
580
- "testcase.xml"
581
- ]);
582
- function toPosixRelative(root, absoluteFile) {
583
- return relative(root, absoluteFile).split(sep).join("/");
584
- }
585
- function artifactTagFromRelPath(relPosix) {
586
- const parts = relPosix.split("/");
587
- const fileName = parts[parts.length - 1] ?? relPosix;
588
- if (parts.length > 1) {
589
- return parts[0] ?? fileName;
590
- }
591
- const dot = fileName.lastIndexOf(".");
592
- return dot > 0 ? fileName.slice(0, dot) : fileName;
593
- }
594
- function* walkMarkdownFiles(dir) {
595
- const names = readdirSync2(dir);
596
- for (const name of names) {
597
- if (name.startsWith(".")) continue;
598
- if (name === REQUIREMENT_ATTACHMENTS_SUBDIR) continue;
599
- const full = join4(dir, name);
600
- const st = statSync2(full);
601
- if (st.isDirectory()) {
602
- yield* walkMarkdownFiles(full);
603
- continue;
604
- }
605
- if (!st.isFile()) continue;
606
- if (!name.toLowerCase().endsWith(".md")) continue;
607
- yield full;
608
- }
609
- }
610
- async function runUploadArtifact(requirementId, workspaceDir) {
611
- const cfg = await ensureLoggedConfig();
612
- const api = createApmApiClient(cfg);
613
- const root = requirementWorkitemsDir(requirementId, workspaceDir);
614
- if (!existsSync3(root)) {
615
- console.error(
616
- `[apm] \u76EE\u5F55\u4E0D\u5B58\u5728: ${root}
617
- \u8BF7\u5148\u6267\u884C: apm pull ${requirementId}`
618
- );
619
- process.exit(1);
620
- }
621
- for (const abs of walkMarkdownFiles(root)) {
622
- const relPosix = toPosixRelative(root, abs);
623
- if (EXCLUDED_RELATIVE_PATHS.has(relPosix)) continue;
624
- const content = readFileSync3(abs, "utf8");
625
- const tag = artifactTagFromRelPath(relPosix);
626
- await api.requirementArtifact.upsert({
627
- requirementId,
628
- tag,
629
- fileName: relPosix,
630
- content
631
- });
632
- console.log(`[apm] \u5DF2\u540C\u6B65\u4EA7\u7269: ${relPosix}`);
633
- }
634
- }
635
-
636
- // src/commands/pull.ts
637
- import { writeFileSync as writeFileSync3 } from "fs";
638
- import { join as join5 } from "path";
639
- import { stringify as yamlStringify } from "yaml";
640
-
641
- // src/pull-reviews-xml.ts
642
- function escapeForCdata(text) {
643
- return text.replace(/\]\]>/g, "]]]]><![CDATA[>");
644
- }
645
- function buildReviewsXml(reviews) {
646
- const lines = ["<reviews>"];
647
- for (const review of reviews) {
648
- lines.push(
649
- ` <review id="${xmlEscape(review.id)}" model="${xmlEscape(
650
- review.model ?? ""
651
- )}" stance="${xmlEscape(review.stance ?? "")}" memberRole="${xmlEscape(
652
- review.memberRole
653
- )}">`
654
- );
655
- for (const item of review.items) {
656
- const kind = item.kind.toLowerCase();
657
- lines.push(
658
- ` <item id="${xmlEscape(item.id)}" start="${item.startLine}" end="${item.endLine}" kind="${xmlEscape(kind)}" status="open">`
659
- );
660
- lines.push(
661
- ` <body><![CDATA[${escapeForCdata(item.body ?? "")}]]></body>`
662
- );
663
- const reply = item.reply?.trim() ?? "";
664
- lines.push(` <reply><![CDATA[${escapeForCdata(reply)}]]></reply>`);
665
- lines.push(" </item>");
666
- }
667
- lines.push(" </review>");
668
- }
669
- lines.push("</reviews>", "");
670
- return lines.join("\n");
671
- }
672
-
673
- // src/commands/pull.ts
674
- var PULL_ARTIFACT_FILE_NAMES = ["api.md", "backend.md"];
675
- function normalizeArtifactPath(fileName) {
676
- return fileName.trim().replace(/\\/g, "/").replace(/^\/+/, "");
677
- }
678
- function artifactFileNameMatches(itemFileName, targetFileName) {
679
- const norm = normalizeArtifactPath(itemFileName);
680
- const target = normalizeArtifactPath(targetFileName);
681
- if (norm === target) return true;
682
- const base = norm.split("/").filter(Boolean).pop() ?? norm;
683
- return base === target;
684
- }
685
- async function fetchArtifactContentByFileName(api, requirementId, fileName) {
686
- const pageSize = 500;
687
- let page = 1;
688
- const items = [];
689
- while (true) {
690
- const batch = await api.requirementArtifact.list({
691
- requirementId,
692
- page,
693
- pageSize
694
- });
695
- items.push(...batch.items);
696
- if (batch.total === 0 || items.length >= batch.total) break;
697
- page += 1;
698
- }
699
- const hit = items.find(
700
- (item) => artifactFileNameMatches(item.fileName, fileName)
701
- );
702
- if (!hit) return null;
703
- const art = await api.requirementArtifact.get({ artifactId: hit.id });
704
- return art.content;
705
- }
706
- function valueToXmlContent(value) {
707
- if (value === null || value === void 0) return "";
708
- if (typeof value === "string") return xmlEscape(value);
709
- if (typeof value === "number" || typeof value === "boolean") {
710
- return xmlEscape(String(value));
711
- }
712
- return `<![CDATA[${JSON.stringify(value)}]]>`;
713
- }
714
- function recordToXmlLines(record, indent) {
715
- const lines = [];
716
- for (const [rawKey, val] of Object.entries(record)) {
717
- const key = /^[a-zA-Z_][\w.-]*$/.test(rawKey) ? rawKey : "field";
718
- lines.push(`${indent}<${key}>${valueToXmlContent(val)}</${key}>`);
719
- }
720
- return lines;
721
- }
722
- function unknownArrayToXml(rootName, itemName, items) {
723
- const lines = [`<${rootName}>`];
724
- items.forEach((item, index) => {
725
- if (item !== null && typeof item === "object" && !Array.isArray(item)) {
726
- lines.push(` <${itemName} index="${index}">`);
727
- lines.push(...recordToXmlLines(item, " "));
728
- lines.push(` </${itemName}>`);
729
- } else {
730
- lines.push(
731
- ` <${itemName} index="${index}">${valueToXmlContent(
732
- item
733
- )}</${itemName}>`
734
- );
735
- }
736
- });
737
- lines.push(`</${rootName}>`, "");
738
- return lines.join("\n");
739
- }
740
- function escapeForCdata2(text) {
741
- return text.replace(/\]\]>/g, "]]]]><![CDATA[>");
742
- }
743
- function defectsToXml(defects) {
744
- const sorted = [...defects].sort(
745
- (a, b) => new Date(a.createdAt ?? 0).getTime() - new Date(b.createdAt ?? 0).getTime()
746
- );
747
- const lines = ["<defects>"];
748
- for (const d of sorted) {
749
- lines.push(` <defect id="${xmlEscape(d.id)}">`);
750
- lines.push(` <status>${xmlEscape(d.status)}</status>`);
751
- lines.push(
752
- ` <current><![CDATA[${escapeForCdata2(
753
- d.currentState ?? ""
754
- )}]]></current>`
755
- );
756
- lines.push(
757
- ` <expected><![CDATA[${escapeForCdata2(
758
- d.expectedEffect ?? ""
759
- )}]]></expected>`
760
- );
761
- lines.push(` </defect>`);
762
- }
763
- lines.push("</defects>", "");
764
- return lines.join("\n");
765
- }
766
- async function runPull(requirementId, workspaceDir) {
767
- const cfg = await ensureLoggedConfig();
768
- const api = createApmApiClient(cfg);
769
- const data = await api.cliRequirements.pull({ requirementId });
770
- const WORKITEMS_DIR = requirementWorkitemsDir(requirementId, workspaceDir);
771
- await ensureDirExists(WORKITEMS_DIR);
772
- const req2 = data.requirement;
773
- const statusYaml = yamlStringify(
774
- {
775
- id: req2.id,
776
- status: req2.status,
777
- title: req2.title
778
- },
779
- { lineWidth: 0 }
780
- );
781
- writeFileSync3(
782
- join5(WORKITEMS_DIR, "requirement-status.yaml"),
783
- statusYaml.endsWith("\n") ? statusYaml : `${statusYaml}
784
- `,
785
- "utf8"
786
- );
787
- writeFileSync3(join5(WORKITEMS_DIR, "prd.md"), req2.content || "", "utf8");
788
- const reviewsXml = buildReviewsXml(data.reviews ?? []);
789
- writeFileSync3(join5(WORKITEMS_DIR, "reviews.xml"), reviewsXml, "utf8");
790
- const defectsXml = defectsToXml(data.defects ?? []);
791
- writeFileSync3(join5(WORKITEMS_DIR, "defect.xml"), defectsXml, "utf8");
792
- const testCasesXml = unknownArrayToXml(
793
- "testcases",
794
- "case",
795
- data.testCases ?? []
796
- );
797
- writeFileSync3(join5(WORKITEMS_DIR, "testcase.xml"), testCasesXml, "utf8");
798
- for (const fileName of PULL_ARTIFACT_FILE_NAMES) {
799
- const content = await fetchArtifactContentByFileName(
800
- api,
801
- requirementId,
802
- fileName
803
- );
804
- if (content !== null) {
805
- writeFileSync3(join5(WORKITEMS_DIR, fileName), content, "utf8");
806
- }
807
- }
808
- await syncRequirementAttachments(api, requirementId, WORKITEMS_DIR);
809
- return WORKITEMS_DIR;
810
- }
811
-
812
- // src/commands/pull-theater-session.ts
813
- import { writeFileSync as writeFileSync4 } from "fs";
814
- import { dirname as dirname2, join as join6 } from "path";
815
- function normalizeRelativePath(fileName) {
816
- return fileName.trim().replace(/\\/g, "/").replace(/^\/+/, "");
817
- }
818
- async function runPullTheaterSession(sessionId, apmRoot = WORKSPACE_APM_DIR) {
819
- const trimmedSessionId = sessionId.trim();
820
- if (!trimmedSessionId) {
821
- throw new Error("sessionId \u4E0D\u80FD\u4E3A\u7A7A");
822
- }
823
- const cfg = await ensureLoggedConfig();
824
- const api = createApmApiClient(cfg);
825
- const sessionDir = theaterSessionDir(trimmedSessionId, apmRoot);
826
- await ensureDirExists(sessionDir);
827
- const pageSize = 500;
828
- let page = 1;
829
- const items = [];
830
- while (true) {
831
- const batch = await api.theaterSessionArtifact.list({
832
- sessionId: trimmedSessionId,
833
- page,
834
- pageSize
835
- });
836
- items.push(...batch.items);
837
- if (batch.total === 0 || items.length >= batch.total) break;
838
- page += 1;
839
- }
840
- for (const item of items) {
841
- const art = await api.theaterSessionArtifact.get({
842
- sessionId: trimmedSessionId,
843
- artifactId: item.id
844
- });
845
- const relativePath = normalizeRelativePath(art.fileName);
846
- const destPath = join6(sessionDir, relativePath);
847
- await ensureDirExists(dirname2(destPath));
848
- writeFileSync4(destPath, art.content, "utf8");
849
- console.log(`[apm] \u5DF2\u8986\u76D6\u4F1A\u8BDD\u6587\u6863: ${relativePath}`);
850
- }
851
- return sessionDir;
852
- }
853
-
854
- // src/theater-job-report.ts
855
- import { randomUUID } from "crypto";
856
- function extractErrorMessage(err) {
857
- if (err instanceof Error && err.message.trim()) {
858
- return err.message.trim();
859
- }
860
- if (typeof err === "string" && err.trim()) {
861
- return err.trim();
862
- }
863
- if (typeof err === "object" && err && "message" in err) {
864
- const msg = err.message;
865
- if (typeof msg === "string" && msg.trim()) return msg.trim();
866
- if (Array.isArray(msg)) return msg.map(String).join("\uFF1B");
867
- }
868
- return "\u672A\u77E5\u9519\u8BEF";
869
- }
870
- function isUnauthorizedMessage(msg) {
871
- return msg.includes("\u672A\u767B\u5F55") || /401/.test(msg);
872
- }
873
- function sendTheaterReportWs(ws, payload) {
874
- return new Promise((resolve5, reject) => {
875
- const msg_id = randomUUID();
876
- const timer = setTimeout(() => {
877
- ws.off("message", onMessage);
878
- reject(new Error("WebSocket \u4E0A\u62A5\u8D85\u65F6"));
879
- }, 3e4);
880
- const onMessage = (data) => {
881
- const text = typeof data === "string" ? data : data.toString();
882
- let frame;
883
- try {
884
- frame = JSON.parse(text);
885
- } catch {
886
- return;
887
- }
888
- if (frame.msg_id !== msg_id) return;
889
- clearTimeout(timer);
890
- ws.off("message", onMessage);
891
- if (frame.type === "ERROR") {
892
- reject(
893
- new Error(frame.payload?.message?.trim() || "WebSocket \u4E0A\u62A5\u5931\u8D25")
894
- );
895
- return;
896
- }
897
- if (frame.type === "ACK") {
898
- resolve5();
899
- return;
900
- }
901
- };
902
- ws.on("message", onMessage);
903
- ws.send(
904
- JSON.stringify({
905
- type: "THEATER_REPORT",
906
- msg_id,
907
- ts: (/* @__PURE__ */ new Date()).toISOString(),
908
- payload
909
- })
910
- );
911
- });
912
- }
913
- async function reportTheaterProgress(api, ws, params) {
914
- const body = {
915
- sessionId: params.sessionId,
916
- memberKey: params.memberKey,
917
- status: "IN_PROGRESS",
918
- logId: params.logId
919
- };
920
- try {
921
- await api.theater.reportMemberAgentProgress(body);
922
- } catch (err) {
923
- const msg = extractErrorMessage(err);
924
- if (!isUnauthorizedMessage(msg)) throw err;
925
- await sendTheaterReportWs(ws, { action: "progress", ...body });
926
- }
927
- }
928
- async function submitTheaterResult(api, ws, params) {
929
- const body = {
930
- sessionId: params.sessionId,
931
- memberKey: params.memberKey,
932
- content: params.content,
933
- agentId: params.agentId ?? "",
934
- agentStatus: params.agentStatus,
935
- logId: params.logId
936
- };
937
- try {
938
- await api.theater.submitMemberResponse(body);
939
- } catch (err) {
940
- const msg = extractErrorMessage(err);
941
- if (!isUnauthorizedMessage(msg)) throw err;
942
- await sendTheaterReportWs(ws, {
943
- action: "submit",
944
- sessionId: params.sessionId,
945
- memberKey: params.memberKey,
946
- content: params.content,
947
- agentId: params.agentId,
948
- agentStatus: params.agentStatus,
949
- logId: params.logId
950
- });
951
- }
952
- }
953
- async function submitTheaterFailure(api, ws, params) {
954
- const content = `[${params.stage}] ${params.error}`;
955
- try {
956
- await submitTheaterResult(api, ws, {
957
- sessionId: params.sessionId,
958
- memberKey: params.memberKey,
959
- content,
960
- agentId: params.agentId,
961
- agentStatus: "FAILED",
962
- logId: params.logId
963
- });
964
- return true;
965
- } catch (submitErr) {
966
- console.error(
967
- "[apm] \u65E0\u6CD5\u5C06\u5931\u8D25\u72B6\u6001\u5199\u5165\u5267\u573A\u4F1A\u8BDD:",
968
- extractErrorMessage(submitErr)
969
- );
970
- return false;
971
- }
972
- }
973
- function logTheaterJobError(stage, err, meta) {
974
- const msg = extractErrorMessage(err);
975
- const ctx = [
976
- meta?.sessionId ? `session=${meta.sessionId}` : "",
977
- meta?.memberKey ? `member=${meta.memberKey}` : ""
978
- ].filter(Boolean).join(" ");
979
- console.error(
980
- `[apm] \u5267\u573A THEATER_JOB \u5931\u8D25 \xB7 \u9636\u6BB5=${stage}${ctx ? ` \xB7 ${ctx}` : ""} \xB7 ${msg}`
981
- );
982
- if (isUnauthorizedMessage(msg)) {
983
- console.error("[apm] \u63D0\u793A: \u8BF7\u6267\u884C apm login \u91CD\u65B0\u767B\u5F55\u540E\u518D\u8BD5");
984
- }
985
- }
986
-
987
- // src/commands/connect.ts
988
- var DEFAULT_AGENT_MODEL_ID = "default";
989
- function resolveAgentModelId(model) {
990
- const id = model?.trim();
991
- return id || DEFAULT_AGENT_MODEL_ID;
992
- }
993
- async function createAgentForJob(payload) {
994
- const options = {
995
- apiKey: payload.apiKey,
996
- model: { id: resolveAgentModelId(payload.model) },
997
- local: { cwd: payload.cwd }
998
- };
999
- if (payload.agentId) {
1000
- return Agent.resume(payload.agentId, options);
1001
- }
1002
- return Agent.create(options);
1003
- }
1004
- async function runAgentStream(payload, session, agent) {
1005
- const run = await agent.send(payload.prompt);
1006
- let failed = false;
1007
- let failReason = "";
1008
- for await (const event of run.stream()) {
1009
- if (event.type === "status") {
1010
- console.log(`[Status]`, event.message || event.status);
1011
- if (event.status === "ERROR") {
1012
- failed = true;
1013
- failReason = event.message?.trim() ?? "";
1014
- console.error("[apm] Agent \u6267\u884C\u5931\u8D25:", failReason || "(\u65E0\u5931\u8D25\u539F\u56E0)");
1015
- break;
1016
- }
1017
- continue;
1018
- }
1019
- if (event.type === "assistant") {
1020
- const output = event.message.content[0].text;
1021
- console.log(`[Output]`, output);
1022
- session.addEvent(event);
1023
- continue;
1024
- }
1025
- if (event.type === "thinking") {
1026
- console.log(`[Thinking]`, event.text);
1027
- continue;
1028
- }
1029
- if (event.type === "tool_call") {
1030
- if (event.status === "completed" || event.status === "error") {
1031
- console.log(`[ToolCall(${event.call_id}) Result]`);
1032
- }
1033
- session.addEvent(event);
1034
- continue;
1035
- }
1036
- if (event.type === "task") {
1037
- console.log(`[Task:${event.status}]`);
1038
- session.addEvent(event);
1039
- continue;
1040
- }
1041
- if (event.type === "request") {
1042
- console.log(JSON.stringify(event, null, 2));
1043
- session.addEvent(event);
1044
- continue;
1045
- }
1046
- console.log("\u672A\u77E5\u4E8B\u4EF6:", JSON.stringify(event, null, 2));
1047
- }
1048
- return { agentId: run.agentId, failed, failReason };
1049
- }
1050
- async function handleTheaterJob(api, ws, payload) {
1051
- const sessionId = payload.theaterSessionId.trim();
1052
- const memberKey = payload.memberKey.trim();
1053
- const logId = payload.logId?.trim() || void 0;
1054
- if (!sessionId) {
1055
- throw new Error("THEATER_JOB \u7F3A\u5C11 theaterSessionId");
1056
- }
1057
- if (!memberKey) {
1058
- throw new Error("THEATER_JOB \u7F3A\u5C11 memberKey");
1059
- }
1060
- console.log("[apm] \u5267\u573A\u4F1A\u8BDD:", sessionId, "\u6210\u5458:", memberKey);
1061
- const apmRoot = join7(payload.cwd, ".apm");
1062
- let agentId = payload.agentId?.trim() || void 0;
1063
- try {
1064
- await reportTheaterProgress(api, ws, { sessionId, memberKey, logId });
1065
- } catch (err) {
1066
- logTheaterJobError("\u4E0A\u62A5\u6267\u884C\u8FDB\u5EA6", err, { sessionId, memberKey });
1067
- await submitTheaterFailure(api, ws, {
1068
- sessionId,
1069
- memberKey,
1070
- logId,
1071
- agentId,
1072
- stage: "\u4E0A\u62A5\u8FDB\u5EA6",
1073
- error: extractErrorMessage(err)
1074
- });
1075
- return;
1076
- }
1077
- try {
1078
- await runPullTheaterSession(sessionId, apmRoot);
1079
- } catch (pullErr) {
1080
- logTheaterJobError("\u62C9\u53D6\u4F1A\u8BDD\u6587\u6863", pullErr, { sessionId, memberKey });
1081
- await submitTheaterFailure(api, ws, {
1082
- sessionId,
1083
- memberKey,
1084
- logId,
1085
- agentId,
1086
- stage: "\u62C9\u53D6\u4F1A\u8BDD\u6587\u6863",
1087
- error: extractErrorMessage(pullErr)
1088
- });
1089
- return;
1090
- }
1091
- try {
1092
- const eventSession = new EventSession(payload.prompt);
1093
- const agent = await createAgentForJob(payload);
1094
- const runResult = await runAgentStream(payload, eventSession, agent);
1095
- agentId = runResult.agentId;
1096
- const assistantText = eventSession.getAssistantText();
1097
- const content = runResult.failed ? runResult.failReason || assistantText || "\u672A\u77E5\u9519\u8BEF" : assistantText || "[Agent \u672A\u4EA7\u751F\u6587\u672C\u8F93\u51FA]";
1098
- await submitTheaterResult(api, ws, {
1099
- sessionId,
1100
- memberKey,
1101
- content,
1102
- agentId,
1103
- agentStatus: runResult.failed ? "FAILED" : "SUCCESS",
1104
- logId
1105
- });
1106
- console.log(
1107
- "[apm] \u5267\u573A\u6210\u5458\u56DE\u590D\u5DF2\u63D0\u4EA4",
1108
- runResult.failed ? "(\u6267\u884C\u5931\u8D25)" : ""
1109
- );
1110
- } catch (err) {
1111
- logTheaterJobError("\u6267\u884C\u6216\u63D0\u4EA4\u56DE\u590D", err, { sessionId, memberKey });
1112
- const reported = await submitTheaterFailure(api, ws, {
1113
- sessionId,
1114
- memberKey,
1115
- logId,
1116
- agentId,
1117
- stage: "\u6267\u884C\u4EFB\u52A1",
1118
- error: extractErrorMessage(err)
1119
- });
1120
- if (reported) {
1121
- console.error("[apm] \u5DF2\u5C06\u5931\u8D25\u539F\u56E0\u5199\u5165\u5267\u573A\u4F1A\u8BDD\uFF0C\u53EF\u5728\u7F51\u9875\u67E5\u770B");
1122
- }
1123
- }
1124
- }
1125
- async function handleWorkflowJob(api, payload) {
1126
- const apmRoot = join7(payload.cwd, ".apm");
1127
- console.log("[apm] ROOT:", apmRoot);
1128
- console.log("[apm] workflow node:", payload.nodeId);
1129
- try {
1130
- await runPull(payload.requirementId, apmRoot);
1131
- } catch (pullErr) {
1132
- console.error("[apm] apm pull \u5931\u8D25:", pullErr);
1133
- throw pullErr;
1134
- }
1135
- const agent = await createAgentForJob(payload);
1136
- await api.cliRequirements.updateDevStatus({
1137
- requirementId: payload.requirementId,
1138
- status: "WORKING"
1139
- });
1140
- await api.spaceWorkflow.aiStartNode({
1141
- requirementId: payload.requirementId,
1142
- nodeId: payload.nodeId,
1143
- agentId: agent.agentId
1144
- });
1145
- const session = new EventSession(payload.prompt);
1146
- const { agentId, failed, failReason } = await runAgentStream(
1147
- payload,
1148
- session,
1149
- agent
1150
- );
1151
- await api.cliRequirements.updateDevStatus({
1152
- requirementId: payload.requirementId,
1153
- status: "IDLE"
1154
- });
1155
- session.writeToFile(payload.cwd, payload.requirementId, agentId);
1156
- if (failed) {
1157
- const failResult = await api.spaceWorkflow.aiFailNode({
1158
- requirementId: payload.requirementId,
1159
- nodeId: payload.nodeId,
1160
- error: failReason
1161
- });
1162
- console.log("[apm] \u5DE5\u4F5C\u6D41\u8282\u70B9\u72B6\u6001:", failResult.nodeStatus);
1163
- return;
1164
- }
1165
- console.log("[Done]");
1166
- try {
1167
- await runUploadArtifact(payload.requirementId, apmRoot);
1168
- } catch (pullErr) {
1169
- console.error("[apm] apm upload-artifact \u5931\u8D25:", pullErr);
1170
- throw pullErr;
1171
- }
1172
- const confirmResult = await api.spaceWorkflow.aiConfirmNode({
1173
- requirementId: payload.requirementId,
1174
- nodeId: payload.nodeId
223
+ // src/api/client.ts
224
+ function createApmApiClient(cfg) {
225
+ const baseURL = `${cfg.baseUrl.trim().replace(/\/+$/, "")}/api`;
226
+ return createApiClient(requestConfig, {
227
+ baseURL,
228
+ getToken: () => cfg.token || void 0,
229
+ successCodes: [0],
230
+ unauthorizedCodes: [401]
1175
231
  });
1176
- console.log("[apm] \u5DE5\u4F5C\u6D41\u8282\u70B9\u72B6\u6001:", confirmResult.nodeStatus);
1177
- }
1178
- function runConnect(opts) {
1179
- void (async () => {
1180
- const cfg = await ensureApmConfig();
1181
- const server = opts.server?.trim();
1182
- if (server) {
1183
- cfg.baseUrl = server.replace(/\/+$/, "");
1184
- await writeApmConfig(cfg);
1185
- }
1186
- if (!cfg.token) {
1187
- console.error(
1188
- "[apm] \u672A\u68C0\u6D4B\u5230 token\uFF0C\u8BF7\u5148\u6267\u884C: apm login --email ... --password ..."
1189
- );
1190
- console.error(`[apm] \u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84: ${APM_CONFIG_PATH}`);
1191
- process.exit(1);
1192
- }
1193
- const api = createApmApiClient(cfg);
1194
- const url = buildAgentWsUrl(cfg.baseUrl, cfg.token);
1195
- console.error(`[apm] \u8FDE\u63A5 ${url.replace(cfg.token, "<token>")} \u2026`);
1196
- const ws = new WebSocket(url);
1197
- ws.on("open", () => {
1198
- console.error("[apm] \u5DF2\u8FDE\u63A5");
1199
- const sendHeartbeat = () => {
1200
- const frame = {
1201
- type: "HEARTBEAT",
1202
- msg_id: randomUUID2(),
1203
- ts: (/* @__PURE__ */ new Date()).toISOString(),
1204
- payload: {}
1205
- };
1206
- ws.send(JSON.stringify(frame));
1207
- };
1208
- sendHeartbeat();
1209
- const interval = setInterval(sendHeartbeat, 15e3);
1210
- ws.on("close", () => clearInterval(interval));
1211
- });
1212
- ws.on("message", async (data) => {
1213
- const text = typeof data === "string" ? data : data.toString();
1214
- let msg;
1215
- try {
1216
- msg = JSON.parse(text);
1217
- } catch (err) {
1218
- console.error(
1219
- "[apm] \u65E0\u6CD5\u89E3\u6790 WebSocket \u6D88\u606F:",
1220
- text,
1221
- err.message
1222
- );
1223
- return;
1224
- }
1225
- try {
1226
- const jobType = msg.type;
1227
- if (jobType === "THEATER_JOB") {
1228
- await handleTheaterJob(api, ws, msg.payload);
1229
- return;
1230
- }
1231
- if (jobType === "JOB") {
1232
- await handleWorkflowJob(api, msg.payload);
1233
- return;
1234
- }
1235
- } catch (err) {
1236
- const jobType = msg.type;
1237
- const label = jobType === "THEATER_JOB" ? "\u5267\u573A THEATER_JOB" : jobType === "JOB" ? "\u5DE5\u4F5C\u6D41 JOB" : String(jobType);
1238
- console.error(`[apm] ${label} \u5904\u7406\u5931\u8D25:`, extractErrorMessage(err));
1239
- }
1240
- });
1241
- ws.on("error", (err) => {
1242
- console.error("[apm] WebSocket \u9519\u8BEF:", err.message);
1243
- });
1244
- ws.on("close", (code, reason) => {
1245
- console.error(`[apm] \u8FDE\u63A5\u5173\u95ED code=${code} reason=${reason.toString()}`);
1246
- process.exit(code === 1e3 ? 0 : 1);
1247
- });
1248
- })();
1249
- }
1250
-
1251
- // src/commands/init.ts
1252
- import { join as join8 } from "path";
1253
- import { readFileSync as readFileSync4, writeFileSync as writeFileSync5 } from "fs";
1254
- async function runInit(name) {
1255
- await ensureWorkspaceApmDirForInit();
1256
- await copyTemplateFiles(WORKSPACE_APM_DIR);
1257
- if (name) {
1258
- const apmConfigPath = join8(WORKSPACE_APM_DIR, "apm.config.json");
1259
- const config = readFileSync4(apmConfigPath, "utf8");
1260
- const configJson = JSON.parse(config);
1261
- configJson.name = name;
1262
- writeFileSync5(apmConfigPath, JSON.stringify(configJson, null, 2), "utf8");
1263
- }
1264
232
  }
1265
233
 
1266
234
  // src/commands/login.ts
1267
- import { ApiError } from "listpage-http";
1268
235
  async function runLogin(opts) {
1269
236
  const baseUrl = (opts.server?.trim() || process.env.AI_PM_SERVER?.trim() || DEFAULT_BASE_URL).replace(/\/+$/, "");
1270
237
  const api = createApmApiClient({
@@ -1310,15 +277,15 @@ async function runLogin(opts) {
1310
277
 
1311
278
  // src/commands/branch.ts
1312
279
  import { execFile } from "child_process";
1313
- import { resolve as resolve3 } from "path";
280
+ import { resolve as resolve2 } from "path";
1314
281
  import { promisify } from "util";
1315
282
  var execFileAsync = promisify(execFile);
1316
- async function fetchBaselineBranchFromApi(requirementId, cwd) {
283
+ async function fetchBaselineBranchFromApi(sessionId, cwd) {
1317
284
  const cfg = await ensureLoggedConfig();
1318
285
  const api = createApmApiClient(cfg);
1319
- const workdirPath = resolve3(cwd);
1320
- const res = await api.cliRequirements.branchBaseline({
1321
- requirementId,
286
+ const workdirPath = resolve2(cwd);
287
+ const res = await api.cli.branchBaseline({
288
+ sessionId,
1322
289
  workdirPath
1323
290
  });
1324
291
  if (!res.repositoryId) {
@@ -1395,17 +362,26 @@ async function localBranchExists(cwd, branch) {
1395
362
  return false;
1396
363
  }
1397
364
  }
1398
- async function runBranch(requirementId, options = {}) {
365
+ async function commitWorkingTreeIfDirty(cwd, message) {
366
+ await ensureGitRepo(cwd);
367
+ if (!await isWorkingTreeDirty(cwd)) {
368
+ return false;
369
+ }
370
+ const commitMessage = message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${await getCurrentBranch(cwd)})`;
371
+ await execGit(cwd, ["add", "-A"]);
372
+ await execGit(cwd, ["commit", "-m", commitMessage]);
373
+ console.log(`[apm] \u5DF2\u63D0\u4EA4\u5DE5\u4F5C\u533A\u53D8\u66F4: ${commitMessage}`);
374
+ return true;
375
+ }
376
+ async function ensureFeatureBranch(branch, options, fetchBaseline) {
1399
377
  const cwd = options.cwd ?? process.cwd();
1400
- const branch = branchNameForRequirement(requirementId);
1401
378
  const commitMessage = options.message?.trim() || `chore(apm): \u540C\u6B65\u5DE5\u4F5C\u533A (${branch})`;
1402
379
  await ensureGitRepo(cwd);
1403
380
  const current = await getCurrentBranch(cwd);
1404
381
  const dirty = await isWorkingTreeDirty(cwd);
1405
382
  if (dirty) {
1406
383
  if (current === branch) {
1407
- await execGit(cwd, ["add", "-A"]);
1408
- await execGit(cwd, ["commit", "-m", commitMessage]);
384
+ await commitWorkingTreeIfDirty(cwd, commitMessage);
1409
385
  } else {
1410
386
  await execGit(cwd, [
1411
387
  "stash",
@@ -1433,10 +409,7 @@ async function runBranch(requirementId, options = {}) {
1433
409
  if (hasLocal) {
1434
410
  await execGit(cwd, ["checkout", branch]);
1435
411
  } else {
1436
- const baselineBranch = await fetchBaselineBranchFromApi(
1437
- requirementId,
1438
- cwd
1439
- );
412
+ const baselineBranch = await fetchBaseline();
1440
413
  await execGit(cwd, ["fetch", "origin", baselineBranch]);
1441
414
  await execGit(cwd, [
1442
415
  "checkout",
@@ -1451,32 +424,129 @@ async function runBranch(requirementId, options = {}) {
1451
424
  console.log(`[apm] \u5DF2\u5C31\u7EEA\u5206\u652F ${branch}`);
1452
425
  return branch;
1453
426
  }
427
+ async function runBranch(sessionId, options = {}) {
428
+ const trimmedSessionId = sessionId.trim();
429
+ if (!trimmedSessionId) {
430
+ console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
431
+ process.exit(1);
432
+ }
433
+ const cfg = await ensureLoggedConfig();
434
+ const api = createApmApiClient(cfg);
435
+ const cwd = options.cwd ?? process.cwd();
436
+ const baseline = await api.cli.branchBaseline({
437
+ sessionId: trimmedSessionId,
438
+ workdirPath: resolve2(cwd)
439
+ });
440
+ const branch = branchNameForRequirement(baseline.requirementId);
441
+ return ensureFeatureBranch(
442
+ branch,
443
+ options,
444
+ () => fetchBaselineBranchFromApi(trimmedSessionId, cwd)
445
+ );
446
+ }
447
+
448
+ // src/commands/pull.ts
449
+ import { writeFileSync as writeFileSync4 } from "fs";
450
+ import { join as join5 } from "path";
451
+ import { stringify as yamlStringify } from "yaml";
452
+
453
+ // src/commands/sync-session-attachments.ts
454
+ import { writeFileSync as writeFileSync3 } from "fs";
455
+ import { join as join4 } from "path";
456
+ async function downloadAttachment(cfg, sessionId, attachmentId) {
457
+ const base = cfg.baseUrl.trim().replace(/\/+$/, "");
458
+ const q = new URLSearchParams({ sessionId, attachmentId });
459
+ const url = `${base}/api/cli/attachments/file?${q.toString()}`;
460
+ const res = await fetch(url, {
461
+ headers: { Authorization: `Bearer ${cfg.token}` }
462
+ });
463
+ if (!res.ok) {
464
+ throw new Error(
465
+ `[apm] \u4E0B\u8F7D\u9644\u4EF6\u5931\u8D25 (${res.status}): attachmentId=${attachmentId}`
466
+ );
467
+ }
468
+ return Buffer.from(await res.arrayBuffer());
469
+ }
470
+ async function syncSessionAttachments(cfg, sessionId, attachments, apmRoot) {
471
+ if (attachments.length === 0) return;
472
+ const dir = join4(sessionDir(sessionId, apmRoot), SESSION_ATTACHMENTS_SUBDIR);
473
+ await ensureDirExists(dir);
474
+ for (const item of attachments) {
475
+ const dest = join4(dir, item.name);
476
+ const buffer = await downloadAttachment(cfg, sessionId, item.id);
477
+ writeFileSync3(dest, buffer);
478
+ console.log(
479
+ `[apm] \u5DF2\u4E0B\u8F7D\u9644\u4EF6: ${SESSION_ATTACHMENTS_SUBDIR}/${item.name}`
480
+ );
481
+ }
482
+ }
1454
483
 
1455
- // src/commands/refine.ts
1456
- import { readFileSync as readFileSync5 } from "fs";
1457
- import { join as join9 } from "path";
1458
- async function runRefine(requirementId) {
484
+ // src/commands/pull.ts
485
+ async function runPull(sessionId, apmRoot) {
486
+ const trimmedId = sessionId.trim();
487
+ if (!trimmedId) {
488
+ console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
489
+ process.exit(1);
490
+ }
1459
491
  const cfg = await ensureLoggedConfig();
1460
- const filePath = join9(WORKSPACE_APM_DIR, "workitems", requirementId, "prd.md");
1461
- const content = readFileSync5(filePath, "utf8");
1462
492
  const api = createApmApiClient(cfg);
1463
- const data = await api.cliRequirements.refine({ requirementId, content });
1464
- console.log(JSON.stringify(data, null, 2));
493
+ const [detail, members, documents, attachments] = await Promise.all([
494
+ api.cli.sessionDetail({ sessionId: trimmedId }),
495
+ api.cli.sessionMembers({ sessionId: trimmedId }),
496
+ api.cli.listDocuments({ sessionId: trimmedId }),
497
+ api.cli.listAttachments({ sessionId: trimmedId })
498
+ ]);
499
+ const dir = sessionDir(trimmedId, apmRoot);
500
+ const docsDir = sessionDocsDir(trimmedId, apmRoot);
501
+ await ensureDirExists(docsDir);
502
+ writeFileSync4(
503
+ sessionRulePath(trimmedId, apmRoot),
504
+ detail.description ?? "",
505
+ "utf8"
506
+ );
507
+ for (const doc of documents) {
508
+ const fileName = documentLocalFileName(doc.name);
509
+ writeFileSync4(join5(docsDir, fileName), doc.content ?? "", "utf8");
510
+ }
511
+ const sessionYaml = yamlStringify(
512
+ {
513
+ name: detail.title,
514
+ description: "./RULE.md",
515
+ members: members.map((m) => ({
516
+ name: m.displayName,
517
+ position: m.position,
518
+ system_persona: m.systemPersona,
519
+ skills: []
520
+ })),
521
+ skills: [],
522
+ attachments: attachments.map((a) => ({ name: a.name }))
523
+ },
524
+ { lineWidth: 0 }
525
+ );
526
+ writeFileSync4(
527
+ sessionYamlPath(trimmedId, apmRoot),
528
+ sessionYaml.endsWith("\n") ? sessionYaml : `${sessionYaml}
529
+ `,
530
+ "utf8"
531
+ );
532
+ await syncSessionAttachments(cfg, trimmedId, attachments, apmRoot);
533
+ console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u5DE5\u4F5C\u533A: ${dir}`);
534
+ return dir;
1465
535
  }
1466
536
 
1467
537
  // src/commands/update.ts
1468
538
  import { spawnSync } from "child_process";
1469
539
 
1470
540
  // src/version.ts
1471
- import { readFileSync as readFileSync6 } from "fs";
1472
- import { dirname as dirname3, join as join10 } from "path";
541
+ import { readFileSync as readFileSync3 } from "fs";
542
+ import { dirname as dirname2, join as join6 } from "path";
1473
543
  import { fileURLToPath as fileURLToPath2 } from "url";
1474
544
  var CLI_PACKAGE_NAME = "ai-project-manage-cli";
1475
545
  function readCliVersion() {
1476
546
  try {
1477
- const dir = dirname3(fileURLToPath2(import.meta.url));
1478
- const pkgPath = join10(dir, "..", "package.json");
1479
- const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
547
+ const dir = dirname2(fileURLToPath2(import.meta.url));
548
+ const pkgPath = join6(dir, "..", "package.json");
549
+ const pkg = JSON.parse(readFileSync3(pkgPath, "utf8"));
1480
550
  return pkg.version ?? "0.0.0";
1481
551
  } catch {
1482
552
  return "0.0.0";
@@ -1543,147 +613,139 @@ async function runUpdate() {
1543
613
  }
1544
614
 
1545
615
  // src/commands/update-skills.ts
1546
- import { cpSync as cpSync2, existsSync as existsSync4, rmSync, statSync as statSync3 } from "fs";
1547
- import { join as join11 } from "path";
1548
- var TEMPLATE_SKILLS_DIR = join11(CLI_TEMPLATE_DIR, "skills");
616
+ import { existsSync as existsSync2, mkdirSync as mkdirSync3, rmSync, statSync as statSync2, writeFileSync as writeFileSync5 } from "fs";
617
+ import { join as join7 } from "path";
618
+ function sanitizeDirName(name) {
619
+ const trimmed = name.trim();
620
+ if (!trimmed) return "skill";
621
+ return trimmed.replace(/[/\\:*?"<>|]/g, "_");
622
+ }
1549
623
  async function runUpdateSkills() {
1550
- const apmDir = WORKSPACE_APM_DIR;
1551
- if (!existsSync4(apmDir)) {
624
+ const apmDir = workspaceApmDir();
625
+ if (!existsSync2(apmDir)) {
1552
626
  console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1553
627
  process.exit(1);
1554
628
  }
1555
- const apmStat = statSync3(apmDir);
629
+ const apmStat = statSync2(apmDir);
1556
630
  if (!apmStat.isDirectory()) {
1557
631
  throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
1558
632
  }
1559
- let templateStat;
1560
- try {
1561
- templateStat = statSync3(TEMPLATE_SKILLS_DIR);
1562
- } catch {
1563
- throw new Error(`[apm] \u5185\u7F6E\u6280\u80FD\u6A21\u677F\u4E0D\u5B58\u5728: ${TEMPLATE_SKILLS_DIR}`);
1564
- }
1565
- if (!templateStat.isDirectory()) {
1566
- throw new Error(`[apm] \u5185\u7F6E\u6280\u80FD\u6A21\u677F\u4E0D\u662F\u76EE\u5F55: ${TEMPLATE_SKILLS_DIR}`);
1567
- }
1568
- const skillsDir = join11(apmDir, "skills");
1569
- if (existsSync4(skillsDir)) {
633
+ const cfg = await ensureLoggedConfig();
634
+ const api = createApmApiClient(cfg);
635
+ const { list } = await api.cli.listSkills({});
636
+ const skillsDir = join7(apmDir, "skills");
637
+ if (existsSync2(skillsDir)) {
1570
638
  rmSync(skillsDir, { recursive: true, force: true });
1571
639
  }
1572
- await ensureDirExists(apmDir);
1573
- cpSync2(TEMPLATE_SKILLS_DIR, skillsDir, { recursive: true });
1574
- console.log("[apm] \u5DF2\u66F4\u65B0 .apm/skills");
1575
- }
1576
-
1577
- // src/commands/update-theater-skills.ts
1578
- import { cpSync as cpSync3, existsSync as existsSync5, rmSync as rmSync2, statSync as statSync4 } from "fs";
1579
- import { join as join12 } from "path";
1580
- var TEMPLATE_THEATER_SKILLS_DIR = join12(CLI_TEMPLATE_DIR, "theater-skills");
1581
- async function runUpdateTheaterSkills() {
1582
- const apmDir = WORKSPACE_APM_DIR;
1583
- if (!existsSync5(apmDir)) {
1584
- console.error("[apm] \u672A\u627E\u5230 .apm \u76EE\u5F55\uFF0C\u8BF7\u5148\u6267\u884C apm init");
1585
- process.exit(1);
1586
- }
1587
- const apmStat = statSync4(apmDir);
1588
- if (!apmStat.isDirectory()) {
1589
- throw new Error(`[apm] \u8DEF\u5F84\u5DF2\u5B58\u5728\u4F46\u4E0D\u662F\u76EE\u5F55: ${apmDir}`);
1590
- }
1591
- let templateStat;
1592
- try {
1593
- templateStat = statSync4(TEMPLATE_THEATER_SKILLS_DIR);
1594
- } catch {
1595
- throw new Error(
1596
- `[apm] \u5185\u7F6E\u5267\u573A\u6280\u80FD\u6A21\u677F\u4E0D\u5B58\u5728: ${TEMPLATE_THEATER_SKILLS_DIR}`
1597
- );
640
+ mkdirSync3(skillsDir, { recursive: true });
641
+ for (const skill of list) {
642
+ const dirName = sanitizeDirName(skill.name);
643
+ const skillDir = join7(skillsDir, dirName);
644
+ mkdirSync3(skillDir, { recursive: true });
645
+ writeFileSync5(join7(skillDir, "SKILL.md"), skill.content ?? "", "utf8");
646
+ console.log(`[apm] \u5DF2\u5199\u5165\u6280\u80FD: skills/${dirName}/SKILL.md`);
1598
647
  }
1599
- if (!templateStat.isDirectory()) {
1600
- throw new Error(
1601
- `[apm] \u5185\u7F6E\u5267\u573A\u6280\u80FD\u6A21\u677F\u4E0D\u662F\u76EE\u5F55: ${TEMPLATE_THEATER_SKILLS_DIR}`
1602
- );
1603
- }
1604
- const theaterSkillsDir = join12(apmDir, "theater-skills");
1605
- if (existsSync5(theaterSkillsDir)) {
1606
- rmSync2(theaterSkillsDir, { recursive: true, force: true });
1607
- }
1608
- await ensureDirExists(apmDir);
1609
- cpSync3(TEMPLATE_THEATER_SKILLS_DIR, theaterSkillsDir, { recursive: true });
1610
- console.log("[apm] \u5DF2\u66F4\u65B0 .apm/theater-skills");
648
+ console.log(`[apm] \u5DF2\u66F4\u65B0 ${list.length} \u4E2A\u6280\u80FD\u5230 .apm/skills`);
1611
649
  }
1612
650
 
1613
- // src/commands/sync-session-document.ts
1614
- import { existsSync as existsSync6, readFileSync as readFileSync7 } from "fs";
1615
- var DEFAULT_RELATIVE_PATH = "docs/prd.md";
1616
- async function runSyncSessionDocument(sessionId, options) {
651
+ // src/commands/sync-document.ts
652
+ import { existsSync as existsSync3, readFileSync as readFileSync4 } from "fs";
653
+ async function runSyncDocument(sessionId, options) {
1617
654
  const trimmedSessionId = sessionId.trim();
1618
655
  if (!trimmedSessionId) {
1619
656
  console.error("[apm] sessionId \u4E0D\u80FD\u4E3A\u7A7A");
1620
657
  process.exit(1);
1621
658
  }
1622
- const relativePath = (options?.file?.trim() || DEFAULT_RELATIVE_PATH).replace(
1623
- /\\/g,
1624
- "/"
1625
- );
1626
- const apmRoot = options?.apmRoot ?? WORKSPACE_APM_DIR;
1627
- const absPath = theaterSessionDocumentPath(
1628
- trimmedSessionId,
1629
- relativePath,
1630
- apmRoot
1631
- );
1632
- if (!existsSync6(absPath)) {
1633
- console.error(`[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}
1634
- \u8BF7\u5148\u521B\u5EFA\u8BE5\u6587\u4EF6\u540E\u518D\u540C\u6B65`);
659
+ const fileArg = options.file?.trim();
660
+ if (!fileArg) {
661
+ console.error("[apm] \u8BF7\u6307\u5B9A --file <\u672C\u5730\u8DEF\u5F84>");
1635
662
  process.exit(1);
1636
663
  }
1637
- const content = readFileSync7(absPath, "utf8");
664
+ const absPath = resolveCwdPath(fileArg);
665
+ if (!existsSync3(absPath)) {
666
+ console.error(`[apm] \u6587\u6863\u4E0D\u5B58\u5728: ${absPath}`);
667
+ process.exit(1);
668
+ }
669
+ const content = readFileSync4(absPath, "utf8");
670
+ const name = documentPlatformName(absPath);
1638
671
  const cfg = await ensureLoggedConfig();
1639
672
  const api = createApmApiClient(cfg);
1640
- const data = await api.theaterSessionArtifact.upsert({
673
+ const doc = await api.cli.upsertDocument({
1641
674
  sessionId: trimmedSessionId,
1642
- fileName: relativePath,
675
+ name,
1643
676
  content
1644
677
  });
1645
- console.log(`[apm] \u5DF2\u540C\u6B65\u4F1A\u8BDD\u6587\u6863: ${relativePath} (artifactId=${data.id})`);
678
+ console.log(`[apm] \u5DF2\u540C\u6B65\u6587\u6863: ${name} (id=${doc.id})`);
1646
679
  }
1647
680
 
1648
- // src/commands/update-dev-status.ts
1649
- async function runUpdateDevStatus(requirementId, status) {
681
+ // src/commands/append-message.ts
682
+ import { existsSync as existsSync4, readFileSync as readFileSync5 } from "fs";
683
+ async function runAppendMessage(options) {
684
+ const messageId = options.id?.trim();
685
+ if (!messageId) {
686
+ console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
687
+ process.exit(1);
688
+ }
689
+ const fileArg = options.file?.trim();
690
+ if (!fileArg) {
691
+ console.error("[apm] \u8BF7\u6307\u5B9A --file <\u672C\u5730\u8DEF\u5F84>");
692
+ process.exit(1);
693
+ }
694
+ const absPath = resolveCwdPath(fileArg);
695
+ if (!existsSync4(absPath)) {
696
+ console.error(`[apm] \u6587\u4EF6\u4E0D\u5B58\u5728: ${absPath}`);
697
+ process.exit(1);
698
+ }
699
+ const content = readFileSync5(absPath, "utf8");
1650
700
  const cfg = await ensureLoggedConfig();
1651
701
  const api = createApmApiClient(cfg);
1652
- const data = await api.cliRequirements.updateDevStatus({
1653
- requirementId,
1654
- status
1655
- });
1656
- console.log(JSON.stringify(data, null, 2));
702
+ await api.cli.appendMessageContent({ id: messageId, content });
703
+ console.log(`[apm] \u5DF2\u8FFD\u52A0\u6D88\u606F\u5185\u5BB9: ${messageId}`);
1657
704
  }
1658
705
 
1659
- // src/commands/update-status.ts
1660
- async function runUpdateStatus(requirementId, status) {
706
+ // src/commands/update-message-status.ts
707
+ var VALID_STATUSES = [
708
+ "CREATED",
709
+ "TYPING",
710
+ "SUCCESS",
711
+ "FAILED"
712
+ ];
713
+ async function runUpdateMessageStatus(options) {
714
+ const messageId = options.id?.trim();
715
+ const status = options.status?.trim().toUpperCase();
716
+ if (!messageId) {
717
+ console.error("[apm] \u8BF7\u6307\u5B9A --id <messageId>");
718
+ process.exit(1);
719
+ }
720
+ if (!VALID_STATUSES.includes(status)) {
721
+ console.error(
722
+ `[apm] \u65E0\u6548\u72B6\u6001: ${options.status}\uFF0C\u53EF\u9009: ${VALID_STATUSES.join(", ")}`
723
+ );
724
+ process.exit(1);
725
+ }
1661
726
  const cfg = await ensureLoggedConfig();
1662
727
  const api = createApmApiClient(cfg);
1663
- const data = await api.cliRequirements.updateStatus({
1664
- requirementId,
1665
- status
1666
- });
1667
- console.log(JSON.stringify(data, null, 2));
728
+ await api.cli.updateMessageStatus({ id: messageId, status });
729
+ console.log(`[apm] \u5DF2\u66F4\u65B0\u6D88\u606F\u72B6\u6001: ${messageId} \u2192 ${status}`);
1668
730
  }
1669
731
 
1670
732
  // src/commands/deploy/backend.ts
1671
733
  import path5 from "node:path";
1672
734
 
1673
735
  // src/commands/deploy/internal/apm-config.ts
1674
- import { existsSync as existsSync7, readFileSync as readFileSync8 } from "node:fs";
1675
- import { resolve as resolve4 } from "node:path";
736
+ import { existsSync as existsSync5, readFileSync as readFileSync6 } from "node:fs";
737
+ import { resolve as resolve3 } from "node:path";
1676
738
  function loadApmConfig(options) {
1677
- const p = resolve4(
739
+ const p = resolve3(
1678
740
  process.cwd(),
1679
- options?.configPath ?? resolve4(WORKSPACE_APM_DIR, "apm.config.json")
741
+ options?.configPath ?? resolve3(workspaceApmDir(), "apm.config.json")
1680
742
  );
1681
- if (!existsSync7(p)) {
743
+ if (!existsSync5(p)) {
1682
744
  console.error(`\u672A\u627E\u5230\u914D\u7F6E\u6587\u4EF6\uFF1A${p}`);
1683
745
  process.exit(1);
1684
746
  }
1685
747
  try {
1686
- const raw = readFileSync8(p, "utf8");
748
+ const raw = readFileSync6(p, "utf8");
1687
749
  return JSON.parse(raw);
1688
750
  } catch (e) {
1689
751
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -1710,9 +772,7 @@ function reqTopLevelName(cfg) {
1710
772
  function reqBackendPositiveInt(v, field) {
1711
773
  const n = Number(v);
1712
774
  if (!Number.isFinite(n) || !Number.isInteger(n) || n < 1) {
1713
- console.error(
1714
- `apm.config.json \u4E2D backendDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`
1715
- );
775
+ console.error(`apm.config.json \u4E2D backendDeploy.${field} \u987B\u4E3A\u6B63\u6574\u6570`);
1716
776
  process.exit(1);
1717
777
  }
1718
778
  return n;
@@ -1782,7 +842,7 @@ import path4 from "node:path";
1782
842
  import Docker from "dockerode";
1783
843
 
1784
844
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
1785
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
845
+ import { existsSync as existsSync6, readFileSync as readFileSync7 } from "node:fs";
1786
846
  import path from "node:path";
1787
847
  function asOptionalTlsBuffer(value) {
1788
848
  if (typeof value !== "string") {
@@ -1794,8 +854,8 @@ function asOptionalTlsBuffer(value) {
1794
854
  if (normalized === "") {
1795
855
  return void 0;
1796
856
  }
1797
- if (existsSync8(normalized)) {
1798
- return readFileSync9(normalized);
857
+ if (existsSync6(normalized)) {
858
+ return readFileSync7(normalized);
1799
859
  }
1800
860
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
1801
861
  if (looksLikePath) {
@@ -1920,17 +980,17 @@ var DockerodeClient = class {
1920
980
  await this.client.getImage(image).remove({ force: true });
1921
981
  }
1922
982
  async pullImage(image, auth) {
1923
- const stream = await new Promise((resolve5, reject) => {
983
+ const stream = await new Promise((resolve4, reject) => {
1924
984
  const pullOptions = auth ? { authconfig: auth } : void 0;
1925
985
  this.client.pull(image, pullOptions, (err, output) => {
1926
986
  if (err || !output) {
1927
987
  reject(err ?? new Error("docker pull \u8FD4\u56DE\u7A7A\u8F93\u51FA"));
1928
988
  return;
1929
989
  }
1930
- resolve5(output);
990
+ resolve4(output);
1931
991
  });
1932
992
  });
1933
- await new Promise((resolve5, reject) => {
993
+ await new Promise((resolve4, reject) => {
1934
994
  this.client.modem.followProgress(
1935
995
  stream,
1936
996
  (err) => {
@@ -1938,7 +998,7 @@ var DockerodeClient = class {
1938
998
  reject(err);
1939
999
  return;
1940
1000
  }
1941
- resolve5();
1001
+ resolve4();
1942
1002
  },
1943
1003
  () => void 0
1944
1004
  );
@@ -2005,7 +1065,7 @@ var DockerodeClient = class {
2005
1065
  var createDockerodeClient = (config) => new DockerodeClient(config);
2006
1066
 
2007
1067
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
2008
- import { existsSync as existsSync9, readFileSync as readFileSync10, statSync as statSync5 } from "node:fs";
1068
+ import { existsSync as existsSync7, readFileSync as readFileSync8, statSync as statSync3 } from "node:fs";
2009
1069
  import path2 from "node:path";
2010
1070
  function stripSurroundingQuotes(value) {
2011
1071
  const t = value.trim();
@@ -2022,10 +1082,10 @@ function loadEnvFromFile(envFilePath) {
2022
1082
  return {};
2023
1083
  }
2024
1084
  const targetPath = path2.resolve(envFilePath);
2025
- if (!existsSync9(targetPath) || !statSync5(targetPath).isFile()) {
1085
+ if (!existsSync7(targetPath) || !statSync3(targetPath).isFile()) {
2026
1086
  return {};
2027
1087
  }
2028
- const raw = readFileSync10(targetPath, "utf-8");
1088
+ const raw = readFileSync8(targetPath, "utf-8");
2029
1089
  const result = {};
2030
1090
  for (const line of raw.split(/\r?\n/)) {
2031
1091
  const normalized = line.trim();
@@ -2196,12 +1256,12 @@ function dockerPushImage(params, cwd) {
2196
1256
  }
2197
1257
 
2198
1258
  // src/commands/deploy/internal/backend-deploy/resolve-dockerfile.ts
2199
- import { existsSync as existsSync10 } from "node:fs";
1259
+ import { existsSync as existsSync8 } from "node:fs";
2200
1260
  import path3 from "node:path";
2201
1261
  function resolveDockerBuildPaths(cwd) {
2202
1262
  const dockerfilePath = path3.join(cwd, "Dockerfile");
2203
1263
  Logger.info(`\u67E5\u627EDockerfile\u6587\u4EF6\uFF0C\u8DEF\u5F84: ${dockerfilePath}`);
2204
- if (!existsSync10(dockerfilePath)) {
1264
+ if (!existsSync8(dockerfilePath)) {
2205
1265
  throw new Error(`Dockerfile \u4E0D\u5B58\u5728\uFF1A${dockerfilePath}`);
2206
1266
  }
2207
1267
  Logger.info("\u2713 Dockerfile \u5B58\u5728");
@@ -2330,16 +1390,16 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
2330
1390
  import path7 from "node:path";
2331
1391
 
2332
1392
  // src/commands/deploy/internal/load-apm-dotenv.ts
2333
- import { existsSync as existsSync11, readFileSync as readFileSync11 } from "node:fs";
2334
- import { join as join13 } from "node:path";
1393
+ import { existsSync as existsSync9, readFileSync as readFileSync9 } from "node:fs";
1394
+ import { join as join8 } from "node:path";
2335
1395
  function loadApmDotEnvIfPresent() {
2336
- const p = join13(WORKSPACE_APM_DIR, ".env");
2337
- if (!existsSync11(p)) {
1396
+ const p = join8(workspaceApmDir(), ".env");
1397
+ if (!existsSync9(p)) {
2338
1398
  return;
2339
1399
  }
2340
1400
  let text;
2341
1401
  try {
2342
- text = readFileSync11(p, "utf8");
1402
+ text = readFileSync9(p, "utf8");
2343
1403
  } catch {
2344
1404
  return;
2345
1405
  }
@@ -2364,14 +1424,14 @@ function loadApmDotEnvIfPresent() {
2364
1424
  }
2365
1425
 
2366
1426
  // src/commands/deploy/internal/minio.ts
2367
- import { statSync as statSync6 } from "node:fs";
1427
+ import { statSync as statSync4 } from "node:fs";
2368
1428
  import { readdir, readFile } from "node:fs/promises";
2369
1429
  import path6 from "node:path";
2370
1430
  import * as Minio from "minio";
2371
1431
  var DEFAULT_MAX_FILE_SIZE_MB = 50;
2372
1432
  async function isDirectoryPath(dir) {
2373
1433
  try {
2374
- const st = statSync6(dir);
1434
+ const st = statSync4(dir);
2375
1435
  return st.isDirectory();
2376
1436
  } catch {
2377
1437
  return false;
@@ -2401,7 +1461,7 @@ async function collectFiles(root) {
2401
1461
  if (e.isDirectory()) {
2402
1462
  await walk(abs, rel);
2403
1463
  } else if (e.isFile()) {
2404
- const st = statSync6(abs);
1464
+ const st = statSync4(abs);
2405
1465
  out.push({
2406
1466
  absPath: abs,
2407
1467
  relativePath: rel.replace(/\\/g, "/"),
@@ -2463,14 +1523,14 @@ var MinioClient = class {
2463
1523
  async deleteObjectsByPrefix(bucket, prefix) {
2464
1524
  const objectsStream = this.inner.listObjectsV2(bucket, prefix, true);
2465
1525
  const keys = [];
2466
- await new Promise((resolve5, reject) => {
1526
+ await new Promise((resolve4, reject) => {
2467
1527
  objectsStream.on("data", (obj) => {
2468
1528
  if (obj.name) {
2469
1529
  keys.push(obj.name);
2470
1530
  }
2471
1531
  });
2472
1532
  objectsStream.on("error", reject);
2473
- objectsStream.on("end", resolve5);
1533
+ objectsStream.on("end", resolve4);
2474
1534
  });
2475
1535
  const chunkSize = 500;
2476
1536
  for (let i = 0; i < keys.length; i += chunkSize) {
@@ -2697,7 +1757,7 @@ function registerDeployCommands(program) {
2697
1757
  function buildProgram() {
2698
1758
  const program = new Command();
2699
1759
  program.name("apm").description(
2700
- `\u5E73\u53F0\u547D\u4EE4\u884C\uFF08Agent \u901A\u9053\u4E0E\u9700\u6C42 CLI\uFF09\u3002
1760
+ `OPC \u5E73\u53F0\u547D\u4EE4\u884C\uFF08\u4F1A\u8BDD\u5DE5\u4F5C\u533A\u4E0E\u7814\u53D1\u81EA\u52A8\u5316\uFF09\u3002
2701
1761
  \u672A\u4F20 --server \u65F6\u4F18\u5148\u4F7F\u7528\u73AF\u5883\u53D8\u91CF AI_PM_SERVER\uFF0C\u5426\u5219\u9ED8\u8BA4 ${DEFAULT_BASE_URL}\u3002`
2702
1762
  ).version(readCliVersion(), "-V, --version", "\u663E\u793A\u7248\u672C\u53F7").helpOption("-h, --help", "\u663E\u793A\u5E2E\u52A9").showHelpAfterError(true);
2703
1763
  program.command("login").description(
@@ -2707,9 +1767,6 @@ function buildProgram() {
2707
1767
  await runLogin(opts);
2708
1768
  }
2709
1769
  );
2710
- program.command("connect").description("\u8FDE\u63A5 WebSocket /ws/agent\uFF0C\u5E76\u6309\u56FA\u5B9A\u95F4\u9694\u53D1\u9001 HEARTBEAT").option("--server <url>", "API \u6839\u5730\u5740\uFF08\u5199\u5165\u672C\u5730\u914D\u7F6E\uFF09").action((opts) => {
2711
- runConnect(opts);
2712
- });
2713
1770
  program.command("init").description("\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u521D\u59CB\u5316 .apm \u6A21\u677F\uFF08\u82E5 .apm \u5DF2\u5B58\u5728\u4E14\u975E\u7A7A\u5219\u62A5\u9519\uFF09").option("--name <name>", "\u5DE5\u4F5C\u76EE\u5F55\u540D\u79F0").action(async (opts) => {
2714
1771
  await runInit(opts.name);
2715
1772
  });
@@ -2718,72 +1775,30 @@ function buildProgram() {
2718
1775
  ).action(async () => {
2719
1776
  await runUpdate();
2720
1777
  });
2721
- program.command("update-skills").description(
2722
- "\u5220\u9664\u5DE5\u4F5C\u533A .apm/skills \u540E\uFF0C\u4ECE\u5F53\u524D CLI \u5185\u7F6E\u6A21\u677F\u91CD\u65B0\u590D\u5236\u6280\u80FD\u76EE\u5F55"
2723
- ).action(async () => {
1778
+ program.command("update-skills").description("\u4ECE\u5E73\u53F0\u62C9\u53D6\u5F53\u524D\u7528\u6237\u53EF\u8BBF\u95EE\u6280\u80FD\u5230 .apm/skills/").action(async () => {
2724
1779
  await runUpdateSkills();
2725
1780
  });
2726
- program.command("update-theater-skills").description(
2727
- "\u5220\u9664\u5DE5\u4F5C\u533A .apm/theater-skills \u540E\uFF0C\u4ECE\u5F53\u524D CLI \u5185\u7F6E\u6A21\u677F\u91CD\u65B0\u590D\u5236\u5267\u573A\u6280\u80FD\u76EE\u5F55"
2728
- ).action(async () => {
2729
- await runUpdateTheaterSkills();
2730
- });
2731
- program.command("pull-session").description(
2732
- "\u5C06\u5E73\u53F0\u5267\u573A\u4F1A\u8BDD\u4EA7\u7269\u6587\u6863\u4E0B\u8F7D\u5230 .apm/theater-sessions/<sessionId>/\uFF08\u5982 docs/prd.md\uFF09\uFF1B\u672C\u5730\u5DF2\u6709\u540C\u540D\u6587\u4EF6\u65F6\u8986\u76D6"
2733
- ).argument("<sessionId>", "\u5267\u573A\u4F1A\u8BDD ID").action(async (sessionId) => {
2734
- const dir = await runPullTheaterSession(sessionId);
2735
- console.log(`[apm] \u4F1A\u8BDD\u6587\u6863\u76EE\u5F55: ${dir}`);
1781
+ program.command("pull").description(
1782
+ "\u62C9\u53D6\u6C9F\u901A\u7FA4\u6570\u636E\u5230 .apm/sessions/<sessionId>/\uFF08session.yaml\u3001RULE.md\u3001docs\u3001attachments\uFF09"
1783
+ ).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").action(async (sessionId) => {
1784
+ await runPull(sessionId);
2736
1785
  });
2737
- program.command("sync-session-document").description(
2738
- "\u5C06 .apm/theater-sessions/<sessionId>/docs/ \u4E0B Markdown \u6587\u6863 upsert \u5230\u5E73\u53F0\u4F1A\u8BDD\u4EA7\u7269\uFF08\u4E0D\u542B\u5BF9\u8BDD\u65E5\u5FD7\uFF09"
2739
- ).argument("<sessionId>", "\u5267\u573A\u4F1A\u8BDD ID").option(
2740
- "--file <path>",
2741
- "\u76F8\u5BF9\u4F1A\u8BDD\u76EE\u5F55\u7684\u6587\u4EF6\u8DEF\u5F84\uFF08\u9ED8\u8BA4 docs/prd.md\uFF1B\u540E\u7AEF\u5E38\u7528 docs/backend.md\u3001docs/api.md\uFF09"
2742
- ).action(async (sessionId, opts) => {
2743
- await runSyncSessionDocument(sessionId, { file: opts.file });
1786
+ program.command("sync-document").description("\u5C06\u672C\u5730 Markdown \u8986\u76D6\u5F0F upsert \u5230\u5E73\u53F0\u9700\u6C42\u6587\u6863").argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").requiredOption("--file <path>", "\u672C\u5730\u6587\u4EF6\u8DEF\u5F84").action(async (sessionId, opts) => {
1787
+ await runSyncDocument(sessionId, { file: opts.file });
2744
1788
  });
2745
- program.command("pull").description(
2746
- "GET /api/cli/requirements/pull\uFF0C\u540C\u6B65\u6570\u636E\u4E0E\u9644\u4EF6\u5230 .apm/workitems/<\u9700\u6C42ID>"
2747
- ).argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
2748
- await runPull(requirementId);
1789
+ program.command("append-message").description("\u5411\u5E73\u53F0\u4F1A\u8BDD\u6D88\u606F\u8FFD\u52A0\u5185\u5BB9\uFF08PUT /api/cli/messages/content\uFF09").requiredOption("--id <messageId>", "\u6D88\u606F ID").requiredOption("--file <path>", "\u672C\u5730\u6587\u4EF6\u8DEF\u5F84").action(async (opts) => {
1790
+ await runAppendMessage(opts);
2749
1791
  });
2750
- program.command("upload-artifact").description(
2751
- "\u5148\u6E05\u7A7A\u8BE5\u9700\u6C42\u5728\u5E73\u53F0\u4E0A\u7684\u4EA7\u7269\u6587\u6863\uFF0C\u518D\u5C06 .apm/workitems/<\u9700\u6C42ID> \u4E0B Markdown \u540C\u6B65\u4E0A\u53BB\uFF08\u6392\u9664 pull \u7CFB\u7EDF\u6587\u4EF6\u4E0E attachments \u76EE\u5F55\uFF09"
2752
- ).argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
2753
- await runUploadArtifact(requirementId);
1792
+ program.command("update-message-status").description("\u66F4\u65B0\u5E73\u53F0\u4F1A\u8BDD\u6D88\u606F\u72B6\u6001").requiredOption("--id <messageId>", "\u6D88\u606F ID").requiredOption("--status <status>", "CREATED | TYPING | SUCCESS | FAILED").action(async (opts) => {
1793
+ await runUpdateMessageStatus(opts);
2754
1794
  });
2755
1795
  program.command("branch").description(
2756
- "\u5207\u6362\u6216\u521B\u5EFA\u9700\u6C42\u5206\u652F feat/req-<ID>\uFF1A\u8FDC\u7AEF\u5B58\u5728\u5219\u62C9\u53D6\u6700\u65B0\uFF1B\u8FDC\u7AEF\u5C1A\u65E0\u8BE5\u5206\u652F\u4E14\u672C\u5730\u4E5F\u65E0\u540C\u540D\u5206\u652F\u65F6\uFF0C\u9700\u5DF2 login\uFF0C\u5E76\u7531\u5E73\u53F0\u6839\u636E\u5F53\u524D\u76EE\u5F55\u8DEF\u5F84\u89E3\u6790\u4ED3\u5E93\u57FA\u7EBF\u5206\u652F\u540E\u4ECE origin \u68C0\u51FA\u518D\u63A8\u9001\uFF1B\u6709\u672C\u5730\u672A\u63D0\u4EA4\u6539\u52A8\u65F6\u5728\u975E\u76EE\u6807\u5206\u652F\u5148 stash\uFF08\u4E0D\u81EA\u52A8\u6062\u590D\uFF09\uFF0C\u5728\u76EE\u6807\u5206\u652F\u5219\u5148 commit"
2757
- ).argument("<requirementId>", "\u9700\u6C42 ID").option(
1796
+ "\u5207\u6362\u6216\u521B\u5EFA\u9700\u6C42\u5206\u652F feat/req-<requirementId>\uFF08\u7531 sessionId \u89E3\u6790\u9700\u6C42\uFF09"
1797
+ ).argument("<sessionId>", "\u6C9F\u901A\u7FA4 ID").option(
2758
1798
  "-m, --message <text>",
2759
- "\u5DF2\u5728\u76EE\u6807\u5206\u652F\u4E14\u9700\u63D0\u4EA4\u672C\u5730\u6539\u52A8\u65F6\u4F7F\u7528\u7684\u63D0\u4EA4\u8BF4\u660E\uFF08\u9ED8\u8BA4\u81EA\u52A8\u751F\u6210\uFF09"
2760
- ).action(async (requirementId, opts) => {
2761
- await runBranch(requirementId, { message: opts.message });
2762
- });
2763
- program.command("comment").description(
2764
- "\u63D0\u4EA4\u9700\u6C42\u8BC4\u5BA1\uFF1Alegacy \u4E3A\u6574\u7BC7 Markdown\uFF08POST \u2026/comment\uFF09\uFF1Bstructured \u4E3A\u884C\u7EA7 YAML\uFF08POST \u2026/comment-structured\uFF09"
2765
- ).argument("<requirementId>", "\u9700\u6C42 ID").requiredOption(
2766
- "--file <path>",
2767
- "\u8BC4\u8BBA\u6587\u4EF6\u8DEF\u5F84\uFF08.yaml/.yml \u9ED8\u8BA4 structured\uFF09"
2768
- ).option(
2769
- "--format <format>",
2770
- "structured | legacy\uFF08\u9ED8\u8BA4 legacy\uFF1B.yaml/.yml \u672A\u6307\u5B9A\u65F6\u89C6\u4E3A structured\uFF09"
2771
- ).option("--model <model>", "\u8BC4\u8BBA\u6A21\u578B\uFF08\u8986\u76D6 YAML reviewer.model\uFF09").action(
2772
- async (requirementId, options) => {
2773
- await runComment(requirementId, options.file, {
2774
- model: options.model,
2775
- format: options.format
2776
- });
2777
- }
2778
- );
2779
- program.command("refine").description("POST /api/cli/requirements/refine\uFF08\u6B63\u6587\u6765\u81EA\u6587\u4EF6\uFF09").argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
2780
- await runRefine(requirementId);
2781
- });
2782
- program.command("update-status").description("POST /api/cli/requirements/update-status").argument("<requirementId>", "\u9700\u6C42 ID").requiredOption("--status <RequirementStatus>", "\u9700\u6C42\u72B6\u6001\u679A\u4E3E\u503C").action(async (requirementId, options) => {
2783
- await runUpdateStatus(requirementId, options.status);
2784
- });
2785
- program.command("update-dev-status").description("POST /api/cli/requirements/update-dev-status").argument("<requirementId>", "\u9700\u6C42 ID").requiredOption("--status <status>", "\u6210\u5458\u5F00\u53D1\u72B6\u6001\uFF08\u81EA\u7531\u6587\u672C\uFF09").action(async (requirementId, options) => {
2786
- await runUpdateDevStatus(requirementId, options.status);
1799
+ "\u5DF2\u5728\u76EE\u6807\u5206\u652F\u4E14\u9700\u63D0\u4EA4\u672C\u5730\u6539\u52A8\u65F6\u4F7F\u7528\u7684\u63D0\u4EA4\u8BF4\u660E"
1800
+ ).action(async (sessionId, opts) => {
1801
+ await runBranch(sessionId, { message: opts.message });
2787
1802
  });
2788
1803
  registerDeployCommands(program);
2789
1804
  return program;