devloop.js 2.7 KB
/**
 * DevLoop · REST Routes (P1)
 *
 *   POST /api/devloop/projects
 *     body: { company_id, name?, requirement, mode?, target_path?, mock? }
 *     → 创建项目 + 跑 PM / Architect 两个 Agent (mock 模式产出 PRD/SDD)
 *     → 返 { pid, root_path, pm: {...}, architect: {...} }
 *
 *   GET /api/devloop/projects/:pid/state
 *     → 返 { pid, project, units, prd_rev, sdd_rev, prd_site, sdd_site, timeline_tail }
 *
 *   GET /api/devloop/projects   (额外:列项目)
 *     → 返 { projects: [...] }
 *
 * 规格: docs/prd/newfunction/_spec/plan-migration.md · P1 验收
 */

const express = require('express');
const router = express.Router();

const loopController = require('../src/core/devloop/loop-controller');
const pathResolver = require('../services/devloop/path-resolver');

const h = (fn) => async (req, res) => {
  try { await fn(req, res); }
  catch (e) {
    console.error(`[DevLoop] ${req.method} ${req.path}:`, e.message);
    res.status(e.statusCode || 500).json({ error: e.message });
  }
};

// POST /api/devloop/projects
router.post('/projects', h(async (req, res) => {
  const { company_id, requirement, mode, target_path, mock, name } = req.body || {};
  if (!company_id) {
    return res.status(400).json({ error: 'missing field: company_id' });
  }
  if (!requirement || typeof requirement !== 'string') {
    return res.status(400).json({ error: 'missing field: requirement' });
  }

  // 1. 创建项目 (sandbox 默认)
  const proj = await loopController.createProject({
    company_id,
    mode: mode || 'sandbox',
    target_path,
    idea: name ? `${name}: ${requirement}` : requirement,
  });

  // 2. PM → Architect (mock 模式)
  const phase = await loopController.runDocsPhase(proj.pid, requirement, {
    mock: mock !== false,
  });

  res.json({
    ok: true,
    pid: proj.pid,
    root_path: proj.root_path,
    mode: proj.mode,
    pm: {
      files_written: phase.pm.files_written.length,
      commit: phase.pm.commit?.sha || null,
      mock: phase.pm.mock,
    },
    architect: {
      files_written: phase.architect.files_written.length,
      commit: phase.architect.commit?.sha || null,
      mock: phase.architect.mock,
    },
  });
}));

// GET /api/devloop/projects
router.get('/projects', h(async (_req, res) => {
  const projects = pathResolver.listProjects();
  res.json({ projects });
}));

// GET /api/devloop/projects/:pid/state
router.get('/projects/:pid/state', h(async (req, res) => {
  try {
    const state = loopController.getProjectState(req.params.pid);
    res.json(state);
  } catch (e) {
    if (/Unknown project/.test(e.message)) {
      return res.status(404).json({ error: 'unknown project: ' + req.params.pid });
    }
    throw e;
  }
}));

module.exports = router;