diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 975a639..e873502 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -4,9 +4,47 @@ on: push: branches: - main + - 'fix/**' + - 'feat/**' + pull_request: + branches: + - main jobs: - deploy: + deploy-staging: + if: github.ref != 'refs/heads/main' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: '20' + + - name: Install dependencies + run: npm ci + + - name: Deploy to Staging + uses: cloudflare/wrangler-action@v3 + with: + apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + command: deploy --env staging + + - name: Comment staging URL + if: github.event_name == 'pull_request' + uses: actions/github-script@v7 + with: + script: | + github.rest.issues.createComment({ + issue_number: context.issue.number, + owner: context.repo.owner, + repo: context.repo.repo, + body: '🚀 **Deployed to staging!**\n\nTest at: `https://devplan-mcp-server-staging..workers.dev`\n\nUse `mcporter call https://devplan-mcp-server-staging...` to test.' + }) + + deploy-production: + if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -19,7 +57,8 @@ jobs: - name: Install dependencies run: npm ci - - name: Deploy to Cloudflare Workers + - name: Deploy to Production uses: cloudflare/wrangler-action@v3 with: apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }} + command: deploy --env="" diff --git a/.gitignore b/.gitignore index 81b6d9c..7a58499 100644 --- a/.gitignore +++ b/.gitignore @@ -28,4 +28,11 @@ CLAUDE.md # Generated types worker-configuration.d.ts -AGENTS.md\nHEARTBEAT.md\nIDENTITY.md\nSOUL.md\nTOOLS.md\nUSER.md + +# OpenClaw workspace files +AGENTS.md +HEARTBEAT.md +IDENTITY.md +SOUL.md +TOOLS.md +USER.md diff --git a/heartbeat-state.json b/heartbeat-state.json new file mode 100644 index 0000000..6f9c78f --- /dev/null +++ b/heartbeat-state.json @@ -0,0 +1,17 @@ +{ + "lastUpdate": "2026-02-13T18:04:00-08:00", + "activeWork": { + "branch": "fix/feature-grouping-146", + "status": "pr_ready", + "bugs": { + "completed": [130, 125, 126, 137, 138, 141, 146], + "remaining": [139, 140, 142] + } + }, + "nextSteps": [ + "Create PR for fix/feature-grouping-146", + "Fix language propagation (#139, #140)", + "Filter lessons by language (#142)" + ], + "notes": "Language defaults not propagating - getLanguageDefaults called but results not applied" +} diff --git a/memory/2026-02-13.md b/memory/2026-02-13.md new file mode 100644 index 0000000..5681fa6 --- /dev/null +++ b/memory/2026-02-13.md @@ -0,0 +1,38 @@ +# 2026-02-13 - DevPlan Bug Fixes Session + +## Summary +Working through batch of bugs from testing (#137-#143, #146). PR #144 merged with 7 fixes. + +## Completed Today +- PR #144 merged: #130, #125, #126, #137, #138, #141 fixes +- Branch `fix/feature-grouping-146` created - fixes feature grouping regression +- #146 root cause: `groupRelatedFeatures` only pushed groups that had items (endpoints), but features without endpoints are valid phases too + +## Active Work +- **Branch:** `fix/feature-grouping-146` (builds pass, ready for PR) +- **Remaining bugs:** #139, #140, #142 + +## Bug Analysis +### #139 - CLAUDE.md ignores detected language +- `getLanguageDefaults` imported at line 12, called at line 222 +- Language param passed but defaults not applied correctly +- Need to trace how language flows through generator pipeline + +### #140 - Nonsensical file paths +- `.txt` extensions, Python naming conventions for Java projects +- Same root cause as #139 - language defaults not propagating + +### #142 - Irrelevant lessons in plans +- Graph API/Cargo/RocksDB lessons appearing in Java plans +- Decision: Infer language from lesson content rather than add `languages` field + +## Key Files +- `src/generators.ts` - main plan generation logic +- `src/language-defaults.ts` - language-specific defaults +- `src/adapters/claude.ts` - CLAUDE.md generator +- `src/templates.ts` - template system + +## Next Session +1. Create PR for #146 fix +2. Fix language propagation in generators (#139, #140) +3. Filter lessons by inferred language (#142) diff --git a/src/generators.ts b/src/generators.ts index 76e6a4f..74398c4 100644 --- a/src/generators.ts +++ b/src/generators.ts @@ -12,6 +12,13 @@ import { getTemplate, PROJECT_TYPE_TASKS, findTemplate, type PhaseTemplate } fro import { getLanguageDefaults, LanguageDefaults } from "./language-defaults"; import { generateReadmeDiagrams, formatDiagramsAsMarkdown } from "./readme-diagrams"; +/** + * Inline completion instruction for Haiku agent. + * Must be included in each subtask since Haiku only sees the current context. + */ +const COMPLETION_INSTRUCTION = ` +✅ **When Complete**: Change the subtask checkbox above from \`- [ ]\` to \`- [x]\` and fill in the Completion Notes.`; + export interface BriefInput { name: string; projectType: string; @@ -34,7 +41,7 @@ export interface BriefInput { */ export interface TemplateKey { projectType: "cli" | "web_app" | "api" | "library"; - language: "python" | "typescript" | "javascript" | "go" | "rust" | "unknown"; + language: DetectedLanguage; // Reuse the detected language type variant?: "static" | "serverless" | "fullstack" | "minimal"; } @@ -53,29 +60,8 @@ export function resolveTemplateKey(brief: ProjectBrief): TemplateKey { // Detect language from must_use tech stack const detectedLang = detectLanguage(brief.mustUseTech); - // Map detected language to TemplateKey language type - let language: TemplateKey["language"]; - switch (detectedLang) { - case "python": - language = "python"; - break; - case "typescript": - language = "typescript"; - break; - case "javascript": - language = "javascript"; - break; - default: - // Check for Go indicators - const techLower = brief.mustUseTech.map((t) => t.toLowerCase()).join(" "); - if (techLower.includes("go") || techLower.includes("golang")) { - language = "go"; - } else if (techLower.includes("rust") || techLower.includes("cargo")) { - language = "rust"; - } else { - language = "unknown"; - } - } + // Use detected language directly - types are aligned + const language = detectedLang; // Detect variant (placeholder until detectVariant is implemented in 1.1.2) const variant = detectVariant(brief); @@ -315,6 +301,7 @@ ${diagramsSection} - **Build**: N/A (setup) - **Branch**: feature/0-1-repo-setup - **Notes**: (any additional context) +${COMPLETION_INSTRUCTION} --- @@ -348,6 +335,7 @@ ${successCriteria} - **Build**: N/A - **Branch**: feature/0-1-repo-setup - **Notes**: (any additional context) +${COMPLETION_INSTRUCTION} --- @@ -386,6 +374,7 @@ ${lintingCriteria} - **Build**: (linter: pass/fail) - **Branch**: feature/0-2-dev-tools - **Notes**: (any additional context) +${COMPLETION_INSTRUCTION} --- @@ -421,6 +410,7 @@ ${testingCriteria} - **Build**: (test runner: pass/fail) - **Branch**: feature/0-2-dev-tools - **Notes**: (any additional context) +${COMPLETION_INSTRUCTION} --- @@ -530,6 +520,7 @@ ${featureList} - **Build**: (pass/fail) - **Branch**: feature/1-1-core-module - **Notes**: (any additional context) +${COMPLETION_INSTRUCTION} --- ${domainSubtasks} @@ -574,6 +565,7 @@ ${domainSubtasks} - **Build**: (pass/fail) - **Branch**: feature/1-1-core-module - **Notes**: (any additional context) +${COMPLETION_INSTRUCTION} --- @@ -650,6 +642,7 @@ ${allSchemaItems.map(item => `- [ ] \`${item.name}\` table can be created and qu - **Build**: (pass/fail) - **Branch**: feature/1-1-core-module - **Notes**: (any additional context) +${COMPLETION_INSTRUCTION} --- `); @@ -705,6 +698,7 @@ ${allApiItems.map(item => `- [ ] \`${item.name}\` returns expected response form - **Build**: (pass/fail) - **Branch**: feature/1-1-core-module - **Notes**: (any additional context) +${COMPLETION_INSTRUCTION} --- `); @@ -760,6 +754,7 @@ ${allPipelineItems.map(item => `- [ ] ${item.name} completes successfully`).join - **Build**: (pass/fail) - **Branch**: feature/1-1-core-module - **Notes**: (any additional context) +${COMPLETION_INSTRUCTION} --- `); @@ -1648,6 +1643,7 @@ ${successCriteria} - **Build**: (ruff: pass/fail, mypy: pass/fail) - **Branch**: feature/${task.id}-${subtask.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 20)} - **Notes**: (any additional context) +${COMPLETION_INSTRUCTION} ---`; }) @@ -3330,7 +3326,8 @@ function groupRelatedFeatures(features: string[]): Array<{ name: string; items: } } else { // This is a regular feature - finalize previous group and start new one - if (currentGroup && currentGroup.items.length > 0) { + if (currentGroup) { + // Always push the previous group (features without endpoints are valid phases) groups.push(currentGroup); } currentGroup = { name: trimmed, items: [] }; diff --git a/src/index.ts b/src/index.ts index 0b59361..4d0decc 100644 --- a/src/index.ts +++ b/src/index.ts @@ -82,6 +82,7 @@ interface Env { SESSION_INACTIVITY_TTL_DAYS: string; SESSION_ABSOLUTE_TTL_DAYS: string; CLEANUP_CHECK_HOURS: string; + ENVIRONMENT?: string; // "staging" for staging environment DEVPLAN_KV: KVNamespace; MCP_OBJECT: DurableObjectNamespace; // Cloudflare Analytics API (optional - for dashboard) @@ -2235,8 +2236,8 @@ export default { const isRootDomain = url.hostname === "devplanmcp.store"; const isWorkersDevDomain = url.hostname.endsWith(".workers.dev"); - // Redirect workers.dev to MCP subdomain for MCP endpoints, root for others - if (isWorkersDevDomain) { + // Redirect workers.dev to custom domain (skip for staging environment) + if (isWorkersDevDomain && env.ENVIRONMENT !== "staging") { const isMcpPath = url.pathname === "/sse" || url.pathname === "/sse/message" || url.pathname === "/mcp"; const targetDomain = isMcpPath ? "https://mcp.devplanmcp.store" : "https://devplanmcp.store"; const newUrl = new URL(url.pathname + url.search, targetDomain);