viepilot 2.4.0 → 2.12.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.
@@ -0,0 +1,80 @@
1
+ 'use strict';
2
+ /**
3
+ * google-slides-exporter.cjs
4
+ * Uploads a .pptx file to Google Drive and converts it to a Google Slides presentation.
5
+ *
6
+ * Requirements (optional — only needed when --slides flag is used):
7
+ * npm install @googleapis/slides
8
+ * export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json
9
+ *
10
+ * See docs/user/features/proposal.md → "Google Slides Export" for setup guide.
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ /**
17
+ * Upload a local .pptx file to Google Drive and convert to Google Slides.
18
+ *
19
+ * @param {string} pptxPath Absolute path to the .pptx file
20
+ * @param {string} title Title for the Google Slides presentation
21
+ * @returns {Promise<string>} Public edit URL of the created presentation
22
+ */
23
+ async function uploadToSlides(pptxPath, title) {
24
+ // Lazy-load @googleapis/slides — it is an optionalDependency.
25
+ // Provide a clear, actionable error when the package is not installed.
26
+ let googleApis;
27
+ try {
28
+ googleApis = require('googleapis');
29
+ } catch {
30
+ throw new Error(
31
+ 'Google Slides export requires the @googleapis/slides package.\n\n' +
32
+ 'Install it:\n' +
33
+ ' npm install @googleapis/slides\n\n' +
34
+ 'Then set your service account credentials:\n' +
35
+ ' export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json\n\n' +
36
+ 'See: docs/user/features/proposal.md → "Google Slides Export"'
37
+ );
38
+ }
39
+
40
+ const { google } = googleApis;
41
+
42
+ // Verify credentials env var is set before attempting auth
43
+ if (!process.env.GOOGLE_APPLICATION_CREDENTIALS) {
44
+ throw new Error(
45
+ 'GOOGLE_APPLICATION_CREDENTIALS environment variable is not set.\n\n' +
46
+ 'Set it to the path of your Google service account JSON key:\n' +
47
+ ' export GOOGLE_APPLICATION_CREDENTIALS=/path/to/service-account-key.json\n\n' +
48
+ 'See: docs/user/features/proposal.md → "Google Slides Export"'
49
+ );
50
+ }
51
+
52
+ // Authenticate via service account (no browser interaction required)
53
+ const auth = new google.auth.GoogleAuth({
54
+ scopes: [
55
+ 'https://www.googleapis.com/auth/drive',
56
+ 'https://www.googleapis.com/auth/presentations',
57
+ ],
58
+ });
59
+
60
+ const authClient = await auth.getClient();
61
+ const drive = google.drive({ version: 'v3', auth: authClient });
62
+
63
+ // Upload .pptx and request conversion to Google Slides format
64
+ const { data: file } = await drive.files.create({
65
+ requestBody: {
66
+ name: title,
67
+ mimeType: 'application/vnd.google-apps.presentation',
68
+ },
69
+ media: {
70
+ mimeType:
71
+ 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
72
+ body: fs.createReadStream(pptxPath),
73
+ },
74
+ fields: 'id',
75
+ });
76
+
77
+ return `https://docs.google.com/presentation/d/${file.id}/edit`;
78
+ }
79
+
80
+ module.exports = { uploadToSlides };
@@ -0,0 +1,249 @@
1
+ 'use strict';
2
+
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ // ─────────────────────────────────────────────────────────────────────────────
7
+ // Proposal type definitions
8
+ // ─────────────────────────────────────────────────────────────────────────────
9
+ const PROPOSAL_TYPES = {
10
+ 'project-proposal': { slides: 10, label: 'Project Proposal' },
11
+ 'tech-architecture': { slides: 12, label: 'Technical Architecture' },
12
+ 'product-pitch': { slides: 12, label: 'Product Pitch Deck' },
13
+ 'general': { slides: 8, label: 'General Proposal' },
14
+ };
15
+
16
+ // ─────────────────────────────────────────────────────────────────────────────
17
+ // Template resolution — 2-tier
18
+ // Tier 1: {projectRoot}/.viepilot/proposal-templates/{type}.{ext} (project override)
19
+ // Tier 2: {packageRoot}/templates/proposal/{ext}/{type}.{ext} (stock)
20
+ // ─────────────────────────────────────────────────────────────────────────────
21
+ function resolveTemplate(type, ext, projectRoot) {
22
+ const override = path.join(projectRoot, '.viepilot', 'proposal-templates', `${type}.${ext}`);
23
+ if (fs.existsSync(override)) return override;
24
+ // Stock fallback — lives next to this file at ../templates/proposal/{ext}/
25
+ return path.join(__dirname, '..', 'templates', 'proposal', ext, `${type}.${ext}`);
26
+ }
27
+
28
+ // ─────────────────────────────────────────────────────────────────────────────
29
+ // Context detection — auto-load latest brainstorm session
30
+ // ─────────────────────────────────────────────────────────────────────────────
31
+ function detectBrainstormSession(projectRoot) {
32
+ const dir = path.join(projectRoot, 'docs', 'brainstorm');
33
+ if (!fs.existsSync(dir)) return null;
34
+ const files = fs.readdirSync(dir)
35
+ .filter(f => f.startsWith('session-') && f.endsWith('.md'))
36
+ .sort()
37
+ .reverse();
38
+ return files.length ? path.join(dir, files[0]) : null;
39
+ }
40
+
41
+ // ─────────────────────────────────────────────────────────────────────────────
42
+ // Proposal type validation
43
+ // ─────────────────────────────────────────────────────────────────────────────
44
+ function validateType(type) {
45
+ if (!PROPOSAL_TYPES[type]) {
46
+ throw new Error(
47
+ `Unknown proposal type "${type}". Valid types: ${Object.keys(PROPOSAL_TYPES).join(', ')}`
48
+ );
49
+ }
50
+ return PROPOSAL_TYPES[type];
51
+ }
52
+
53
+ // ─────────────────────────────────────────────────────────────────────────────
54
+ // Manifest meta schema (ENH-040)
55
+ // ─────────────────────────────────────────────────────────────────────────────
56
+ /**
57
+ * Manifest meta object — collected via Step 2C quality brief.
58
+ *
59
+ * @typedef {Object} ProposalMeta
60
+ * @property {string} cta - Decision/action the proposal should drive
61
+ * @property {string} [budget] - Approximate budget range (optional)
62
+ * @property {string} [timeline] - Key deadline or constraint (optional)
63
+ * @property {string} decisionMaker - Who is the primary audience/decision-maker
64
+ */
65
+
66
+ // ─────────────────────────────────────────────────────────────────────────────
67
+ // Output path builder
68
+ // ─────────────────────────────────────────────────────────────────────────────
69
+ function buildOutputPaths(slug, projectRoot) {
70
+ const date = new Date().toISOString().slice(0, 10);
71
+ const base = path.join(projectRoot, 'docs', 'proposals', `${slug}-${date}`);
72
+ return {
73
+ md: `${base}.md`,
74
+ pptx: `${base}.pptx`,
75
+ docx: `${base}.docx`,
76
+ slides: `${base}-slides.txt`,
77
+ };
78
+ }
79
+
80
+ // ─────────────────────────────────────────────────────────────────────────────
81
+ // Language instruction builder (ENH-039)
82
+ // ─────────────────────────────────────────────────────────────────────────────
83
+
84
+ const LANG_NAMES = {
85
+ vi: 'Vietnamese', ja: 'Japanese', fr: 'French', zh: 'Chinese',
86
+ ko: 'Korean', de: 'German', es: 'Spanish', pt: 'Portuguese',
87
+ it: 'Italian', th: 'Thai', ar: 'Arabic', hi: 'Hindi',
88
+ };
89
+
90
+ /**
91
+ * Build a language instruction string to prepend to the AI content generation prompt.
92
+ *
93
+ * @param {string} lang - ISO 639-1 code (e.g. 'vi', 'en', 'ja')
94
+ * @param {boolean} [contentOnly=false] - if true, translate content only; keep structural labels English
95
+ * @returns {string} Instruction to inject into AI prompt (empty string for English)
96
+ */
97
+ function buildLangInstruction(lang, contentOnly = false) {
98
+ if (!lang || lang === 'en') {
99
+ return ''; // English is default — no explicit instruction needed
100
+ }
101
+ const langName = LANG_NAMES[lang] || lang.toUpperCase();
102
+
103
+ if (contentOnly) {
104
+ return (
105
+ `LANGUAGE INSTRUCTION: Generate all slide content (bullet points, body text, ` +
106
+ `speaker notes, and paragraph content) in ${langName}. ` +
107
+ `Keep structural labels, section names, and template placeholders in English.`
108
+ );
109
+ }
110
+ return (
111
+ `LANGUAGE INSTRUCTION: Generate ALL content — slide headings, bullet points, ` +
112
+ `body text, speaker notes, document section titles, and paragraph content — in ${langName}. ` +
113
+ `Do not mix languages.`
114
+ );
115
+ }
116
+
117
+ /** @type {Record<string, string[]>} */
118
+ const DIAGRAM_TYPES_BY_PROPOSAL = {
119
+ 'project-proposal': ['flowchart', 'gantt'],
120
+ 'tech-architecture': ['flowchart', 'sequenceDiagram', 'classDiagram'],
121
+ 'product-pitch': ['flowchart', 'sequenceDiagram'],
122
+ 'general': ['flowchart'],
123
+ };
124
+
125
+ /**
126
+ * Returns the list of Mermaid diagram types to generate for a given proposal type.
127
+ * @param {string} typeId - Proposal type ID (e.g. 'project-proposal')
128
+ * @returns {string[]} Array of Mermaid diagram type identifiers
129
+ */
130
+ function getDiagramTypes(typeId) {
131
+ return DIAGRAM_TYPES_BY_PROPOSAL[typeId] || ['flowchart'];
132
+ }
133
+
134
+ /** Known architect workspace page filenames (from vp-brainstorm --architect) */
135
+ const ARCHITECT_PAGES = [
136
+ 'architecture.html',
137
+ 'erd.html',
138
+ 'sequence-diagram.html',
139
+ 'feature-map.html',
140
+ 'user-use-cases.html',
141
+ 'deployment.html',
142
+ 'apis.html',
143
+ 'tech-notes.html',
144
+ 'data-flow.html',
145
+ 'decisions.html',
146
+ ];
147
+
148
+ /**
149
+ * Detect available HTML visual artifacts for screenshot embedding in PPTX slides.
150
+ * Scans the ui-direction session directory for index.html, pages/*.html, and
151
+ * architect workspace pages (architecture.html, erd.html, etc.).
152
+ *
153
+ * @param {string} [sessionDir] - Absolute path to a .viepilot/ui-direction/{session}/ dir.
154
+ * If omitted, auto-detects the latest session by scanning CWD/.viepilot/ui-direction/.
155
+ * @returns {{ uiPages: string[], architectPages: string[], sessionDir: string|null }}
156
+ */
157
+ function detectVisualArtifacts(sessionDir) {
158
+ const fs = require('fs');
159
+ const path = require('path');
160
+
161
+ // Auto-detect latest session when no dir provided
162
+ let resolvedDir = sessionDir || null;
163
+ if (!resolvedDir) {
164
+ const uiBase = path.join(process.cwd(), '.viepilot', 'ui-direction');
165
+ if (fs.existsSync(uiBase)) {
166
+ const entries = fs.readdirSync(uiBase)
167
+ .filter(e => {
168
+ try { return fs.statSync(path.join(uiBase, e)).isDirectory(); } catch { return false; }
169
+ })
170
+ .sort()
171
+ .reverse(); // ISO date dirs: latest first
172
+ if (entries.length > 0) resolvedDir = path.join(uiBase, entries[0]);
173
+ }
174
+ }
175
+
176
+ const result = { uiPages: [], architectPages: [], sessionDir: resolvedDir };
177
+ if (!resolvedDir || !fs.existsSync(resolvedDir)) return result;
178
+
179
+ // ui-direction index page
180
+ const indexHtml = path.join(resolvedDir, 'index.html');
181
+ if (fs.existsSync(indexHtml)) result.uiPages.push(indexHtml);
182
+
183
+ // ui-direction sub-pages
184
+ const pagesDir = path.join(resolvedDir, 'pages');
185
+ if (fs.existsSync(pagesDir)) {
186
+ const pageFiles = fs.readdirSync(pagesDir)
187
+ .filter(f => f.endsWith('.html'))
188
+ .sort()
189
+ .map(f => path.join(pagesDir, f));
190
+ result.uiPages.push(...pageFiles);
191
+ }
192
+
193
+ // Architect workspace pages (in same session dir)
194
+ for (const page of ARCHITECT_PAGES) {
195
+ const p = path.join(resolvedDir, page);
196
+ if (fs.existsSync(p)) result.architectPages.push(p);
197
+ }
198
+
199
+ return result;
200
+ }
201
+
202
+ // ─────────────────────────────────────────────────────────────────────────────
203
+ // Design configs — ENH-045
204
+ // ─────────────────────────────────────────────────────────────────────────────
205
+ const DESIGN_CONFIGS = {
206
+ 'modern-tech': {
207
+ colorPalette: 'navy-electric',
208
+ layoutStyle: 'modern-tech',
209
+ fontPair: 'Calibri / Calibri Light',
210
+ },
211
+ 'enterprise': {
212
+ colorPalette: 'navy-gold',
213
+ layoutStyle: 'enterprise',
214
+ fontPair: 'Georgia / Calibri',
215
+ },
216
+ 'creative': {
217
+ colorPalette: 'dark-vibrant',
218
+ layoutStyle: 'creative',
219
+ fontPair: 'Calibri / Calibri',
220
+ },
221
+ };
222
+
223
+ /**
224
+ * Choose design config based on project context signals (ENH-045).
225
+ * @param {Object} ctx - { sector?, audience?, tone?, proposalType? }
226
+ * @returns {{ colorPalette: string, layoutStyle: string, fontPair: string }}
227
+ */
228
+ function getDesignConfig(ctx = {}) {
229
+ const { sector = '', audience = '', tone = '' } = ctx;
230
+ const lower = `${sector} ${audience} ${tone}`.toLowerCase();
231
+ if (/bank|financ|enterprise|corp|legal|compliance|government/.test(lower))
232
+ return DESIGN_CONFIGS['enterprise'];
233
+ if (/startup|creative|design|game|media|agency|art/.test(lower))
234
+ return DESIGN_CONFIGS['creative'];
235
+ return DESIGN_CONFIGS['modern-tech'];
236
+ }
237
+
238
+ module.exports = {
239
+ PROPOSAL_TYPES,
240
+ resolveTemplate,
241
+ detectBrainstormSession,
242
+ validateType,
243
+ buildOutputPaths,
244
+ buildLangInstruction,
245
+ getDiagramTypes,
246
+ detectVisualArtifacts,
247
+ DESIGN_CONFIGS,
248
+ getDesignConfig,
249
+ };
@@ -0,0 +1,142 @@
1
+ 'use strict';
2
+ /**
3
+ * screenshot-artifact.cjs
4
+ * ENH-042 + ENH-043: Optional screenshot/render utility for vp-proposal visual embedding.
5
+ *
6
+ * - puppeteer (optional): screenshots HTML artifact files → PNG
7
+ * - mmdc CLI (optional): renders Mermaid source → PNG
8
+ * Both return null gracefully when the tool is absent — no crash, no hard dep.
9
+ *
10
+ * Usage:
11
+ * const { screenshotArtifact, isPuppeteerAvailable,
12
+ * isMmdcAvailable, renderMermaidToPng, cleanupScreenshot }
13
+ * = require('./lib/screenshot-artifact.cjs');
14
+ */
15
+
16
+ const os = require('os');
17
+ const path = require('path');
18
+ const fs = require('fs');
19
+ const cp = require('child_process');
20
+
21
+ /**
22
+ * Check if puppeteer is available without throwing.
23
+ * @returns {boolean}
24
+ */
25
+ function isPuppeteerAvailable() {
26
+ try { require.resolve('puppeteer'); return true; } catch { return false; }
27
+ }
28
+
29
+ /**
30
+ * Screenshot an HTML file to a temporary PNG using puppeteer (headless Chrome).
31
+ * Returns null silently when puppeteer is not installed.
32
+ *
33
+ * @param {string} htmlPath - Absolute path to the HTML file to screenshot
34
+ * @param {object} [opts]
35
+ * @param {number} [opts.width=1280] - Viewport width in px
36
+ * @param {number} [opts.height=720] - Viewport height in px (matches 16:9 slide ratio)
37
+ * @returns {Promise<string|null>} Absolute path to a temp PNG file, or null if unavailable
38
+ */
39
+ async function screenshotArtifact(htmlPath, opts = {}) {
40
+ // Graceful: return null if puppeteer not installed
41
+ let puppeteer;
42
+ try { puppeteer = require('puppeteer'); } catch { return null; }
43
+
44
+ if (!htmlPath || !fs.existsSync(htmlPath)) return null;
45
+
46
+ const { width = 1280, height = 720 } = opts;
47
+ const tmpFile = path.join(os.tmpdir(), `vp-artifact-${Date.now()}.png`);
48
+
49
+ const browser = await puppeteer.launch({
50
+ headless: 'new', // puppeteer ≥ 20 compat
51
+ args: ['--no-sandbox', '--disable-setuid-sandbox'], // CI-safe
52
+ });
53
+ try {
54
+ const page = await browser.newPage();
55
+ await page.setViewport({ width, height });
56
+ await page.goto(`file://${htmlPath}`, { waitUntil: 'networkidle0', timeout: 15000 });
57
+ await page.screenshot({ path: tmpFile, fullPage: false });
58
+ return tmpFile;
59
+ } finally {
60
+ await browser.close();
61
+ }
62
+ }
63
+
64
+ /**
65
+ * Clean up a temp screenshot file after embedding.
66
+ * @param {string|null} tmpPath
67
+ */
68
+ function cleanupScreenshot(tmpPath) {
69
+ if (tmpPath && fs.existsSync(tmpPath)) {
70
+ try { fs.unlinkSync(tmpPath); } catch { /* ignore cleanup errors */ }
71
+ }
72
+ }
73
+
74
+ // ── Mermaid rendering (ENH-043) ───────────────────────────────────────────────
75
+
76
+ /**
77
+ * Check if @mermaid-js/mermaid-cli (mmdc) is available on PATH.
78
+ * @returns {boolean}
79
+ */
80
+ function isMmdcAvailable() {
81
+ try {
82
+ const r = cp.spawnSync('mmdc', ['--version'], { encoding: 'utf8', timeout: 5000 });
83
+ return r.status === 0;
84
+ } catch {
85
+ return false;
86
+ }
87
+ }
88
+
89
+ /**
90
+ * Render a Mermaid diagram source string to a PNG file using the mmdc CLI.
91
+ * Returns null silently when mmdc is not available or rendering fails.
92
+ *
93
+ * @param {string} mermaidSource - Valid Mermaid 10+ source code
94
+ * @param {string} outputPath - Absolute path for the output .png file
95
+ * @returns {string|null} outputPath on success, null if mmdc absent or error
96
+ */
97
+ function renderMermaidToPng(mermaidSource, outputPath) {
98
+ if (!isMmdcAvailable()) return null;
99
+ if (!mermaidSource || !mermaidSource.trim()) return null;
100
+ if (!outputPath) return null;
101
+
102
+ const tmpInput = outputPath.replace(/\.png$/i, '.mmd');
103
+ try {
104
+ fs.writeFileSync(tmpInput, mermaidSource, 'utf8');
105
+ const r = cp.spawnSync(
106
+ 'mmdc',
107
+ ['-i', tmpInput, '-o', outputPath, '-b', 'white'],
108
+ { encoding: 'utf8', timeout: 30000 }
109
+ );
110
+ return (r.status === 0 && fs.existsSync(outputPath)) ? outputPath : null;
111
+ } catch {
112
+ return null;
113
+ } finally {
114
+ try { if (fs.existsSync(tmpInput)) fs.unlinkSync(tmpInput); } catch { /* ignore */ }
115
+ }
116
+ }
117
+
118
+ // ── Missing-tool warning (ENH-044) ───────────────────────────────────────────
119
+
120
+ /**
121
+ * Emit a standardized warning to stderr when a visual rendering tool is absent
122
+ * but visual artifacts exist and embedding is mandatory.
123
+ *
124
+ * @param {string} tool - Tool name (e.g. 'puppeteer', 'mmdc')
125
+ * @param {string} installCmd - Install hint (e.g. 'npm install puppeteer')
126
+ */
127
+ function warnMissingTool(tool, installCmd) {
128
+ process.stderr.write(
129
+ `\n[vp-proposal] ⚠ Visual artifacts found but '${tool}' is not installed.\n` +
130
+ ` Install to enable screenshots: ${installCmd}\n` +
131
+ ` Using placeholder/text fallback instead.\n\n`
132
+ );
133
+ }
134
+
135
+ module.exports = {
136
+ screenshotArtifact,
137
+ isPuppeteerAvailable,
138
+ cleanupScreenshot,
139
+ isMmdcAvailable,
140
+ renderMermaidToPng,
141
+ warnMissingTool,
142
+ };
@@ -13,12 +13,16 @@ const fs = require('fs');
13
13
  const path = require('path');
14
14
  const os = require('os');
15
15
 
16
- /** @type {{ language: { communication: string, document: string } }} */
16
+ /** @type {{ language: { communication: string, document: string }, proposal: { recentLangs: string[], defaultLang: string } }} */
17
17
  const DEFAULTS = {
18
18
  language: {
19
19
  communication: 'en',
20
20
  document: 'en',
21
21
  },
22
+ proposal: {
23
+ recentLangs: [], // MRU list, most recent first, max 5
24
+ defaultLang: 'en', // kept in sync with recentLangs[0]
25
+ },
22
26
  };
23
27
 
24
28
  /**
@@ -94,10 +98,37 @@ function resetConfig(overrideHomedir) {
94
98
  fs.writeFileSync(configPath, JSON.stringify(DEFAULTS, null, 2) + '\n', 'utf8');
95
99
  }
96
100
 
101
+ /**
102
+ * Get the suggested default language for proposals.
103
+ * Returns recentLangs[0] if present, else 'en'.
104
+ * @param {string | undefined} overrideHomedir
105
+ * @returns {string}
106
+ */
107
+ function getProposalLang(overrideHomedir) {
108
+ const cfg = readConfig(overrideHomedir);
109
+ const recent = cfg.proposal && cfg.proposal.recentLangs;
110
+ return (Array.isArray(recent) && recent.length > 0) ? recent[0] : 'en';
111
+ }
112
+
113
+ /**
114
+ * Record a used language: prepend to recentLangs, dedup, cap at 5, sync defaultLang.
115
+ * @param {string} lang - ISO 639-1 code
116
+ * @param {string | undefined} overrideHomedir
117
+ */
118
+ function recordProposalLang(lang, overrideHomedir) {
119
+ const cfg = readConfig(overrideHomedir);
120
+ const existing = (cfg.proposal && Array.isArray(cfg.proposal.recentLangs))
121
+ ? cfg.proposal.recentLangs : [];
122
+ const updated = [lang, ...existing.filter(l => l !== lang)].slice(0, 5);
123
+ writeConfig({ proposal: { recentLangs: updated, defaultLang: updated[0] } }, overrideHomedir);
124
+ }
125
+
97
126
  module.exports = {
98
127
  DEFAULTS,
99
128
  getConfigPath,
100
129
  readConfig,
101
130
  writeConfig,
102
131
  resetConfig,
132
+ getProposalLang,
133
+ recordProposalLang,
103
134
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "viepilot",
3
- "version": "2.4.0",
3
+ "version": "2.12.0",
4
4
  "description": "**Autonomous Vibe Coding Framework / Bộ khung phát triển tự động có kiểm soát**",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -72,6 +72,13 @@
72
72
  }
73
73
  }
74
74
  },
75
+ "dependencies": {
76
+ "docx": "^9.0.0",
77
+ "pptxgenjs": "^3.12.0"
78
+ },
79
+ "optionalDependencies": {
80
+ "@googleapis/slides": "^1.0.0"
81
+ },
75
82
  "devDependencies": {
76
83
  "jest": "^30.3.0"
77
84
  }
@@ -34,6 +34,17 @@ Audit ViePilot project state and documentation to detect drift.
34
34
  Works on **any project** using ViePilot (Java, Node, Python, etc.).
35
35
  Auto-detects if running inside the viepilot framework repo to enable framework-specific checks.
36
36
 
37
+ **Brownfield Import Compatibility (FEAT-018):**
38
+
39
+ When auditing a project bootstrapped via `vp-crystallize --brownfield`:
40
+
41
+ - If `docs/brainstorm/` exists and contains **only** `session-brownfield-import.md` (no `session-*.md` greenfield files):
42
+ - **Valid brownfield import** — do NOT flag as missing brainstorm session.
43
+ - Verify `session-brownfield-import.md` contains a `## Scan Report` section with a YAML block.
44
+ - If YAML block absent → flag LOW severity: "Brownfield stub missing Scan Report content."
45
+ - If `.viepilot/TRACKER.md` contains `## Brownfield Import` section → brownfield metadata confirmed; no further brainstorm check needed.
46
+ - If `docs/brainstorm/` is completely absent → flag MEDIUM severity: "No brainstorm session or brownfield stub found — run `/vp-crystallize` or `/vp-crystallize --brownfield`."
47
+
37
48
  **Tier 1 — ViePilot State Consistency (all projects):**
38
49
  - `.viepilot/TRACKER.md` current state vs `.viepilot/phases/*/PHASE-STATE.md`
39
50
  - `.viepilot/ROADMAP.md` phase status vs PHASE-STATE.md
@@ -77,6 +77,33 @@ Convert brainstorm sessions into structured artifacts for autonomous AI executio
77
77
  - `DOCUMENT_LANG` controls content language for all generated files (ROADMAP, TRACKER, ARCHITECTURE, etc.).
78
78
  - `COMMUNICATION_LANG` controls prompt/confirmation language for this session.
79
79
  - Configure via: `vp-tools config set language.document vi`
80
+
81
+ **Brownfield Mode (`--brownfield`) — FEAT-018:**
82
+
83
+ Use when adopting ViePilot on an **existing project** (no brainstorm session required).
84
+
85
+ Flags:
86
+ - `--brownfield` : Explicit brownfield mode
87
+ - *(auto-detected)* : Triggers when `docs/brainstorm/` is absent/empty AND `.viepilot/` does not exist
88
+
89
+ Scanner runs 12 signal categories across the existing codebase:
90
+ 1. **Build manifests** — `package.json`, `pom.xml`, `pyproject.toml`, `Cargo.toml`, `go.mod`, etc. (11 platforms) → infers project_name, version, language, deps
91
+ 2. **Framework detection** — 40+ dependency patterns → backend/frontend/ORM/auth/broker/test frameworks
92
+ 3. **Architecture layers** — 18 directory patterns → controller/service/repository/frontend/infra/etc.
93
+ 4. **Database schema signals** — Flyway/Liquibase/Prisma/Rails migrations + docker-compose services
94
+ 5. **API contracts** — OpenAPI, gRPC `.proto`, GraphQL schemas
95
+ 6. **Infrastructure** — Dockerfile, docker-compose, k8s, Terraform, Vercel, Fly.io, etc. (16 patterns)
96
+ 7. **Environment config** — `.env.example` key names (never reads `.env`)
97
+ 8. **Test coverage** — Jest/pytest/JUnit/Cypress config + coverage report dirs
98
+ 9. **Code quality tools** — ESLint/Prettier/SonarQube/pre-commit/golangci-lint/etc. (14 patterns)
99
+ 10. **Documentation** — README, CHANGELOG, ADRs, docs/ (priority-ordered)
100
+ 11. **Git history** — commit convention, version pattern, contributors, repo URL
101
+ 12. **Language survey** — file extension glob → language distribution
102
+
103
+ Produces **Scan Report** (YAML) with DETECTED / ASSUMED / MISSING classification.
104
+ MUST-DETECT gaps (project_name, primary_language, ≥1 framework, current_version) block artifact generation until user fills interactively.
105
+ Generates `docs/brainstorm/session-brownfield-import.md` stub for `vp-audit` compatibility.
106
+ Safety: never reads `.env`; skips `node_modules/`, `.git/`, `target/`, `build/`, `dist/`.
80
107
  </objective>
81
108
 
82
109
  <execution_context>