@vibecompass/vibecompass 0.3.4 → 0.4.0

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/status.js ADDED
@@ -0,0 +1,350 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { inspectProjectCompatibility } from './compatibility.js';
4
+ import { parseSimpleYaml } from './simple-yaml.js';
5
+ import { listProjectSessions } from './session.js';
6
+ import { syncAgentInstructionFiles } from './generators/agent-files/index.js';
7
+
8
+ export async function getProjectStatus(options = {}) {
9
+ const cwd = options.cwd ? path.resolve(options.cwd) : process.cwd();
10
+ const rootDir = path.resolve(cwd, options.rootDir ?? '.compass');
11
+ const toolingRootDir = options.toolingRootDir
12
+ ? path.resolve(cwd, options.toolingRootDir)
13
+ : cwd;
14
+
15
+ const compatibility = await inspectProjectCompatibility({
16
+ rootDir,
17
+ cwd,
18
+ ...(options.packageVersion ? { packageVersion: options.packageVersion } : {}),
19
+ });
20
+ const [project, sessions, docsReview, agentFiles] = await Promise.all([
21
+ readProjectSummary(compatibility.projectFilePath),
22
+ readSessionsStatus(rootDir),
23
+ readDocsReviewStatus(rootDir),
24
+ readAgentFileStatus(rootDir, toolingRootDir),
25
+ ]);
26
+
27
+ return {
28
+ generatedAt: new Date().toISOString(),
29
+ rootDir,
30
+ toolingRootDir,
31
+ project,
32
+ compatibility: {
33
+ cliPackageVersion: compatibility.cliPackageVersion,
34
+ package: compatibility.package,
35
+ state: compatibility.state,
36
+ warnings: compatibility.warnings,
37
+ },
38
+ sessions,
39
+ docsReview,
40
+ agentFiles,
41
+ recommendations: buildRecommendations({
42
+ project,
43
+ compatibility,
44
+ sessions,
45
+ docsReview,
46
+ agentFiles,
47
+ }),
48
+ };
49
+ }
50
+
51
+ export function renderStatusText(status) {
52
+ if (status.project.status === 'unreadable') {
53
+ return [
54
+ 'VibeCompass status',
55
+ `Status: not initialized`,
56
+ `Generated: ${status.generatedAt}`,
57
+ `Root: ${status.rootDir}`,
58
+ `Message: Not a VibeCompass project memory root. Run \`vibecompass init --guided\` to set one up.`,
59
+ '',
60
+ 'Recommended next commands:',
61
+ '- vibecompass init --guided',
62
+ '',
63
+ ].join('\n');
64
+ }
65
+
66
+ const summary = hasStatusDrift(status) ? 'drift detected' : 'ok';
67
+ const lines = [
68
+ 'VibeCompass status',
69
+ `Status: ${summary}`,
70
+ `Generated: ${status.generatedAt}`,
71
+ `Project: ${status.project.name ?? 'Unknown'}`,
72
+ `Mode: ${status.project.mode ?? 'Unknown'}`,
73
+ `Root: ${status.rootDir}`,
74
+ `Repos: ${status.project.repos.length > 0 ? status.project.repos.map((repo) => repo.id).join(', ') : 'None recorded'}`,
75
+ `Active lanes: ${formatActiveLanes(status.sessions)}`,
76
+ '',
77
+ 'Package:',
78
+ `- CLI version: ${status.compatibility.cliPackageVersion}`,
79
+ `- Root stamp: ${formatPackageStatus(status.compatibility.package)}`,
80
+ ...(status.compatibility.state.packageObservedVersion
81
+ ? [`- Local observed: ${status.compatibility.state.packageObservedVersion} (from state/manifest.json)`]
82
+ : []),
83
+ `- State manifest: ${formatStateStatus(status.compatibility.state)}`,
84
+ ];
85
+
86
+ if (status.compatibility.warnings.length > 0) {
87
+ lines.push('', 'Warnings:');
88
+ for (const warning of status.compatibility.warnings) {
89
+ lines.push(`- ${warning.message}`);
90
+ }
91
+ }
92
+
93
+ lines.push(
94
+ '',
95
+ 'Docs review:',
96
+ `- ${formatDocsReviewStatus(status.docsReview)}`,
97
+ '',
98
+ 'Agent files:',
99
+ ...formatAgentFileStatus(status.agentFiles),
100
+ );
101
+
102
+ if (status.recommendations.length > 0) {
103
+ lines.push('', 'Recommended next commands:');
104
+ for (const recommendation of status.recommendations) {
105
+ lines.push(`- ${recommendation}`);
106
+ }
107
+ }
108
+
109
+ return `${lines.join('\n')}\n`;
110
+ }
111
+
112
+ export function toStatusJson(status) {
113
+ // Keep this as an explicit public-output shape. Nested status fields must
114
+ // remain free of raw manifests, preview tokens, and resolved env values.
115
+ return {
116
+ generatedAt: status.generatedAt,
117
+ rootDir: status.rootDir,
118
+ toolingRootDir: status.toolingRootDir,
119
+ project: status.project,
120
+ compatibility: status.compatibility,
121
+ sessions: status.sessions,
122
+ docsReview: status.docsReview,
123
+ agentFiles: status.agentFiles,
124
+ recommendations: status.recommendations,
125
+ };
126
+ }
127
+
128
+ async function readProjectSummary(projectFilePath) {
129
+ try {
130
+ const config = parseSimpleYaml(await readFile(projectFilePath, 'utf8'), {
131
+ sourceName: projectFilePath,
132
+ });
133
+ return {
134
+ status: 'ok',
135
+ name: typeof config.name === 'string' ? config.name : null,
136
+ mode: typeof config.mode === 'string' ? config.mode : null,
137
+ repos: Array.isArray(config.repos)
138
+ ? config.repos
139
+ .filter((repo) => repo && typeof repo === 'object')
140
+ .map((repo) => ({
141
+ id: typeof repo.id === 'string' ? repo.id : null,
142
+ remote: typeof repo.remote === 'string' ? repo.remote : null,
143
+ defaultBranch: typeof repo.default_branch === 'string' ? repo.default_branch : null,
144
+ }))
145
+ .filter((repo) => repo.id)
146
+ : [],
147
+ sync: config.sync && typeof config.sync === 'object'
148
+ ? {
149
+ provider: typeof config.sync.provider === 'string' ? config.sync.provider : null,
150
+ apiUrl: typeof config.sync.api_url === 'string' ? config.sync.api_url : null,
151
+ projectId: typeof config.sync.project_id === 'string' ? config.sync.project_id : null,
152
+ credentialSource: typeof config.sync.credential_source === 'string' ? config.sync.credential_source : null,
153
+ credentialEnvVar: typeof config.sync.credential_env_var === 'string' ? config.sync.credential_env_var : null,
154
+ }
155
+ : null,
156
+ };
157
+ } catch (error) {
158
+ return {
159
+ status: 'unreadable',
160
+ name: null,
161
+ mode: null,
162
+ repos: [],
163
+ sync: null,
164
+ errorMessage: error instanceof Error ? error.message : 'Unable to read project.yaml.',
165
+ };
166
+ }
167
+ }
168
+
169
+ async function readSessionsStatus(rootDir) {
170
+ try {
171
+ const sessions = await listProjectSessions({ rootDir });
172
+ return {
173
+ status: 'ok',
174
+ current: sessions.current,
175
+ lanes: sessions.lanes.map((lane) => ({
176
+ id: lane.id,
177
+ status: lane.status,
178
+ workingOn: lane.workingOn,
179
+ })),
180
+ };
181
+ } catch (error) {
182
+ return {
183
+ status: 'unreadable',
184
+ current: null,
185
+ lanes: [],
186
+ errorMessage: error instanceof Error ? error.message : 'Unable to read active sessions.',
187
+ };
188
+ }
189
+ }
190
+
191
+ async function readDocsReviewStatus(rootDir) {
192
+ const markerPath = path.join(rootDir, 'state', 'docs-review.json');
193
+ try {
194
+ const marker = JSON.parse(await readFile(markerPath, 'utf8'));
195
+ return {
196
+ status: typeof marker.status === 'string' ? marker.status : 'unknown',
197
+ markerPath,
198
+ completedAt: typeof marker.completed_at === 'string' ? marker.completed_at : null,
199
+ provider: typeof marker.provider === 'string' ? marker.provider : null,
200
+ model: typeof marker.model === 'string' ? marker.model : null,
201
+ };
202
+ } catch (error) {
203
+ if (error && typeof error === 'object' && 'code' in error && error.code === 'ENOENT') {
204
+ return {
205
+ status: 'missing',
206
+ markerPath,
207
+ };
208
+ }
209
+
210
+ return {
211
+ status: 'unreadable',
212
+ markerPath,
213
+ errorMessage: error instanceof Error ? error.message : 'Unable to read docs-review marker.',
214
+ };
215
+ }
216
+ }
217
+
218
+ async function readAgentFileStatus(rootDir, toolingRootDir) {
219
+ try {
220
+ const result = await syncAgentInstructionFiles({
221
+ rootDir,
222
+ toolingRootDir,
223
+ dryRun: true,
224
+ });
225
+ return {
226
+ status: 'ok',
227
+ dryRun: true,
228
+ results: result.results.map((item) => ({
229
+ format: item.format,
230
+ relativePath: item.relativePath,
231
+ status: item.status,
232
+ warning: item.warning,
233
+ changed: item.changed,
234
+ conflicts: item.conflicts ?? [],
235
+ })),
236
+ };
237
+ } catch (error) {
238
+ return {
239
+ status: 'unavailable',
240
+ dryRun: true,
241
+ results: [],
242
+ errorMessage: error instanceof Error ? error.message : 'Unable to inspect agent files.',
243
+ };
244
+ }
245
+ }
246
+
247
+ function buildRecommendations({ project, compatibility, sessions, docsReview, agentFiles }) {
248
+ const recommendations = [];
249
+ if (project.status === 'unreadable') {
250
+ recommendations.push('vibecompass init --guided');
251
+ return recommendations;
252
+ }
253
+
254
+ const packageStatus = compatibility.package.status;
255
+ const stateStatus = compatibility.state.status;
256
+ if (['legacy', 'behind', 'ahead', 'invalid'].includes(packageStatus) || ['missing', 'unreadable', 'unsupported'].includes(stateStatus)) {
257
+ recommendations.push('vibecompass refresh-workflow --dry-run');
258
+ }
259
+
260
+ if (docsReview.status === 'missing') {
261
+ recommendations.push('vibecompass docs-review --guided');
262
+ }
263
+
264
+ if (agentFiles.status === 'ok' && agentFiles.results.some((item) => item.changed || item.warning)) {
265
+ recommendations.push('vibecompass sync-agents --dry-run');
266
+ }
267
+
268
+ if (sessions.status === 'ok' && sessions.lanes.length === 0) {
269
+ recommendations.push('vibecompass start-session --id LANE_ID --working-on "TASK"');
270
+ } else if (sessions.status === 'ok' && sessions.lanes.length >= 2) {
271
+ recommendations.push('vibecompass list-sessions');
272
+ }
273
+
274
+ return [...new Set(recommendations)];
275
+ }
276
+
277
+ function hasStatusDrift(status) {
278
+ return (
279
+ status.compatibility.warnings.length > 0 ||
280
+ status.docsReview.status !== 'completed' ||
281
+ status.agentFiles.status !== 'ok' ||
282
+ status.agentFiles.results.some((item) => item.changed || item.warning)
283
+ );
284
+ }
285
+
286
+ function formatActiveLanes(sessions) {
287
+ if (sessions.status !== 'ok') {
288
+ return `unavailable (${sessions.errorMessage})`;
289
+ }
290
+
291
+ if (sessions.lanes.length === 0) {
292
+ return 'none';
293
+ }
294
+
295
+ return `${sessions.lanes.length}${sessions.current ? ` (current: ${sessions.current})` : ''}`;
296
+ }
297
+
298
+ function formatPackageStatus(packageStatus) {
299
+ if (packageStatus.status === 'legacy') {
300
+ return 'legacy/unknown';
301
+ }
302
+ if (packageStatus.status === 'unknown') {
303
+ return `unknown (${packageStatus.errorMessage})`;
304
+ }
305
+ if (packageStatus.status === 'invalid') {
306
+ return `invalid (${String(packageStatus.rootVersion)})`;
307
+ }
308
+ return `${packageStatus.rootVersion ?? 'none'} (${packageStatus.status})`;
309
+ }
310
+
311
+ function formatStateStatus(stateStatus) {
312
+ if (stateStatus.status === 'current') {
313
+ return `state_version ${stateStatus.observedVersion} (current)`;
314
+ }
315
+ if (stateStatus.status === 'unsupported') {
316
+ return `state_version ${stateStatus.observedVersion ?? 'missing'} (unsupported; expected ${stateStatus.expectedVersion})`;
317
+ }
318
+ if (stateStatus.status === 'unreadable') {
319
+ return `unreadable (${stateStatus.errorMessage})`;
320
+ }
321
+ if (stateStatus.status === 'missing') {
322
+ return 'no manifest yet (will be created on first command that writes state)';
323
+ }
324
+ return stateStatus.status;
325
+ }
326
+
327
+ function formatDocsReviewStatus(docsReview) {
328
+ if (docsReview.status === 'missing') {
329
+ return 'No docs-review marker found';
330
+ }
331
+ if (docsReview.status === 'unreadable') {
332
+ return `Marker unreadable (${docsReview.errorMessage})`;
333
+ }
334
+ return `${docsReview.status}${docsReview.completedAt ? ` at ${docsReview.completedAt}` : ''}`;
335
+ }
336
+
337
+ function formatAgentFileStatus(agentFiles) {
338
+ if (agentFiles.status !== 'ok') {
339
+ return [`- unavailable (${agentFiles.errorMessage})`];
340
+ }
341
+
342
+ if (agentFiles.results.length === 0) {
343
+ return ['- none configured'];
344
+ }
345
+
346
+ return agentFiles.results.map((item) => {
347
+ const warning = item.warning ? ` — ${item.warning}` : '';
348
+ return `- ${item.relativePath}: ${item.status}${warning}`;
349
+ });
350
+ }
package/src/version.js ADDED
@@ -0,0 +1,6 @@
1
+ import { createRequire } from 'node:module';
2
+
3
+ const require = createRequire(import.meta.url);
4
+ const packageJson = require('../package.json');
5
+
6
+ export const PACKAGE_VERSION = packageJson.version;