diff --git a/package-lock.json b/package-lock.json index e1daf932f..03dad207d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@fission-ai/openspec", - "version": "1.1.1", + "version": "1.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@fission-ai/openspec", - "version": "1.1.1", + "version": "1.2.0", "hasInstallScript": true, "license": "MIT", "dependencies": { @@ -1803,6 +1803,7 @@ "integrity": "sha512-oH72nZRfDv9lADUBSo104Aq7gPHpQZc4BTx38r9xf9pg5LfP6EzSyH2n7qFmmxRQXh7YlUXODcYsg6PuTDSxGg==", "devOptional": true, "license": "MIT", + "peer": true, "dependencies": { "undici-types": "~7.16.0" } @@ -1852,6 +1853,7 @@ "integrity": "sha512-IgSWvLobTDOjnaxAfDTIHaECbkNlAlKv2j5SjpB2v7QHKv1FIfjwMy8FsDbVfDX/KjmCmYICcw7uGaXLhtsLNg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@typescript-eslint/scope-manager": "8.56.0", "@typescript-eslint/types": "8.56.0", @@ -2182,6 +2184,7 @@ "integrity": "sha512-hGISOaP18plkzbWEcP/QvtRW1xDXF2+96HbEX6byqQhAUbiS5oH6/9JwW+QsQCIYON2bI6QZBF+2PvOmrRZ9wA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@vitest/utils": "3.2.4", "fflate": "^0.8.2", @@ -2219,6 +2222,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2685,6 +2689,7 @@ "integrity": "sha512-VmQ+sifHUbI/IcSopBCF/HO3YiHQx/AVd3UVyYL6weuwW+HvON9VYn5l6Zl1WZzPWXPNZrSQpxwkkZ/VuvJZzg==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -4448,6 +4453,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4546,6 +4552,7 @@ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", + "peer": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -4611,6 +4618,7 @@ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -4727,6 +4735,7 @@ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4740,6 +4749,7 @@ "integrity": "sha512-LUCP5ev3GURDysTWiP47wRRUpLKMOfPh+yKTx3kVIEiu5KOMeqzpnYNsKyOoVrULivR8tLcks4+lga33Whn90A==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/chai": "^5.2.2", "@vitest/expect": "3.2.4", @@ -4919,6 +4929,7 @@ "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", "license": "ISC", + "peer": true, "bin": { "yaml": "bin.mjs" }, diff --git a/package.json b/package.json index 4087d8a2a..48493b9f4 100644 --- a/package.json +++ b/package.json @@ -79,5 +79,6 @@ "posthog-node": "^5.20.0", "yaml": "^2.8.2", "zod": "^4.0.17" - } + }, + "packageManager": "pnpm@10.30.3+sha512.c961d1e0a2d8e354ecaa5166b822516668b7f44cb5bd95122d590dd81922f606f5473b6d23ec4a5be05e7fcd18e8488d47d978bbe981872f1145d06e9a740017" } diff --git a/src/core/view.ts b/src/core/view.ts index e67c35268..f82c57f23 100644 --- a/src/core/view.ts +++ b/src/core/view.ts @@ -3,6 +3,7 @@ import * as path from 'path'; import chalk from 'chalk'; import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progress.js'; import { MarkdownParser } from './parsers/markdown-parser.js'; +import { loadChangeContext, formatChangeStatus } from './artifact-graph/index.js'; export class ViewCommand { async execute(targetPath: string = '.'): Promise { @@ -46,6 +47,11 @@ export class ViewCommand { console.log( ` ${chalk.yellow('◉')} ${chalk.bold(change.name.padEnd(30))} ${progressBar} ${chalk.dim(`${percentage}%`)}` ); + + if (change.workflowStatus) { + const { schema, artifacts } = change.workflowStatus; + console.log(` ${chalk.dim(`└─ [${schema}] ${artifacts}`)}`); + } }); } @@ -80,17 +86,26 @@ export class ViewCommand { private async getChangesData(openspecDir: string): Promise<{ draft: Array<{ name: string }>; - active: Array<{ name: string; progress: { total: number; completed: number } }>; + active: Array<{ + name: string; + progress: { total: number; completed: number }; + workflowStatus?: { schema: string; artifacts: string } | null; + }>; completed: Array<{ name: string }>; }> { const changesDir = path.join(openspecDir, 'changes'); + const projectRoot = path.dirname(openspecDir); if (!fs.existsSync(changesDir)) { return { draft: [], active: [], completed: [] }; } const draft: Array<{ name: string }> = []; - const active: Array<{ name: string; progress: { total: number; completed: number } }> = []; + const active: Array<{ + name: string; + progress: { total: number; completed: number }; + workflowStatus?: { schema: string; artifacts: string } | null; + }> = []; const completed: Array<{ name: string }> = []; const entries = fs.readdirSync(changesDir, { withFileTypes: true }); @@ -107,7 +122,19 @@ export class ViewCommand { completed.push({ name: entry.name }); } else { // Has tasks but not all complete - active.push({ name: entry.name, progress }); + // Try to load workflow status + let workflowStatus: { schema: string; artifacts: string } | null = null; + try { + const context = loadChangeContext(projectRoot, entry.name); + const status = formatChangeStatus(context); + workflowStatus = { + schema: status.schemaName, + artifacts: this.formatWorkflowArtifacts(status.artifacts), + }; + } catch (e) { + // Change doesn't have valid workflow metadata, skip workflow status + } + active.push({ name: entry.name, progress, workflowStatus }); } } } @@ -206,14 +233,35 @@ export class ViewCommand { private createProgressBar(completed: number, total: number, width: number = 20): string { if (total === 0) return chalk.dim('─'.repeat(width)); - + const percentage = completed / total; const filled = Math.round(percentage * width); const empty = width - filled; - + const filledBar = chalk.green('█'.repeat(filled)); const emptyBar = chalk.dim('░'.repeat(empty)); - + return `[${filledBar}${emptyBar}]`; } + + /** + * Formats workflow artifact status into a compact string. + * Examples: + * - "proposal ✓ specs → design tasks" + * - "proposal ✓ specs ✓ design → tasks" + * - "proposal → specs design tasks" + */ + private formatWorkflowArtifacts(artifacts: Array<{ id: string; status: 'done' | 'ready' | 'blocked' }>): string { + return artifacts + .map((artifact) => { + if (artifact.status === 'done') { + return `${artifact.id}${chalk.green('✓')}`; + } else if (artifact.status === 'ready') { + return `${artifact.id}${chalk.cyan('→')}`; + } else { + return artifact.id; + } + }) + .join(' '); + } } \ No newline at end of file