mirror of
https://github.com/Fission-AI/OpenSpec.git
synced 2026-10-04 06:18:24 +08:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
03ea32369a |
+2
-2
@@ -3,7 +3,6 @@ import { createRequire } from 'module';
|
||||
import ora from 'ora';
|
||||
import path from 'path';
|
||||
import { promises as fs } from 'fs';
|
||||
import { InitCommand } from '../core/init.js';
|
||||
import { AI_TOOLS } from '../core/config.js';
|
||||
import { UpdateCommand } from '../core/update.js';
|
||||
import { ListCommand } from '../core/list.js';
|
||||
@@ -30,7 +29,7 @@ program.option('--no-color', 'Disable color output');
|
||||
// Apply global flags before any command runs
|
||||
program.hook('preAction', (thisCommand) => {
|
||||
const opts = thisCommand.opts();
|
||||
if (opts.noColor) {
|
||||
if (opts.color === false) {
|
||||
process.env.NO_COLOR = '1';
|
||||
}
|
||||
});
|
||||
@@ -63,6 +62,7 @@ program
|
||||
}
|
||||
}
|
||||
|
||||
const { InitCommand } = await import('../core/init.js');
|
||||
const initCommand = new InitCommand({
|
||||
tools: options?.tools,
|
||||
});
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import { select } from '@inquirer/prompts';
|
||||
import { JsonConverter } from '../core/converters/json-converter.js';
|
||||
import { Validator } from '../core/validation/validator.js';
|
||||
import { ChangeParser } from '../core/parsers/change-parser.js';
|
||||
@@ -30,9 +29,10 @@ export class ChangeCommand {
|
||||
const changesPath = path.join(process.cwd(), 'openspec', 'changes');
|
||||
|
||||
if (!changeName) {
|
||||
const canPrompt = isInteractive(options?.noInteractive);
|
||||
const canPrompt = isInteractive(options);
|
||||
const changes = await this.getActiveChanges(changesPath);
|
||||
if (canPrompt && changes.length > 0) {
|
||||
const { select } = await import('@inquirer/prompts');
|
||||
const selected = await select({
|
||||
message: 'Select a change to show',
|
||||
choices: changes.map(id => ({ name: id, value: id })),
|
||||
@@ -186,9 +186,10 @@ export class ChangeCommand {
|
||||
const changesPath = path.join(process.cwd(), 'openspec', 'changes');
|
||||
|
||||
if (!changeName) {
|
||||
const canPrompt = isInteractive(options?.noInteractive);
|
||||
const canPrompt = isInteractive(options);
|
||||
const changes = await getActiveChangeIds();
|
||||
if (canPrompt && changes.length > 0) {
|
||||
const { select } = await import('@inquirer/prompts');
|
||||
const selected = await select({
|
||||
message: 'Select a change to validate',
|
||||
choices: changes.map(id => ({ name: id, value: id })),
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import ora from 'ora';
|
||||
import { confirm } from '@inquirer/prompts';
|
||||
import { CompletionFactory } from '../core/completions/factory.js';
|
||||
import { COMMAND_REGISTRY } from '../core/completions/command-registry.js';
|
||||
import { detectShell, SupportedShell } from '../utils/shell-detection.js';
|
||||
@@ -179,6 +178,7 @@ export class CompletionCommand {
|
||||
|
||||
// Prompt for confirmation unless --yes flag is provided
|
||||
if (!skipConfirmation) {
|
||||
const { confirm } = await import('@inquirer/prompts');
|
||||
const confirmed = await confirm({
|
||||
message: 'Remove OpenSpec configuration from ~/.zshrc?',
|
||||
default: false,
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { select } from '@inquirer/prompts';
|
||||
import path from 'path';
|
||||
import { isInteractive } from '../utils/interactive.js';
|
||||
import { getActiveChangeIds, getSpecIds } from '../utils/item-discovery.js';
|
||||
@@ -13,11 +12,12 @@ const SPEC_FLAG_KEYS = new Set(['requirements', 'scenarios', 'requirement']);
|
||||
|
||||
export class ShowCommand {
|
||||
async execute(itemName?: string, options: { json?: boolean; type?: string; noInteractive?: boolean; [k: string]: any } = {}): Promise<void> {
|
||||
const interactive = isInteractive(options.noInteractive);
|
||||
const interactive = isInteractive(options);
|
||||
const typeOverride = this.normalizeType(options.type);
|
||||
|
||||
if (!itemName) {
|
||||
if (interactive) {
|
||||
const { select } = await import('@inquirer/prompts');
|
||||
const type = await select<ItemType>({
|
||||
message: 'What would you like to show?',
|
||||
choices: [
|
||||
@@ -44,6 +44,7 @@ export class ShowCommand {
|
||||
}
|
||||
|
||||
private async runInteractiveByType(type: ItemType, options: { json?: boolean; noInteractive?: boolean; [k: string]: any }): Promise<void> {
|
||||
const { select } = await import('@inquirer/prompts');
|
||||
if (type === 'change') {
|
||||
const changes = await getActiveChangeIds();
|
||||
if (changes.length === 0) {
|
||||
@@ -135,5 +136,3 @@ export class ShowCommand {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -4,7 +4,6 @@ import { join } from 'path';
|
||||
import { MarkdownParser } from '../core/parsers/markdown-parser.js';
|
||||
import { Validator } from '../core/validation/validator.js';
|
||||
import type { Spec } from '../core/schemas/index.js';
|
||||
import { select } from '@inquirer/prompts';
|
||||
import { isInteractive } from '../utils/interactive.js';
|
||||
import { getSpecIds } from '../utils/item-discovery.js';
|
||||
|
||||
@@ -70,9 +69,10 @@ export class SpecCommand {
|
||||
|
||||
async show(specId?: string, options: ShowOptions = {}): Promise<void> {
|
||||
if (!specId) {
|
||||
const canPrompt = isInteractive(options?.noInteractive);
|
||||
const canPrompt = isInteractive(options);
|
||||
const specIds = await getSpecIds();
|
||||
if (canPrompt && specIds.length > 0) {
|
||||
const { select } = await import('@inquirer/prompts');
|
||||
specId = await select({
|
||||
message: 'Select a spec to show',
|
||||
choices: specIds.map(id => ({ name: id, value: id })),
|
||||
@@ -204,9 +204,10 @@ export function registerSpecCommand(rootProgram: typeof program) {
|
||||
.action(async (specId: string | undefined, options: { strict?: boolean; json?: boolean; noInteractive?: boolean }) => {
|
||||
try {
|
||||
if (!specId) {
|
||||
const canPrompt = isInteractive(options?.noInteractive);
|
||||
const canPrompt = isInteractive(options);
|
||||
const specIds = await getSpecIds();
|
||||
if (canPrompt && specIds.length > 0) {
|
||||
const { select } = await import('@inquirer/prompts');
|
||||
specId = await select({
|
||||
message: 'Select a spec to validate',
|
||||
choices: specIds.map(id => ({ name: id, value: id })),
|
||||
@@ -247,4 +248,4 @@ export function registerSpecCommand(rootProgram: typeof program) {
|
||||
});
|
||||
|
||||
return specCommand;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { select } from '@inquirer/prompts';
|
||||
import ora from 'ora';
|
||||
import path from 'path';
|
||||
import { Validator } from '../core/validation/validator.js';
|
||||
@@ -29,7 +28,7 @@ interface BulkItemResult {
|
||||
|
||||
export class ValidateCommand {
|
||||
async execute(itemName: string | undefined, options: ExecuteOptions = {}): Promise<void> {
|
||||
const interactive = isInteractive(options.noInteractive);
|
||||
const interactive = isInteractive(options);
|
||||
|
||||
// Handle bulk flags first
|
||||
if (options.all || options.changes || options.specs) {
|
||||
@@ -64,6 +63,7 @@ export class ValidateCommand {
|
||||
}
|
||||
|
||||
private async runInteractiveSelector(opts: { strict: boolean; json: boolean; concurrency?: string }): Promise<void> {
|
||||
const { select } = await import('@inquirer/prompts');
|
||||
const choice = await select({
|
||||
message: 'What would you like to validate?',
|
||||
choices: [
|
||||
@@ -212,6 +212,28 @@ export class ValidateCommand {
|
||||
});
|
||||
}
|
||||
|
||||
if (queue.length === 0) {
|
||||
spinner?.stop();
|
||||
|
||||
const summary = {
|
||||
totals: { items: 0, passed: 0, failed: 0 },
|
||||
byType: {
|
||||
...(scope.changes ? { change: { items: 0, passed: 0, failed: 0 } } : {}),
|
||||
...(scope.specs ? { spec: { items: 0, passed: 0, failed: 0 } } : {}),
|
||||
},
|
||||
} as const;
|
||||
|
||||
if (opts.json) {
|
||||
const out = { items: [] as BulkItemResult[], summary, version: '1.0' };
|
||||
console.log(JSON.stringify(out, null, 2));
|
||||
} else {
|
||||
console.log('No items found to validate.');
|
||||
}
|
||||
|
||||
process.exitCode = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
const results: BulkItemResult[] = [];
|
||||
let index = 0;
|
||||
let running = 0;
|
||||
@@ -301,5 +323,3 @@ function getPlannedType(index: number, changeIds: string[], specIds: string[]):
|
||||
if (specIndex >= 0 && specIndex < specIds.length) return 'spec';
|
||||
return undefined;
|
||||
}
|
||||
|
||||
|
||||
|
||||
+4
-1
@@ -1,6 +1,5 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import path from 'path';
|
||||
import { select, confirm } from '@inquirer/prompts';
|
||||
import { FileSystemUtils } from '../utils/file-system.js';
|
||||
import { getTaskProgressForChange, formatTaskStatus } from '../utils/task-progress.js';
|
||||
import { Validator } from './validation/validator.js';
|
||||
@@ -125,6 +124,7 @@ export class ArchiveCommand {
|
||||
const timestamp = new Date().toISOString();
|
||||
|
||||
if (!options.yes) {
|
||||
const { confirm } = await import('@inquirer/prompts');
|
||||
const proceed = await confirm({
|
||||
message: chalk.yellow('⚠️ WARNING: Skipping validation may archive invalid specs. Continue? (y/N)'),
|
||||
default: false
|
||||
@@ -149,6 +149,7 @@ export class ArchiveCommand {
|
||||
const incompleteTasks = Math.max(progress.total - progress.completed, 0);
|
||||
if (incompleteTasks > 0) {
|
||||
if (!options.yes) {
|
||||
const { confirm } = await import('@inquirer/prompts');
|
||||
const proceed = await confirm({
|
||||
message: `Warning: ${incompleteTasks} incomplete task(s) found. Continue?`,
|
||||
default: false
|
||||
@@ -179,6 +180,7 @@ export class ArchiveCommand {
|
||||
|
||||
let shouldUpdateSpecs = true;
|
||||
if (!options.yes) {
|
||||
const { confirm } = await import('@inquirer/prompts');
|
||||
shouldUpdateSpecs = await confirm({
|
||||
message: 'Proceed with spec updates?',
|
||||
default: true
|
||||
@@ -256,6 +258,7 @@ export class ArchiveCommand {
|
||||
}
|
||||
|
||||
private async selectChange(changesDir: string): Promise<string | null> {
|
||||
const { select } = await import('@inquirer/prompts');
|
||||
// Get all directories in changes (excluding archive)
|
||||
const entries = await fs.readdir(changesDir, { withFileTypes: true });
|
||||
const changeDirs = entries
|
||||
|
||||
@@ -1,7 +1,22 @@
|
||||
export function isInteractive(noInteractiveFlag?: boolean): boolean {
|
||||
if (noInteractiveFlag) return false;
|
||||
type InteractiveOptions = {
|
||||
/**
|
||||
* Explicit "disable prompts" flag passed by internal callers.
|
||||
*/
|
||||
noInteractive?: boolean;
|
||||
/**
|
||||
* Commander-style negated option: `--no-interactive` sets this to false.
|
||||
*/
|
||||
interactive?: boolean;
|
||||
};
|
||||
|
||||
function resolveNoInteractive(value?: boolean | InteractiveOptions): boolean {
|
||||
if (typeof value === 'boolean') return value;
|
||||
return value?.noInteractive === true || value?.interactive === false;
|
||||
}
|
||||
|
||||
export function isInteractive(value?: boolean | InteractiveOptions): boolean {
|
||||
if (resolveNoInteractive(value)) return false;
|
||||
if (process.env.OPEN_SPEC_INTERACTIVE === '0') return false;
|
||||
return !!process.stdin.isTTY;
|
||||
}
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user