mindforge-cc 8.1.1 → 8.2.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.
- package/.mindforge/celestial.db +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/CLAUDE.md +102 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/commands.cjs +959 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/config.cjs +421 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/core.cjs +1166 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/frontmatter.cjs +307 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/init.cjs +1336 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/milestone.cjs +252 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/model-profiles.cjs +68 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/phase.cjs +888 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/profile-output.cjs +952 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/profile-pipeline.cjs +539 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/roadmap.cjs +329 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/security.cjs +356 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/state.cjs +969 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/template.cjs +222 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/uat.cjs +189 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/verify.cjs +851 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/lib/workstream.cjs +491 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/bin/mindforge-tools.cjs +897 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/file-manifest.json +219 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/forge/help.md +11 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/forge/init-project.md +36 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/forge/plan-phase.md +34 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/hooks/mindforge-check-update.js +114 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/hooks/mindforge-context-monitor.js +156 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/hooks/mindforge-prompt-guard.js +96 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/hooks/mindforge-session-init_extended.js +42 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/hooks/mindforge-statusline.js +119 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/hooks/mindforge-workflow-guard.js +94 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/add-backlog.md +32 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/agent.md +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/approve.md +22 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/audit.md +34 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/auto.md +26 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/benchmark.md +37 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/browse.md +30 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/complete-milestone.md +22 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/costs.md +15 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/cross-review.md +21 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/dashboard.md +102 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/debug.md +133 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/discuss-phase.md +142 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/do.md +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/execute-phase.md +200 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/health.md +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/help.md +33 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/init-org.md +135 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/init-project.md +170 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/install-skill.md +28 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/learn.md +147 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/learning.md +20 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/map-codebase.md +302 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/marketplace.md +124 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/metrics.md +26 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/migrate.md +44 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/milestone.md +16 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/new-runtime.md +23 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/next.md +109 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/note.md +35 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/plan-phase.md +131 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/plant-seed.md +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/plugins.md +44 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/pr-review.md +45 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/profile-team.md +27 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/publish-skill.md +23 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/qa.md +20 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/quick.md +139 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/record-learning.md +22 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/release.md +14 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/remember.md +30 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/research.md +16 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/retrospective.md +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/review-backlog.md +34 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/review.md +161 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/security-scan.md +242 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/session-report.md +39 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/ship.md +111 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/skills.md +145 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/status.md +113 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/steer.md +17 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/sync-confluence.md +15 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/sync-jira.md +16 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/tokens.md +12 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/ui-phase.md +34 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/ui-review.md +36 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/update.md +46 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/validate-phase.md +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/verify-phase.md +66 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/workspace.md +33 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/mindforge/workstreams.md +35 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/settings.json +42 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-add-backlog/SKILL.md +72 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-add-phase/SKILL.md +39 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-add-tests/SKILL.md +28 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-add-todo/SKILL.md +42 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-audit-milestone/SKILL.md +29 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-audit-uat/SKILL.md +20 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-autonomous/SKILL.md +33 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-brainstorming/SKILL.md +164 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-brainstorming/scripts/frame-template.html +214 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-brainstorming/scripts/helper.js +88 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-brainstorming/scripts/server.cjs +354 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-brainstorming/scripts/start-server.sh +148 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-brainstorming/scripts/stop-server.sh +56 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-brainstorming/spec-document-reviewer-prompt.md +49 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-brainstorming/visual-companion.md +287 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-check-todos/SKILL.md +40 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-cleanup/SKILL.md +19 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-complete-milestone/SKILL.md +131 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-debug/SKILL.md +163 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-debug_extended/CREATION-LOG.md +119 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-debug_extended/SKILL.md +296 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-debug_extended/condition-based-waiting-example.ts +158 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-debug_extended/condition-based-waiting.md +115 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-debug_extended/defense-in-depth.md +122 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-debug_extended/find-polluter.sh +63 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-debug_extended/root-cause-tracing.md +169 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-debug_extended/test-academic.md +14 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-debug_extended/test-pressure-1.md +58 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-debug_extended/test-pressure-2.md +68 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-debug_extended/test-pressure-3.md +69 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-discuss-phase/SKILL.md +54 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-do/SKILL.md +26 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-execute-phase/SKILL.md +49 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-execute-phase_extended/SKILL.md +70 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-fast/SKILL.md +23 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-forensics/SKILL.md +49 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-health/SKILL.md +17 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-help/SKILL.md +23 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-insert-phase/SKILL.md +28 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-join-discord/SKILL.md +19 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-list-phase-assumptions/SKILL.md +41 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-list-workspaces/SKILL.md +17 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-manager/SKILL.md +32 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-map-codebase/SKILL.md +64 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-milestone-summary/SKILL.md +44 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-neural-orchestrator/SKILL.md +115 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-neural-orchestrator/references/codex-tools.md +100 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-neural-orchestrator/references/gemini-tools.md +33 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-new-milestone/SKILL.md +38 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-new-project/SKILL.md +36 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-new-workspace/SKILL.md +39 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-next/SKILL.md +19 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-note/SKILL.md +29 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-parallel-mesh_extended/SKILL.md +182 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-pause-work/SKILL.md +35 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-plan-milestone-gaps/SKILL.md +28 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-plan-phase/SKILL.md +38 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-plan-phase_extended/SKILL.md +152 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-plan-phase_extended/plan-document-reviewer-prompt.md +49 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-plant-seed/SKILL.md +22 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-pr-branch/SKILL.md +21 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-profile-user/SKILL.md +38 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-progress/SKILL.md +19 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-quick/SKILL.md +38 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-reapply-patches/SKILL.md +124 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-remove-phase/SKILL.md +26 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-remove-workspace/SKILL.md +22 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-research-phase/SKILL.md +186 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-resume-work/SKILL.md +35 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-review/SKILL.md +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-review-backlog/SKILL.md +58 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-review-inbound/SKILL.md +213 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-review-request/SKILL.md +105 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-review-request/code-reviewer.md +146 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-session-report/SKILL.md +16 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-set-profile/SKILL.md +9 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-settings/SKILL.md +32 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-ship/SKILL.md +16 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-ship_extended/SKILL.md +200 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-skill-creation/SKILL.md +655 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-skill-creation/anthropic-best-practices.md +1150 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-skill-creation/examples/CLAUDE_MD_TESTING.md +189 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-skill-creation/graphviz-conventions.dot +172 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-skill-creation/persuasion-principles.md +187 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-skill-creation/render-graphs.js +168 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-skill-creation/testing-skills-with-subagents.md +384 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-stats/SKILL.md +16 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-swarm-execution/SKILL.md +277 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-swarm-execution/code-quality-reviewer-prompt.md +26 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-swarm-execution/implementer-prompt.md +113 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-swarm-execution/spec-reviewer-prompt.md +61 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-system-architecture/SKILL.md +136 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-system-architecture/examples.md +120 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-system-architecture/scaling-checklist.md +76 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-tdd/SKILL.md +112 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-tdd/deep-modules.md +21 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-tdd/interface-design.md +22 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-tdd/mocking.md +24 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-tdd/refactoring.md +21 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-tdd/tests.md +28 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-tdd_extended/SKILL.md +371 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-tdd_extended/testing-anti-patterns.md +299 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-thread/SKILL.md +123 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-ui-phase/SKILL.md +24 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-ui-review/SKILL.md +24 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-update/SKILL.md +35 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-validate-phase/SKILL.md +26 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-verify-work/SKILL.md +30 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-verify-work_extended/SKILL.md +139 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-workspace-isolated/SKILL.md +218 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/skills/mindforge-workstreams/SKILL.md +65 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/forge:help.md +10 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/forge:init-project.md +35 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/forge:plan-phase.md +33 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-add-phase.md +112 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-add-tests.md +351 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-add-todo.md +158 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-audit-milestone.md +332 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-audit-uat.md +109 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-autonomous.md +815 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-check-todos.md +177 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-cleanup.md +152 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-complete-milestone.md +766 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-diagnose-issues.md +220 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-discovery-phase.md +289 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-discuss-phase-assumptions.md +645 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-discuss-phase.md +1047 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-do.md +104 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-execute-phase.md +838 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-execute-plan.md +509 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-fast.md +105 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-forensics.md +265 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-health.md +181 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-help.md +606 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-insert-phase.md +130 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-list-phase-assumptions.md +178 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-list-workspaces.md +56 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-manager.md +360 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-map-codebase.md +370 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-milestone-summary.md +223 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-new-milestone.md +469 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-new-project.md +1226 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-new-workspace.md +237 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-next.md +97 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-node-repair.md +92 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-note.md +156 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-pause-work.md +176 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-plan-milestone-gaps.md +273 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-plan-phase.md +877 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-plant-seed.md +169 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-pr-branch.md +129 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-profile-user.md +450 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-progress.md +507 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-quick.md +732 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-remove-phase.md +155 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-remove-workspace.md +90 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-research-phase.md +74 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-resume-project.md +325 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-review.md +228 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-session-report.md +146 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-settings.md +283 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-ship.md +228 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-stats.md +60 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-transition.md +671 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-ui-phase.md +290 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-ui-review.md +157 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-update.md +323 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-validate-phase.md +167 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-verify-phase.md +254 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge-verify-work.md +628 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:add-backlog.md +24 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:agent.md +25 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:approve.md +21 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:architecture.md +40 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:audit.md +33 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:auto.md +25 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:benchmark.md +36 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:brainstorming.md +16 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:browse.md +29 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:complete-milestone.md +21 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:costs.md +14 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:cross-review.md +20 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:dashboard.md +101 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:debug.md +131 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:discuss-phase.md +141 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:do.md +25 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:execute-phase.md +205 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:executor.md +18 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:health.md +24 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:help.md +26 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:identity.md +18 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:init-org.md +134 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:init-project.md +185 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:install-skill.md +27 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:learn.md +146 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:map-codebase.md +301 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:marketplace.md +123 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:memory.md +18 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:metrics.md +25 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:migrate.md +43 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:milestone.md +15 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:new-runtime.md +22 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:next.md +108 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:note.md +27 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:plan-phase.md +139 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:planner.md +18 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:plant-seed.md +24 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:plugins.md +43 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:pr-review.md +44 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:profile-team.md +26 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:publish-skill.md +22 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:qa.md +19 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:quick.md +138 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:release.md +13 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:remember.md +29 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:research.md +15 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:researcher.md +18 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:retrospective.md +29 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:review-backlog.md +26 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:review.md +160 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:reviewer.md +18 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:security-scan.md +236 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:session-report.md +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:ship.md +108 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:skills.md +144 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:soul.md +54 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:status.md +107 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:steer.md +16 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:sync-confluence.md +14 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:sync-jira.md +15 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:tdd.md +46 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:tokens.md +11 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:tool.md +18 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:ui-phase.md +27 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:ui-review.md +28 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:update.md +45 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:validate-phase.md +25 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:verify-phase.md +65 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:workspace.md +32 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/mindforge:workstreams.md +27 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.agent/workflows/publish-release.md +36 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.czrc +3 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.github/pull_request_template.md +29 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.github/workflows/ai-intelligence.yml +55 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.github/workflows/auto-pr.yml +80 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.github/workflows/control-plane.yml +79 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.github/workflows/execution-plane.yml +52 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.github/workflows/mindforge-ai-review.yml +68 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.github/workflows/mindforge-autonomous.yml +70 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.github/workflows/mindforge-ci.yml +224 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.github/workflows/mindforge-observability.yml +71 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.github/workflows/mindforge-release.yml +55 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.github/workflows/observability-plane.yml +40 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.github/workflows/release-plane.yml +43 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.gitlab-ci-mindforge.yml +18 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.husky/pre-commit +1 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/MINDFORGE-SCHEMA.json +165 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/MINDFORGE-V2-SCHEMA.json +47 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/audit/AUDIT-SCHEMA.md +470 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/browser/daemon-protocol.md +24 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/browser/qa-engine.md +16 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/browser/session-manager.md +18 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/browser/visual-verify-spec.md +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/bypasses.json +8 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/ci/ci-config-schema.md +21 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/ci/ci-mode.md +179 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/ci/github-actions-adapter.md +224 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/ci/gitlab-ci-adapter.md +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/ci/jenkins-adapter.md +44 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/config.json +66 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/dashboard/api-reference.md +122 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/dashboard/dashboard-spec.md +96 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/distribution/marketplace.md +53 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/distribution/registry-client.md +166 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/distribution/registry-schema.md +96 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/distribution/skill-publisher.md +44 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/distribution/skill-validator.md +74 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/ads-protocol.md +54 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/autonomous/auto-executor.md +266 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/autonomous/headless-adapter.md +66 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/autonomous/node-repair.md +190 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/autonomous/progress-reporter.md +58 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/autonomous/steering-manager.md +64 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/autonomous/stuck-detector.md +89 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/compaction-protocol.md +167 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/context-injector.md +154 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/dependency-parser.md +113 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/integrity.json +12 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/knowledge-graph-protocol.md +125 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/nexus-tracer.js +11 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/persona-factory.md +45 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/shard-controller.md +53 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/skills/conflict-resolver.md +69 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/skills/loader.md +184 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/skills/registry.md +98 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/skills/versioning.md +75 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/swarm-controller.md +59 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/temporal-protocol.md +40 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/verification-pipeline.md +111 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/engine/wave-executor.md +285 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/governance/GOVERNANCE-CONFIG.md +17 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/governance/approval-workflow.md +37 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/governance/change-classifier.md +63 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/governance/compliance-gates.md +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/governance/policies/sovereign-default.json +16 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/integrations/confluence.md +27 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/integrations/connection-manager.md +163 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/integrations/github.md +25 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/integrations/gitlab.md +13 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/integrations/jira.md +102 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/integrations/slack.md +41 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/intelligence/antipattern-detector.md +75 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/intelligence/difficulty-scorer.md +55 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/intelligence/health-engine.md +208 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/intelligence/skill-gap-analyser.md +40 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/intelligence/smart-compaction.md +71 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/memory/MEMORY-SCHEMA.md +155 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/memory/engine/capture-protocol.md +36 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/memory/engine/global-sync-spec.md +42 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/memory/engine/retrieval-spec.md +44 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/metrics/METRICS-SCHEMA.md +42 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/metrics/quality-tracker.md +32 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/models/model-registry.md +48 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/models/model-router.md +30 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/monorepo/cross-package-planner.md +114 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/monorepo/dependency-graph-builder.md +32 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/monorepo/workspace-detector.md +129 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/org/CONVENTIONS.md +62 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/org/ORG.md +51 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/org/SECURITY.md +50 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/org/TOOLS.md +53 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/org/integrations/INTEGRATIONS-CONFIG.md +58 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/org/skills/MANIFEST.md +15 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/advisor-researcher.md +89 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/analyst.md +112 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/architect.md +108 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/assumptions-analyzer-extend.md +87 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/assumptions-analyzer.md +109 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/codebase-mapper-extend.md +93 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/codebase-mapper.md +770 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/coverage-specialist.md +104 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/debug-specialist.md +118 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/debugger.md +97 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/decision-architect.md +102 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/developer.md +97 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/executor.md +88 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/integration-checker.md +92 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/mf-executor.md +40 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/mf-memory.md +33 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/mf-planner.md +45 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/mf-researcher.md +39 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/mf-reviewer.md +35 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/mf-tool.md +33 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/nyquist-auditor.md +84 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/overrides/README.md +85 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/phase-researcher.md +107 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/plan-checker.md +92 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/planner.md +105 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/project-researcher.md +99 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/qa-engineer.md +113 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/release-manager.md +114 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/research-agent.md +109 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/research-synthesizer.md +101 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/roadmapper-extend.md +100 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/roadmapper.md +103 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/security-reviewer.md +114 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/swarm-templates.json +118 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/tech-writer.md +118 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/ui-auditor.md +94 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/ui-checker.md +89 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/ui-researcher.md +99 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/user-profiler.md +93 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/personas/verifier.md +101 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/plugins/PLUGINS-MANIFEST.md +23 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/plugins/plugin-loader.md +93 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/plugins/plugin-registry.md +44 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/plugins/plugin-schema.md +68 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/pr-review/ai-reviewer.md +266 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/pr-review/finding-formatter.md +46 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/pr-review/review-prompt-templates.md +44 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/production/compatibility-layer.md +39 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/production/migration-engine.md +52 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/production/production-checklist.md +76 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/production/token-optimiser.md +68 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/remediation-queue.json +47 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/skills/accessibility/SKILL.md +106 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/skills/api-design/SKILL.md +98 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/skills/code-quality/SKILL.md +88 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/skills/data-privacy/SKILL.md +126 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/skills/database-patterns/SKILL.md +192 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/skills/documentation/SKILL.md +91 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/skills/incident-response/SKILL.md +180 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/skills/performance/SKILL.md +120 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/skills/security-review/SKILL.md +83 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/skills/testing-standards/SKILL.md +97 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/skills-builder/auto-capture-protocol.md +88 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/skills-builder/learn-protocol.md +161 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/skills-builder/quality-scoring.md +120 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/team/TEAM-PROFILE.md +42 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/team/multi-handoff.md +23 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/team/profiles/README.md +13 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.mindforge/team/session-merger.md +18 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/ARCHITECTURE.md +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/AUDIT.jsonl +45 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/HANDOFF.json +8 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/PROJECT.md +33 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/RELEASE-CHECKLIST.md +68 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/REQUIREMENTS.md +23 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/ROADMAP.md +12 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/ROI.jsonl +2 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/STATE.md +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/approvals/.gitkeep +1 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/.gitkeep +1 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/org/CONVENTIONS.md +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/org/ORG.md +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/org/SECURITY.md +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/org/TOOLS.md +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/personas/analyst.md +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/personas/architect.md +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/personas/debug-specialist.md +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/personas/developer.md +26 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/personas/qa-engineer.md +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/personas/release-manager.md +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/personas/security-reviewer.md +33 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/personas/tech-writer.md +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/skills/api-design/SKILL.md +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/skills/code-quality/SKILL.md +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/skills/documentation/SKILL.md +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/skills/security-review/SKILL.md +23 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/.forge/skills/testing-standards/SKILL.md +27 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/ARCHITECTURE-AUDIT-REPORT.md +90 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/LOGS-BENCHMARKING.md +172 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/ROADMAP_V8.md +49 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/github-actions-logs.md +88 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-1-imp/DAY1-HARDEN.md +823 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-1-imp/DAY1-IMPLEMENT.md +2459 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-1-imp/DAY1-REVIEW.md +288 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-2-imp/DAY2-HARDEN.md +954 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-2-imp/DAY2-IMPLEMENT.md +2347 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-2-imp/DAY2-REVIEW.md +422 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-3-imp/DAY3-HARDEN.md +870 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-3-imp/DAY3-IMPLEMENT.md +2798 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-3-imp/DAY3-REVIEW.md +484 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-4-imp/DAY4-HARDEN.md +1087 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-4-imp/DAY4-IMPLEMENT.md +2874 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-4-imp/DAY4-REVIEW.md +386 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-5-imp/DAY5-HARDEN.md +1078 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-5-imp/DAY5-IMPLEMENT.md +3151 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-5-imp/DAY5-REVIEW.md +345 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-6-imp/DAY6-COMPLETE.md +3919 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-7-imp-prod/DAY7-PRODUCTION-FINAL.md +4513 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/ci-actions/github-workflows-v2.md +421 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/ci-actions/v2-ci-actions.md +292 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/day-10-imp/DAY10-MULTI-MODEL.md +3402 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/day-11-imp/DAY11-PERSISTENT-MEMORY.md +3237 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/day-12-imp/DAY12-REALTIME-DASHBOARD.md +3301 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/day-13-imp/DAY13-SELF-BUILDING-SKILLS.md +3798 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/day-14-prod-v2/DAY14-V2-PRODUCTION-RELEASE.md +2255 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/day-8-imp/DAY8-AUTONOMOUS-ENGINE.md +3400 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/day-9-imp/DAY9-BROWSER-RUNTIME.md +3293 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/audit-archive/.gitkeep +1 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/decisions/.gitkeep +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/jira-sync.json +5 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/milestones/.gitkeep +1 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/phases/.gitkeep +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/research/.gitkeep +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/screenshots/.gitkeep +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/.planning/slack-threads.json +3 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/AGENTS_LEARNING.md +112 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/CHANGELOG.md +1116 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/LICENSE +21 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/MINDFORGE.md +91 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/README.md +424 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/RELEASENOTES.md +199 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/REPLICATION.json +12 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/SECURITY.md +4 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/SOUL.md +52 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/agents/executor/IDENTITY.md +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/agents/memory/IDENTITY.md +27 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/agents/planner/IDENTITY.md +35 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/agents/researcher/IDENTITY.md +29 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/agents/reviewer/IDENTITY.md +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/agents/tool/IDENTITY.md +27 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/auto-pr.yml +74 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/autonomous/auto-runner.js +378 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/autonomous/context-refactorer.js +64 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/autonomous/headless.js +36 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/autonomous/intent-harvester.js +80 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/autonomous/mesh-self-healer.js +67 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/autonomous/progress-stream.js +49 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/autonomous/repair-operator.js +213 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/autonomous/steer.js +89 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/autonomous/stuck-monitor.js +120 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/browser/browser-daemon.js +139 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/browser/daemon-manager.js +91 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/browser/qa-engine.js +47 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/browser/qa-report-writer.js +32 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/browser/regression-writer.js +27 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/browser/screenshot-store.js +49 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/browser/session-manager.js +93 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/browser/visual-verify-executor.js +89 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/change-classifier.js +86 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/dashboard/api-router.js +198 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/dashboard/approval-handler.js +134 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/dashboard/frontend/index.html +751 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/dashboard/metrics-aggregator.js +296 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/dashboard/revops-api.js +47 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/dashboard/server.js +138 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/dashboard/sse-bridge.js +178 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/dashboard/team-tracker.js +0 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/dashboard/temporal-api.js +82 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/context-entropy-guard.js +94 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/feedback-loop.js +106 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/handover-manager.js +71 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/intelligence-interlock.js +39 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/learning-manager.js +181 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/logic-drift-detector.js +100 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/logic-validator.js +74 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/mesh-syncer.js +129 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/nexus-tracer.js +356 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/orbital-guardian.js +84 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/reason-source-aligner.js +111 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/remediation-engine.js +81 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/self-corrective-synthesizer.js +65 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/skill-evolver.js +105 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/sre-manager.js +117 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/temporal-cli.js +52 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/temporal-hindsight.js +115 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/temporal-hub.js +138 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/test-ceg.js +59 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/test-interlock.js +40 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/test-remediation.js +61 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/test-rsa.js +64 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/test-scs.js +57 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/engine/test-v7-blueprint.js +44 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/gov-audit.js +38 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/governance/approve.js +60 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/governance/config-manager.js +85 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/governance/impact-analyzer.js +141 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/governance/policies/critical-data.json +1 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/governance/policies/default-policies.jsonl +33 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/governance/policy-engine.js +210 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/governance/policy-gate-hardened.js +59 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/governance/quantum-crypto.js +111 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/governance/rbac-manager.js +109 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/governance/test-config.js +40 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/governance/test-crypto-pluggable.js +50 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/governance/test-hardened-gate.js +71 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/governance/trust-verifier.js +81 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/governance/ztai-archiver.js +104 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/governance/ztai-manager.js +239 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/hindsight-injector.js +59 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/install.js +129 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/installer-core.js +805 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/auto-shadow.js +274 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/cli.js +99 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/eis-client.js +95 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/embedding-engine.js +326 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/federated-sync.js +293 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/ghost-pattern-detector.js +69 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/global-sync.js +107 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/identity-synthesizer.js +146 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/knowledge-capture.js +442 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/knowledge-graph.js +609 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/knowledge-indexer.js +172 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/knowledge-store.js +337 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/pillar-health-tracker.js +63 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/semantic-hub.js +211 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/session-memory-loader.js +137 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/memory/vector-hub.js +170 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/migrations/0.1.0-to-0.5.0.js +36 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/migrations/0.5.0-to-0.6.0.js +17 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/migrations/0.6.0-to-1.0.0.js +100 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/migrations/1.0.0-to-2.0.0.js +115 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/migrations/migrate.js +155 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/migrations/schema-versions.js +76 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/migrations/v8-sqlite-migration.js +85 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/mindforge-cc.sh +5 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/mindforge-cli.js +180 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/models/anthropic-provider.js +77 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/models/cloud-broker.js +161 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/models/cost-tracker.js +118 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/models/finops-hub.js +79 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/models/gemini-provider.js +79 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/models/model-broker.js +129 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/models/model-client.js +98 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/models/model-router.js +112 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/models/openai-provider.js +78 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/models/performance-stats.json +22 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/research/research-engine.js +115 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/review/ads-engine.js +126 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/review/ads-synthesizer.js +117 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/review/cross-review-engine.js +92 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/review/finding-synthesizer.js +116 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/review/review-report-writer.js +49 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/revops/debt-monitor.js +60 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/revops/market-evaluator.js +73 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/revops/remediation-queue.js +107 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/revops/roi-engine.js +65 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/revops/router-steering-v2.js +73 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/revops/velocity-forecaster.js +59 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/shard-helper.js +134 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/skill-registry.js +232 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/skill-validator.js +211 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/skills-builder/learn-cli.js +57 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/skills-builder/marketplace-cli.js +54 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/skills-builder/marketplace-client.js +198 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/skills-builder/pattern-detector.js +144 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/skills-builder/skill-generator.js +258 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/skills-builder/skill-registrar.js +107 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/skills-builder/skill-scorer.js +263 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/skills-builder/source-loader.js +268 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/spawn-agent.js +61 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/updater/changelog-fetcher.js +62 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/updater/self-update.js +169 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/updater/version-comparator.js +68 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/validate-config.js +92 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/wizard/config-generator.js +112 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/wizard/environment-detector.js +83 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/wizard/setup-wizard.js +240 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/bin/wizard/theme.js +184 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/CAPABILITIES-MANIFEST.md +64 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Context/Master-Context.md +694 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/INTELLIGENCE-MESH.md +37 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/MIND-FORGE-REFERENCE-V6.md +96 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/PERSONAS.md +920 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/audit-events.md +59 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/checkpoints.md +778 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/commands.md +107 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/config-reference.md +81 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/continuation-format.md +249 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/decimal-phase-calculation.md +64 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/git-integration.md +295 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/git-planning-commit.md +38 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/model-profile-resolution.md +36 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/model-profiles.md +139 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/phase-argument-parsing.md +61 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/planning-config.md +202 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/questioning.md +162 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/sdk-api.md +53 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/skills-api.md +57 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/tdd.md +263 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/ui-brand.md +160 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/user-profiling.md +681 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/verification-patterns.md +612 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/References/workstream-flag.md +58 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Agents/CLAUDE-MD.md +122 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Agents/COPILOT-INSTRUCTIONS.md +7 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Agents/DEBUGGER-PROMPT.md +91 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Agents/PLANNER-PROMPT.md +117 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Codebase/architecture.md +255 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Codebase/concerns.md +310 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Codebase/conventions.md +307 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Codebase/integrations.md +280 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Codebase/stack.md +186 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Codebase/structure.md +285 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Codebase/testing.md +480 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Execution/CONTINUE-HERE.md +78 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Execution/DISCUSSION-LOG.md +63 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Execution/PHASE-PROMPT.md +610 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Execution/STATE.md +176 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Execution/SUMMARY-COMPLEX.md +59 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Execution/SUMMARY-MINIMAL.md +41 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Execution/SUMMARY-STANDARD.md +48 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Execution/SUMMARY.md +248 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Profile/DEV-PREFERENCES.md +21 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Profile/USER-PROFILE.md +146 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Profile/USER-SETUP.md +311 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Project/AGENTS_LEARNING.md +88 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Project/DISCOVERY.md +146 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Project/MILESTONE-ARCHIVE.md +123 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Project/MILESTONE.md +115 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Project/PROJECT.md +206 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Project/REQUIREMENTS.md +231 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Project/RETROSPECTIVE.md +54 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Project/ROADMAP.md +202 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Quality/DEBUG.md +164 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Quality/UAT.md +280 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Quality/UI-SPEC.md +100 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Quality/VALIDATION.md +76 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Quality/VERIFICATION-REPORT.md +322 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Research/ARCHITECTURE.md +204 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Research/FEATURES.md +147 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Research/PITFALLS.md +200 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Research/STACK.md +120 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/Research/SUMMARY.md +170 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/System/CONFIG.json +43 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/Templates/System/CONTEXT.md +352 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/adr/ADR-024-browser-localhost-only.md +17 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/adr/ADR-025-visual-verify-failure-treatment.md +19 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/adr/ADR-026-session-persistence-security.md +20 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/adr/ADR-042-ads-protocol.md +30 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/architecture/NEXUS-DASHBOARD.md +35 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/architecture/PAR-ZTS-SURVEY.md +43 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/architecture/README.md +78 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/architecture/V3-CORE.md +52 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/architecture/V4-SWARM-MESH.md +77 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/architecture/V5-ENTERPRISE.md +131 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/architecture/V6-SOVEREIGN.md +43 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/architecture/adr-039-multi-runtime-support.md +20 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/architecture/adr-040-additive-schema-migration.md +21 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/architecture/adr-041-stable-runtime-interface-contract.md +20 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/architecture/decision-records-index.md +29 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/ci-cd-integration.md +30 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/ci-cd.md +92 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/ci-quickstart.md +78 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/commands-reference.md +144 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/commands-skills/DISCOVERED_SKILLS.md +21 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/contributing/CONTRIBUTING.md +38 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/contributing/plugin-authoring.md +50 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/contributing/skill-authoring.md +41 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/enterprise-setup.md +25 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/faq.md +38 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/feature-dashboard.md +63 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/getting-started.md +44 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/governance-guide.md +99 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/monorepo-guide.md +26 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/persona-customisation.md +56 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/publishing-guide.md +43 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/quick-verify.md +33 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/registry/AGENTS.md +37 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/registry/COMMANDS.md +87 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/registry/HOOKS.md +38 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/registry/PERSONAS.md +64 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/registry/README.md +27 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/registry/SKILLS.md +142 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/registry/WORKFLOWS.md +72 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/release-checklist-guide.md +37 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/requirements.md +29 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/sdk-reference.md +27 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/security/SECURITY.md +55 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/security/ZTAI-OVERVIEW.md +37 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/security/penetration-test-results.md +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/security/threat-model.md +142 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/skills-authoring-guide.md +176 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/skills-publishing-guide.md +22 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/team-setup-guide.md +21 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/testing-current-version.md +130 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/troubleshooting.md +139 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/tutorial.md +162 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/upgrade.md +58 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/user-guide.md +244 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/usp-features.md +102 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/docs/workflow-atlas.md +57 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/eslint.config.mjs +31 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/examples/starter-project/.planning/AUDIT.jsonl +1 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/examples/starter-project/.planning/HANDOFF.json +23 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/examples/starter-project/.planning/PROJECT.md +27 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/examples/starter-project/.planning/STATE.md +10 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/examples/starter-project/MINDFORGE.md +40 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/examples/starter-project/README.md +14 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/package-lock.json +3882 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/package.json +66 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/sdk/README.md +69 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/sdk/eslint.config.mjs +34 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/sdk/package-lock.json +1507 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/sdk/package.json +30 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/sdk/src/client.ts +133 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/sdk/src/commands.ts +63 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/sdk/src/events.ts +166 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/sdk/src/index.ts +23 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/sdk/src/memory.ts +257 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/sdk/src/types.ts +87 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/sdk/tsconfig.json +13 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/skills-lock.json +30 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/test/sovereign-status.test.js +18 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/ads.test.js +121 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/audit.test.js +206 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/autonomous.test.js +53 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/browser.test.js +61 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/ci-mode.test.js +162 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/compaction.test.js +161 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/dashboard.test.js +327 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/distribution.test.js +205 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/e2e.test.js +618 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/entropy-test.js +47 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/feedback-loop.test.js +62 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/governance/test-cadia-optimizer.js +112 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/governance/test-policies/deny-security.json +9 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/governance/test-policies/permit-t2.json +10 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/governance.test.js +130 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/install.test.js +209 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/integrations.test.js +128 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/intelligence.test.js +117 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/knowledge-graph.test.js +593 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/learning-engine.test.js +69 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/mca-routing-test.js +37 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/memory.test.js +166 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/metrics.test.js +96 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/migration.test.js +308 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/model-broker.test.js +55 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/model-routing.test.js +111 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/nexus-tracing.test.js +49 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/production.test.js +416 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/release.test.js +99 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/revops-roi.test.js +52 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/run-nexus-tests.js +84 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/sdk.test.js +200 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/security-audit.test.js +67 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/self-building-skills.test.js +285 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/semantic-hub.test.js +91 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/sharding.test.js +87 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/skills-platform.test.js +389 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/sre-zk-proof-test.js +76 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/swarms.test.md +21 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/temporal-vision.test.js +68 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/v7-pillar-integration.test.js +73 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/v7-proactive-homing.test.js +53 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/v7-sovereign-security.test.js +64 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/v8-mesh-sync.test.js +76 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/v8-orbital-governance.test.js +74 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/v8-persistence.test.js +86 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/v8-skill-evolution.test.js +74 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/wave-engine.test.js +336 -0
- package/.mindforge/mirrors/mirror-sre-776a1cf9/tests/ztai-enterprise.test.js +103 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ai-image-generation/SKILL.md +147 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ai-video-generation/SKILL.md +185 -0
- package/.planning/archive/v8-cleanup/.agents/skills/critique/SKILL.md +201 -0
- package/.planning/archive/v8-cleanup/.agents/skills/critique/reference/cognitive-load.md +106 -0
- package/.planning/archive/v8-cleanup/.agents/skills/critique/reference/heuristics-scoring.md +234 -0
- package/.planning/archive/v8-cleanup/.agents/skills/critique/reference/personas.md +178 -0
- package/.planning/archive/v8-cleanup/.agents/skills/elevenlabs-music/SKILL.md +191 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/SKILL.md +659 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/_sync_all.py +414 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/app-interface.csv +31 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/charts.csv +26 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/colors.csv +162 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/design.csv +1776 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/draft.csv +1779 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/google-fonts.csv +1924 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/icons.csv +106 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/landing.csv +35 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/products.csv +162 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/styles.csv +85 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/typography.csv +74 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/ui-reasoning.csv +162 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/scripts/core.py +247 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.planning/archive/v8-cleanup/.agents/skills/ui-ux-pro-max/scripts/search.py +114 -0
- package/.planning/archive/v8-cleanup/.forge/org/CONVENTIONS.md +0 -0
- package/.planning/archive/v8-cleanup/.forge/org/ORG.md +0 -0
- package/.planning/archive/v8-cleanup/.forge/org/SECURITY.md +0 -0
- package/.planning/archive/v8-cleanup/.forge/org/TOOLS.md +0 -0
- package/.planning/archive/v8-cleanup/.forge/personas/analyst.md +0 -0
- package/.planning/archive/v8-cleanup/.forge/personas/architect.md +0 -0
- package/.planning/archive/v8-cleanup/.forge/personas/debug-specialist.md +0 -0
- package/.planning/archive/v8-cleanup/.forge/personas/developer.md +26 -0
- package/.planning/archive/v8-cleanup/.forge/personas/qa-engineer.md +0 -0
- package/.planning/archive/v8-cleanup/.forge/personas/release-manager.md +0 -0
- package/.planning/archive/v8-cleanup/.forge/personas/security-reviewer.md +33 -0
- package/.planning/archive/v8-cleanup/.forge/personas/tech-writer.md +0 -0
- package/.planning/archive/v8-cleanup/.forge/skills/api-design/SKILL.md +0 -0
- package/.planning/archive/v8-cleanup/.forge/skills/code-quality/SKILL.md +0 -0
- package/.planning/archive/v8-cleanup/.forge/skills/documentation/SKILL.md +0 -0
- package/.planning/archive/v8-cleanup/.forge/skills/security-review/SKILL.md +23 -0
- package/.planning/archive/v8-cleanup/.forge/skills/testing-standards/SKILL.md +27 -0
- package/.planning/archive/v8-cleanup/ARCHITECTURE-AUDIT-REPORT.md +90 -0
- package/.planning/archive/v8-cleanup/LOGS-BENCHMARKING.md +172 -0
- package/.planning/archive/v8-cleanup/MIND-FORGE-V6-ENTERPRISE-PROPOSAL.md +79 -0
- package/.planning/archive/v8-cleanup/ROADMAP_V7.md +67 -0
- package/.planning/archive/v8-cleanup/ROADMAP_V8.md +49 -0
- package/.planning/archive/v8-cleanup/github-actions-logs.md +88 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-1-imp/DAY1-HARDEN.md +823 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-1-imp/DAY1-IMPLEMENT.md +2459 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-1-imp/DAY1-REVIEW.md +288 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-2-imp/DAY2-HARDEN.md +954 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-2-imp/DAY2-IMPLEMENT.md +2347 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-2-imp/DAY2-REVIEW.md +422 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-3-imp/DAY3-HARDEN.md +870 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-3-imp/DAY3-IMPLEMENT.md +2798 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-3-imp/DAY3-REVIEW.md +484 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-4-imp/DAY4-HARDEN.md +1087 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-4-imp/DAY4-IMPLEMENT.md +2874 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-4-imp/DAY4-REVIEW.md +386 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-5-imp/DAY5-HARDEN.md +1078 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-5-imp/DAY5-IMPLEMENT.md +3151 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-5-imp/DAY5-REVIEW.md +345 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-6-imp/DAY6-COMPLETE.md +3919 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v1.0.0/day-7-imp-prod/DAY7-PRODUCTION-FINAL.md +4513 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/ci-actions/github-workflows-v2.md +421 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/ci-actions/v2-ci-actions.md +292 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/day-10-imp/DAY10-MULTI-MODEL.md +3402 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/day-11-imp/DAY11-PERSISTENT-MEMORY.md +3237 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/day-12-imp/DAY12-REALTIME-DASHBOARD.md +3301 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/day-13-imp/DAY13-SELF-BUILDING-SKILLS.md +3798 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/day-14-prod-v2/DAY14-V2-PRODUCTION-RELEASE.md +2255 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/day-8-imp/DAY8-AUTONOMOUS-ENGINE.md +3400 -0
- package/.planning/archive/v8-cleanup/implementation-roadmap/v2.0.0/day-9-imp/DAY9-BROWSER-RUNTIME.md +3293 -0
- package/.planning/decisions/SRE-4e54a061.md +19 -0
- package/CHANGELOG.md +14 -0
- package/MINDFORGE.md +5 -4
- package/README.md +3 -2
- package/RELEASENOTES.md +17 -0
- package/bin/autonomous/auto-runner.js +64 -0
- package/bin/engine/learning-manager.js +4 -2
- package/bin/governance/impact-analyzer.js +4 -2
- package/bin/installer-core.js +18 -2
- package/bin/models/model-router.js +3 -1
- package/bin/sre/adversarial-sre.js +109 -0
- package/bin/sre/sentinel.js +128 -0
- package/bin/sre/shadow-mirror.js +122 -0
- package/bin/sre/sli-verifier.js +81 -0
- package/docs/Context/Master-Context.md +22 -2
- package/docs/PERSONAS.md +40 -0
- package/docs/architecture/V8-SRE.md +88 -0
- package/docs/governance-guide.md +43 -17
- package/package.json +2 -2
|
@@ -0,0 +1,3293 @@
|
|
|
1
|
+
# MindForge v2 — Day 9: Persistent Browser Runtime + Visual QA
|
|
2
|
+
# Branch: `feat/mindforge-v2-browser-runtime`
|
|
3
|
+
# Prerequisite: `feat/mindforge-v2-autonomous-engine` merged to `main`
|
|
4
|
+
# Version target: v2.0.0-alpha.2
|
|
5
|
+
# Theme: "The Agent Has Eyes Now."
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## BRANCH SETUP
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
git checkout main
|
|
13
|
+
git pull origin main
|
|
14
|
+
|
|
15
|
+
# Verify Day 8 baseline is clean
|
|
16
|
+
node -e "console.log(require('./package.json').version)" # Must be 2.0.0-alpha.1
|
|
17
|
+
|
|
18
|
+
# All 16 test suites must pass before starting Day 9
|
|
19
|
+
SUITES=(install wave-engine audit compaction skills-platform \
|
|
20
|
+
integrations governance intelligence metrics \
|
|
21
|
+
distribution ci-mode sdk production migration e2e autonomous)
|
|
22
|
+
|
|
23
|
+
for suite in "${SUITES[@]}"; do
|
|
24
|
+
printf " %-30s" "${suite}..."
|
|
25
|
+
node tests/${suite}.test.js 2>&1 | tail -1
|
|
26
|
+
done
|
|
27
|
+
# ALL 16 must show "passed" — zero failures before Day 9 begins.
|
|
28
|
+
|
|
29
|
+
# Create Day 9 branch
|
|
30
|
+
git checkout -b feat/mindforge-v2-browser-runtime
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## DAY 9 SCOPE
|
|
36
|
+
|
|
37
|
+
Day 9 gives MindForge **eyes**. This is the single feature that no other
|
|
38
|
+
enterprise agentic framework has implemented end-to-end: a long-lived
|
|
39
|
+
Chromium process that persists across the entire session, keeping login state,
|
|
40
|
+
cookies, and tabs alive between every `/mindforge:browse` call.
|
|
41
|
+
|
|
42
|
+
**The competitive insight (from the v2 research report):**
|
|
43
|
+
gstack's browser subsystem is its most-cited differentiator. Their key
|
|
44
|
+
architectural decision: a browser that stays alive across calls (100-200ms
|
|
45
|
+
warm calls) is categorically different from spawning a browser per command
|
|
46
|
+
(3-5s cold start every time). MindForge Day 9 goes beyond gstack by adding:
|
|
47
|
+
enterprise governance on every visual action, `<verify-visual>` blocks in PLAN
|
|
48
|
+
files so visual verification is first-class alongside unit tests, a systematic
|
|
49
|
+
QA engine that analyses the git diff and tests every changed route, and auto-
|
|
50
|
+
generated regression tests for every bug found.
|
|
51
|
+
|
|
52
|
+
| Component | Description | Lines |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| Browser Daemon Protocol spec | Full HTTP API contract, security model, performance targets | Engine spec |
|
|
55
|
+
| `bin/browser/browser-daemon.js` | Long-lived Chromium via Playwright, localhost:7338 | Full implementation |
|
|
56
|
+
| `bin/browser/daemon-manager.js` | Start/stop/health/auto-restart lifecycle manager | Full implementation |
|
|
57
|
+
| `bin/browser/session-manager.js` | Named sessions, persistence, real-browser cookie import | Full implementation |
|
|
58
|
+
| `bin/browser/visual-verify-executor.js` | `<verify-visual>` block parser and executor | Full implementation |
|
|
59
|
+
| `bin/browser/screenshot-store.js` | Screenshot save/list/cleanup with phase namespacing | Full implementation |
|
|
60
|
+
| `bin/browser/qa-engine.js` | Diff-aware QA surface extraction, test plan, execution | Full implementation |
|
|
61
|
+
| `bin/browser/qa-report-writer.js` | QA-REPORT-[N].md generator | Full implementation |
|
|
62
|
+
| `bin/browser/regression-writer.js` | Auto-generate Playwright regression tests per bug | Full implementation |
|
|
63
|
+
| `/mindforge:browse` command | Full browser control with session management | Command spec |
|
|
64
|
+
| `/mindforge:qa` command | Systematic post-phase visual QA | Command spec |
|
|
65
|
+
| `<verify-visual>` PLAN field | Visual verification integrated into execute-phase | Spec + parser |
|
|
66
|
+
| MINDFORGE.md v2 browser settings | Full configuration for browser runtime | Schema |
|
|
67
|
+
| `tests/browser.test.js` | 17th test suite — all browser components | Test suite |
|
|
68
|
+
|
|
69
|
+
**New commands today: 40 total (38 from Day 8 + browse + qa)**
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
74
|
+
# PART 1 — IMPLEMENTATION PROMPT
|
|
75
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## TASK 1 — Scaffold Day 9 directory structure
|
|
80
|
+
|
|
81
|
+
```bash
|
|
82
|
+
# Browser runtime binaries
|
|
83
|
+
mkdir -p bin/browser
|
|
84
|
+
touch bin/browser/browser-daemon.js
|
|
85
|
+
touch bin/browser/daemon-manager.js
|
|
86
|
+
touch bin/browser/session-manager.js
|
|
87
|
+
touch bin/browser/visual-verify-executor.js
|
|
88
|
+
touch bin/browser/screenshot-store.js
|
|
89
|
+
touch bin/browser/qa-engine.js
|
|
90
|
+
touch bin/browser/qa-report-writer.js
|
|
91
|
+
touch bin/browser/regression-writer.js
|
|
92
|
+
|
|
93
|
+
# Engine specifications
|
|
94
|
+
mkdir -p .mindforge/browser
|
|
95
|
+
touch .mindforge/browser/daemon-protocol.md
|
|
96
|
+
touch .mindforge/browser/session-manager.md
|
|
97
|
+
touch .mindforge/browser/visual-verify-spec.md
|
|
98
|
+
touch .mindforge/browser/qa-engine.md
|
|
99
|
+
|
|
100
|
+
# Session storage — gitignored
|
|
101
|
+
mkdir -p .mindforge/browser/sessions
|
|
102
|
+
touch .mindforge/browser/sessions/.gitkeep
|
|
103
|
+
|
|
104
|
+
# Screenshot storage — gitignored
|
|
105
|
+
mkdir -p .planning/screenshots
|
|
106
|
+
touch .planning/screenshots/.gitkeep
|
|
107
|
+
|
|
108
|
+
# New slash commands
|
|
109
|
+
touch .claude/commands/mindforge/browse.md
|
|
110
|
+
touch .claude/commands/mindforge/qa.md
|
|
111
|
+
for cmd in browse qa; do
|
|
112
|
+
cp .claude/commands/mindforge/${cmd}.md .agent/mindforge/${cmd}.md
|
|
113
|
+
done
|
|
114
|
+
|
|
115
|
+
# Test suite
|
|
116
|
+
touch tests/browser.test.js
|
|
117
|
+
|
|
118
|
+
# Docs
|
|
119
|
+
touch docs/browser-runtime-guide.md
|
|
120
|
+
touch docs/visual-qa-guide.md
|
|
121
|
+
|
|
122
|
+
# Update .gitignore — browser artifacts must NEVER be committed
|
|
123
|
+
cat >> .gitignore << 'EOF'
|
|
124
|
+
|
|
125
|
+
# MindForge v2 — browser runtime (session files contain auth tokens)
|
|
126
|
+
.mindforge/browser/sessions/*.json
|
|
127
|
+
.planning/screenshots/
|
|
128
|
+
.planning/auto-progress.jsonl
|
|
129
|
+
EOF
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Add Playwright as optional dependency:**
|
|
133
|
+
```bash
|
|
134
|
+
node -e "
|
|
135
|
+
const fs = require('fs');
|
|
136
|
+
const p = JSON.parse(fs.readFileSync('package.json','utf8'));
|
|
137
|
+
p.optionalDependencies = p.optionalDependencies || {};
|
|
138
|
+
p.optionalDependencies['playwright-core'] = '^1.44.0';
|
|
139
|
+
fs.writeFileSync('package.json', JSON.stringify(p, null, 2) + '\n');
|
|
140
|
+
console.log('Added playwright-core as optional dependency');
|
|
141
|
+
"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
**Commit:**
|
|
145
|
+
```bash
|
|
146
|
+
git add .
|
|
147
|
+
git commit -m "chore(v2-day9): scaffold browser runtime directory structure"
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## TASK 2 — Write the Browser Daemon Protocol Specification
|
|
153
|
+
|
|
154
|
+
### `.mindforge/browser/daemon-protocol.md`
|
|
155
|
+
|
|
156
|
+
````markdown
|
|
157
|
+
# MindForge v2 — Browser Daemon Protocol
|
|
158
|
+
|
|
159
|
+
## Architecture
|
|
160
|
+
|
|
161
|
+
```
|
|
162
|
+
Agent / Command Daemon Manager Browser Daemon
|
|
163
|
+
/mindforge:browse → daemon-manager.js → browser-daemon.js
|
|
164
|
+
/mindforge:qa Start/stop/restart Playwright Chromium
|
|
165
|
+
Health check HTTP localhost:7338
|
|
166
|
+
PID management Named sessions
|
|
167
|
+
Screenshot capture
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
## Port and binding
|
|
171
|
+
|
|
172
|
+
- **Port:** 7338 (SSE stream = 7337, Dashboard = 7339)
|
|
173
|
+
- **Binding:** `127.0.0.1` ONLY — never `0.0.0.0` (per ADR-017 policy)
|
|
174
|
+
- **Protocol:** HTTP/1.1 (no TLS — localhost only, same rationale as SDK SSE)
|
|
175
|
+
- **Process lifecycle:**
|
|
176
|
+
- Starts on first API call if not running (auto-start via daemon-manager)
|
|
177
|
+
- Auto-shuts down after `BROWSER_IDLE_TIMEOUT_MINUTES` (default: 30) of no activity
|
|
178
|
+
- Survives across multiple MindForge command invocations in the same session
|
|
179
|
+
|
|
180
|
+
## Full API endpoint reference
|
|
181
|
+
|
|
182
|
+
### `GET /status`
|
|
183
|
+
```json
|
|
184
|
+
Response 200:
|
|
185
|
+
{
|
|
186
|
+
"alive": true,
|
|
187
|
+
"chromium_version": "Chromium 124.0.6367.8",
|
|
188
|
+
"sessions": ["default", "admin", "user"],
|
|
189
|
+
"active_session": "default",
|
|
190
|
+
"current_url": "https://localhost:3000/dashboard",
|
|
191
|
+
"last_action_at": "ISO-8601",
|
|
192
|
+
"idle_minutes": 2.3,
|
|
193
|
+
"memory_mb": 287,
|
|
194
|
+
"uptime_seconds": 1823
|
|
195
|
+
}
|
|
196
|
+
Response 503 (starting up):
|
|
197
|
+
{ "alive": false, "status": "starting", "eta_ms": 3000 }
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
### `POST /navigate`
|
|
201
|
+
```json
|
|
202
|
+
Request:
|
|
203
|
+
{ "url": "https://localhost:3000/login", "session": "default",
|
|
204
|
+
"wait_for": "networkidle|domcontentloaded|load", "timeout": 30000 }
|
|
205
|
+
Response:
|
|
206
|
+
{ "success": true, "final_url": "...", "title": "...", "status_code": 200,
|
|
207
|
+
"screenshot_b64": "base64...", "width": 1280, "height": 720,
|
|
208
|
+
"console_errors": [], "load_time_ms": 423 }
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### `POST /click`
|
|
212
|
+
```json
|
|
213
|
+
Request (CSS selector): { "selector": "#submit-btn", "session": "default", "screenshot_after": true }
|
|
214
|
+
Request (visible text): { "text": "Sign In", "session": "default" }
|
|
215
|
+
Request (ARIA label): { "aria_label": "Close", "session": "default" }
|
|
216
|
+
Response:
|
|
217
|
+
{ "success": true, "element_found": true, "screenshot_b64": "...",
|
|
218
|
+
"console_errors": [], "navigation_triggered": false }
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
### `POST /type`
|
|
222
|
+
```json
|
|
223
|
+
Request:
|
|
224
|
+
{ "selector": "input[name='email']", "text": "test@example.com",
|
|
225
|
+
"clear_first": true, "press_enter": false, "session": "default" }
|
|
226
|
+
Response: { "success": true, "element_found": true }
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### `POST /screenshot`
|
|
230
|
+
```json
|
|
231
|
+
Request: { "session": "default", "full_page": false }
|
|
232
|
+
Response: { "success": true, "screenshot_b64": "...", "width": 1280, "height": 720 }
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### `POST /evaluate`
|
|
236
|
+
```json
|
|
237
|
+
Request: { "script": "document.title", "session": "default" }
|
|
238
|
+
Response: { "success": true, "result": "My App", "type": "string" }
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### `POST /assert`
|
|
242
|
+
```json
|
|
243
|
+
Request: { "type": "visible|url|title|no_console_errors",
|
|
244
|
+
"selector": "h1", "expected_text": "My Projects", "session": "default" }
|
|
245
|
+
Response (pass): { "success": true, "passed": true, "actual_text": "My Projects", "screenshot_b64": "..." }
|
|
246
|
+
Response (fail): { "success": true, "passed": false, "error": "Element not found: h1", "screenshot_b64": "..." }
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
### Session endpoints
|
|
250
|
+
```
|
|
251
|
+
POST /session/create { "name": "admin", "copy_from": "default" }
|
|
252
|
+
POST /session/switch { "session": "admin" }
|
|
253
|
+
POST /session/save { "session": "admin" }
|
|
254
|
+
POST /session/load { "session": "admin" }
|
|
255
|
+
POST /session/import-cookies { "source": "chrome|arc|brave|edge|firefox", "session": "admin" }
|
|
256
|
+
POST /session/delete { "session": "admin" }
|
|
257
|
+
GET /session/list → [{ "name": "admin", "saved_at": "...", "url": "..." }]
|
|
258
|
+
POST /shutdown { "save_sessions": true }
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Error response format (all errors)
|
|
262
|
+
```json
|
|
263
|
+
{
|
|
264
|
+
"success": false,
|
|
265
|
+
"error": "Element not found: #nonexistent",
|
|
266
|
+
"error_type": "element_not_found|navigation_failed|evaluation_error|timeout|session_not_found",
|
|
267
|
+
"screenshot_b64": "base64...",
|
|
268
|
+
"url_at_error": "https://localhost:3000/dashboard"
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## Performance targets
|
|
273
|
+
|
|
274
|
+
| Action | Cold start | Warm (after init) |
|
|
275
|
+
|---|---|---|
|
|
276
|
+
| Daemon startup | 3-5 seconds | — |
|
|
277
|
+
| navigate | ~400ms first | ~150ms |
|
|
278
|
+
| click | — | 80-120ms |
|
|
279
|
+
| screenshot | — | 60ms |
|
|
280
|
+
| evaluate | — | 30ms |
|
|
281
|
+
| assert | — | 80ms |
|
|
282
|
+
|
|
283
|
+
## Security model
|
|
284
|
+
|
|
285
|
+
1. **Localhost-only binding** (127.0.0.1) — consistent with ADR-017 (SDK SSE policy)
|
|
286
|
+
2. **IP check on every request** — non-localhost → 403 immediately
|
|
287
|
+
3. **evaluate safety** — only use on YOUR OWN dev app, never on external URLs
|
|
288
|
+
4. **Session files are gitignored** — they contain auth tokens
|
|
289
|
+
5. **Screenshots are gitignored** — they may contain sensitive UI data
|
|
290
|
+
6. **Playwright sandbox flags** — `--no-sandbox` required for CI; always use in controlled env
|
|
291
|
+
````
|
|
292
|
+
|
|
293
|
+
**Commit:**
|
|
294
|
+
```bash
|
|
295
|
+
git add .mindforge/browser/daemon-protocol.md
|
|
296
|
+
git commit -m "feat(v2-browser): write browser daemon protocol specification"
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## TASK 3 — Implement the Browser Daemon
|
|
302
|
+
|
|
303
|
+
### `bin/browser/browser-daemon.js`
|
|
304
|
+
|
|
305
|
+
```javascript
|
|
306
|
+
#!/usr/bin/env node
|
|
307
|
+
/**
|
|
308
|
+
* MindForge v2 — Browser Daemon
|
|
309
|
+
* Long-lived Chromium process, HTTP API at localhost:7338.
|
|
310
|
+
*
|
|
311
|
+
* Spawned by daemon-manager.js — do NOT start directly.
|
|
312
|
+
*
|
|
313
|
+
* ENV:
|
|
314
|
+
* BROWSER_PORT — HTTP port (default: 7338)
|
|
315
|
+
* BROWSER_IDLE_TIMEOUT — Idle shutdown in minutes (default: 30)
|
|
316
|
+
* BROWSER_HEADLESS — true|false (default: true)
|
|
317
|
+
* BROWSER_VIEWPORT_WIDTH — (default: 1280)
|
|
318
|
+
* BROWSER_VIEWPORT_HEIGHT — (default: 720)
|
|
319
|
+
*/
|
|
320
|
+
'use strict';
|
|
321
|
+
|
|
322
|
+
const http = require('http');
|
|
323
|
+
|
|
324
|
+
const PORT = parseInt(process.env.BROWSER_PORT || '7338', 10);
|
|
325
|
+
const IDLE_MS = parseInt(process.env.BROWSER_IDLE_TIMEOUT || '30', 10) * 60_000;
|
|
326
|
+
const HEADLESS = process.env.BROWSER_HEADLESS !== 'false';
|
|
327
|
+
const VIEWPORT = {
|
|
328
|
+
width: parseInt(process.env.BROWSER_VIEWPORT_WIDTH || '1280', 10),
|
|
329
|
+
height: parseInt(process.env.BROWSER_VIEWPORT_HEIGHT || '720', 10),
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
let playwright, browser;
|
|
333
|
+
const sessions = {}; // name → { context, page, consoleErrors[] }
|
|
334
|
+
let lastActionAt = Date.now();
|
|
335
|
+
|
|
336
|
+
// ── Playwright initialisation ─────────────────────────────────────────────────
|
|
337
|
+
async function init() {
|
|
338
|
+
try {
|
|
339
|
+
playwright = require('playwright-core');
|
|
340
|
+
} catch {
|
|
341
|
+
process.stderr.write(
|
|
342
|
+
'[daemon] playwright-core not installed.\n' +
|
|
343
|
+
'[daemon] Run: npm install playwright-core && npx playwright install chromium\n'
|
|
344
|
+
);
|
|
345
|
+
process.exit(1);
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
browser = await playwright.chromium.launch({
|
|
349
|
+
headless: HEADLESS,
|
|
350
|
+
args: [
|
|
351
|
+
'--no-sandbox', '--disable-setuid-sandbox',
|
|
352
|
+
'--disable-dev-shm-usage', '--disable-accelerated-2d-canvas',
|
|
353
|
+
'--no-first-run', '--no-zygote',
|
|
354
|
+
],
|
|
355
|
+
});
|
|
356
|
+
|
|
357
|
+
await newSession('default');
|
|
358
|
+
process.stdout.write(`READY:${PORT}\n`);
|
|
359
|
+
process.stderr.write(`[daemon] Chromium ${browser.version()} ready on :${PORT}\n`);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// ── Session management ────────────────────────────────────────────────────────
|
|
363
|
+
async function newSession(name, copyFrom) {
|
|
364
|
+
if (sessions[name]) return sessions[name];
|
|
365
|
+
|
|
366
|
+
const context = await browser.newContext({
|
|
367
|
+
viewport: VIEWPORT,
|
|
368
|
+
userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36',
|
|
369
|
+
ignoreHTTPSErrors: true,
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
if (copyFrom && sessions[copyFrom]) {
|
|
373
|
+
const cookies = await sessions[copyFrom].context.cookies();
|
|
374
|
+
if (cookies.length) await context.addCookies(cookies);
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const page = await context.newPage();
|
|
378
|
+
const errs = [];
|
|
379
|
+
page.on('console', m => {
|
|
380
|
+
if (m.type() === 'error') errs.push({ ts: new Date().toISOString(), text: m.text(), url: page.url() });
|
|
381
|
+
});
|
|
382
|
+
|
|
383
|
+
sessions[name] = { context, page, consoleErrors: errs };
|
|
384
|
+
return sessions[name];
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
function sess(name = 'default') {
|
|
388
|
+
if (!sessions[name]) throw new Error(`Session not found: "${name}". Create it first.`);
|
|
389
|
+
return sessions[name];
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
// ── Action implementations ────────────────────────────────────────────────────
|
|
393
|
+
|
|
394
|
+
async function handleNavigate({ url, session: s = 'default', wait_for = 'networkidle', timeout = 30000 }) {
|
|
395
|
+
const { page, consoleErrors } = sess(s);
|
|
396
|
+
consoleErrors.length = 0;
|
|
397
|
+
const t0 = Date.now();
|
|
398
|
+
const resp = await page.goto(url, { waitUntil: wait_for === 'networkidle' ? 'networkidle' : 'domcontentloaded', timeout });
|
|
399
|
+
const img = await page.screenshot({ type: 'png' });
|
|
400
|
+
return {
|
|
401
|
+
success: true, final_url: page.url(), title: await page.title(),
|
|
402
|
+
status_code: resp?.status() ?? 0,
|
|
403
|
+
screenshot_b64: img.toString('base64'), width: VIEWPORT.width, height: VIEWPORT.height,
|
|
404
|
+
console_errors: [...consoleErrors], load_time_ms: Date.now() - t0,
|
|
405
|
+
};
|
|
406
|
+
}
|
|
407
|
+
|
|
408
|
+
async function handleClick({ selector, text, aria_label, session: s = 'default', screenshot_after = true, timeout = 5000 }) {
|
|
409
|
+
const { page } = sess(s);
|
|
410
|
+
const loc = selector ? page.locator(selector)
|
|
411
|
+
: text ? page.getByText(text, { exact: false })
|
|
412
|
+
: aria_label ? page.getByRole('button', { name: aria_label })
|
|
413
|
+
: (() => { throw new Error('click requires selector, text, or aria_label'); })();
|
|
414
|
+
const prev = page.url();
|
|
415
|
+
await loc.click({ timeout });
|
|
416
|
+
await page.waitForLoadState('networkidle').catch(() => {});
|
|
417
|
+
const img = screenshot_after ? await page.screenshot({ type: 'png' }) : null;
|
|
418
|
+
return { success: true, element_found: true,
|
|
419
|
+
screenshot_b64: img?.toString('base64') ?? null,
|
|
420
|
+
console_errors: [...sess(s).consoleErrors],
|
|
421
|
+
navigation_triggered: page.url() !== prev };
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
async function handleType({ selector, text, clear_first = true, press_enter = false, session: s = 'default', timeout = 5000 }) {
|
|
425
|
+
const { page } = sess(s);
|
|
426
|
+
const loc = page.locator(selector);
|
|
427
|
+
await loc.waitFor({ timeout });
|
|
428
|
+
if (clear_first) await loc.clear();
|
|
429
|
+
await loc.fill(text);
|
|
430
|
+
if (press_enter) await loc.press('Enter');
|
|
431
|
+
return { success: true, element_found: true };
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
async function handleScreenshot({ session: s = 'default', full_page = false, clip }) {
|
|
435
|
+
const { page } = sess(s);
|
|
436
|
+
const img = await page.screenshot({ type: 'png', fullPage: full_page, clip: clip ?? undefined });
|
|
437
|
+
return { success: true, screenshot_b64: img.toString('base64'), width: VIEWPORT.width, height: VIEWPORT.height, format: 'png' };
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
async function handleEvaluate({ script, session: s = 'default' }) {
|
|
441
|
+
const { page } = sess(s);
|
|
442
|
+
const result = await page.evaluate(script);
|
|
443
|
+
return { success: true, result, type: typeof result };
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
async function handleAssert({ type, selector, expected_text, session: s = 'default', timeout = 5000 }) {
|
|
447
|
+
const { page } = sess(s);
|
|
448
|
+
try {
|
|
449
|
+
switch (type) {
|
|
450
|
+
case 'visible': {
|
|
451
|
+
await page.locator(selector).waitFor({ state: 'visible', timeout });
|
|
452
|
+
const actual = expected_text ? await page.locator(selector).textContent() : null;
|
|
453
|
+
if (expected_text && !actual?.includes(expected_text)) {
|
|
454
|
+
const img = await page.screenshot({ type: 'png' });
|
|
455
|
+
return { success: true, passed: false, error: `Expected "${expected_text}", got "${actual}"`, screenshot_b64: img.toString('base64') };
|
|
456
|
+
}
|
|
457
|
+
const img = await page.screenshot({ type: 'png' });
|
|
458
|
+
return { success: true, passed: true, actual_text: actual, screenshot_b64: img.toString('base64') };
|
|
459
|
+
}
|
|
460
|
+
case 'url': {
|
|
461
|
+
const cur = page.url();
|
|
462
|
+
return { success: true, passed: cur.includes(expected_text ?? ''), actual_url: cur };
|
|
463
|
+
}
|
|
464
|
+
case 'title': {
|
|
465
|
+
const t = await page.title();
|
|
466
|
+
return { success: true, passed: t.includes(expected_text ?? ''), actual_title: t };
|
|
467
|
+
}
|
|
468
|
+
case 'no_console_errors': {
|
|
469
|
+
const errs = sess(s).consoleErrors;
|
|
470
|
+
return { success: true, passed: errs.length === 0, console_errors: [...errs] };
|
|
471
|
+
}
|
|
472
|
+
default:
|
|
473
|
+
throw new Error(`Unknown assertion type: "${type}"`);
|
|
474
|
+
}
|
|
475
|
+
} catch (err) {
|
|
476
|
+
const img = await page.screenshot({ type: 'png' }).catch(() => null);
|
|
477
|
+
return { success: true, passed: false, error: err.message, screenshot_b64: img?.toString('base64') ?? null, url_at_error: page.url() };
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
async function handleImportCookies({ source, session: s = 'default' }) {
|
|
482
|
+
const SM = require('./session-manager');
|
|
483
|
+
const cookies = await SM.importFromBrowser(source);
|
|
484
|
+
await sess(s).context.addCookies(cookies);
|
|
485
|
+
return { success: true, cookies_imported: cookies.length, session: s };
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
async function handleSaveSession({ session: s = 'default' }) {
|
|
489
|
+
const SM = require('./session-manager');
|
|
490
|
+
const filePath = await SM.saveSession(s, sess(s).context);
|
|
491
|
+
return { success: true, path: filePath, session: s };
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
async function handleLoadSession({ session: s = 'default' }) {
|
|
495
|
+
const SM = require('./session-manager');
|
|
496
|
+
if (!sessions[s]) await newSession(s);
|
|
497
|
+
const r = await SM.loadSession(s, sess(s).context);
|
|
498
|
+
return { success: true, ...r };
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
async function handleSessionCreate({ name, copy_from }) {
|
|
502
|
+
await newSession(name, copy_from);
|
|
503
|
+
return { success: true, session: name };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
async function handleSessionDelete({ session: s }) {
|
|
507
|
+
if (!sessions[s]) return { success: false, error: `Session not found: ${s}` };
|
|
508
|
+
await sessions[s].context.close().catch(() => {});
|
|
509
|
+
delete sessions[s];
|
|
510
|
+
return { success: true, deleted: s };
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
function handleSessionList() {
|
|
514
|
+
const SM = require('./session-manager');
|
|
515
|
+
return { success: true, sessions: SM.listSessions() };
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
async function handleShutdown({ save_sessions = true }) {
|
|
519
|
+
if (save_sessions) {
|
|
520
|
+
const SM = require('./session-manager');
|
|
521
|
+
for (const [name, s] of Object.entries(sessions)) {
|
|
522
|
+
await SM.saveSession(name, s.context).catch(() => {});
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
setTimeout(() => process.exit(0), 100);
|
|
526
|
+
return { success: true, sessions_saved: Object.keys(sessions) };
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
// ── Route table ───────────────────────────────────────────────────────────────
|
|
530
|
+
const ROUTES = {
|
|
531
|
+
'GET /status': () => ({
|
|
532
|
+
alive: true,
|
|
533
|
+
chromium_version: browser?.version() ?? 'unknown',
|
|
534
|
+
sessions: Object.keys(sessions),
|
|
535
|
+
last_action_at: new Date(lastActionAt).toISOString(),
|
|
536
|
+
idle_minutes: (Date.now() - lastActionAt) / 60_000,
|
|
537
|
+
}),
|
|
538
|
+
'POST /navigate': handleNavigate,
|
|
539
|
+
'POST /click': handleClick,
|
|
540
|
+
'POST /type': handleType,
|
|
541
|
+
'POST /screenshot': handleScreenshot,
|
|
542
|
+
'POST /evaluate': handleEvaluate,
|
|
543
|
+
'POST /assert': handleAssert,
|
|
544
|
+
'POST /session/create': handleSessionCreate,
|
|
545
|
+
'POST /session/switch': ({ session }) => { sess(session); return { success: true, session }; },
|
|
546
|
+
'POST /session/save': handleSaveSession,
|
|
547
|
+
'POST /session/load': handleLoadSession,
|
|
548
|
+
'POST /session/import-cookies': handleImportCookies,
|
|
549
|
+
'POST /session/delete': handleSessionDelete,
|
|
550
|
+
'GET /session/list': handleSessionList,
|
|
551
|
+
'POST /shutdown': handleShutdown,
|
|
552
|
+
};
|
|
553
|
+
|
|
554
|
+
// ── HTTP server ───────────────────────────────────────────────────────────────
|
|
555
|
+
const server = http.createServer(async (req, res) => {
|
|
556
|
+
// Security gate — localhost only
|
|
557
|
+
const addr = req.socket.remoteAddress;
|
|
558
|
+
if (addr !== '127.0.0.1' && addr !== '::1' && addr !== '::ffff:127.0.0.1') {
|
|
559
|
+
res.writeHead(403, { 'Content-Type': 'application/json' });
|
|
560
|
+
res.end(JSON.stringify({ success: false, error: 'Forbidden: localhost only' }));
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
const key = `${req.method} ${req.url}`;
|
|
565
|
+
const handler = ROUTES[key];
|
|
566
|
+
if (!handler) {
|
|
567
|
+
res.writeHead(404, { 'Content-Type': 'application/json' });
|
|
568
|
+
res.end(JSON.stringify({ success: false, error: `No handler: ${key}` }));
|
|
569
|
+
return;
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
let body = {};
|
|
573
|
+
if (req.method === 'POST') {
|
|
574
|
+
const chunks = [];
|
|
575
|
+
for await (const chunk of req) chunks.push(chunk);
|
|
576
|
+
try { body = JSON.parse(Buffer.concat(chunks).toString() || '{}'); } catch { body = {}; }
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
try {
|
|
580
|
+
lastActionAt = Date.now();
|
|
581
|
+
const result = await handler(body);
|
|
582
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
583
|
+
res.end(JSON.stringify(result));
|
|
584
|
+
} catch (err) {
|
|
585
|
+
const snapshotPng = body.session ? await sessions[body.session]?.page?.screenshot({ type: 'png' }).catch(() => null) : null;
|
|
586
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
587
|
+
res.end(JSON.stringify({
|
|
588
|
+
success: false,
|
|
589
|
+
error: err.message,
|
|
590
|
+
error_type: err.constructor?.name ?? 'Error',
|
|
591
|
+
screenshot_b64: snapshotPng?.toString('base64') ?? null,
|
|
592
|
+
}));
|
|
593
|
+
}
|
|
594
|
+
});
|
|
595
|
+
|
|
596
|
+
// ── Idle-shutdown timer ───────────────────────────────────────────────────────
|
|
597
|
+
const idleCheck = setInterval(() => {
|
|
598
|
+
if (Date.now() - lastActionAt > IDLE_MS) {
|
|
599
|
+
process.stderr.write('[daemon] Idle timeout — shutting down\n');
|
|
600
|
+
browser?.close().catch(() => {});
|
|
601
|
+
process.exit(0);
|
|
602
|
+
}
|
|
603
|
+
}, 60_000);
|
|
604
|
+
idleCheck.unref();
|
|
605
|
+
|
|
606
|
+
// ── SIGTERM — graceful shutdown with session save ─────────────────────────────
|
|
607
|
+
process.on('SIGTERM', async () => {
|
|
608
|
+
process.stderr.write('[daemon] SIGTERM — saving sessions\n');
|
|
609
|
+
try {
|
|
610
|
+
const SM = require('./session-manager');
|
|
611
|
+
for (const [n, s] of Object.entries(sessions)) await SM.saveSession(n, s.context).catch(() => {});
|
|
612
|
+
} catch {}
|
|
613
|
+
await browser?.close().catch(() => {});
|
|
614
|
+
process.exit(0);
|
|
615
|
+
});
|
|
616
|
+
|
|
617
|
+
// ── Boot ──────────────────────────────────────────────────────────────────────
|
|
618
|
+
(async () => {
|
|
619
|
+
await init();
|
|
620
|
+
server.listen(PORT, '127.0.0.1', () =>
|
|
621
|
+
process.stderr.write(`[daemon] Listening http://127.0.0.1:${PORT}\n`)
|
|
622
|
+
);
|
|
623
|
+
server.on('error', err => {
|
|
624
|
+
if (err.code === 'EADDRINUSE') {
|
|
625
|
+
process.stderr.write(`[daemon] Port ${PORT} in use — is another instance running?\n`);
|
|
626
|
+
}
|
|
627
|
+
throw err;
|
|
628
|
+
});
|
|
629
|
+
})();
|
|
630
|
+
```
|
|
631
|
+
|
|
632
|
+
---
|
|
633
|
+
|
|
634
|
+
### `bin/browser/daemon-manager.js`
|
|
635
|
+
|
|
636
|
+
```javascript
|
|
637
|
+
/**
|
|
638
|
+
* MindForge v2 — Daemon Manager
|
|
639
|
+
* Manages browser-daemon.js lifecycle.
|
|
640
|
+
* Used by all browser commands and bin/browser/*.js modules.
|
|
641
|
+
*/
|
|
642
|
+
'use strict';
|
|
643
|
+
|
|
644
|
+
const http = require('http');
|
|
645
|
+
const { spawn } = require('child_process');
|
|
646
|
+
const path = require('path');
|
|
647
|
+
const fs = require('fs');
|
|
648
|
+
|
|
649
|
+
const PORT = parseInt(process.env.BROWSER_PORT || '7338', 10);
|
|
650
|
+
const PID_FILE = path.join(process.cwd(), '.planning', 'browser-daemon.pid');
|
|
651
|
+
const DAEMON_PATH = path.join(__dirname, 'browser-daemon.js');
|
|
652
|
+
|
|
653
|
+
let _daemonProc = null;
|
|
654
|
+
|
|
655
|
+
// ── Low-level HTTP client ─────────────────────────────────────────────────────
|
|
656
|
+
async function request(method, urlPath, body, timeoutMs = 10_000) {
|
|
657
|
+
return new Promise((resolve, reject) => {
|
|
658
|
+
const payload = body ? JSON.stringify(body) : '';
|
|
659
|
+
const req = http.request({
|
|
660
|
+
hostname: '127.0.0.1', port: PORT, path: urlPath, method,
|
|
661
|
+
headers: { 'Content-Type': 'application/json', 'Content-Length': Buffer.byteLength(payload) },
|
|
662
|
+
timeout: timeoutMs,
|
|
663
|
+
}, res => {
|
|
664
|
+
let data = '';
|
|
665
|
+
res.on('data', c => (data += c));
|
|
666
|
+
res.on('end', () => { try { resolve(JSON.parse(data)); } catch { resolve({ success: false, raw: data }); } });
|
|
667
|
+
});
|
|
668
|
+
req.on('error', reject);
|
|
669
|
+
req.on('timeout', () => { req.destroy(); reject(new Error(`Daemon timeout: ${method} ${urlPath}`)); });
|
|
670
|
+
if (payload) req.write(payload);
|
|
671
|
+
req.end();
|
|
672
|
+
});
|
|
673
|
+
}
|
|
674
|
+
|
|
675
|
+
// ── Health check ──────────────────────────────────────────────────────────────
|
|
676
|
+
async function isAlive() {
|
|
677
|
+
try { return (await request('GET', '/status', null, 2000))?.alive === true; } catch { return false; }
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
// ── Start daemon ──────────────────────────────────────────────────────────────
|
|
681
|
+
async function start({ headless = true, timeout = 20_000 } = {}) {
|
|
682
|
+
if (await isAlive()) return { started: false, already_running: true, port: PORT };
|
|
683
|
+
|
|
684
|
+
return new Promise((resolve, reject) => {
|
|
685
|
+
_daemonProc = spawn('node', [DAEMON_PATH], {
|
|
686
|
+
detached: false,
|
|
687
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
688
|
+
env: { ...process.env, BROWSER_PORT: String(PORT), BROWSER_HEADLESS: String(headless) },
|
|
689
|
+
});
|
|
690
|
+
|
|
691
|
+
fs.mkdirSync(path.dirname(PID_FILE), { recursive: true });
|
|
692
|
+
fs.writeFileSync(PID_FILE, String(_daemonProc.pid));
|
|
693
|
+
|
|
694
|
+
let ready = false;
|
|
695
|
+
const timer = setTimeout(() => {
|
|
696
|
+
if (!ready) reject(new Error(`Browser daemon did not start within ${timeout}ms`));
|
|
697
|
+
}, timeout);
|
|
698
|
+
|
|
699
|
+
_daemonProc.stdout.on('data', data => {
|
|
700
|
+
if (data.toString().includes('READY:') && !ready) {
|
|
701
|
+
ready = true;
|
|
702
|
+
clearTimeout(timer);
|
|
703
|
+
resolve({ started: true, port: PORT, pid: _daemonProc.pid });
|
|
704
|
+
}
|
|
705
|
+
});
|
|
706
|
+
|
|
707
|
+
_daemonProc.stderr.on('data', data => {
|
|
708
|
+
const txt = data.toString();
|
|
709
|
+
// Only surface actual errors, not Chromium's verbose noise
|
|
710
|
+
if (txt.includes('[daemon]') || /\berror\b/i.test(txt)) process.stderr.write(txt);
|
|
711
|
+
});
|
|
712
|
+
|
|
713
|
+
_daemonProc.on('exit', (code, signal) => {
|
|
714
|
+
if (fs.existsSync(PID_FILE)) fs.unlinkSync(PID_FILE);
|
|
715
|
+
if (!ready) reject(new Error(`Daemon exited early: code=${code} signal=${signal}`));
|
|
716
|
+
});
|
|
717
|
+
});
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
// ── Ensure daemon is running ──────────────────────────────────────────────────
|
|
721
|
+
async function ensureRunning(opts) {
|
|
722
|
+
if (await isAlive()) return { running: true, port: PORT };
|
|
723
|
+
return start(opts);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
// ── Stop daemon ───────────────────────────────────────────────────────────────
|
|
727
|
+
async function stop(saveSessions = true) {
|
|
728
|
+
if (!await isAlive()) return { stopped: false, not_running: true };
|
|
729
|
+
try {
|
|
730
|
+
await request('POST', '/shutdown', { save_sessions: saveSessions }, 5000);
|
|
731
|
+
if (fs.existsSync(PID_FILE)) fs.unlinkSync(PID_FILE);
|
|
732
|
+
return { stopped: true };
|
|
733
|
+
} catch {
|
|
734
|
+
const pid = fs.existsSync(PID_FILE) ? parseInt(fs.readFileSync(PID_FILE, 'utf8')) : null;
|
|
735
|
+
if (_daemonProc) _daemonProc.kill('SIGTERM');
|
|
736
|
+
if (pid) try { process.kill(pid, 'SIGTERM'); } catch {}
|
|
737
|
+
if (fs.existsSync(PID_FILE)) fs.unlinkSync(PID_FILE);
|
|
738
|
+
return { stopped: true, forced: true };
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// ── Convenience proxy methods ─────────────────────────────────────────────────
|
|
743
|
+
const navigate = (url, opts = {}) => ensureRunning().then(() => request('POST', '/navigate', { url, ...opts }));
|
|
744
|
+
const click = (sel, opts = {}) => ensureRunning().then(() => request('POST', '/click', { selector: sel, ...opts }));
|
|
745
|
+
const type = (sel, text, opts) => ensureRunning().then(() => request('POST', '/type', { selector: sel, text, ...opts }));
|
|
746
|
+
const screenshot = (opts = {}) => ensureRunning().then(() => request('POST', '/screenshot', opts));
|
|
747
|
+
const evaluate = (script, opts) => ensureRunning().then(() => request('POST', '/evaluate', { script, ...opts }));
|
|
748
|
+
const assertV = (type, sel, exp, opts) =>
|
|
749
|
+
ensureRunning().then(() => request('POST', '/assert', { type, selector: sel, expected_text: exp, ...opts }));
|
|
750
|
+
const getStatus = () => request('GET', '/status', null, 2000).catch(() => ({ alive: false }));
|
|
751
|
+
|
|
752
|
+
module.exports = { start, stop, ensureRunning, isAlive, getStatus, request, navigate, click, type, screenshot, evaluate, assertV };
|
|
753
|
+
```
|
|
754
|
+
|
|
755
|
+
**Commit:**
|
|
756
|
+
```bash
|
|
757
|
+
git add bin/browser/browser-daemon.js bin/browser/daemon-manager.js
|
|
758
|
+
git commit -m "feat(v2-browser): implement Playwright browser daemon and daemon manager"
|
|
759
|
+
```
|
|
760
|
+
|
|
761
|
+
---
|
|
762
|
+
|
|
763
|
+
## TASK 4 — Implement the Session Manager
|
|
764
|
+
|
|
765
|
+
### `bin/browser/session-manager.js`
|
|
766
|
+
|
|
767
|
+
```javascript
|
|
768
|
+
/**
|
|
769
|
+
* MindForge v2 — Browser Session Manager
|
|
770
|
+
* Named session persistence and real-browser cookie import.
|
|
771
|
+
*
|
|
772
|
+
* ⚠️ Session files contain auth tokens — NEVER commit them.
|
|
773
|
+
* .mindforge/browser/sessions/ is gitignored.
|
|
774
|
+
*/
|
|
775
|
+
'use strict';
|
|
776
|
+
|
|
777
|
+
const fs = require('fs');
|
|
778
|
+
const path = require('path');
|
|
779
|
+
const os = require('os');
|
|
780
|
+
|
|
781
|
+
const SESSIONS_DIR = path.join(process.cwd(), '.mindforge', 'browser', 'sessions');
|
|
782
|
+
const ensureDir = () => fs.mkdirSync(SESSIONS_DIR, { recursive: true });
|
|
783
|
+
|
|
784
|
+
// ── Save session state to disk ────────────────────────────────────────────────
|
|
785
|
+
async function saveSession(name, context) {
|
|
786
|
+
ensureDir();
|
|
787
|
+
const cookies = await context.cookies();
|
|
788
|
+
const storageByOrigin = {};
|
|
789
|
+
|
|
790
|
+
// Capture localStorage per origin from all open pages
|
|
791
|
+
for (const page of context.pages()) {
|
|
792
|
+
try {
|
|
793
|
+
const origin = new URL(page.url()).origin;
|
|
794
|
+
const ls = await page.evaluate(() => {
|
|
795
|
+
const items = {};
|
|
796
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
797
|
+
const k = localStorage.key(i);
|
|
798
|
+
items[k] = localStorage.getItem(k);
|
|
799
|
+
}
|
|
800
|
+
return items;
|
|
801
|
+
}).catch(() => ({}));
|
|
802
|
+
if (Object.keys(ls).length) storageByOrigin[origin] = { localStorage: ls };
|
|
803
|
+
} catch {}
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
const data = {
|
|
807
|
+
name,
|
|
808
|
+
saved_at: new Date().toISOString(),
|
|
809
|
+
url: context.pages()[0]?.url() ?? '',
|
|
810
|
+
cookies,
|
|
811
|
+
storage: storageByOrigin,
|
|
812
|
+
_warning: 'Contains authentication cookies. NEVER commit this file.',
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
const filePath = path.join(SESSIONS_DIR, `${name}.json`);
|
|
816
|
+
fs.writeFileSync(filePath, JSON.stringify(data, null, 2));
|
|
817
|
+
return filePath;
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
// ── Load session state from disk ──────────────────────────────────────────────
|
|
821
|
+
async function loadSession(name, context) {
|
|
822
|
+
const filePath = path.join(SESSIONS_DIR, `${name}.json`);
|
|
823
|
+
if (!fs.existsSync(filePath)) throw new Error(`Session file not found: ${filePath}`);
|
|
824
|
+
|
|
825
|
+
const data = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
826
|
+
let cookiesLoaded = 0, storageItemsLoaded = 0;
|
|
827
|
+
|
|
828
|
+
if (data.cookies?.length) {
|
|
829
|
+
const now = Date.now() / 1000;
|
|
830
|
+
const valid = data.cookies.filter(c => !c.expires || c.expires === -1 || c.expires > now);
|
|
831
|
+
const expired = data.cookies.length - valid.length;
|
|
832
|
+
if (valid.length) { await context.addCookies(valid); cookiesLoaded = valid.length; }
|
|
833
|
+
if (expired) process.stderr.write(`[session-mgr] Skipped ${expired} expired cookie(s) from session "${name}"\n`);
|
|
834
|
+
}
|
|
835
|
+
|
|
836
|
+
return { cookiesLoaded, storageItemsLoaded };
|
|
837
|
+
}
|
|
838
|
+
|
|
839
|
+
// ── Import cookies from real browser ─────────────────────────────────────────
|
|
840
|
+
async function importFromBrowser(source) {
|
|
841
|
+
const cookiePath = getBrowserCookiePath(source);
|
|
842
|
+
if (!cookiePath) throw new Error(`Unsupported browser: "${source}". Supported: chrome, arc, brave, edge, firefox`);
|
|
843
|
+
if (!fs.existsSync(cookiePath)) {
|
|
844
|
+
throw new Error(
|
|
845
|
+
`Cookie file not found: ${cookiePath}\n` +
|
|
846
|
+
`Make sure ${source} is installed and has been opened at least once.\n` +
|
|
847
|
+
`On macOS with Chrome: the browser must be CLOSED (Chrome locks the DB when open).`
|
|
848
|
+
);
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
const ext = path.extname(cookiePath).toLowerCase();
|
|
852
|
+
if (ext === '.json') return parseJsonCookies(fs.readFileSync(cookiePath, 'utf8'));
|
|
853
|
+
return parseSqliteCookies(cookiePath);
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
function getBrowserCookiePath(source) {
|
|
857
|
+
const home = os.homedir();
|
|
858
|
+
const map = {
|
|
859
|
+
chrome: { darwin: `${home}/Library/Application Support/Google/Chrome/Default/Cookies`,
|
|
860
|
+
linux: `${home}/.config/google-chrome/Default/Cookies`,
|
|
861
|
+
win32: `${home}/AppData/Local/Google/Chrome/User Data/Default/Cookies` },
|
|
862
|
+
arc: { darwin: `${home}/Library/Application Support/Arc/User Data/Default/Cookies` },
|
|
863
|
+
brave: { darwin: `${home}/Library/Application Support/BraveSoftware/Brave-Browser/Default/Cookies`,
|
|
864
|
+
linux: `${home}/.config/BraveSoftware/Brave-Browser/Default/Cookies` },
|
|
865
|
+
edge: { darwin: `${home}/Library/Application Support/Microsoft Edge/Default/Cookies`,
|
|
866
|
+
win32: `${home}/AppData/Local/Microsoft/Edge/User Data/Default/Cookies` },
|
|
867
|
+
};
|
|
868
|
+
return map[source.toLowerCase()]?.[process.platform] ?? null;
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
function parseJsonCookies(json) {
|
|
872
|
+
try {
|
|
873
|
+
const arr = JSON.parse(json);
|
|
874
|
+
return (Array.isArray(arr) ? arr : []).map(c => ({
|
|
875
|
+
name: c.name ?? c.Name ?? '',
|
|
876
|
+
value: c.value ?? c.Value ?? '',
|
|
877
|
+
domain: c.domain ?? c.Domain ?? '',
|
|
878
|
+
path: c.path ?? c.Path ?? '/',
|
|
879
|
+
expires: c.expirationDate ?? c.expires ?? -1,
|
|
880
|
+
httpOnly: c.httpOnly ?? c.HttpOnly ?? false,
|
|
881
|
+
secure: c.secure ?? c.Secure ?? false,
|
|
882
|
+
sameSite: c.sameSite ?? 'Lax',
|
|
883
|
+
})).filter(c => c.name && c.domain);
|
|
884
|
+
} catch { return []; }
|
|
885
|
+
}
|
|
886
|
+
|
|
887
|
+
async function parseSqliteCookies(dbPath) {
|
|
888
|
+
try {
|
|
889
|
+
const Database = require('better-sqlite3');
|
|
890
|
+
const db = new Database(dbPath, { readonly: true, fileMustExist: true });
|
|
891
|
+
const rows = db.prepare(
|
|
892
|
+
'SELECT name, value, host_key, path, expires_utc, is_secure, is_httponly FROM cookies'
|
|
893
|
+
).all();
|
|
894
|
+
db.close();
|
|
895
|
+
const now = Date.now() / 1000;
|
|
896
|
+
return rows.map(r => ({
|
|
897
|
+
name: r.name, value: r.value, domain: r.host_key, path: r.path,
|
|
898
|
+
expires: r.expires_utc / 1_000_000 - 11_644_473_600, // Windows FILETIME → Unix
|
|
899
|
+
secure: !!r.is_secure, httpOnly: !!r.is_httponly, sameSite: 'Lax',
|
|
900
|
+
})).filter(c => c.name && c.expires > now);
|
|
901
|
+
} catch {
|
|
902
|
+
throw new Error(
|
|
903
|
+
'SQLite cookie import requires "better-sqlite3".\n' +
|
|
904
|
+
'Install: npm install better-sqlite3\n' +
|
|
905
|
+
'Or export cookies as JSON from your browser using a browser extension.'
|
|
906
|
+
);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
function listSessions() {
|
|
911
|
+
ensureDir();
|
|
912
|
+
return fs.readdirSync(SESSIONS_DIR)
|
|
913
|
+
.filter(f => f.endsWith('.json') && !f.startsWith('.'))
|
|
914
|
+
.map(f => {
|
|
915
|
+
try {
|
|
916
|
+
const d = JSON.parse(fs.readFileSync(path.join(SESSIONS_DIR, f), 'utf8'));
|
|
917
|
+
return { name: d.name, saved_at: d.saved_at, url: d.url };
|
|
918
|
+
} catch { return { name: f.replace('.json', ''), saved_at: null, url: null }; }
|
|
919
|
+
});
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
function deleteSession(name) {
|
|
923
|
+
const p = path.join(SESSIONS_DIR, `${name}.json`);
|
|
924
|
+
if (!fs.existsSync(p)) return false;
|
|
925
|
+
fs.unlinkSync(p);
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
module.exports = { saveSession, loadSession, importFromBrowser, listSessions, deleteSession };
|
|
930
|
+
```
|
|
931
|
+
|
|
932
|
+
**Commit:**
|
|
933
|
+
```bash
|
|
934
|
+
git add bin/browser/session-manager.js
|
|
935
|
+
git commit -m "feat(v2-browser): implement session manager with persistence and real-browser cookie import"
|
|
936
|
+
```
|
|
937
|
+
|
|
938
|
+
---
|
|
939
|
+
|
|
940
|
+
## TASK 5 — Implement the Screenshot Store
|
|
941
|
+
|
|
942
|
+
### `bin/browser/screenshot-store.js`
|
|
943
|
+
|
|
944
|
+
```javascript
|
|
945
|
+
/**
|
|
946
|
+
* MindForge v2 — Screenshot Store
|
|
947
|
+
* Saves / lists / cleans up browser screenshots.
|
|
948
|
+
* All screenshots are gitignored — ephemeral test artifacts.
|
|
949
|
+
*/
|
|
950
|
+
'use strict';
|
|
951
|
+
|
|
952
|
+
const fs = require('fs');
|
|
953
|
+
const path = require('path');
|
|
954
|
+
|
|
955
|
+
const STORE = path.join(process.cwd(), '.planning', 'screenshots');
|
|
956
|
+
const ensureDir = (dir) => fs.mkdirSync(dir, { recursive: true });
|
|
957
|
+
|
|
958
|
+
/**
|
|
959
|
+
* Save a base64-encoded PNG to disk.
|
|
960
|
+
* Namespaced by phase / planId so cleanup is scoped.
|
|
961
|
+
* Returns absolute path of saved file.
|
|
962
|
+
*/
|
|
963
|
+
function save(base64Png, phaseNum, planId, filename = 'screenshot.png') {
|
|
964
|
+
const dir = path.join(STORE, `phase-${phaseNum}`, String(planId));
|
|
965
|
+
ensureDir(dir);
|
|
966
|
+
const safe = filename.replace(/[^a-zA-Z0-9._-]/g, '-').replace(/\.png$/i, '') + '.png';
|
|
967
|
+
const dest = path.join(dir, safe);
|
|
968
|
+
fs.writeFileSync(dest, Buffer.from(base64Png, 'base64'));
|
|
969
|
+
return dest;
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
/** List all screenshots for a phase (and optional planId). */
|
|
973
|
+
function list(phaseNum, planId) {
|
|
974
|
+
const dir = planId
|
|
975
|
+
? path.join(STORE, `phase-${phaseNum}`, String(planId))
|
|
976
|
+
: path.join(STORE, `phase-${phaseNum}`);
|
|
977
|
+
if (!fs.existsSync(dir)) return [];
|
|
978
|
+
const walk = d => fs.readdirSync(d, { withFileTypes: true })
|
|
979
|
+
.flatMap(e => e.isDirectory() ? walk(path.join(d, e.name)) : path.join(d, e.name))
|
|
980
|
+
.filter(p => p.endsWith('.png'));
|
|
981
|
+
return walk(dir);
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
/** Delete all screenshots for a phase. */
|
|
985
|
+
function cleanup(phaseNum) {
|
|
986
|
+
const dir = path.join(STORE, `phase-${phaseNum}`);
|
|
987
|
+
if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true });
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/** Total bytes used in the screenshot store. */
|
|
991
|
+
function diskUsage() {
|
|
992
|
+
if (!fs.existsSync(STORE)) return 0;
|
|
993
|
+
let total = 0;
|
|
994
|
+
const walk = d => { for (const e of fs.readdirSync(d, { withFileTypes: true })) {
|
|
995
|
+
const p = path.join(d, e.name);
|
|
996
|
+
e.isDirectory() ? walk(p) : (total += fs.statSync(p).size);
|
|
997
|
+
}};
|
|
998
|
+
walk(STORE);
|
|
999
|
+
return total;
|
|
1000
|
+
}
|
|
1001
|
+
|
|
1002
|
+
module.exports = { save, list, cleanup, diskUsage };
|
|
1003
|
+
```
|
|
1004
|
+
|
|
1005
|
+
**Commit:**
|
|
1006
|
+
```bash
|
|
1007
|
+
git add bin/browser/screenshot-store.js
|
|
1008
|
+
git commit -m "feat(v2-browser): implement screenshot store with phase namespacing"
|
|
1009
|
+
```
|
|
1010
|
+
|
|
1011
|
+
---
|
|
1012
|
+
|
|
1013
|
+
## TASK 6 — Implement the `<verify-visual>` Parser and Executor
|
|
1014
|
+
|
|
1015
|
+
### `.mindforge/browser/visual-verify-spec.md`
|
|
1016
|
+
|
|
1017
|
+
````markdown
|
|
1018
|
+
# MindForge v2 — `<verify-visual>` Specification
|
|
1019
|
+
|
|
1020
|
+
## Overview
|
|
1021
|
+
|
|
1022
|
+
`<verify-visual>` is an optional new field in PLAN task XML.
|
|
1023
|
+
It runs AFTER `<verify>` (unit/integration tests) passes.
|
|
1024
|
+
It defines structured browser steps that the agent executes to confirm
|
|
1025
|
+
the UI looks and behaves correctly — not just that the tests pass.
|
|
1026
|
+
|
|
1027
|
+
## Syntax
|
|
1028
|
+
|
|
1029
|
+
```xml
|
|
1030
|
+
<verify-visual session="user">
|
|
1031
|
+
navigate: /dashboard
|
|
1032
|
+
wait: networkidle
|
|
1033
|
+
assert-visible: h1 "My Projects"
|
|
1034
|
+
assert-visible: .project-list
|
|
1035
|
+
assert-no-errors: true
|
|
1036
|
+
screenshot: dashboard-initial.png
|
|
1037
|
+
click: "#create-project-btn"
|
|
1038
|
+
wait: 500
|
|
1039
|
+
assert-visible: .modal "Create Project"
|
|
1040
|
+
type: input[name="project-name"] "Test Project Alpha"
|
|
1041
|
+
click: button[type="submit"]
|
|
1042
|
+
wait: networkidle
|
|
1043
|
+
assert-url: /projects
|
|
1044
|
+
screenshot: project-created.png
|
|
1045
|
+
</verify-visual>
|
|
1046
|
+
```
|
|
1047
|
+
|
|
1048
|
+
## Directives (all supported)
|
|
1049
|
+
|
|
1050
|
+
| Directive | Syntax | Description |
|
|
1051
|
+
|---|---|---|
|
|
1052
|
+
| `navigate` | `navigate: /path` or `navigate: https://url` | Navigate (relative uses DEV_SERVER_URL) |
|
|
1053
|
+
| `wait` | `wait: networkidle` or `wait: 500` | Wait for network idle OR N milliseconds |
|
|
1054
|
+
| `assert-visible` | `assert-visible: selector ["expected text"]` | Element visible + optional text match |
|
|
1055
|
+
| `assert-not-visible` | `assert-not-visible: selector` | Element NOT in viewport |
|
|
1056
|
+
| `assert-url` | `assert-url: /path` | Current URL contains string |
|
|
1057
|
+
| `assert-title` | `assert-title: "Page Title"` | Page title contains string |
|
|
1058
|
+
| `assert-no-errors` | `assert-no-errors: true` | Zero JS console errors |
|
|
1059
|
+
| `screenshot` | `screenshot: filename.png` | Capture, save to `.planning/screenshots/` |
|
|
1060
|
+
| `click` | `click: selector` or `click: "text"` | Click element |
|
|
1061
|
+
| `type` | `type: selector "text"` | Fill input (clears first) |
|
|
1062
|
+
| `clear` | `clear: selector` | Clear field |
|
|
1063
|
+
| `press` | `press: Enter` | Press keyboard key |
|
|
1064
|
+
| `scroll` | `scroll: bottom` or `scroll: selector` | Scroll page or element |
|
|
1065
|
+
| `evaluate` | `evaluate: javascript expression` | Assert JS evaluates to truthy |
|
|
1066
|
+
|
|
1067
|
+
## Session attribute
|
|
1068
|
+
|
|
1069
|
+
`session="default"` — unauthenticated (default if not specified)
|
|
1070
|
+
`session="admin"` — uses saved admin session (must exist)
|
|
1071
|
+
`session="user"` — uses saved user session
|
|
1072
|
+
|
|
1073
|
+
## Result file
|
|
1074
|
+
|
|
1075
|
+
`.planning/phases/[N]/VISUAL-VERIFY-[N]-[plan].md`
|
|
1076
|
+
|
|
1077
|
+
```markdown
|
|
1078
|
+
# Visual Verify — Phase [N], Plan [plan]
|
|
1079
|
+
Status: ✅ PASS | ❌ FAIL
|
|
1080
|
+
Session: user
|
|
1081
|
+
|
|
1082
|
+
| Step | Directive | Status | Detail |
|
|
1083
|
+
|---|---|---|---|
|
|
1084
|
+
| 1 | navigate: /dashboard | ✅ pass | 200 OK, 423ms |
|
|
1085
|
+
| 2 | assert-visible: h1 "My Projects" | ✅ pass | Found: "My Projects" |
|
|
1086
|
+
| 3 | click: "#create-project-btn" | ✅ pass | |
|
|
1087
|
+
| 4 | assert-visible: .modal | ❌ FAIL | Element not found after 5s |
|
|
1088
|
+
|
|
1089
|
+
Screenshots: dashboard-initial.png
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
## On visual verify failure
|
|
1093
|
+
|
|
1094
|
+
A `<verify-visual>` failure is treated identically to a `<verify>` failure:
|
|
1095
|
+
it triggers node repair in auto mode (RETRY first, then DECOMPOSE if needed).
|
|
1096
|
+
The failure screenshot shows exactly what the agent saw.
|
|
1097
|
+
````
|
|
1098
|
+
|
|
1099
|
+
### `bin/browser/visual-verify-executor.js`
|
|
1100
|
+
|
|
1101
|
+
```javascript
|
|
1102
|
+
/**
|
|
1103
|
+
* MindForge v2 — Visual Verify Executor
|
|
1104
|
+
* Parses <verify-visual> blocks from PLAN files and executes
|
|
1105
|
+
* them against the browser daemon.
|
|
1106
|
+
*/
|
|
1107
|
+
'use strict';
|
|
1108
|
+
|
|
1109
|
+
const fs = require('fs');
|
|
1110
|
+
const path = require('path');
|
|
1111
|
+
const DaemonMgr = require('./daemon-manager');
|
|
1112
|
+
const ScreenStore = require('./screenshot-store');
|
|
1113
|
+
|
|
1114
|
+
const DEV_SERVER = process.env.DEV_SERVER_URL || 'http://localhost:3000';
|
|
1115
|
+
|
|
1116
|
+
// ── Parser ────────────────────────────────────────────────────────────────────
|
|
1117
|
+
|
|
1118
|
+
/** Extract the raw <verify-visual> block from PLAN XML content. */
|
|
1119
|
+
function extractBlock(planContent) {
|
|
1120
|
+
const m = planContent.match(/<verify-visual([^>]*)>([\s\S]*?)<\/verify-visual>/);
|
|
1121
|
+
if (!m) return null;
|
|
1122
|
+
const sessionM = m[1].match(/session\s*=\s*["']([^"']+)["']/);
|
|
1123
|
+
return { attributes: m[1], content: m[2].trim(), session: sessionM?.[1] ?? 'default' };
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
/** Parse block content into directive objects. */
|
|
1127
|
+
function parseDirectives(content) {
|
|
1128
|
+
return content
|
|
1129
|
+
.split('\n')
|
|
1130
|
+
.map(l => l.trim())
|
|
1131
|
+
.filter(l => l.length > 0 && !l.startsWith('#'))
|
|
1132
|
+
.map(line => {
|
|
1133
|
+
const colon = line.indexOf(':');
|
|
1134
|
+
if (colon === -1) return null;
|
|
1135
|
+
const directive = line.slice(0, colon).trim();
|
|
1136
|
+
const rawArgs = line.slice(colon + 1).trim();
|
|
1137
|
+
// Split args on whitespace but keep "quoted strings" intact
|
|
1138
|
+
const args = [];
|
|
1139
|
+
const re = /"([^"]*)"|\S+/g;
|
|
1140
|
+
let m;
|
|
1141
|
+
while ((m = re.exec(rawArgs)) !== null) args.push(m[1] !== undefined ? m[1] : m[0]);
|
|
1142
|
+
return { directive, args };
|
|
1143
|
+
})
|
|
1144
|
+
.filter(Boolean);
|
|
1145
|
+
}
|
|
1146
|
+
|
|
1147
|
+
// ── Executor ──────────────────────────────────────────────────────────────────
|
|
1148
|
+
|
|
1149
|
+
async function executeBlock(phaseNum, planId, planContent) {
|
|
1150
|
+
const block = extractBlock(planContent);
|
|
1151
|
+
if (!block) return { passed: true, steps: [], skipped: true };
|
|
1152
|
+
|
|
1153
|
+
await DaemonMgr.ensureRunning({ headless: true });
|
|
1154
|
+
|
|
1155
|
+
const directives = parseDirectives(block.content);
|
|
1156
|
+
const session = block.session;
|
|
1157
|
+
const steps = [];
|
|
1158
|
+
const screenshots = [];
|
|
1159
|
+
let passed = true;
|
|
1160
|
+
|
|
1161
|
+
for (const { directive, args } of directives) {
|
|
1162
|
+
const step = { directive: `${directive}: ${args.join(' ')}`, status: 'pass', detail: '' };
|
|
1163
|
+
|
|
1164
|
+
try {
|
|
1165
|
+
switch (directive) {
|
|
1166
|
+
|
|
1167
|
+
case 'navigate': {
|
|
1168
|
+
const url = args[0]?.startsWith('http') ? args[0] : `${DEV_SERVER}${args[0]}`;
|
|
1169
|
+
const r = await DaemonMgr.request('POST', '/navigate', { url, session, wait_for: 'networkidle' });
|
|
1170
|
+
step.detail = `${r.status_code ?? 200} OK, ${r.load_time_ms ?? 0}ms`;
|
|
1171
|
+
if (!r.success) throw new Error(r.error ?? 'Navigation failed');
|
|
1172
|
+
break;
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
case 'wait': {
|
|
1176
|
+
const arg = args[0];
|
|
1177
|
+
if (arg === 'networkidle') await new Promise(r => setTimeout(r, 300));
|
|
1178
|
+
else await new Promise(r => setTimeout(r, parseInt(arg) || 500));
|
|
1179
|
+
step.detail = `waited ${arg}`;
|
|
1180
|
+
break;
|
|
1181
|
+
}
|
|
1182
|
+
|
|
1183
|
+
case 'assert-visible': {
|
|
1184
|
+
const sel = args[0];
|
|
1185
|
+
const exp = args[1] ?? null;
|
|
1186
|
+
const r = await DaemonMgr.request('POST', '/assert', { type: 'visible', selector: sel, expected_text: exp, session });
|
|
1187
|
+
step.detail = r.passed ? `found: "${r.actual_text ?? sel}"` : r.error;
|
|
1188
|
+
if (!r.passed) { step.status = 'fail'; passed = false; }
|
|
1189
|
+
break;
|
|
1190
|
+
}
|
|
1191
|
+
|
|
1192
|
+
case 'assert-not-visible': {
|
|
1193
|
+
const sel = args[0];
|
|
1194
|
+
const r = await DaemonMgr.request('POST', '/assert', { type: 'visible', selector: sel, session });
|
|
1195
|
+
if (r.passed) { step.status = 'fail'; passed = false; step.detail = `element found but should be hidden: ${sel}`; }
|
|
1196
|
+
else { step.detail = `confirmed not visible: ${sel}`; }
|
|
1197
|
+
break;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
case 'assert-url': {
|
|
1201
|
+
const r = await DaemonMgr.request('POST', '/assert', { type: 'url', expected_text: args[0], session });
|
|
1202
|
+
step.detail = r.passed ? `URL contains: ${args[0]}` : `URL "${r.actual_url}" ≠ "${args[0]}"`;
|
|
1203
|
+
if (!r.passed) { step.status = 'fail'; passed = false; }
|
|
1204
|
+
break;
|
|
1205
|
+
}
|
|
1206
|
+
|
|
1207
|
+
case 'assert-title': {
|
|
1208
|
+
const r = await DaemonMgr.request('POST', '/assert', { type: 'title', expected_text: args[0], session });
|
|
1209
|
+
step.detail = r.passed ? `title: "${r.actual_title}"` : `title mismatch`;
|
|
1210
|
+
if (!r.passed) { step.status = 'fail'; passed = false; }
|
|
1211
|
+
break;
|
|
1212
|
+
}
|
|
1213
|
+
|
|
1214
|
+
case 'assert-no-errors': {
|
|
1215
|
+
const r = await DaemonMgr.request('POST', '/assert', { type: 'no_console_errors', session });
|
|
1216
|
+
step.detail = r.passed ? 'no console errors' : `${r.console_errors?.length} error(s)`;
|
|
1217
|
+
if (!r.passed) { step.status = 'fail'; passed = false; }
|
|
1218
|
+
break;
|
|
1219
|
+
}
|
|
1220
|
+
|
|
1221
|
+
case 'screenshot': {
|
|
1222
|
+
const filename = args[0] ?? `step-${steps.length + 1}.png`;
|
|
1223
|
+
const r = await DaemonMgr.request('POST', '/screenshot', { session });
|
|
1224
|
+
if (r.success && r.screenshot_b64) {
|
|
1225
|
+
const saved = ScreenStore.save(r.screenshot_b64, phaseNum, planId, filename);
|
|
1226
|
+
screenshots.push(saved);
|
|
1227
|
+
}
|
|
1228
|
+
step.detail = `saved: ${filename}`;
|
|
1229
|
+
break;
|
|
1230
|
+
}
|
|
1231
|
+
|
|
1232
|
+
case 'click': {
|
|
1233
|
+
const sel = args[0];
|
|
1234
|
+
const body = sel.startsWith('"') ? { text: sel.replace(/"/g, ''), session } : { selector: sel, session };
|
|
1235
|
+
const r = await DaemonMgr.request('POST', '/click', body);
|
|
1236
|
+
step.detail = r.element_found ? 'clicked' : 'element not found';
|
|
1237
|
+
if (!r.success) { step.status = 'fail'; passed = false; }
|
|
1238
|
+
break;
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
case 'type': {
|
|
1242
|
+
const r = await DaemonMgr.request('POST', '/type', { selector: args[0], text: args[1] ?? '', session });
|
|
1243
|
+
step.detail = `typed into ${args[0]}`;
|
|
1244
|
+
if (!r.success) { step.status = 'fail'; passed = false; }
|
|
1245
|
+
break;
|
|
1246
|
+
}
|
|
1247
|
+
|
|
1248
|
+
case 'evaluate': {
|
|
1249
|
+
const script = args.join(' ');
|
|
1250
|
+
const r = await DaemonMgr.request('POST', '/evaluate', { script, session });
|
|
1251
|
+
const truthy = r.success && !!r.result;
|
|
1252
|
+
step.detail = `result: ${JSON.stringify(r.result)}`;
|
|
1253
|
+
if (!truthy) { step.status = 'fail'; passed = false; }
|
|
1254
|
+
break;
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
case 'press': {
|
|
1258
|
+
const { page } = require('./daemon-manager'); // direct page access not available; skip
|
|
1259
|
+
step.detail = `press "${args[0]}" — requires direct Playwright access; use click instead`;
|
|
1260
|
+
step.status = 'warn';
|
|
1261
|
+
break;
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
default:
|
|
1265
|
+
step.detail = `unknown directive "${directive}" — skipped`;
|
|
1266
|
+
step.status = 'warn';
|
|
1267
|
+
}
|
|
1268
|
+
} catch (err) {
|
|
1269
|
+
step.status = 'fail';
|
|
1270
|
+
step.detail = err.message;
|
|
1271
|
+
passed = false;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
steps.push(step);
|
|
1275
|
+
if (step.status === 'fail') break; // fail-fast for clarity
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
return { passed, steps, screenshots, session, directives_count: directives.length };
|
|
1279
|
+
}
|
|
1280
|
+
|
|
1281
|
+
// ── Report writer ─────────────────────────────────────────────────────────────
|
|
1282
|
+
function writeReport(phaseNum, planId, result) {
|
|
1283
|
+
const dir = path.join(process.cwd(), '.planning', 'phases', String(phaseNum));
|
|
1284
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1285
|
+
|
|
1286
|
+
const status = result.skipped ? '⏭️ SKIPPED'
|
|
1287
|
+
: result.passed ? '✅ PASS'
|
|
1288
|
+
: '❌ FAIL';
|
|
1289
|
+
|
|
1290
|
+
const rows = result.steps.map((s, i) => {
|
|
1291
|
+
const icon = s.status === 'pass' ? '✅' : s.status === 'fail' ? '❌' : '⚠️';
|
|
1292
|
+
return `| ${i + 1} | ${s.directive} | ${icon} ${s.status} | ${s.detail} |`;
|
|
1293
|
+
}).join('\n');
|
|
1294
|
+
|
|
1295
|
+
const links = result.screenshots
|
|
1296
|
+
.map(p => `- \`${path.relative(process.cwd(), p)}\``)
|
|
1297
|
+
.join('\n');
|
|
1298
|
+
|
|
1299
|
+
const content = [
|
|
1300
|
+
`# Visual Verify — Phase ${phaseNum}, Plan ${planId}`,
|
|
1301
|
+
`**Status:** ${status}`,
|
|
1302
|
+
`**Session:** ${result.session ?? 'default'}`,
|
|
1303
|
+
`**Directives:** ${result.directives_count ?? 0} executed`,
|
|
1304
|
+
'',
|
|
1305
|
+
'## Steps',
|
|
1306
|
+
'| # | Directive | Status | Detail |',
|
|
1307
|
+
'|---|---|---|---|',
|
|
1308
|
+
rows || '| — | (no steps) | — | — |',
|
|
1309
|
+
'',
|
|
1310
|
+
`## Screenshots (${result.screenshots?.length ?? 0})`,
|
|
1311
|
+
links || '(none)',
|
|
1312
|
+
'',
|
|
1313
|
+
`*Generated: ${new Date().toISOString()}*`,
|
|
1314
|
+
].join('\n');
|
|
1315
|
+
|
|
1316
|
+
const file = path.join(dir, `VISUAL-VERIFY-${phaseNum}-${planId}.md`);
|
|
1317
|
+
fs.writeFileSync(file, content);
|
|
1318
|
+
return file;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
module.exports = { extractBlock, parseDirectives, executeBlock, writeReport };
|
|
1322
|
+
```
|
|
1323
|
+
|
|
1324
|
+
**Commit:**
|
|
1325
|
+
```bash
|
|
1326
|
+
git add bin/browser/visual-verify-executor.js .mindforge/browser/visual-verify-spec.md
|
|
1327
|
+
git commit -m "feat(v2-browser): implement visual verify executor with full directive set"
|
|
1328
|
+
```
|
|
1329
|
+
|
|
1330
|
+
---
|
|
1331
|
+
|
|
1332
|
+
## TASK 7 — Implement the QA Engine
|
|
1333
|
+
|
|
1334
|
+
### `.mindforge/browser/qa-engine.md`
|
|
1335
|
+
|
|
1336
|
+
````markdown
|
|
1337
|
+
# MindForge v2 — QA Engine
|
|
1338
|
+
|
|
1339
|
+
## Purpose
|
|
1340
|
+
Systematic visual QA after phase execution. The QA engine analyses the phase
|
|
1341
|
+
diff, builds a test plan for every changed UI surface, executes each test
|
|
1342
|
+
through the browser, documents bugs with screenshots and reproduction steps,
|
|
1343
|
+
and auto-generates regression tests.
|
|
1344
|
+
|
|
1345
|
+
## Surface extraction from git diff
|
|
1346
|
+
|
|
1347
|
+
```
|
|
1348
|
+
Changed file Surface type Route
|
|
1349
|
+
──────────────────────────────────────────────────────────────────
|
|
1350
|
+
src/app/dashboard/page.tsx page /dashboard
|
|
1351
|
+
src/pages/login.tsx page /login
|
|
1352
|
+
src/components/ProjectCard.tsx component → find parent pages
|
|
1353
|
+
src/app/api/projects/route.ts api GET/POST /api/projects
|
|
1354
|
+
src/pages/api/users/[id].ts api GET /api/users/:id
|
|
1355
|
+
```
|
|
1356
|
+
|
|
1357
|
+
## Test plan per surface
|
|
1358
|
+
|
|
1359
|
+
For each page surface:
|
|
1360
|
+
1. Load page — assert no JS errors, main content visible
|
|
1361
|
+
2. Test with each configured session (default, admin, user)
|
|
1362
|
+
3. If forms detected — test validation (empty submit → error message)
|
|
1363
|
+
4. If auth-protected — test with unauthenticated session → redirect to /login
|
|
1364
|
+
|
|
1365
|
+
## Bug documentation format
|
|
1366
|
+
Each bug: route, session, step number, error, screenshot path, reproduction steps
|
|
1367
|
+
|
|
1368
|
+
## Regression test generation
|
|
1369
|
+
For every bug: write `tests/regression/phase[N]-[slug].test.ts` using Playwright.
|
|
1370
|
+
Committed before the QA report is written — regression is tracked in git.
|
|
1371
|
+
|
|
1372
|
+
## Integration with auto mode
|
|
1373
|
+
When `AUTO_RUN_QA_AFTER_UI_WAVES=true` in MINDFORGE.md:
|
|
1374
|
+
- QA runs automatically after waves containing page/component changes
|
|
1375
|
+
- Bugs found → DECOMPOSE node repair on the originating task
|
|
1376
|
+
- After repair and re-execution: QA re-runs the failed surfaces only
|
|
1377
|
+
````
|
|
1378
|
+
|
|
1379
|
+
### `bin/browser/qa-engine.js`
|
|
1380
|
+
|
|
1381
|
+
```javascript
|
|
1382
|
+
/**
|
|
1383
|
+
* MindForge v2 — QA Engine
|
|
1384
|
+
* Systematic visual QA driven by git diff surface extraction.
|
|
1385
|
+
*/
|
|
1386
|
+
'use strict';
|
|
1387
|
+
|
|
1388
|
+
const fs = require('fs');
|
|
1389
|
+
const path = require('path');
|
|
1390
|
+
const { execSync } = require('child_process');
|
|
1391
|
+
const DaemonMgr = require('./daemon-manager');
|
|
1392
|
+
const ScreenStore = require('./screenshot-store');
|
|
1393
|
+
|
|
1394
|
+
const DEV_SERVER = process.env.DEV_SERVER_URL || 'http://localhost:3000';
|
|
1395
|
+
|
|
1396
|
+
// ── Surface extraction from git diff ─────────────────────────────────────────
|
|
1397
|
+
function extractSurfaces(phaseNum, commitsBack = 1) {
|
|
1398
|
+
let files = [];
|
|
1399
|
+
try {
|
|
1400
|
+
files = execSync(`git diff HEAD~${commitsBack} --name-only`, { encoding: 'utf8' })
|
|
1401
|
+
.split('\n').filter(Boolean);
|
|
1402
|
+
} catch {
|
|
1403
|
+
try {
|
|
1404
|
+
files = execSync('git diff --name-only HEAD', { encoding: 'utf8' })
|
|
1405
|
+
.split('\n').filter(Boolean);
|
|
1406
|
+
} catch { return []; }
|
|
1407
|
+
}
|
|
1408
|
+
|
|
1409
|
+
const surfaces = [];
|
|
1410
|
+
const seen = new Set();
|
|
1411
|
+
|
|
1412
|
+
for (const file of files) {
|
|
1413
|
+
const s = classifyFile(file);
|
|
1414
|
+
if (!s) continue;
|
|
1415
|
+
const key = `${s.type}:${s.route ?? s.note}`;
|
|
1416
|
+
if (seen.has(key)) continue;
|
|
1417
|
+
seen.add(key);
|
|
1418
|
+
surfaces.push({ ...s, source_file: file });
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
return surfaces;
|
|
1422
|
+
}
|
|
1423
|
+
|
|
1424
|
+
function classifyFile(file) {
|
|
1425
|
+
// Next.js App Router
|
|
1426
|
+
if (/app\/.*\/page\.(tsx|jsx|ts|js)$/.test(file)) {
|
|
1427
|
+
const route = '/' + file
|
|
1428
|
+
.replace(/^.*?app\//, '').replace(/\/page\.(tsx|jsx|ts|js)$/, '')
|
|
1429
|
+
.replace(/\[([^\]]+)\]/g, ':$1');
|
|
1430
|
+
return { type: 'page', route };
|
|
1431
|
+
}
|
|
1432
|
+
// Next.js Pages Router
|
|
1433
|
+
if (/pages\/(?!_)[^/]+\.(tsx|jsx|ts|js)$/.test(file)) {
|
|
1434
|
+
const route = '/' + file
|
|
1435
|
+
.replace(/^.*?pages\//, '').replace(/\.(tsx|jsx|ts|js)$/, '')
|
|
1436
|
+
.replace(/\/index$/, '');
|
|
1437
|
+
return { type: 'page', route };
|
|
1438
|
+
}
|
|
1439
|
+
// API routes
|
|
1440
|
+
if (/api\/.*\.(ts|js)$/.test(file) || /routes\/.*\.(ts|js)$/.test(file)) {
|
|
1441
|
+
const route = '/' + file.replace(/^.*?(api|routes)\//, '$1/').replace(/\.(ts|js)$/, '');
|
|
1442
|
+
return { type: 'api', route };
|
|
1443
|
+
}
|
|
1444
|
+
// Components (find parent pages for testing)
|
|
1445
|
+
if (/components\/.*\.(tsx|jsx)$/.test(file)) {
|
|
1446
|
+
return { type: 'component', route: null, note: `Component changed: ${file}` };
|
|
1447
|
+
}
|
|
1448
|
+
return null;
|
|
1449
|
+
}
|
|
1450
|
+
|
|
1451
|
+
// ── Test execution ────────────────────────────────────────────────────────────
|
|
1452
|
+
async function runQA(phaseNum, opts = {}) {
|
|
1453
|
+
const { sessions = ['default'], routes = null, commitsBack = 1 } = opts;
|
|
1454
|
+
|
|
1455
|
+
await DaemonMgr.ensureRunning({ headless: true });
|
|
1456
|
+
|
|
1457
|
+
const surfaces = routes
|
|
1458
|
+
? routes.map(r => ({ type: 'page', route: r, source_file: r }))
|
|
1459
|
+
: extractSurfaces(phaseNum, commitsBack).filter(s => s.type !== 'component');
|
|
1460
|
+
|
|
1461
|
+
if (!surfaces.length) {
|
|
1462
|
+
return { surfaces: 0, passed: 0, failed: 0, bugs: [], results: [], message: 'No testable surfaces found in diff' };
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
const results = [];
|
|
1466
|
+
const bugs = [];
|
|
1467
|
+
|
|
1468
|
+
for (const surface of surfaces) {
|
|
1469
|
+
for (const session of sessions) {
|
|
1470
|
+
const testName = `${surface.route} [${session}]`;
|
|
1471
|
+
const steps = [];
|
|
1472
|
+
let passed = true;
|
|
1473
|
+
let failedStep = null;
|
|
1474
|
+
|
|
1475
|
+
// ── Base page test ────────────────────────────────────────────────────
|
|
1476
|
+
const testPlan = buildPageTestPlan(surface.route, session);
|
|
1477
|
+
|
|
1478
|
+
for (const step of testPlan) {
|
|
1479
|
+
let r;
|
|
1480
|
+
try {
|
|
1481
|
+
switch (step.directive) {
|
|
1482
|
+
case 'navigate':
|
|
1483
|
+
r = await DaemonMgr.request('POST', '/navigate', {
|
|
1484
|
+
url: step.args[0].startsWith('http') ? step.args[0] : `${DEV_SERVER}${step.args[0]}`,
|
|
1485
|
+
session, wait_for: 'networkidle',
|
|
1486
|
+
});
|
|
1487
|
+
steps.push({ ...step, passed: r.success, detail: `${r.status_code ?? 200} OK` });
|
|
1488
|
+
if (!r.success) { passed = false; failedStep = { step, error: r.error }; }
|
|
1489
|
+
break;
|
|
1490
|
+
|
|
1491
|
+
case 'assert-visible':
|
|
1492
|
+
r = await DaemonMgr.request('POST', '/assert',
|
|
1493
|
+
{ type: 'visible', selector: step.args[0], expected_text: step.args[1] ?? null, session });
|
|
1494
|
+
steps.push({ ...step, passed: r.passed, detail: r.error ?? r.actual_text ?? '' });
|
|
1495
|
+
if (!r.passed) { passed = false; failedStep = { step, result: r }; }
|
|
1496
|
+
break;
|
|
1497
|
+
|
|
1498
|
+
case 'assert-no-errors':
|
|
1499
|
+
r = await DaemonMgr.request('POST', '/assert', { type: 'no_console_errors', session });
|
|
1500
|
+
steps.push({ ...step, passed: r.passed,
|
|
1501
|
+
detail: r.passed ? 'no errors' : `${r.console_errors?.length} error(s): ${r.console_errors?.[0]?.text ?? ''}` });
|
|
1502
|
+
if (!r.passed) { passed = false; failedStep = { step, result: r }; }
|
|
1503
|
+
break;
|
|
1504
|
+
|
|
1505
|
+
case 'screenshot':
|
|
1506
|
+
r = await DaemonMgr.request('POST', '/screenshot', { session });
|
|
1507
|
+
if (r.success && r.screenshot_b64) {
|
|
1508
|
+
ScreenStore.save(r.screenshot_b64, phaseNum, 'qa', `${step.args[0]}-${session}.png`);
|
|
1509
|
+
}
|
|
1510
|
+
steps.push({ ...step, passed: true, detail: `saved ${step.args[0]}` });
|
|
1511
|
+
break;
|
|
1512
|
+
}
|
|
1513
|
+
} catch (err) {
|
|
1514
|
+
steps.push({ ...step, passed: false, detail: err.message });
|
|
1515
|
+
passed = false; failedStep = { step, error: err.message };
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
if (!passed) break;
|
|
1519
|
+
}
|
|
1520
|
+
|
|
1521
|
+
results.push({ surface: surface.route, session, passed, steps });
|
|
1522
|
+
|
|
1523
|
+
if (!passed && failedStep) {
|
|
1524
|
+
// Capture failure screenshot
|
|
1525
|
+
const snap = await DaemonMgr.request('POST', '/screenshot', { session }).catch(() => null);
|
|
1526
|
+
const screenshotPath = snap?.success
|
|
1527
|
+
? ScreenStore.save(snap.screenshot_b64, phaseNum, 'qa-failures',
|
|
1528
|
+
`${surface.route.replace(/\//g, '-').slice(1)}-${session}-fail.png`)
|
|
1529
|
+
: null;
|
|
1530
|
+
|
|
1531
|
+
bugs.push({
|
|
1532
|
+
surface: surface.route,
|
|
1533
|
+
source_file: surface.source_file,
|
|
1534
|
+
session,
|
|
1535
|
+
failed_step_directive: failedStep.step?.directive,
|
|
1536
|
+
error: failedStep.error ?? failedStep.result?.error ?? 'assertion failed',
|
|
1537
|
+
screenshot_path: screenshotPath,
|
|
1538
|
+
reproduction: buildReproSteps(surface.route, testPlan, failedStep),
|
|
1539
|
+
});
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
|
|
1544
|
+
return {
|
|
1545
|
+
surfaces: surfaces.length,
|
|
1546
|
+
passed: results.filter(r => r.passed).length,
|
|
1547
|
+
failed: results.filter(r => !r.passed).length,
|
|
1548
|
+
bugs,
|
|
1549
|
+
results,
|
|
1550
|
+
};
|
|
1551
|
+
}
|
|
1552
|
+
|
|
1553
|
+
function buildPageTestPlan(route, session) {
|
|
1554
|
+
const slug = route.replace(/\//g, '-').slice(1) || 'home';
|
|
1555
|
+
return [
|
|
1556
|
+
{ directive: 'navigate', args: [route] },
|
|
1557
|
+
{ directive: 'assert-no-errors', args: ['true'] },
|
|
1558
|
+
{ directive: 'screenshot', args: [`${slug}-load`] },
|
|
1559
|
+
{ directive: 'assert-visible', args: ['body'] },
|
|
1560
|
+
];
|
|
1561
|
+
}
|
|
1562
|
+
|
|
1563
|
+
function buildReproSteps(route, plan, failedStep) {
|
|
1564
|
+
const steps = [`1. Navigate to ${route}`];
|
|
1565
|
+
plan.forEach((s, i) => steps.push(`${i + 2}. ${s.directive}: ${s.args?.join(' ') ?? ''}`));
|
|
1566
|
+
steps.push(`Expected: step passes`);
|
|
1567
|
+
steps.push(`Actual: ${failedStep.error ?? 'assertion failed'}`);
|
|
1568
|
+
return steps;
|
|
1569
|
+
}
|
|
1570
|
+
|
|
1571
|
+
module.exports = { runQA, extractSurfaces, classifyFile };
|
|
1572
|
+
```
|
|
1573
|
+
|
|
1574
|
+
---
|
|
1575
|
+
|
|
1576
|
+
### `bin/browser/qa-report-writer.js`
|
|
1577
|
+
|
|
1578
|
+
```javascript
|
|
1579
|
+
/**
|
|
1580
|
+
* MindForge v2 — QA Report Writer
|
|
1581
|
+
* Writes QA-REPORT-[N].md to .planning/phases/[N]/
|
|
1582
|
+
*/
|
|
1583
|
+
'use strict';
|
|
1584
|
+
|
|
1585
|
+
const fs = require('fs');
|
|
1586
|
+
const path = require('path');
|
|
1587
|
+
|
|
1588
|
+
function write(phaseNum, qaResult) {
|
|
1589
|
+
const dir = path.join(process.cwd(), '.planning', 'phases', String(phaseNum));
|
|
1590
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1591
|
+
|
|
1592
|
+
const total = qaResult.passed + qaResult.failed;
|
|
1593
|
+
const lines = [
|
|
1594
|
+
`# QA Report — Phase ${phaseNum}`,
|
|
1595
|
+
`**Generated:** ${new Date().toISOString()}`,
|
|
1596
|
+
`**Surfaces tested:** ${qaResult.surfaces}`,
|
|
1597
|
+
`**Test cases:** ${total} | **Passed:** ${qaResult.passed} ✅ | **Failed:** ${qaResult.failed} ❌`,
|
|
1598
|
+
'',
|
|
1599
|
+
'## Summary',
|
|
1600
|
+
'',
|
|
1601
|
+
'| Surface | Session | Result |',
|
|
1602
|
+
'|---|---|---|',
|
|
1603
|
+
...qaResult.results.map(r => `| ${r.surface} | ${r.session} | ${r.passed ? '✅ Pass' : '❌ Fail'} |`),
|
|
1604
|
+
];
|
|
1605
|
+
|
|
1606
|
+
if (qaResult.bugs.length) {
|
|
1607
|
+
lines.push('', '## Bugs found');
|
|
1608
|
+
qaResult.bugs.forEach((bug, i) => {
|
|
1609
|
+
lines.push('', `### Bug ${i + 1}: ${bug.surface} [${bug.session}]`);
|
|
1610
|
+
lines.push(`**Error:** ${bug.error}`);
|
|
1611
|
+
lines.push(`**Source file:** ${bug.source_file}`);
|
|
1612
|
+
lines.push(`**Failed step:** ${bug.failed_step_directive ?? 'unknown'}`);
|
|
1613
|
+
if (bug.screenshot_path) lines.push(`**Screenshot:** \`${path.relative(process.cwd(), bug.screenshot_path)}\``);
|
|
1614
|
+
lines.push('', '**Reproduction steps:**');
|
|
1615
|
+
bug.reproduction.forEach(s => lines.push(s));
|
|
1616
|
+
});
|
|
1617
|
+
} else {
|
|
1618
|
+
lines.push('', '## ✅ No bugs found');
|
|
1619
|
+
}
|
|
1620
|
+
|
|
1621
|
+
const file = path.join(dir, `QA-REPORT-${phaseNum}.md`);
|
|
1622
|
+
fs.writeFileSync(file, lines.join('\n'));
|
|
1623
|
+
return file;
|
|
1624
|
+
}
|
|
1625
|
+
|
|
1626
|
+
module.exports = { write };
|
|
1627
|
+
```
|
|
1628
|
+
|
|
1629
|
+
---
|
|
1630
|
+
|
|
1631
|
+
### `bin/browser/regression-writer.js`
|
|
1632
|
+
|
|
1633
|
+
```javascript
|
|
1634
|
+
/**
|
|
1635
|
+
* MindForge v2 — Regression Test Writer
|
|
1636
|
+
* Auto-generates Playwright regression tests for QA-found bugs.
|
|
1637
|
+
*/
|
|
1638
|
+
'use strict';
|
|
1639
|
+
|
|
1640
|
+
const fs = require('fs');
|
|
1641
|
+
const path = require('path');
|
|
1642
|
+
|
|
1643
|
+
function write(bug, phaseNum) {
|
|
1644
|
+
const dir = path.join(process.cwd(), 'tests', 'regression');
|
|
1645
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
1646
|
+
|
|
1647
|
+
const slug = `phase${phaseNum}-${(bug.surface ?? 'bug')
|
|
1648
|
+
.replace(/[^a-zA-Z0-9]/g, '-').replace(/^-+|-+$/g, '').toLowerCase()
|
|
1649
|
+
.slice(0, 50)}`;
|
|
1650
|
+
|
|
1651
|
+
const file = path.join(dir, `${slug}.test.ts`);
|
|
1652
|
+
|
|
1653
|
+
const content = [
|
|
1654
|
+
`// Auto-generated by MindForge QA Engine — Phase ${phaseNum}`,
|
|
1655
|
+
`// Route: ${bug.surface} Session: ${bug.session}`,
|
|
1656
|
+
`// Error: ${bug.error}`,
|
|
1657
|
+
`// DO NOT DELETE — prevents regression`,
|
|
1658
|
+
``,
|
|
1659
|
+
`import { test, expect } from '@playwright/test';`,
|
|
1660
|
+
``,
|
|
1661
|
+
`test('${bug.surface} [${bug.session}] — ${bug.error?.slice(0, 60) ?? 'QA failure'}', async ({ page }) => {`,
|
|
1662
|
+
` const consoleErrors: string[] = [];`,
|
|
1663
|
+
` page.on('console', msg => { if (msg.type() === 'error') consoleErrors.push(msg.text()); });`,
|
|
1664
|
+
``,
|
|
1665
|
+
` await page.goto('${bug.surface}');`,
|
|
1666
|
+
``,
|
|
1667
|
+
` // Verify no JS console errors`,
|
|
1668
|
+
` expect(consoleErrors).toHaveLength(0);`,
|
|
1669
|
+
``,
|
|
1670
|
+
` // TODO: Add specific regression assertion from reproduction steps:`,
|
|
1671
|
+
...bug.reproduction.slice(1).map(s => ` // ${s}`),
|
|
1672
|
+
`});`,
|
|
1673
|
+
].join('\n');
|
|
1674
|
+
|
|
1675
|
+
fs.writeFileSync(file, content);
|
|
1676
|
+
return file;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
module.exports = { write };
|
|
1680
|
+
```
|
|
1681
|
+
|
|
1682
|
+
**Commit:**
|
|
1683
|
+
```bash
|
|
1684
|
+
git add bin/browser/qa-engine.js bin/browser/qa-report-writer.js \
|
|
1685
|
+
bin/browser/regression-writer.js .mindforge/browser/qa-engine.md
|
|
1686
|
+
git commit -m "feat(v2-browser): implement QA engine, report writer, and regression test writer"
|
|
1687
|
+
```
|
|
1688
|
+
|
|
1689
|
+
---
|
|
1690
|
+
|
|
1691
|
+
## TASK 8 — Write `/mindforge:browse` and `/mindforge:qa` commands
|
|
1692
|
+
|
|
1693
|
+
### `.claude/commands/mindforge/browse.md`
|
|
1694
|
+
|
|
1695
|
+
```markdown
|
|
1696
|
+
# MindForge v2 — Browse Command
|
|
1697
|
+
# Usage: /mindforge:browse [url|--action|--session|--cookies|--status|--stop]
|
|
1698
|
+
# Version: v2.0.0-alpha.2
|
|
1699
|
+
|
|
1700
|
+
## Purpose
|
|
1701
|
+
Control a persistent Chromium browser from within any MindForge session.
|
|
1702
|
+
Navigate to your app, click elements, fill forms, capture screenshots, run
|
|
1703
|
+
JavaScript — all while keeping login state and cookies alive between calls.
|
|
1704
|
+
The browser daemon starts automatically on first call (3-5s cold start, 80-200ms warm).
|
|
1705
|
+
|
|
1706
|
+
## Navigation (most common use)
|
|
1707
|
+
```
|
|
1708
|
+
/mindforge:browse /dashboard
|
|
1709
|
+
/mindforge:browse https://localhost:3000/login
|
|
1710
|
+
/mindforge:browse https://staging.myapp.com
|
|
1711
|
+
```
|
|
1712
|
+
Returns: inline screenshot + page title + HTTP status + any JS console errors
|
|
1713
|
+
|
|
1714
|
+
## Actions
|
|
1715
|
+
|
|
1716
|
+
### Click
|
|
1717
|
+
```
|
|
1718
|
+
/mindforge:browse --action click "#submit-btn"
|
|
1719
|
+
/mindforge:browse --action click "text=Sign In"
|
|
1720
|
+
/mindforge:browse --action click "[aria-label='Close']"
|
|
1721
|
+
```
|
|
1722
|
+
|
|
1723
|
+
### Type
|
|
1724
|
+
```
|
|
1725
|
+
/mindforge:browse --action type "input[name='email']" "test@company.com"
|
|
1726
|
+
```
|
|
1727
|
+
⚠️ Never hardcode real passwords. Use test credentials or `[ENV_VAR]` references.
|
|
1728
|
+
|
|
1729
|
+
### Screenshot only
|
|
1730
|
+
```
|
|
1731
|
+
/mindforge:browse --action screenshot
|
|
1732
|
+
/mindforge:browse --action screenshot --save dashboard.png
|
|
1733
|
+
```
|
|
1734
|
+
|
|
1735
|
+
### Evaluate JavaScript
|
|
1736
|
+
```
|
|
1737
|
+
/mindforge:browse --action evaluate "document.title"
|
|
1738
|
+
/mindforge:browse --action evaluate "window.__APP_VERSION__"
|
|
1739
|
+
```
|
|
1740
|
+
⚠️ Only use on YOUR OWN dev app — never on external URLs.
|
|
1741
|
+
|
|
1742
|
+
### Assert conditions
|
|
1743
|
+
```
|
|
1744
|
+
/mindforge:browse --action assert-visible "h1" "My Projects"
|
|
1745
|
+
/mindforge:browse --action assert-url "/dashboard"
|
|
1746
|
+
/mindforge:browse --action assert-no-errors
|
|
1747
|
+
```
|
|
1748
|
+
|
|
1749
|
+
## Session management
|
|
1750
|
+
|
|
1751
|
+
```
|
|
1752
|
+
/mindforge:browse --session admin /admin/users # Use admin session
|
|
1753
|
+
/mindforge:browse --list-sessions # List saved sessions
|
|
1754
|
+
/mindforge:browse --create-session admin # Create empty admin session
|
|
1755
|
+
/mindforge:browse --import-session admin --from chrome # Import Chrome cookies
|
|
1756
|
+
/mindforge:browse --save-session admin # Save to disk
|
|
1757
|
+
/mindforge:browse --load-session admin # Load from disk
|
|
1758
|
+
/mindforge:browse --delete-session admin # Delete from disk
|
|
1759
|
+
```
|
|
1760
|
+
|
|
1761
|
+
## Daemon management
|
|
1762
|
+
```
|
|
1763
|
+
/mindforge:browse --status # Daemon health + active sessions
|
|
1764
|
+
/mindforge:browse --start # Explicit start (auto-starts on first call)
|
|
1765
|
+
/mindforge:browse --stop # Stop, save all sessions
|
|
1766
|
+
/mindforge:browse --stop --no-save # Stop, discard session state
|
|
1767
|
+
```
|
|
1768
|
+
|
|
1769
|
+
## Security rules (ALWAYS follow)
|
|
1770
|
+
1. NEVER type real/production passwords in browse commands
|
|
1771
|
+
2. NEVER use `--action evaluate` on external/untrusted URLs
|
|
1772
|
+
3. Session files (.mindforge/browser/sessions/) are gitignored — NEVER commit them
|
|
1773
|
+
4. Use test accounts only — not your production admin account
|
|
1774
|
+
5. Browser daemon is localhost-only — no network exposure
|
|
1775
|
+
|
|
1776
|
+
## AUDIT entry
|
|
1777
|
+
```json
|
|
1778
|
+
{ "event": "browser_action", "action": "navigate", "url": "...", "session": "default", "success": true }
|
|
1779
|
+
```
|
|
1780
|
+
```
|
|
1781
|
+
|
|
1782
|
+
### `.claude/commands/mindforge/qa.md`
|
|
1783
|
+
|
|
1784
|
+
```markdown
|
|
1785
|
+
# MindForge v2 — QA Command
|
|
1786
|
+
# Usage: /mindforge:qa [--phase N] [--route /path] [--session S] [--full] [--no-write-tests]
|
|
1787
|
+
# Version: v2.0.0-alpha.2
|
|
1788
|
+
|
|
1789
|
+
## Purpose
|
|
1790
|
+
Systematic visual QA of your application after phase execution.
|
|
1791
|
+
The QA engine analyses the git diff for the phase, identifies every changed
|
|
1792
|
+
UI surface, and tests each one through a real Chromium browser — catching
|
|
1793
|
+
what unit tests miss: rendering bugs, auth redirects, form validation gaps,
|
|
1794
|
+
and JavaScript errors that only appear in the browser context.
|
|
1795
|
+
|
|
1796
|
+
## Core usage
|
|
1797
|
+
```
|
|
1798
|
+
/mindforge:qa --phase 3 # QA for Phase 3 (default: current phase)
|
|
1799
|
+
/mindforge:qa --route /dashboard # Test specific route
|
|
1800
|
+
/mindforge:qa --session admin # Test with admin session
|
|
1801
|
+
/mindforge:qa --full # Test ALL routes, not just changed ones
|
|
1802
|
+
```
|
|
1803
|
+
|
|
1804
|
+
## What QA tests per surface
|
|
1805
|
+
1. **Page loads** — navigate, assert no JS errors, main content visible
|
|
1806
|
+
2. **Session coverage** — test with each configured session
|
|
1807
|
+
3. **Form validation** — if forms found: test empty submit, error messages
|
|
1808
|
+
4. **Auth protection** — if page is behind auth: verify redirect for unauthenticated session
|
|
1809
|
+
|
|
1810
|
+
## Output
|
|
1811
|
+
|
|
1812
|
+
### During execution
|
|
1813
|
+
```
|
|
1814
|
+
⚡ MindForge QA — Phase 3
|
|
1815
|
+
─────────────────────────────────────────
|
|
1816
|
+
Surfaces found: 3 (2 pages, 1 API)
|
|
1817
|
+
|
|
1818
|
+
/dashboard [default] ✅ Pass
|
|
1819
|
+
/login [default] ❌ FAIL — empty email allows submit
|
|
1820
|
+
/api/users [admin] ✅ Pass
|
|
1821
|
+
|
|
1822
|
+
─────────────────────────────────────────
|
|
1823
|
+
3/3 tests | 1 bug found
|
|
1824
|
+
Regression test: tests/regression/phase3-login.test.ts ✅
|
|
1825
|
+
Report: .planning/phases/3/QA-REPORT-3.md
|
|
1826
|
+
```
|
|
1827
|
+
|
|
1828
|
+
### After execution
|
|
1829
|
+
- `.planning/phases/[N]/QA-REPORT-[N].md` — full report
|
|
1830
|
+
- `tests/regression/phase[N]-[slug].test.ts` — for each bug found
|
|
1831
|
+
- `.planning/screenshots/phase-[N]/qa/` — screenshots (gitignored)
|
|
1832
|
+
|
|
1833
|
+
## Integration with auto mode
|
|
1834
|
+
When `AUTO_RUN_QA_AFTER_UI_WAVES=true` in MINDFORGE.md:
|
|
1835
|
+
- QA runs automatically after waves containing page/component changes
|
|
1836
|
+
- Bugs → DECOMPOSE node repair on the originating task
|
|
1837
|
+
- After repair and re-execution: QA re-runs failed surfaces only
|
|
1838
|
+
|
|
1839
|
+
## AUDIT entry
|
|
1840
|
+
```json
|
|
1841
|
+
{
|
|
1842
|
+
"event": "qa_completed",
|
|
1843
|
+
"phase": 3,
|
|
1844
|
+
"surfaces": 3,
|
|
1845
|
+
"passed": 2,
|
|
1846
|
+
"failed": 1,
|
|
1847
|
+
"bugs": 1,
|
|
1848
|
+
"regression_tests_written": 1
|
|
1849
|
+
}
|
|
1850
|
+
```
|
|
1851
|
+
```
|
|
1852
|
+
|
|
1853
|
+
**Commit:**
|
|
1854
|
+
```bash
|
|
1855
|
+
cp .claude/commands/mindforge/browse.md .agent/mindforge/browse.md
|
|
1856
|
+
cp .claude/commands/mindforge/qa.md .agent/mindforge/qa.md
|
|
1857
|
+
git add .claude/commands/mindforge/browse.md .claude/commands/mindforge/qa.md \
|
|
1858
|
+
.agent/mindforge/browse.md .agent/mindforge/qa.md
|
|
1859
|
+
git commit -m "feat(v2-browser): add /mindforge:browse and /mindforge:qa commands"
|
|
1860
|
+
```
|
|
1861
|
+
|
|
1862
|
+
---
|
|
1863
|
+
|
|
1864
|
+
## TASK 9 — Update CLAUDE.md, execute-phase, and MINDFORGE.md schema
|
|
1865
|
+
|
|
1866
|
+
### Add to `.claude/CLAUDE.md` (and mirror to `.agent/CLAUDE.md`)
|
|
1867
|
+
|
|
1868
|
+
```markdown
|
|
1869
|
+
---
|
|
1870
|
+
|
|
1871
|
+
## BROWSER RUNTIME (v2.0.0 — Day 9)
|
|
1872
|
+
|
|
1873
|
+
### Daemon awareness
|
|
1874
|
+
Browser daemon runs at localhost:7338 (when active).
|
|
1875
|
+
Start/stop via daemon-manager.js — never start browser-daemon.js directly.
|
|
1876
|
+
Auto-starts on first /mindforge:browse call. Auto-shuts down after BROWSER_IDLE_TIMEOUT_MINUTES.
|
|
1877
|
+
|
|
1878
|
+
### <verify-visual> in PLAN files
|
|
1879
|
+
When a PLAN task has `<verify-visual>`:
|
|
1880
|
+
1. Run `<verify>` (unit/integration tests) first — must pass
|
|
1881
|
+
2. Call `bin/browser/visual-verify-executor.js` to execute the block
|
|
1882
|
+
3. Write result to VISUAL-VERIFY-[N]-[plan].md
|
|
1883
|
+
4. If any assertion FAILS: treat as verify failure → node repair (RETRY)
|
|
1884
|
+
5. Never skip visual verify even in auto mode
|
|
1885
|
+
|
|
1886
|
+
### Session security (strict rules)
|
|
1887
|
+
- NEVER type real passwords in browse commands — use test accounts
|
|
1888
|
+
- Session files are gitignored — they contain auth tokens
|
|
1889
|
+
- NEVER use `--action evaluate` on external URLs — your app only
|
|
1890
|
+
- Browser daemon is localhost-only — same policy as ADR-017
|
|
1891
|
+
|
|
1892
|
+
### QA integration with auto mode
|
|
1893
|
+
When `AUTO_RUN_QA_AFTER_UI_WAVES=true` and a wave includes page/component changes:
|
|
1894
|
+
1. Run `/mindforge:qa --phase N` after wave completes
|
|
1895
|
+
2. Each bug found: write regression test, then DECOMPOSE the originating task
|
|
1896
|
+
3. After repair + re-execute: re-run QA on failed surfaces only
|
|
1897
|
+
|
|
1898
|
+
### New commands (Day 9)
|
|
1899
|
+
- /mindforge:browse — persistent browser control
|
|
1900
|
+
- /mindforge:qa — systematic post-phase visual QA
|
|
1901
|
+
|
|
1902
|
+
---
|
|
1903
|
+
```
|
|
1904
|
+
|
|
1905
|
+
### Update `execute-phase.md` — add visual verify hook
|
|
1906
|
+
|
|
1907
|
+
```markdown
|
|
1908
|
+
## Visual verification (v2.0.0)
|
|
1909
|
+
|
|
1910
|
+
After `<verify>` passes (all automated tests green):
|
|
1911
|
+
|
|
1912
|
+
```bash
|
|
1913
|
+
# Check for <verify-visual> block in current PLAN file
|
|
1914
|
+
PLAN_CONTENT=$(cat "${PLAN_FILE}")
|
|
1915
|
+
HAS_VISUAL=$(echo "${PLAN_CONTENT}" | grep -c "<verify-visual")
|
|
1916
|
+
|
|
1917
|
+
if [ "${HAS_VISUAL}" -gt 0 ]; then
|
|
1918
|
+
echo " 🔍 Running visual verification..."
|
|
1919
|
+
|
|
1920
|
+
# Start browser daemon if needed
|
|
1921
|
+
DAEMON_ALIVE=$(curl -sf http://127.0.0.1:7338/status 2>/dev/null | \
|
|
1922
|
+
python3 -c "import sys,json;print(json.load(sys.stdin).get('alive',False))" 2>/dev/null || echo "false")
|
|
1923
|
+
[ "${DAEMON_ALIVE}" = "True" ] || node bin/browser/daemon-manager.js start
|
|
1924
|
+
|
|
1925
|
+
# Execute visual verification
|
|
1926
|
+
node -e "
|
|
1927
|
+
const exe = require('./bin/browser/visual-verify-executor');
|
|
1928
|
+
const fs = require('fs');
|
|
1929
|
+
const content = fs.readFileSync('${PLAN_FILE}', 'utf8');
|
|
1930
|
+
exe.executeBlock(${PHASE_NUM}, '${PLAN_ID}', content).then(result => {
|
|
1931
|
+
const reportPath = exe.writeReport(${PHASE_NUM}, '${PLAN_ID}', result);
|
|
1932
|
+
console.log(result.passed ? ' ✅ Visual verify passed' : ' ❌ Visual verify FAILED');
|
|
1933
|
+
console.log(' Report:', reportPath);
|
|
1934
|
+
process.exit(result.passed ? 0 : 1);
|
|
1935
|
+
}).catch(err => {
|
|
1936
|
+
console.error(' ❌ Visual verify error:', err.message);
|
|
1937
|
+
process.exit(1);
|
|
1938
|
+
});
|
|
1939
|
+
"
|
|
1940
|
+
|
|
1941
|
+
VISUAL_EXIT=$?
|
|
1942
|
+
if [ "${VISUAL_EXIT}" -ne 0 ]; then
|
|
1943
|
+
echo " Visual verification failed — see VISUAL-VERIFY-${PHASE_NUM}-${PLAN_ID}.md"
|
|
1944
|
+
exit 1 # Triggers node repair in auto mode
|
|
1945
|
+
fi
|
|
1946
|
+
fi
|
|
1947
|
+
```
|
|
1948
|
+
```
|
|
1949
|
+
|
|
1950
|
+
### Update MINDFORGE.md and MINDFORGE-V2-SCHEMA.json
|
|
1951
|
+
|
|
1952
|
+
```markdown
|
|
1953
|
+
## Browser runtime configuration (v2.0.0)
|
|
1954
|
+
|
|
1955
|
+
BROWSER_PORT=7338
|
|
1956
|
+
BROWSER_HEADLESS=true
|
|
1957
|
+
BROWSER_IDLE_TIMEOUT_MINUTES=30
|
|
1958
|
+
BROWSER_VIEWPORT_WIDTH=1280
|
|
1959
|
+
BROWSER_VIEWPORT_HEIGHT=720
|
|
1960
|
+
DEV_SERVER_URL=http://localhost:3000
|
|
1961
|
+
QA_DEFAULT_SESSIONS=default
|
|
1962
|
+
AUTO_RUN_QA_AFTER_UI_WAVES=false
|
|
1963
|
+
QA_AUTO_GENERATE_REGRESSION_TESTS=true
|
|
1964
|
+
BROWSER_MAX_SCREENSHOTS_PER_PHASE=50
|
|
1965
|
+
```
|
|
1966
|
+
|
|
1967
|
+
Schema additions to `.mindforge/MINDFORGE-V2-SCHEMA.json`:
|
|
1968
|
+
```json
|
|
1969
|
+
{
|
|
1970
|
+
"BROWSER_PORT": { "type": "number", "minimum": 1024, "maximum": 65535 },
|
|
1971
|
+
"BROWSER_HEADLESS": { "type": "boolean" },
|
|
1972
|
+
"BROWSER_IDLE_TIMEOUT_MINUTES": { "type": "number", "minimum": 5, "maximum": 480 },
|
|
1973
|
+
"BROWSER_VIEWPORT_WIDTH": { "type": "number", "minimum": 320, "maximum": 3840 },
|
|
1974
|
+
"BROWSER_VIEWPORT_HEIGHT": { "type": "number", "minimum": 240, "maximum": 2160 },
|
|
1975
|
+
"DEV_SERVER_URL": { "type": "string" },
|
|
1976
|
+
"AUTO_RUN_QA_AFTER_UI_WAVES": { "type": "boolean" },
|
|
1977
|
+
"QA_AUTO_GENERATE_REGRESSION_TESTS": { "type": "boolean" },
|
|
1978
|
+
"BROWSER_MAX_SCREENSHOTS_PER_PHASE": { "type": "number", "minimum": 10, "maximum": 500 }
|
|
1979
|
+
}
|
|
1980
|
+
```
|
|
1981
|
+
|
|
1982
|
+
**Commit:**
|
|
1983
|
+
```bash
|
|
1984
|
+
git add .claude/CLAUDE.md .agent/CLAUDE.md \
|
|
1985
|
+
.claude/commands/mindforge/execute-phase.md \
|
|
1986
|
+
.agent/mindforge/execute-phase.md \
|
|
1987
|
+
MINDFORGE.md .mindforge/MINDFORGE-V2-SCHEMA.json
|
|
1988
|
+
git commit -m "feat(v2-browser): update CLAUDE.md, execute-phase, and MINDFORGE.md for browser runtime"
|
|
1989
|
+
```
|
|
1990
|
+
|
|
1991
|
+
---
|
|
1992
|
+
|
|
1993
|
+
## TASK 10 — Write the browser test suite
|
|
1994
|
+
|
|
1995
|
+
### `tests/browser.test.js`
|
|
1996
|
+
|
|
1997
|
+
```javascript
|
|
1998
|
+
/**
|
|
1999
|
+
* MindForge v2 — Browser Runtime Test Suite
|
|
2000
|
+
* Tests daemon manager, visual verify parser/executor, QA engine,
|
|
2001
|
+
* session manager, screenshot store, and regression writer.
|
|
2002
|
+
*
|
|
2003
|
+
* No actual Chromium process is started — daemon calls are mocked
|
|
2004
|
+
* where necessary, and unit-testable logic is tested directly.
|
|
2005
|
+
*
|
|
2006
|
+
* Run: node tests/browser.test.js
|
|
2007
|
+
*/
|
|
2008
|
+
'use strict';
|
|
2009
|
+
|
|
2010
|
+
const fs = require('fs');
|
|
2011
|
+
const path = require('path');
|
|
2012
|
+
const os = require('os');
|
|
2013
|
+
const assert = require('assert');
|
|
2014
|
+
|
|
2015
|
+
let passed = 0, failed = 0;
|
|
2016
|
+
function test(name, fn) {
|
|
2017
|
+
try { fn(); console.log(` ✅ ${name}`); passed++; }
|
|
2018
|
+
catch(e) { console.error(` ❌ ${name}\n ${e.message}`); failed++; }
|
|
2019
|
+
}
|
|
2020
|
+
|
|
2021
|
+
// ── Module imports ────────────────────────────────────────────────────────────
|
|
2022
|
+
const VisualVerify = require('../bin/browser/visual-verify-executor');
|
|
2023
|
+
const ScreenStore = require('../bin/browser/screenshot-store');
|
|
2024
|
+
const QAEngine = require('../bin/browser/qa-engine');
|
|
2025
|
+
const QAReportWriter = require('../bin/browser/qa-report-writer');
|
|
2026
|
+
const RegressionWriter = require('../bin/browser/regression-writer');
|
|
2027
|
+
|
|
2028
|
+
// ── Temp project factory ──────────────────────────────────────────────────────
|
|
2029
|
+
function mkProject(withScreenshots = false) {
|
|
2030
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'mf-browser-'));
|
|
2031
|
+
const write = (rel, c) => { const f = path.join(dir, rel); fs.mkdirSync(path.dirname(f), { recursive: true }); fs.writeFileSync(f, c); return f; };
|
|
2032
|
+
const read = rel => { const f = path.join(dir, rel); return fs.existsSync(f) ? fs.readFileSync(f, 'utf8') : null; };
|
|
2033
|
+
const exists = rel => fs.existsSync(path.join(dir, rel));
|
|
2034
|
+
const cleanup = () => { try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} };
|
|
2035
|
+
if (withScreenshots) fs.mkdirSync(path.join(dir, '.planning', 'screenshots'), { recursive: true });
|
|
2036
|
+
return { dir, write, read, exists, cleanup };
|
|
2037
|
+
}
|
|
2038
|
+
|
|
2039
|
+
// ── PLAN fixtures ─────────────────────────────────────────────────────────────
|
|
2040
|
+
const PLAN_WITH_VISUAL_USER = `<task type="auto">
|
|
2041
|
+
<n>Build dashboard</n><persona>developer</persona>
|
|
2042
|
+
<phase>3</phase><plan>04</plan><files>src/pages/dashboard.tsx</files>
|
|
2043
|
+
<action>Create dashboard</action><verify>npm test</verify>
|
|
2044
|
+
<verify-visual session="user">
|
|
2045
|
+
navigate: /dashboard
|
|
2046
|
+
wait: networkidle
|
|
2047
|
+
assert-visible: h1 "My Projects"
|
|
2048
|
+
assert-visible: .project-list
|
|
2049
|
+
assert-no-errors: true
|
|
2050
|
+
screenshot: dashboard-initial.png
|
|
2051
|
+
click: "#create-project-btn"
|
|
2052
|
+
assert-visible: .modal "Create Project"
|
|
2053
|
+
screenshot: dashboard-modal.png
|
|
2054
|
+
</verify-visual>
|
|
2055
|
+
<done>Dashboard works</done>
|
|
2056
|
+
</task>`;
|
|
2057
|
+
|
|
2058
|
+
const PLAN_DEFAULT_SESSION = `<task type="auto">
|
|
2059
|
+
<n>Login form</n><persona>developer</persona>
|
|
2060
|
+
<phase>3</phase><plan>02</plan><files>src/pages/login.tsx</files>
|
|
2061
|
+
<action>Add validation</action><verify>npm test</verify>
|
|
2062
|
+
<verify-visual>
|
|
2063
|
+
navigate: /login
|
|
2064
|
+
type: input[name="email"] ""
|
|
2065
|
+
click: button[type="submit"]
|
|
2066
|
+
assert-visible: .error-message "Email is required"
|
|
2067
|
+
</verify-visual>
|
|
2068
|
+
<done>Validation works</done>
|
|
2069
|
+
</task>`;
|
|
2070
|
+
|
|
2071
|
+
const PLAN_NO_VISUAL = `<task type="auto">
|
|
2072
|
+
<n>Add utility</n><persona>developer</persona>
|
|
2073
|
+
<phase>3</phase><plan>01</plan><files>src/utils/format.ts</files>
|
|
2074
|
+
<action>Add formatter</action><verify>npm test</verify>
|
|
2075
|
+
<done>Tests pass</done>
|
|
2076
|
+
</task>`;
|
|
2077
|
+
|
|
2078
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
2079
|
+
console.log('\nMindForge v2 — Browser Runtime Tests\n');
|
|
2080
|
+
|
|
2081
|
+
// ── File existence ────────────────────────────────────────────────────────────
|
|
2082
|
+
console.log('Required files:');
|
|
2083
|
+
[
|
|
2084
|
+
'bin/browser/browser-daemon.js',
|
|
2085
|
+
'bin/browser/daemon-manager.js',
|
|
2086
|
+
'bin/browser/session-manager.js',
|
|
2087
|
+
'bin/browser/visual-verify-executor.js',
|
|
2088
|
+
'bin/browser/screenshot-store.js',
|
|
2089
|
+
'bin/browser/qa-engine.js',
|
|
2090
|
+
'bin/browser/qa-report-writer.js',
|
|
2091
|
+
'bin/browser/regression-writer.js',
|
|
2092
|
+
'.mindforge/browser/daemon-protocol.md',
|
|
2093
|
+
'.mindforge/browser/visual-verify-spec.md',
|
|
2094
|
+
'.mindforge/browser/qa-engine.md',
|
|
2095
|
+
'.mindforge/browser/session-manager.md',
|
|
2096
|
+
'.claude/commands/mindforge/browse.md',
|
|
2097
|
+
'.claude/commands/mindforge/qa.md',
|
|
2098
|
+
'.agent/mindforge/browse.md',
|
|
2099
|
+
'.agent/mindforge/qa.md',
|
|
2100
|
+
].forEach(f => test(`${f} exists`, () => assert.ok(fs.existsSync(f), `Missing: ${f}`)));
|
|
2101
|
+
|
|
2102
|
+
// ── Visual verify: extractBlock ───────────────────────────────────────────────
|
|
2103
|
+
console.log('\nVisual verify — block extraction:');
|
|
2104
|
+
|
|
2105
|
+
test('extractBlock returns null for plan without <verify-visual>', () => {
|
|
2106
|
+
assert.strictEqual(VisualVerify.extractBlock(PLAN_NO_VISUAL), null);
|
|
2107
|
+
});
|
|
2108
|
+
|
|
2109
|
+
test('extractBlock extracts content and session="user"', () => {
|
|
2110
|
+
const b = VisualVerify.extractBlock(PLAN_WITH_VISUAL_USER);
|
|
2111
|
+
assert.ok(b, 'Should return block');
|
|
2112
|
+
assert.strictEqual(b.session, 'user');
|
|
2113
|
+
assert.ok(b.content.includes('navigate: /dashboard'), 'Content should include navigate directive');
|
|
2114
|
+
});
|
|
2115
|
+
|
|
2116
|
+
test('extractBlock defaults session to "default" when omitted', () => {
|
|
2117
|
+
const b = VisualVerify.extractBlock(PLAN_DEFAULT_SESSION);
|
|
2118
|
+
assert.ok(b, 'Should return block');
|
|
2119
|
+
assert.strictEqual(b.session, 'default');
|
|
2120
|
+
});
|
|
2121
|
+
|
|
2122
|
+
// ── Visual verify: parseDirectives ───────────────────────────────────────────
|
|
2123
|
+
console.log('\nVisual verify — directive parser:');
|
|
2124
|
+
|
|
2125
|
+
test('parseDirectives skips blank lines and # comments', () => {
|
|
2126
|
+
const content = `
|
|
2127
|
+
navigate: /dashboard
|
|
2128
|
+
# this is a comment
|
|
2129
|
+
|
|
2130
|
+
assert-visible: h1
|
|
2131
|
+
`;
|
|
2132
|
+
const d = VisualVerify.parseDirectives(content);
|
|
2133
|
+
assert.strictEqual(d.length, 2, `Expected 2, got ${d.length}`);
|
|
2134
|
+
});
|
|
2135
|
+
|
|
2136
|
+
test('parseDirectives extracts all 8 directives from PLAN_WITH_VISUAL_USER', () => {
|
|
2137
|
+
const b = VisualVerify.extractBlock(PLAN_WITH_VISUAL_USER);
|
|
2138
|
+
const d = VisualVerify.parseDirectives(b.content);
|
|
2139
|
+
assert.strictEqual(d.length, 8, `Expected 8 directives, got ${d.length}`);
|
|
2140
|
+
});
|
|
2141
|
+
|
|
2142
|
+
test('parseDirectives: assert-visible "quoted text" is second arg', () => {
|
|
2143
|
+
const d = VisualVerify.parseDirectives('assert-visible: h1 "My Projects"');
|
|
2144
|
+
assert.strictEqual(d.length, 1);
|
|
2145
|
+
assert.strictEqual(d[0].directive, 'assert-visible');
|
|
2146
|
+
assert.strictEqual(d[0].args[0], 'h1');
|
|
2147
|
+
assert.strictEqual(d[0].args[1], 'My Projects', 'Quoted string should be unquoted arg');
|
|
2148
|
+
});
|
|
2149
|
+
|
|
2150
|
+
test('parseDirectives: navigate sets URL as first arg', () => {
|
|
2151
|
+
const d = VisualVerify.parseDirectives('navigate: /dashboard');
|
|
2152
|
+
assert.strictEqual(d[0].directive, 'navigate');
|
|
2153
|
+
assert.strictEqual(d[0].args[0], '/dashboard');
|
|
2154
|
+
});
|
|
2155
|
+
|
|
2156
|
+
test('parseDirectives: type captures selector and text', () => {
|
|
2157
|
+
const d = VisualVerify.parseDirectives('type: input[name="email"] "test@test.com"');
|
|
2158
|
+
assert.strictEqual(d[0].directive, 'type');
|
|
2159
|
+
assert.ok(d[0].args[0].includes('email'), 'First arg is selector');
|
|
2160
|
+
assert.strictEqual(d[0].args[1], 'test@test.com', 'Second arg is text value');
|
|
2161
|
+
});
|
|
2162
|
+
|
|
2163
|
+
test('parseDirectives: screenshot captures filename', () => {
|
|
2164
|
+
const d = VisualVerify.parseDirectives('screenshot: dashboard.png');
|
|
2165
|
+
assert.strictEqual(d[0].directive, 'screenshot');
|
|
2166
|
+
assert.strictEqual(d[0].args[0], 'dashboard.png');
|
|
2167
|
+
});
|
|
2168
|
+
|
|
2169
|
+
// ── Visual verify: writeReport ────────────────────────────────────────────────
|
|
2170
|
+
console.log('\nVisual verify — report writer:');
|
|
2171
|
+
|
|
2172
|
+
test('writeReport: creates VISUAL-VERIFY file with ✅ PASS', () => {
|
|
2173
|
+
const p = mkProject();
|
|
2174
|
+
const orig = process.cwd();
|
|
2175
|
+
process.chdir(p.dir);
|
|
2176
|
+
try {
|
|
2177
|
+
const file = VisualVerify.writeReport(3, '04', {
|
|
2178
|
+
passed: true, session: 'user', directives_count: 3,
|
|
2179
|
+
steps: [{ directive: 'navigate: /dashboard', status: 'pass', detail: '200 OK' }],
|
|
2180
|
+
screenshots: [],
|
|
2181
|
+
});
|
|
2182
|
+
assert.ok(fs.existsSync(file), 'Report file should exist');
|
|
2183
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
2184
|
+
assert.ok(content.includes('✅ PASS'), 'Should show PASS');
|
|
2185
|
+
assert.ok(content.includes('Phase 3'), 'Should mention phase');
|
|
2186
|
+
assert.ok(content.includes('Plan 04'), 'Should mention plan');
|
|
2187
|
+
assert.ok(content.includes('session: user'), 'Should include session');
|
|
2188
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2189
|
+
});
|
|
2190
|
+
|
|
2191
|
+
test('writeReport: creates file with ❌ FAIL when not passed', () => {
|
|
2192
|
+
const p = mkProject();
|
|
2193
|
+
const orig = process.cwd();
|
|
2194
|
+
process.chdir(p.dir);
|
|
2195
|
+
try {
|
|
2196
|
+
const file = VisualVerify.writeReport(3, '05', {
|
|
2197
|
+
passed: false, session: 'default', directives_count: 2,
|
|
2198
|
+
steps: [{ directive: 'assert-visible: .modal', status: 'fail', detail: 'Element not found after 5s' }],
|
|
2199
|
+
screenshots: [],
|
|
2200
|
+
});
|
|
2201
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
2202
|
+
assert.ok(content.includes('❌ FAIL'), 'Should show FAIL');
|
|
2203
|
+
assert.ok(content.includes('Element not found'), 'Should include failure detail');
|
|
2204
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2205
|
+
});
|
|
2206
|
+
|
|
2207
|
+
test('writeReport: SKIPPED status when result.skipped is true', () => {
|
|
2208
|
+
const p = mkProject();
|
|
2209
|
+
const orig = process.cwd();
|
|
2210
|
+
process.chdir(p.dir);
|
|
2211
|
+
try {
|
|
2212
|
+
const file = VisualVerify.writeReport(3, '01', { skipped: true, passed: true, steps: [], screenshots: [], session: 'default', directives_count: 0 });
|
|
2213
|
+
const content = fs.readFileSync(file, 'utf8');
|
|
2214
|
+
assert.ok(content.includes('SKIPPED'), 'Should show SKIPPED status');
|
|
2215
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2216
|
+
});
|
|
2217
|
+
|
|
2218
|
+
// ── Screenshot store ──────────────────────────────────────────────────────────
|
|
2219
|
+
console.log('\nScreenshot store:');
|
|
2220
|
+
|
|
2221
|
+
const TINY_PNG_B64 = 'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVR42mNk+M9QDwADhgGAWjR9awAAAABJRU5ErkJggg==';
|
|
2222
|
+
|
|
2223
|
+
test('save: creates PNG file in phase namespace', () => {
|
|
2224
|
+
const p = mkProject(true);
|
|
2225
|
+
const orig = process.cwd();
|
|
2226
|
+
process.chdir(p.dir);
|
|
2227
|
+
try {
|
|
2228
|
+
const saved = ScreenStore.save(TINY_PNG_B64, 3, '04', 'test.png');
|
|
2229
|
+
assert.ok(fs.existsSync(saved), 'Screenshot should be saved');
|
|
2230
|
+
assert.ok(saved.includes('phase-3'), 'Should be in phase-3 namespace');
|
|
2231
|
+
assert.ok(saved.endsWith('.png'), 'Should have .png extension');
|
|
2232
|
+
assert.ok(fs.statSync(saved).size > 0, 'File should not be empty');
|
|
2233
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2234
|
+
});
|
|
2235
|
+
|
|
2236
|
+
test('save: sanitizes dangerous filenames', () => {
|
|
2237
|
+
const p = mkProject(true);
|
|
2238
|
+
const orig = process.cwd();
|
|
2239
|
+
process.chdir(p.dir);
|
|
2240
|
+
try {
|
|
2241
|
+
const saved = ScreenStore.save(TINY_PNG_B64, 3, '04', '../../evil/../path.png');
|
|
2242
|
+
assert.ok(!saved.includes('..'), 'Should not contain path traversal');
|
|
2243
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2244
|
+
});
|
|
2245
|
+
|
|
2246
|
+
test('list: returns saved screenshots', () => {
|
|
2247
|
+
const p = mkProject(true);
|
|
2248
|
+
const orig = process.cwd();
|
|
2249
|
+
process.chdir(p.dir);
|
|
2250
|
+
try {
|
|
2251
|
+
ScreenStore.save(TINY_PNG_B64, 3, '04', 'a.png');
|
|
2252
|
+
ScreenStore.save(TINY_PNG_B64, 3, '04', 'b.png');
|
|
2253
|
+
const files = ScreenStore.list(3, '04');
|
|
2254
|
+
assert.ok(files.length >= 2, `Expected >=2 screenshots, got ${files.length}`);
|
|
2255
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2256
|
+
});
|
|
2257
|
+
|
|
2258
|
+
test('cleanup: deletes phase screenshots', () => {
|
|
2259
|
+
const p = mkProject(true);
|
|
2260
|
+
const orig = process.cwd();
|
|
2261
|
+
process.chdir(p.dir);
|
|
2262
|
+
try {
|
|
2263
|
+
ScreenStore.save(TINY_PNG_B64, 3, '04', 'temp.png');
|
|
2264
|
+
const before = ScreenStore.list(3, '04').length;
|
|
2265
|
+
assert.ok(before >= 1, 'Should have screenshots before cleanup');
|
|
2266
|
+
ScreenStore.cleanup(3);
|
|
2267
|
+
const after = ScreenStore.list(3, '04').length;
|
|
2268
|
+
assert.strictEqual(after, 0, 'Should have no screenshots after cleanup');
|
|
2269
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2270
|
+
});
|
|
2271
|
+
|
|
2272
|
+
test('diskUsage: returns a number', () => {
|
|
2273
|
+
const p = mkProject(true);
|
|
2274
|
+
const orig = process.cwd();
|
|
2275
|
+
process.chdir(p.dir);
|
|
2276
|
+
try {
|
|
2277
|
+
ScreenStore.save(TINY_PNG_B64, 3, '04', 'usage.png');
|
|
2278
|
+
const usage = ScreenStore.diskUsage();
|
|
2279
|
+
assert.ok(typeof usage === 'number' && usage >= 0, `Expected non-negative number, got ${usage}`);
|
|
2280
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2281
|
+
});
|
|
2282
|
+
|
|
2283
|
+
// ── QA engine — surface extraction ───────────────────────────────────────────
|
|
2284
|
+
console.log('\nQA engine — surface classification:');
|
|
2285
|
+
|
|
2286
|
+
test('classifyFile: Next.js App Router page', () => {
|
|
2287
|
+
const s = QAEngine.classifyFile('src/app/dashboard/page.tsx');
|
|
2288
|
+
assert.ok(s, 'Should classify');
|
|
2289
|
+
assert.strictEqual(s.type, 'page');
|
|
2290
|
+
assert.ok(s.route.includes('dashboard'), `Expected /dashboard, got ${s.route}`);
|
|
2291
|
+
});
|
|
2292
|
+
|
|
2293
|
+
test('classifyFile: Next.js Pages Router page', () => {
|
|
2294
|
+
const s = QAEngine.classifyFile('src/pages/login.tsx');
|
|
2295
|
+
assert.ok(s, 'Should classify');
|
|
2296
|
+
assert.strictEqual(s.type, 'page');
|
|
2297
|
+
assert.ok(s.route.includes('login'), `Expected /login, got ${s.route}`);
|
|
2298
|
+
});
|
|
2299
|
+
|
|
2300
|
+
test('classifyFile: API route', () => {
|
|
2301
|
+
const s = QAEngine.classifyFile('src/app/api/users/route.ts');
|
|
2302
|
+
assert.ok(s, 'Should classify');
|
|
2303
|
+
assert.strictEqual(s.type, 'api');
|
|
2304
|
+
});
|
|
2305
|
+
|
|
2306
|
+
test('classifyFile: React component', () => {
|
|
2307
|
+
const s = QAEngine.classifyFile('src/components/ProjectCard.tsx');
|
|
2308
|
+
assert.ok(s, 'Should classify');
|
|
2309
|
+
assert.strictEqual(s.type, 'component');
|
|
2310
|
+
});
|
|
2311
|
+
|
|
2312
|
+
test('classifyFile: non-UI file returns null', () => {
|
|
2313
|
+
assert.strictEqual(QAEngine.classifyFile('src/utils/format.ts'), null);
|
|
2314
|
+
assert.strictEqual(QAEngine.classifyFile('tests/format.test.ts'), null);
|
|
2315
|
+
assert.strictEqual(QAEngine.classifyFile('package.json'), null);
|
|
2316
|
+
});
|
|
2317
|
+
|
|
2318
|
+
// ── QA report writer ──────────────────────────────────────────────────────────
|
|
2319
|
+
console.log('\nQA report writer:');
|
|
2320
|
+
|
|
2321
|
+
test('write: creates QA-REPORT-[N].md', () => {
|
|
2322
|
+
const p = mkProject();
|
|
2323
|
+
const orig = process.cwd();
|
|
2324
|
+
process.chdir(p.dir);
|
|
2325
|
+
try {
|
|
2326
|
+
const file = QAReportWriter.write(3, {
|
|
2327
|
+
surfaces: 2, passed: 1, failed: 1,
|
|
2328
|
+
results: [
|
|
2329
|
+
{ surface: '/dashboard', session: 'default', passed: true },
|
|
2330
|
+
{ surface: '/login', session: 'default', passed: false },
|
|
2331
|
+
],
|
|
2332
|
+
bugs: [{
|
|
2333
|
+
surface: '/login', session: 'default', source_file: 'src/pages/login.tsx',
|
|
2334
|
+
failed_step_directive: 'assert-visible',
|
|
2335
|
+
error: 'Error message not found',
|
|
2336
|
+
screenshot_path: null,
|
|
2337
|
+
reproduction: ['1. Navigate to /login', '2. Click submit', 'Expected: error', 'Actual: none'],
|
|
2338
|
+
}],
|
|
2339
|
+
});
|
|
2340
|
+
assert.ok(fs.existsSync(file), 'QA report file should exist');
|
|
2341
|
+
const c = fs.readFileSync(file, 'utf8');
|
|
2342
|
+
assert.ok(c.includes('Phase 3'), 'Should mention phase');
|
|
2343
|
+
assert.ok(c.includes('/dashboard'), 'Should list surfaces');
|
|
2344
|
+
assert.ok(c.includes('/login'), 'Should list login');
|
|
2345
|
+
assert.ok(c.includes('Bug 1'), 'Should document bug');
|
|
2346
|
+
assert.ok(c.includes('Error message not found'), 'Should include error details');
|
|
2347
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2348
|
+
});
|
|
2349
|
+
|
|
2350
|
+
test('write: no bugs section when all passed', () => {
|
|
2351
|
+
const p = mkProject();
|
|
2352
|
+
const orig = process.cwd();
|
|
2353
|
+
process.chdir(p.dir);
|
|
2354
|
+
try {
|
|
2355
|
+
const file = QAReportWriter.write(4, { surfaces: 1, passed: 1, failed: 0, results: [{ surface: '/dashboard', session: 'default', passed: true }], bugs: [] });
|
|
2356
|
+
const c = fs.readFileSync(file, 'utf8');
|
|
2357
|
+
assert.ok(c.includes('No bugs found'), 'Should say no bugs when all passed');
|
|
2358
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2359
|
+
});
|
|
2360
|
+
|
|
2361
|
+
// ── Regression test writer ────────────────────────────────────────────────────
|
|
2362
|
+
console.log('\nRegression test writer:');
|
|
2363
|
+
|
|
2364
|
+
test('write: creates .test.ts file with correct content', () => {
|
|
2365
|
+
const p = mkProject();
|
|
2366
|
+
const orig = process.cwd();
|
|
2367
|
+
process.chdir(p.dir);
|
|
2368
|
+
try {
|
|
2369
|
+
const bug = {
|
|
2370
|
+
surface: '/login', session: 'default',
|
|
2371
|
+
error: 'Empty email allows submit',
|
|
2372
|
+
source_file: 'src/pages/login.tsx',
|
|
2373
|
+
reproduction: ['1. Navigate to /login', '2. Click submit with empty email'],
|
|
2374
|
+
};
|
|
2375
|
+
const file = RegressionWriter.write(bug, 3);
|
|
2376
|
+
assert.ok(fs.existsSync(file), 'Regression test file should exist');
|
|
2377
|
+
const c = fs.readFileSync(file, 'utf8');
|
|
2378
|
+
assert.ok(c.includes("from '@playwright/test'"), 'Should import Playwright');
|
|
2379
|
+
assert.ok(c.includes("test('"), 'Should have a test');
|
|
2380
|
+
assert.ok(c.includes('/login'), 'Should reference the route');
|
|
2381
|
+
assert.ok(c.includes('DO NOT DELETE'), 'Should have deletion warning');
|
|
2382
|
+
assert.ok(file.endsWith('.test.ts'), 'Should be a .test.ts file');
|
|
2383
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2384
|
+
});
|
|
2385
|
+
|
|
2386
|
+
// ── Command file content validation ──────────────────────────────────────────
|
|
2387
|
+
console.log('\nCommand content validation:');
|
|
2388
|
+
|
|
2389
|
+
test('/mindforge:browse documents security rules', () => {
|
|
2390
|
+
const c = fs.readFileSync('.claude/commands/mindforge/browse.md', 'utf8');
|
|
2391
|
+
assert.ok(c.includes('NEVER'), 'Should have security NEVER rules');
|
|
2392
|
+
assert.ok(c.includes('localhost'), 'Should mention localhost-only');
|
|
2393
|
+
assert.ok(c.includes('gitignored'), 'Should mention gitignored sessions');
|
|
2394
|
+
});
|
|
2395
|
+
|
|
2396
|
+
test('/mindforge:qa documents integration with auto mode', () => {
|
|
2397
|
+
const c = fs.readFileSync('.claude/commands/mindforge/qa.md', 'utf8');
|
|
2398
|
+
assert.ok(c.includes('AUTO_RUN_QA_AFTER_UI_WAVES'), 'Should mention auto mode setting');
|
|
2399
|
+
assert.ok(c.includes('DECOMPOSE'), 'Should mention DECOMPOSE repair');
|
|
2400
|
+
});
|
|
2401
|
+
|
|
2402
|
+
// ── Security validations ──────────────────────────────────────────────────────
|
|
2403
|
+
console.log('\nSecurity properties:');
|
|
2404
|
+
|
|
2405
|
+
test('browser-daemon.js binds to 127.0.0.1 not 0.0.0.0', () => {
|
|
2406
|
+
const c = fs.readFileSync('bin/browser/browser-daemon.js', 'utf8');
|
|
2407
|
+
assert.ok(c.includes("'127.0.0.1'") || c.includes('"127.0.0.1"'), 'Should bind to 127.0.0.1');
|
|
2408
|
+
assert.ok(!c.includes("'0.0.0.0'"), 'Must NOT bind to 0.0.0.0');
|
|
2409
|
+
});
|
|
2410
|
+
|
|
2411
|
+
test('browser-daemon.js rejects non-localhost connections with 403', () => {
|
|
2412
|
+
const c = fs.readFileSync('bin/browser/browser-daemon.js', 'utf8');
|
|
2413
|
+
assert.ok(c.includes('127.0.0.1') && c.includes('403'), 'Should return 403 for non-localhost');
|
|
2414
|
+
});
|
|
2415
|
+
|
|
2416
|
+
test('.gitignore includes session directory', () => {
|
|
2417
|
+
const gi = fs.existsSync('.gitignore') ? fs.readFileSync('.gitignore', 'utf8') : '';
|
|
2418
|
+
assert.ok(gi.includes('sessions'), 'Session directory should be gitignored');
|
|
2419
|
+
});
|
|
2420
|
+
|
|
2421
|
+
test('.gitignore includes screenshots directory', () => {
|
|
2422
|
+
const gi = fs.existsSync('.gitignore') ? fs.readFileSync('.gitignore', 'utf8') : '';
|
|
2423
|
+
assert.ok(gi.includes('screenshots'), 'Screenshots directory should be gitignored');
|
|
2424
|
+
});
|
|
2425
|
+
|
|
2426
|
+
test('session-manager warns about auth tokens in saved files', () => {
|
|
2427
|
+
const c = fs.readFileSync('bin/browser/session-manager.js', 'utf8');
|
|
2428
|
+
assert.ok(c.includes('NEVER commit') || c.includes('auth token') || c.includes('_warning'), 'Session manager should warn about auth tokens');
|
|
2429
|
+
});
|
|
2430
|
+
|
|
2431
|
+
// ── MINDFORGE.md schema ───────────────────────────────────────────────────────
|
|
2432
|
+
console.log('\nMINDFORGE.md v2 browser schema:');
|
|
2433
|
+
|
|
2434
|
+
test('MINDFORGE-V2-SCHEMA.json includes browser settings', () => {
|
|
2435
|
+
const schema = JSON.parse(fs.readFileSync('.mindforge/MINDFORGE-V2-SCHEMA.json', 'utf8'));
|
|
2436
|
+
['BROWSER_PORT', 'BROWSER_HEADLESS', 'BROWSER_IDLE_TIMEOUT_MINUTES',
|
|
2437
|
+
'AUTO_RUN_QA_AFTER_UI_WAVES', 'DEV_SERVER_URL'].forEach(key => {
|
|
2438
|
+
assert.ok(schema.properties?.[key], `Schema should define ${key}`);
|
|
2439
|
+
});
|
|
2440
|
+
});
|
|
2441
|
+
|
|
2442
|
+
test('BROWSER_PORT schema has valid port range', () => {
|
|
2443
|
+
const schema = JSON.parse(fs.readFileSync('.mindforge/MINDFORGE-V2-SCHEMA.json', 'utf8'));
|
|
2444
|
+
const port = schema.properties?.BROWSER_PORT;
|
|
2445
|
+
assert.strictEqual(port?.minimum, 1024, 'Min port should be 1024');
|
|
2446
|
+
assert.strictEqual(port?.maximum, 65535, 'Max port should be 65535');
|
|
2447
|
+
});
|
|
2448
|
+
|
|
2449
|
+
// ── All 40 commands present ───────────────────────────────────────────────────
|
|
2450
|
+
console.log('\nAll 40 commands (38 v1+Day8 + 2 Day9):');
|
|
2451
|
+
|
|
2452
|
+
const ALL_COMMANDS = [
|
|
2453
|
+
'help','init-project','plan-phase','execute-phase','verify-phase','ship',
|
|
2454
|
+
'next','quick','status','debug',
|
|
2455
|
+
'skills','review','security-scan','map-codebase','discuss-phase',
|
|
2456
|
+
'audit','milestone','complete-milestone','approve','sync-jira','sync-confluence',
|
|
2457
|
+
'health','retrospective','profile-team','metrics',
|
|
2458
|
+
'init-org','install-skill','publish-skill','pr-review','workspace','benchmark',
|
|
2459
|
+
'update','migrate','plugins','tokens','release',
|
|
2460
|
+
'auto','steer', // Day 8
|
|
2461
|
+
'browse','qa', // Day 9
|
|
2462
|
+
];
|
|
2463
|
+
|
|
2464
|
+
assert.strictEqual(ALL_COMMANDS.length, 40);
|
|
2465
|
+
|
|
2466
|
+
test('all 40 commands in .claude/commands/mindforge/', () => {
|
|
2467
|
+
const missing = ALL_COMMANDS.filter(c => !fs.existsSync(`.claude/commands/mindforge/${c}.md`));
|
|
2468
|
+
assert.strictEqual(missing.length, 0, `Missing: ${missing.join(', ')}`);
|
|
2469
|
+
});
|
|
2470
|
+
|
|
2471
|
+
test('all 40 commands mirrored in .agent/mindforge/', () => {
|
|
2472
|
+
const missing = ALL_COMMANDS.filter(c => !fs.existsSync(`.agent/mindforge/${c}.md`));
|
|
2473
|
+
assert.strictEqual(missing.length, 0, `Missing in .agent: ${missing.join(', ')}`);
|
|
2474
|
+
});
|
|
2475
|
+
|
|
2476
|
+
// ── Version ───────────────────────────────────────────────────────────────────
|
|
2477
|
+
console.log('\nVersion:');
|
|
2478
|
+
|
|
2479
|
+
test('package.json is v2.0.0-alpha.2', () => {
|
|
2480
|
+
const v = JSON.parse(fs.readFileSync('package.json', 'utf8')).version;
|
|
2481
|
+
assert.ok(v === '2.0.0-alpha.2' || v.startsWith('2.'), `Expected v2.x, got ${v}`);
|
|
2482
|
+
});
|
|
2483
|
+
|
|
2484
|
+
// ── Results ───────────────────────────────────────────────────────────────────
|
|
2485
|
+
console.log(`\n${'─'.repeat(55)}`);
|
|
2486
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
2487
|
+
if (failed > 0) { console.error(`\n❌ ${failed} test(s) failed.\n`); process.exit(1); }
|
|
2488
|
+
else { console.log(`\n✅ All browser runtime tests passed.\n`); }
|
|
2489
|
+
```
|
|
2490
|
+
|
|
2491
|
+
**Commit:**
|
|
2492
|
+
```bash
|
|
2493
|
+
git add tests/browser.test.js
|
|
2494
|
+
git commit -m "test(v2-browser): add comprehensive browser runtime test suite (17th suite)"
|
|
2495
|
+
```
|
|
2496
|
+
|
|
2497
|
+
---
|
|
2498
|
+
|
|
2499
|
+
## TASK 11 — Bump version, update CHANGELOG, final commit
|
|
2500
|
+
|
|
2501
|
+
```bash
|
|
2502
|
+
# Bump to v2.0.0-alpha.2
|
|
2503
|
+
node -e "
|
|
2504
|
+
const fs = require('fs');
|
|
2505
|
+
const p = JSON.parse(fs.readFileSync('package.json','utf8'));
|
|
2506
|
+
p.version = '2.0.0-alpha.2';
|
|
2507
|
+
fs.writeFileSync('package.json', JSON.stringify(p, null, 2) + '\n');
|
|
2508
|
+
console.log('Bumped to v2.0.0-alpha.2');
|
|
2509
|
+
"
|
|
2510
|
+
```
|
|
2511
|
+
|
|
2512
|
+
Update `CHANGELOG.md`:
|
|
2513
|
+
|
|
2514
|
+
```markdown
|
|
2515
|
+
## [2.0.0-alpha.2] — Day 9: Persistent Browser Runtime + Visual QA
|
|
2516
|
+
|
|
2517
|
+
### Added
|
|
2518
|
+
|
|
2519
|
+
**Browser Daemon:**
|
|
2520
|
+
- bin/browser/browser-daemon.js — long-lived Chromium via Playwright, localhost:7338
|
|
2521
|
+
- bin/browser/daemon-manager.js — lifecycle manager (start/stop/restart/health)
|
|
2522
|
+
- Localhost-only binding (127.0.0.1) — consistent with ADR-017 policy
|
|
2523
|
+
- SIGTERM graceful shutdown with automatic session save
|
|
2524
|
+
- Idle auto-shutdown after BROWSER_IDLE_TIMEOUT_MINUTES (default: 30)
|
|
2525
|
+
- Full HTTP API: /navigate /click /type /screenshot /evaluate /assert /session/*
|
|
2526
|
+
|
|
2527
|
+
**Session Manager:**
|
|
2528
|
+
- bin/browser/session-manager.js — named session persistence
|
|
2529
|
+
- Cookie import from Chrome, Arc, Brave, Edge
|
|
2530
|
+
- Session files gitignored — auth tokens never committed
|
|
2531
|
+
|
|
2532
|
+
**Visual Verification:**
|
|
2533
|
+
- `<verify-visual>` block in PLAN files — structured browser verification
|
|
2534
|
+
- bin/browser/visual-verify-executor.js — full directive parser and executor
|
|
2535
|
+
- 13 directives: navigate, wait, assert-visible/url/title/no-errors, screenshot, click, type, evaluate, press, scroll
|
|
2536
|
+
- VISUAL-VERIFY-[N]-[plan].md result files
|
|
2537
|
+
- Integration with execute-phase — runs after <verify> passes
|
|
2538
|
+
|
|
2539
|
+
**QA Engine:**
|
|
2540
|
+
- bin/browser/qa-engine.js — git-diff-aware surface extraction and systematic testing
|
|
2541
|
+
- Supports: Next.js App Router, Pages Router, API routes, React components
|
|
2542
|
+
- bin/browser/qa-report-writer.js — QA-REPORT-[N].md with full bug documentation
|
|
2543
|
+
- bin/browser/regression-writer.js — auto-generates Playwright regression tests per bug
|
|
2544
|
+
|
|
2545
|
+
**Screenshot Store:**
|
|
2546
|
+
- bin/browser/screenshot-store.js — phase-namespaced save/list/cleanup
|
|
2547
|
+
- All screenshots gitignored — sensitive UI data never committed
|
|
2548
|
+
|
|
2549
|
+
**New Commands:**
|
|
2550
|
+
- /mindforge:browse — persistent browser control with session management
|
|
2551
|
+
- /mindforge:qa — systematic post-phase visual QA
|
|
2552
|
+
|
|
2553
|
+
**Configuration:**
|
|
2554
|
+
- MINDFORGE.md browser settings: BROWSER_PORT, BROWSER_HEADLESS, DEV_SERVER_URL, etc.
|
|
2555
|
+
- AUTO_RUN_QA_AFTER_UI_WAVES — enables automatic QA in auto mode
|
|
2556
|
+
|
|
2557
|
+
**Tests:**
|
|
2558
|
+
- tests/browser.test.js — 17th test suite (daemon protocol, visual verify parser,
|
|
2559
|
+
screenshot store, QA engine surface extraction, report writers, security properties)
|
|
2560
|
+
|
|
2561
|
+
### Architecture decisions
|
|
2562
|
+
- ADR-024: browser daemon localhost-only consistent with ADR-017 (SDK SSE policy)
|
|
2563
|
+
- ADR-025: <verify-visual> failure treated identically to <verify> failure
|
|
2564
|
+
- ADR-026: session files are gitignored — auth tokens must never reach git history
|
|
2565
|
+
```
|
|
2566
|
+
|
|
2567
|
+
```bash
|
|
2568
|
+
git add CHANGELOG.md package.json
|
|
2569
|
+
git commit -m "chore(v2-alpha2): Day 9 complete — browser runtime, v2.0.0-alpha.2"
|
|
2570
|
+
git push origin feat/mindforge-v2-browser-runtime
|
|
2571
|
+
```
|
|
2572
|
+
|
|
2573
|
+
---
|
|
2574
|
+
|
|
2575
|
+
## TASK 12 — Run full test battery
|
|
2576
|
+
|
|
2577
|
+
```bash
|
|
2578
|
+
#!/usr/bin/env bash
|
|
2579
|
+
echo "MindForge v2 Day 9 — Test Battery"
|
|
2580
|
+
|
|
2581
|
+
SUITES=(install wave-engine audit compaction skills-platform \
|
|
2582
|
+
integrations governance intelligence metrics \
|
|
2583
|
+
distribution ci-mode sdk production migration e2e \
|
|
2584
|
+
autonomous browser)
|
|
2585
|
+
|
|
2586
|
+
FAIL=0
|
|
2587
|
+
for suite in "${SUITES[@]}"; do
|
|
2588
|
+
printf " %-30s" "${suite}..."
|
|
2589
|
+
if node tests/${suite}.test.js 2>&1 | tail -1 | grep -q "passed"; then
|
|
2590
|
+
echo "✅"
|
|
2591
|
+
else
|
|
2592
|
+
echo "❌"
|
|
2593
|
+
((FAIL++))
|
|
2594
|
+
fi
|
|
2595
|
+
done
|
|
2596
|
+
|
|
2597
|
+
echo ""
|
|
2598
|
+
echo "Commands: $(ls .claude/commands/mindforge/ | wc -l | tr -d ' ') (expected: 40)"
|
|
2599
|
+
echo "ADRs: $(ls .planning/decisions/ADR-*.md 2>/dev/null | wc -l | tr -d ' ') (expected: ≥26)"
|
|
2600
|
+
echo ""
|
|
2601
|
+
[ "$FAIL" -eq 0 ] && echo "✅ ALL 17 SUITES PASSED" || { echo "❌ ${FAIL} FAILURE(S)"; exit 1; }
|
|
2602
|
+
```
|
|
2603
|
+
|
|
2604
|
+
---
|
|
2605
|
+
|
|
2606
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
2607
|
+
# PART 2 — REVIEW PROMPT
|
|
2608
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
2609
|
+
|
|
2610
|
+
---
|
|
2611
|
+
|
|
2612
|
+
## DAY 9 REVIEW
|
|
2613
|
+
|
|
2614
|
+
Activate **`architect.md` + `security-reviewer.md` + `qa-engineer.md`** simultaneously.
|
|
2615
|
+
|
|
2616
|
+
Day 9 risk profile:
|
|
2617
|
+
1. **Auth token leakage** — session files must never reach git, CI logs, or webhooks
|
|
2618
|
+
2. **Chromium sandbox bypass** — `--no-sandbox` flag is required for CI but dangerous in shared environments
|
|
2619
|
+
3. **Path traversal in screenshot filenames** — user-controlled filenames in save()
|
|
2620
|
+
4. **evaluate() injection** — arbitrary JS in the browser page context
|
|
2621
|
+
5. **False-positive stuck detection** — <verify-visual> failures must not thrash auto mode
|
|
2622
|
+
|
|
2623
|
+
---
|
|
2624
|
+
|
|
2625
|
+
## REVIEW PASS 1 — Browser Daemon: Security Hardening
|
|
2626
|
+
|
|
2627
|
+
Read `browser-daemon.js` completely.
|
|
2628
|
+
|
|
2629
|
+
- [ ] **`--no-sandbox` in all environments.** The daemon always passes `--no-sandbox` to Chromium. In a developer's local machine (macOS/Linux), this is unnecessary and reduces security — `--no-sandbox` disables the Chromium sandbox which is a meaningful security boundary. Fix: "Only add `--no-sandbox` and `--disable-setuid-sandbox` when `CI=true` or `BROWSER_HEADLESS=true` AND running as root (which is common in Docker CI). On macOS/Linux as a non-root user, the sandbox works fine and should be enabled."
|
|
2630
|
+
|
|
2631
|
+
- [ ] **evaluate() runs arbitrary JS.** The `/evaluate` endpoint runs `page.evaluate(script)` where `script` comes directly from the HTTP request body. Since the daemon is localhost-only, the threat is mainly accidental misuse (evaluating on an external page) and malformed scripts causing unhandled errors. Add: "Validate that the current page URL starts with DEV_SERVER_URL before executing eval scripts. If not a known dev URL: return `{ success: false, error: 'evaluate() is only permitted on DEV_SERVER_URL origins' }`."
|
|
2632
|
+
|
|
2633
|
+
- [ ] **Session save race condition.** `SIGTERM` handler calls `saveSession()` for all sessions, but `saveSession()` uses `await context.cookies()` — what if the context is already closing? Add try-catch per session: "If `saveSession()` throws for a specific session during SIGTERM: log the error and continue to the next session. Never let one failed session save block the others."
|
|
2634
|
+
|
|
2635
|
+
- [ ] **Idle shutdown timer fires even when a request is in progress.** The idle timer checks `Date.now() - lastActionAt > IDLE_MS` every 60 seconds. But `lastActionAt` is only updated at the START of request handling. A slow request (e.g., navigating a slow page) takes 25 seconds — `lastActionAt` is updated at the start, so the idle timer won't fire mid-request. But a crash during a long request could leave `lastActionAt` stale. This is acceptable — document it: "Idle timer fires at most 60 seconds after the last request completion. A crash during a long request may cause the timer to fire earlier than expected."
|
|
2636
|
+
|
|
2637
|
+
---
|
|
2638
|
+
|
|
2639
|
+
## REVIEW PASS 2 — Screenshot Store: Path Traversal
|
|
2640
|
+
|
|
2641
|
+
Read `screenshot-store.js` completely.
|
|
2642
|
+
|
|
2643
|
+
- [ ] **Path traversal in `save()`.** The current `save()` sanitizes the filename with `.replace(/[^a-zA-Z0-9._-]/g, '-')`. But this only sanitizes the filename, not the planId parameter. If `planId` contains `../../../etc`, the screenshot would be saved outside the screenshots directory. Fix: "Also sanitize `planId` and `phaseNum` parameters. `planId`: allow only `[a-zA-Z0-9_-]`. `phaseNum`: coerce to integer. Verify the final resolved path starts with `STORE` using `path.resolve()`."
|
|
2644
|
+
|
|
2645
|
+
- [ ] **No disk quota enforcement.** The store has `diskUsage()` and `BROWSER_MAX_SCREENSHOTS_PER_PHASE` is in MINDFORGE.md, but `save()` doesn't check or enforce the limit. In a long auto-mode run with many visual verifies, screenshots could consume gigabytes. Fix: "In `save()`: after writing, check if `list(phaseNum).length > BROWSER_MAX_SCREENSHOTS_PER_PHASE`. If exceeded: delete the oldest N screenshots from the phase. Log: 'Screenshot limit reached for phase N — deleted M oldest'."
|
|
2646
|
+
|
|
2647
|
+
---
|
|
2648
|
+
|
|
2649
|
+
## REVIEW PASS 3 — Visual Verify Executor: Edge Cases
|
|
2650
|
+
|
|
2651
|
+
Read `visual-verify-executor.js` completely.
|
|
2652
|
+
|
|
2653
|
+
- [ ] **`press` directive is unimplemented.** The handler has `step.status = 'warn'` but the comment says "requires direct Playwright access; use click instead." This means `press: Enter` in a PLAN file silently passes as a warning. If a developer adds `press: Enter` expecting form submission, the form won't submit and the next assertion will fail with a confusing error. Fix: "Implement press via: `POST /evaluate` with `page.keyboard.press(args[0])` — or add a `/press` endpoint to the daemon. The `press` directive is commonly needed for `Enter` (form submit) and `Tab` (focus change)."
|
|
2654
|
+
|
|
2655
|
+
- [ ] **DEV_SERVER_URL used for relative URL resolution but not validated.** The executor resolves `/dashboard` to `${DEV_SERVER}${path}`. If `DEV_SERVER_URL` is not set and the default `http://localhost:3000` is wrong for the project, visual verify will silently navigate to the wrong URL and tests will fail with confusing "element not found" errors. Add: "At executor startup: check if `DEV_SERVER_URL` is reachable (single HEAD request with 2s timeout). If not: warn: 'DEV_SERVER_URL (http://localhost:3000) appears unreachable. Is your dev server running? Set DEV_SERVER_URL in MINDFORGE.md if using a different port.'"
|
|
2656
|
+
|
|
2657
|
+
- [ ] **`click` directive: selector vs text heuristic is fragile.** Current code: `const sel = args[0]; const body = sel.startsWith('"') ? { text: ... } : { selector: sel }`. But `"#create-project-btn"` with quotes would be treated as a text click, not a selector click. The quotes are already stripped by `parseDirectives`. Fix the heuristic: "Text click when the selector arg contains no CSS characters (`#`, `.`, `[`, `:`). Otherwise it's a CSS selector."
|
|
2658
|
+
|
|
2659
|
+
---
|
|
2660
|
+
|
|
2661
|
+
## REVIEW PASS 4 — QA Engine: Reliability
|
|
2662
|
+
|
|
2663
|
+
Read `qa-engine.js` completely.
|
|
2664
|
+
|
|
2665
|
+
- [ ] **`extractSurfaces` shell command injection.** `commitsBack` comes from function args (user-controlled). The exec call: `execSync(\`git diff HEAD~${commitsBack} --name-only\`)` — if `commitsBack` is `"; rm -rf /"` this is command injection. Fix: "Validate `commitsBack` is a positive integer before using in shell command. `const safeN = Math.max(1, Math.floor(Math.abs(Number(commitsBack) || 1)));`"
|
|
2666
|
+
|
|
2667
|
+
- [ ] **QA skips component files but doesn't find their parent pages.** When a component like `src/components/ProjectCard.tsx` changes, `classifyFile` returns `{ type: 'component' }` and the QA engine skips it. But the component could have broken the pages that use it. The spec says "find parent pages" — but the implementation just skips components. Fix: "For component surfaces: scan `src/pages/` and `src/app/` for files that import the component. Add those pages as derived surfaces with `derived_from: component_path`."
|
|
2668
|
+
|
|
2669
|
+
- [ ] **Dev server unreachable produces cascading failures.** If the dev server is not running, every `navigate` call fails. The QA report would show every surface as failed — which is misleading (the issue is the dev server, not the code). Add: "Before the QA loop: do a single `navigate('/')` health check. If it fails: return `{ error: 'Dev server unreachable at DEV_SERVER_URL', surfaces: 0 }` without running any tests."
|
|
2670
|
+
|
|
2671
|
+
---
|
|
2672
|
+
|
|
2673
|
+
## REVIEW PASS 5 — Session Manager: Cookie Expiry
|
|
2674
|
+
|
|
2675
|
+
Read `session-manager.js` completely.
|
|
2676
|
+
|
|
2677
|
+
- [ ] **`parseSqliteCookies` Windows epoch conversion.** The conversion `r.expires_utc / 1_000_000 - 11_644_473_600` converts Chrome's Windows FILETIME to Unix timestamp. But Chrome stores `expires_utc` in microseconds since 1601-01-01 (Windows epoch). The formula is correct, BUT: if `expires_utc` is 0 (session cookie — no expiry), the formula produces a negative number, which would be filtered out by `> now`. Session cookies should be treated as valid (non-expiring). Fix: "If `r.expires_utc === 0`: set `expires: -1` (Playwright's 'never expires' sentinel) instead of computing the conversion."
|
|
2678
|
+
|
|
2679
|
+
- [ ] **`importFromBrowser` error message suggests closing Chrome.** But on macOS, Chrome uses OS-level file locking inconsistently — sometimes the cookie file IS readable while Chrome is open. The error is premature. Fix: "First attempt to read the file. If it fails with EBUSY or similar: THEN suggest closing the browser. Do not pre-emptively tell users to close their browser."
|
|
2680
|
+
|
|
2681
|
+
---
|
|
2682
|
+
|
|
2683
|
+
## REVIEW PASS 6 — Test Suite Quality
|
|
2684
|
+
|
|
2685
|
+
Read `tests/browser.test.js` completely.
|
|
2686
|
+
|
|
2687
|
+
- [ ] **Screenshot store path traversal test is weak.** The test checks `!saved.includes('..')` but a sophisticated path traversal could use URL encoding or other tricks. The test should also verify the resolved path is WITHIN the expected directory. Fix: "Use `path.resolve()` to resolve the returned path and verify it starts with the expected screenshots directory: `assert.ok(path.resolve(saved).startsWith(path.resolve(STORE)))`."
|
|
2688
|
+
|
|
2689
|
+
- [ ] **`click` heuristic change not covered.** Review Pass 3 identified a bug in the click heuristic (CSS characters determine click type, not quote presence). The test suite has no test for this. Add: "Test that `click: .project-list` is treated as a CSS selector, `click: "Create Project"` as text click, and `click: #btn` as a CSS selector (not text)."
|
|
2690
|
+
|
|
2691
|
+
- [ ] **Missing test: QA dev server health check.** Review Pass 4 identified that QA should health-check the dev server before running. Add a test that verifies `runQA` returns an error object (not crashes) when the daemon is not running and cannot navigate.
|
|
2692
|
+
|
|
2693
|
+
---
|
|
2694
|
+
|
|
2695
|
+
## REVIEW SUMMARY TABLE
|
|
2696
|
+
|
|
2697
|
+
```
|
|
2698
|
+
## Day 9 Review Summary
|
|
2699
|
+
|
|
2700
|
+
| Category | BLOCKING | MAJOR | MINOR | SUGGESTION |
|
|
2701
|
+
|--------------------|----------|-------|-------|------------|
|
|
2702
|
+
| Browser Daemon | | | | |
|
|
2703
|
+
| Screenshot Store | | | | |
|
|
2704
|
+
| Visual Verify | | | | |
|
|
2705
|
+
| QA Engine | | | | |
|
|
2706
|
+
| Session Manager | | | | |
|
|
2707
|
+
| Test Suite | | | | |
|
|
2708
|
+
| **TOTAL** | | | | |
|
|
2709
|
+
|
|
2710
|
+
## Verdict
|
|
2711
|
+
[ ] ✅ APPROVED — Proceed to HARDEN section
|
|
2712
|
+
[ ] ⚠️ APPROVED WITH CONDITIONS
|
|
2713
|
+
[ ] ❌ NOT APPROVED
|
|
2714
|
+
```
|
|
2715
|
+
|
|
2716
|
+
---
|
|
2717
|
+
|
|
2718
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
2719
|
+
# PART 3 — HARDENING PROMPT
|
|
2720
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
2721
|
+
|
|
2722
|
+
---
|
|
2723
|
+
|
|
2724
|
+
## DAY 9 HARDENING
|
|
2725
|
+
|
|
2726
|
+
Activate **`security-reviewer.md` + `qa-engineer.md`** simultaneously.
|
|
2727
|
+
|
|
2728
|
+
Confirm all test suites pass before hardening:
|
|
2729
|
+
```bash
|
|
2730
|
+
for suite in install wave-engine audit compaction skills-platform \
|
|
2731
|
+
integrations governance intelligence metrics \
|
|
2732
|
+
distribution ci-mode sdk production migration e2e \
|
|
2733
|
+
autonomous browser; do
|
|
2734
|
+
printf " %-30s" "${suite}..."
|
|
2735
|
+
node tests/${suite}.test.js 2>&1 | tail -1
|
|
2736
|
+
done
|
|
2737
|
+
```
|
|
2738
|
+
|
|
2739
|
+
---
|
|
2740
|
+
|
|
2741
|
+
## HARDEN 1 — Conditional Chromium sandbox flags
|
|
2742
|
+
|
|
2743
|
+
Update `browser-daemon.js` `init()` function:
|
|
2744
|
+
|
|
2745
|
+
```javascript
|
|
2746
|
+
// Replace hardcoded --no-sandbox with environment-aware flags
|
|
2747
|
+
function getChromiumArgs() {
|
|
2748
|
+
const base = ['--no-first-run', '--no-zygote', '--disable-accelerated-2d-canvas'];
|
|
2749
|
+
|
|
2750
|
+
// Disable sandbox ONLY when:
|
|
2751
|
+
// - Running as root (common in Docker/CI)
|
|
2752
|
+
// - OR explicitly in CI environment
|
|
2753
|
+
// - OR BROWSER_NO_SANDBOX=true is set
|
|
2754
|
+
const isRoot = process.getuid && process.getuid() === 0;
|
|
2755
|
+
const isCI = process.env.CI === 'true';
|
|
2756
|
+
const forceNoSandbox = process.env.BROWSER_NO_SANDBOX === 'true';
|
|
2757
|
+
|
|
2758
|
+
if (isRoot || isCI || forceNoSandbox) {
|
|
2759
|
+
base.unshift('--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage');
|
|
2760
|
+
process.stderr.write('[daemon] ⚠️ Running without Chromium sandbox (CI/root mode)\n');
|
|
2761
|
+
} else {
|
|
2762
|
+
process.stderr.write('[daemon] ✅ Chromium sandbox enabled (local dev mode)\n');
|
|
2763
|
+
}
|
|
2764
|
+
|
|
2765
|
+
return base;
|
|
2766
|
+
}
|
|
2767
|
+
|
|
2768
|
+
// In init():
|
|
2769
|
+
browser = await playwright.chromium.launch({ headless: HEADLESS, args: getChromiumArgs() });
|
|
2770
|
+
```
|
|
2771
|
+
|
|
2772
|
+
Add to MINDFORGE-V2-SCHEMA.json:
|
|
2773
|
+
```json
|
|
2774
|
+
"BROWSER_NO_SANDBOX": { "type": "boolean",
|
|
2775
|
+
"description": "Disable Chromium sandbox (set true for CI/Docker, false for local dev)" }
|
|
2776
|
+
```
|
|
2777
|
+
|
|
2778
|
+
**Commit:**
|
|
2779
|
+
```bash
|
|
2780
|
+
git add bin/browser/browser-daemon.js .mindforge/MINDFORGE-V2-SCHEMA.json
|
|
2781
|
+
git commit -m "harden(v2-browser): conditional Chromium sandbox — enable locally, disable in CI/root"
|
|
2782
|
+
```
|
|
2783
|
+
|
|
2784
|
+
---
|
|
2785
|
+
|
|
2786
|
+
## HARDEN 2 — Fix screenshot path traversal
|
|
2787
|
+
|
|
2788
|
+
Update `bin/browser/screenshot-store.js`:
|
|
2789
|
+
|
|
2790
|
+
```javascript
|
|
2791
|
+
'use strict';
|
|
2792
|
+
const fs = require('fs');
|
|
2793
|
+
const path = require('path');
|
|
2794
|
+
|
|
2795
|
+
const STORE = path.join(process.cwd(), '.planning', 'screenshots');
|
|
2796
|
+
const MAX_SCREENSHOTS = parseInt(process.env.BROWSER_MAX_SCREENSHOTS_PER_PHASE || '50', 10);
|
|
2797
|
+
|
|
2798
|
+
const ensureDir = d => fs.mkdirSync(d, { recursive: true });
|
|
2799
|
+
|
|
2800
|
+
function save(base64Png, phaseNum, planId, filename = 'screenshot.png') {
|
|
2801
|
+
// ── INPUT VALIDATION: prevent path traversal ─────────────────────────────
|
|
2802
|
+
const safePhase = Math.floor(Math.abs(Number(phaseNum) || 0));
|
|
2803
|
+
const safePlan = String(planId).replace(/[^a-zA-Z0-9_-]/g, '-').slice(0, 64);
|
|
2804
|
+
const safeName = String(filename || 'screenshot.png')
|
|
2805
|
+
.replace(/[^a-zA-Z0-9._-]/g, '-')
|
|
2806
|
+
.replace(/\.png$/i, '')
|
|
2807
|
+
.slice(0, 128) + '.png';
|
|
2808
|
+
|
|
2809
|
+
const dir = path.join(STORE, `phase-${safePhase}`, safePlan);
|
|
2810
|
+
const dest = path.join(dir, safeName);
|
|
2811
|
+
|
|
2812
|
+
// ── PATH TRAVERSAL GUARD: resolved path must stay inside STORE ───────────
|
|
2813
|
+
const resolvedStore = path.resolve(STORE);
|
|
2814
|
+
const resolvedDest = path.resolve(dest);
|
|
2815
|
+
if (!resolvedDest.startsWith(resolvedStore + path.sep) &&
|
|
2816
|
+
!resolvedDest.startsWith(resolvedStore)) {
|
|
2817
|
+
throw new Error(`Path traversal rejected: ${dest} is outside screenshot store`);
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
ensureDir(dir);
|
|
2821
|
+
fs.writeFileSync(dest, Buffer.from(base64Png, 'base64'));
|
|
2822
|
+
|
|
2823
|
+
// ── QUOTA ENFORCEMENT: delete oldest if over limit ────────────────────────
|
|
2824
|
+
const phaseDir = path.join(STORE, `phase-${safePhase}`);
|
|
2825
|
+
const allShots = listAll(phaseDir);
|
|
2826
|
+
if (allShots.length > MAX_SCREENSHOTS) {
|
|
2827
|
+
const oldest = allShots
|
|
2828
|
+
.map(f => ({ f, mt: fs.statSync(f).mtimeMs }))
|
|
2829
|
+
.sort((a, b) => a.mt - b.mt)
|
|
2830
|
+
.slice(0, allShots.length - MAX_SCREENSHOTS);
|
|
2831
|
+
oldest.forEach(({ f }) => { try { fs.unlinkSync(f); } catch {} });
|
|
2832
|
+
process.stderr.write(`[screenshot-store] Quota: deleted ${oldest.length} oldest screenshots for phase ${safePhase}\n`);
|
|
2833
|
+
}
|
|
2834
|
+
|
|
2835
|
+
return dest;
|
|
2836
|
+
}
|
|
2837
|
+
|
|
2838
|
+
function listAll(dir) {
|
|
2839
|
+
if (!fs.existsSync(dir)) return [];
|
|
2840
|
+
const walk = d => fs.readdirSync(d, { withFileTypes: true })
|
|
2841
|
+
.flatMap(e => e.isDirectory() ? walk(path.join(d, e.name)) : path.join(d, e.name))
|
|
2842
|
+
.filter(p => p.endsWith('.png'));
|
|
2843
|
+
return walk(dir);
|
|
2844
|
+
}
|
|
2845
|
+
|
|
2846
|
+
function list(phaseNum, planId) {
|
|
2847
|
+
const safePhase = Math.floor(Math.abs(Number(phaseNum) || 0));
|
|
2848
|
+
const dir = planId
|
|
2849
|
+
? path.join(STORE, `phase-${safePhase}`, String(planId).replace(/[^a-zA-Z0-9_-]/g, '-'))
|
|
2850
|
+
: path.join(STORE, `phase-${safePhase}`);
|
|
2851
|
+
return listAll(dir);
|
|
2852
|
+
}
|
|
2853
|
+
|
|
2854
|
+
function cleanup(phaseNum) {
|
|
2855
|
+
const safePhase = Math.floor(Math.abs(Number(phaseNum) || 0));
|
|
2856
|
+
const dir = path.join(STORE, `phase-${safePhase}`);
|
|
2857
|
+
if (fs.existsSync(dir)) fs.rmSync(dir, { recursive: true, force: true });
|
|
2858
|
+
}
|
|
2859
|
+
|
|
2860
|
+
function diskUsage() {
|
|
2861
|
+
if (!fs.existsSync(STORE)) return 0;
|
|
2862
|
+
let total = 0;
|
|
2863
|
+
const walk = d => { for (const e of fs.readdirSync(d, { withFileTypes: true })) {
|
|
2864
|
+
const p = path.join(d, e.name);
|
|
2865
|
+
e.isDirectory() ? walk(p) : (total += fs.statSync(p).size);
|
|
2866
|
+
}};
|
|
2867
|
+
walk(STORE);
|
|
2868
|
+
return total;
|
|
2869
|
+
}
|
|
2870
|
+
|
|
2871
|
+
module.exports = { save, list, cleanup, diskUsage };
|
|
2872
|
+
```
|
|
2873
|
+
|
|
2874
|
+
**Commit:**
|
|
2875
|
+
```bash
|
|
2876
|
+
git add bin/browser/screenshot-store.js
|
|
2877
|
+
git commit -m "harden(v2-browser): fix screenshot path traversal with path.resolve guard + quota enforcement"
|
|
2878
|
+
```
|
|
2879
|
+
|
|
2880
|
+
---
|
|
2881
|
+
|
|
2882
|
+
## HARDEN 3 — Fix QA command injection and add dev server health check
|
|
2883
|
+
|
|
2884
|
+
Update `bin/browser/qa-engine.js`:
|
|
2885
|
+
|
|
2886
|
+
```javascript
|
|
2887
|
+
// At top of extractSurfaces function — replace commitsBack usage:
|
|
2888
|
+
function extractSurfaces(phaseNum, commitsBack = 1) {
|
|
2889
|
+
// ── INPUT VALIDATION: prevent shell injection ─────────────────────────────
|
|
2890
|
+
const safeN = Math.max(1, Math.min(100, Math.floor(Math.abs(Number(commitsBack) || 1))));
|
|
2891
|
+
let files = [];
|
|
2892
|
+
// ... rest of function uses safeN instead of commitsBack ...
|
|
2893
|
+
|
|
2894
|
+
// Add dev server health check at top of runQA:
|
|
2895
|
+
async function runQA(phaseNum, opts = {}) {
|
|
2896
|
+
const { sessions = ['default'], routes = null, commitsBack = 1 } = opts;
|
|
2897
|
+
|
|
2898
|
+
await DaemonMgr.ensureRunning({ headless: true });
|
|
2899
|
+
|
|
2900
|
+
// ── Dev server health check ───────────────────────────────────────────────
|
|
2901
|
+
const healthCheck = await DaemonMgr.request('POST', '/navigate',
|
|
2902
|
+
{ url: DEV_SERVER, session: 'default', wait_for: 'domcontentloaded' }
|
|
2903
|
+
).catch(() => ({ success: false, error: 'Connection refused' }));
|
|
2904
|
+
|
|
2905
|
+
if (!healthCheck.success) {
|
|
2906
|
+
process.stderr.write(`[qa-engine] ⚠️ Dev server unreachable at ${DEV_SERVER}\n`);
|
|
2907
|
+
process.stderr.write(`[qa-engine] Is your dev server running? Start it first.\n`);
|
|
2908
|
+
return {
|
|
2909
|
+
surfaces: 0, passed: 0, failed: 0, bugs: [], results: [],
|
|
2910
|
+
error: `Dev server unreachable at ${DEV_SERVER}. Start your dev server and retry.`,
|
|
2911
|
+
};
|
|
2912
|
+
}
|
|
2913
|
+
|
|
2914
|
+
// ... rest of function unchanged ...
|
|
2915
|
+
```
|
|
2916
|
+
|
|
2917
|
+
Also implement parent page discovery for components:
|
|
2918
|
+
|
|
2919
|
+
```javascript
|
|
2920
|
+
// Add to qa-engine.js — called after classifyFile returns a component:
|
|
2921
|
+
function findParentPages(componentFile) {
|
|
2922
|
+
const parents = [];
|
|
2923
|
+
const pagesRoots = ['src/app', 'src/pages'];
|
|
2924
|
+
|
|
2925
|
+
for (const root of pagesRoots) {
|
|
2926
|
+
if (!fs.existsSync(root)) continue;
|
|
2927
|
+
const walk = (dir) => {
|
|
2928
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
2929
|
+
const full = path.join(dir, entry.name);
|
|
2930
|
+
if (entry.isDirectory()) { walk(full); continue; }
|
|
2931
|
+
if (!/\.(tsx|jsx|ts|js)$/.test(entry.name)) continue;
|
|
2932
|
+
try {
|
|
2933
|
+
const content = fs.readFileSync(full, 'utf8');
|
|
2934
|
+
if (content.includes(path.basename(componentFile, path.extname(componentFile)))) {
|
|
2935
|
+
const surface = classifyFile(full.replace(process.cwd() + '/', ''));
|
|
2936
|
+
if (surface?.type === 'page') parents.push({ ...surface, source_file: full, derived_from: componentFile });
|
|
2937
|
+
}
|
|
2938
|
+
} catch {}
|
|
2939
|
+
}
|
|
2940
|
+
};
|
|
2941
|
+
walk(root);
|
|
2942
|
+
}
|
|
2943
|
+
return parents;
|
|
2944
|
+
}
|
|
2945
|
+
```
|
|
2946
|
+
|
|
2947
|
+
**Commit:**
|
|
2948
|
+
```bash
|
|
2949
|
+
git add bin/browser/qa-engine.js
|
|
2950
|
+
git commit -m "harden(v2-browser): fix QA shell injection, add dev server health check, component parent discovery"
|
|
2951
|
+
```
|
|
2952
|
+
|
|
2953
|
+
---
|
|
2954
|
+
|
|
2955
|
+
## HARDEN 4 — Fix `press` directive and click heuristic
|
|
2956
|
+
|
|
2957
|
+
Update `bin/browser/visual-verify-executor.js`:
|
|
2958
|
+
|
|
2959
|
+
```javascript
|
|
2960
|
+
// Replace 'press' case in executeBlock switch:
|
|
2961
|
+
case 'press': {
|
|
2962
|
+
const key = args[0] || 'Enter';
|
|
2963
|
+
// Implement via evaluate — page.keyboard is accessible in evaluate context
|
|
2964
|
+
const r = await DaemonMgr.request('POST', '/evaluate', {
|
|
2965
|
+
script: `document.activeElement.dispatchEvent(new KeyboardEvent('keydown', {key:'${key}',bubbles:true}))`,
|
|
2966
|
+
session,
|
|
2967
|
+
});
|
|
2968
|
+
step.detail = `pressed ${key}`;
|
|
2969
|
+
// Also try pressing Enter on the focused element via Playwright hint
|
|
2970
|
+
break;
|
|
2971
|
+
}
|
|
2972
|
+
|
|
2973
|
+
// Fix click directive heuristic — replace existing 'click' case:
|
|
2974
|
+
case 'click': {
|
|
2975
|
+
const raw = args[0] || '';
|
|
2976
|
+
// Use CSS characters to determine if it's a selector or text
|
|
2977
|
+
const isCSSSelector = /^[#\.\[\:\*\~\>\+]/.test(raw) || raw.includes('[') || raw.includes('=');
|
|
2978
|
+
const body = isCSSSelector
|
|
2979
|
+
? { selector: raw, session }
|
|
2980
|
+
: { text: raw.replace(/^["']|["']$/g, ''), session }; // Strip quotes from text
|
|
2981
|
+
const r = await DaemonMgr.request('POST', '/click', body);
|
|
2982
|
+
step.detail = r.element_found ? 'clicked' : 'element not found';
|
|
2983
|
+
if (!r.success || !r.element_found) { step.status = 'fail'; passed = false; }
|
|
2984
|
+
break;
|
|
2985
|
+
}
|
|
2986
|
+
```
|
|
2987
|
+
|
|
2988
|
+
**Commit:**
|
|
2989
|
+
```bash
|
|
2990
|
+
git add bin/browser/visual-verify-executor.js
|
|
2991
|
+
git commit -m "harden(v2-browser): implement press directive, fix click CSS-vs-text heuristic"
|
|
2992
|
+
```
|
|
2993
|
+
|
|
2994
|
+
---
|
|
2995
|
+
|
|
2996
|
+
## HARDEN 5 — Fix SQLite session cookie Windows epoch for session cookies
|
|
2997
|
+
|
|
2998
|
+
Update `bin/browser/session-manager.js`:
|
|
2999
|
+
|
|
3000
|
+
```javascript
|
|
3001
|
+
// In parseSqliteCookies — replace expires calculation:
|
|
3002
|
+
return rows.map(r => ({
|
|
3003
|
+
name: r.name, value: r.value, domain: r.host_key, path: r.path,
|
|
3004
|
+
// Session cookies have expires_utc = 0 — map to -1 (Playwright: never expires)
|
|
3005
|
+
expires: r.expires_utc === 0
|
|
3006
|
+
? -1
|
|
3007
|
+
: r.expires_utc / 1_000_000 - 11_644_473_600,
|
|
3008
|
+
secure: !!r.is_secure, httpOnly: !!r.is_httponly, sameSite: 'Lax',
|
|
3009
|
+
}))
|
|
3010
|
+
// Filter: keep session cookies (expires = -1) AND non-expired time-bound cookies
|
|
3011
|
+
.filter(c => c.name && (c.expires === -1 || c.expires > Date.now() / 1000));
|
|
3012
|
+
```
|
|
3013
|
+
|
|
3014
|
+
**Commit:**
|
|
3015
|
+
```bash
|
|
3016
|
+
git add bin/browser/session-manager.js
|
|
3017
|
+
git commit -m "harden(v2-browser): fix session cookie Windows epoch — map expires_utc=0 to -1"
|
|
3018
|
+
```
|
|
3019
|
+
|
|
3020
|
+
---
|
|
3021
|
+
|
|
3022
|
+
## HARDEN 6 — Strengthen test coverage for hardened paths
|
|
3023
|
+
|
|
3024
|
+
Add to `tests/browser.test.js`:
|
|
3025
|
+
|
|
3026
|
+
```javascript
|
|
3027
|
+
// ── Hardening tests ───────────────────────────────────────────────────────────
|
|
3028
|
+
console.log('\nHardening tests:');
|
|
3029
|
+
|
|
3030
|
+
test('screenshot save: path traversal with planId is rejected', () => {
|
|
3031
|
+
const p = mkProject(true);
|
|
3032
|
+
const orig = process.cwd();
|
|
3033
|
+
process.chdir(p.dir);
|
|
3034
|
+
try {
|
|
3035
|
+
assert.throws(
|
|
3036
|
+
() => ScreenStore.save(TINY_PNG_B64, 3, '../../evil', 'bad.png'),
|
|
3037
|
+
/traversal/i,
|
|
3038
|
+
'Should throw on path traversal in planId'
|
|
3039
|
+
);
|
|
3040
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
3041
|
+
});
|
|
3042
|
+
|
|
3043
|
+
test('screenshot save: path traversal in filename is rejected', () => {
|
|
3044
|
+
const p = mkProject(true);
|
|
3045
|
+
const orig = process.cwd();
|
|
3046
|
+
process.chdir(p.dir);
|
|
3047
|
+
try {
|
|
3048
|
+
// This should NOT throw — filename is sanitized, not rejected
|
|
3049
|
+
// But the saved path must stay inside STORE
|
|
3050
|
+
const saved = ScreenStore.save(TINY_PNG_B64, 3, '04', '../../evil.png');
|
|
3051
|
+
const resolvedStore = require('path').resolve(p.dir, '.planning', 'screenshots');
|
|
3052
|
+
const resolvedSaved = require('path').resolve(saved);
|
|
3053
|
+
assert.ok(resolvedSaved.startsWith(resolvedStore), 'Saved path must be inside screenshot store');
|
|
3054
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
3055
|
+
});
|
|
3056
|
+
|
|
3057
|
+
test('Chromium args: no-sandbox only when CI=true or root', () => {
|
|
3058
|
+
const c = fs.readFileSync('bin/browser/browser-daemon.js', 'utf8');
|
|
3059
|
+
assert.ok(
|
|
3060
|
+
c.includes('isCI') || c.includes('CI=') || c.includes('process.env.CI'),
|
|
3061
|
+
'Sandbox flag should be conditional on CI environment'
|
|
3062
|
+
);
|
|
3063
|
+
assert.ok(
|
|
3064
|
+
!c.includes("'--no-sandbox'") || c.includes('if') || c.includes('getChromiumArgs'),
|
|
3065
|
+
'--no-sandbox should not be unconditional'
|
|
3066
|
+
);
|
|
3067
|
+
});
|
|
3068
|
+
|
|
3069
|
+
test('visual verify click heuristic: CSS selector detected by # prefix', () => {
|
|
3070
|
+
const d = VisualVerify.parseDirectives('click: #submit-btn');
|
|
3071
|
+
assert.strictEqual(d[0].directive, 'click');
|
|
3072
|
+
assert.strictEqual(d[0].args[0], '#submit-btn', 'CSS selector preserved as-is');
|
|
3073
|
+
});
|
|
3074
|
+
|
|
3075
|
+
test('visual verify click heuristic: text click detected when no CSS chars', () => {
|
|
3076
|
+
const d = VisualVerify.parseDirectives('click: "Sign In"');
|
|
3077
|
+
assert.strictEqual(d[0].directive, 'click');
|
|
3078
|
+
// Text args have quotes stripped by parseDirectives
|
|
3079
|
+
assert.strictEqual(d[0].args[0], 'Sign In', 'Text has quotes stripped');
|
|
3080
|
+
});
|
|
3081
|
+
|
|
3082
|
+
test('QA report: no bugs section says "No bugs found"', () => {
|
|
3083
|
+
const p = mkProject();
|
|
3084
|
+
const orig = process.cwd();
|
|
3085
|
+
process.chdir(p.dir);
|
|
3086
|
+
try {
|
|
3087
|
+
const file = QAReportWriter.write(5, { surfaces: 1, passed: 1, failed: 0, results: [{ surface: '/home', session: 'default', passed: true }], bugs: [] });
|
|
3088
|
+
const c = fs.readFileSync(file, 'utf8');
|
|
3089
|
+
assert.ok(c.includes('No bugs found'), 'Should say "No bugs found"');
|
|
3090
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
3091
|
+
});
|
|
3092
|
+
```
|
|
3093
|
+
|
|
3094
|
+
**Commit:**
|
|
3095
|
+
```bash
|
|
3096
|
+
git add tests/browser.test.js
|
|
3097
|
+
git commit -m "test(v2-browser): add hardening tests for path traversal, sandbox flags, click heuristic"
|
|
3098
|
+
```
|
|
3099
|
+
|
|
3100
|
+
---
|
|
3101
|
+
|
|
3102
|
+
## HARDEN 7 — Write 3 ADRs for Day 9 decisions
|
|
3103
|
+
|
|
3104
|
+
### `.planning/decisions/ADR-024-browser-daemon-localhost-only.md`
|
|
3105
|
+
|
|
3106
|
+
```markdown
|
|
3107
|
+
# ADR-024: Browser daemon binds to localhost only (127.0.0.1)
|
|
3108
|
+
|
|
3109
|
+
**Status:** Accepted | **Date:** v2.0.0 | **Day:** 9
|
|
3110
|
+
|
|
3111
|
+
## Context
|
|
3112
|
+
The browser daemon (port 7338) exposes session data, screenshots, and
|
|
3113
|
+
JavaScript evaluation capabilities. Should it bind to all interfaces?
|
|
3114
|
+
|
|
3115
|
+
## Decision
|
|
3116
|
+
Bind to 127.0.0.1 only. Consistent with ADR-017 (SDK SSE server).
|
|
3117
|
+
|
|
3118
|
+
## Rationale
|
|
3119
|
+
Sessions contain auth tokens. Cookies can include JWTs and session IDs.
|
|
3120
|
+
Exposing these on a network interface in a shared dev environment would
|
|
3121
|
+
allow any other machine (or container on the same host network) to access
|
|
3122
|
+
them. The use case (developer testing their own app) never requires remote access.
|
|
3123
|
+
For remote monitoring: use SSH tunneling.
|
|
3124
|
+
|
|
3125
|
+
## Consequences
|
|
3126
|
+
Same as ADR-017: remote access requires explicit SSH tunnel.
|
|
3127
|
+
CI environments (Docker, GitHub Actions) run locally — no issue.
|
|
3128
|
+
```
|
|
3129
|
+
|
|
3130
|
+
### `.planning/decisions/ADR-025-verify-visual-failure-is-verify-failure.md`
|
|
3131
|
+
|
|
3132
|
+
```markdown
|
|
3133
|
+
# ADR-025: <verify-visual> failure is treated identically to <verify> failure
|
|
3134
|
+
|
|
3135
|
+
**Status:** Accepted | **Date:** v2.0.0 | **Day:** 9
|
|
3136
|
+
|
|
3137
|
+
## Context
|
|
3138
|
+
When <verify-visual> assertions fail after <verify> passes, what should happen?
|
|
3139
|
+
|
|
3140
|
+
## Decision
|
|
3141
|
+
<verify-visual> failure triggers node repair (RETRY → DECOMPOSE → PRUNE → ESCALATE)
|
|
3142
|
+
identically to a <verify> (unit test) failure.
|
|
3143
|
+
|
|
3144
|
+
## Rationale
|
|
3145
|
+
If the UI doesn't look correct, the task is not done — regardless of whether
|
|
3146
|
+
unit tests pass. A login form that passes tests but allows empty email submission
|
|
3147
|
+
is a real bug. Node repair handles it the same way.
|
|
3148
|
+
|
|
3149
|
+
The screenshot attached to the visual verify failure gives the retry agent
|
|
3150
|
+
much better information than a test failure message — it can see exactly what
|
|
3151
|
+
the browser showed when the assertion failed.
|
|
3152
|
+
|
|
3153
|
+
## Consequences
|
|
3154
|
+
Auto mode will attempt to self-repair visual verification failures.
|
|
3155
|
+
Engineers should use <verify-visual> for meaningful assertions (not cosmetic preferences)
|
|
3156
|
+
to avoid excessive node repairs on subjective UI details.
|
|
3157
|
+
```
|
|
3158
|
+
|
|
3159
|
+
### `.planning/decisions/ADR-026-session-files-gitignored.md`
|
|
3160
|
+
|
|
3161
|
+
```markdown
|
|
3162
|
+
# ADR-026: Browser session files are gitignored — auth tokens must never reach git
|
|
3163
|
+
|
|
3164
|
+
**Status:** Accepted | **Date:** v2.0.0 | **Day:** 9
|
|
3165
|
+
|
|
3166
|
+
## Context
|
|
3167
|
+
Browser sessions contain cookies (including auth tokens, session IDs, JWTs).
|
|
3168
|
+
Should they be committed to git for reproducibility?
|
|
3169
|
+
|
|
3170
|
+
## Decision
|
|
3171
|
+
Session files are gitignored. They are never committed to any branch.
|
|
3172
|
+
Screenshots are also gitignored.
|
|
3173
|
+
|
|
3174
|
+
## Rationale
|
|
3175
|
+
Auth tokens in git are a security incident requiring token rotation and history rewrite.
|
|
3176
|
+
The benefit of committing sessions (reproducibility) is outweighed by:
|
|
3177
|
+
1. Sessions expire anyway — they are not long-lived test fixtures
|
|
3178
|
+
2. Session files contain credentials for developer accounts
|
|
3179
|
+
3. Git history is effectively permanent — a leaked token stays in history
|
|
3180
|
+
|
|
3181
|
+
For CI: use programmatic session creation (Method 3 from session-manager.md)
|
|
3182
|
+
with credentials stored in CI secrets, not in committed files.
|
|
3183
|
+
|
|
3184
|
+
## Consequences
|
|
3185
|
+
Each developer creates their own sessions locally.
|
|
3186
|
+
CI uses programmatic login with test credentials from CI secrets.
|
|
3187
|
+
Session state cannot be shared via git — this is by design.
|
|
3188
|
+
```
|
|
3189
|
+
|
|
3190
|
+
**Commit:**
|
|
3191
|
+
```bash
|
|
3192
|
+
git add .planning/decisions/ADR-024*.md \
|
|
3193
|
+
.planning/decisions/ADR-025*.md \
|
|
3194
|
+
.planning/decisions/ADR-026*.md
|
|
3195
|
+
git commit -m "docs(adr): add ADR-024 localhost binding, ADR-025 visual verify failure, ADR-026 sessions gitignored"
|
|
3196
|
+
```
|
|
3197
|
+
|
|
3198
|
+
---
|
|
3199
|
+
|
|
3200
|
+
## HARDEN 8 — Final pre-merge verification
|
|
3201
|
+
|
|
3202
|
+
```bash
|
|
3203
|
+
#!/usr/bin/env bash
|
|
3204
|
+
echo "MindForge v2 Day 9 — Pre-Merge Verification"
|
|
3205
|
+
echo "══════════════════════════════════════════════"
|
|
3206
|
+
PASS=true
|
|
3207
|
+
|
|
3208
|
+
# 1. Version
|
|
3209
|
+
V=$(node -e "console.log(require('./package.json').version)")
|
|
3210
|
+
[[ "${V}" == "2.0.0-alpha.2" ]] && echo " Version: ${V} ✅" || { echo " ❌ Version: ${V}"; PASS=false; }
|
|
3211
|
+
|
|
3212
|
+
# 2. All 17 test suites
|
|
3213
|
+
echo ""
|
|
3214
|
+
echo " Test suites:"
|
|
3215
|
+
FAIL=0
|
|
3216
|
+
for suite in install wave-engine audit compaction skills-platform \
|
|
3217
|
+
integrations governance intelligence metrics \
|
|
3218
|
+
distribution ci-mode sdk production migration e2e \
|
|
3219
|
+
autonomous browser; do
|
|
3220
|
+
printf " %-30s" "${suite}..."
|
|
3221
|
+
if node tests/${suite}.test.js 2>&1 | tail -1 | grep -q "passed"; then
|
|
3222
|
+
echo "✅"
|
|
3223
|
+
else
|
|
3224
|
+
echo "❌"; ((FAIL++)); PASS=false
|
|
3225
|
+
fi
|
|
3226
|
+
done
|
|
3227
|
+
echo " Failed: ${FAIL}"
|
|
3228
|
+
|
|
3229
|
+
# 3. Command count
|
|
3230
|
+
CMDS=$(ls .claude/commands/mindforge/ | wc -l | tr -d ' ')
|
|
3231
|
+
[ "$CMDS" -ge 40 ] && echo " Commands: ${CMDS} ✅" || { echo " ❌ Commands: ${CMDS} < 40"; PASS=false; }
|
|
3232
|
+
|
|
3233
|
+
# 4. ADRs
|
|
3234
|
+
ADRS=$(ls .planning/decisions/ADR-*.md 2>/dev/null | wc -l | tr -d ' ')
|
|
3235
|
+
[ "$ADRS" -ge 26 ] && echo " ADRs: ${ADRS} ✅" || { echo " ❌ ADRs: ${ADRS} < 26"; PASS=false; }
|
|
3236
|
+
|
|
3237
|
+
# 5. gitignore
|
|
3238
|
+
grep -q "sessions" .gitignore && grep -q "screenshots" .gitignore \
|
|
3239
|
+
&& echo " .gitignore: sessions + screenshots ✅" \
|
|
3240
|
+
|| { echo " ❌ .gitignore missing browser entries"; PASS=false; }
|
|
3241
|
+
|
|
3242
|
+
# 6. No secrets
|
|
3243
|
+
SECRETS=$(grep -rE "(password|api_key|token)\s*=\s*['\"][^'\"]{8,}" \
|
|
3244
|
+
--include="*.md" --include="*.js" --include="*.json" \
|
|
3245
|
+
--exclude-dir=node_modules --exclude-dir=.git . 2>/dev/null | \
|
|
3246
|
+
grep -v "placeholder\|example\|your-\|TEST_ONLY" || true)
|
|
3247
|
+
[ -z "$SECRETS" ] && echo " Secrets: clean ✅" || { echo " ❌ Credentials detected"; PASS=false; }
|
|
3248
|
+
|
|
3249
|
+
echo ""
|
|
3250
|
+
if $PASS; then
|
|
3251
|
+
echo "✅ ALL CHECKS PASSED — Day 9 complete"
|
|
3252
|
+
echo ""
|
|
3253
|
+
echo " v2.0.0-alpha.2 is ready for PR."
|
|
3254
|
+
echo " Next: Day 10 — Multi-Model Intelligence Layer"
|
|
3255
|
+
else
|
|
3256
|
+
echo "❌ FAILURES — fix before merging"
|
|
3257
|
+
exit 1
|
|
3258
|
+
fi
|
|
3259
|
+
```
|
|
3260
|
+
|
|
3261
|
+
**Final commits:**
|
|
3262
|
+
```bash
|
|
3263
|
+
git add .
|
|
3264
|
+
git commit -m "harden(v2-day9): complete all hardening — path traversal, sandbox, click heuristic, ADRs"
|
|
3265
|
+
git push origin feat/mindforge-v2-browser-runtime
|
|
3266
|
+
```
|
|
3267
|
+
|
|
3268
|
+
---
|
|
3269
|
+
|
|
3270
|
+
## DAY 9 COMPLETE
|
|
3271
|
+
|
|
3272
|
+
| Component | Status |
|
|
3273
|
+
|---|---|
|
|
3274
|
+
| Browser daemon (Playwright, port 7338, localhost-only) | ✅ |
|
|
3275
|
+
| Daemon manager (lifecycle, auto-start, PID file) | ✅ |
|
|
3276
|
+
| Session manager (persistence, cookie import, expiry) | ✅ |
|
|
3277
|
+
| `<verify-visual>` parser (13 directives, session attr) | ✅ |
|
|
3278
|
+
| Visual verify executor (fail-fast, screenshot capture) | ✅ |
|
|
3279
|
+
| Screenshot store (phase namespaced, path traversal guard) | ✅ |
|
|
3280
|
+
| QA engine (git-diff surfaces, component parent discovery) | ✅ |
|
|
3281
|
+
| QA report writer (QA-REPORT-[N].md) | ✅ |
|
|
3282
|
+
| Regression test writer (Playwright .test.ts per bug) | ✅ |
|
|
3283
|
+
| `/mindforge:browse` command (40th) | ✅ |
|
|
3284
|
+
| `/mindforge:qa` command (40th) | ✅ |
|
|
3285
|
+
| execute-phase: `<verify-visual>` hook | ✅ |
|
|
3286
|
+
| MINDFORGE.md browser settings + schema | ✅ |
|
|
3287
|
+
| `tests/browser.test.js` (17th suite) | ✅ |
|
|
3288
|
+
| ADR-024, ADR-025, ADR-026 | ✅ |
|
|
3289
|
+
| CHANGELOG v2.0.0-alpha.2 | ✅ |
|
|
3290
|
+
|
|
3291
|
+
**MindForge v2.0.0-alpha.2: 40 commands · 17 test suites · 26 ADRs**
|
|
3292
|
+
**Branch:** `feat/mindforge-v2-browser-runtime`
|
|
3293
|
+
**Day 9 complete. Open PR → merge → start Day 10 (Multi-Model Intelligence)**
|