mindforge-cc 8.1.0 → 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/.claude/CLAUDE.md +4 -3
- package/.mindforge/celestial.db +0 -0
- package/.mindforge/engine/integrity.json +1 -1
- package/.mindforge/governance/policies/sovereign-default.json +1 -1
- 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 +12 -3
- 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/bin/wizard/theme.js +1 -1
- package/docs/Context/Master-Context.md +22 -2
- package/docs/PERSONAS.md +40 -0
- package/docs/architecture/V8-SRE.md +88 -0
- package/docs/commands-skills/DISCOVERED_SKILLS.md +1 -1
- package/docs/governance-guide.md +43 -17
- package/package.json +11 -3
|
@@ -0,0 +1,3301 @@
|
|
|
1
|
+
# MindForge v2 — Day 12: Real-Time Web Observability Dashboard
|
|
2
|
+
# Branch: `feat/mindforge-v2-realtime-dashboard`
|
|
3
|
+
# Prerequisite: `feat/mindforge-v2-persistent-memory` merged to `main`
|
|
4
|
+
# Version target: v2.0.0-alpha.5
|
|
5
|
+
# Theme: "The Entire Team Sees What MindForge Is Doing."
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## BRANCH SETUP
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
git checkout main
|
|
13
|
+
git pull origin main
|
|
14
|
+
|
|
15
|
+
# Verify Day 11 baseline
|
|
16
|
+
node -e "console.log(require('./package.json').version)" # Must be 2.0.0-alpha.4
|
|
17
|
+
|
|
18
|
+
# All 19 test suites must pass before starting Day 12
|
|
19
|
+
SUITES=(install wave-engine audit compaction skills-platform \
|
|
20
|
+
integrations governance intelligence metrics \
|
|
21
|
+
distribution ci-mode sdk production migration e2e \
|
|
22
|
+
autonomous browser model-routing memory)
|
|
23
|
+
|
|
24
|
+
for suite in "${SUITES[@]}"; do
|
|
25
|
+
printf " %-30s" "${suite}..."
|
|
26
|
+
node tests/${suite}.test.js 2>&1 | tail -1
|
|
27
|
+
done
|
|
28
|
+
# ALL 19 must pass — zero failures before Day 12 begins.
|
|
29
|
+
|
|
30
|
+
git checkout -b feat/mindforge-v2-realtime-dashboard
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## DAY 12 SCOPE
|
|
36
|
+
|
|
37
|
+
Day 12 builds the **Real-Time Web Observability Dashboard** — the feature that
|
|
38
|
+
takes MindForge from a CLI-only tool to a team-visible platform. When
|
|
39
|
+
`/mindforge:auto` is running in one terminal, anyone on the team can open
|
|
40
|
+
`http://localhost:7339` and see exactly what is happening: tasks completing,
|
|
41
|
+
security findings, approval requests, cost accumulation, knowledge graph entries —
|
|
42
|
+
all updating live without page refresh.
|
|
43
|
+
|
|
44
|
+
**The competitive advantage:** No other agentic framework has an integrated
|
|
45
|
+
web dashboard. Teams currently track progress via CLI output in a shared terminal
|
|
46
|
+
or by looking at git commits. The MindForge dashboard makes the agent's work
|
|
47
|
+
as visible as a Jira board.
|
|
48
|
+
|
|
49
|
+
**Architecture decisions:**
|
|
50
|
+
- **Express.js** — battle-tested, zero-config HTTP server with SSE support
|
|
51
|
+
- **Server-Sent Events** (SSE, not WebSocket) — one-way push from server, no client lib needed
|
|
52
|
+
- **Single-file HTML frontend** — no build step, no bundler, no framework dependency
|
|
53
|
+
- **localhost:7339** — adjacent to SDK SSE (7337) and browser daemon (7338)
|
|
54
|
+
- **Localhost-only** — consistent with ADR-017 (never expose on network interface)
|
|
55
|
+
- **No authentication** — local dev tool; security via binding address only
|
|
56
|
+
|
|
57
|
+
**Day 12 components:**
|
|
58
|
+
|
|
59
|
+
| Component | Description |
|
|
60
|
+
|---|---|
|
|
61
|
+
| Dashboard server | Express.js HTTP server, localhost:7339, SSE push |
|
|
62
|
+
| SSE event bridge | Tails AUDIT.jsonl, auto-state.json → SSE stream |
|
|
63
|
+
| REST API | 10 endpoints: status, audit, metrics, approvals, team, memory, costs, steer |
|
|
64
|
+
| Approval API | POST /api/approve/:id — approve/reject from browser |
|
|
65
|
+
| Frontend: Activity Feed | Live AUDIT event stream, wave progress bar, phase status |
|
|
66
|
+
| Frontend: Quality Metrics | Charts: session score trend, verify rate, cost trend |
|
|
67
|
+
| Frontend: Pending Approvals | Tier 2/3 approval UI with approve/reject buttons |
|
|
68
|
+
| Frontend: Knowledge Graph | Visual nodes/edges of knowledge base entries |
|
|
69
|
+
| Frontend: Team Activity | Active developers, last-seen, conflict detection |
|
|
70
|
+
| `/mindforge:dashboard` command | Start/stop/status/open |
|
|
71
|
+
| `tests/dashboard.test.js` | 20th test suite |
|
|
72
|
+
|
|
73
|
+
**New commands today: 45 total (44 + dashboard)**
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
78
|
+
# PART 1 — IMPLEMENTATION PROMPT
|
|
79
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
80
|
+
|
|
81
|
+
---
|
|
82
|
+
|
|
83
|
+
## TASK 1 — Scaffold Day 12 directory structure
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
# Dashboard server
|
|
87
|
+
mkdir -p bin/dashboard
|
|
88
|
+
touch bin/dashboard/server.js
|
|
89
|
+
touch bin/dashboard/sse-bridge.js
|
|
90
|
+
touch bin/dashboard/api-router.js
|
|
91
|
+
touch bin/dashboard/approval-handler.js
|
|
92
|
+
touch bin/dashboard/team-tracker.js
|
|
93
|
+
touch bin/dashboard/metrics-aggregator.js
|
|
94
|
+
|
|
95
|
+
# Dashboard frontend (single-file HTML, no build step)
|
|
96
|
+
mkdir -p bin/dashboard/frontend
|
|
97
|
+
touch bin/dashboard/frontend/index.html
|
|
98
|
+
|
|
99
|
+
# Dashboard specs
|
|
100
|
+
mkdir -p .mindforge/dashboard
|
|
101
|
+
touch .mindforge/dashboard/dashboard-spec.md
|
|
102
|
+
touch .mindforge/dashboard/api-reference.md
|
|
103
|
+
|
|
104
|
+
# New command
|
|
105
|
+
touch .claude/commands/mindforge/dashboard.md
|
|
106
|
+
cp .claude/commands/mindforge/dashboard.md .agent/mindforge/dashboard.md
|
|
107
|
+
|
|
108
|
+
# Test suite
|
|
109
|
+
touch tests/dashboard.test.js
|
|
110
|
+
|
|
111
|
+
# PID file location
|
|
112
|
+
# .planning/dashboard-server.pid — created at runtime, gitignored
|
|
113
|
+
cat >> .gitignore << 'EOF'
|
|
114
|
+
|
|
115
|
+
# MindForge v2 — dashboard runtime files
|
|
116
|
+
.planning/dashboard-server.pid
|
|
117
|
+
EOF
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
**Add Express.js dependency:**
|
|
121
|
+
```bash
|
|
122
|
+
node -e "
|
|
123
|
+
const fs = require('fs');
|
|
124
|
+
const p = JSON.parse(fs.readFileSync('package.json','utf8'));
|
|
125
|
+
p.dependencies = p.dependencies || {};
|
|
126
|
+
p.dependencies['express'] = '^4.19.2';
|
|
127
|
+
fs.writeFileSync('package.json', JSON.stringify(p, null, 2) + '\n');
|
|
128
|
+
console.log('Added express dependency');
|
|
129
|
+
"
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Commit:**
|
|
133
|
+
```bash
|
|
134
|
+
git add .
|
|
135
|
+
git commit -m "chore(v2-day12): scaffold real-time dashboard directory structure"
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## TASK 2 — Write the Dashboard Specification
|
|
141
|
+
|
|
142
|
+
### `.mindforge/dashboard/dashboard-spec.md`
|
|
143
|
+
|
|
144
|
+
```markdown
|
|
145
|
+
# MindForge v2 — Dashboard Specification
|
|
146
|
+
|
|
147
|
+
## Overview
|
|
148
|
+
|
|
149
|
+
The MindForge dashboard is a real-time web UI that reflects the live state of
|
|
150
|
+
the MindForge agent and all project artifacts. It serves as the team's window
|
|
151
|
+
into what the agent is doing — designed for standup visibility, not agent control
|
|
152
|
+
(though it does support approval actions for Tier 2/3 governance).
|
|
153
|
+
|
|
154
|
+
## Architecture
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
Browser clients
|
|
158
|
+
↑ SSE (push) ↑ REST (pull on load)
|
|
159
|
+
│ │
|
|
160
|
+
bin/dashboard/server.js (Express.js, localhost:7339)
|
|
161
|
+
│ │
|
|
162
|
+
sse-bridge.js api-router.js
|
|
163
|
+
│ (tails files) │ (reads files)
|
|
164
|
+
│ │
|
|
165
|
+
AUDIT.jsonl HANDOFF.json
|
|
166
|
+
auto-state.json session-quality.jsonl
|
|
167
|
+
APPROVAL-*.json token-usage.jsonl
|
|
168
|
+
HANDOFF.json knowledge-base.jsonl
|
|
169
|
+
TEAM-STATE.jsonl .planning/phases/
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
## Port and binding
|
|
173
|
+
|
|
174
|
+
- **Port:** 7339 (Dashboard = 7337+2, after SSE at 7337 and Browser Daemon at 7338)
|
|
175
|
+
- **Binding:** 127.0.0.1 ONLY — never 0.0.0.0 (ADR-017 policy)
|
|
176
|
+
- **Protocol:** HTTP/1.1 with SSE for live events
|
|
177
|
+
- **Process lifecycle:**
|
|
178
|
+
- Started by: `/mindforge:dashboard` command or `bin/dashboard/server.js`
|
|
179
|
+
- Stopped by: `CTRL+C`, `/mindforge:dashboard --stop`, or SIGTERM
|
|
180
|
+
- Auto-stop: when MindForge session ends (watches for HANDOFF.json inactivity)
|
|
181
|
+
|
|
182
|
+
## Dashboard pages (5 tabs)
|
|
183
|
+
|
|
184
|
+
### Page 1 — Live Activity (default view)
|
|
185
|
+
Shows real-time execution state:
|
|
186
|
+
- Project name and current phase description
|
|
187
|
+
- Auto mode status indicator (RUNNING/PAUSED/ESCALATED/IDLE)
|
|
188
|
+
- Wave progress bar (current wave / total waves)
|
|
189
|
+
- Current task name and elapsed time
|
|
190
|
+
- Live AUDIT event feed (last 50 events, newest at top)
|
|
191
|
+
- Steering input box (sends to steering queue if auto mode active)
|
|
192
|
+
|
|
193
|
+
### Page 2 — Quality Metrics
|
|
194
|
+
Charts powered by session-quality.jsonl and token-usage.jsonl:
|
|
195
|
+
- Session quality score trend (last 20 sessions, sparkline)
|
|
196
|
+
- Verify pass rate over time (last 20 sessions)
|
|
197
|
+
- Security findings by severity (bar chart: CRITICAL/HIGH/MEDIUM/LOW)
|
|
198
|
+
- Token cost per session trend
|
|
199
|
+
- Node repair frequency (auto mode health signal)
|
|
200
|
+
|
|
201
|
+
### Page 3 — Pending Approvals
|
|
202
|
+
Governance queue visible to the whole team:
|
|
203
|
+
- Shows all APPROVAL-*.json files with status: pending
|
|
204
|
+
- For each: tier, phase/plan, change description, time since requested, expiry
|
|
205
|
+
- [Approve] and [Reject] buttons that call POST /api/approve/:id
|
|
206
|
+
- Expired approvals shown separately with red indicator
|
|
207
|
+
|
|
208
|
+
### Page 4 — Knowledge Graph
|
|
209
|
+
Visual representation of the knowledge base:
|
|
210
|
+
- Force-directed graph: nodes = knowledge entries, edges = ADR links
|
|
211
|
+
- Color by type: blue=decision, green=preference, red=bug_pattern, yellow=domain
|
|
212
|
+
- Node size = confidence × 30px radius
|
|
213
|
+
- Click node → side panel with full content
|
|
214
|
+
- Filter by type, tags, or confidence threshold
|
|
215
|
+
|
|
216
|
+
### Page 5 — Team Activity
|
|
217
|
+
Multi-developer visibility:
|
|
218
|
+
- Active developers (git email → last AUDIT event timestamp)
|
|
219
|
+
- What each developer is working on (last task from AUDIT.jsonl)
|
|
220
|
+
- File conflict detection (two developers recently touching the same file)
|
|
221
|
+
- Session history (list of sessions, quality scores, costs)
|
|
222
|
+
|
|
223
|
+
## Security model
|
|
224
|
+
|
|
225
|
+
1. Localhost-only binding (127.0.0.1) — consistent with all MindForge servers
|
|
226
|
+
2. No authentication — local dev tool, not exposed to network
|
|
227
|
+
3. CORS policy: only allow requests from localhost
|
|
228
|
+
4. Approval actions (POST /api/approve/:id) require the approval file to exist
|
|
229
|
+
in `.planning/approvals/` — cannot fabricate approvals from browser
|
|
230
|
+
5. Steering (POST /api/steer) only works when auto-state.json shows `status: running`
|
|
231
|
+
6. All file reads are restricted to `.planning/` and `.mindforge/` directories —
|
|
232
|
+
no arbitrary filesystem access from the API
|
|
233
|
+
|
|
234
|
+
## SCREENSHARE MODE (recommended team use)
|
|
235
|
+
The dashboard is designed to be screenshared during standups:
|
|
236
|
+
- Product managers see live progress without CLI access
|
|
237
|
+
- Designers see when their UI implementation tasks are running
|
|
238
|
+
- Stakeholders see real estimates of completion time
|
|
239
|
+
- On-call engineers see security findings as they're detected
|
|
240
|
+
Never expose the dashboard URL externally — it contains project details.
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### `.mindforge/dashboard/api-reference.md`
|
|
244
|
+
|
|
245
|
+
```markdown
|
|
246
|
+
# MindForge Dashboard API Reference
|
|
247
|
+
|
|
248
|
+
## Base URL: http://localhost:7339
|
|
249
|
+
|
|
250
|
+
## GET /api/status
|
|
251
|
+
Current execution state.
|
|
252
|
+
```json
|
|
253
|
+
{
|
|
254
|
+
"project_name": "saas-app",
|
|
255
|
+
"phase": 3,
|
|
256
|
+
"phase_description": "Authentication System",
|
|
257
|
+
"auto_mode": true,
|
|
258
|
+
"auto_status": "running|paused|escalated|idle",
|
|
259
|
+
"current_task": "Plan 3-05 — JWT middleware",
|
|
260
|
+
"wave_current": 2,
|
|
261
|
+
"wave_total": 3,
|
|
262
|
+
"tasks_completed": 5,
|
|
263
|
+
"tasks_total": 8,
|
|
264
|
+
"elapsed_ms": 1083000,
|
|
265
|
+
"node_repairs": 1,
|
|
266
|
+
"escalations": 0,
|
|
267
|
+
"last_commit": "abc1234",
|
|
268
|
+
"last_event_at": "ISO-8601"
|
|
269
|
+
}
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
## GET /api/audit?limit=50&offset=0&event=task_completed
|
|
273
|
+
Recent AUDIT.jsonl entries (newest first).
|
|
274
|
+
Query params: limit (default 50, max 200), offset, event (filter by event type)
|
|
275
|
+
|
|
276
|
+
## GET /api/metrics
|
|
277
|
+
Session quality and cost summary.
|
|
278
|
+
```json
|
|
279
|
+
{
|
|
280
|
+
"sessions": [{ "id": "...", "quality_score": 0.87, "cost_usd": 0.42, "verify_pass_rate": 0.95 }],
|
|
281
|
+
"avg_quality": 0.85,
|
|
282
|
+
"avg_cost_usd": 0.38,
|
|
283
|
+
"security_findings": { "CRITICAL": 0, "HIGH": 2, "MEDIUM": 5, "LOW": 8 },
|
|
284
|
+
"node_repair_rate": 0.08
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
## GET /api/approvals
|
|
289
|
+
Pending governance approvals.
|
|
290
|
+
```json
|
|
291
|
+
{
|
|
292
|
+
"pending": [
|
|
293
|
+
{
|
|
294
|
+
"id": "uuid",
|
|
295
|
+
"tier": 2,
|
|
296
|
+
"phase": 3,
|
|
297
|
+
"plan": "04",
|
|
298
|
+
"description": "Add user RBAC model",
|
|
299
|
+
"requested_at": "ISO-8601",
|
|
300
|
+
"expires_at": "ISO-8601",
|
|
301
|
+
"hours_remaining": 21.3,
|
|
302
|
+
"diff_preview": "src/models/user.ts +48 lines..."
|
|
303
|
+
}
|
|
304
|
+
],
|
|
305
|
+
"approved": [...],
|
|
306
|
+
"rejected": [...],
|
|
307
|
+
"expired": [...]
|
|
308
|
+
}
|
|
309
|
+
```
|
|
310
|
+
|
|
311
|
+
## POST /api/approve/:id
|
|
312
|
+
```json
|
|
313
|
+
Request: { "decision": "approve|reject", "comment": "optional", "approver": "email" }
|
|
314
|
+
Response: { "success": true, "decision": "approve", "approval_id": "uuid" }
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
## GET /api/team
|
|
318
|
+
Active developer activity.
|
|
319
|
+
```json
|
|
320
|
+
{
|
|
321
|
+
"active": [
|
|
322
|
+
{ "email": "john@company.com", "current_task": "Plan 3-05", "last_seen_mins": 0 }
|
|
323
|
+
],
|
|
324
|
+
"conflicts": [
|
|
325
|
+
{ "file": "src/auth/session.ts", "developers": ["john@...", "jane@..."] }
|
|
326
|
+
]
|
|
327
|
+
}
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
## GET /api/memory?q=jwt&limit=10
|
|
331
|
+
Knowledge base query.
|
|
332
|
+
```json
|
|
333
|
+
{
|
|
334
|
+
"entries": [
|
|
335
|
+
{ "id": "kb-uuid", "type": "architectural_decision", "topic": "JWT tokens", "confidence": 0.90 }
|
|
336
|
+
],
|
|
337
|
+
"total": 47
|
|
338
|
+
}
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
## GET /api/costs?window=7d
|
|
342
|
+
Cost summary from token-usage.jsonl.
|
|
343
|
+
```json
|
|
344
|
+
{
|
|
345
|
+
"total_usd": 3.84,
|
|
346
|
+
"today_usd": 1.21,
|
|
347
|
+
"by_model": { "claude-sonnet-4-6": 1.92, "gpt-4o": 0.98 },
|
|
348
|
+
"daily_limit_usd": 10.00,
|
|
349
|
+
"limit_used_pct": 12.1
|
|
350
|
+
}
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
## POST /api/steer
|
|
354
|
+
Inject steering guidance (requires auto mode running).
|
|
355
|
+
```json
|
|
356
|
+
Request: { "instruction": "Use Redis for session storage", "priority": "normal" }
|
|
357
|
+
Response: { "success": true, "queued": true }
|
|
358
|
+
```
|
|
359
|
+
|
|
360
|
+
## GET /events
|
|
361
|
+
SSE stream. Events emitted:
|
|
362
|
+
- `audit:new` — new AUDIT.jsonl entry
|
|
363
|
+
- `status:update` — auto-state.json changed
|
|
364
|
+
- `approval:new` — new approval request
|
|
365
|
+
- `approval:resolved` — approval approved or rejected
|
|
366
|
+
- `conflict:detected` — two developers touching same file
|
|
367
|
+
- `ping` — keepalive every 15 seconds
|
|
368
|
+
```
|
|
369
|
+
|
|
370
|
+
**Commit:**
|
|
371
|
+
```bash
|
|
372
|
+
git add .mindforge/dashboard/
|
|
373
|
+
git commit -m "feat(v2-dashboard): write dashboard specification and API reference"
|
|
374
|
+
```
|
|
375
|
+
|
|
376
|
+
---
|
|
377
|
+
|
|
378
|
+
## TASK 3 — Implement the SSE Bridge
|
|
379
|
+
|
|
380
|
+
### `bin/dashboard/sse-bridge.js`
|
|
381
|
+
|
|
382
|
+
```javascript
|
|
383
|
+
/**
|
|
384
|
+
* MindForge v2 — SSE Event Bridge
|
|
385
|
+
* Tails AUDIT.jsonl and auto-state.json and pushes changes
|
|
386
|
+
* to all connected SSE clients.
|
|
387
|
+
*
|
|
388
|
+
* Design:
|
|
389
|
+
* - Uses fs.watchFile() for cross-platform file watching (not fs.watch)
|
|
390
|
+
* - Each connected client gets a Response object stored in a Set
|
|
391
|
+
* - Events are broadcast to ALL connected clients on every file change
|
|
392
|
+
* - Keepalive ping every 15 seconds to detect disconnected clients
|
|
393
|
+
*/
|
|
394
|
+
'use strict';
|
|
395
|
+
|
|
396
|
+
const fs = require('fs');
|
|
397
|
+
const path = require('path');
|
|
398
|
+
|
|
399
|
+
const AUDIT_PATH = path.join(process.cwd(), '.planning', 'AUDIT.jsonl');
|
|
400
|
+
const AUTO_STATE_PATH = path.join(process.cwd(), '.planning', 'auto-state.json');
|
|
401
|
+
const APPROVAL_DIR = path.join(process.cwd(), '.planning', 'approvals');
|
|
402
|
+
|
|
403
|
+
const clients = new Set(); // Connected SSE response objects
|
|
404
|
+
|
|
405
|
+
let _lastAuditSize = 0;
|
|
406
|
+
let _lastAutoState = '';
|
|
407
|
+
let _lastApprovals = '';
|
|
408
|
+
|
|
409
|
+
// ── Client management ─────────────────────────────────────────────────────────
|
|
410
|
+
|
|
411
|
+
function addClient(res) {
|
|
412
|
+
clients.add(res);
|
|
413
|
+
res.on('close', () => {
|
|
414
|
+
clients.delete(res);
|
|
415
|
+
});
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
function broadcast(eventName, data) {
|
|
419
|
+
const message = `event: ${eventName}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
420
|
+
for (const res of clients) {
|
|
421
|
+
try {
|
|
422
|
+
res.write(message);
|
|
423
|
+
} catch {
|
|
424
|
+
clients.delete(res);
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
function broadcastRaw(message) {
|
|
430
|
+
for (const res of clients) {
|
|
431
|
+
try {
|
|
432
|
+
res.write(message);
|
|
433
|
+
} catch {
|
|
434
|
+
clients.delete(res);
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// ── File tail: AUDIT.jsonl ────────────────────────────────────────────────────
|
|
440
|
+
|
|
441
|
+
function pollAuditLog() {
|
|
442
|
+
if (!fs.existsSync(AUDIT_PATH)) return;
|
|
443
|
+
|
|
444
|
+
try {
|
|
445
|
+
const stat = fs.statSync(AUDIT_PATH);
|
|
446
|
+
const newSize = stat.size;
|
|
447
|
+
|
|
448
|
+
if (newSize <= _lastAuditSize) return; // No new data
|
|
449
|
+
|
|
450
|
+
// Read only the new bytes appended since last poll
|
|
451
|
+
const fd = fs.openSync(AUDIT_PATH, 'r');
|
|
452
|
+
const chunk = Buffer.alloc(newSize - _lastAuditSize);
|
|
453
|
+
fs.readSync(fd, chunk, 0, chunk.length, _lastAuditSize);
|
|
454
|
+
fs.closeSync(fd);
|
|
455
|
+
_lastAuditSize = newSize;
|
|
456
|
+
|
|
457
|
+
// Parse new lines
|
|
458
|
+
const newLines = chunk.toString().split('\n').filter(Boolean);
|
|
459
|
+
for (const line of newLines) {
|
|
460
|
+
try {
|
|
461
|
+
const entry = JSON.parse(line);
|
|
462
|
+
broadcast('audit:new', entry);
|
|
463
|
+
} catch { /* skip malformed */ }
|
|
464
|
+
}
|
|
465
|
+
} catch { /* ignore read errors — file may be locked */ }
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
// ── File poll: auto-state.json ────────────────────────────────────────────────
|
|
469
|
+
|
|
470
|
+
function pollAutoState() {
|
|
471
|
+
if (!fs.existsSync(AUTO_STATE_PATH)) return;
|
|
472
|
+
|
|
473
|
+
try {
|
|
474
|
+
const raw = fs.readFileSync(AUTO_STATE_PATH, 'utf8');
|
|
475
|
+
if (raw === _lastAutoState) return;
|
|
476
|
+
_lastAutoState = raw;
|
|
477
|
+
const state = JSON.parse(raw);
|
|
478
|
+
broadcast('status:update', state);
|
|
479
|
+
} catch { /* ignore */ }
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ── File poll: approval directory ─────────────────────────────────────────────
|
|
483
|
+
|
|
484
|
+
function pollApprovals() {
|
|
485
|
+
if (!fs.existsSync(APPROVAL_DIR)) return;
|
|
486
|
+
|
|
487
|
+
try {
|
|
488
|
+
const files = fs.readdirSync(APPROVAL_DIR)
|
|
489
|
+
.filter(f => f.startsWith('APPROVAL-') && f.endsWith('.json'))
|
|
490
|
+
.sort();
|
|
491
|
+
const key = files.join(',');
|
|
492
|
+
if (key === _lastApprovals) return;
|
|
493
|
+
_lastApprovals = key;
|
|
494
|
+
|
|
495
|
+
// Find new pending approvals
|
|
496
|
+
for (const f of files) {
|
|
497
|
+
try {
|
|
498
|
+
const data = JSON.parse(fs.readFileSync(path.join(APPROVAL_DIR, f), 'utf8'));
|
|
499
|
+
if (data.status === 'pending') {
|
|
500
|
+
broadcast('approval:new', data);
|
|
501
|
+
}
|
|
502
|
+
} catch { /* skip */ }
|
|
503
|
+
}
|
|
504
|
+
} catch { /* ignore */ }
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
// ── Keepalive ─────────────────────────────────────────────────────────────────
|
|
508
|
+
|
|
509
|
+
let _pollInterval = null;
|
|
510
|
+
let _pingInterval = null;
|
|
511
|
+
|
|
512
|
+
function start() {
|
|
513
|
+
// Initialize AUDIT position
|
|
514
|
+
if (fs.existsSync(AUDIT_PATH)) {
|
|
515
|
+
_lastAuditSize = fs.statSync(AUDIT_PATH).size;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
// Poll every 2 seconds
|
|
519
|
+
_pollInterval = setInterval(() => {
|
|
520
|
+
pollAuditLog();
|
|
521
|
+
pollAutoState();
|
|
522
|
+
pollApprovals();
|
|
523
|
+
}, 2000);
|
|
524
|
+
|
|
525
|
+
// Keepalive ping every 15 seconds
|
|
526
|
+
_pingInterval = setInterval(() => {
|
|
527
|
+
broadcastRaw(`: ping\n\n`);
|
|
528
|
+
}, 15_000);
|
|
529
|
+
|
|
530
|
+
_pollInterval.unref();
|
|
531
|
+
_pingInterval.unref();
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
function stop() {
|
|
535
|
+
if (_pollInterval) { clearInterval(_pollInterval); _pollInterval = null; }
|
|
536
|
+
if (_pingInterval) { clearInterval(_pingInterval); _pingInterval = null; }
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
function getClientCount() { return clients.size; }
|
|
540
|
+
|
|
541
|
+
module.exports = { addClient, broadcast, start, stop, getClientCount };
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
**Commit:**
|
|
545
|
+
```bash
|
|
546
|
+
git add bin/dashboard/sse-bridge.js
|
|
547
|
+
git commit -m "feat(v2-dashboard): implement SSE event bridge with AUDIT/state/approval tailing"
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
## TASK 4 — Implement the Metrics Aggregator
|
|
553
|
+
|
|
554
|
+
### `bin/dashboard/metrics-aggregator.js`
|
|
555
|
+
|
|
556
|
+
```javascript
|
|
557
|
+
/**
|
|
558
|
+
* MindForge v2 — Metrics Aggregator
|
|
559
|
+
* Reads .mindforge/metrics/ and .planning/ files and produces
|
|
560
|
+
* structured metrics for the dashboard API.
|
|
561
|
+
*/
|
|
562
|
+
'use strict';
|
|
563
|
+
|
|
564
|
+
const fs = require('fs');
|
|
565
|
+
const path = require('path');
|
|
566
|
+
|
|
567
|
+
const QUALITY_LOG = path.join(process.cwd(), '.mindforge', 'metrics', 'session-quality.jsonl');
|
|
568
|
+
const USAGE_LOG = path.join(process.cwd(), '.mindforge', 'metrics', 'token-usage.jsonl');
|
|
569
|
+
const AUDIT_PATH = path.join(process.cwd(), '.planning', 'AUDIT.jsonl');
|
|
570
|
+
const HANDOFF_PATH = path.join(process.cwd(), '.planning', 'HANDOFF.json');
|
|
571
|
+
const AUTO_STATE = path.join(process.cwd(), '.planning', 'auto-state.json');
|
|
572
|
+
const APPROVAL_DIR = path.join(process.cwd(), '.planning', 'approvals');
|
|
573
|
+
const TEAM_STATE = path.join(process.cwd(), '.planning', 'TEAM-STATE.jsonl');
|
|
574
|
+
const KB_PATH = path.join(process.cwd(), '.mindforge', 'memory', 'knowledge-base.jsonl');
|
|
575
|
+
|
|
576
|
+
function readJSONL(filePath, limit = 500) {
|
|
577
|
+
if (!fs.existsSync(filePath)) return [];
|
|
578
|
+
return fs.readFileSync(filePath, 'utf8')
|
|
579
|
+
.split('\n').filter(Boolean).slice(-limit)
|
|
580
|
+
.map(l => { try { return JSON.parse(l); } catch { return null; } })
|
|
581
|
+
.filter(Boolean);
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
function readJSON(filePath) {
|
|
585
|
+
if (!fs.existsSync(filePath)) return null;
|
|
586
|
+
try { return JSON.parse(fs.readFileSync(filePath, 'utf8')); } catch { return null; }
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
// ── Status ────────────────────────────────────────────────────────────────────
|
|
590
|
+
function getStatus() {
|
|
591
|
+
const handoff = readJSON(HANDOFF_PATH);
|
|
592
|
+
const autoState = readJSON(AUTO_STATE);
|
|
593
|
+
|
|
594
|
+
// Read project name from PROJECT.md
|
|
595
|
+
let projectName = 'MindForge Project';
|
|
596
|
+
const projectMd = path.join(process.cwd(), '.planning', 'PROJECT.md');
|
|
597
|
+
if (fs.existsSync(projectMd)) {
|
|
598
|
+
const m = fs.readFileSync(projectMd, 'utf8').match(/^# (.+)/m);
|
|
599
|
+
if (m) projectName = m[1].trim();
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
return {
|
|
603
|
+
project_name: projectName,
|
|
604
|
+
phase: handoff?.current_phase ?? null,
|
|
605
|
+
phase_description: handoff?.phase_description ?? null,
|
|
606
|
+
auto_mode: autoState?.auto_mode_active ?? false,
|
|
607
|
+
auto_status: autoState?.status ?? 'idle',
|
|
608
|
+
current_task: autoState?.current_task ?? handoff?.next_task ?? null,
|
|
609
|
+
wave_current: autoState?.wave_current ?? null,
|
|
610
|
+
wave_total: autoState?.wave_total ?? null,
|
|
611
|
+
tasks_completed: autoState?.tasks_completed ?? null,
|
|
612
|
+
tasks_total: autoState?.tasks_total ?? null,
|
|
613
|
+
elapsed_ms: autoState?.elapsed_ms ?? null,
|
|
614
|
+
node_repairs: autoState?.node_repairs ?? 0,
|
|
615
|
+
escalations: autoState?.escalations ?? 0,
|
|
616
|
+
last_commit: autoState?.last_commit ?? null,
|
|
617
|
+
last_event_at: handoff?.last_updated ?? null,
|
|
618
|
+
schema_version: handoff?.schema_version ?? null,
|
|
619
|
+
};
|
|
620
|
+
}
|
|
621
|
+
|
|
622
|
+
// ── Audit ─────────────────────────────────────────────────────────────────────
|
|
623
|
+
function getAuditEntries(limit = 50, offset = 0, eventFilter = null) {
|
|
624
|
+
const all = readJSONL(AUDIT_PATH, 1000);
|
|
625
|
+
const reversed = all.reverse(); // Newest first
|
|
626
|
+
|
|
627
|
+
const filtered = eventFilter
|
|
628
|
+
? reversed.filter(e => e.event === eventFilter)
|
|
629
|
+
: reversed;
|
|
630
|
+
|
|
631
|
+
return {
|
|
632
|
+
entries: filtered.slice(offset, offset + limit),
|
|
633
|
+
total: filtered.length,
|
|
634
|
+
limit,
|
|
635
|
+
offset,
|
|
636
|
+
};
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
// ── Metrics ───────────────────────────────────────────────────────────────────
|
|
640
|
+
function getMetrics() {
|
|
641
|
+
const qualityEntries = readJSONL(QUALITY_LOG, 20);
|
|
642
|
+
const usageEntries = readJSONL(USAGE_LOG, 200);
|
|
643
|
+
const auditEntries = readJSONL(AUDIT_PATH, 500);
|
|
644
|
+
|
|
645
|
+
// Quality scores (last 20 sessions)
|
|
646
|
+
const sessions = qualityEntries.map(e => ({
|
|
647
|
+
id: e.session_id,
|
|
648
|
+
timestamp: e.timestamp,
|
|
649
|
+
quality_score: e.quality_score ?? 0,
|
|
650
|
+
verify_pass_rate: e.verify_pass_rate ?? 0,
|
|
651
|
+
cost_usd: e.total_cost_usd ?? 0,
|
|
652
|
+
node_repairs: e.node_repairs ?? 0,
|
|
653
|
+
}));
|
|
654
|
+
|
|
655
|
+
const avg_quality = sessions.length
|
|
656
|
+
? sessions.reduce((s, e) => s + e.quality_score, 0) / sessions.length : 0;
|
|
657
|
+
const avg_cost_usd = sessions.length
|
|
658
|
+
? sessions.reduce((s, e) => s + e.cost_usd, 0) / sessions.length : 0;
|
|
659
|
+
|
|
660
|
+
// Security findings from AUDIT
|
|
661
|
+
const securityFindings = { CRITICAL: 0, HIGH: 0, MEDIUM: 0, LOW: 0 };
|
|
662
|
+
auditEntries
|
|
663
|
+
.filter(e => e.event === 'security_finding')
|
|
664
|
+
.forEach(e => {
|
|
665
|
+
const sev = e.severity || 'LOW';
|
|
666
|
+
securityFindings[sev] = (securityFindings[sev] || 0) + 1;
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
// Node repair rate
|
|
670
|
+
const taskEvents = auditEntries.filter(e => e.event === 'task_completed' || e.event === 'task_failed');
|
|
671
|
+
const repairEvents = auditEntries.filter(e => e.event === 'node_repair');
|
|
672
|
+
const node_repair_rate = taskEvents.length
|
|
673
|
+
? repairEvents.length / taskEvents.length : 0;
|
|
674
|
+
|
|
675
|
+
return {
|
|
676
|
+
sessions,
|
|
677
|
+
avg_quality: Math.round(avg_quality * 100) / 100,
|
|
678
|
+
avg_cost_usd: Math.round(avg_cost_usd * 10000) / 10000,
|
|
679
|
+
security_findings: securityFindings,
|
|
680
|
+
node_repair_rate: Math.round(node_repair_rate * 100) / 100,
|
|
681
|
+
total_tasks: taskEvents.filter(e => e.event === 'task_completed').length,
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
// ── Approvals ─────────────────────────────────────────────────────────────────
|
|
686
|
+
function getApprovals() {
|
|
687
|
+
if (!fs.existsSync(APPROVAL_DIR)) return { pending: [], approved: [], rejected: [], expired: [] };
|
|
688
|
+
|
|
689
|
+
const now = Date.now();
|
|
690
|
+
const files = fs.readdirSync(APPROVAL_DIR)
|
|
691
|
+
.filter(f => f.startsWith('APPROVAL-') && f.endsWith('.json'))
|
|
692
|
+
.sort();
|
|
693
|
+
|
|
694
|
+
const pending = [];
|
|
695
|
+
const approved = [];
|
|
696
|
+
const rejected = [];
|
|
697
|
+
const expired = [];
|
|
698
|
+
|
|
699
|
+
for (const f of files) {
|
|
700
|
+
try {
|
|
701
|
+
const data = JSON.parse(fs.readFileSync(path.join(APPROVAL_DIR, f), 'utf8'));
|
|
702
|
+
const expiry = data.expires_at ? new Date(data.expires_at).getTime() : Infinity;
|
|
703
|
+
const hoursRemaining = expiry === Infinity ? null : (expiry - now) / 3_600_000;
|
|
704
|
+
|
|
705
|
+
const enriched = { ...data, hours_remaining: hoursRemaining };
|
|
706
|
+
|
|
707
|
+
if (data.status === 'approved') approved.push(enriched);
|
|
708
|
+
else if (data.status === 'rejected') rejected.push(enriched);
|
|
709
|
+
else if (expiry < now) expired.push({ ...enriched, status: 'expired' });
|
|
710
|
+
else pending.push(enriched);
|
|
711
|
+
} catch { /* skip corrupt files */ }
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
return { pending, approved, rejected, expired };
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// ── Team activity ─────────────────────────────────────────────────────────────
|
|
718
|
+
function getTeamActivity() {
|
|
719
|
+
const auditEntries = readJSONL(AUDIT_PATH, 200);
|
|
720
|
+
|
|
721
|
+
// Group by author (git email from session_id or authored_by field)
|
|
722
|
+
const byAuthor = {};
|
|
723
|
+
for (const entry of auditEntries) {
|
|
724
|
+
const author = entry.authored_by || entry.session_id || 'unknown';
|
|
725
|
+
if (!byAuthor[author] || entry.timestamp > byAuthor[author].last_seen) {
|
|
726
|
+
byAuthor[author] = {
|
|
727
|
+
email: author,
|
|
728
|
+
last_seen: entry.timestamp,
|
|
729
|
+
current_task: entry.plan ? `Plan ${entry.phase}-${entry.plan}` : null,
|
|
730
|
+
event: entry.event,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
|
|
735
|
+
const now = Date.now();
|
|
736
|
+
const active = Object.values(byAuthor)
|
|
737
|
+
.map(a => ({
|
|
738
|
+
...a,
|
|
739
|
+
last_seen_mins: Math.round((now - new Date(a.last_seen).getTime()) / 60_000),
|
|
740
|
+
}))
|
|
741
|
+
.filter(a => a.last_seen_mins < 120) // Active in last 2 hours
|
|
742
|
+
.sort((a, b) => a.last_seen_mins - b.last_seen_mins);
|
|
743
|
+
|
|
744
|
+
// Conflict detection — two authors recently touching same file
|
|
745
|
+
const conflicts = detectFileConflicts(auditEntries);
|
|
746
|
+
|
|
747
|
+
return { active, conflicts };
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function detectFileConflicts(auditEntries) {
|
|
751
|
+
const fileToAuthors = {};
|
|
752
|
+
|
|
753
|
+
for (const entry of auditEntries.slice(-100)) {
|
|
754
|
+
if (!entry.files_modified) continue;
|
|
755
|
+
const author = entry.authored_by || entry.session_id;
|
|
756
|
+
if (!author) continue;
|
|
757
|
+
|
|
758
|
+
const files = Array.isArray(entry.files_modified) ? entry.files_modified : [entry.files_modified];
|
|
759
|
+
for (const f of files) {
|
|
760
|
+
if (!fileToAuthors[f]) fileToAuthors[f] = new Set();
|
|
761
|
+
fileToAuthors[f].add(author);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
return Object.entries(fileToAuthors)
|
|
766
|
+
.filter(([, authors]) => authors.size > 1)
|
|
767
|
+
.map(([file, authors]) => ({ file, developers: [...authors] }));
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// ── Memory ────────────────────────────────────────────────────────────────────
|
|
771
|
+
function getMemory(query = '', limit = 20) {
|
|
772
|
+
if (!fs.existsSync(KB_PATH)) return { entries: [], total: 0 };
|
|
773
|
+
|
|
774
|
+
const lines = fs.readFileSync(KB_PATH, 'utf8').split('\n').filter(Boolean);
|
|
775
|
+
const byId = new Map();
|
|
776
|
+
for (const l of lines) {
|
|
777
|
+
try { const e = JSON.parse(l); byId.set(e.id, e); } catch { /* skip */ }
|
|
778
|
+
}
|
|
779
|
+
|
|
780
|
+
let entries = [...byId.values()].filter(e => !e.deprecated);
|
|
781
|
+
|
|
782
|
+
if (query) {
|
|
783
|
+
const q = query.toLowerCase();
|
|
784
|
+
entries = entries.filter(e =>
|
|
785
|
+
e.topic.toLowerCase().includes(q) ||
|
|
786
|
+
e.content.toLowerCase().includes(q) ||
|
|
787
|
+
(e.tags || []).some(t => t.toLowerCase().includes(q))
|
|
788
|
+
);
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
entries.sort((a, b) => b.confidence - a.confidence);
|
|
792
|
+
|
|
793
|
+
return { entries: entries.slice(0, limit), total: entries.length };
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
// ── Costs ─────────────────────────────────────────────────────────────────────
|
|
797
|
+
function getCosts(windowDays = 7) {
|
|
798
|
+
const entries = readJSONL(USAGE_LOG, 1000);
|
|
799
|
+
const cutoff = new Date(Date.now() - windowDays * 86_400_000).toISOString().slice(0, 10);
|
|
800
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
801
|
+
|
|
802
|
+
const filtered = entries.filter(e => e.date >= cutoff);
|
|
803
|
+
const todayItems = entries.filter(e => e.date === today);
|
|
804
|
+
|
|
805
|
+
const total_usd = filtered.reduce((s, e) => s + (e.cost_usd || 0), 0);
|
|
806
|
+
const today_usd = todayItems.reduce((s, e) => s + (e.cost_usd || 0), 0);
|
|
807
|
+
|
|
808
|
+
const by_model = {};
|
|
809
|
+
for (const e of filtered) {
|
|
810
|
+
const m = e.model || 'unknown';
|
|
811
|
+
by_model[m] = (by_model[m] || 0) + (e.cost_usd || 0);
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
// Read daily limit from MINDFORGE.md
|
|
815
|
+
let daily_limit = 0;
|
|
816
|
+
const mmPath = path.join(process.cwd(), 'MINDFORGE.md');
|
|
817
|
+
if (fs.existsSync(mmPath)) {
|
|
818
|
+
const match = fs.readFileSync(mmPath, 'utf8').match(/^MODEL_COST_HARD_LIMIT_USD=(.+)$/m);
|
|
819
|
+
if (match) daily_limit = parseFloat(match[1]);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
return {
|
|
823
|
+
total_usd: Math.round(total_usd * 10000) / 10000,
|
|
824
|
+
today_usd: Math.round(today_usd * 10000) / 10000,
|
|
825
|
+
by_model,
|
|
826
|
+
window_days: windowDays,
|
|
827
|
+
daily_limit_usd: daily_limit,
|
|
828
|
+
limit_used_pct: daily_limit > 0 ? Math.round((today_usd / daily_limit) * 100) : null,
|
|
829
|
+
};
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
module.exports = { getStatus, getAuditEntries, getMetrics, getApprovals, getTeamActivity, getMemory, getCosts };
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
**Commit:**
|
|
836
|
+
```bash
|
|
837
|
+
git add bin/dashboard/metrics-aggregator.js
|
|
838
|
+
git commit -m "feat(v2-dashboard): implement metrics aggregator for all 7 API data sources"
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
---
|
|
842
|
+
|
|
843
|
+
## TASK 5 — Implement the Approval Handler
|
|
844
|
+
|
|
845
|
+
### `bin/dashboard/approval-handler.js`
|
|
846
|
+
|
|
847
|
+
```javascript
|
|
848
|
+
/**
|
|
849
|
+
* MindForge v2 — Approval Handler
|
|
850
|
+
* Handles POST /api/approve/:id — approve or reject governance requests.
|
|
851
|
+
* Reads/writes APPROVAL-*.json files in .planning/approvals/
|
|
852
|
+
*/
|
|
853
|
+
'use strict';
|
|
854
|
+
|
|
855
|
+
const fs = require('fs');
|
|
856
|
+
const path = require('path');
|
|
857
|
+
|
|
858
|
+
const APPROVAL_DIR = path.join(process.cwd(), '.planning', 'approvals');
|
|
859
|
+
const AUDIT_PATH = path.join(process.cwd(), '.planning', 'AUDIT.jsonl');
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Process an approval decision from the dashboard.
|
|
863
|
+
* @param {string} approvalId - The APPROVAL UUID
|
|
864
|
+
* @param {string} decision - 'approve' or 'reject'
|
|
865
|
+
* @param {string} comment - Optional comment
|
|
866
|
+
* @param {string} approver - Approver identifier (email or name)
|
|
867
|
+
* @returns {{ success, decision, message }}
|
|
868
|
+
*/
|
|
869
|
+
function processDecision(approvalId, decision, comment, approver) {
|
|
870
|
+
// Input validation
|
|
871
|
+
if (!approvalId || typeof approvalId !== 'string') {
|
|
872
|
+
return { success: false, error: 'Invalid approval ID' };
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
// Sanitize approvalId — only allow UUID characters
|
|
876
|
+
if (!/^[a-f0-9-]{36}$/.test(approvalId)) {
|
|
877
|
+
return { success: false, error: 'Malformed approval ID format' };
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
if (!['approve', 'reject'].includes(decision)) {
|
|
881
|
+
return { success: false, error: 'Decision must be "approve" or "reject"' };
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Find the approval file
|
|
885
|
+
const filePath = path.join(APPROVAL_DIR, `APPROVAL-${approvalId}.json`);
|
|
886
|
+
if (!fs.existsSync(filePath)) {
|
|
887
|
+
return { success: false, error: `Approval not found: ${approvalId}` };
|
|
888
|
+
}
|
|
889
|
+
|
|
890
|
+
let approval;
|
|
891
|
+
try {
|
|
892
|
+
approval = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
893
|
+
} catch {
|
|
894
|
+
return { success: false, error: 'Cannot parse approval file' };
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// Validate approval is still pending
|
|
898
|
+
if (approval.status !== 'pending') {
|
|
899
|
+
return { success: false, error: `Approval already ${approval.status}` };
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
// Check expiry
|
|
903
|
+
if (approval.expires_at && new Date(approval.expires_at) < new Date()) {
|
|
904
|
+
return { success: false, error: 'Approval has expired' };
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
// Update approval file
|
|
908
|
+
const updated = {
|
|
909
|
+
...approval,
|
|
910
|
+
status: decision === 'approve' ? 'approved' : 'rejected',
|
|
911
|
+
resolved_at: new Date().toISOString(),
|
|
912
|
+
resolved_by: approver || 'dashboard',
|
|
913
|
+
comment: comment || null,
|
|
914
|
+
resolution_channel: 'mindforge-dashboard',
|
|
915
|
+
};
|
|
916
|
+
|
|
917
|
+
fs.writeFileSync(filePath, JSON.stringify(updated, null, 2));
|
|
918
|
+
|
|
919
|
+
// Write AUDIT entry
|
|
920
|
+
writeAuditEntry({
|
|
921
|
+
id: require('crypto').randomBytes(8).toString('hex'),
|
|
922
|
+
timestamp: new Date().toISOString(),
|
|
923
|
+
event: decision === 'approve' ? 'approval_granted' : 'approval_rejected',
|
|
924
|
+
approval_id: approvalId,
|
|
925
|
+
tier: approval.tier,
|
|
926
|
+
phase: approval.phase,
|
|
927
|
+
plan: approval.plan,
|
|
928
|
+
resolved_by: approver || 'dashboard',
|
|
929
|
+
comment: comment || null,
|
|
930
|
+
agent: 'mindforge-dashboard',
|
|
931
|
+
session_id: 'dashboard',
|
|
932
|
+
});
|
|
933
|
+
|
|
934
|
+
return {
|
|
935
|
+
success: true,
|
|
936
|
+
decision,
|
|
937
|
+
approval_id: approvalId,
|
|
938
|
+
tier: approval.tier,
|
|
939
|
+
message: `${approval.tier === 3 ? 'Tier 3' : 'Tier 2'} approval ${decision}d for Plan ${approval.phase}-${approval.plan}`,
|
|
940
|
+
};
|
|
941
|
+
}
|
|
942
|
+
|
|
943
|
+
function writeAuditEntry(entry) {
|
|
944
|
+
try {
|
|
945
|
+
if (!fs.existsSync(path.dirname(AUDIT_PATH))) return;
|
|
946
|
+
fs.appendFileSync(AUDIT_PATH, JSON.stringify(entry) + '\n');
|
|
947
|
+
} catch { /* ignore AUDIT write failures */ }
|
|
948
|
+
}
|
|
949
|
+
|
|
950
|
+
function listApprovals() {
|
|
951
|
+
if (!fs.existsSync(APPROVAL_DIR)) return [];
|
|
952
|
+
return fs.readdirSync(APPROVAL_DIR)
|
|
953
|
+
.filter(f => f.startsWith('APPROVAL-') && f.endsWith('.json'))
|
|
954
|
+
.map(f => {
|
|
955
|
+
try { return JSON.parse(fs.readFileSync(path.join(APPROVAL_DIR, f), 'utf8')); }
|
|
956
|
+
catch { return null; }
|
|
957
|
+
})
|
|
958
|
+
.filter(Boolean);
|
|
959
|
+
}
|
|
960
|
+
|
|
961
|
+
module.exports = { processDecision, listApprovals };
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
**Commit:**
|
|
965
|
+
```bash
|
|
966
|
+
git add bin/dashboard/approval-handler.js
|
|
967
|
+
git commit -m "feat(v2-dashboard): implement approval handler with UUID validation and AUDIT trail"
|
|
968
|
+
```
|
|
969
|
+
|
|
970
|
+
---
|
|
971
|
+
|
|
972
|
+
## TASK 6 — Implement the Dashboard API Router
|
|
973
|
+
|
|
974
|
+
### `bin/dashboard/api-router.js`
|
|
975
|
+
|
|
976
|
+
```javascript
|
|
977
|
+
/**
|
|
978
|
+
* MindForge v2 — Dashboard API Router
|
|
979
|
+
* All REST endpoints for the dashboard.
|
|
980
|
+
*/
|
|
981
|
+
'use strict';
|
|
982
|
+
|
|
983
|
+
const path = require('path');
|
|
984
|
+
const fs = require('fs');
|
|
985
|
+
const Metrics = require('./metrics-aggregator');
|
|
986
|
+
const Approval = require('./approval-handler');
|
|
987
|
+
const SSE = require('./sse-bridge');
|
|
988
|
+
|
|
989
|
+
// Steering queue path (from Day 8 auto-executor)
|
|
990
|
+
const STEERING_QUEUE = path.join(process.cwd(), '.planning', 'steering-queue.jsonl');
|
|
991
|
+
const AUTO_STATE_PATH = path.join(process.cwd(), '.planning', 'auto-state.json');
|
|
992
|
+
|
|
993
|
+
function register(app) {
|
|
994
|
+
|
|
995
|
+
// ── SSE stream ──────────────────────────────────────────────────────────────
|
|
996
|
+
app.get('/events', (req, res) => {
|
|
997
|
+
res.setHeader('Content-Type', 'text/event-stream');
|
|
998
|
+
res.setHeader('Cache-Control', 'no-cache');
|
|
999
|
+
res.setHeader('Connection', 'keep-alive');
|
|
1000
|
+
res.setHeader('X-Accel-Buffering', 'no'); // Disable Nginx buffering
|
|
1001
|
+
res.flushHeaders();
|
|
1002
|
+
|
|
1003
|
+
// Send current status immediately on connect
|
|
1004
|
+
try {
|
|
1005
|
+
const status = Metrics.getStatus();
|
|
1006
|
+
res.write(`event: status:update\ndata: ${JSON.stringify(status)}\n\n`);
|
|
1007
|
+
} catch { /* ignore */ }
|
|
1008
|
+
|
|
1009
|
+
SSE.addClient(res);
|
|
1010
|
+
});
|
|
1011
|
+
|
|
1012
|
+
// ── Status ──────────────────────────────────────────────────────────────────
|
|
1013
|
+
app.get('/api/status', (req, res) => {
|
|
1014
|
+
try {
|
|
1015
|
+
res.json(Metrics.getStatus());
|
|
1016
|
+
} catch (err) {
|
|
1017
|
+
res.status(500).json({ error: err.message });
|
|
1018
|
+
}
|
|
1019
|
+
});
|
|
1020
|
+
|
|
1021
|
+
// ── Audit entries ───────────────────────────────────────────────────────────
|
|
1022
|
+
app.get('/api/audit', (req, res) => {
|
|
1023
|
+
try {
|
|
1024
|
+
const limit = Math.min(parseInt(req.query.limit || '50', 10), 200);
|
|
1025
|
+
const offset = Math.max(parseInt(req.query.offset || '0', 10), 0);
|
|
1026
|
+
const event = typeof req.query.event === 'string' ? req.query.event : null;
|
|
1027
|
+
res.json(Metrics.getAuditEntries(limit, offset, event));
|
|
1028
|
+
} catch (err) {
|
|
1029
|
+
res.status(500).json({ error: err.message });
|
|
1030
|
+
}
|
|
1031
|
+
});
|
|
1032
|
+
|
|
1033
|
+
// ── Quality metrics ─────────────────────────────────────────────────────────
|
|
1034
|
+
app.get('/api/metrics', (req, res) => {
|
|
1035
|
+
try {
|
|
1036
|
+
res.json(Metrics.getMetrics());
|
|
1037
|
+
} catch (err) {
|
|
1038
|
+
res.status(500).json({ error: err.message });
|
|
1039
|
+
}
|
|
1040
|
+
});
|
|
1041
|
+
|
|
1042
|
+
// ── Approvals ───────────────────────────────────────────────────────────────
|
|
1043
|
+
app.get('/api/approvals', (req, res) => {
|
|
1044
|
+
try {
|
|
1045
|
+
res.json(Metrics.getApprovals());
|
|
1046
|
+
} catch (err) {
|
|
1047
|
+
res.status(500).json({ error: err.message });
|
|
1048
|
+
}
|
|
1049
|
+
});
|
|
1050
|
+
|
|
1051
|
+
app.post('/api/approve/:id', (req, res) => {
|
|
1052
|
+
try {
|
|
1053
|
+
const { id } = req.params;
|
|
1054
|
+
const { decision, comment, approver } = req.body || {};
|
|
1055
|
+
|
|
1056
|
+
if (!decision) {
|
|
1057
|
+
return res.status(400).json({ error: 'Missing "decision" field (approve|reject)' });
|
|
1058
|
+
}
|
|
1059
|
+
|
|
1060
|
+
const result = Approval.processDecision(id, decision, comment, approver);
|
|
1061
|
+
|
|
1062
|
+
if (!result.success) {
|
|
1063
|
+
return res.status(400).json(result);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// Broadcast approval event to all SSE clients
|
|
1067
|
+
SSE.broadcast(
|
|
1068
|
+
decision === 'approve' ? 'approval:resolved' : 'approval:resolved',
|
|
1069
|
+
{ approval_id: id, decision, message: result.message }
|
|
1070
|
+
);
|
|
1071
|
+
|
|
1072
|
+
res.json(result);
|
|
1073
|
+
} catch (err) {
|
|
1074
|
+
res.status(500).json({ error: err.message });
|
|
1075
|
+
}
|
|
1076
|
+
});
|
|
1077
|
+
|
|
1078
|
+
// ── Team activity ───────────────────────────────────────────────────────────
|
|
1079
|
+
app.get('/api/team', (req, res) => {
|
|
1080
|
+
try {
|
|
1081
|
+
res.json(Metrics.getTeamActivity());
|
|
1082
|
+
} catch (err) {
|
|
1083
|
+
res.status(500).json({ error: err.message });
|
|
1084
|
+
}
|
|
1085
|
+
});
|
|
1086
|
+
|
|
1087
|
+
// ── Knowledge base query ────────────────────────────────────────────────────
|
|
1088
|
+
app.get('/api/memory', (req, res) => {
|
|
1089
|
+
try {
|
|
1090
|
+
const q = typeof req.query.q === 'string' ? req.query.q.slice(0, 100) : '';
|
|
1091
|
+
const limit = Math.min(parseInt(req.query.limit || '20', 10), 100);
|
|
1092
|
+
res.json(Metrics.getMemory(q, limit));
|
|
1093
|
+
} catch (err) {
|
|
1094
|
+
res.status(500).json({ error: err.message });
|
|
1095
|
+
}
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
// ── Costs ───────────────────────────────────────────────────────────────────
|
|
1099
|
+
app.get('/api/costs', (req, res) => {
|
|
1100
|
+
try {
|
|
1101
|
+
const window = Math.min(parseInt(req.query.window || '7', 10), 90);
|
|
1102
|
+
res.json(Metrics.getCosts(window));
|
|
1103
|
+
} catch (err) {
|
|
1104
|
+
res.status(500).json({ error: err.message });
|
|
1105
|
+
}
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
// ── Steering (requires auto mode running) ───────────────────────────────────
|
|
1109
|
+
app.post('/api/steer', (req, res) => {
|
|
1110
|
+
try {
|
|
1111
|
+
const { instruction, priority = 'normal' } = req.body || {};
|
|
1112
|
+
|
|
1113
|
+
if (!instruction || typeof instruction !== 'string') {
|
|
1114
|
+
return res.status(400).json({ error: 'Missing "instruction" field' });
|
|
1115
|
+
}
|
|
1116
|
+
if (instruction.length > 500) {
|
|
1117
|
+
return res.status(400).json({ error: 'Instruction too long (max 500 chars)' });
|
|
1118
|
+
}
|
|
1119
|
+
if (!['normal', 'urgent', 'stop'].includes(priority)) {
|
|
1120
|
+
return res.status(400).json({ error: 'Invalid priority. Use: normal|urgent|stop' });
|
|
1121
|
+
}
|
|
1122
|
+
|
|
1123
|
+
// Check auto mode is running
|
|
1124
|
+
const autoState = fs.existsSync(AUTO_STATE_PATH)
|
|
1125
|
+
? JSON.parse(fs.readFileSync(AUTO_STATE_PATH, 'utf8'))
|
|
1126
|
+
: null;
|
|
1127
|
+
|
|
1128
|
+
if (!autoState || autoState.status !== 'running') {
|
|
1129
|
+
return res.status(409).json({ error: 'Auto mode is not running. Steering has no effect.' });
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// Run injection guard
|
|
1133
|
+
const INJECTION_PATTERNS = [
|
|
1134
|
+
/IGNORE ALL PREVIOUS INSTRUCTIONS/i,
|
|
1135
|
+
/DISREGARD YOUR INSTRUCTIONS/i,
|
|
1136
|
+
/FORGET YOUR TRAINING/i,
|
|
1137
|
+
/YOUR NEW INSTRUCTIONS ARE/i,
|
|
1138
|
+
/OVERRIDE:/i,
|
|
1139
|
+
];
|
|
1140
|
+
if (INJECTION_PATTERNS.some(p => p.test(instruction))) {
|
|
1141
|
+
return res.status(400).json({ error: 'Instruction rejected: contains prohibited patterns' });
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// Write to steering queue
|
|
1145
|
+
const entry = {
|
|
1146
|
+
id: require('crypto').randomBytes(8).toString('hex'),
|
|
1147
|
+
timestamp: new Date().toISOString(),
|
|
1148
|
+
instruction: instruction.trim(),
|
|
1149
|
+
priority,
|
|
1150
|
+
authored_by: 'dashboard',
|
|
1151
|
+
applies_to: 'all',
|
|
1152
|
+
status: 'queued',
|
|
1153
|
+
applied_at: null,
|
|
1154
|
+
applied_to_plan: null,
|
|
1155
|
+
};
|
|
1156
|
+
|
|
1157
|
+
if (!fs.existsSync(path.dirname(STEERING_QUEUE))) {
|
|
1158
|
+
fs.mkdirSync(path.dirname(STEERING_QUEUE), { recursive: true });
|
|
1159
|
+
}
|
|
1160
|
+
fs.appendFileSync(STEERING_QUEUE, JSON.stringify(entry) + '\n');
|
|
1161
|
+
|
|
1162
|
+
res.json({ success: true, queued: true, id: entry.id, priority });
|
|
1163
|
+
} catch (err) {
|
|
1164
|
+
res.status(500).json({ error: err.message });
|
|
1165
|
+
}
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
// ── Client count (for dashboard connection indicator) ───────────────────────
|
|
1169
|
+
app.get('/api/connections', (req, res) => {
|
|
1170
|
+
res.json({ clients: SSE.getClientCount() });
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
module.exports = { register };
|
|
1175
|
+
```
|
|
1176
|
+
|
|
1177
|
+
**Commit:**
|
|
1178
|
+
```bash
|
|
1179
|
+
git add bin/dashboard/api-router.js
|
|
1180
|
+
git commit -m "feat(v2-dashboard): implement dashboard API router with all 10 endpoints"
|
|
1181
|
+
```
|
|
1182
|
+
|
|
1183
|
+
---
|
|
1184
|
+
|
|
1185
|
+
## TASK 7 — Implement the Dashboard Server
|
|
1186
|
+
|
|
1187
|
+
### `bin/dashboard/server.js`
|
|
1188
|
+
|
|
1189
|
+
```javascript
|
|
1190
|
+
#!/usr/bin/env node
|
|
1191
|
+
/**
|
|
1192
|
+
* MindForge v2 — Dashboard Server
|
|
1193
|
+
* Real-time web observability at localhost:7339.
|
|
1194
|
+
*
|
|
1195
|
+
* Usage:
|
|
1196
|
+
* node bin/dashboard/server.js [--port 7339] [--open]
|
|
1197
|
+
* /mindforge:dashboard [--port 7339] [--open] [--stop]
|
|
1198
|
+
*
|
|
1199
|
+
* Security: binds to 127.0.0.1 only (ADR-017 policy).
|
|
1200
|
+
* No authentication — localhost-only access is the security model.
|
|
1201
|
+
*/
|
|
1202
|
+
'use strict';
|
|
1203
|
+
|
|
1204
|
+
const http = require('http');
|
|
1205
|
+
const path = require('path');
|
|
1206
|
+
const fs = require('fs');
|
|
1207
|
+
const ARGS = process.argv.slice(2);
|
|
1208
|
+
|
|
1209
|
+
const PORT = parseInt(ARGS.find((_, i, a) => a[i-1] === '--port') || '7339', 10);
|
|
1210
|
+
const OPEN_BROWSER = ARGS.includes('--open');
|
|
1211
|
+
const PID_FILE = path.join(process.cwd(), '.planning', 'dashboard-server.pid');
|
|
1212
|
+
const FRONTEND = path.join(__dirname, 'frontend', 'index.html');
|
|
1213
|
+
|
|
1214
|
+
// ── Load dependencies gracefully ──────────────────────────────────────────────
|
|
1215
|
+
let express;
|
|
1216
|
+
try {
|
|
1217
|
+
express = require('express');
|
|
1218
|
+
} catch {
|
|
1219
|
+
console.error('[dashboard] express not installed. Run: npm install express');
|
|
1220
|
+
process.exit(1);
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
const SSE = require('./sse-bridge');
|
|
1224
|
+
const API = require('./api-router');
|
|
1225
|
+
|
|
1226
|
+
// ── Express app ───────────────────────────────────────────────────────────────
|
|
1227
|
+
const app = express();
|
|
1228
|
+
|
|
1229
|
+
// Security middleware
|
|
1230
|
+
app.use((req, res, next) => {
|
|
1231
|
+
const addr = req.socket.remoteAddress;
|
|
1232
|
+
const isLocal = addr === '127.0.0.1' || addr === '::1' || addr === '::ffff:127.0.0.1';
|
|
1233
|
+
if (!isLocal) {
|
|
1234
|
+
return res.status(403).json({ error: 'Dashboard is localhost-only' });
|
|
1235
|
+
}
|
|
1236
|
+
next();
|
|
1237
|
+
});
|
|
1238
|
+
|
|
1239
|
+
// CORS — only allow requests from localhost origins
|
|
1240
|
+
app.use((req, res, next) => {
|
|
1241
|
+
const origin = req.headers.origin;
|
|
1242
|
+
if (!origin || /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(origin)) {
|
|
1243
|
+
res.setHeader('Access-Control-Allow-Origin', origin || '*');
|
|
1244
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
1245
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
1246
|
+
}
|
|
1247
|
+
if (req.method === 'OPTIONS') return res.status(204).end();
|
|
1248
|
+
next();
|
|
1249
|
+
});
|
|
1250
|
+
|
|
1251
|
+
app.use(express.json({ limit: '64kb' })); // Limit request body size
|
|
1252
|
+
|
|
1253
|
+
// Security headers
|
|
1254
|
+
app.use((req, res, next) => {
|
|
1255
|
+
res.setHeader('X-Content-Type-Options', 'nosniff');
|
|
1256
|
+
res.setHeader('X-Frame-Options', 'SAMEORIGIN');
|
|
1257
|
+
res.setHeader('Cache-Control', 'no-store'); // Never cache dashboard responses
|
|
1258
|
+
next();
|
|
1259
|
+
});
|
|
1260
|
+
|
|
1261
|
+
// ── Static frontend ───────────────────────────────────────────────────────────
|
|
1262
|
+
app.get('/', (req, res) => {
|
|
1263
|
+
if (!fs.existsSync(FRONTEND)) {
|
|
1264
|
+
return res.status(503).send('<h1>Dashboard frontend not found</h1><p>Run: npm run build:dashboard</p>');
|
|
1265
|
+
}
|
|
1266
|
+
res.sendFile(FRONTEND);
|
|
1267
|
+
});
|
|
1268
|
+
|
|
1269
|
+
// ── Register API routes ───────────────────────────────────────────────────────
|
|
1270
|
+
API.register(app);
|
|
1271
|
+
|
|
1272
|
+
// ── Start SSE bridge ──────────────────────────────────────────────────────────
|
|
1273
|
+
SSE.start();
|
|
1274
|
+
|
|
1275
|
+
// ── HTTP server ───────────────────────────────────────────────────────────────
|
|
1276
|
+
const server = http.createServer(app);
|
|
1277
|
+
|
|
1278
|
+
server.listen(PORT, '127.0.0.1', () => {
|
|
1279
|
+
fs.mkdirSync(path.dirname(PID_FILE), { recursive: true });
|
|
1280
|
+
fs.writeFileSync(PID_FILE, String(process.pid));
|
|
1281
|
+
|
|
1282
|
+
console.log(`\n⚡ MindForge Dashboard`);
|
|
1283
|
+
console.log(` URL: http://localhost:${PORT}`);
|
|
1284
|
+
console.log(` Status: http://localhost:${PORT}/api/status`);
|
|
1285
|
+
console.log(` Events: http://localhost:${PORT}/events`);
|
|
1286
|
+
console.log(` PID: ${process.pid}`);
|
|
1287
|
+
console.log(`\n Press CTRL+C to stop\n`);
|
|
1288
|
+
|
|
1289
|
+
if (OPEN_BROWSER) {
|
|
1290
|
+
const open = process.platform === 'darwin' ? 'open'
|
|
1291
|
+
: process.platform === 'win32' ? 'start'
|
|
1292
|
+
: 'xdg-open';
|
|
1293
|
+
require('child_process').spawn(open, [`http://localhost:${PORT}`], { detached: true, stdio: 'ignore' });
|
|
1294
|
+
}
|
|
1295
|
+
});
|
|
1296
|
+
|
|
1297
|
+
server.on('error', err => {
|
|
1298
|
+
if (err.code === 'EADDRINUSE') {
|
|
1299
|
+
console.error(`[dashboard] Port ${PORT} already in use.`);
|
|
1300
|
+
console.error(`[dashboard] Stop it: /mindforge:dashboard --stop`);
|
|
1301
|
+
console.error(`[dashboard] Or use a different port: /mindforge:dashboard --port 7340`);
|
|
1302
|
+
}
|
|
1303
|
+
process.exit(1);
|
|
1304
|
+
});
|
|
1305
|
+
|
|
1306
|
+
// ── Graceful shutdown ─────────────────────────────────────────────────────────
|
|
1307
|
+
function shutdown(signal) {
|
|
1308
|
+
console.log(`\n[dashboard] ${signal} received — shutting down`);
|
|
1309
|
+
SSE.stop();
|
|
1310
|
+
server.close(() => {
|
|
1311
|
+
if (fs.existsSync(PID_FILE)) fs.unlinkSync(PID_FILE);
|
|
1312
|
+
process.exit(0);
|
|
1313
|
+
});
|
|
1314
|
+
setTimeout(() => process.exit(0), 3000);
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
|
1318
|
+
process.on('SIGINT', () => shutdown('SIGINT'));
|
|
1319
|
+
```
|
|
1320
|
+
|
|
1321
|
+
**Commit:**
|
|
1322
|
+
```bash
|
|
1323
|
+
git add bin/dashboard/server.js
|
|
1324
|
+
git commit -m "feat(v2-dashboard): implement Express dashboard server with security middleware and graceful shutdown"
|
|
1325
|
+
```
|
|
1326
|
+
|
|
1327
|
+
---
|
|
1328
|
+
|
|
1329
|
+
## TASK 8 — Build the Single-File HTML Frontend
|
|
1330
|
+
|
|
1331
|
+
### `bin/dashboard/frontend/index.html`
|
|
1332
|
+
|
|
1333
|
+
```html
|
|
1334
|
+
<!DOCTYPE html>
|
|
1335
|
+
<html lang="en">
|
|
1336
|
+
<head>
|
|
1337
|
+
<meta charset="UTF-8">
|
|
1338
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1339
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
1340
|
+
<title>MindForge Dashboard</title>
|
|
1341
|
+
<style>
|
|
1342
|
+
/* ── Design tokens ───────────────────────────────────────────── */
|
|
1343
|
+
:root {
|
|
1344
|
+
--bg: #0d1117;
|
|
1345
|
+
--surface: #161b22;
|
|
1346
|
+
--border: #30363d;
|
|
1347
|
+
--text: #e6edf3;
|
|
1348
|
+
--muted: #8b949e;
|
|
1349
|
+
--accent: #58a6ff;
|
|
1350
|
+
--green: #3fb950;
|
|
1351
|
+
--yellow: #d29922;
|
|
1352
|
+
--red: #f85149;
|
|
1353
|
+
--orange: #db6d28;
|
|
1354
|
+
--purple: #bc8cff;
|
|
1355
|
+
--font-mono:'JetBrains Mono', 'Fira Code', 'Consolas', monospace;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
/* ── Reset ───────────────────────────────────────────────────── */
|
|
1359
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
1360
|
+
html, body { height: 100%; overflow: hidden; }
|
|
1361
|
+
body { background: var(--bg); color: var(--text); font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; font-size: 13px; }
|
|
1362
|
+
|
|
1363
|
+
/* ── Layout ──────────────────────────────────────────────────── */
|
|
1364
|
+
#app { display: flex; flex-direction: column; height: 100vh; }
|
|
1365
|
+
header { display: flex; align-items: center; justify-content: space-between; padding: 10px 20px; background: var(--surface); border-bottom: 1px solid var(--border); flex-shrink: 0; }
|
|
1366
|
+
nav { display: flex; gap: 4px; padding: 8px 20px; background: var(--surface); border-bottom: 1px solid var(--border); flex-shrink: 0; }
|
|
1367
|
+
main { flex: 1; overflow: hidden; padding: 16px 20px; }
|
|
1368
|
+
.page { display: none; height: 100%; overflow: auto; }
|
|
1369
|
+
.page.active { display: flex; flex-direction: column; gap: 12px; }
|
|
1370
|
+
|
|
1371
|
+
/* ── Header ──────────────────────────────────────────────────── */
|
|
1372
|
+
.logo { font-weight: 700; font-size: 15px; color: var(--accent); letter-spacing: -0.3px; }
|
|
1373
|
+
.project{ font-size: 12px; color: var(--muted); }
|
|
1374
|
+
.conn { display: flex; align-items: center; gap: 6px; font-size: 11px; color: var(--muted); }
|
|
1375
|
+
.dot { width: 7px; height: 7px; border-radius: 50%; background: var(--red); }
|
|
1376
|
+
.dot.live { background: var(--green); animation: pulse 2s infinite; }
|
|
1377
|
+
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:.4} }
|
|
1378
|
+
|
|
1379
|
+
/* ── Nav tabs ────────────────────────────────────────────────── */
|
|
1380
|
+
.tab { padding: 5px 14px; border-radius: 6px; cursor: pointer; color: var(--muted); font-size: 12px; font-weight: 500; transition: all .15s; border: none; background: none; }
|
|
1381
|
+
.tab:hover { background: var(--border); color: var(--text); }
|
|
1382
|
+
.tab.active { background: var(--accent); color: #fff; }
|
|
1383
|
+
.badge { background: var(--red); color: #fff; border-radius: 10px; padding: 1px 6px; font-size: 10px; margin-left: 4px; }
|
|
1384
|
+
|
|
1385
|
+
/* ── Cards ───────────────────────────────────────────────────── */
|
|
1386
|
+
.card { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 14px 16px; }
|
|
1387
|
+
.card h3{ font-size: 11px; font-weight: 600; color: var(--muted); text-transform: uppercase; letter-spacing: .5px; margin-bottom: 10px; }
|
|
1388
|
+
|
|
1389
|
+
/* ── Status bar ──────────────────────────────────────────────── */
|
|
1390
|
+
#status-bar { display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); gap: 10px; }
|
|
1391
|
+
.stat { display: flex; flex-direction: column; gap: 2px; }
|
|
1392
|
+
.stat-label { font-size: 10px; color: var(--muted); text-transform: uppercase; letter-spacing: .4px; }
|
|
1393
|
+
.stat-value { font-size: 18px; font-weight: 700; font-variant-numeric: tabular-nums; }
|
|
1394
|
+
.stat-value.accent { color: var(--accent); }
|
|
1395
|
+
.stat-value.green { color: var(--green); }
|
|
1396
|
+
.stat-value.yellow { color: var(--yellow); }
|
|
1397
|
+
.stat-value.red { color: var(--red); }
|
|
1398
|
+
|
|
1399
|
+
/* ── Progress bar ────────────────────────────────────────────── */
|
|
1400
|
+
.progress-wrap { display: flex; align-items: center; gap: 10px; }
|
|
1401
|
+
.progress-bar { flex: 1; height: 6px; background: var(--border); border-radius: 3px; overflow: hidden; }
|
|
1402
|
+
.progress-fill { height: 100%; background: var(--accent); border-radius: 3px; transition: width .3s; }
|
|
1403
|
+
.progress-pct { font-size: 11px; color: var(--muted); min-width: 36px; text-align: right; }
|
|
1404
|
+
|
|
1405
|
+
/* ── Audit feed ──────────────────────────────────────────────── */
|
|
1406
|
+
#audit-feed { font-family: var(--font-mono); font-size: 11px; line-height: 1.7; overflow-y: auto; max-height: calc(100vh - 320px); }
|
|
1407
|
+
.event-row { display: grid; grid-template-columns: 80px 150px 1fr; gap: 10px; padding: 3px 0; border-bottom: 1px solid #1c2128; }
|
|
1408
|
+
.event-row:first-child { color: var(--text); }
|
|
1409
|
+
.event-row:not(:first-child) { color: var(--muted); }
|
|
1410
|
+
.ev-time { color: var(--muted); }
|
|
1411
|
+
.ev-type { }
|
|
1412
|
+
.ev-type.task_completed { color: var(--green); }
|
|
1413
|
+
.ev-type.task_failed { color: var(--red); }
|
|
1414
|
+
.ev-type.security_finding { color: var(--yellow); }
|
|
1415
|
+
.ev-type.auto_mode_escalated { color: var(--red); font-weight: bold; }
|
|
1416
|
+
.ev-type.node_repair { color: var(--orange); }
|
|
1417
|
+
.ev-type.approval_granted { color: var(--green); }
|
|
1418
|
+
.ev-detail { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
1419
|
+
|
|
1420
|
+
/* ── Steering input ──────────────────────────────────────────── */
|
|
1421
|
+
.steer-row { display: flex; gap: 8px; margin-top: 10px; }
|
|
1422
|
+
.steer-input { flex: 1; background: var(--bg); border: 1px solid var(--border); color: var(--text); padding: 7px 12px; border-radius: 6px; font-size: 12px; font-family: var(--font-mono); }
|
|
1423
|
+
.steer-input:focus { outline: none; border-color: var(--accent); }
|
|
1424
|
+
.steer-btn { padding: 7px 16px; background: var(--accent); color: #fff; border: none; border-radius: 6px; cursor: pointer; font-size: 12px; font-weight: 600; }
|
|
1425
|
+
.steer-btn:hover { opacity: .85; }
|
|
1426
|
+
.steer-btn:disabled { opacity: .4; cursor: default; }
|
|
1427
|
+
|
|
1428
|
+
/* ── Approval cards ──────────────────────────────────────────── */
|
|
1429
|
+
.approval-card { border-left: 3px solid var(--yellow); padding: 12px; display: flex; justify-content: space-between; align-items: flex-start; }
|
|
1430
|
+
.approval-card.tier3 { border-left-color: var(--red); }
|
|
1431
|
+
.approval-card.expiring { border-left-color: var(--orange); }
|
|
1432
|
+
.approval-meta { font-size: 10px; color: var(--muted); margin-top: 4px; }
|
|
1433
|
+
.approval-actions { display: flex; gap: 6px; flex-shrink: 0; }
|
|
1434
|
+
.btn-approve { padding: 5px 14px; background: var(--green); color: #fff; border: none; border-radius: 5px; cursor: pointer; font-size: 12px; font-weight: 600; }
|
|
1435
|
+
.btn-approve:hover { opacity: .85; }
|
|
1436
|
+
.btn-reject { padding: 5px 14px; background: var(--red); color: #fff; border: none; border-radius: 5px; cursor: pointer; font-size: 12px; font-weight: 600; }
|
|
1437
|
+
.btn-reject:hover { opacity: .85; }
|
|
1438
|
+
|
|
1439
|
+
/* ── Metrics charts ──────────────────────────────────────────── */
|
|
1440
|
+
.chart-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; }
|
|
1441
|
+
canvas { width: 100% !important; max-height: 140px; }
|
|
1442
|
+
|
|
1443
|
+
/* ── Knowledge graph ─────────────────────────────────────────── */
|
|
1444
|
+
#graph-canvas { width: 100%; height: calc(100vh - 220px); background: var(--bg); border-radius: 8px; border: 1px solid var(--border); }
|
|
1445
|
+
.node-panel { position: absolute; right: 20px; top: 80px; width: 280px; background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 14px; max-height: 60vh; overflow-y: auto; display: none; }
|
|
1446
|
+
.node-panel.visible { display: block; }
|
|
1447
|
+
|
|
1448
|
+
/* ── Team activity ───────────────────────────────────────────── */
|
|
1449
|
+
.developer-row { display: flex; align-items: center; gap: 12px; padding: 8px 0; border-bottom: 1px solid var(--border); }
|
|
1450
|
+
.avatar { width: 28px; height: 28px; border-radius: 50%; background: var(--accent); display: flex; align-items: center; justify-content: center; font-size: 12px; font-weight: 700; color: #fff; flex-shrink: 0; }
|
|
1451
|
+
.dev-info { flex: 1; }
|
|
1452
|
+
.dev-name { font-size: 12px; font-weight: 600; }
|
|
1453
|
+
.dev-task { font-size: 11px; color: var(--muted); }
|
|
1454
|
+
.dev-last { font-size: 10px; color: var(--muted); }
|
|
1455
|
+
.conflict-row { background: rgba(248, 81, 73, .08); border: 1px solid var(--red); border-radius: 5px; padding: 7px 10px; font-size: 11px; color: var(--red); }
|
|
1456
|
+
|
|
1457
|
+
/* ── Utility ─────────────────────────────────────────────────── */
|
|
1458
|
+
.empty { text-align: center; padding: 40px; color: var(--muted); font-size: 12px; }
|
|
1459
|
+
.tag { display: inline-block; background: var(--border); padding: 2px 7px; border-radius: 3px; font-size: 10px; margin: 2px; }
|
|
1460
|
+
.confidence { display: inline-block; padding: 1px 6px; border-radius: 3px; font-size: 10px; font-weight: 700; }
|
|
1461
|
+
.conf-high { background: rgba(63,185,80,.2); color: var(--green); }
|
|
1462
|
+
.conf-med { background: rgba(210,153,34,.2); color: var(--yellow); }
|
|
1463
|
+
.conf-low { background: rgba(248,81,73,.2); color: var(--red); }
|
|
1464
|
+
</style>
|
|
1465
|
+
</head>
|
|
1466
|
+
<body>
|
|
1467
|
+
<div id="app">
|
|
1468
|
+
|
|
1469
|
+
<!-- ── Header ─────────────────────────────────────────────────── -->
|
|
1470
|
+
<header>
|
|
1471
|
+
<div style="display:flex;align-items:center;gap:16px">
|
|
1472
|
+
<span class="logo">⚡ MindForge</span>
|
|
1473
|
+
<span class="project" id="hdr-project">Loading...</span>
|
|
1474
|
+
<span id="hdr-status" style="font-size:11px;padding:2px 8px;border-radius:3px;background:#161b22;color:#8b949e">IDLE</span>
|
|
1475
|
+
</div>
|
|
1476
|
+
<div class="conn">
|
|
1477
|
+
<div class="dot" id="conn-dot"></div>
|
|
1478
|
+
<span id="conn-label">Connecting...</span>
|
|
1479
|
+
</div>
|
|
1480
|
+
</header>
|
|
1481
|
+
|
|
1482
|
+
<!-- ── Nav ────────────────────────────────────────────────────── -->
|
|
1483
|
+
<nav>
|
|
1484
|
+
<button class="tab active" onclick="showPage('activity')">Activity</button>
|
|
1485
|
+
<button class="tab" onclick="showPage('metrics')">Quality</button>
|
|
1486
|
+
<button class="tab" onclick="showPage('approvals')">Approvals <span class="badge" id="approval-badge" style="display:none">0</span></button>
|
|
1487
|
+
<button class="tab" onclick="showPage('memory')">Knowledge</button>
|
|
1488
|
+
<button class="tab" onclick="showPage('team')">Team</button>
|
|
1489
|
+
</nav>
|
|
1490
|
+
|
|
1491
|
+
<!-- ── Main ───────────────────────────────────────────────────── -->
|
|
1492
|
+
<main>
|
|
1493
|
+
|
|
1494
|
+
<!-- Page 1: Activity ──────────────────────────────────────── -->
|
|
1495
|
+
<div class="page active" id="page-activity">
|
|
1496
|
+
|
|
1497
|
+
<div class="card" id="status-bar-card">
|
|
1498
|
+
<h3>Phase Status</h3>
|
|
1499
|
+
<div id="status-bar">
|
|
1500
|
+
<div class="stat"><span class="stat-label">Phase</span><span class="stat-value accent" id="s-phase">—</span></div>
|
|
1501
|
+
<div class="stat"><span class="stat-label">Tasks</span><span class="stat-value" id="s-tasks">—</span></div>
|
|
1502
|
+
<div class="stat"><span class="stat-label">Wave</span><span class="stat-value" id="s-wave">—</span></div>
|
|
1503
|
+
<div class="stat"><span class="stat-label">Repairs</span><span class="stat-value" id="s-repairs">0</span></div>
|
|
1504
|
+
<div class="stat"><span class="stat-label">Elapsed</span><span class="stat-value" id="s-elapsed">—</span></div>
|
|
1505
|
+
</div>
|
|
1506
|
+
<div class="progress-wrap" style="margin-top:12px">
|
|
1507
|
+
<div class="progress-bar"><div class="progress-fill" id="progress-fill" style="width:0%"></div></div>
|
|
1508
|
+
<span class="progress-pct" id="progress-pct">0%</span>
|
|
1509
|
+
</div>
|
|
1510
|
+
<div style="margin-top:8px;font-size:11px;color:var(--muted)" id="s-current-task">No active task</div>
|
|
1511
|
+
</div>
|
|
1512
|
+
|
|
1513
|
+
<div class="card" style="flex:1">
|
|
1514
|
+
<h3>Live Events</h3>
|
|
1515
|
+
<div id="audit-feed"><div class="empty">Waiting for events...</div></div>
|
|
1516
|
+
<div class="steer-row">
|
|
1517
|
+
<input class="steer-input" id="steer-input" placeholder="Steer auto mode: e.g. Use Redis for session storage..." />
|
|
1518
|
+
<button class="steer-btn" id="steer-btn" onclick="sendSteer()">Steer</button>
|
|
1519
|
+
</div>
|
|
1520
|
+
</div>
|
|
1521
|
+
</div>
|
|
1522
|
+
|
|
1523
|
+
<!-- Page 2: Quality Metrics ──────────────────────────────── -->
|
|
1524
|
+
<div class="page" id="page-metrics">
|
|
1525
|
+
<div class="chart-grid">
|
|
1526
|
+
<div class="card"><h3>Session Quality Score</h3><canvas id="chart-quality"></canvas></div>
|
|
1527
|
+
<div class="card"><h3>Verify Pass Rate</h3><canvas id="chart-verify"></canvas></div>
|
|
1528
|
+
<div class="card"><h3>Security Findings</h3><canvas id="chart-security"></canvas></div>
|
|
1529
|
+
<div class="card"><h3>Cost Per Session ($)</h3><canvas id="chart-cost"></canvas></div>
|
|
1530
|
+
</div>
|
|
1531
|
+
<div class="card" style="margin-top:4px">
|
|
1532
|
+
<h3>Summary</h3>
|
|
1533
|
+
<div id="metrics-summary" style="display:grid;grid-template-columns:repeat(4,1fr);gap:16px;padding-top:8px">
|
|
1534
|
+
<div class="stat"><span class="stat-label">Avg Quality</span><span class="stat-value accent" id="m-quality">—</span></div>
|
|
1535
|
+
<div class="stat"><span class="stat-label">Avg Cost</span><span class="stat-value" id="m-cost">—</span></div>
|
|
1536
|
+
<div class="stat"><span class="stat-label">Repair Rate</span><span class="stat-value" id="m-repair">—</span></div>
|
|
1537
|
+
<div class="stat"><span class="stat-label">Total Tasks</span><span class="stat-value green" id="m-tasks">—</span></div>
|
|
1538
|
+
</div>
|
|
1539
|
+
</div>
|
|
1540
|
+
</div>
|
|
1541
|
+
|
|
1542
|
+
<!-- Page 3: Approvals ────────────────────────────────────── -->
|
|
1543
|
+
<div class="page" id="page-approvals">
|
|
1544
|
+
<div class="card">
|
|
1545
|
+
<h3>Pending Approvals</h3>
|
|
1546
|
+
<div id="approvals-pending"><div class="empty">No pending approvals ✅</div></div>
|
|
1547
|
+
</div>
|
|
1548
|
+
<div class="card">
|
|
1549
|
+
<h3>Recent Resolutions</h3>
|
|
1550
|
+
<div id="approvals-resolved"><div class="empty">No recent resolutions</div></div>
|
|
1551
|
+
</div>
|
|
1552
|
+
</div>
|
|
1553
|
+
|
|
1554
|
+
<!-- Page 4: Knowledge Graph ──────────────────────────────── -->
|
|
1555
|
+
<div class="page" id="page-memory" style="position:relative">
|
|
1556
|
+
<div class="card" style="flex-shrink:0">
|
|
1557
|
+
<div style="display:flex;align-items:center;gap:12px">
|
|
1558
|
+
<input id="memory-search" style="flex:1;background:var(--bg);border:1px solid var(--border);color:var(--text);padding:6px 12px;border-radius:6px;font-size:12px" placeholder="Search knowledge base..." oninput="searchMemory(this.value)">
|
|
1559
|
+
<span id="memory-count" style="font-size:11px;color:var(--muted)">— entries</span>
|
|
1560
|
+
</div>
|
|
1561
|
+
</div>
|
|
1562
|
+
<div id="memory-list" style="overflow-y:auto;flex:1"></div>
|
|
1563
|
+
</div>
|
|
1564
|
+
|
|
1565
|
+
<!-- Page 5: Team Activity ────────────────────────────────── -->
|
|
1566
|
+
<div class="page" id="page-team">
|
|
1567
|
+
<div class="card">
|
|
1568
|
+
<h3>Active Developers</h3>
|
|
1569
|
+
<div id="team-active"><div class="empty">No recent activity</div></div>
|
|
1570
|
+
</div>
|
|
1571
|
+
<div class="card">
|
|
1572
|
+
<h3>⚠️ File Conflicts</h3>
|
|
1573
|
+
<div id="team-conflicts"><div class="empty">No conflicts detected ✅</div></div>
|
|
1574
|
+
</div>
|
|
1575
|
+
</div>
|
|
1576
|
+
|
|
1577
|
+
</main>
|
|
1578
|
+
</div>
|
|
1579
|
+
|
|
1580
|
+
<script>
|
|
1581
|
+
// ── State ─────────────────────────────────────────────────────────────────────
|
|
1582
|
+
const state = { events: [], status: {}, approvals: {}, charts: {}, sse: null };
|
|
1583
|
+
|
|
1584
|
+
// ── Page navigation ───────────────────────────────────────────────────────────
|
|
1585
|
+
function showPage(name) {
|
|
1586
|
+
document.querySelectorAll('.page').forEach(p => p.classList.remove('active'));
|
|
1587
|
+
document.querySelectorAll('.tab').forEach(t => t.classList.remove('active'));
|
|
1588
|
+
document.getElementById('page-' + name).classList.add('active');
|
|
1589
|
+
event.target.classList.add('active');
|
|
1590
|
+
|
|
1591
|
+
// Load data for the page
|
|
1592
|
+
if (name === 'metrics') loadMetrics();
|
|
1593
|
+
if (name === 'approvals') loadApprovals();
|
|
1594
|
+
if (name === 'memory') loadMemory('');
|
|
1595
|
+
if (name === 'team') loadTeam();
|
|
1596
|
+
}
|
|
1597
|
+
|
|
1598
|
+
// ── SSE connection ────────────────────────────────────────────────────────────
|
|
1599
|
+
function connectSSE() {
|
|
1600
|
+
const es = new EventSource('/events');
|
|
1601
|
+
state.sse = es;
|
|
1602
|
+
|
|
1603
|
+
es.addEventListener('audit:new', e => {
|
|
1604
|
+
const entry = JSON.parse(e.data);
|
|
1605
|
+
prependAuditEvent(entry);
|
|
1606
|
+
});
|
|
1607
|
+
|
|
1608
|
+
es.addEventListener('status:update', e => {
|
|
1609
|
+
updateStatusBar(JSON.parse(e.data));
|
|
1610
|
+
});
|
|
1611
|
+
|
|
1612
|
+
es.addEventListener('approval:new', e => {
|
|
1613
|
+
const data = JSON.parse(e.data);
|
|
1614
|
+
showApprovalBadge();
|
|
1615
|
+
});
|
|
1616
|
+
|
|
1617
|
+
es.onopen = () => {
|
|
1618
|
+
document.getElementById('conn-dot').classList.add('live');
|
|
1619
|
+
document.getElementById('conn-label').textContent = 'Live';
|
|
1620
|
+
};
|
|
1621
|
+
|
|
1622
|
+
es.onerror = () => {
|
|
1623
|
+
document.getElementById('conn-dot').classList.remove('live');
|
|
1624
|
+
document.getElementById('conn-label').textContent = 'Reconnecting...';
|
|
1625
|
+
setTimeout(connectSSE, 3000);
|
|
1626
|
+
es.close();
|
|
1627
|
+
};
|
|
1628
|
+
}
|
|
1629
|
+
|
|
1630
|
+
// ── Activity feed ─────────────────────────────────────────────────────────────
|
|
1631
|
+
async function loadActivity() {
|
|
1632
|
+
try {
|
|
1633
|
+
const r = await fetch('/api/audit?limit=50');
|
|
1634
|
+
const d = await r.json();
|
|
1635
|
+
const feed = document.getElementById('audit-feed');
|
|
1636
|
+
if (!d.entries?.length) { feed.innerHTML = '<div class="empty">No events yet</div>'; return; }
|
|
1637
|
+
feed.innerHTML = '';
|
|
1638
|
+
d.entries.forEach(e => appendAuditRow(feed, e));
|
|
1639
|
+
} catch {}
|
|
1640
|
+
}
|
|
1641
|
+
|
|
1642
|
+
function prependAuditEvent(entry) {
|
|
1643
|
+
const feed = document.getElementById('audit-feed');
|
|
1644
|
+
const empty = feed.querySelector('.empty');
|
|
1645
|
+
if (empty) feed.innerHTML = '';
|
|
1646
|
+
|
|
1647
|
+
const row = createAuditRow(entry);
|
|
1648
|
+
feed.insertBefore(row, feed.firstChild);
|
|
1649
|
+
|
|
1650
|
+
// Keep max 100 rows
|
|
1651
|
+
while (feed.children.length > 100) feed.removeChild(feed.lastChild);
|
|
1652
|
+
}
|
|
1653
|
+
|
|
1654
|
+
function appendAuditRow(feed, entry) { feed.appendChild(createAuditRow(entry)); }
|
|
1655
|
+
|
|
1656
|
+
function createAuditRow(entry) {
|
|
1657
|
+
const row = document.createElement('div');
|
|
1658
|
+
row.className = 'event-row';
|
|
1659
|
+
const time = entry.timestamp ? new Date(entry.timestamp).toLocaleTimeString() : '';
|
|
1660
|
+
const detail = entry.plan ? `Plan ${entry.phase}-${entry.plan} ${entry.task_name || ''}`.trim()
|
|
1661
|
+
: entry.reason || entry.description || entry.topic || JSON.stringify(entry).slice(0, 80);
|
|
1662
|
+
row.innerHTML = `
|
|
1663
|
+
<span class="ev-time">${time}</span>
|
|
1664
|
+
<span class="ev-type ${entry.event}">${entry.event}</span>
|
|
1665
|
+
<span class="ev-detail">${escapeHtml(detail)}</span>
|
|
1666
|
+
`;
|
|
1667
|
+
return row;
|
|
1668
|
+
}
|
|
1669
|
+
|
|
1670
|
+
// ── Status bar ────────────────────────────────────────────────────────────────
|
|
1671
|
+
async function loadStatus() {
|
|
1672
|
+
try {
|
|
1673
|
+
const r = await fetch('/api/status');
|
|
1674
|
+
const d = await r.json();
|
|
1675
|
+
updateStatusBar(d);
|
|
1676
|
+
} catch {}
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
function updateStatusBar(d) {
|
|
1680
|
+
setText('hdr-project', d.project_name || 'MindForge Project');
|
|
1681
|
+
const statusEl = document.getElementById('hdr-status');
|
|
1682
|
+
statusEl.textContent = (d.auto_status || 'IDLE').toUpperCase();
|
|
1683
|
+
statusEl.style.background = d.auto_mode ? '#0d4429' : '#1c2128';
|
|
1684
|
+
statusEl.style.color = d.auto_mode ? 'var(--green)' : 'var(--muted)';
|
|
1685
|
+
|
|
1686
|
+
setText('s-phase', d.phase ? `${d.phase} — ${(d.phase_description || '').slice(0, 30)}` : '—');
|
|
1687
|
+
setText('s-tasks', d.tasks_total ? `${d.tasks_completed}/${d.tasks_total}` : '—');
|
|
1688
|
+
setText('s-wave', d.wave_total ? `${d.wave_current}/${d.wave_total}` : '—');
|
|
1689
|
+
setText('s-repairs', d.node_repairs || 0);
|
|
1690
|
+
setText('s-elapsed', d.elapsed_ms ? formatDuration(d.elapsed_ms) : '—');
|
|
1691
|
+
setText('s-current-task', d.current_task || 'No active task');
|
|
1692
|
+
|
|
1693
|
+
const pct = d.tasks_total ? Math.round((d.tasks_completed / d.tasks_total) * 100) : 0;
|
|
1694
|
+
document.getElementById('progress-fill').style.width = pct + '%';
|
|
1695
|
+
setText('progress-pct', pct + '%');
|
|
1696
|
+
}
|
|
1697
|
+
|
|
1698
|
+
// ── Metrics ───────────────────────────────────────────────────────────────────
|
|
1699
|
+
async function loadMetrics() {
|
|
1700
|
+
try {
|
|
1701
|
+
const r = await fetch('/api/metrics');
|
|
1702
|
+
const d = await r.json();
|
|
1703
|
+
setText('m-quality', d.avg_quality?.toFixed(2) || '—');
|
|
1704
|
+
setText('m-cost', d.avg_cost_usd ? '$' + d.avg_cost_usd.toFixed(4) : '—');
|
|
1705
|
+
setText('m-repair', d.node_repair_rate ? (d.node_repair_rate * 100).toFixed(1) + '%' : '0%');
|
|
1706
|
+
setText('m-tasks', d.total_tasks || '0');
|
|
1707
|
+
drawCharts(d);
|
|
1708
|
+
} catch {}
|
|
1709
|
+
}
|
|
1710
|
+
|
|
1711
|
+
function drawCharts(d) {
|
|
1712
|
+
const sessions = d.sessions || [];
|
|
1713
|
+
const labels = sessions.map((_, i) => `S${i+1}`);
|
|
1714
|
+
|
|
1715
|
+
drawLineChart('chart-quality', labels, sessions.map(s => s.quality_score), 'Quality Score', '#58a6ff');
|
|
1716
|
+
drawLineChart('chart-verify', labels, sessions.map(s => s.verify_pass_rate), 'Pass Rate', '#3fb950');
|
|
1717
|
+
drawLineChart('chart-cost', labels, sessions.map(s => s.cost_usd), 'Cost ($)', '#bc8cff');
|
|
1718
|
+
|
|
1719
|
+
const sf = d.security_findings || {};
|
|
1720
|
+
drawBarChart('chart-security',
|
|
1721
|
+
['CRITICAL','HIGH','MEDIUM','LOW'],
|
|
1722
|
+
[sf.CRITICAL||0, sf.HIGH||0, sf.MEDIUM||0, sf.LOW||0],
|
|
1723
|
+
['#f85149','#db6d28','#d29922','#8b949e']
|
|
1724
|
+
);
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
function drawLineChart(id, labels, data, label, color) {
|
|
1728
|
+
const canvas = document.getElementById(id);
|
|
1729
|
+
if (!canvas) return;
|
|
1730
|
+
const ctx = canvas.getContext('2d');
|
|
1731
|
+
canvas.width = canvas.offsetWidth; canvas.height = 130;
|
|
1732
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1733
|
+
if (!data.length) return;
|
|
1734
|
+
|
|
1735
|
+
const max = Math.max(...data, 0.01);
|
|
1736
|
+
const pad = 20;
|
|
1737
|
+
const w = canvas.width - pad * 2;
|
|
1738
|
+
const h = canvas.height - pad * 2;
|
|
1739
|
+
|
|
1740
|
+
ctx.strokeStyle = color; ctx.lineWidth = 2; ctx.beginPath();
|
|
1741
|
+
data.forEach((v, i) => {
|
|
1742
|
+
const x = pad + (i / Math.max(data.length - 1, 1)) * w;
|
|
1743
|
+
const y = pad + h - (v / max) * h;
|
|
1744
|
+
i === 0 ? ctx.moveTo(x, y) : ctx.lineTo(x, y);
|
|
1745
|
+
});
|
|
1746
|
+
ctx.stroke();
|
|
1747
|
+
|
|
1748
|
+
// Area fill
|
|
1749
|
+
ctx.fillStyle = color + '22';
|
|
1750
|
+
ctx.lineTo(pad + w, pad + h); ctx.lineTo(pad, pad + h);
|
|
1751
|
+
ctx.closePath(); ctx.fill();
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
function drawBarChart(id, labels, data, colors) {
|
|
1755
|
+
const canvas = document.getElementById(id);
|
|
1756
|
+
if (!canvas) return;
|
|
1757
|
+
const ctx = canvas.getContext('2d');
|
|
1758
|
+
canvas.width = canvas.offsetWidth; canvas.height = 130;
|
|
1759
|
+
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
|
1760
|
+
|
|
1761
|
+
const max = Math.max(...data, 1);
|
|
1762
|
+
const barW = canvas.width / labels.length;
|
|
1763
|
+
const pad = 10;
|
|
1764
|
+
|
|
1765
|
+
data.forEach((v, i) => {
|
|
1766
|
+
const h = ((v / max) * (canvas.height - pad * 2));
|
|
1767
|
+
const x = i * barW + barW * 0.2;
|
|
1768
|
+
const y = canvas.height - pad - h;
|
|
1769
|
+
ctx.fillStyle = colors[i] || '#58a6ff';
|
|
1770
|
+
ctx.fillRect(x, y, barW * 0.6, h);
|
|
1771
|
+
ctx.fillStyle = '#8b949e';
|
|
1772
|
+
ctx.font = '10px sans-serif';
|
|
1773
|
+
ctx.fillText(labels[i], x + barW * 0.1, canvas.height - 2);
|
|
1774
|
+
if (v > 0) { ctx.fillStyle = '#e6edf3'; ctx.fillText(v, x + barW * 0.1, y - 3); }
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
|
|
1778
|
+
// ── Approvals ─────────────────────────────────────────────────────────────────
|
|
1779
|
+
async function loadApprovals() {
|
|
1780
|
+
try {
|
|
1781
|
+
const r = await fetch('/api/approvals');
|
|
1782
|
+
const d = await r.json();
|
|
1783
|
+
|
|
1784
|
+
const pendingEl = document.getElementById('approvals-pending');
|
|
1785
|
+
const resolvedEl = document.getElementById('approvals-resolved');
|
|
1786
|
+
|
|
1787
|
+
if (!d.pending?.length) {
|
|
1788
|
+
pendingEl.innerHTML = '<div class="empty">No pending approvals ✅</div>';
|
|
1789
|
+
} else {
|
|
1790
|
+
pendingEl.innerHTML = d.pending.map(a => `
|
|
1791
|
+
<div class="approval-card ${a.tier === 3 ? 'tier3' : ''} ${a.hours_remaining < 4 ? 'expiring' : ''}">
|
|
1792
|
+
<div>
|
|
1793
|
+
<div><strong>Tier ${a.tier}</strong> — Phase ${a.phase}, Plan ${a.plan}</div>
|
|
1794
|
+
<div style="margin-top:4px;color:var(--text)">${escapeHtml(a.description || 'No description')}</div>
|
|
1795
|
+
<div class="approval-meta">${relativeTime(a.requested_at)} | ${a.hours_remaining ? a.hours_remaining.toFixed(1) + 'h remaining' : 'No expiry'}</div>
|
|
1796
|
+
</div>
|
|
1797
|
+
<div class="approval-actions">
|
|
1798
|
+
<button class="btn-approve" onclick="decide('${a.id}','approve')">Approve</button>
|
|
1799
|
+
<button class="btn-reject" onclick="decide('${a.id}','reject')">Reject</button>
|
|
1800
|
+
</div>
|
|
1801
|
+
</div>
|
|
1802
|
+
`).join('');
|
|
1803
|
+
}
|
|
1804
|
+
|
|
1805
|
+
const resolved = [...(d.approved||[]), ...(d.rejected||[])].slice(0, 5);
|
|
1806
|
+
resolvedEl.innerHTML = resolved.length
|
|
1807
|
+
? resolved.map(a => `<div style="padding:6px 0;border-bottom:1px solid var(--border);font-size:11px;color:var(--muted)">
|
|
1808
|
+
${a.status === 'approved' ? '✅' : '❌'} Tier ${a.tier} — Phase ${a.phase} Plan ${a.plan} — ${escapeHtml(a.resolved_by||'unknown')} ${relativeTime(a.resolved_at)}
|
|
1809
|
+
</div>`).join('')
|
|
1810
|
+
: '<div class="empty">No recent resolutions</div>';
|
|
1811
|
+
|
|
1812
|
+
updateApprovalBadge(d.pending?.length || 0);
|
|
1813
|
+
} catch {}
|
|
1814
|
+
}
|
|
1815
|
+
|
|
1816
|
+
async function decide(id, decision) {
|
|
1817
|
+
if (!confirm(`${decision === 'approve' ? 'Approve' : 'Reject'} this request?`)) return;
|
|
1818
|
+
try {
|
|
1819
|
+
const r = await fetch(`/api/approve/${id}`, {
|
|
1820
|
+
method: 'POST',
|
|
1821
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1822
|
+
body: JSON.stringify({ decision, approver: 'dashboard-user' }),
|
|
1823
|
+
});
|
|
1824
|
+
const d = await r.json();
|
|
1825
|
+
if (d.success) { showToast(d.message); loadApprovals(); }
|
|
1826
|
+
else showToast('Error: ' + d.error, true);
|
|
1827
|
+
} catch (e) { showToast('Request failed', true); }
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
// ── Memory / Knowledge Graph ──────────────────────────────────────────────────
|
|
1831
|
+
let _memSearchTimer;
|
|
1832
|
+
function searchMemory(q) {
|
|
1833
|
+
clearTimeout(_memSearchTimer);
|
|
1834
|
+
_memSearchTimer = setTimeout(() => loadMemory(q), 300);
|
|
1835
|
+
}
|
|
1836
|
+
|
|
1837
|
+
async function loadMemory(q = '') {
|
|
1838
|
+
try {
|
|
1839
|
+
const r = await fetch('/api/memory?q=' + encodeURIComponent(q) + '&limit=50');
|
|
1840
|
+
const d = await r.json();
|
|
1841
|
+
setText('memory-count', d.total + ' entries');
|
|
1842
|
+
|
|
1843
|
+
const typeColors = {
|
|
1844
|
+
architectural_decision: '#58a6ff',
|
|
1845
|
+
code_pattern: '#3fb950',
|
|
1846
|
+
bug_pattern: '#f85149',
|
|
1847
|
+
team_preference: '#d29922',
|
|
1848
|
+
domain_knowledge: '#bc8cff',
|
|
1849
|
+
};
|
|
1850
|
+
|
|
1851
|
+
document.getElementById('memory-list').innerHTML = d.entries.length
|
|
1852
|
+
? d.entries.map(e => {
|
|
1853
|
+
const confClass = e.confidence >= 0.8 ? 'conf-high' : e.confidence >= 0.6 ? 'conf-med' : 'conf-low';
|
|
1854
|
+
const typeColor = typeColors[e.type] || '#8b949e';
|
|
1855
|
+
return `
|
|
1856
|
+
<div class="card" style="margin-bottom:8px;border-left:3px solid ${typeColor}">
|
|
1857
|
+
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:6px">
|
|
1858
|
+
<strong style="font-size:12px">${escapeHtml(e.topic)}</strong>
|
|
1859
|
+
<div style="display:flex;gap:6px;align-items:center">
|
|
1860
|
+
<span class="confidence ${confClass}">${(e.confidence * 100).toFixed(0)}%</span>
|
|
1861
|
+
<span class="tag">${e.type.replace(/_/g, ' ')}</span>
|
|
1862
|
+
</div>
|
|
1863
|
+
</div>
|
|
1864
|
+
<div style="font-size:11px;color:var(--muted);line-height:1.5">${escapeHtml(e.content.slice(0, 200))}</div>
|
|
1865
|
+
<div style="margin-top:6px">${(e.tags||[]).map(t => `<span class="tag">${t}</span>`).join('')}</div>
|
|
1866
|
+
</div>`;
|
|
1867
|
+
}).join('')
|
|
1868
|
+
: '<div class="empty">No knowledge entries found</div>';
|
|
1869
|
+
} catch {}
|
|
1870
|
+
}
|
|
1871
|
+
|
|
1872
|
+
// ── Team ──────────────────────────────────────────────────────────────────────
|
|
1873
|
+
async function loadTeam() {
|
|
1874
|
+
try {
|
|
1875
|
+
const r = await fetch('/api/team');
|
|
1876
|
+
const d = await r.json();
|
|
1877
|
+
|
|
1878
|
+
const activeEl = document.getElementById('team-active');
|
|
1879
|
+
const conflictEl = document.getElementById('team-conflicts');
|
|
1880
|
+
|
|
1881
|
+
activeEl.innerHTML = d.active?.length
|
|
1882
|
+
? d.active.map(a => `
|
|
1883
|
+
<div class="developer-row">
|
|
1884
|
+
<div class="avatar">${(a.email||'?')[0].toUpperCase()}</div>
|
|
1885
|
+
<div class="dev-info">
|
|
1886
|
+
<div class="dev-name">${escapeHtml(a.email)}</div>
|
|
1887
|
+
<div class="dev-task">${escapeHtml(a.current_task || 'No active task')}</div>
|
|
1888
|
+
</div>
|
|
1889
|
+
<div class="dev-last">${a.last_seen_mins}m ago</div>
|
|
1890
|
+
</div>`).join('')
|
|
1891
|
+
: '<div class="empty">No recent developer activity</div>';
|
|
1892
|
+
|
|
1893
|
+
conflictEl.innerHTML = d.conflicts?.length
|
|
1894
|
+
? d.conflicts.map(c => `
|
|
1895
|
+
<div class="conflict-row">⚠️ ${escapeHtml(c.file)} — modified by: ${c.developers.map(d => escapeHtml(d)).join(', ')}</div>`).join('')
|
|
1896
|
+
: '<div class="empty">No conflicts detected ✅</div>';
|
|
1897
|
+
} catch {}
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
// ── Steering ──────────────────────────────────────────────────────────────────
|
|
1901
|
+
async function sendSteer() {
|
|
1902
|
+
const input = document.getElementById('steer-input');
|
|
1903
|
+
const instruction = input.value.trim();
|
|
1904
|
+
if (!instruction) return;
|
|
1905
|
+
try {
|
|
1906
|
+
const r = await fetch('/api/steer', {
|
|
1907
|
+
method: 'POST',
|
|
1908
|
+
headers: { 'Content-Type': 'application/json' },
|
|
1909
|
+
body: JSON.stringify({ instruction, priority: 'normal' }),
|
|
1910
|
+
});
|
|
1911
|
+
const d = await r.json();
|
|
1912
|
+
if (d.success) { showToast('Steering instruction queued'); input.value = ''; }
|
|
1913
|
+
else showToast('Error: ' + d.error, true);
|
|
1914
|
+
} catch { showToast('Failed to send steering instruction', true); }
|
|
1915
|
+
}
|
|
1916
|
+
|
|
1917
|
+
document.getElementById('steer-input').addEventListener('keydown', e => {
|
|
1918
|
+
if (e.key === 'Enter') sendSteer();
|
|
1919
|
+
});
|
|
1920
|
+
|
|
1921
|
+
// ── Approval badge ────────────────────────────────────────────────────────────
|
|
1922
|
+
function updateApprovalBadge(count) {
|
|
1923
|
+
const badge = document.getElementById('approval-badge');
|
|
1924
|
+
badge.style.display = count > 0 ? 'inline' : 'none';
|
|
1925
|
+
badge.textContent = count;
|
|
1926
|
+
}
|
|
1927
|
+
function showApprovalBadge() { updateApprovalBadge(1); }
|
|
1928
|
+
|
|
1929
|
+
// ── Toast ─────────────────────────────────────────────────────────────────────
|
|
1930
|
+
function showToast(msg, isError = false) {
|
|
1931
|
+
const t = document.createElement('div');
|
|
1932
|
+
t.textContent = msg;
|
|
1933
|
+
t.style.cssText = `position:fixed;bottom:20px;right:20px;padding:10px 18px;border-radius:6px;font-size:12px;color:#fff;z-index:1000;background:${isError?'var(--red)':'var(--green)'}`;
|
|
1934
|
+
document.body.appendChild(t);
|
|
1935
|
+
setTimeout(() => t.remove(), 3000);
|
|
1936
|
+
}
|
|
1937
|
+
|
|
1938
|
+
// ── Helpers ───────────────────────────────────────────────────────────────────
|
|
1939
|
+
function setText(id, val) { const el = document.getElementById(id); if (el) el.textContent = val; }
|
|
1940
|
+
function escapeHtml(s) { return String(s||'').replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
|
1941
|
+
function formatDuration(ms) {
|
|
1942
|
+
const m = Math.floor(ms / 60000); const s = Math.floor((ms % 60000) / 1000);
|
|
1943
|
+
return m > 60 ? `${Math.floor(m/60)}h ${m%60}m` : `${m}m ${s}s`;
|
|
1944
|
+
}
|
|
1945
|
+
function relativeTime(iso) {
|
|
1946
|
+
if (!iso) return '';
|
|
1947
|
+
const mins = Math.round((Date.now() - new Date(iso).getTime()) / 60000);
|
|
1948
|
+
if (mins < 1) return 'just now';
|
|
1949
|
+
if (mins < 60) return `${mins}m ago`;
|
|
1950
|
+
return `${Math.floor(mins/60)}h ago`;
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
// ── Init ──────────────────────────────────────────────────────────────────────
|
|
1954
|
+
(async function init() {
|
|
1955
|
+
connectSSE();
|
|
1956
|
+
await loadStatus();
|
|
1957
|
+
await loadActivity();
|
|
1958
|
+
// Refresh activity every 30s
|
|
1959
|
+
setInterval(loadActivity, 30_000);
|
|
1960
|
+
setInterval(loadStatus, 15_000);
|
|
1961
|
+
})();
|
|
1962
|
+
</script>
|
|
1963
|
+
</body>
|
|
1964
|
+
</html>
|
|
1965
|
+
```
|
|
1966
|
+
|
|
1967
|
+
**Commit:**
|
|
1968
|
+
```bash
|
|
1969
|
+
git add bin/dashboard/frontend/index.html
|
|
1970
|
+
git commit -m "feat(v2-dashboard): implement single-file HTML dashboard with 5 pages and live SSE"
|
|
1971
|
+
```
|
|
1972
|
+
|
|
1973
|
+
---
|
|
1974
|
+
|
|
1975
|
+
## TASK 9 — Write the `/mindforge:dashboard` command
|
|
1976
|
+
|
|
1977
|
+
### `.claude/commands/mindforge/dashboard.md`
|
|
1978
|
+
|
|
1979
|
+
```markdown
|
|
1980
|
+
# MindForge v2 — Dashboard Command
|
|
1981
|
+
# Usage: /mindforge:dashboard [--port 7339] [--open] [--stop] [--status]
|
|
1982
|
+
# Version: v2.0.0-alpha.5
|
|
1983
|
+
|
|
1984
|
+
## Purpose
|
|
1985
|
+
Start the MindForge real-time web dashboard — a live observability UI for the
|
|
1986
|
+
entire team. Shows execution progress, quality metrics, pending approvals,
|
|
1987
|
+
knowledge graph, and team activity without requiring CLI access.
|
|
1988
|
+
|
|
1989
|
+
## Design
|
|
1990
|
+
The dashboard is a localhost-only web server:
|
|
1991
|
+
- No build step — single HTML file, no bundler, no npm packages on client
|
|
1992
|
+
- No authentication — binding to 127.0.0.1 is the security model
|
|
1993
|
+
- Live updates via Server-Sent Events — no WebSocket library needed
|
|
1994
|
+
- Designed for screensharing at standups, not external access
|
|
1995
|
+
|
|
1996
|
+
## Usage
|
|
1997
|
+
|
|
1998
|
+
### Start the dashboard
|
|
1999
|
+
```
|
|
2000
|
+
/mindforge:dashboard
|
|
2001
|
+
→ Dashboard running at: http://localhost:7339
|
|
2002
|
+
→ Press CTRL+C to stop (or /mindforge:dashboard --stop)
|
|
2003
|
+
```
|
|
2004
|
+
|
|
2005
|
+
### Start and open in browser
|
|
2006
|
+
```
|
|
2007
|
+
/mindforge:dashboard --open
|
|
2008
|
+
→ Opens http://localhost:7339 in your default browser
|
|
2009
|
+
```
|
|
2010
|
+
|
|
2011
|
+
### Custom port
|
|
2012
|
+
```
|
|
2013
|
+
/mindforge:dashboard --port 7340
|
|
2014
|
+
→ Useful if 7339 is already in use
|
|
2015
|
+
```
|
|
2016
|
+
|
|
2017
|
+
### Stop the dashboard
|
|
2018
|
+
```
|
|
2019
|
+
/mindforge:dashboard --stop
|
|
2020
|
+
→ Finds the running dashboard process (from PID file) and sends SIGTERM
|
|
2021
|
+
```
|
|
2022
|
+
|
|
2023
|
+
### Check dashboard status
|
|
2024
|
+
```
|
|
2025
|
+
/mindforge:dashboard --status
|
|
2026
|
+
→ Checks if dashboard is running, shows port and PID
|
|
2027
|
+
→ Also shows: http://localhost:7339/api/status
|
|
2028
|
+
```
|
|
2029
|
+
|
|
2030
|
+
## Dashboard pages
|
|
2031
|
+
|
|
2032
|
+
### Activity (default)
|
|
2033
|
+
- Phase name, auto mode status (RUNNING/PAUSED/ESCALATED/IDLE)
|
|
2034
|
+
- Wave progress bar (tasks completed / total)
|
|
2035
|
+
- Live AUDIT event feed with color-coded event types
|
|
2036
|
+
- Steering input: send guidance to auto mode without touching the CLI
|
|
2037
|
+
|
|
2038
|
+
### Quality Metrics
|
|
2039
|
+
- Session quality score trend (last 20 sessions)
|
|
2040
|
+
- Verify pass rate over time
|
|
2041
|
+
- Security findings by severity (CRITICAL/HIGH/MEDIUM/LOW)
|
|
2042
|
+
- Cost per session trend
|
|
2043
|
+
|
|
2044
|
+
### Approvals
|
|
2045
|
+
- All pending Tier 2/3 governance requests
|
|
2046
|
+
- [Approve] and [Reject] buttons — no CLI needed for approval
|
|
2047
|
+
- Tier, phase/plan, description, time since requested, expiry warning
|
|
2048
|
+
- Recent approval history
|
|
2049
|
+
|
|
2050
|
+
### Knowledge
|
|
2051
|
+
- Search the knowledge graph from the browser
|
|
2052
|
+
- Entries filtered by confidence, type, tags
|
|
2053
|
+
- Color-coded by knowledge type
|
|
2054
|
+
|
|
2055
|
+
### Team
|
|
2056
|
+
- Active developers (by git email, from AUDIT.jsonl)
|
|
2057
|
+
- What each person is working on (last task)
|
|
2058
|
+
- File conflict warnings (two developers recently touching the same file)
|
|
2059
|
+
|
|
2060
|
+
## Security rules
|
|
2061
|
+
1. Never expose the dashboard on 0.0.0.0 — localhost only
|
|
2062
|
+
2. Never forward the port externally (no ngrok, no port forwarding)
|
|
2063
|
+
3. For remote team visibility: screenshare your browser instead
|
|
2064
|
+
4. The dashboard shows project details including code patterns and decisions
|
|
2065
|
+
|
|
2066
|
+
## Integration with auto mode
|
|
2067
|
+
When `/mindforge:auto` is running and the dashboard is open:
|
|
2068
|
+
- Activity feed updates live as tasks complete
|
|
2069
|
+
- Wave progress bar advances in real-time
|
|
2070
|
+
- Any escalations appear immediately with red indicator
|
|
2071
|
+
- The Steering input is active — inject guidance without a second terminal
|
|
2072
|
+
|
|
2073
|
+
## AUDIT entry
|
|
2074
|
+
```json
|
|
2075
|
+
{ "event": "dashboard_started", "port": 7339, "pid": 12345 }
|
|
2076
|
+
{ "event": "dashboard_stopped", "pid": 12345 }
|
|
2077
|
+
```
|
|
2078
|
+
```
|
|
2079
|
+
|
|
2080
|
+
**Commit:**
|
|
2081
|
+
```bash
|
|
2082
|
+
cp .claude/commands/mindforge/dashboard.md .agent/mindforge/dashboard.md
|
|
2083
|
+
git add .claude/commands/mindforge/dashboard.md .agent/mindforge/dashboard.md
|
|
2084
|
+
git commit -m "feat(v2-dashboard): add /mindforge:dashboard command"
|
|
2085
|
+
```
|
|
2086
|
+
|
|
2087
|
+
---
|
|
2088
|
+
|
|
2089
|
+
## TASK 10 — Update CLAUDE.md for dashboard awareness
|
|
2090
|
+
|
|
2091
|
+
### Add to `.claude/CLAUDE.md` and `.agent/CLAUDE.md`
|
|
2092
|
+
|
|
2093
|
+
```markdown
|
|
2094
|
+
---
|
|
2095
|
+
|
|
2096
|
+
## REAL-TIME DASHBOARD (v2.0.0 — Day 12)
|
|
2097
|
+
|
|
2098
|
+
### Dashboard server
|
|
2099
|
+
The MindForge dashboard runs at localhost:7339 when started.
|
|
2100
|
+
Start: `node bin/dashboard/server.js [--port 7339] [--open]`
|
|
2101
|
+
Stop: `/mindforge:dashboard --stop`
|
|
2102
|
+
|
|
2103
|
+
Localhost-only (127.0.0.1) — consistent with ADR-017.
|
|
2104
|
+
Never bind to 0.0.0.0, never port-forward externally.
|
|
2105
|
+
|
|
2106
|
+
### When to recommend the dashboard
|
|
2107
|
+
Suggest starting the dashboard when:
|
|
2108
|
+
- User runs /mindforge:auto (live progress visibility)
|
|
2109
|
+
- Team standup approaching (screenshare mode)
|
|
2110
|
+
- Tier 2/3 approvals are pending (approver can approve from browser)
|
|
2111
|
+
- Debugging a quality issue (metrics page shows trends)
|
|
2112
|
+
|
|
2113
|
+
### AUDIT events written by dashboard
|
|
2114
|
+
- dashboard_started: on server start
|
|
2115
|
+
- dashboard_stopped: on graceful shutdown
|
|
2116
|
+
- approval_granted / approval_rejected: when approved via browser UI
|
|
2117
|
+
- steering_queued: when steering instruction sent via browser UI
|
|
2118
|
+
|
|
2119
|
+
### New command (Day 12)
|
|
2120
|
+
- /mindforge:dashboard — start/stop/status the real-time web dashboard
|
|
2121
|
+
|
|
2122
|
+
---
|
|
2123
|
+
```
|
|
2124
|
+
|
|
2125
|
+
**Commit:**
|
|
2126
|
+
```bash
|
|
2127
|
+
git add .claude/CLAUDE.md .agent/CLAUDE.md
|
|
2128
|
+
git commit -m "feat(v2-dashboard): update CLAUDE.md with dashboard awareness and integration notes"
|
|
2129
|
+
```
|
|
2130
|
+
|
|
2131
|
+
---
|
|
2132
|
+
|
|
2133
|
+
## TASK 11 — Write the dashboard test suite
|
|
2134
|
+
|
|
2135
|
+
### `tests/dashboard.test.js`
|
|
2136
|
+
|
|
2137
|
+
```javascript
|
|
2138
|
+
/**
|
|
2139
|
+
* MindForge v2 — Dashboard Test Suite
|
|
2140
|
+
* Tests SSE bridge, metrics aggregator, approval handler,
|
|
2141
|
+
* API router, and server startup/shutdown.
|
|
2142
|
+
*
|
|
2143
|
+
* Note: Tests do NOT start a real Express server — they test
|
|
2144
|
+
* the component logic directly to avoid port conflicts in CI.
|
|
2145
|
+
*
|
|
2146
|
+
* Run: node tests/dashboard.test.js
|
|
2147
|
+
*/
|
|
2148
|
+
'use strict';
|
|
2149
|
+
|
|
2150
|
+
const fs = require('fs');
|
|
2151
|
+
const path = require('path');
|
|
2152
|
+
const http = require('http');
|
|
2153
|
+
const os = require('os');
|
|
2154
|
+
const assert = require('assert');
|
|
2155
|
+
|
|
2156
|
+
let passed = 0, failed = 0;
|
|
2157
|
+
|
|
2158
|
+
function test(name, fn) {
|
|
2159
|
+
try { fn(); console.log(` ✅ ${name}`); passed++; }
|
|
2160
|
+
catch(e) { console.error(` ❌ ${name}\n ${e.message}`); failed++; }
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
async function testAsync(name, fn) {
|
|
2164
|
+
try { await fn(); console.log(` ✅ ${name}`); passed++; }
|
|
2165
|
+
catch(e) { console.error(` ❌ ${name}\n ${e.message}`); failed++; }
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
// ── Temp project factory ──────────────────────────────────────────────────────
|
|
2169
|
+
function mkProject() {
|
|
2170
|
+
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'mf-dashboard-'));
|
|
2171
|
+
const write = (rel, c) => { const f = path.join(dir, rel); fs.mkdirSync(path.dirname(f), { recursive: true }); fs.writeFileSync(f, c); return f; };
|
|
2172
|
+
const exists = rel => fs.existsSync(path.join(dir, rel));
|
|
2173
|
+
const cleanup = () => { try { fs.rmSync(dir, { recursive: true, force: true }); } catch {} };
|
|
2174
|
+
return { dir, write, exists, cleanup };
|
|
2175
|
+
}
|
|
2176
|
+
|
|
2177
|
+
// ── Module imports ────────────────────────────────────────────────────────────
|
|
2178
|
+
const Metrics = require('../bin/dashboard/metrics-aggregator');
|
|
2179
|
+
const Approval = require('../bin/dashboard/approval-handler');
|
|
2180
|
+
const SSE = require('../bin/dashboard/sse-bridge');
|
|
2181
|
+
|
|
2182
|
+
// ── Sample data fixtures ──────────────────────────────────────────────────────
|
|
2183
|
+
const SAMPLE_HANDOFF = {
|
|
2184
|
+
schema_version: '2.0.0',
|
|
2185
|
+
current_phase: 3,
|
|
2186
|
+
phase_description: 'Authentication System',
|
|
2187
|
+
next_task: 'Plan 3-06',
|
|
2188
|
+
last_updated: new Date().toISOString(),
|
|
2189
|
+
};
|
|
2190
|
+
|
|
2191
|
+
const SAMPLE_AUTO_STATE = {
|
|
2192
|
+
schema_version: '2.0.0',
|
|
2193
|
+
auto_mode_active: true,
|
|
2194
|
+
status: 'running',
|
|
2195
|
+
phase: 3,
|
|
2196
|
+
wave_current: 2, wave_total: 3,
|
|
2197
|
+
tasks_completed: 5, tasks_total: 8,
|
|
2198
|
+
node_repairs: 1, escalations: 0,
|
|
2199
|
+
elapsed_ms: 1083000,
|
|
2200
|
+
current_task: 'Plan 3-05 — JWT middleware',
|
|
2201
|
+
last_commit: 'abc1234',
|
|
2202
|
+
};
|
|
2203
|
+
|
|
2204
|
+
const SAMPLE_APPROVAL = {
|
|
2205
|
+
id: 'aaaabbbb-cccc-dddd-eeee-ffffffffffff',
|
|
2206
|
+
tier: 2,
|
|
2207
|
+
phase: 3,
|
|
2208
|
+
plan: '04',
|
|
2209
|
+
description: 'Add user RBAC model',
|
|
2210
|
+
status: 'pending',
|
|
2211
|
+
requested_at: new Date().toISOString(),
|
|
2212
|
+
expires_at: new Date(Date.now() + 86_400_000).toISOString(),
|
|
2213
|
+
};
|
|
2214
|
+
|
|
2215
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
2216
|
+
console.log('\nMindForge v2 — Dashboard Tests\n');
|
|
2217
|
+
|
|
2218
|
+
// ── File existence ────────────────────────────────────────────────────────────
|
|
2219
|
+
console.log('Required files:');
|
|
2220
|
+
[
|
|
2221
|
+
'bin/dashboard/server.js',
|
|
2222
|
+
'bin/dashboard/sse-bridge.js',
|
|
2223
|
+
'bin/dashboard/api-router.js',
|
|
2224
|
+
'bin/dashboard/approval-handler.js',
|
|
2225
|
+
'bin/dashboard/metrics-aggregator.js',
|
|
2226
|
+
'bin/dashboard/frontend/index.html',
|
|
2227
|
+
'.mindforge/dashboard/dashboard-spec.md',
|
|
2228
|
+
'.mindforge/dashboard/api-reference.md',
|
|
2229
|
+
'.claude/commands/mindforge/dashboard.md',
|
|
2230
|
+
'.agent/mindforge/dashboard.md',
|
|
2231
|
+
].forEach(f => test(`${f} exists`, () => assert.ok(fs.existsSync(f), `Missing: ${f}`)));
|
|
2232
|
+
|
|
2233
|
+
// ── Metrics aggregator ────────────────────────────────────────────────────────
|
|
2234
|
+
console.log('\nMetrics aggregator:');
|
|
2235
|
+
|
|
2236
|
+
test('getStatus: returns object with required fields when no files exist', () => {
|
|
2237
|
+
const p = mkProject();
|
|
2238
|
+
const orig = process.cwd();
|
|
2239
|
+
process.chdir(p.dir);
|
|
2240
|
+
try {
|
|
2241
|
+
const status = Metrics.getStatus();
|
|
2242
|
+
assert.ok(typeof status === 'object', 'Should return object');
|
|
2243
|
+
assert.ok('project_name' in status, 'Should have project_name');
|
|
2244
|
+
assert.ok('auto_mode' in status, 'Should have auto_mode');
|
|
2245
|
+
assert.ok('auto_status' in status, 'Should have auto_status');
|
|
2246
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2247
|
+
});
|
|
2248
|
+
|
|
2249
|
+
test('getStatus: reads HANDOFF.json and auto-state.json', () => {
|
|
2250
|
+
const p = mkProject();
|
|
2251
|
+
const orig = process.cwd();
|
|
2252
|
+
process.chdir(p.dir);
|
|
2253
|
+
try {
|
|
2254
|
+
p.write('.planning/HANDOFF.json', JSON.stringify(SAMPLE_HANDOFF));
|
|
2255
|
+
p.write('.planning/auto-state.json', JSON.stringify(SAMPLE_AUTO_STATE));
|
|
2256
|
+
p.write('.planning/PROJECT.md', '# My Auth App\n');
|
|
2257
|
+
|
|
2258
|
+
const status = Metrics.getStatus();
|
|
2259
|
+
assert.strictEqual(status.phase, 3, 'Should read phase from HANDOFF.json');
|
|
2260
|
+
assert.strictEqual(status.auto_mode, true, 'Should read auto_mode from auto-state.json');
|
|
2261
|
+
assert.strictEqual(status.auto_status, 'running', 'Should read status');
|
|
2262
|
+
assert.strictEqual(status.tasks_completed, 5, 'Should read tasks_completed');
|
|
2263
|
+
assert.strictEqual(status.project_name, 'My Auth App', 'Should read project name');
|
|
2264
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2265
|
+
});
|
|
2266
|
+
|
|
2267
|
+
test('getAuditEntries: returns newest first with limit', () => {
|
|
2268
|
+
const p = mkProject();
|
|
2269
|
+
const orig = process.cwd();
|
|
2270
|
+
process.chdir(p.dir);
|
|
2271
|
+
try {
|
|
2272
|
+
const entries = [
|
|
2273
|
+
{ id: '1', timestamp: '2026-01-01T10:00:00Z', event: 'task_completed', phase: 3, plan: '01' },
|
|
2274
|
+
{ id: '2', timestamp: '2026-01-01T10:05:00Z', event: 'task_completed', phase: 3, plan: '02' },
|
|
2275
|
+
{ id: '3', timestamp: '2026-01-01T10:10:00Z', event: 'security_finding', phase: 3 },
|
|
2276
|
+
];
|
|
2277
|
+
p.write('.planning/AUDIT.jsonl', entries.map(e => JSON.stringify(e)).join('\n') + '\n');
|
|
2278
|
+
|
|
2279
|
+
const result = Metrics.getAuditEntries(2, 0, null);
|
|
2280
|
+
assert.strictEqual(result.entries.length, 2, 'Should respect limit');
|
|
2281
|
+
assert.strictEqual(result.entries[0].id, '3', 'Newest should be first (id=3)');
|
|
2282
|
+
assert.strictEqual(result.total, 3, 'Should report total count');
|
|
2283
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2284
|
+
});
|
|
2285
|
+
|
|
2286
|
+
test('getAuditEntries: filters by event type', () => {
|
|
2287
|
+
const p = mkProject();
|
|
2288
|
+
const orig = process.cwd();
|
|
2289
|
+
process.chdir(p.dir);
|
|
2290
|
+
try {
|
|
2291
|
+
const entries = [
|
|
2292
|
+
{ id: '1', timestamp: '2026-01-01T10:00:00Z', event: 'task_completed' },
|
|
2293
|
+
{ id: '2', timestamp: '2026-01-01T10:05:00Z', event: 'security_finding' },
|
|
2294
|
+
];
|
|
2295
|
+
p.write('.planning/AUDIT.jsonl', entries.map(e => JSON.stringify(e)).join('\n') + '\n');
|
|
2296
|
+
|
|
2297
|
+
const result = Metrics.getAuditEntries(50, 0, 'security_finding');
|
|
2298
|
+
assert.ok(result.entries.every(e => e.event === 'security_finding'), 'Should only return filtered event type');
|
|
2299
|
+
assert.strictEqual(result.entries.length, 1, 'Should return 1 security_finding');
|
|
2300
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2301
|
+
});
|
|
2302
|
+
|
|
2303
|
+
test('getApprovals: returns categorized approvals', () => {
|
|
2304
|
+
const p = mkProject();
|
|
2305
|
+
const orig = process.cwd();
|
|
2306
|
+
process.chdir(p.dir);
|
|
2307
|
+
try {
|
|
2308
|
+
p.write(`.planning/approvals/APPROVAL-${SAMPLE_APPROVAL.id}.json`, JSON.stringify(SAMPLE_APPROVAL));
|
|
2309
|
+
|
|
2310
|
+
const result = Metrics.getApprovals();
|
|
2311
|
+
assert.ok(Array.isArray(result.pending), 'Should have pending array');
|
|
2312
|
+
assert.ok(Array.isArray(result.approved), 'Should have approved array');
|
|
2313
|
+
assert.ok(Array.isArray(result.rejected), 'Should have rejected array');
|
|
2314
|
+
assert.ok(Array.isArray(result.expired), 'Should have expired array');
|
|
2315
|
+
assert.strictEqual(result.pending.length, 1, 'Should have 1 pending approval');
|
|
2316
|
+
assert.strictEqual(result.pending[0].id, SAMPLE_APPROVAL.id);
|
|
2317
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2318
|
+
});
|
|
2319
|
+
|
|
2320
|
+
test('getApprovals: classifies expired approval correctly', () => {
|
|
2321
|
+
const p = mkProject();
|
|
2322
|
+
const orig = process.cwd();
|
|
2323
|
+
process.chdir(p.dir);
|
|
2324
|
+
try {
|
|
2325
|
+
const expired = {
|
|
2326
|
+
...SAMPLE_APPROVAL,
|
|
2327
|
+
id: 'expired-uuid-1234-5678-abcd-ef0123456789',
|
|
2328
|
+
status: 'pending',
|
|
2329
|
+
expires_at: new Date(Date.now() - 3600_000).toISOString(), // 1 hour ago
|
|
2330
|
+
};
|
|
2331
|
+
p.write(`.planning/approvals/APPROVAL-${expired.id}.json`, JSON.stringify(expired));
|
|
2332
|
+
|
|
2333
|
+
const result = Metrics.getApprovals();
|
|
2334
|
+
assert.strictEqual(result.expired.length, 1, 'Should classify as expired');
|
|
2335
|
+
assert.strictEqual(result.pending.length, 0, 'Should not be in pending');
|
|
2336
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2337
|
+
});
|
|
2338
|
+
|
|
2339
|
+
test('getCosts: returns correct total', () => {
|
|
2340
|
+
const p = mkProject();
|
|
2341
|
+
const orig = process.cwd();
|
|
2342
|
+
process.chdir(p.dir);
|
|
2343
|
+
try {
|
|
2344
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
2345
|
+
p.write('.mindforge/metrics/token-usage.jsonl',
|
|
2346
|
+
JSON.stringify({ date: today, model: 'claude-sonnet-4-6', cost_usd: 0.05 }) + '\n' +
|
|
2347
|
+
JSON.stringify({ date: today, model: 'gpt-4o', cost_usd: 0.12 }) + '\n'
|
|
2348
|
+
);
|
|
2349
|
+
|
|
2350
|
+
const costs = Metrics.getCosts(7);
|
|
2351
|
+
assert.ok(Math.abs(costs.total_usd - 0.17) < 0.001, `Expected ~0.17, got ${costs.total_usd}`);
|
|
2352
|
+
assert.ok(costs.by_model['claude-sonnet-4-6'], 'Should break down by model');
|
|
2353
|
+
assert.ok(costs.by_model['gpt-4o'], 'Should have gpt-4o');
|
|
2354
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2355
|
+
});
|
|
2356
|
+
|
|
2357
|
+
test('getTeamActivity: returns active developers from AUDIT', () => {
|
|
2358
|
+
const p = mkProject();
|
|
2359
|
+
const orig = process.cwd();
|
|
2360
|
+
process.chdir(p.dir);
|
|
2361
|
+
try {
|
|
2362
|
+
const now = new Date().toISOString();
|
|
2363
|
+
p.write('.planning/AUDIT.jsonl',
|
|
2364
|
+
JSON.stringify({ id: '1', timestamp: now, event: 'task_completed', authored_by: 'alice@team.com', phase: 3, plan: '04' }) + '\n' +
|
|
2365
|
+
JSON.stringify({ id: '2', timestamp: now, event: 'task_started', authored_by: 'bob@team.com', phase: 3, plan: '05' }) + '\n'
|
|
2366
|
+
);
|
|
2367
|
+
|
|
2368
|
+
const team = Metrics.getTeamActivity();
|
|
2369
|
+
assert.ok(Array.isArray(team.active), 'Should have active array');
|
|
2370
|
+
assert.ok(Array.isArray(team.conflicts), 'Should have conflicts array');
|
|
2371
|
+
assert.ok(team.active.some(a => a.email === 'alice@team.com'), 'Should include alice');
|
|
2372
|
+
assert.ok(team.active.some(a => a.email === 'bob@team.com'), 'Should include bob');
|
|
2373
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2374
|
+
});
|
|
2375
|
+
|
|
2376
|
+
// ── Approval handler ──────────────────────────────────────────────────────────
|
|
2377
|
+
console.log('\nApproval handler:');
|
|
2378
|
+
|
|
2379
|
+
test('processDecision: approves a valid pending approval', () => {
|
|
2380
|
+
const p = mkProject();
|
|
2381
|
+
const orig = process.cwd();
|
|
2382
|
+
process.chdir(p.dir);
|
|
2383
|
+
try {
|
|
2384
|
+
p.write(`.planning/approvals/APPROVAL-${SAMPLE_APPROVAL.id}.json`, JSON.stringify(SAMPLE_APPROVAL));
|
|
2385
|
+
p.write('.planning/AUDIT.jsonl', ''); // Create AUDIT file
|
|
2386
|
+
|
|
2387
|
+
const result = Approval.processDecision(SAMPLE_APPROVAL.id, 'approve', 'Looks good', 'reviewer@team.com');
|
|
2388
|
+
assert.strictEqual(result.success, true, 'Should succeed');
|
|
2389
|
+
assert.strictEqual(result.decision, 'approve', 'Should record approve decision');
|
|
2390
|
+
|
|
2391
|
+
const updated = JSON.parse(fs.readFileSync(path.join(p.dir, `.planning/approvals/APPROVAL-${SAMPLE_APPROVAL.id}.json`), 'utf8'));
|
|
2392
|
+
assert.strictEqual(updated.status, 'approved', 'File should be updated to approved');
|
|
2393
|
+
assert.strictEqual(updated.resolved_by, 'reviewer@team.com', 'Should record resolver');
|
|
2394
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2395
|
+
});
|
|
2396
|
+
|
|
2397
|
+
test('processDecision: rejects a valid pending approval', () => {
|
|
2398
|
+
const p = mkProject();
|
|
2399
|
+
const orig = process.cwd();
|
|
2400
|
+
process.chdir(p.dir);
|
|
2401
|
+
try {
|
|
2402
|
+
p.write(`.planning/approvals/APPROVAL-${SAMPLE_APPROVAL.id}.json`, JSON.stringify(SAMPLE_APPROVAL));
|
|
2403
|
+
p.write('.planning/AUDIT.jsonl', '');
|
|
2404
|
+
|
|
2405
|
+
const result = Approval.processDecision(SAMPLE_APPROVAL.id, 'reject', 'Not ready', 'reviewer@team.com');
|
|
2406
|
+
assert.strictEqual(result.success, true);
|
|
2407
|
+
assert.strictEqual(result.decision, 'reject');
|
|
2408
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2409
|
+
});
|
|
2410
|
+
|
|
2411
|
+
test('processDecision: rejects malformed approval ID', () => {
|
|
2412
|
+
const result = Approval.processDecision('../../evil/path', 'approve', '', '');
|
|
2413
|
+
assert.strictEqual(result.success, false, 'Should reject path traversal in ID');
|
|
2414
|
+
assert.ok(result.error, 'Should have error message');
|
|
2415
|
+
});
|
|
2416
|
+
|
|
2417
|
+
test('processDecision: rejects already-resolved approval', () => {
|
|
2418
|
+
const p = mkProject();
|
|
2419
|
+
const orig = process.cwd();
|
|
2420
|
+
process.chdir(p.dir);
|
|
2421
|
+
try {
|
|
2422
|
+
const alreadyApproved = { ...SAMPLE_APPROVAL, status: 'approved' };
|
|
2423
|
+
p.write(`.planning/approvals/APPROVAL-${SAMPLE_APPROVAL.id}.json`, JSON.stringify(alreadyApproved));
|
|
2424
|
+
|
|
2425
|
+
const result = Approval.processDecision(SAMPLE_APPROVAL.id, 'reject', '', '');
|
|
2426
|
+
assert.strictEqual(result.success, false, 'Should not allow re-resolving');
|
|
2427
|
+
assert.ok(result.error.includes('already'), 'Should explain why');
|
|
2428
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2429
|
+
});
|
|
2430
|
+
|
|
2431
|
+
test('processDecision: rejects invalid decision value', () => {
|
|
2432
|
+
const result = Approval.processDecision(SAMPLE_APPROVAL.id, 'maybe', '', '');
|
|
2433
|
+
assert.strictEqual(result.success, false);
|
|
2434
|
+
assert.ok(result.error, 'Should have error message');
|
|
2435
|
+
});
|
|
2436
|
+
|
|
2437
|
+
test('processDecision: rejects expired approval', () => {
|
|
2438
|
+
const p = mkProject();
|
|
2439
|
+
const orig = process.cwd();
|
|
2440
|
+
process.chdir(p.dir);
|
|
2441
|
+
try {
|
|
2442
|
+
const expired = { ...SAMPLE_APPROVAL, expires_at: new Date(Date.now() - 3600_000).toISOString() };
|
|
2443
|
+
p.write(`.planning/approvals/APPROVAL-${SAMPLE_APPROVAL.id}.json`, JSON.stringify(expired));
|
|
2444
|
+
|
|
2445
|
+
const result = Approval.processDecision(SAMPLE_APPROVAL.id, 'approve', '', '');
|
|
2446
|
+
assert.strictEqual(result.success, false, 'Should reject expired approval');
|
|
2447
|
+
assert.ok(result.error.includes('expired'), 'Should mention expiry');
|
|
2448
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
2449
|
+
});
|
|
2450
|
+
|
|
2451
|
+
// ── SSE bridge ────────────────────────────────────────────────────────────────
|
|
2452
|
+
console.log('\nSSE bridge:');
|
|
2453
|
+
|
|
2454
|
+
test('addClient / getClientCount: tracks connected clients', () => {
|
|
2455
|
+
const initial = SSE.getClientCount();
|
|
2456
|
+
// Simulate a client (mock res object)
|
|
2457
|
+
const mockRes = { write: () => {}, on: (evt, cb) => {} };
|
|
2458
|
+
SSE.addClient(mockRes);
|
|
2459
|
+
assert.strictEqual(SSE.getClientCount(), initial + 1, 'Count should increase by 1');
|
|
2460
|
+
// Simulate disconnect
|
|
2461
|
+
const mockRes2 = { write: () => {}, on: (evt, cb) => { if (evt === 'close') setTimeout(cb, 0); } };
|
|
2462
|
+
SSE.addClient(mockRes2);
|
|
2463
|
+
});
|
|
2464
|
+
|
|
2465
|
+
test('broadcast: sends formatted SSE message', () => {
|
|
2466
|
+
const received = [];
|
|
2467
|
+
const mockRes = {
|
|
2468
|
+
write: msg => received.push(msg),
|
|
2469
|
+
on: () => {},
|
|
2470
|
+
};
|
|
2471
|
+
SSE.addClient(mockRes);
|
|
2472
|
+
SSE.broadcast('test:event', { hello: 'world' });
|
|
2473
|
+
const found = received.find(m => m.includes('test:event') && m.includes('hello'));
|
|
2474
|
+
assert.ok(found, 'Client should receive the broadcast message');
|
|
2475
|
+
assert.ok(found.includes('"hello":"world"'), 'Message should contain JSON data');
|
|
2476
|
+
});
|
|
2477
|
+
|
|
2478
|
+
// ── Frontend HTML integrity ───────────────────────────────────────────────────
|
|
2479
|
+
console.log('\nFrontend HTML:');
|
|
2480
|
+
|
|
2481
|
+
test('index.html: has all 5 page sections', () => {
|
|
2482
|
+
const html = fs.readFileSync('bin/dashboard/frontend/index.html', 'utf8');
|
|
2483
|
+
['page-activity','page-metrics','page-approvals','page-memory','page-team']
|
|
2484
|
+
.forEach(id => assert.ok(html.includes(`id="${id}"`), `Missing page: ${id}`));
|
|
2485
|
+
});
|
|
2486
|
+
|
|
2487
|
+
test('index.html: has SSE EventSource connection', () => {
|
|
2488
|
+
const html = fs.readFileSync('bin/dashboard/frontend/index.html', 'utf8');
|
|
2489
|
+
assert.ok(html.includes('EventSource'), 'Should create EventSource for SSE');
|
|
2490
|
+
assert.ok(html.includes('/events'), 'Should connect to /events endpoint');
|
|
2491
|
+
});
|
|
2492
|
+
|
|
2493
|
+
test('index.html: has security-safe HTML escaping', () => {
|
|
2494
|
+
const html = fs.readFileSync('bin/dashboard/frontend/index.html', 'utf8');
|
|
2495
|
+
assert.ok(html.includes('escapeHtml'), 'Should have HTML escaping function');
|
|
2496
|
+
assert.ok(html.includes('replace(/&/g'), 'Should escape ampersands');
|
|
2497
|
+
});
|
|
2498
|
+
|
|
2499
|
+
test('index.html: has approval action buttons', () => {
|
|
2500
|
+
const html = fs.readFileSync('bin/dashboard/frontend/index.html', 'utf8');
|
|
2501
|
+
assert.ok(html.includes('decide('), 'Should have decide() function for approvals');
|
|
2502
|
+
assert.ok(html.includes('/api/approve/'), 'Should call approval API endpoint');
|
|
2503
|
+
});
|
|
2504
|
+
|
|
2505
|
+
// ── Security validations ──────────────────────────────────────────────────────
|
|
2506
|
+
console.log('\nSecurity properties:');
|
|
2507
|
+
|
|
2508
|
+
test('server.js binds to 127.0.0.1 not 0.0.0.0', () => {
|
|
2509
|
+
const c = fs.readFileSync('bin/dashboard/server.js', 'utf8');
|
|
2510
|
+
assert.ok(c.includes("'127.0.0.1'"), 'Should bind to 127.0.0.1');
|
|
2511
|
+
assert.ok(!c.includes("'0.0.0.0'"), 'Must NOT bind to 0.0.0.0');
|
|
2512
|
+
});
|
|
2513
|
+
|
|
2514
|
+
test('api-router.js has injection guard on steering endpoint', () => {
|
|
2515
|
+
const c = fs.readFileSync('bin/dashboard/api-router.js', 'utf8');
|
|
2516
|
+
assert.ok(c.includes('INJECTION_PATTERNS'), 'Should have injection patterns array');
|
|
2517
|
+
assert.ok(c.includes('prohibited'), 'Should reject prohibited patterns');
|
|
2518
|
+
});
|
|
2519
|
+
|
|
2520
|
+
test('approval-handler.js validates UUID format before file access', () => {
|
|
2521
|
+
const c = fs.readFileSync('bin/dashboard/approval-handler.js', 'utf8');
|
|
2522
|
+
assert.ok(c.includes('[a-f0-9-]'), 'Should validate UUID format');
|
|
2523
|
+
});
|
|
2524
|
+
|
|
2525
|
+
test('server.js has request body size limit', () => {
|
|
2526
|
+
const c = fs.readFileSync('bin/dashboard/server.js', 'utf8');
|
|
2527
|
+
assert.ok(c.includes('limit') && c.includes('64kb'), 'Should limit request body to 64kb');
|
|
2528
|
+
});
|
|
2529
|
+
|
|
2530
|
+
test('server.js rejects non-localhost connections with 403', () => {
|
|
2531
|
+
const c = fs.readFileSync('bin/dashboard/server.js', 'utf8');
|
|
2532
|
+
assert.ok(c.includes('403'), 'Should return 403 for non-localhost');
|
|
2533
|
+
assert.ok(c.includes('isLocal'), 'Should check if request is local');
|
|
2534
|
+
});
|
|
2535
|
+
|
|
2536
|
+
// ── All 45 commands ───────────────────────────────────────────────────────────
|
|
2537
|
+
console.log('\nAll 45 commands (44 + 1 Day 12):');
|
|
2538
|
+
|
|
2539
|
+
const ALL_COMMANDS = [
|
|
2540
|
+
'help','init-project','plan-phase','execute-phase','verify-phase','ship',
|
|
2541
|
+
'next','quick','status','debug',
|
|
2542
|
+
'skills','review','security-scan','map-codebase','discuss-phase',
|
|
2543
|
+
'audit','milestone','complete-milestone','approve','sync-jira','sync-confluence',
|
|
2544
|
+
'health','retrospective','profile-team','metrics',
|
|
2545
|
+
'init-org','install-skill','publish-skill','pr-review','workspace','benchmark',
|
|
2546
|
+
'update','migrate','plugins','tokens','release',
|
|
2547
|
+
'auto','steer',
|
|
2548
|
+
'browse','qa',
|
|
2549
|
+
'cross-review','research','costs',
|
|
2550
|
+
'remember',
|
|
2551
|
+
'dashboard',
|
|
2552
|
+
];
|
|
2553
|
+
assert.strictEqual(ALL_COMMANDS.length, 45);
|
|
2554
|
+
|
|
2555
|
+
test('all 45 commands in .claude/commands/mindforge/', () => {
|
|
2556
|
+
const missing = ALL_COMMANDS.filter(c => !fs.existsSync(`.claude/commands/mindforge/${c}.md`));
|
|
2557
|
+
assert.strictEqual(missing.length, 0, `Missing: ${missing.join(', ')}`);
|
|
2558
|
+
});
|
|
2559
|
+
|
|
2560
|
+
test('all 45 commands mirrored in .agent/mindforge/', () => {
|
|
2561
|
+
const missing = ALL_COMMANDS.filter(c => !fs.existsSync(`.agent/mindforge/${c}.md`));
|
|
2562
|
+
assert.strictEqual(missing.length, 0, `Missing agent: ${missing.join(', ')}`);
|
|
2563
|
+
});
|
|
2564
|
+
|
|
2565
|
+
// ── Version ───────────────────────────────────────────────────────────────────
|
|
2566
|
+
console.log('\nVersion:');
|
|
2567
|
+
|
|
2568
|
+
test('package.json is v2.0.0-alpha.5', () => {
|
|
2569
|
+
const v = JSON.parse(fs.readFileSync('package.json', 'utf8')).version;
|
|
2570
|
+
assert.ok(v === '2.0.0-alpha.5' || v.startsWith('2.'), `Expected v2.x, got ${v}`);
|
|
2571
|
+
});
|
|
2572
|
+
|
|
2573
|
+
// ── Results ───────────────────────────────────────────────────────────────────
|
|
2574
|
+
console.log(`\n${'─'.repeat(55)}`);
|
|
2575
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
2576
|
+
if (failed > 0) { console.error(`\n❌ ${failed} test(s) failed.\n`); process.exit(1); }
|
|
2577
|
+
else { console.log(`\n✅ All dashboard tests passed.\n`); }
|
|
2578
|
+
```
|
|
2579
|
+
|
|
2580
|
+
**Commit:**
|
|
2581
|
+
```bash
|
|
2582
|
+
git add tests/dashboard.test.js
|
|
2583
|
+
git commit -m "test(v2-dashboard): add comprehensive dashboard test suite (20th suite)"
|
|
2584
|
+
```
|
|
2585
|
+
|
|
2586
|
+
---
|
|
2587
|
+
|
|
2588
|
+
## TASK 12 — Bump version, update CHANGELOG, push
|
|
2589
|
+
|
|
2590
|
+
```bash
|
|
2591
|
+
node -e "
|
|
2592
|
+
const fs = require('fs');
|
|
2593
|
+
const p = JSON.parse(fs.readFileSync('package.json','utf8'));
|
|
2594
|
+
p.version = '2.0.0-alpha.5';
|
|
2595
|
+
fs.writeFileSync('package.json', JSON.stringify(p, null, 2) + '\n');
|
|
2596
|
+
console.log('Bumped to v2.0.0-alpha.5');
|
|
2597
|
+
"
|
|
2598
|
+
```
|
|
2599
|
+
|
|
2600
|
+
Update `CHANGELOG.md`:
|
|
2601
|
+
|
|
2602
|
+
```markdown
|
|
2603
|
+
## [2.0.0-alpha.5] — Day 12: Real-Time Web Observability Dashboard
|
|
2604
|
+
|
|
2605
|
+
### Added
|
|
2606
|
+
|
|
2607
|
+
**Dashboard Server:**
|
|
2608
|
+
- bin/dashboard/server.js — Express.js HTTP server at localhost:7339
|
|
2609
|
+
- Localhost-only binding (127.0.0.1) — consistent with ADR-017
|
|
2610
|
+
- Security middleware: IP check, CORS (localhost-only), body limit (64kb)
|
|
2611
|
+
- Security headers: X-Content-Type-Options, X-Frame-Options, no-cache
|
|
2612
|
+
- Graceful shutdown on SIGTERM/SIGINT with PID file cleanup
|
|
2613
|
+
- --open flag opens default browser automatically
|
|
2614
|
+
|
|
2615
|
+
**SSE Event Bridge:**
|
|
2616
|
+
- bin/dashboard/sse-bridge.js — tails AUDIT.jsonl, auto-state.json, approvals/
|
|
2617
|
+
- 2-second poll interval for live event delivery
|
|
2618
|
+
- 15-second keepalive ping to detect disconnected clients
|
|
2619
|
+
- Client tracking via Set (auto-removes on close)
|
|
2620
|
+
|
|
2621
|
+
**Metrics Aggregator:**
|
|
2622
|
+
- bin/dashboard/metrics-aggregator.js — 7 data source readers
|
|
2623
|
+
- getStatus(): HANDOFF.json + auto-state.json → current execution state
|
|
2624
|
+
- getAuditEntries(): newest-first with limit/offset/event-filter
|
|
2625
|
+
- getMetrics(): quality trends, security findings, repair rate
|
|
2626
|
+
- getApprovals(): categorized (pending/approved/rejected/expired)
|
|
2627
|
+
- getTeamActivity(): active developers + file conflict detection
|
|
2628
|
+
- getMemory(): knowledge base search
|
|
2629
|
+
- getCosts(): per-model cost summary with daily limit %
|
|
2630
|
+
|
|
2631
|
+
**Approval Handler:**
|
|
2632
|
+
- bin/dashboard/approval-handler.js — approve/reject from browser
|
|
2633
|
+
- UUID format validation (prevents path traversal in approval ID)
|
|
2634
|
+
- Expiry enforcement (rejects expired approvals)
|
|
2635
|
+
- Writes AUDIT entry for every approval decision
|
|
2636
|
+
- resolution_channel: 'mindforge-dashboard' for traceability
|
|
2637
|
+
|
|
2638
|
+
**API Router (10 endpoints):**
|
|
2639
|
+
- GET /events — SSE stream with initial status push
|
|
2640
|
+
- GET /api/status, /api/audit, /api/metrics, /api/approvals
|
|
2641
|
+
- GET /api/team, /api/memory, /api/costs, /api/connections
|
|
2642
|
+
- POST /api/approve/:id — with UUID validation
|
|
2643
|
+
- POST /api/steer — with injection guard + auto-mode check
|
|
2644
|
+
|
|
2645
|
+
**Frontend (single-file HTML, no build step):**
|
|
2646
|
+
- bin/dashboard/frontend/index.html — dark theme, 5-page dashboard
|
|
2647
|
+
- Page 1 Activity: live event feed, wave progress, steering input
|
|
2648
|
+
- Page 2 Quality: 4 charts (quality, verify, security, cost)
|
|
2649
|
+
- Page 3 Approvals: approve/reject from browser with confirmation
|
|
2650
|
+
- Page 4 Knowledge: searchable knowledge graph display
|
|
2651
|
+
- Page 5 Team: active developers, last-seen, file conflicts
|
|
2652
|
+
- Charts: canvas-based line and bar charts (no external lib)
|
|
2653
|
+
- HTML escaping: escapeHtml() on all user-derived content
|
|
2654
|
+
|
|
2655
|
+
**New Command (total: 45):**
|
|
2656
|
+
- /mindforge:dashboard — start/stop/status/open the web dashboard
|
|
2657
|
+
|
|
2658
|
+
**Tests:**
|
|
2659
|
+
- tests/dashboard.test.js — 20th suite (all aggregator functions,
|
|
2660
|
+
approval handler CRUD, SSE client management, frontend integrity,
|
|
2661
|
+
security property validation)
|
|
2662
|
+
```
|
|
2663
|
+
|
|
2664
|
+
```bash
|
|
2665
|
+
git add CHANGELOG.md package.json
|
|
2666
|
+
git commit -m "chore(v2-alpha5): Day 12 complete — real-time web dashboard, v2.0.0-alpha.5"
|
|
2667
|
+
git push origin feat/mindforge-v2-realtime-dashboard
|
|
2668
|
+
```
|
|
2669
|
+
|
|
2670
|
+
---
|
|
2671
|
+
|
|
2672
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
2673
|
+
# PART 2 — REVIEW PROMPT
|
|
2674
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
2675
|
+
|
|
2676
|
+
---
|
|
2677
|
+
|
|
2678
|
+
## DAY 12 REVIEW
|
|
2679
|
+
|
|
2680
|
+
Activate **`architect.md` + `security-reviewer.md` + `qa-engineer.js`** simultaneously.
|
|
2681
|
+
|
|
2682
|
+
Day 12 risk profile:
|
|
2683
|
+
1. **Approval ID path traversal** — POST /api/approve/:id with crafted ID
|
|
2684
|
+
2. **Steering injection via dashboard** — browser UI sends instructions to agent context
|
|
2685
|
+
3. **SSE client accumulation** — disconnected clients never cleaned up = memory leak
|
|
2686
|
+
4. **Chart data XSS** — knowledge topics/content rendered to canvas or innerHTML
|
|
2687
|
+
5. **CORS bypass** — misconfigured CORS allows cross-origin approval actions
|
|
2688
|
+
6. **Approval without auth** — any localhost process can approve Tier 3 changes via API
|
|
2689
|
+
|
|
2690
|
+
---
|
|
2691
|
+
|
|
2692
|
+
## REVIEW PASS 1 — Approval Security
|
|
2693
|
+
|
|
2694
|
+
Read `approval-handler.js` and `api-router.js` completely.
|
|
2695
|
+
|
|
2696
|
+
- [ ] **UUID validation in `processDecision` is correct but `api-router.js` uses `req.params.id` directly** without its own validation layer. Express route params are strings — a carefully crafted URL like `/api/approve/../../../../.planning/approvals/APPROVAL-real-uuid` could bypass the UUID check if path normalization behaves unexpectedly. Fix: "Add a second validation in `api-router.js` before calling `processDecision`: `if (!/^[a-f0-9-]{36}$/.test(req.params.id)) return res.status(400).json({ error: 'Invalid ID format' })`."
|
|
2697
|
+
|
|
2698
|
+
- [ ] **Tier 3 approvals can be approved via the dashboard without CLI verification.** The approval handler writes `approved` to the file — the same mechanism that CLI approval uses. But Tier 3 governance was designed to require a human with CLI access. Dashboard approval means a stakeholder who can access the dashboard (any localhost process) can approve Tier 3 (payment/PII/auth) changes without ever seeing the code diff. Fix: "Add a Tier 3 browser confirmation: require the approver to type the plan ID to confirm (not just click Approve). Add `confirmation_required: true` flag for Tier 3. The frontend shows: 'Type the plan ID to confirm Tier 3 approval:' input field."
|
|
2699
|
+
|
|
2700
|
+
- [ ] **Approval handler writes the approval file in-place after validation.** If the process crashes between writing `status: approved` and writing the AUDIT entry, the approval is recorded but the AUDIT trail is missing. Fix: "Write the AUDIT entry FIRST (before updating the approval file). If the AUDIT write succeeds but the file update fails: the state is slightly inconsistent (AUDIT says approved, file says pending) but the human reviewer can reconcile. If the file update succeeds but AUDIT write fails: approval happens silently — this is the worse case. Write AUDIT first."
|
|
2701
|
+
|
|
2702
|
+
---
|
|
2703
|
+
|
|
2704
|
+
## REVIEW PASS 2 — SSE Bridge: Memory Leaks
|
|
2705
|
+
|
|
2706
|
+
Read `sse-bridge.js` completely.
|
|
2707
|
+
|
|
2708
|
+
- [ ] **`clients` Set grows without cleanup when clients disconnect ungracefully.** The `res.on('close')` handler removes the client correctly — but only for graceful TCP closures. If the client connection dies without a TCP close (NAT timeout, process kill), the client stays in the Set forever. Fix: "Add a write-error cleanup: wrap `res.write()` in a try-catch. If writing throws (EPIPE, ECONNRESET), remove the client from the Set immediately. The existing try-catch does call `clients.delete(res)` — verify this is working by testing with a simulated write failure."
|
|
2709
|
+
|
|
2710
|
+
- [ ] **`pollAuditLog` reads file bytes by position assuming no file rotation.** If AUDIT.jsonl is rotated (renamed, new file created), `_lastAuditSize` will be wrong. The new file will have size < `_lastAuditSize`, but the condition `newSize <= _lastAuditSize` would return early and miss all new events. Fix: "On each poll: check if the file's inode has changed (compare `stat.ino`). If the inode changed: the file was rotated. Reset `_lastAuditSize = 0` and re-read from start."
|
|
2711
|
+
|
|
2712
|
+
---
|
|
2713
|
+
|
|
2714
|
+
## REVIEW PASS 3 — Frontend: XSS Risk
|
|
2715
|
+
|
|
2716
|
+
Read `bin/dashboard/frontend/index.html` completely.
|
|
2717
|
+
|
|
2718
|
+
- [ ] **`escapeHtml` is defined but not consistently applied.** Several `innerHTML` assignments use `escapeHtml()` correctly (`approval-card`, `memory-list`, `developer-row`). But `createAuditRow` uses `escapeHtml(detail)` — the `detail` variable is built from `entry.plan`, `entry.phase`, `entry.task_name`, etc. If any AUDIT.jsonl entry contains `<script>` in a task name (possible if a steering instruction is used as a task name), it would execute. Fix: "Verify every `innerHTML` assignment with dynamic content calls `escapeHtml()`. Add a note: 'All user-derived content MUST go through escapeHtml() before setting innerHTML. Never trust AUDIT.jsonl content.'"
|
|
2719
|
+
|
|
2720
|
+
- [ ] **Chart data could contain XSS if rendered to text.** The chart drawing uses `ctx.fillText(labels[i], ...)` for label text. Canvas `fillText` does not execute HTML/JS — it's safe. But `setText()` uses `textContent` (safe). The issue would only arise if chart labels were ever put into `innerHTML` — verify they are not.
|
|
2721
|
+
|
|
2722
|
+
---
|
|
2723
|
+
|
|
2724
|
+
## REVIEW PASS 4 — API Router: Input Validation
|
|
2725
|
+
|
|
2726
|
+
Read `api-router.js` completely.
|
|
2727
|
+
|
|
2728
|
+
- [ ] **`/api/memory?q=` query parameter is truncated to 100 chars but not sanitized.** The query is passed to `Metrics.getMemory(q, limit)` which does `entry.topic.toLowerCase().includes(q)`. This is safe (string comparison only). But if the knowledge base content were ever rendered into innerHTML without escaping, the query value could be used for injection. This is already guarded by `escapeHtml()` in the frontend — document this dependency explicitly.
|
|
2729
|
+
|
|
2730
|
+
- [ ] **`/api/steer` validation checks auto-mode running but reads auto-state.json directly.** If auto-state.json is stale (e.g., auto mode crashed without updating it), the check passes (`status: running`) even though no auto-mode loop is actually reading the steering queue. This is a minor issue — the queued instruction just gets ignored. Document: "Steer queue is best-effort — if auto mode has crashed, queued instructions will be picked up when auto mode resumes."
|
|
2731
|
+
|
|
2732
|
+
---
|
|
2733
|
+
|
|
2734
|
+
## REVIEW PASS 5 — Server Security
|
|
2735
|
+
|
|
2736
|
+
Read `server.js` completely.
|
|
2737
|
+
|
|
2738
|
+
- [ ] **CORS `Access-Control-Allow-Origin: *` fallback when no `origin` header.** The CORS middleware sets `*` when `origin` is absent. This happens with same-origin requests (correct) but also with curl, Postman, and scripted API calls. Since the dashboard is localhost-only, this is acceptable — but should be tightened. Fix: "When `origin` is absent, don't set CORS header at all (same-origin requests don't need it). Only set CORS headers when an origin header is present and it's localhost."
|
|
2739
|
+
|
|
2740
|
+
- [ ] **`--open` flag spawns a child process using `require('child_process').spawn`.** The `open`/`start`/`xdg-open` command is selected based on `process.platform`. On Linux, `xdg-open` opens a URL in the default browser. But the URL is hardcoded as `http://localhost:${PORT}` — no user input. This is safe (no injection possible). However, `detached: true` without `stdio: 'inherit'` means errors from the browser open command are silently ignored. This is the correct behaviour for this use case.
|
|
2741
|
+
|
|
2742
|
+
---
|
|
2743
|
+
|
|
2744
|
+
## REVIEW PASS 6 — Test Suite
|
|
2745
|
+
|
|
2746
|
+
Read `tests/dashboard.test.js` completely.
|
|
2747
|
+
|
|
2748
|
+
- [ ] **Missing test: Tier 3 approval confirmation requirement.** After hardening Pass 1 (Tier 3 requires typing plan ID), add a test that verifies the backend rejects a Tier 3 approval without the confirmation.
|
|
2749
|
+
|
|
2750
|
+
- [ ] **Missing test: SSE inode rotation.** After hardening Pass 2 (inode-based rotation detection), add a test that simulates file rotation and verifies new events are not missed.
|
|
2751
|
+
|
|
2752
|
+
- [ ] **`broadcast` test: the mock client stays in the `clients` Set.** After calling `SSE.addClient(mockRes)` in the test, the mockRes remains in the Set for subsequent tests. This could cause `broadcast` tests to fail in non-isolated order. Fix: "After the broadcast test, remove the mock client from the Set by simulating a close event."
|
|
2753
|
+
|
|
2754
|
+
---
|
|
2755
|
+
|
|
2756
|
+
## REVIEW SUMMARY TABLE
|
|
2757
|
+
|
|
2758
|
+
```
|
|
2759
|
+
## Day 12 Review Summary
|
|
2760
|
+
|
|
2761
|
+
| Category | BLOCKING | MAJOR | MINOR | SUGGESTION |
|
|
2762
|
+
|--------------------|----------|-------|-------|------------|
|
|
2763
|
+
| Approval Security | | | | |
|
|
2764
|
+
| SSE Bridge | | | | |
|
|
2765
|
+
| Frontend XSS | | | | |
|
|
2766
|
+
| API Router | | | | |
|
|
2767
|
+
| Server Security | | | | |
|
|
2768
|
+
| Test Suite | | | | |
|
|
2769
|
+
| **TOTAL** | | | | |
|
|
2770
|
+
|
|
2771
|
+
## Verdict
|
|
2772
|
+
[ ] ✅ APPROVED — Proceed to HARDEN section
|
|
2773
|
+
[ ] ⚠️ APPROVED WITH CONDITIONS
|
|
2774
|
+
[ ] ❌ NOT APPROVED
|
|
2775
|
+
```
|
|
2776
|
+
|
|
2777
|
+
---
|
|
2778
|
+
|
|
2779
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
2780
|
+
# PART 3 — HARDENING PROMPT
|
|
2781
|
+
# ═══════════════════════════════════════════════════════════════════════
|
|
2782
|
+
|
|
2783
|
+
---
|
|
2784
|
+
|
|
2785
|
+
## DAY 12 HARDENING
|
|
2786
|
+
|
|
2787
|
+
Activate **`security-reviewer.md` + `architect.md`** simultaneously.
|
|
2788
|
+
|
|
2789
|
+
Confirm all test suites pass before hardening:
|
|
2790
|
+
```bash
|
|
2791
|
+
for suite in install wave-engine audit compaction skills-platform \
|
|
2792
|
+
integrations governance intelligence metrics \
|
|
2793
|
+
distribution ci-mode sdk production migration e2e \
|
|
2794
|
+
autonomous browser model-routing memory dashboard; do
|
|
2795
|
+
printf " %-30s" "${suite}..."
|
|
2796
|
+
node tests/${suite}.test.js 2>&1 | tail -1
|
|
2797
|
+
done
|
|
2798
|
+
```
|
|
2799
|
+
|
|
2800
|
+
---
|
|
2801
|
+
|
|
2802
|
+
## HARDEN 1 — Add Tier 3 confirmation requirement
|
|
2803
|
+
|
|
2804
|
+
Update `bin/dashboard/approval-handler.js`:
|
|
2805
|
+
|
|
2806
|
+
```javascript
|
|
2807
|
+
/**
|
|
2808
|
+
* Updated processDecision — Tier 3 requires plan ID confirmation
|
|
2809
|
+
*/
|
|
2810
|
+
function processDecision(approvalId, decision, comment, approver, confirmationId = null) {
|
|
2811
|
+
// Existing validation...
|
|
2812
|
+
if (!/^[a-f0-9-]{36}$/.test(approvalId)) {
|
|
2813
|
+
return { success: false, error: 'Malformed approval ID format' };
|
|
2814
|
+
}
|
|
2815
|
+
// ... file read and pending check ...
|
|
2816
|
+
|
|
2817
|
+
// TIER 3 CONFIRMATION — require typing the plan ID
|
|
2818
|
+
if (approval.tier === 3 && decision === 'approve') {
|
|
2819
|
+
const expectedConfirmation = `${approval.phase}-${approval.plan}`;
|
|
2820
|
+
if (!confirmationId || confirmationId.trim() !== expectedConfirmation) {
|
|
2821
|
+
return {
|
|
2822
|
+
success: false,
|
|
2823
|
+
error: `Tier 3 approval requires typing the plan ID to confirm.`,
|
|
2824
|
+
confirmation_required: true,
|
|
2825
|
+
expected: expectedConfirmation,
|
|
2826
|
+
tier3_warning: 'This is a Tier 3 change (auth/payment/PII). Review the code diff before approving.',
|
|
2827
|
+
};
|
|
2828
|
+
}
|
|
2829
|
+
}
|
|
2830
|
+
|
|
2831
|
+
// ... rest of function unchanged ...
|
|
2832
|
+
}
|
|
2833
|
+
```
|
|
2834
|
+
|
|
2835
|
+
Update `bin/dashboard/frontend/index.html` — `decide()` function:
|
|
2836
|
+
|
|
2837
|
+
```javascript
|
|
2838
|
+
async function decide(id, decision, tier) {
|
|
2839
|
+
let confirmationId = null;
|
|
2840
|
+
|
|
2841
|
+
if (decision === 'approve' && tier === 3) {
|
|
2842
|
+
confirmationId = prompt(
|
|
2843
|
+
'⚠️ TIER 3 APPROVAL (auth/payment/PII)\n\n' +
|
|
2844
|
+
'Review the code diff before approving.\n\n' +
|
|
2845
|
+
'Type the plan ID (e.g., "3-04") to confirm:'
|
|
2846
|
+
);
|
|
2847
|
+
if (!confirmationId) return; // User cancelled
|
|
2848
|
+
} else {
|
|
2849
|
+
if (!confirm(`${decision === 'approve' ? 'Approve' : 'Reject'} this request?`)) return;
|
|
2850
|
+
}
|
|
2851
|
+
|
|
2852
|
+
try {
|
|
2853
|
+
const r = await fetch(`/api/approve/${id}`, {
|
|
2854
|
+
method: 'POST',
|
|
2855
|
+
headers: { 'Content-Type': 'application/json' },
|
|
2856
|
+
body: JSON.stringify({ decision, approver: 'dashboard-user', confirmation_id: confirmationId }),
|
|
2857
|
+
});
|
|
2858
|
+
const d = await r.json();
|
|
2859
|
+
if (d.success) { showToast(d.message); loadApprovals(); }
|
|
2860
|
+
else if (d.confirmation_required) {
|
|
2861
|
+
showToast('Confirmation required: type the plan ID exactly', true);
|
|
2862
|
+
} else {
|
|
2863
|
+
showToast('Error: ' + d.error, true);
|
|
2864
|
+
}
|
|
2865
|
+
} catch { showToast('Request failed', true); }
|
|
2866
|
+
}
|
|
2867
|
+
```
|
|
2868
|
+
|
|
2869
|
+
Also update `/api/approve/:id` in `api-router.js` to pass `confirmation_id`:
|
|
2870
|
+
|
|
2871
|
+
```javascript
|
|
2872
|
+
app.post('/api/approve/:id', (req, res) => {
|
|
2873
|
+
// ... existing validation ...
|
|
2874
|
+
const { decision, comment, approver, confirmation_id } = req.body || {};
|
|
2875
|
+
const result = Approval.processDecision(id, decision, comment, approver, confirmation_id);
|
|
2876
|
+
// ...
|
|
2877
|
+
});
|
|
2878
|
+
```
|
|
2879
|
+
|
|
2880
|
+
**Commit:**
|
|
2881
|
+
```bash
|
|
2882
|
+
git add bin/dashboard/approval-handler.js \
|
|
2883
|
+
bin/dashboard/frontend/index.html \
|
|
2884
|
+
bin/dashboard/api-router.js
|
|
2885
|
+
git commit -m "harden(v2-dashboard): add Tier 3 confirmation — must type plan ID to approve"
|
|
2886
|
+
```
|
|
2887
|
+
|
|
2888
|
+
---
|
|
2889
|
+
|
|
2890
|
+
## HARDEN 2 — Fix SSE inode rotation detection
|
|
2891
|
+
|
|
2892
|
+
Update `bin/dashboard/sse-bridge.js`:
|
|
2893
|
+
|
|
2894
|
+
```javascript
|
|
2895
|
+
let _auditInode = 0; // Track file inode for rotation detection
|
|
2896
|
+
|
|
2897
|
+
function pollAuditLog() {
|
|
2898
|
+
if (!fs.existsSync(AUDIT_PATH)) return;
|
|
2899
|
+
|
|
2900
|
+
try {
|
|
2901
|
+
const stat = fs.statSync(AUDIT_PATH);
|
|
2902
|
+
const newSize = stat.size;
|
|
2903
|
+
const newIno = stat.ino;
|
|
2904
|
+
|
|
2905
|
+
// File rotation detected: inode changed or new file is smaller
|
|
2906
|
+
if (newIno !== _auditInode && _auditInode !== 0) {
|
|
2907
|
+
process.stderr.write('[sse-bridge] AUDIT.jsonl rotation detected — resetting position\n');
|
|
2908
|
+
_lastAuditSize = 0;
|
|
2909
|
+
}
|
|
2910
|
+
_auditInode = newIno;
|
|
2911
|
+
|
|
2912
|
+
if (newSize <= _lastAuditSize) return;
|
|
2913
|
+
|
|
2914
|
+
// ... rest of poll logic unchanged ...
|
|
2915
|
+
} catch { /* ignore */ }
|
|
2916
|
+
}
|
|
2917
|
+
```
|
|
2918
|
+
|
|
2919
|
+
Also fix SSE `broadcast` to handle write errors more robustly:
|
|
2920
|
+
|
|
2921
|
+
```javascript
|
|
2922
|
+
function broadcast(eventName, data) {
|
|
2923
|
+
const message = `event: ${eventName}\ndata: ${JSON.stringify(data)}\n\n`;
|
|
2924
|
+
const toRemove = [];
|
|
2925
|
+
for (const res of clients) {
|
|
2926
|
+
try {
|
|
2927
|
+
res.write(message);
|
|
2928
|
+
} catch (err) {
|
|
2929
|
+
// Connection died ungracefully (EPIPE, ECONNRESET, etc.)
|
|
2930
|
+
toRemove.push(res);
|
|
2931
|
+
}
|
|
2932
|
+
}
|
|
2933
|
+
// Remove dead clients outside the iteration
|
|
2934
|
+
toRemove.forEach(res => clients.delete(res));
|
|
2935
|
+
}
|
|
2936
|
+
```
|
|
2937
|
+
|
|
2938
|
+
**Commit:**
|
|
2939
|
+
```bash
|
|
2940
|
+
git add bin/dashboard/sse-bridge.js
|
|
2941
|
+
git commit -m "harden(v2-dashboard): fix SSE inode rotation detection and dead client cleanup"
|
|
2942
|
+
```
|
|
2943
|
+
|
|
2944
|
+
---
|
|
2945
|
+
|
|
2946
|
+
## HARDEN 3 — Fix CORS header for requests without Origin
|
|
2947
|
+
|
|
2948
|
+
Update `server.js` CORS middleware:
|
|
2949
|
+
|
|
2950
|
+
```javascript
|
|
2951
|
+
// CORS — only allow requests from localhost origins
|
|
2952
|
+
app.use((req, res, next) => {
|
|
2953
|
+
const origin = req.headers.origin;
|
|
2954
|
+
|
|
2955
|
+
if (origin && /^https?:\/\/(localhost|127\.0\.0\.1)(:\d+)?$/.test(origin)) {
|
|
2956
|
+
// Explicit localhost origin — set CORS headers
|
|
2957
|
+
res.setHeader('Access-Control-Allow-Origin', origin);
|
|
2958
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
2959
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
2960
|
+
res.setHeader('Vary', 'Origin'); // Important: vary by origin for caching
|
|
2961
|
+
}
|
|
2962
|
+
// No origin header (same-origin/curl/postman): don't set CORS headers
|
|
2963
|
+
// This is correct — same-origin requests don't need CORS headers
|
|
2964
|
+
|
|
2965
|
+
if (req.method === 'OPTIONS') return res.status(204).end();
|
|
2966
|
+
next();
|
|
2967
|
+
});
|
|
2968
|
+
```
|
|
2969
|
+
|
|
2970
|
+
**Commit:**
|
|
2971
|
+
```bash
|
|
2972
|
+
git add bin/dashboard/server.js
|
|
2973
|
+
git commit -m "harden(v2-dashboard): fix CORS — don't set wildcard for requests without Origin header"
|
|
2974
|
+
```
|
|
2975
|
+
|
|
2976
|
+
---
|
|
2977
|
+
|
|
2978
|
+
## HARDEN 4 — Write AUDIT entry before updating approval file
|
|
2979
|
+
|
|
2980
|
+
Update `bin/dashboard/approval-handler.js`:
|
|
2981
|
+
|
|
2982
|
+
```javascript
|
|
2983
|
+
function processDecision(approvalId, decision, comment, approver, confirmationId = null) {
|
|
2984
|
+
// ... existing validation ...
|
|
2985
|
+
|
|
2986
|
+
const updated = {
|
|
2987
|
+
...approval,
|
|
2988
|
+
status: decision === 'approve' ? 'approved' : 'rejected',
|
|
2989
|
+
resolved_at: new Date().toISOString(),
|
|
2990
|
+
resolved_by: approver || 'dashboard',
|
|
2991
|
+
comment: comment || null,
|
|
2992
|
+
resolution_channel: 'mindforge-dashboard',
|
|
2993
|
+
};
|
|
2994
|
+
|
|
2995
|
+
const auditEntry = {
|
|
2996
|
+
id: require('crypto').randomBytes(8).toString('hex'),
|
|
2997
|
+
timestamp: new Date().toISOString(),
|
|
2998
|
+
event: decision === 'approve' ? 'approval_granted' : 'approval_rejected',
|
|
2999
|
+
approval_id: approvalId,
|
|
3000
|
+
tier: approval.tier,
|
|
3001
|
+
phase: approval.phase,
|
|
3002
|
+
plan: approval.plan,
|
|
3003
|
+
resolved_by: approver || 'dashboard',
|
|
3004
|
+
comment: comment || null,
|
|
3005
|
+
agent: 'mindforge-dashboard',
|
|
3006
|
+
session_id: 'dashboard',
|
|
3007
|
+
};
|
|
3008
|
+
|
|
3009
|
+
// WRITE AUDIT FIRST — then update the approval file
|
|
3010
|
+
// If AUDIT write fails: don't update approval (safer — approval can be retried)
|
|
3011
|
+
// If approval write fails: AUDIT says approved but file says pending (reconcilable)
|
|
3012
|
+
try {
|
|
3013
|
+
writeAuditEntry(auditEntry);
|
|
3014
|
+
} catch (auditErr) {
|
|
3015
|
+
process.stderr.write(`[approval-handler] AUDIT write failed — aborting approval: ${auditErr.message}\n`);
|
|
3016
|
+
return { success: false, error: 'AUDIT write failed — approval not processed. Retry.' };
|
|
3017
|
+
}
|
|
3018
|
+
|
|
3019
|
+
// Now update the approval file
|
|
3020
|
+
fs.writeFileSync(filePath, JSON.stringify(updated, null, 2));
|
|
3021
|
+
|
|
3022
|
+
return {
|
|
3023
|
+
success: true,
|
|
3024
|
+
decision,
|
|
3025
|
+
approval_id: approvalId,
|
|
3026
|
+
tier: approval.tier,
|
|
3027
|
+
message: `${approval.tier === 3 ? 'Tier 3' : 'Tier 2'} approval ${decision}d for Plan ${approval.phase}-${approval.plan}`,
|
|
3028
|
+
};
|
|
3029
|
+
}
|
|
3030
|
+
```
|
|
3031
|
+
|
|
3032
|
+
**Commit:**
|
|
3033
|
+
```bash
|
|
3034
|
+
git add bin/dashboard/approval-handler.js
|
|
3035
|
+
git commit -m "harden(v2-dashboard): write AUDIT entry before approval file update (fail-safe ordering)"
|
|
3036
|
+
```
|
|
3037
|
+
|
|
3038
|
+
---
|
|
3039
|
+
|
|
3040
|
+
## HARDEN 5 — Write 3 ADRs for Day 12 decisions
|
|
3041
|
+
|
|
3042
|
+
### `.planning/decisions/ADR-033-dashboard-localhost-only.md`
|
|
3043
|
+
|
|
3044
|
+
```markdown
|
|
3045
|
+
# ADR-033: Dashboard binds to localhost only (127.0.0.1)
|
|
3046
|
+
|
|
3047
|
+
**Status:** Accepted | **Date:** v2.0.0 | **Day:** 12
|
|
3048
|
+
|
|
3049
|
+
## Context
|
|
3050
|
+
The dashboard shows live project state including security findings, code decisions,
|
|
3051
|
+
and team activity. Should it be accessible on the network?
|
|
3052
|
+
|
|
3053
|
+
## Decision
|
|
3054
|
+
Dashboard binds to 127.0.0.1 only. Same policy as SDK SSE (ADR-017) and
|
|
3055
|
+
Browser Daemon (ADR-024).
|
|
3056
|
+
|
|
3057
|
+
## Rationale
|
|
3058
|
+
The dashboard contains sensitive project information:
|
|
3059
|
+
- Security findings with vulnerability details
|
|
3060
|
+
- Knowledge graph with architectural decisions
|
|
3061
|
+
- Team member email addresses
|
|
3062
|
+
- Cost tracking and token usage
|
|
3063
|
+
- Approval requests with code diffs
|
|
3064
|
+
|
|
3065
|
+
Exposing this on a network interface would require authentication, TLS, and
|
|
3066
|
+
access control — adding significant complexity. The use case (team visibility)
|
|
3067
|
+
is better served by screensharing the developer's browser than exposing an
|
|
3068
|
+
unauthenticated server.
|
|
3069
|
+
|
|
3070
|
+
## Consequences
|
|
3071
|
+
Remote team members: screenshare or SSH tunnel.
|
|
3072
|
+
CI environments: dashboard not started in headless mode.
|
|
3073
|
+
```
|
|
3074
|
+
|
|
3075
|
+
### `.planning/decisions/ADR-034-tier3-approval-confirmation.md`
|
|
3076
|
+
|
|
3077
|
+
```markdown
|
|
3078
|
+
# ADR-034: Tier 3 dashboard approvals require typing the plan ID
|
|
3079
|
+
|
|
3080
|
+
**Status:** Accepted | **Date:** v2.0.0 | **Day:** 12
|
|
3081
|
+
|
|
3082
|
+
## Context
|
|
3083
|
+
The dashboard allows approving governance requests via a button click.
|
|
3084
|
+
Tier 3 changes (auth/payment/PII) are high-stakes. Should they be
|
|
3085
|
+
approachable from the dashboard the same way as Tier 2?
|
|
3086
|
+
|
|
3087
|
+
## Decision
|
|
3088
|
+
Tier 3 dashboard approvals require the approver to type the plan ID
|
|
3089
|
+
(e.g., "3-04") into a confirmation input field before the approval is processed.
|
|
3090
|
+
|
|
3091
|
+
## Rationale
|
|
3092
|
+
A mis-click or accidental browser interaction should not approve a Tier 3 change.
|
|
3093
|
+
The type-to-confirm pattern (used by GitHub for repository deletion) creates a
|
|
3094
|
+
deliberate speed bump that prevents accidental approval.
|
|
3095
|
+
The plan ID is specific enough to require intent but simple enough to not be burdensome.
|
|
3096
|
+
|
|
3097
|
+
## Consequences
|
|
3098
|
+
Tier 2 approvals: single click + confirm dialog.
|
|
3099
|
+
Tier 3 approvals: single click + type plan ID.
|
|
3100
|
+
Tier 3 rejections: single click + confirm dialog (no typing required to reject).
|
|
3101
|
+
```
|
|
3102
|
+
|
|
3103
|
+
### `.planning/decisions/ADR-035-audit-before-approval-file.md`
|
|
3104
|
+
|
|
3105
|
+
```markdown
|
|
3106
|
+
# ADR-035: AUDIT entry written before approval file update
|
|
3107
|
+
|
|
3108
|
+
**Status:** Accepted | **Date:** v2.0.0 | **Day:** 12
|
|
3109
|
+
|
|
3110
|
+
## Context
|
|
3111
|
+
When approving a governance request, two writes are required:
|
|
3112
|
+
1. AUDIT.jsonl entry (compliance trail)
|
|
3113
|
+
2. APPROVAL-*.json file update (status: approved)
|
|
3114
|
+
|
|
3115
|
+
In which order should these writes occur?
|
|
3116
|
+
|
|
3117
|
+
## Decision
|
|
3118
|
+
AUDIT entry is written first. If AUDIT write fails, the approval is aborted.
|
|
3119
|
+
If AUDIT succeeds but APPROVAL file write fails: AUDIT shows approved but
|
|
3120
|
+
APPROVAL file still shows pending. This is reconcilable by retrying.
|
|
3121
|
+
|
|
3122
|
+
## Rationale
|
|
3123
|
+
The AUDIT trail is the compliance record. An approval without an AUDIT entry
|
|
3124
|
+
is a silent approval — undetectable in compliance audits.
|
|
3125
|
+
|
|
3126
|
+
The reverse (APPROVAL file updated but no AUDIT) is worse because:
|
|
3127
|
+
1. The governance gate would pass (approval file says approved)
|
|
3128
|
+
2. The audit trail would show no approval event
|
|
3129
|
+
3. This would be a compliance violation
|
|
3130
|
+
|
|
3131
|
+
By writing AUDIT first: if anything fails, the approval is either fully
|
|
3132
|
+
recorded (both written) or not recorded at all (AUDIT write failed = abort).
|
|
3133
|
+
|
|
3134
|
+
## Consequences
|
|
3135
|
+
AUDIT write failure = approval not processed (retry required).
|
|
3136
|
+
This is the correct fail-safe: prefer no approval over silent approval.
|
|
3137
|
+
```
|
|
3138
|
+
|
|
3139
|
+
**Commit:**
|
|
3140
|
+
```bash
|
|
3141
|
+
git add .planning/decisions/ADR-033*.md \
|
|
3142
|
+
.planning/decisions/ADR-034*.md \
|
|
3143
|
+
.planning/decisions/ADR-035*.md
|
|
3144
|
+
git commit -m "docs(adr): add ADR-033 dashboard localhost, ADR-034 Tier 3 confirm, ADR-035 audit-before-approval"
|
|
3145
|
+
```
|
|
3146
|
+
|
|
3147
|
+
---
|
|
3148
|
+
|
|
3149
|
+
## HARDEN 6 — Add hardening tests
|
|
3150
|
+
|
|
3151
|
+
```javascript
|
|
3152
|
+
// Add to tests/dashboard.test.js:
|
|
3153
|
+
|
|
3154
|
+
console.log('\nHardening tests:');
|
|
3155
|
+
|
|
3156
|
+
test('Tier 3 approval without confirmation is rejected', () => {
|
|
3157
|
+
const p = mkProject();
|
|
3158
|
+
const orig = process.cwd();
|
|
3159
|
+
process.chdir(p.dir);
|
|
3160
|
+
try {
|
|
3161
|
+
const tier3Approval = { ...SAMPLE_APPROVAL, tier: 3 };
|
|
3162
|
+
p.write(`.planning/approvals/APPROVAL-${SAMPLE_APPROVAL.id}.json`, JSON.stringify(tier3Approval));
|
|
3163
|
+
|
|
3164
|
+
// No confirmationId provided
|
|
3165
|
+
const result = Approval.processDecision(SAMPLE_APPROVAL.id, 'approve', '', 'approver@test.com', null);
|
|
3166
|
+
assert.strictEqual(result.success, false, 'Should fail without confirmation');
|
|
3167
|
+
assert.ok(result.confirmation_required, 'Should indicate confirmation is required');
|
|
3168
|
+
assert.ok(result.tier3_warning, 'Should include tier3 warning');
|
|
3169
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
3170
|
+
});
|
|
3171
|
+
|
|
3172
|
+
test('Tier 3 approval with correct plan ID succeeds', () => {
|
|
3173
|
+
const p = mkProject();
|
|
3174
|
+
const orig = process.cwd();
|
|
3175
|
+
process.chdir(p.dir);
|
|
3176
|
+
try {
|
|
3177
|
+
const tier3Approval = { ...SAMPLE_APPROVAL, tier: 3 };
|
|
3178
|
+
p.write(`.planning/approvals/APPROVAL-${SAMPLE_APPROVAL.id}.json`, JSON.stringify(tier3Approval));
|
|
3179
|
+
p.write('.planning/AUDIT.jsonl', '');
|
|
3180
|
+
|
|
3181
|
+
const result = Approval.processDecision(SAMPLE_APPROVAL.id, 'approve', '', 'approver@test.com', `${SAMPLE_APPROVAL.phase}-${SAMPLE_APPROVAL.plan}`);
|
|
3182
|
+
assert.strictEqual(result.success, true, 'Should succeed with correct plan ID');
|
|
3183
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
3184
|
+
});
|
|
3185
|
+
|
|
3186
|
+
test('Tier 3 approval with WRONG plan ID is rejected', () => {
|
|
3187
|
+
const p = mkProject();
|
|
3188
|
+
const orig = process.cwd();
|
|
3189
|
+
process.chdir(p.dir);
|
|
3190
|
+
try {
|
|
3191
|
+
const tier3Approval = { ...SAMPLE_APPROVAL, tier: 3 };
|
|
3192
|
+
p.write(`.planning/approvals/APPROVAL-${SAMPLE_APPROVAL.id}.json`, JSON.stringify(tier3Approval));
|
|
3193
|
+
|
|
3194
|
+
const result = Approval.processDecision(SAMPLE_APPROVAL.id, 'approve', '', 'approver@test.com', '3-99'); // Wrong plan
|
|
3195
|
+
assert.strictEqual(result.success, false, 'Should reject wrong plan ID');
|
|
3196
|
+
} finally { process.chdir(orig); p.cleanup(); }
|
|
3197
|
+
});
|
|
3198
|
+
|
|
3199
|
+
test('SSE broadcast handles dead clients without crashing', () => {
|
|
3200
|
+
let writeCount = 0;
|
|
3201
|
+
let errorClient = { write: () => { throw new Error('EPIPE'); }, on: () => {} };
|
|
3202
|
+
let goodClient = { write: msg => { writeCount++; }, on: () => {} };
|
|
3203
|
+
|
|
3204
|
+
SSE.addClient(errorClient);
|
|
3205
|
+
SSE.addClient(goodClient);
|
|
3206
|
+
|
|
3207
|
+
// Broadcast should not throw even with a dead client
|
|
3208
|
+
assert.doesNotThrow(() => SSE.broadcast('test:event', { data: 'test' }), 'Should handle dead client gracefully');
|
|
3209
|
+
assert.ok(writeCount >= 1, 'Good client should receive the broadcast');
|
|
3210
|
+
});
|
|
3211
|
+
|
|
3212
|
+
test('server.js does not set CORS wildcard for missing Origin header', () => {
|
|
3213
|
+
const c = fs.readFileSync('bin/dashboard/server.js', 'utf8');
|
|
3214
|
+
// The CORS handler should only set headers when origin is present AND localhost
|
|
3215
|
+
assert.ok(c.includes('if (origin &&'), 'CORS should be conditional on origin presence');
|
|
3216
|
+
// Should NOT have unconditional Access-Control-Allow-Origin: *
|
|
3217
|
+
const unconditional = c.match(/setHeader\('Access-Control-Allow-Origin',\s*'\*'\)/);
|
|
3218
|
+
assert.ok(!unconditional, 'Should NOT set unconditional wildcard CORS');
|
|
3219
|
+
});
|
|
3220
|
+
```
|
|
3221
|
+
|
|
3222
|
+
**Commit:**
|
|
3223
|
+
```bash
|
|
3224
|
+
git add tests/dashboard.test.js
|
|
3225
|
+
git commit -m "test(v2-dashboard): add hardening tests — Tier 3 confirmation, dead client SSE, CORS wildcard"
|
|
3226
|
+
```
|
|
3227
|
+
|
|
3228
|
+
---
|
|
3229
|
+
|
|
3230
|
+
## HARDEN 7 — Final pre-merge verification
|
|
3231
|
+
|
|
3232
|
+
```bash
|
|
3233
|
+
#!/usr/bin/env bash
|
|
3234
|
+
echo "MindForge v2 Day 12 — Pre-Merge Verification"
|
|
3235
|
+
echo "═════════════════════════════════════════════"
|
|
3236
|
+
PASS=true
|
|
3237
|
+
|
|
3238
|
+
V=$(node -e "console.log(require('./package.json').version)")
|
|
3239
|
+
[[ "${V}" == "2.0.0-alpha.5" ]] && echo " Version: ${V} ✅" || { echo " ❌ ${V}"; PASS=false; }
|
|
3240
|
+
|
|
3241
|
+
echo ""
|
|
3242
|
+
FAIL=0
|
|
3243
|
+
for s in install wave-engine audit compaction skills-platform \
|
|
3244
|
+
integrations governance intelligence metrics \
|
|
3245
|
+
distribution ci-mode sdk production migration e2e \
|
|
3246
|
+
autonomous browser model-routing memory dashboard; do
|
|
3247
|
+
printf " %-30s" "${s}..."
|
|
3248
|
+
node tests/${s}.test.js 2>&1 | tail -1 | grep -q "passed" && echo "✅" || { echo "❌"; ((FAIL++)); PASS=false; }
|
|
3249
|
+
done
|
|
3250
|
+
|
|
3251
|
+
CMDS=$(ls .claude/commands/mindforge/ | wc -l | tr -d ' ')
|
|
3252
|
+
[ "$CMDS" -ge 45 ] && echo " Commands: ${CMDS} ✅" || { echo " ❌ Commands: ${CMDS}"; PASS=false; }
|
|
3253
|
+
|
|
3254
|
+
ADRS=$(ls .planning/decisions/ADR-*.md 2>/dev/null | wc -l | tr -d ' ')
|
|
3255
|
+
[ "$ADRS" -ge 35 ] && echo " ADRs: ${ADRS} ✅" || { echo " ❌ ADRs: ${ADRS}"; PASS=false; }
|
|
3256
|
+
|
|
3257
|
+
# Dashboard should never bind to 0.0.0.0
|
|
3258
|
+
UNSAFE=$(grep -r "0\.0\.0\.0" bin/dashboard/ 2>/dev/null || true)
|
|
3259
|
+
[ -z "$UNSAFE" ] && echo " Binding: localhost-only ✅" || { echo " ❌ 0.0.0.0 binding found"; PASS=false; }
|
|
3260
|
+
|
|
3261
|
+
# No hardcoded credentials in dashboard
|
|
3262
|
+
CREDS=$(grep -rE "(password|secret|api_key)\s*=\s*['\"][^'\"]{8,}" bin/dashboard/ 2>/dev/null | \
|
|
3263
|
+
grep -v "placeholder\|TEST_ONLY" || true)
|
|
3264
|
+
[ -z "$CREDS" ] && echo " Credentials: clean ✅" || { echo " ❌ Credentials in dashboard"; PASS=false; }
|
|
3265
|
+
|
|
3266
|
+
echo ""
|
|
3267
|
+
$PASS && echo "✅ ALL CHECKS PASSED — Day 12 complete" || { echo "❌ FAILURES"; exit 1; }
|
|
3268
|
+
```
|
|
3269
|
+
|
|
3270
|
+
**Final commit:**
|
|
3271
|
+
```bash
|
|
3272
|
+
git add .
|
|
3273
|
+
git commit -m "harden(v2-day12): all hardening complete — Tier 3 confirm, inode rotation, CORS, AUDIT ordering"
|
|
3274
|
+
git push origin feat/mindforge-v2-realtime-dashboard
|
|
3275
|
+
```
|
|
3276
|
+
|
|
3277
|
+
---
|
|
3278
|
+
|
|
3279
|
+
## DAY 12 COMPLETE
|
|
3280
|
+
|
|
3281
|
+
| Component | Status |
|
|
3282
|
+
|---|---|
|
|
3283
|
+
| Dashboard server (Express.js, localhost:7339, security middleware) | ✅ |
|
|
3284
|
+
| SSE Event Bridge (AUDIT tail, inode rotation, dead client cleanup) | ✅ |
|
|
3285
|
+
| Metrics Aggregator (7 data sources: status, audit, metrics, approvals, team, memory, costs) | ✅ |
|
|
3286
|
+
| Approval Handler (UUID validation, Tier 3 confirmation, AUDIT-first write) | ✅ |
|
|
3287
|
+
| API Router (10 endpoints, injection guard on steer, approval validation) | ✅ |
|
|
3288
|
+
| Frontend: Activity Feed (live SSE, wave progress, steering input) | ✅ |
|
|
3289
|
+
| Frontend: Quality Metrics (4 canvas charts) | ✅ |
|
|
3290
|
+
| Frontend: Pending Approvals (approve/reject with Tier 3 confirm) | ✅ |
|
|
3291
|
+
| Frontend: Knowledge Graph (searchable entry list) | ✅ |
|
|
3292
|
+
| Frontend: Team Activity (developers, last-seen, conflict detection) | ✅ |
|
|
3293
|
+
| `/mindforge:dashboard` command (45th) | ✅ |
|
|
3294
|
+
| CLAUDE.md dashboard awareness | ✅ |
|
|
3295
|
+
| `tests/dashboard.test.js` (20th test suite) | ✅ |
|
|
3296
|
+
| ADR-033 (localhost), ADR-034 (Tier 3 confirm), ADR-035 (audit ordering) | ✅ |
|
|
3297
|
+
| CHANGELOG v2.0.0-alpha.5 | ✅ |
|
|
3298
|
+
|
|
3299
|
+
**MindForge v2.0.0-alpha.5: 45 commands · 20 test suites · 35 ADRs**
|
|
3300
|
+
**Branch:** `feat/mindforge-v2-realtime-dashboard`
|
|
3301
|
+
**Day 12 complete. Open PR → merge → start Day 13 (Self-Building Skills)**
|