feat(mosaic): route prdy and mission --plan through PrdService (#1275)
- mosaic prdy becomes a thin adapter: --init/--update (wizard for TTY, service otherwise), --import with --accept-successor, --export - mosaic mission --plan persists the mission↔PRD linkage (mission id + version marker, PRD id + version) through the service instead of invoking the wizard as a second writer; non-TTY runs create non-interactively
This commit is contained in:
@@ -256,14 +256,41 @@ async function planMission(
|
|||||||
console.log(`Planning mission: ${mission.name}\n`);
|
console.log(`Planning mission: ${mission.name}\n`);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { runPrdWizard } = await import('@mosaicstack/prdy');
|
// Thin adapter: the PRD authority (create + mission↔PRD linkage) lives in
|
||||||
await runPrdWizard({
|
// PrdService — no second writer path. The mission's updatedAt serves as
|
||||||
|
// its version marker (the gateway exposes no numeric mission version).
|
||||||
|
const { PrdService, runPrdWizard } = await import('@mosaicstack/prdy');
|
||||||
|
const service = new PrdService({ projectPath: process.cwd() });
|
||||||
|
|
||||||
|
if (process.stdout.isTTY) {
|
||||||
|
const created = await runPrdWizard({
|
||||||
name: mission.name,
|
name: mission.name,
|
||||||
projectPath: process.cwd(),
|
projectPath: process.cwd(),
|
||||||
interactive: true,
|
interactive: true,
|
||||||
});
|
});
|
||||||
|
const linked = await service.linkMission({
|
||||||
|
prdId: created.id,
|
||||||
|
missionId: mission.id,
|
||||||
|
missionVersion: mission.updatedAt,
|
||||||
|
requirementIds: [],
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
`\nMission ${mission.id} linked to PRD ${linked.id} v${linked.version} (docs/prdy/).`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const doc = await service.planForMission({
|
||||||
|
name: mission.name,
|
||||||
|
missionId: mission.id,
|
||||||
|
missionVersion: mission.updatedAt,
|
||||||
|
requirementIds: [],
|
||||||
|
});
|
||||||
|
console.log(
|
||||||
|
`PRD created and linked: ${doc.id} v${doc.version} — mission ${mission.id} (docs/prdy/).`,
|
||||||
|
);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(`PRD wizard failed: ${err instanceof Error ? err.message : String(err)}`);
|
console.error(`PRD planning failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,10 @@ import type { Command } from 'commander';
|
|||||||
import { withAuth } from './with-auth.js';
|
import { withAuth } from './with-auth.js';
|
||||||
import { fetchProjects } from '../tui/gateway-api.js';
|
import { fetchProjects } from '../tui/gateway-api.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* `mosaic prdy` — thin adapter over PrdService (@mosaicstack/prdy).
|
||||||
|
* All reads/writes go through the service; there is no local writer path.
|
||||||
|
*/
|
||||||
export function registerPrdyCommand(program: Command) {
|
export function registerPrdyCommand(program: Command) {
|
||||||
const cmd = program
|
const cmd = program
|
||||||
.command('prdy')
|
.command('prdy')
|
||||||
@@ -9,12 +13,18 @@ export function registerPrdyCommand(program: Command) {
|
|||||||
.option('-g, --gateway <url>', 'Gateway URL', 'http://localhost:14242')
|
.option('-g, --gateway <url>', 'Gateway URL', 'http://localhost:14242')
|
||||||
.option('--init [name]', 'Create a new PRD')
|
.option('--init [name]', 'Create a new PRD')
|
||||||
.option('--update [name]', 'Update an existing PRD')
|
.option('--update [name]', 'Update an existing PRD')
|
||||||
|
.option('--import <file>', 'Import a YAML PRD document (validated, conflict-aware)')
|
||||||
|
.option('--accept-successor', 'With --import: accept a conflicted import as next version')
|
||||||
|
.option('--export [id]', 'Export a PRD as a labeled generated-view Markdown file')
|
||||||
.option('--project <idOrName>', 'Scope to project')
|
.option('--project <idOrName>', 'Scope to project')
|
||||||
.action(
|
.action(
|
||||||
async (opts: {
|
async (opts: {
|
||||||
gateway: string;
|
gateway: string;
|
||||||
init?: string | boolean;
|
init?: string | boolean;
|
||||||
update?: string | boolean;
|
update?: string | boolean;
|
||||||
|
import?: string;
|
||||||
|
acceptSuccessor?: boolean;
|
||||||
|
export?: string | boolean;
|
||||||
project?: string;
|
project?: string;
|
||||||
}) => {
|
}) => {
|
||||||
// Detect project context when --project flag is provided
|
// Detect project context when --project flag is provided
|
||||||
@@ -31,20 +41,69 @@ export function registerPrdyCommand(program: Command) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const { PrdService, runPrdWizard } = await import('@mosaicstack/prdy');
|
||||||
|
const service = new PrdService({ projectPath: process.cwd() });
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const { runPrdWizard } = await import('@mosaicstack/prdy');
|
if (opts.import !== undefined) {
|
||||||
|
const input = { filePath: opts.import };
|
||||||
|
|
||||||
|
if (opts.acceptSuccessor) {
|
||||||
|
const successor = await service.acceptSuccessor(input);
|
||||||
|
console.log(
|
||||||
|
`Import accepted as successor: ${successor.id} v${successor.version} (status: ${successor.status})`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await service.importDocument(input);
|
||||||
|
console.log(
|
||||||
|
result.kind === 'created'
|
||||||
|
? `Imported PRD ${result.document.id} v${result.document.version} (status: ${result.document.status})`
|
||||||
|
: `PRD ${result.document.id} already present with identical content — nothing to do.`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (opts.export !== undefined) {
|
||||||
|
const id =
|
||||||
|
typeof opts.export === 'string' && opts.export.length > 0 ? opts.export : undefined;
|
||||||
|
const result = await service.exportMarkdown({ id });
|
||||||
|
console.log(
|
||||||
|
`Generated view written: ${result.filePath} (source authority: YAML under docs/prdy/ — do not edit the Markdown)`,
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const name =
|
const name =
|
||||||
typeof opts.init === 'string'
|
typeof opts.init === 'string'
|
||||||
? opts.init
|
? opts.init
|
||||||
: typeof opts.update === 'string'
|
: typeof opts.update === 'string'
|
||||||
? opts.update
|
? opts.update
|
||||||
: 'untitled';
|
: 'untitled';
|
||||||
|
|
||||||
|
if (process.stdout.isTTY) {
|
||||||
await runPrdWizard({
|
await runPrdWizard({
|
||||||
name,
|
name,
|
||||||
projectPath: process.cwd(),
|
projectPath: process.cwd(),
|
||||||
interactive: true,
|
interactive: true,
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Non-interactive fallback routes through the service directly.
|
||||||
|
const doc = await service.create({ name });
|
||||||
|
console.log(`PRD created: ${doc.id} v${doc.version} (status: ${doc.status})`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
|
if (err instanceof Error && err.name === 'PrdImportConflictError') {
|
||||||
|
const conflict = err as { proposal?: { version?: number } };
|
||||||
|
console.error(`${err.message}`);
|
||||||
|
console.error(
|
||||||
|
`Original PRD left untouched. To accept the proposed successor (v${conflict.proposal?.version}), re-run with --accept-successor.`,
|
||||||
|
);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
console.error(`PRD wizard failed: ${err instanceof Error ? err.message : String(err)}`);
|
console.error(`PRD wizard failed: ${err instanceof Error ? err.message : String(err)}`);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user