Add preset devflow init bootstrap#65
Conversation
There was a problem hiding this comment.
Code Review
This pull request introduces a preset-aware bootstrap and initialization path for Devflow Native via the devflow init command and a new devflow.init MCP tool. It supports configuring presets (solo-product, research, content-site), targets, CI providers, and review policies, while automatically inferring verification gates from package.json scripts and safely augmenting existing AGENTS.md files. Feedback on the changes suggests improving the robustness of input normalization helpers by using loose equality checks to handle explicit null values, and refining the AGENTS.md appending logic to prevent unnecessary leading newlines in empty files.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| function normalizeInitPreset(input = {}) { | ||
| const rawPreset = input.preset ?? input.profile ?? "standard"; | ||
| const preset = String(rawPreset || "standard").trim() || "standard"; | ||
| if (input.preset !== undefined && !INIT_PRESETS.has(preset)) { | ||
| throw new Error(`Unsupported devflow init preset: ${preset}`); | ||
| } | ||
| return preset; | ||
| } |
There was a problem hiding this comment.
Using loose equality check != null is safer and more robust than strict inequality !== undefined when normalizing input presets, as it correctly handles both null and undefined values that might be passed programmatically (e.g., via MCP clients).
| function normalizeInitPreset(input = {}) { | |
| const rawPreset = input.preset ?? input.profile ?? "standard"; | |
| const preset = String(rawPreset || "standard").trim() || "standard"; | |
| if (input.preset !== undefined && !INIT_PRESETS.has(preset)) { | |
| throw new Error(`Unsupported devflow init preset: ${preset}`); | |
| } | |
| return preset; | |
| } | |
| function normalizeInitPreset(input = {}) { | |
| const rawPreset = input.preset ?? input.profile ?? "standard"; | |
| const preset = String(rawPreset || "standard").trim() || "standard"; | |
| if (input.preset != null && !INIT_PRESETS.has(preset)) { | |
| throw new Error("Unsupported devflow init preset: " + preset); | |
| } | |
| return preset; | |
| } |
| function normalizeInitTargets(targets) { | ||
| if (targets === undefined) { | ||
| return []; | ||
| } | ||
| if (Array.isArray(targets) && targets.length === 0) { | ||
| return []; | ||
| } | ||
| return normalizeHarnessTargets(Array.isArray(targets) ? targets : [targets]) | ||
| .filter((target) => target === "codex" || target === "claude"); | ||
| } |
There was a problem hiding this comment.
Using targets == null instead of targets === undefined ensures that explicit null values passed programmatically are safely handled and default to an empty array, preventing downstream type errors or unexpected behavior.
| function normalizeInitTargets(targets) { | |
| if (targets === undefined) { | |
| return []; | |
| } | |
| if (Array.isArray(targets) && targets.length === 0) { | |
| return []; | |
| } | |
| return normalizeHarnessTargets(Array.isArray(targets) ? targets : [targets]) | |
| .filter((target) => target === "codex" || target === "claude"); | |
| } | |
| function normalizeInitTargets(targets) { | |
| if (targets == null) { | |
| return []; | |
| } | |
| if (Array.isArray(targets) && targets.length === 0) { | |
| return []; | |
| } | |
| return normalizeHarnessTargets(Array.isArray(targets) ? targets : [targets]) | |
| .filter((target) => target === "codex" || target === "claude"); | |
| } |
| function normalizeInitCi(ci) { | ||
| if (ci === undefined || ci === false) { | ||
| return "none"; | ||
| } | ||
| const normalized = String(ci).trim().toLowerCase(); | ||
| if (normalized === "none") { | ||
| return "none"; | ||
| } | ||
| if (normalized !== "github") { | ||
| throw new Error(`Unsupported devflow init ci provider: ${ci}`); | ||
| } | ||
| return normalized; | ||
| } |
There was a problem hiding this comment.
Checking ci == null instead of ci === undefined ensures that explicit null values are safely normalized to "none" instead of throwing an unsupported provider error due to string coercion of null.
| function normalizeInitCi(ci) { | |
| if (ci === undefined || ci === false) { | |
| return "none"; | |
| } | |
| const normalized = String(ci).trim().toLowerCase(); | |
| if (normalized === "none") { | |
| return "none"; | |
| } | |
| if (normalized !== "github") { | |
| throw new Error(`Unsupported devflow init ci provider: ${ci}`); | |
| } | |
| return normalized; | |
| } | |
| function normalizeInitCi(ci) { | |
| if (ci == null || ci === false) { | |
| return "none"; | |
| } | |
| const normalized = String(ci).trim().toLowerCase(); | |
| if (normalized === "none") { | |
| return "none"; | |
| } | |
| if (normalized !== "github") { | |
| throw new Error("Unsupported devflow init ci provider: " + ci); | |
| } | |
| return normalized; | |
| } |
| function normalizeInitReviewRequired(review, preset) { | ||
| if (review === undefined) { | ||
| return preset === "standard" || preset === "solo-product"; | ||
| } | ||
| if (review === true) { | ||
| return true; | ||
| } | ||
| if (review === false) { | ||
| return false; | ||
| } | ||
| const normalized = String(review).trim().toLowerCase(); | ||
| if (normalized === "required") { | ||
| return true; | ||
| } | ||
| if (normalized === "optional" || normalized === "none") { | ||
| return false; | ||
| } | ||
| throw new Error(`Unsupported devflow init review mode: ${review}`); | ||
| } |
There was a problem hiding this comment.
Using review == null instead of review === undefined ensures that explicit null values are safely handled and default to the preset-based policy, avoiding unexpected errors during string coercion of null.
| function normalizeInitReviewRequired(review, preset) { | |
| if (review === undefined) { | |
| return preset === "standard" || preset === "solo-product"; | |
| } | |
| if (review === true) { | |
| return true; | |
| } | |
| if (review === false) { | |
| return false; | |
| } | |
| const normalized = String(review).trim().toLowerCase(); | |
| if (normalized === "required") { | |
| return true; | |
| } | |
| if (normalized === "optional" || normalized === "none") { | |
| return false; | |
| } | |
| throw new Error(`Unsupported devflow init review mode: ${review}`); | |
| } | |
| function normalizeInitReviewRequired(review, preset) { | |
| if (review == null) { | |
| return preset === "standard" || preset === "solo-product"; | |
| } | |
| if (review === true) { | |
| return true; | |
| } | |
| if (review === false) { | |
| return false; | |
| } | |
| const normalized = String(review).trim().toLowerCase(); | |
| if (normalized === "required") { | |
| return true; | |
| } | |
| if (normalized === "optional" || normalized === "none") { | |
| return false; | |
| } | |
| throw new Error("Unsupported devflow init review mode: " + review); | |
| } |
| function appendDevflowAgentGuide(existing, generated) { | ||
| if (existing.includes("## Devflow Native")) { | ||
| return existing; | ||
| } | ||
| const markerIndex = generated.indexOf("## Devflow Native"); | ||
| const section = markerIndex >= 0 ? generated.slice(markerIndex).trim() : generated.trim(); | ||
| const normalized = existing.length > 0 && !existing.endsWith("\n") ? `${existing}\n` : existing; | ||
| const spacer = normalized.endsWith("\n\n") ? "" : "\n"; | ||
| return `${normalized}${spacer}${section}\n`; | ||
| } |
There was a problem hiding this comment.
When appending the Devflow section to an empty or whitespace-only AGENTS.md file, the current logic introduces an unnecessary leading newline. Checking if normalized.length === 0 prevents this and ensures a clean file format.
| function appendDevflowAgentGuide(existing, generated) { | |
| if (existing.includes("## Devflow Native")) { | |
| return existing; | |
| } | |
| const markerIndex = generated.indexOf("## Devflow Native"); | |
| const section = markerIndex >= 0 ? generated.slice(markerIndex).trim() : generated.trim(); | |
| const normalized = existing.length > 0 && !existing.endsWith("\n") ? `${existing}\n` : existing; | |
| const spacer = normalized.endsWith("\n\n") ? "" : "\n"; | |
| return `${normalized}${spacer}${section}\n`; | |
| } | |
| function appendDevflowAgentGuide(existing, generated) { | |
| if (existing.includes("## Devflow Native")) { | |
| return existing; | |
| } | |
| const markerIndex = generated.indexOf("## Devflow Native"); | |
| const section = markerIndex >= 0 ? generated.slice(markerIndex).trim() : generated.trim(); | |
| const normalized = existing.length > 0 && !existing.endsWith("\n") ? `${existing}\n` : existing; | |
| const spacer = normalized.length === 0 || normalized.endsWith("\n\n") ? "" : "\n"; | |
| return normalized + spacer + section + "\n"; | |
| } |
Summary
devflow initbootstrap forsolo-product,research, andcontent-site..devflow/config.json, AGENTS guidance, inferred gates, GitHub CI, and optional Codex/Claude harness files through shared core contracts.devflow.initthrough MCP with dry-run and confirmed-write paths, plus docs and tests.Verification
npm testnpm run docs:checknode packages/cli/src/index.js health --jsonnode packages/cli/src/index.js harness health --targets codex,claude --jsonnpm run pack:checkgit diff --checkReview
init-mcp-bootstrap: passed.