@vectorplane/ctrl-cli 0.1.8 → 0.1.10

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
@@ -17,6 +17,9 @@ vp login
17
17
  vp logout
18
18
  vp status
19
19
  vp sync
20
+ vp context --workspace <workspace> --delivery
21
+ vp session check-in --workspace <workspace> --agent codex --type codex --client openai-codex --feature feature-key --task task-key --owning-path "src/core,src/ui" --need "api-contract" --provide "implementation" --status "starting implementation"
22
+ vp draft create --type progress --title "Entrega concluída" --content "Resumo da mudança"
20
23
  ```
21
24
 
22
25
  ## Comandos
@@ -45,6 +48,19 @@ vp sync
45
48
  - mostra o estado da sessão local
46
49
  - exibe workspace ativo, diretório atual e última sincronização
47
50
 
51
+ ### `vp draft`
52
+
53
+ - cria drafts editoriais ligados ao workspace atual
54
+ - lista drafts existentes para conferência rápida
55
+ - suporta `ui`, `ux`, `project_skeleton`, `template_engineering`, `patterns`, `progress`, `decisions` e `architecture`
56
+ - aceita `--no-impact` para registrar explicitamente quando uma lane não foi afetada
57
+
58
+ ### `vp session`
59
+
60
+ - suporta `check-in`, `heartbeat` e `check-out`
61
+ - permite declarar `feature`, `task`, `component`, `role`, `owning-path`, `need`, `provide` e `status`
62
+ - deve ser usado em conjunto com `vp context --delivery` para reduzir conflito entre agentes ativos
63
+
48
64
  ### Comandos adicionais já implementados
49
65
 
50
66
  - `vp whoami`
@@ -64,6 +80,8 @@ vp sync
64
80
  - obtém instruções de setup do workspace
65
81
  - `vp event send`
66
82
  - envia eventos operacionais
83
+ - `vp draft`
84
+ - envia e consulta drafts editoriais do workspace
67
85
 
68
86
  ## Persistência local
69
87
 
@@ -0,0 +1 @@
1
+ export declare function runDraftCommand(cliVersion: string, args: string[]): Promise<number>;
@@ -0,0 +1,166 @@
1
+ import { getBooleanOption, getStringOption, parseArgs, requirePositional } from "../core/cli.js";
2
+ import { ensureSessionAvailable, getProfileState } from "../core/config.js";
3
+ import { collectGitContext } from "../core/git.js";
4
+ import { collectMachineContext, collectRuntimeContext } from "../core/machine.js";
5
+ import { loadRuntimeStatus } from "../core/runtime.js";
6
+ import { ensureFreshSession, resolveWorkspaceSlug } from "../core/session.js";
7
+ import { VectorPlaneApiClient } from "../core/api.js";
8
+ import { getBoundWorkspace, resolveWorkspaceRoot } from "../core/workspace-binding.js";
9
+ import { ValidationError } from "../core/errors.js";
10
+ const VALID_DRAFT_TYPES = new Set([
11
+ "architecture",
12
+ "decisions",
13
+ "progress",
14
+ "patterns",
15
+ "ui",
16
+ "ux",
17
+ "template_engineering",
18
+ "project_skeleton",
19
+ ]);
20
+ const VALID_STATUSES = new Set([
21
+ "draft",
22
+ "in_review",
23
+ "approved",
24
+ "merged",
25
+ "rejected",
26
+ "archived",
27
+ ]);
28
+ function slugify(value) {
29
+ return value
30
+ .trim()
31
+ .toLowerCase()
32
+ .normalize("NFD")
33
+ .replace(/[\u0300-\u036f]/g, "")
34
+ .replace(/[^a-z0-9]+/g, "-")
35
+ .replace(/^-+|-+$/g, "")
36
+ .slice(0, 120);
37
+ }
38
+ function inferMemoryKey(type, title) {
39
+ const slug = slugify(title);
40
+ if (!slug)
41
+ return undefined;
42
+ const prefix = type === "decisions"
43
+ ? "decision"
44
+ : type === "patterns"
45
+ ? "pattern"
46
+ : type === "template_engineering"
47
+ ? "template"
48
+ : type === "project_skeleton"
49
+ ? "skeleton"
50
+ : type;
51
+ return `${prefix}:${slug}`;
52
+ }
53
+ function validateDraftType(value) {
54
+ if (!value || !VALID_DRAFT_TYPES.has(value)) {
55
+ throw new ValidationError("Informe um tipo válido em `--type`.");
56
+ }
57
+ return value;
58
+ }
59
+ function validateStatus(value) {
60
+ if (!value)
61
+ return undefined;
62
+ if (!VALID_STATUSES.has(value)) {
63
+ throw new ValidationError("Informe um status válido em `--status`.");
64
+ }
65
+ return value;
66
+ }
67
+ function buildNoImpactDraft(type, reason) {
68
+ const laneLabel = type.replaceAll("_", " ");
69
+ const explanation = reason?.trim() || "sem impacto identificado nesta tarefa";
70
+ return {
71
+ title: `Sem impacto em ${laneLabel}`,
72
+ content: `Esta tarefa não alterou diretamente a lane ${laneLabel}. Escopo efetivo: ${explanation}.`,
73
+ };
74
+ }
75
+ function printDraftList(drafts) {
76
+ if (drafts.length === 0) {
77
+ process.stdout.write("VectorPlane: nenhum draft encontrado.\n");
78
+ return;
79
+ }
80
+ for (const draft of drafts) {
81
+ process.stdout.write(`${draft.id} | ${draft.docType} | ${draft.status} | ${draft.title}\n`);
82
+ }
83
+ }
84
+ async function resolveAuthenticatedWorkspace(cliVersion, rawArgs) {
85
+ const runtime = await loadRuntimeStatus();
86
+ const session = await ensureSessionAvailable(runtime.profile.name);
87
+ const parsed = parseArgs(rawArgs);
88
+ const [git, machine, runtimeContext] = await Promise.all([
89
+ collectGitContext(process.cwd()),
90
+ collectMachineContext(runtime.device, runtime.config),
91
+ collectRuntimeContext(cliVersion, "draft", process.argv.slice(2)),
92
+ ]);
93
+ const apiClient = new VectorPlaneApiClient(runtime.profile.apiBaseUrl, runtime.config.requestTimeoutMs, runtime.logger);
94
+ const freshSession = await ensureFreshSession({
95
+ profileName: runtime.profile.name,
96
+ session,
97
+ machine,
98
+ runtime: runtimeContext,
99
+ device: runtime.device,
100
+ apiClient,
101
+ logger: runtime.logger,
102
+ });
103
+ const rootPath = resolveWorkspaceRoot(process.cwd(), git);
104
+ const boundWorkspace = await getBoundWorkspace(rootPath);
105
+ const profileState = getProfileState(runtime.state, runtime.profile.name);
106
+ const workspace = resolveWorkspaceSlug(runtime.profile, freshSession, getStringOption(parsed, "workspace"), boundWorkspace, profileState.lastWorkspace);
107
+ if (!workspace) {
108
+ throw new ValidationError("Nenhum workspace resolvido para operar drafts.");
109
+ }
110
+ return { parsed, runtime, apiClient, freshSession, git, workspace };
111
+ }
112
+ async function runDraftCreate(cliVersion, args) {
113
+ const { parsed, runtime, apiClient, freshSession, git, workspace } = await resolveAuthenticatedWorkspace(cliVersion, args);
114
+ const type = validateDraftType(getStringOption(parsed, "type"));
115
+ const noImpact = getBooleanOption(parsed, "no-impact");
116
+ const explicitTitle = getStringOption(parsed, "title")?.trim();
117
+ const explicitContent = getStringOption(parsed, "content")?.trim();
118
+ const reason = getStringOption(parsed, "reason");
119
+ const derived = noImpact ? buildNoImpactDraft(type, reason) : null;
120
+ const title = explicitTitle || derived?.title;
121
+ const content = explicitContent || derived?.content;
122
+ if (!title) {
123
+ throw new ValidationError("Informe `--title` para criar o draft.");
124
+ }
125
+ if (!content) {
126
+ throw new ValidationError("Informe `--content` para criar o draft.");
127
+ }
128
+ const payload = {
129
+ docType: type,
130
+ title,
131
+ content,
132
+ memoryKey: getStringOption(parsed, "memory-key")?.trim() || inferMemoryKey(type, title),
133
+ featureKey: getStringOption(parsed, "feature")?.trim() || undefined,
134
+ branch: getStringOption(parsed, "branch")?.trim() || git.branch || undefined,
135
+ taskKey: getStringOption(parsed, "task")?.trim() || undefined,
136
+ };
137
+ const draft = await apiClient.createMemoryDraft(freshSession.accessToken, workspace, payload);
138
+ runtime.logger.success(`draft enviado para ${type}.`);
139
+ process.stdout.write(`ID: ${draft.id}\n`);
140
+ process.stdout.write(`Workspace: ${workspace}\n`);
141
+ process.stdout.write(`Status: ${draft.status}\n`);
142
+ return 0;
143
+ }
144
+ async function runDraftList(cliVersion, args) {
145
+ const { parsed, apiClient, freshSession, workspace } = await resolveAuthenticatedWorkspace(cliVersion, args);
146
+ const status = validateStatus(getStringOption(parsed, "status"));
147
+ const drafts = await apiClient.listMemoryDrafts(freshSession.accessToken, workspace, status);
148
+ if (getBooleanOption(parsed, "json")) {
149
+ process.stdout.write(`${JSON.stringify(drafts, null, 2)}\n`);
150
+ return 0;
151
+ }
152
+ printDraftList(drafts);
153
+ return 0;
154
+ }
155
+ export async function runDraftCommand(cliVersion, args) {
156
+ const parsed = parseArgs(args);
157
+ const subcommand = requirePositional(parsed, 0, "Uso: vp draft <create|list> [...]");
158
+ if (subcommand === "create") {
159
+ return runDraftCreate(cliVersion, args.slice(1));
160
+ }
161
+ if (subcommand === "list") {
162
+ return runDraftList(cliVersion, args.slice(1));
163
+ }
164
+ throw new ValidationError("Uso: vp draft <create|list> [...]");
165
+ }
166
+ //# sourceMappingURL=draft.js.map
@@ -1,4 +1,4 @@
1
- import { getStringOption, parseArgs } from "../core/cli.js";
1
+ import { getStringListOption, getStringOption, parseArgs } from "../core/cli.js";
2
2
  import { ensureSessionAvailable, getProfileState, updateProfileState } from "../core/config.js";
3
3
  import { collectGitContext } from "../core/git.js";
4
4
  import { collectMachineContext, collectRuntimeContext } from "../core/machine.js";
@@ -7,6 +7,25 @@ import { ensureFreshSession, resolveWorkspaceSlug } from "../core/session.js";
7
7
  import { VectorPlaneApiClient } from "../core/api.js";
8
8
  import { deriveClientInstanceId, getBoundWorkspace, resolveWorkspaceRoot } from "../core/workspace-binding.js";
9
9
  import { ValidationError } from "../core/errors.js";
10
+ function readCollaborationMetadata(parsed, rootPath) {
11
+ const owningPaths = getStringListOption(parsed, "owning-path");
12
+ const needs = getStringListOption(parsed, "need");
13
+ const provides = getStringListOption(parsed, "provide");
14
+ const relatedWorkspaceIds = getStringListOption(parsed, "related-workspace");
15
+ return {
16
+ source: "vp-cli",
17
+ rootPath,
18
+ featureKey: getStringOption(parsed, "feature"),
19
+ taskKey: getStringOption(parsed, "task"),
20
+ component: getStringOption(parsed, "component"),
21
+ role: getStringOption(parsed, "role"),
22
+ statusSummary: getStringOption(parsed, "status"),
23
+ owningPaths: owningPaths.length > 0 ? owningPaths : undefined,
24
+ needs: needs.length > 0 ? needs : undefined,
25
+ provides: provides.length > 0 ? provides : undefined,
26
+ relatedWorkspaceIds: relatedWorkspaceIds.length > 0 ? relatedWorkspaceIds : undefined,
27
+ };
28
+ }
10
29
  export async function runSessionCommand(cliVersion, args) {
11
30
  const parsed = parseArgs(args);
12
31
  const [subcommand] = parsed.positionals;
@@ -38,6 +57,7 @@ export async function runSessionCommand(cliVersion, args) {
38
57
  throw new ValidationError("Nenhum workspace resolvido para a sessão.");
39
58
  }
40
59
  const clientInstanceId = getStringOption(parsed, "client-instance-id") ?? deriveClientInstanceId(runtime.device.machineId, rootPath, git);
60
+ const metadata = readCollaborationMetadata(parsed, rootPath);
41
61
  if (subcommand === "check-in") {
42
62
  const response = await apiClient.checkInAgent(freshSession.accessToken, {
43
63
  workspaceId: workspace,
@@ -48,10 +68,7 @@ export async function runSessionCommand(cliVersion, args) {
48
68
  repoUrl: git.remoteOrigin ?? undefined,
49
69
  branch: git.branch,
50
70
  commitSha: git.commitHash,
51
- metadata: {
52
- source: "vp-cli",
53
- rootPath,
54
- },
71
+ metadata,
55
72
  });
56
73
  const sessionId = String(response.id ?? response.sessionId ?? "");
57
74
  await updateProfileState(runtime.profile.name, {
@@ -62,6 +79,12 @@ export async function runSessionCommand(cliVersion, args) {
62
79
  });
63
80
  process.stdout.write(`Session ID: ${sessionId || "não retornado"}\n`);
64
81
  process.stdout.write(`Client Instance ID: ${clientInstanceId}\n`);
82
+ if (metadata.featureKey) {
83
+ process.stdout.write(`Feature: ${metadata.featureKey}\n`);
84
+ }
85
+ if (metadata.taskKey) {
86
+ process.stdout.write(`Task: ${metadata.taskKey}\n`);
87
+ }
65
88
  return 0;
66
89
  }
67
90
  if (subcommand === "heartbeat") {
@@ -74,16 +97,16 @@ export async function runSessionCommand(cliVersion, args) {
74
97
  clientInstanceId,
75
98
  branch: git.branch,
76
99
  commitSha: git.commitHash,
77
- metadata: {
78
- source: "vp-cli",
79
- rootPath,
80
- },
100
+ metadata,
81
101
  });
82
102
  await updateProfileState(runtime.profile.name, {
83
103
  lastCommand: "session:heartbeat",
84
104
  lastError: null,
85
105
  });
86
106
  process.stdout.write(`Heartbeat enviado para ${sessionId}\n`);
107
+ if (metadata.statusSummary) {
108
+ process.stdout.write(`Status: ${metadata.statusSummary}\n`);
109
+ }
87
110
  return 0;
88
111
  }
89
112
  if (subcommand === "check-out") {
@@ -95,6 +118,9 @@ export async function runSessionCommand(cliVersion, args) {
95
118
  sessionId,
96
119
  metadata: {
97
120
  source: "vp-cli",
121
+ featureKey: metadata.featureKey,
122
+ taskKey: metadata.taskKey,
123
+ statusSummary: metadata.statusSummary,
98
124
  },
99
125
  });
100
126
  await updateProfileState(runtime.profile.name, {
@@ -1,6 +1,6 @@
1
1
  import type { Logger } from "./logger.js";
2
2
  import type { CurrentUserResponse, AuthCodeExchangeRequest, AuthTokenExchangeResponse, CreateLoginAttemptRequest, CreateLoginAttemptResponse, LoginAttemptStatusResponse, RefreshTokenRequest } from "../types/auth.js";
3
- import type { AgentCheckoutRequest, AgentHeartbeatRequest, AgentSessionRequest, AgentSessionResponse, EventRequest, ResolveWorkspaceRequest, ResolveWorkspaceResponse, SyncRequest, SyncResponse } from "../types/api.js";
3
+ import type { AgentCheckoutRequest, AgentHeartbeatRequest, AgentSessionRequest, AgentSessionResponse, EventRequest, MemoryDraftCreateRequest, MemoryDraftRecord, MemoryDraftStatus, ResolveWorkspaceRequest, ResolveWorkspaceResponse, SyncRequest, SyncResponse } from "../types/api.js";
4
4
  export declare class VectorPlaneApiClient {
5
5
  private readonly apiBaseUrl;
6
6
  private readonly timeoutMs;
@@ -22,6 +22,8 @@ export declare class VectorPlaneApiClient {
22
22
  getWorkspaceSnapshot(accessToken: string, workspaceId: string): Promise<Record<string, unknown>>;
23
23
  getWorkspaceDeliveryContext(accessToken: string, workspaceId: string): Promise<Record<string, unknown>>;
24
24
  getAgentSetup(accessToken: string, workspaceId: string): Promise<Record<string, unknown>>;
25
+ listMemoryDrafts(accessToken: string, workspaceRef: string, status?: MemoryDraftStatus): Promise<MemoryDraftRecord[]>;
26
+ createMemoryDraft(accessToken: string, workspaceRef: string, payload: MemoryDraftCreateRequest): Promise<MemoryDraftRecord>;
25
27
  checkInAgent(accessToken: string, payload: AgentSessionRequest): Promise<AgentSessionResponse>;
26
28
  heartbeatAgent(accessToken: string, payload: AgentHeartbeatRequest): Promise<AgentSessionResponse>;
27
29
  checkOutAgent(accessToken: string, payload: AgentCheckoutRequest): Promise<AgentSessionResponse>;
package/dist/core/api.js CHANGED
@@ -160,6 +160,25 @@ export class VectorPlaneApiClient {
160
160
  },
161
161
  }, this.timeoutMs, this.logger);
162
162
  }
163
+ async listMemoryDrafts(accessToken, workspaceRef, status) {
164
+ const url = new URL(`${this.apiBaseUrl}/cli/workspace/${workspaceRef}/memory-drafts`);
165
+ if (status) {
166
+ url.searchParams.set("status", status);
167
+ }
168
+ return requestJson(url.toString(), {
169
+ method: "GET",
170
+ headers: {
171
+ "Authorization": `Bearer ${accessToken}`,
172
+ },
173
+ }, this.timeoutMs, this.logger);
174
+ }
175
+ async createMemoryDraft(accessToken, workspaceRef, payload) {
176
+ return requestJson(`${this.apiBaseUrl}/cli/workspace/${workspaceRef}/memory-drafts`, {
177
+ method: "POST",
178
+ headers: authHeaders(accessToken),
179
+ body: JSON.stringify(payload),
180
+ }, this.timeoutMs, this.logger);
181
+ }
163
182
  async checkInAgent(accessToken, payload) {
164
183
  return requestJson(`${this.apiBaseUrl}/cli/workspace/${payload.workspaceId}/agents/check-in`, {
165
184
  method: "POST",
@@ -5,5 +5,6 @@ export interface ParsedArgs {
5
5
  export declare function parseArgs(args: string[]): ParsedArgs;
6
6
  export declare function getStringOption(parsed: ParsedArgs, key: string): string | undefined;
7
7
  export declare function getBooleanOption(parsed: ParsedArgs, key: string): boolean;
8
+ export declare function getStringListOption(parsed: ParsedArgs, key: string): string[];
8
9
  export declare function requirePositional(parsed: ParsedArgs, index: number, message: string): string;
9
10
  export declare function parseJsonOption(value: string | undefined, fallback?: Record<string, unknown>): Record<string, unknown>;
package/dist/core/cli.js CHANGED
@@ -38,6 +38,16 @@ export function getBooleanOption(parsed, key) {
38
38
  }
39
39
  return false;
40
40
  }
41
+ export function getStringListOption(parsed, key) {
42
+ const value = getStringOption(parsed, key);
43
+ if (!value) {
44
+ return [];
45
+ }
46
+ return [...new Set(value
47
+ .split(",")
48
+ .map((item) => item.trim())
49
+ .filter(Boolean))];
50
+ }
41
51
  export function requirePositional(parsed, index, message) {
42
52
  const value = parsed.positionals[index];
43
53
  if (!value) {
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { runBootstrapCommand } from "./commands/bootstrap.js";
5
5
  import { runConfigCommand } from "./commands/config.js";
6
6
  import { runContextCommand } from "./commands/context.js";
7
7
  import { runDoctorCommand } from "./commands/doctor.js";
8
+ import { runDraftCommand } from "./commands/draft.js";
8
9
  import { runEventCommand } from "./commands/event.js";
9
10
  import { runLoginCommand } from "./commands/login.js";
10
11
  import { runLogoutCommand } from "./commands/logout.js";
@@ -32,6 +33,7 @@ function printHelp() {
32
33
  process.stdout.write(" status\n");
33
34
  process.stdout.write(" whoami\n");
34
35
  process.stdout.write(" doctor\n");
36
+ process.stdout.write(" draft <create|list>\n");
35
37
  process.stdout.write(" config <profile|get|set>\n");
36
38
  process.stdout.write(" workspace <current|use|resolve|clear>\n");
37
39
  process.stdout.write(" session <check-in|heartbeat|check-out>\n");
@@ -60,6 +62,8 @@ export async function runCli(args) {
60
62
  return runWhoAmICommand(cliVersion);
61
63
  case "doctor":
62
64
  return runDoctorCommand(cliVersion);
65
+ case "draft":
66
+ return runDraftCommand(cliVersion, rest);
63
67
  case "config":
64
68
  return runConfigCommand(rest);
65
69
  case "workspace":
@@ -59,3 +59,30 @@ export interface AgentSessionResponse {
59
59
  workspaceId?: string;
60
60
  [key: string]: unknown;
61
61
  }
62
+ export type MemoryDraftType = "architecture" | "decisions" | "progress" | "patterns" | "ui" | "ux" | "template_engineering" | "project_skeleton";
63
+ export type MemoryDraftStatus = "draft" | "in_review" | "approved" | "merged" | "rejected" | "archived";
64
+ export interface MemoryDraftCreateRequest {
65
+ docType: MemoryDraftType;
66
+ title: string;
67
+ content: string;
68
+ memoryKey?: string;
69
+ featureKey?: string;
70
+ branch?: string;
71
+ taskKey?: string;
72
+ }
73
+ export interface MemoryDraftRecord {
74
+ id: string;
75
+ workspaceId: string;
76
+ docType: MemoryDraftType;
77
+ title: string;
78
+ content: string;
79
+ memoryKey: string | null;
80
+ featureKey: string | null;
81
+ branch: string | null;
82
+ taskKey: string | null;
83
+ status: MemoryDraftStatus;
84
+ suggestedMergeStrategy?: string | null;
85
+ createdAt: string;
86
+ updatedAt?: string;
87
+ [key: string]: unknown;
88
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vectorplane/ctrl-cli",
3
- "version": "0.1.8",
3
+ "version": "0.1.10",
4
4
  "description": "Official VectorPlane CLI.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",