@vibecompass/vibecompass 0.2.1 → 0.3.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/src/cli.js CHANGED
@@ -2,10 +2,17 @@
2
2
 
3
3
  import { realpathSync } from 'node:fs';
4
4
  import { pathToFileURL } from 'node:url';
5
- import { initializeProjectMemory } from './init.js';
6
- import { resolveInitCliOptions } from './setup.js';
5
+ import { connectHostedProjectMemory, initializeProjectMemory } from './init.js';
6
+ import { preflightDocsReview } from './docs-review.js';
7
+ import { resolveConnectHostedCliOptions, resolveInitCliOptions } from './setup.js';
7
8
  import { closeProjectSession, listProjectSessions, startProjectSession, switchProjectSession } from './session.js';
8
9
  import { syncAgentInstructionFiles } from './generators/agent-files/index.js';
10
+ import {
11
+ applyPullExport,
12
+ pullExportProjectMemory,
13
+ pullPreviewProjectMemory,
14
+ pushProjectMemory,
15
+ } from './sync.js';
9
16
 
10
17
  export async function runCli(argv, io = createDefaultIo(), runtime = {}) {
11
18
  const parsed = parseCliArgs(argv);
@@ -40,7 +47,20 @@ export async function runCli(argv, io = createDefaultIo(), runtime = {}) {
40
47
  );
41
48
 
42
49
  if (result.syncEnvVar) {
43
- io.stdout.write(`Next step: set ${result.syncEnvVar} locally before your first sync.\n`);
50
+ const rootFlag = formatOptionalRootFlag(initPlan.initOptions.rootDir);
51
+ io.stdout.write(`Hosted binding: configured for ${initPlan.initOptions.mode}\n`);
52
+ io.stdout.write(`Next step: set ${result.syncEnvVar} locally before your first hosted command.\n`);
53
+ if (initPlan.initOptions.mode === 'local-primary') {
54
+ io.stdout.write(`Then run: vibecompass push${rootFlag}\n`);
55
+ io.stdout.write(`Hosted docs-review: vibecompass docs-review --submit-hosted${rootFlag}\n`);
56
+ } else if (initPlan.initOptions.mode === 'hosted-only') {
57
+ io.stdout.write(`Hosted docs-review: vibecompass docs-review --submit-hosted${rootFlag}\n`);
58
+ }
59
+ } else if (initPlan.initOptions.mode === 'hosted-only') {
60
+ io.stdout.write('Hosted-only projects need a hosted sync binding before hosted commands.\n');
61
+ io.stdout.write('After creating a sync credential, rerun init with --force and --sync-api-url/--sync-project-id/--sync-credential-env-var, or add project.yaml.sync manually.\n');
62
+ } else if (initPlan.initOptions.mode === 'local-primary') {
63
+ io.stdout.write('Optional hosted setup later: vibecompass connect-hosted\n');
44
64
  }
45
65
 
46
66
  if (result.contextFilePath) {
@@ -70,6 +90,7 @@ export async function runCli(argv, io = createDefaultIo(), runtime = {}) {
70
90
  io.stdout.write(`Updated ${sessionResult.claudePath}\n`);
71
91
  io.stdout.write(`Created ${sessionResult.wipFilePath}\n`);
72
92
  io.stdout.write(`Created ${sessionResult.handoffFilePath}\n`);
93
+ writeWarnings(io, sessionResult.warnings);
73
94
  writeAgentFileSyncResult(io, sessionResult.agentFileSync);
74
95
  }
75
96
 
@@ -85,10 +106,33 @@ export async function runCli(argv, io = createDefaultIo(), runtime = {}) {
85
106
  io.stdout.write(`Updated ${result.claudePath}\n`);
86
107
  io.stdout.write(`Created ${result.wipFilePath}\n`);
87
108
  io.stdout.write(`Created ${result.handoffFilePath}\n`);
109
+ writeWarnings(io, result.warnings);
88
110
  writeAgentFileSyncResult(io, result.agentFileSync);
89
111
  return 0;
90
112
  }
91
113
 
114
+ if (parsed.command === 'connect-hosted') {
115
+ const connectPlan = await resolveConnectHostedCliOptions(parsed.options, {
116
+ cwd: runtime.cwd,
117
+ io,
118
+ runtime,
119
+ });
120
+ const result = await connectHostedProjectMemory({
121
+ ...connectPlan,
122
+ ...(runtime.cwd ? { cwd: runtime.cwd } : {}),
123
+ });
124
+ io.stdout.write(`Connected hosted VibeCompass for ${result.mode}\n`);
125
+ io.stdout.write(`Updated ${result.projectFilePath}\n`);
126
+ io.stdout.write(`Next step: set ${result.syncEnvVar} locally before your first hosted command.\n`);
127
+ if (result.mode === 'local-primary') {
128
+ io.stdout.write('Then run: vibecompass push\n');
129
+ io.stdout.write('Hosted docs-review: vibecompass docs-review --submit-hosted\n');
130
+ } else {
131
+ io.stdout.write('Hosted docs-review: vibecompass docs-review --submit-hosted\n');
132
+ }
133
+ return 0;
134
+ }
135
+
92
136
  if (parsed.command === 'close-session' || parsed.command === 'end-session') {
93
137
  const result = await closeProjectSession({
94
138
  ...parsed.options,
@@ -144,6 +188,129 @@ export async function runCli(argv, io = createDefaultIo(), runtime = {}) {
144
188
  return 0;
145
189
  }
146
190
 
191
+ if (parsed.command === 'docs-review') {
192
+ const result = await preflightDocsReview({
193
+ ...parsed.options,
194
+ ...(runtime.cwd ? { cwd: runtime.cwd } : {}),
195
+ }, {
196
+ io,
197
+ env: runtime.env,
198
+ runtime,
199
+ });
200
+ io.stdout.write(`Docs-review: ${result.status}\n`);
201
+ if (result.llm) {
202
+ io.stdout.write(`LLM: ${result.llm}\n`);
203
+ }
204
+ if (result.model) {
205
+ io.stdout.write(`Model: ${result.model}\n`);
206
+ }
207
+ if (result.runtime) {
208
+ io.stdout.write(`Runtime: ${result.runtime.provider}\n`);
209
+ }
210
+ if (result.hosted) {
211
+ io.stdout.write(`Hosted run: ${result.hosted.run_id}\n`);
212
+ if (result.hosted.phase) {
213
+ io.stdout.write(`Hosted phase: ${result.hosted.phase}\n`);
214
+ }
215
+ }
216
+ if (result.localReview) {
217
+ io.stdout.write(`Local review output: ${result.localReview.output_path}\n`);
218
+ }
219
+ if (result.applied) {
220
+ io.stdout.write(`Applied architecture docs: ${result.applied.architecture_docs.length}\n`);
221
+ for (const entry of result.applied.architecture_docs) {
222
+ io.stdout.write(`- ${entry.path} (${entry.status})\n`);
223
+ }
224
+ }
225
+ if (result.appliedDecisionArtifact) {
226
+ io.stdout.write(`Applied decision artifact: ${result.appliedDecisionArtifact.artifact_id}\n`);
227
+ io.stdout.write(`Decision: D-${String(result.appliedDecisionArtifact.decision_id).padStart(3, '0')}\n`);
228
+ io.stdout.write(`Target: ${result.appliedDecisionArtifact.target_path}\n`);
229
+ }
230
+ writeWarnings(io, result.warnings);
231
+ io.stdout.write(`Recorded ${result.statePath}\n`);
232
+ io.stdout.write(`${result.message}\n`);
233
+ if (result.reviewPrompt) {
234
+ io.stdout.write('Architecture review prompt:\n');
235
+ io.stdout.write(`${result.reviewPrompt}\n`);
236
+ }
237
+ return 0;
238
+ }
239
+
240
+ if (parsed.command === 'push') {
241
+ const result = await pushProjectMemory(parsed.options, {
242
+ cwd: runtime.cwd,
243
+ env: runtime.env,
244
+ runtime,
245
+ });
246
+ io.stdout.write(`Push: ${result.status}\n`);
247
+ io.stdout.write(`Remote revision: ${result.remoteRevisionId}\n`);
248
+ io.stdout.write(`Run: ${result.runId}\n`);
249
+ if (result.appliedProposalIds.length > 0) {
250
+ io.stdout.write(`Applied proposals: ${result.appliedProposalIds.join(', ')}\n`);
251
+ }
252
+ if (result.staleProposalIds.length > 0) {
253
+ io.stdout.write(`Stale proposals: ${result.staleProposalIds.join(', ')}\n`);
254
+ }
255
+ io.stdout.write(`Recorded ${result.manifestPath}\n`);
256
+ io.stdout.write(`${result.message}\n`);
257
+ return 0;
258
+ }
259
+
260
+ if (parsed.command === 'pull-preview') {
261
+ const result = await pullPreviewProjectMemory(parsed.options, {
262
+ cwd: runtime.cwd,
263
+ env: runtime.env,
264
+ runtime,
265
+ });
266
+ io.stdout.write(`Pull-preview: ${result.status}\n`);
267
+ io.stdout.write(`Preview token: ${result.previewToken}\n`);
268
+ io.stdout.write(`Pending proposals: ${result.proposals.length}\n`);
269
+ for (const proposal of result.proposals) {
270
+ io.stdout.write(`- ${proposal.proposal_id}: ${proposal.summary}\n`);
271
+ }
272
+ if (result.conflicts.length > 0) {
273
+ io.stdout.write(`Conflicts: ${result.conflicts.length}\n`);
274
+ for (const conflict of result.conflicts) {
275
+ io.stdout.write(`- ${conflict.code}: ${conflict.message}\n`);
276
+ }
277
+ }
278
+ writeWarnings(io, result.warnings.map((warning) => `${warning.code}: ${warning.message}`));
279
+ io.stdout.write(`Recorded ${result.manifestPath}\n`);
280
+ io.stdout.write(`${result.message}\n`);
281
+ return 0;
282
+ }
283
+
284
+ if (parsed.command === 'pull-export') {
285
+ const result = await pullExportProjectMemory(parsed.options, {
286
+ cwd: runtime.cwd,
287
+ env: runtime.env,
288
+ runtime,
289
+ });
290
+ io.stdout.write(`Pull-export: ${result.status}\n`);
291
+ io.stdout.write(`Run: ${result.runId}\n`);
292
+ io.stdout.write(`Proposal IDs: ${result.proposalIds.join(', ')}\n`);
293
+ io.stdout.write(`Operations: ${result.operationCount}\n`);
294
+ io.stdout.write(`Wrote ${result.outputPath}\n`);
295
+ io.stdout.write(`${result.message}\n`);
296
+ return 0;
297
+ }
298
+
299
+ if (parsed.command === 'apply-export') {
300
+ const result = await applyPullExport(parsed.options, {
301
+ cwd: runtime.cwd,
302
+ env: runtime.env,
303
+ runtime,
304
+ });
305
+ io.stdout.write(`Apply-export: ${result.status}\n`);
306
+ for (const entry of result.applied) {
307
+ io.stdout.write(`- ${entry.path} (${entry.status})\n`);
308
+ }
309
+ io.stdout.write(`Recorded ${result.outputPath}\n`);
310
+ io.stdout.write(`${result.message}\n`);
311
+ return 0;
312
+ }
313
+
147
314
  throw new Error(`Unknown command "${parsed.command}".`);
148
315
  }
149
316
 
@@ -159,6 +326,10 @@ export function parseCliArgs(argv) {
159
326
  return parseStartSessionArgs(rest);
160
327
  }
161
328
 
329
+ if (command === 'connect-hosted') {
330
+ return parseConnectHostedArgs(rest);
331
+ }
332
+
162
333
  if (command === 'close-session' || command === 'end-session') {
163
334
  return parseCloseSessionArgs(rest);
164
335
  }
@@ -175,6 +346,26 @@ export function parseCliArgs(argv) {
175
346
  return parseSyncAgentsArgs(rest);
176
347
  }
177
348
 
349
+ if (command === 'docs-review') {
350
+ return parseDocsReviewArgs(rest);
351
+ }
352
+
353
+ if (command === 'push') {
354
+ return parsePushArgs(rest);
355
+ }
356
+
357
+ if (command === 'pull-preview') {
358
+ return parsePullPreviewArgs(rest);
359
+ }
360
+
361
+ if (command === 'pull-export') {
362
+ return parsePullExportArgs(rest);
363
+ }
364
+
365
+ if (command === 'apply-export') {
366
+ return parseApplyExportArgs(rest);
367
+ }
368
+
178
369
  return { command };
179
370
  }
180
371
 
@@ -400,6 +591,63 @@ function parseStartSessionArgs(argv) {
400
591
  };
401
592
  }
402
593
 
594
+ function parseConnectHostedArgs(argv) {
595
+ const parsed = {};
596
+
597
+ for (let index = 0; index < argv.length; index += 1) {
598
+ const token = argv[index];
599
+
600
+ if (!token.startsWith('--')) {
601
+ throw new Error(`Unexpected argument "${token}".`);
602
+ }
603
+
604
+ const value = argv[index + 1];
605
+ if (value === undefined) {
606
+ throw new Error(`Flag "${token}" requires a value.`);
607
+ }
608
+ index += 1;
609
+
610
+ switch (token) {
611
+ case '--root':
612
+ parsed.rootDir = value;
613
+ break;
614
+ case '--sync-api-url':
615
+ parsed.syncApiUrl = value;
616
+ break;
617
+ case '--sync-project-id':
618
+ parsed.syncProjectId = value;
619
+ break;
620
+ case '--sync-credential-env-var':
621
+ parsed.syncCredentialEnvVar = value;
622
+ break;
623
+ default:
624
+ throw new Error(`Unknown flag "${token}".`);
625
+ }
626
+ }
627
+
628
+ const syncValues = [
629
+ parsed.syncApiUrl,
630
+ parsed.syncProjectId,
631
+ parsed.syncCredentialEnvVar,
632
+ ].filter(Boolean);
633
+
634
+ return {
635
+ command: 'connect-hosted',
636
+ options: {
637
+ rootDir: parsed.rootDir,
638
+ ...(syncValues.length > 0
639
+ ? {
640
+ sync: {
641
+ apiUrl: parsed.syncApiUrl,
642
+ projectId: parsed.syncProjectId,
643
+ credentialEnvVar: parsed.syncCredentialEnvVar,
644
+ },
645
+ }
646
+ : {}),
647
+ },
648
+ };
649
+ }
650
+
403
651
  function parseCloseSessionArgs(argv) {
404
652
  const parsed = {
405
653
  completed: [],
@@ -587,6 +835,248 @@ function parseSyncAgentsArgs(argv) {
587
835
  };
588
836
  }
589
837
 
838
+ function parseDocsReviewArgs(argv) {
839
+ const parsed = {};
840
+
841
+ for (let index = 0; index < argv.length; index += 1) {
842
+ const token = argv[index];
843
+
844
+ if (token === '--guided') {
845
+ parsed.guided = true;
846
+ continue;
847
+ }
848
+
849
+ if (token === '--submit-hosted') {
850
+ parsed.submitHosted = true;
851
+ continue;
852
+ }
853
+
854
+ if (token === '--poll-hosted') {
855
+ parsed.pollHosted = true;
856
+ continue;
857
+ }
858
+
859
+ if (token === '--complete') {
860
+ parsed.complete = true;
861
+ continue;
862
+ }
863
+
864
+ if (token === '--run-local-anthropic') {
865
+ parsed.runLocalAnthropic = true;
866
+ continue;
867
+ }
868
+
869
+ if (token === '--run-local') {
870
+ parsed.runLocal = true;
871
+ continue;
872
+ }
873
+
874
+ if (token === '--apply-output') {
875
+ parsed.applyOutput = true;
876
+ continue;
877
+ }
878
+
879
+ if (token === '--apply-decision-artifact') {
880
+ parsed.applyDecisionArtifact = true;
881
+ continue;
882
+ }
883
+
884
+ if (token === '--refresh-index') {
885
+ parsed.refreshIndex = true;
886
+ continue;
887
+ }
888
+
889
+ if (!token.startsWith('--')) {
890
+ throw new Error(`Unexpected argument "${token}".`);
891
+ }
892
+
893
+ const value = argv[index + 1];
894
+ if (value === undefined) {
895
+ throw new Error(`Flag "${token}" requires a value.`);
896
+ }
897
+ index += 1;
898
+
899
+ switch (token) {
900
+ case '--root':
901
+ parsed.rootDir = value;
902
+ break;
903
+ case '--llm':
904
+ parsed.llm = value;
905
+ break;
906
+ case '--model':
907
+ parsed.model = value;
908
+ break;
909
+ case '--anthropic-env-var':
910
+ parsed.anthropicEnvVar = value;
911
+ break;
912
+ case '--provider':
913
+ parsed.provider = value;
914
+ break;
915
+ case '--output':
916
+ parsed.outputPath = value;
917
+ break;
918
+ case '--artifact':
919
+ parsed.artifactId = value;
920
+ break;
921
+ case '--max-tokens':
922
+ parsed.maxTokens = value;
923
+ break;
924
+ default:
925
+ throw new Error(`Unknown flag "${token}".`);
926
+ }
927
+ }
928
+
929
+ return {
930
+ command: 'docs-review',
931
+ options: parsed,
932
+ };
933
+ }
934
+
935
+ function parsePushArgs(argv) {
936
+ return {
937
+ command: 'push',
938
+ options: parseRootOnlyArgs(argv, 'push'),
939
+ };
940
+ }
941
+
942
+ function parsePullPreviewArgs(argv) {
943
+ const parsed = {};
944
+
945
+ for (let index = 0; index < argv.length; index += 1) {
946
+ const token = argv[index];
947
+ if (token === '--no-pending-proposals') {
948
+ parsed.includePendingProposals = false;
949
+ continue;
950
+ }
951
+
952
+ if (!token.startsWith('--')) {
953
+ throw new Error(`Unexpected argument "${token}".`);
954
+ }
955
+
956
+ const value = argv[index + 1];
957
+ if (value === undefined) {
958
+ throw new Error(`Flag "${token}" requires a value.`);
959
+ }
960
+ index += 1;
961
+
962
+ switch (token) {
963
+ case '--root':
964
+ parsed.rootDir = value;
965
+ break;
966
+ default:
967
+ throw new Error(`Unknown flag "${token}".`);
968
+ }
969
+ }
970
+
971
+ return {
972
+ command: 'pull-preview',
973
+ options: parsed,
974
+ };
975
+ }
976
+
977
+ function parsePullExportArgs(argv) {
978
+ const parsed = {
979
+ proposalIds: [],
980
+ };
981
+
982
+ for (let index = 0; index < argv.length; index += 1) {
983
+ const token = argv[index];
984
+
985
+ if (!token.startsWith('--')) {
986
+ throw new Error(`Unexpected argument "${token}".`);
987
+ }
988
+
989
+ const value = argv[index + 1];
990
+ if (value === undefined) {
991
+ throw new Error(`Flag "${token}" requires a value.`);
992
+ }
993
+ index += 1;
994
+
995
+ switch (token) {
996
+ case '--root':
997
+ parsed.rootDir = value;
998
+ break;
999
+ case '--preview-token':
1000
+ parsed.previewToken = value;
1001
+ break;
1002
+ case '--proposal':
1003
+ parsed.proposalIds.push(value);
1004
+ break;
1005
+ case '--output':
1006
+ parsed.outputPath = value;
1007
+ break;
1008
+ default:
1009
+ throw new Error(`Unknown flag "${token}".`);
1010
+ }
1011
+ }
1012
+
1013
+ return {
1014
+ command: 'pull-export',
1015
+ options: parsed,
1016
+ };
1017
+ }
1018
+
1019
+ function parseApplyExportArgs(argv) {
1020
+ const parsed = {};
1021
+
1022
+ for (let index = 0; index < argv.length; index += 1) {
1023
+ const token = argv[index];
1024
+
1025
+ if (!token.startsWith('--')) {
1026
+ throw new Error(`Unexpected argument "${token}".`);
1027
+ }
1028
+
1029
+ const value = argv[index + 1];
1030
+ if (value === undefined) {
1031
+ throw new Error(`Flag "${token}" requires a value.`);
1032
+ }
1033
+ index += 1;
1034
+
1035
+ switch (token) {
1036
+ case '--root':
1037
+ parsed.rootDir = value;
1038
+ break;
1039
+ case '--output':
1040
+ parsed.outputPath = value;
1041
+ break;
1042
+ default:
1043
+ throw new Error(`Unknown flag "${token}".`);
1044
+ }
1045
+ }
1046
+
1047
+ return {
1048
+ command: 'apply-export',
1049
+ options: parsed,
1050
+ };
1051
+ }
1052
+
1053
+ function parseRootOnlyArgs(argv, commandName) {
1054
+ const parsed = {};
1055
+
1056
+ for (let index = 0; index < argv.length; index += 1) {
1057
+ const token = argv[index];
1058
+ if (!token.startsWith('--')) {
1059
+ throw new Error(`Unexpected argument "${token}".`);
1060
+ }
1061
+
1062
+ const value = argv[index + 1];
1063
+ if (value === undefined) {
1064
+ throw new Error(`Flag "${token}" requires a value.`);
1065
+ }
1066
+ index += 1;
1067
+
1068
+ switch (token) {
1069
+ case '--root':
1070
+ parsed.rootDir = value;
1071
+ break;
1072
+ default:
1073
+ throw new Error(`Unknown flag "${token}" for ${commandName}.`);
1074
+ }
1075
+ }
1076
+
1077
+ return parsed;
1078
+ }
1079
+
590
1080
  function writeAgentFileSyncResult(io, result) {
591
1081
  if (!result) {
592
1082
  return;
@@ -600,6 +1090,12 @@ function writeAgentFileSyncResult(io, result) {
600
1090
  }
601
1091
  }
602
1092
 
1093
+ function writeWarnings(io, warnings = []) {
1094
+ for (const warning of warnings) {
1095
+ io.stderr.write(`Warning: ${warning}\n`);
1096
+ }
1097
+ }
1098
+
603
1099
  function splitAssignment(value, flagName) {
604
1100
  const separatorIndex = value.indexOf('=');
605
1101
  if (separatorIndex <= 0 || separatorIndex === value.length - 1) {
@@ -613,12 +1109,18 @@ function usageText() {
613
1109
  return [
614
1110
  'Usage:',
615
1111
  ' vibecompass init --name <project-name> --mode <local-only|local-primary|hosted-only> --repo <id=remote> [options]',
1112
+ ' vibecompass connect-hosted [options]',
616
1113
  ' vibecompass start-session --id <lane-id> --working-on <text> [options]',
617
1114
  ' vibecompass close-session --title <text> --completed <text> --next-step <text> [options]',
618
1115
  ' vibecompass end-session --title <text> --completed <text> --next-step <text> [options] # alias',
619
1116
  ' vibecompass list-sessions [options]',
620
1117
  ' vibecompass switch-session <id> [options]',
621
1118
  ' vibecompass sync-agents [options]',
1119
+ ' vibecompass push [options]',
1120
+ ' vibecompass pull-preview [options]',
1121
+ ' vibecompass pull-export [options]',
1122
+ ' vibecompass apply-export [options]',
1123
+ ' vibecompass docs-review --guided [options]',
622
1124
  '',
623
1125
  'Init options:',
624
1126
  ' --root <path> Project-memory root. Defaults to .compass',
@@ -643,6 +1145,13 @@ function usageText() {
643
1145
  ' --close-session-git-remote <name> Default Git remote name for that close-session publish step',
644
1146
  ' --force Overwrite an existing project.yaml',
645
1147
  '',
1148
+ 'Connect-hosted options:',
1149
+ ' --root <path> Project-memory root. Defaults to .compass',
1150
+ ' --sync-api-url <url> Hosted sync api_url',
1151
+ ' --sync-project-id <id> Hosted sync project_id',
1152
+ ' --sync-credential-env-var <name> Hosted sync env var reference',
1153
+ ' Replaces any existing project.yaml sync binding',
1154
+ '',
646
1155
  'Start-session options:',
647
1156
  ' --root <path> Project-memory root. Defaults to .compass',
648
1157
  ' --tooling-root <path> Tooling root that contains CLAUDE.md. Defaults to cwd',
@@ -679,6 +1188,34 @@ function usageText() {
679
1188
  ' --tooling-root <path> Directory where agent files are written. Defaults to cwd',
680
1189
  ' --format <name> Optional format: claude_md, agents_md, cursor_rules, copilot_instructions',
681
1190
  ' --dry-run Show planned writes without changing files',
1191
+ '',
1192
+ 'Sync transport options:',
1193
+ ' push --root <path> Push canonical local project memory to hosted',
1194
+ ' pull-preview --root <path> Preview hosted proposals and remote state',
1195
+ ' pull-preview --no-pending-proposals Exclude pending proposals from preview',
1196
+ ' pull-export --proposal <id> Export a selected proposal; repeatable',
1197
+ ' pull-export --preview-token <token> Export from a specific preview token',
1198
+ ' pull-export --output <path> Output path under root; defaults to state/pull-export.json',
1199
+ ' apply-export --output <path> Apply exported bundle; defaults to state/pull-export.json',
1200
+ '',
1201
+ 'Docs-review options:',
1202
+ ' --root <path> Project-memory root. Defaults to .compass',
1203
+ ' --guided Accepted for the explicit comprehensive-review workflow',
1204
+ ' --submit-hosted Submit the generated review request to hosted sync',
1205
+ ' --poll-hosted Poll hosted docs-review run status and update state/docs-review.json',
1206
+ ' --complete Mark accepted docs-review changes as completed locally',
1207
+ ' --run-local Run the generated review request with a local provider',
1208
+ ' --provider <name> Local provider for --run-local. Currently: anthropic',
1209
+ ' --run-local-anthropic Compatibility alias for --run-local --provider anthropic',
1210
+ ' --apply-output Apply accepted architecture doc blocks from review output',
1211
+ ' --output <path> Review output path for --apply-output; defaults to state/docs-review-output.md',
1212
+ ' --apply-decision-artifact Append an accepted hosted decision artifact locally',
1213
+ ' --artifact <id> Artifact ID for --apply-decision-artifact',
1214
+ ' --refresh-index Also regenerate decisions/INDEX.md after applying a decision artifact',
1215
+ ' --llm <name> Preferred LLM/provider to run the external architecture review',
1216
+ ' --model <name> Model name to record for the review',
1217
+ ' --anthropic-env-var <name> Env var to use for local Anthropic docs-review runtime',
1218
+ ' --max-tokens <number> Local Anthropic max_tokens, 1024-32000. Defaults to 16000',
682
1219
  ].join('\n');
683
1220
  }
684
1221
 
@@ -690,6 +1227,14 @@ function createDefaultIo() {
690
1227
  };
691
1228
  }
692
1229
 
1230
+ function formatOptionalRootFlag(rootDir) {
1231
+ if (!rootDir || rootDir === '.compass') {
1232
+ return '';
1233
+ }
1234
+
1235
+ return ` --root ${rootDir}`;
1236
+ }
1237
+
693
1238
  async function main() {
694
1239
  try {
695
1240
  const exitCode = await runCli(process.argv.slice(2));