mindforge-cc 1.0.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/.agent/CLAUDE.md +462 -0
- package/.agent/forge/help.md +7 -0
- package/.agent/forge/init-project.md +32 -0
- package/.agent/forge/plan-phase.md +30 -0
- package/.agent/mindforge/approve.md +18 -0
- package/.agent/mindforge/audit.md +30 -0
- package/.agent/mindforge/benchmark.md +33 -0
- package/.agent/mindforge/complete-milestone.md +18 -0
- package/.agent/mindforge/debug.md +126 -0
- package/.agent/mindforge/discuss-phase.md +138 -0
- package/.agent/mindforge/execute-phase.md +165 -0
- package/.agent/mindforge/health.md +21 -0
- package/.agent/mindforge/help.md +23 -0
- package/.agent/mindforge/init-org.md +131 -0
- package/.agent/mindforge/init-project.md +155 -0
- package/.agent/mindforge/install-skill.md +15 -0
- package/.agent/mindforge/map-codebase.md +298 -0
- package/.agent/mindforge/metrics.md +22 -0
- package/.agent/mindforge/migrate.md +40 -0
- package/.agent/mindforge/milestone.md +12 -0
- package/.agent/mindforge/next.md +105 -0
- package/.agent/mindforge/plan-phase.md +125 -0
- package/.agent/mindforge/plugins.md +40 -0
- package/.agent/mindforge/pr-review.md +41 -0
- package/.agent/mindforge/profile-team.md +23 -0
- package/.agent/mindforge/publish-skill.md +19 -0
- package/.agent/mindforge/quick.md +135 -0
- package/.agent/mindforge/release.md +10 -0
- package/.agent/mindforge/retrospective.md +26 -0
- package/.agent/mindforge/review.md +157 -0
- package/.agent/mindforge/security-scan.md +233 -0
- package/.agent/mindforge/ship.md +100 -0
- package/.agent/mindforge/skills.md +141 -0
- package/.agent/mindforge/status.md +104 -0
- package/.agent/mindforge/sync-confluence.md +11 -0
- package/.agent/mindforge/sync-jira.md +12 -0
- package/.agent/mindforge/tokens.md +8 -0
- package/.agent/mindforge/update.md +42 -0
- package/.agent/mindforge/verify-phase.md +62 -0
- package/.agent/mindforge/workspace.md +29 -0
- package/.claude/CLAUDE.md +462 -0
- package/.claude/commands/forge/help.md +7 -0
- package/.claude/commands/forge/init-project.md +32 -0
- package/.claude/commands/forge/plan-phase.md +30 -0
- package/.claude/commands/mindforge/approve.md +18 -0
- package/.claude/commands/mindforge/audit.md +30 -0
- package/.claude/commands/mindforge/benchmark.md +33 -0
- package/.claude/commands/mindforge/complete-milestone.md +18 -0
- package/.claude/commands/mindforge/debug.md +126 -0
- package/.claude/commands/mindforge/discuss-phase.md +138 -0
- package/.claude/commands/mindforge/execute-phase.md +165 -0
- package/.claude/commands/mindforge/health.md +21 -0
- package/.claude/commands/mindforge/help.md +23 -0
- package/.claude/commands/mindforge/init-org.md +131 -0
- package/.claude/commands/mindforge/init-project.md +155 -0
- package/.claude/commands/mindforge/install-skill.md +15 -0
- package/.claude/commands/mindforge/map-codebase.md +298 -0
- package/.claude/commands/mindforge/metrics.md +22 -0
- package/.claude/commands/mindforge/migrate.md +40 -0
- package/.claude/commands/mindforge/milestone.md +12 -0
- package/.claude/commands/mindforge/next.md +105 -0
- package/.claude/commands/mindforge/plan-phase.md +125 -0
- package/.claude/commands/mindforge/plugins.md +40 -0
- package/.claude/commands/mindforge/pr-review.md +41 -0
- package/.claude/commands/mindforge/profile-team.md +23 -0
- package/.claude/commands/mindforge/publish-skill.md +19 -0
- package/.claude/commands/mindforge/quick.md +135 -0
- package/.claude/commands/mindforge/release.md +10 -0
- package/.claude/commands/mindforge/retrospective.md +26 -0
- package/.claude/commands/mindforge/review.md +157 -0
- package/.claude/commands/mindforge/security-scan.md +233 -0
- package/.claude/commands/mindforge/ship.md +100 -0
- package/.claude/commands/mindforge/skills.md +141 -0
- package/.claude/commands/mindforge/status.md +104 -0
- package/.claude/commands/mindforge/sync-confluence.md +11 -0
- package/.claude/commands/mindforge/sync-jira.md +12 -0
- package/.claude/commands/mindforge/tokens.md +8 -0
- package/.claude/commands/mindforge/update.md +42 -0
- package/.claude/commands/mindforge/verify-phase.md +62 -0
- package/.claude/commands/mindforge/workspace.md +29 -0
- package/.forge/org/CONVENTIONS.md +0 -0
- package/.forge/org/ORG.md +0 -0
- package/.forge/org/SECURITY.md +0 -0
- package/.forge/org/TOOLS.md +0 -0
- package/.forge/personas/analyst.md +0 -0
- package/.forge/personas/architect.md +0 -0
- package/.forge/personas/debug-specialist.md +0 -0
- package/.forge/personas/developer.md +26 -0
- package/.forge/personas/qa-engineer.md +0 -0
- package/.forge/personas/release-manager.md +0 -0
- package/.forge/personas/security-reviewer.md +33 -0
- package/.forge/personas/tech-writer.md +0 -0
- package/.forge/skills/api-design/SKILL.md +0 -0
- package/.forge/skills/code-quality/SKILL.md +0 -0
- package/.forge/skills/documentation/SKILL.md +0 -0
- package/.forge/skills/security-review/SKILL.md +23 -0
- package/.forge/skills/testing-standards/SKILL.md +27 -0
- package/.github/workflows/mindforge-ci.yml +224 -0
- package/.gitlab-ci-mindforge.yml +18 -0
- package/.mindforge/MINDFORGE-SCHEMA.json +165 -0
- package/.mindforge/audit/AUDIT-SCHEMA.md +451 -0
- package/.mindforge/ci/ci-config-schema.md +21 -0
- package/.mindforge/ci/ci-mode.md +179 -0
- package/.mindforge/ci/github-actions-adapter.md +224 -0
- package/.mindforge/ci/gitlab-ci-adapter.md +31 -0
- package/.mindforge/ci/jenkins-adapter.md +44 -0
- package/.mindforge/distribution/registry-client.md +166 -0
- package/.mindforge/distribution/registry-schema.md +96 -0
- package/.mindforge/distribution/skill-publisher.md +44 -0
- package/.mindforge/distribution/skill-validator.md +74 -0
- package/.mindforge/engine/compaction-protocol.md +182 -0
- package/.mindforge/engine/context-injector.md +128 -0
- package/.mindforge/engine/dependency-parser.md +113 -0
- package/.mindforge/engine/skills/conflict-resolver.md +69 -0
- package/.mindforge/engine/skills/loader.md +184 -0
- package/.mindforge/engine/skills/registry.md +98 -0
- package/.mindforge/engine/skills/versioning.md +75 -0
- package/.mindforge/engine/verification-pipeline.md +111 -0
- package/.mindforge/engine/wave-executor.md +235 -0
- package/.mindforge/governance/GOVERNANCE-CONFIG.md +17 -0
- package/.mindforge/governance/approval-workflow.md +37 -0
- package/.mindforge/governance/change-classifier.md +63 -0
- package/.mindforge/governance/compliance-gates.md +31 -0
- package/.mindforge/integrations/confluence.md +27 -0
- package/.mindforge/integrations/connection-manager.md +163 -0
- package/.mindforge/integrations/github.md +25 -0
- package/.mindforge/integrations/gitlab.md +13 -0
- package/.mindforge/integrations/jira.md +102 -0
- package/.mindforge/integrations/slack.md +41 -0
- package/.mindforge/intelligence/antipattern-detector.md +75 -0
- package/.mindforge/intelligence/difficulty-scorer.md +55 -0
- package/.mindforge/intelligence/health-engine.md +208 -0
- package/.mindforge/intelligence/skill-gap-analyser.md +40 -0
- package/.mindforge/intelligence/smart-compaction.md +71 -0
- package/.mindforge/metrics/METRICS-SCHEMA.md +42 -0
- package/.mindforge/metrics/quality-tracker.md +32 -0
- package/.mindforge/monorepo/cross-package-planner.md +114 -0
- package/.mindforge/monorepo/dependency-graph-builder.md +32 -0
- package/.mindforge/monorepo/workspace-detector.md +129 -0
- package/.mindforge/org/CONVENTIONS.md +62 -0
- package/.mindforge/org/ORG.md +51 -0
- package/.mindforge/org/SECURITY.md +50 -0
- package/.mindforge/org/TOOLS.md +53 -0
- package/.mindforge/org/integrations/INTEGRATIONS-CONFIG.md +58 -0
- package/.mindforge/org/skills/MANIFEST.md +38 -0
- package/.mindforge/personas/analyst.md +52 -0
- package/.mindforge/personas/architect.md +75 -0
- package/.mindforge/personas/debug-specialist.md +52 -0
- package/.mindforge/personas/developer.md +85 -0
- package/.mindforge/personas/overrides/README.md +85 -0
- package/.mindforge/personas/qa-engineer.md +61 -0
- package/.mindforge/personas/release-manager.md +76 -0
- package/.mindforge/personas/security-reviewer.md +91 -0
- package/.mindforge/personas/tech-writer.md +51 -0
- package/.mindforge/plugins/PLUGINS-MANIFEST.md +23 -0
- package/.mindforge/plugins/plugin-loader.md +93 -0
- package/.mindforge/plugins/plugin-registry.md +44 -0
- package/.mindforge/plugins/plugin-schema.md +68 -0
- package/.mindforge/pr-review/ai-reviewer.md +266 -0
- package/.mindforge/pr-review/finding-formatter.md +46 -0
- package/.mindforge/pr-review/review-prompt-templates.md +44 -0
- package/.mindforge/production/compatibility-layer.md +39 -0
- package/.mindforge/production/migration-engine.md +52 -0
- package/.mindforge/production/production-checklist.md +165 -0
- package/.mindforge/production/token-optimiser.md +68 -0
- package/.mindforge/skills/accessibility/SKILL.md +106 -0
- package/.mindforge/skills/api-design/SKILL.md +98 -0
- package/.mindforge/skills/code-quality/SKILL.md +88 -0
- package/.mindforge/skills/data-privacy/SKILL.md +126 -0
- package/.mindforge/skills/database-patterns/SKILL.md +192 -0
- package/.mindforge/skills/documentation/SKILL.md +91 -0
- package/.mindforge/skills/incident-response/SKILL.md +180 -0
- package/.mindforge/skills/performance/SKILL.md +120 -0
- package/.mindforge/skills/security-review/SKILL.md +83 -0
- package/.mindforge/skills/testing-standards/SKILL.md +97 -0
- package/.mindforge/team/TEAM-PROFILE.md +42 -0
- package/.mindforge/team/multi-handoff.md +23 -0
- package/.mindforge/team/profiles/README.md +13 -0
- package/.mindforge/team/session-merger.md +18 -0
- package/.planning/ARCHITECTURE.md +0 -0
- package/.planning/AUDIT.jsonl +0 -0
- package/.planning/HANDOFF.json +28 -0
- package/.planning/PROJECT.md +33 -0
- package/.planning/RELEASE-CHECKLIST.md +68 -0
- package/.planning/REQUIREMENTS.md +0 -0
- package/.planning/ROADMAP.md +0 -0
- package/.planning/STATE.md +31 -0
- package/.planning/approvals/.gitkeep +1 -0
- package/.planning/archive/.gitkeep +1 -0
- package/.planning/audit-archive/.gitkeep +1 -0
- package/.planning/decisions/.gitkeep +0 -0
- package/.planning/decisions/ADR-001-handoff-tracking.md +41 -0
- package/.planning/decisions/ADR-002-markdown-commands.md +46 -0
- package/.planning/decisions/ADR-003-skills-trigger-model.md +37 -0
- package/.planning/decisions/ADR-004-wave-parallelism-model.md +45 -0
- package/.planning/decisions/ADR-005-append-only-audit-log.md +51 -0
- package/.planning/decisions/ADR-006-tiered-skills-system.md +22 -0
- package/.planning/decisions/ADR-007-trigger-keyword-model.md +22 -0
- package/.planning/decisions/ADR-008-just-in-time-skill-loading.md +29 -0
- package/.planning/decisions/ADR-009-enterprise-integration-retry-policy.md +8 -0
- package/.planning/decisions/ADR-010-governance-tier-escalation.md +8 -0
- package/.planning/decisions/ADR-011-multi-developer-handoff-contract.md +8 -0
- package/.planning/decisions/ADR-012-intelligence-feedback-loops.md +19 -0
- package/.planning/decisions/ADR-013-mindforge-md-constitution.md +16 -0
- package/.planning/decisions/ADR-014-metrics-as-signals-not-evaluation.md +15 -0
- package/.planning/decisions/ADR-015-npm-based-skill-registry.md +26 -0
- package/.planning/decisions/ADR-016-ci-exit-code-0-on-timeout.md +27 -0
- package/.planning/decisions/ADR-017-sdk-localhost-only.md +28 -0
- package/.planning/decisions/ADR-018-installer-self-install-detection.md +15 -0
- package/.planning/decisions/ADR-019-self-update-scope-preservation.md +14 -0
- package/.planning/decisions/ADR-020-v1.0.0-stable-interface-contract.md +23 -0
- package/.planning/jira-sync.json +9 -0
- package/.planning/milestones/.gitkeep +1 -0
- package/.planning/phases/day1/REVIEW-DAY1.md +50 -0
- package/.planning/phases/day1/SECURITY-REVIEW-DAY1.md +15 -0
- package/.planning/phases/day2/REVIEW-DAY2.md +521 -0
- package/.planning/phases/day3/REVIEW-DAY3.md +234 -0
- package/.planning/slack-threads.json +6 -0
- package/CHANGELOG.md +175 -0
- package/LICENSE +21 -0
- package/MINDFORGE.md +76 -0
- package/README.md +182 -0
- package/RELEASENOTES.md +41 -0
- package/SECURITY.md +4 -0
- package/bin/install.js +120 -0
- package/bin/installer-core.js +292 -0
- package/bin/migrations/0.1.0-to-0.5.0.js +37 -0
- package/bin/migrations/0.5.0-to-0.6.0.js +17 -0
- package/bin/migrations/0.6.0-to-1.0.0.js +100 -0
- package/bin/migrations/migrate.js +151 -0
- package/bin/migrations/schema-versions.js +64 -0
- package/bin/updater/changelog-fetcher.js +62 -0
- package/bin/updater/self-update.js +169 -0
- package/bin/updater/version-comparator.js +68 -0
- package/bin/validate-config.js +92 -0
- package/bin/wizard/config-generator.js +112 -0
- package/bin/wizard/environment-detector.js +76 -0
- package/bin/wizard/setup-wizard.js +237 -0
- package/docs/Context/Master-Context.md +701 -0
- package/docs/architecture/README.md +35 -0
- package/docs/architecture/decision-records-index.md +26 -0
- package/docs/ci-cd-integration.md +30 -0
- package/docs/ci-quickstart.md +78 -0
- package/docs/commands-reference.md +11 -0
- package/docs/contributing/CONTRIBUTING.md +38 -0
- package/docs/contributing/plugin-authoring.md +50 -0
- package/docs/contributing/skill-authoring.md +41 -0
- package/docs/enterprise-setup.md +25 -0
- package/docs/faq.md +38 -0
- package/docs/getting-started.md +36 -0
- package/docs/governance-guide.md +23 -0
- package/docs/mindforge-md-reference.md +53 -0
- package/docs/monorepo-guide.md +26 -0
- package/docs/persona-customisation.md +56 -0
- package/docs/quick-verify.md +33 -0
- package/docs/reference/audit-events.md +53 -0
- package/docs/reference/commands.md +82 -0
- package/docs/reference/config-reference.md +64 -0
- package/docs/reference/sdk-api.md +48 -0
- package/docs/reference/skills-api.md +57 -0
- package/docs/release-checklist-guide.md +37 -0
- package/docs/requirements.md +29 -0
- package/docs/sdk-reference.md +27 -0
- package/docs/security/SECURITY.md +42 -0
- package/docs/security/penetration-test-results.md +31 -0
- package/docs/security/threat-model.md +142 -0
- package/docs/skills-authoring-guide.md +119 -0
- package/docs/skills-publishing-guide.md +21 -0
- package/docs/team-setup-guide.md +21 -0
- package/docs/troubleshooting.md +119 -0
- package/docs/tutorial.md +195 -0
- package/docs/upgrade.md +44 -0
- package/docs/user-guide.md +131 -0
- package/docs/usp-features.md +214 -0
- package/eslint.config.mjs +31 -0
- package/examples/starter-project/.planning/AUDIT.jsonl +1 -0
- package/examples/starter-project/.planning/HANDOFF.json +23 -0
- package/examples/starter-project/.planning/PROJECT.md +27 -0
- package/examples/starter-project/.planning/STATE.md +10 -0
- package/examples/starter-project/MINDFORGE.md +40 -0
- package/examples/starter-project/README.md +14 -0
- package/implementation-roadmap/day-1-imp/DAY1-HARDEN.md +823 -0
- package/implementation-roadmap/day-1-imp/DAY1-IMPLEMENT.md +2459 -0
- package/implementation-roadmap/day-1-imp/DAY1-REVIEW.md +288 -0
- package/implementation-roadmap/day-2-imp/DAY2-HARDEN.md +954 -0
- package/implementation-roadmap/day-2-imp/DAY2-IMPLEMENT.md +2347 -0
- package/implementation-roadmap/day-2-imp/DAY2-REVIEW.md +422 -0
- package/implementation-roadmap/day-3-imp/DAY3-HARDEN.md +870 -0
- package/implementation-roadmap/day-3-imp/DAY3-IMPLEMENT.md +2798 -0
- package/implementation-roadmap/day-3-imp/DAY3-REVIEW.md +484 -0
- package/implementation-roadmap/day-4-imp/DAY4-HARDEN.md +1087 -0
- package/implementation-roadmap/day-4-imp/DAY4-IMPLEMENT.md +2874 -0
- package/implementation-roadmap/day-4-imp/DAY4-REVIEW.md +386 -0
- package/implementation-roadmap/day-5-imp/DAY5-HARDEN.md +1078 -0
- package/implementation-roadmap/day-5-imp/DAY5-IMPLEMENT.md +3151 -0
- package/implementation-roadmap/day-5-imp/DAY5-REVIEW.md +345 -0
- package/implementation-roadmap/day-6-imp/DAY6-COMPLETE.md +3919 -0
- package/implementation-roadmap/day-7-imp-prod/DAY7-PRODUCTION-FINAL.md +4513 -0
- package/package.json +31 -0
- package/sdk/README.md +69 -0
- package/sdk/eslint.config.mjs +34 -0
- package/sdk/package-lock.json +1507 -0
- package/sdk/package.json +30 -0
- package/sdk/src/client.ts +133 -0
- package/sdk/src/commands.ts +63 -0
- package/sdk/src/events.ts +166 -0
- package/sdk/src/index.ts +22 -0
- package/sdk/src/types.ts +87 -0
- package/sdk/tsconfig.json +13 -0
- package/tests/audit.test.js +206 -0
- package/tests/ci-mode.test.js +162 -0
- package/tests/compaction.test.js +161 -0
- package/tests/distribution.test.js +205 -0
- package/tests/e2e.test.js +618 -0
- package/tests/governance.test.js +130 -0
- package/tests/install.test.js +209 -0
- package/tests/integrations.test.js +128 -0
- package/tests/intelligence.test.js +117 -0
- package/tests/metrics.test.js +96 -0
- package/tests/migration.test.js +309 -0
- package/tests/production.test.js +416 -0
- package/tests/sdk.test.js +200 -0
- package/tests/skills-platform.test.js +403 -0
- package/tests/wave-engine.test.js +338 -0
|
@@ -0,0 +1,4513 @@
|
|
|
1
|
+
# MindForge — Day 7: Production Hardening, Polish & v1.0.0 Public Release
|
|
2
|
+
# Branch: `feat/mindforge-production-release`
|
|
3
|
+
# Prerequisite: `feat/mindforge-distribution-platform` merged to `main`
|
|
4
|
+
# Version target: **v1.0.0 — First Stable Public Release**
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## BRANCH SETUP
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
git checkout main
|
|
12
|
+
git pull origin main
|
|
13
|
+
git checkout -b feat/mindforge-production-release
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
Verify all prior days are present and all 12 test suites pass with zero failures:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
cat package.json | grep '"version"' # Must be "0.6.0"
|
|
20
|
+
|
|
21
|
+
SUITES=(install wave-engine audit compaction skills-platform
|
|
22
|
+
integrations governance intelligence metrics
|
|
23
|
+
distribution ci-mode sdk)
|
|
24
|
+
|
|
25
|
+
for SUITE in "${SUITES[@]}"; do
|
|
26
|
+
printf "%-30s" " ${SUITE}..."
|
|
27
|
+
node tests/${SUITE}.test.js 2>&1 | tail -1
|
|
28
|
+
done
|
|
29
|
+
|
|
30
|
+
# ALL 12 must show "All * tests passed" — zero failures before Day 7 begins
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## DAY 7 SCOPE
|
|
36
|
+
|
|
37
|
+
Day 7 is the **Production Hardening & v1.0.0 Public Release** sprint.
|
|
38
|
+
|
|
39
|
+
Days 1–6 built MindForge. Day 7 makes it **shippable to the world**:
|
|
40
|
+
end-to-end tested across all installation paths, fully documented, performance-
|
|
41
|
+
profiled, self-maintaining, migration-safe, adversarially reviewed, and ready
|
|
42
|
+
for the public npm registry at the v1.0.0 stable interface contract.
|
|
43
|
+
|
|
44
|
+
| Component | Description |
|
|
45
|
+
|---|---|
|
|
46
|
+
| Complete `npx mindforge-cc` installer | Every code path covered: first-install, update, uninstall, self-install, CI, bad input |
|
|
47
|
+
| Self-update mechanism | `/mindforge:update` — version check, changelog diff, scope-preserving apply |
|
|
48
|
+
| Version migration engine | Schema migration for HANDOFF.json / STATE.md / AUDIT.jsonl across all prior versions |
|
|
49
|
+
| Plugin system | First-class third-party extensions via `mindforge-plugin-` npm namespace |
|
|
50
|
+
| Token usage optimiser | Measure, profile, and systematically reduce Claude API token consumption |
|
|
51
|
+
| Cross-version compatibility layer | Graceful degradation when older schema files meet newer engine |
|
|
52
|
+
| Complete reference documentation | Full `docs/` hierarchy ready for public release |
|
|
53
|
+
| Threat model & security posture | Adversarial review of all seven attack surfaces |
|
|
54
|
+
| 50-point production readiness checklist | Every item is blocking — all must pass before v1.0.0 tag |
|
|
55
|
+
| v1.0.0 release pipeline | Complete tag, GitHub release, npm publish, announcement |
|
|
56
|
+
| Day 7 test suites | `tests/production.test.js`, `tests/migration.test.js`, `tests/e2e.test.js` |
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
61
|
+
# PART 1 — IMPLEMENTATION PROMPT
|
|
62
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## TASK 1 — Scaffold Day 7 directory additions
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
# Production hardening engine
|
|
70
|
+
mkdir -p .mindforge/production
|
|
71
|
+
touch .mindforge/production/token-optimiser.md
|
|
72
|
+
touch .mindforge/production/migration-engine.md
|
|
73
|
+
touch .mindforge/production/compatibility-layer.md
|
|
74
|
+
touch .mindforge/production/production-checklist.md
|
|
75
|
+
|
|
76
|
+
# Plugin system
|
|
77
|
+
mkdir -p .mindforge/plugins
|
|
78
|
+
touch .mindforge/plugins/plugin-schema.md
|
|
79
|
+
touch .mindforge/plugins/plugin-loader.md
|
|
80
|
+
touch .mindforge/plugins/plugin-registry.md
|
|
81
|
+
touch .mindforge/plugins/PLUGINS-MANIFEST.md
|
|
82
|
+
|
|
83
|
+
# Self-update system
|
|
84
|
+
mkdir -p bin/updater
|
|
85
|
+
touch bin/updater/self-update.js
|
|
86
|
+
touch bin/updater/changelog-fetcher.js
|
|
87
|
+
touch bin/updater/version-comparator.js
|
|
88
|
+
|
|
89
|
+
# Migration system
|
|
90
|
+
mkdir -p bin/migrations
|
|
91
|
+
touch bin/migrations/migrate.js
|
|
92
|
+
touch bin/migrations/schema-versions.js
|
|
93
|
+
touch bin/migrations/0.1.0-to-0.5.0.js
|
|
94
|
+
touch bin/migrations/0.5.0-to-0.6.0.js
|
|
95
|
+
touch bin/migrations/0.6.0-to-1.0.0.js
|
|
96
|
+
|
|
97
|
+
# Complete documentation hierarchy
|
|
98
|
+
mkdir -p docs/reference
|
|
99
|
+
mkdir -p docs/architecture
|
|
100
|
+
mkdir -p docs/contributing
|
|
101
|
+
mkdir -p docs/security
|
|
102
|
+
touch docs/reference/commands.md
|
|
103
|
+
touch docs/reference/skills-api.md
|
|
104
|
+
touch docs/reference/sdk-api.md
|
|
105
|
+
touch docs/reference/config-reference.md
|
|
106
|
+
touch docs/reference/audit-events.md
|
|
107
|
+
touch docs/architecture/README.md
|
|
108
|
+
touch docs/architecture/decision-records-index.md
|
|
109
|
+
touch docs/contributing/CONTRIBUTING.md
|
|
110
|
+
touch docs/contributing/skill-authoring.md
|
|
111
|
+
touch docs/contributing/plugin-authoring.md
|
|
112
|
+
touch docs/security/SECURITY.md
|
|
113
|
+
touch docs/security/threat-model.md
|
|
114
|
+
touch docs/security/penetration-test-results.md
|
|
115
|
+
|
|
116
|
+
# New commands
|
|
117
|
+
touch .claude/commands/mindforge/update.md
|
|
118
|
+
touch .claude/commands/mindforge/migrate.md
|
|
119
|
+
touch .claude/commands/mindforge/plugins.md
|
|
120
|
+
touch .claude/commands/mindforge/tokens.md
|
|
121
|
+
touch .claude/commands/mindforge/release.md
|
|
122
|
+
|
|
123
|
+
# Mirror to Antigravity
|
|
124
|
+
for cmd in update migrate plugins tokens release; do
|
|
125
|
+
cp .claude/commands/mindforge/${cmd}.md .agent/mindforge/${cmd}.md
|
|
126
|
+
done
|
|
127
|
+
|
|
128
|
+
# Test suites
|
|
129
|
+
touch tests/production.test.js
|
|
130
|
+
touch tests/migration.test.js
|
|
131
|
+
touch tests/e2e.test.js
|
|
132
|
+
|
|
133
|
+
# Release artifacts
|
|
134
|
+
touch .planning/RELEASE-CHECKLIST.md
|
|
135
|
+
touch SECURITY.md
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
**Commit:**
|
|
139
|
+
```bash
|
|
140
|
+
git add .
|
|
141
|
+
git commit -m "chore(day7): scaffold production release directory structure"
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
---
|
|
145
|
+
|
|
146
|
+
## TASK 2 — Write the complete `npx mindforge-cc` installer
|
|
147
|
+
|
|
148
|
+
The installer has been scaffolded across Days 1-6 but never implemented end-to-end.
|
|
149
|
+
Day 7 makes it production-complete — every edge case handled, every flag tested.
|
|
150
|
+
|
|
151
|
+
### `bin/install.js` — Main entry point (rewrite completely)
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
#!/usr/bin/env node
|
|
155
|
+
/**
|
|
156
|
+
* MindForge Installer — v1.0.0 Production Release
|
|
157
|
+
*
|
|
158
|
+
* USAGE:
|
|
159
|
+
* npx mindforge-cc@latest → Interactive setup wizard
|
|
160
|
+
* npx mindforge-cc@latest --claude --local → Install for Claude Code, local project
|
|
161
|
+
* npx mindforge-cc@latest --all --global → Install for all runtimes, globally
|
|
162
|
+
* npx mindforge-cc@latest --update → Update existing installation
|
|
163
|
+
* npx mindforge-cc@latest --uninstall → Remove MindForge
|
|
164
|
+
* npx mindforge-cc@latest --check → Check for updates (no install)
|
|
165
|
+
* npx mindforge-cc@latest --version → Print version and exit
|
|
166
|
+
* npx mindforge-cc@latest --help → Print usage and exit
|
|
167
|
+
*
|
|
168
|
+
* Runtime flags: --claude | --antigravity | --all
|
|
169
|
+
* Scope flags: --global (-g) | --local (-l)
|
|
170
|
+
* Action flags: --install (default) | --update | --uninstall | --check
|
|
171
|
+
* Control flags: --skip-wizard | --dry-run | --verbose | --force
|
|
172
|
+
*/
|
|
173
|
+
|
|
174
|
+
'use strict';
|
|
175
|
+
|
|
176
|
+
const VERSION = require('./package.json').version;
|
|
177
|
+
const ARGS = process.argv.slice(2);
|
|
178
|
+
|
|
179
|
+
// ── Minimum Node.js version gate ─────────────────────────────────────────────
|
|
180
|
+
const NODE_MAJOR = parseInt(process.versions.node.split('.')[0], 10);
|
|
181
|
+
if (NODE_MAJOR < 18) {
|
|
182
|
+
process.stderr.write(
|
|
183
|
+
`\n❌ MindForge requires Node.js 18 or later.\n` +
|
|
184
|
+
` Current: v${process.versions.node}\n` +
|
|
185
|
+
` Install: https://nodejs.org/en/download/\n\n`
|
|
186
|
+
);
|
|
187
|
+
process.exit(1);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// ── Quick-exit flags ──────────────────────────────────────────────────────────
|
|
191
|
+
if (ARGS.includes('--version') || ARGS.includes('-v')) {
|
|
192
|
+
process.stdout.write(`mindforge-cc v${VERSION}\n`);
|
|
193
|
+
process.exit(0);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (ARGS.includes('--help') || ARGS.includes('-h')) {
|
|
197
|
+
printHelp();
|
|
198
|
+
process.exit(0);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// ── Determine execution mode ──────────────────────────────────────────────────
|
|
202
|
+
const NON_INTERACTIVE_FLAGS = [
|
|
203
|
+
'--claude', '--antigravity', '--all',
|
|
204
|
+
'--global', '-g', '--local', '-l',
|
|
205
|
+
'--uninstall', '--update', '--check',
|
|
206
|
+
'--skip-wizard', '--dry-run',
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
const IS_NON_INTERACTIVE =
|
|
210
|
+
NON_INTERACTIVE_FLAGS.some(f => ARGS.includes(f)) ||
|
|
211
|
+
process.env.CI === 'true' ||
|
|
212
|
+
process.env.MINDFORGE_CI === 'true' ||
|
|
213
|
+
process.stdin.isTTY === false;
|
|
214
|
+
|
|
215
|
+
if (IS_NON_INTERACTIVE) {
|
|
216
|
+
require('./bin/installer-core').run(ARGS).catch(err => {
|
|
217
|
+
process.stderr.write(`\n❌ Installation failed: ${err.message}\n`);
|
|
218
|
+
process.stderr.write(` For help: npx mindforge-cc --help\n\n`);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
});
|
|
221
|
+
} else {
|
|
222
|
+
require('./bin/wizard/setup-wizard').main().catch(err => {
|
|
223
|
+
process.stderr.write(`\n❌ Setup wizard failed: ${err.message}\n`);
|
|
224
|
+
process.stderr.write(` Try non-interactive: npx mindforge-cc --claude --local\n\n`);
|
|
225
|
+
process.exit(1);
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function printHelp() {
|
|
230
|
+
process.stdout.write(`
|
|
231
|
+
⚡ MindForge v${VERSION} — Enterprise Agentic Framework
|
|
232
|
+
|
|
233
|
+
USAGE
|
|
234
|
+
npx mindforge-cc@latest [runtime] [scope] [action] [options]
|
|
235
|
+
|
|
236
|
+
RUNTIMES (pick one or use --all)
|
|
237
|
+
--claude Claude Code (~/.claude or .claude/)
|
|
238
|
+
--antigravity Antigravity (~/.gemini/antigravity or .agent/)
|
|
239
|
+
--all Both runtimes
|
|
240
|
+
|
|
241
|
+
SCOPE
|
|
242
|
+
--global, -g Install to home directory (all projects)
|
|
243
|
+
--local, -l Install to current directory (this project only)
|
|
244
|
+
|
|
245
|
+
ACTIONS (default: install)
|
|
246
|
+
--install Install MindForge (default)
|
|
247
|
+
--update Update existing installation
|
|
248
|
+
--uninstall Remove MindForge
|
|
249
|
+
--check Check for updates without installing
|
|
250
|
+
|
|
251
|
+
OPTIONS
|
|
252
|
+
--dry-run Show what would happen without making changes
|
|
253
|
+
--force Override existing installation without backup
|
|
254
|
+
--skip-wizard Skip interactive wizard even in TTY
|
|
255
|
+
--verbose Detailed output
|
|
256
|
+
--version, -v Print version
|
|
257
|
+
--help, -h Print this help
|
|
258
|
+
|
|
259
|
+
EXAMPLES
|
|
260
|
+
npx mindforge-cc@latest Interactive setup
|
|
261
|
+
npx mindforge-cc@latest --claude --local Local Claude Code install
|
|
262
|
+
npx mindforge-cc@latest --all --global Global install for all runtimes
|
|
263
|
+
npx mindforge-cc@latest --update --global Update global install
|
|
264
|
+
npx mindforge-cc@latest --uninstall --local Remove local install
|
|
265
|
+
|
|
266
|
+
DOCUMENTATION
|
|
267
|
+
https://github.com/mindforge-dev/mindforge
|
|
268
|
+
docs/enterprise-setup.md (after install)
|
|
269
|
+
\n`);
|
|
270
|
+
}
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
### `bin/installer-core.js` — Complete non-interactive installer
|
|
274
|
+
|
|
275
|
+
```javascript
|
|
276
|
+
/**
|
|
277
|
+
* MindForge Installer Core — Production v1.0.0
|
|
278
|
+
* Handles all non-interactive installation scenarios.
|
|
279
|
+
*/
|
|
280
|
+
'use strict';
|
|
281
|
+
|
|
282
|
+
const fs = require('fs');
|
|
283
|
+
const path = require('path');
|
|
284
|
+
const os = require('os');
|
|
285
|
+
|
|
286
|
+
const VERSION = require('../package.json').version;
|
|
287
|
+
|
|
288
|
+
// ── Runtime configurations ────────────────────────────────────────────────────
|
|
289
|
+
const RUNTIMES = {
|
|
290
|
+
claude: {
|
|
291
|
+
globalDir: path.join(os.homedir(), '.claude'),
|
|
292
|
+
localDir: '.claude',
|
|
293
|
+
commandsSubdir: 'commands/mindforge',
|
|
294
|
+
entryFile: 'CLAUDE.md',
|
|
295
|
+
},
|
|
296
|
+
antigravity: {
|
|
297
|
+
globalDir: path.join(os.homedir(), '.gemini', 'antigravity'),
|
|
298
|
+
localDir: '.agent',
|
|
299
|
+
commandsSubdir: 'mindforge',
|
|
300
|
+
entryFile: 'CLAUDE.md',
|
|
301
|
+
},
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
// ── File system utilities ─────────────────────────────────────────────────────
|
|
305
|
+
const fsu = {
|
|
306
|
+
exists: p => fs.existsSync(p),
|
|
307
|
+
read: p => fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : '',
|
|
308
|
+
write: (p, t) => { fsu.ensureDir(path.dirname(p)); fs.writeFileSync(p, t, 'utf8'); },
|
|
309
|
+
ensureDir: p => { if (!fs.existsSync(p)) fs.mkdirSync(p, { recursive: true }); },
|
|
310
|
+
copy: (src, dst) => { fsu.ensureDir(path.dirname(dst)); fs.copyFileSync(src, dst); },
|
|
311
|
+
listFiles: p => fs.existsSync(p) ? fs.readdirSync(p) : [],
|
|
312
|
+
|
|
313
|
+
copyDir(src, dst, options = {}) {
|
|
314
|
+
const { excludePatterns = [] } = options;
|
|
315
|
+
fsu.ensureDir(dst);
|
|
316
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
317
|
+
const skip = excludePatterns.some(pat =>
|
|
318
|
+
typeof pat === 'string' ? entry.name === pat : pat.test(entry.name)
|
|
319
|
+
);
|
|
320
|
+
if (skip) continue;
|
|
321
|
+
|
|
322
|
+
const s = path.join(src, entry.name);
|
|
323
|
+
const d = path.join(dst, entry.name);
|
|
324
|
+
entry.isDirectory() ? fsu.copyDir(s, d, options) : fsu.copy(s, d);
|
|
325
|
+
}
|
|
326
|
+
},
|
|
327
|
+
};
|
|
328
|
+
|
|
329
|
+
// ── Self-install detection ────────────────────────────────────────────────────
|
|
330
|
+
function isSelfInstall() {
|
|
331
|
+
const pkgPath = path.join(process.cwd(), 'package.json');
|
|
332
|
+
if (!fsu.exists(pkgPath)) return false;
|
|
333
|
+
try {
|
|
334
|
+
return JSON.parse(fsu.read(pkgPath)).name === 'mindforge-cc';
|
|
335
|
+
} catch {
|
|
336
|
+
return false;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ── Source root ───────────────────────────────────────────────────────────────
|
|
341
|
+
const SOURCE_ROOT = path.resolve(__dirname, '..');
|
|
342
|
+
const src = (...parts) => path.join(SOURCE_ROOT, ...parts);
|
|
343
|
+
|
|
344
|
+
// ── Sensitive file exclusions (never copy these) ──────────────────────────────
|
|
345
|
+
const SENSITIVE_EXCLUDE = [
|
|
346
|
+
'.env', /^\.env\..*/,
|
|
347
|
+
'*.key', /\.key$/,
|
|
348
|
+
'*.pem', /\.pem$/,
|
|
349
|
+
'secrets', /^secrets$/,
|
|
350
|
+
'.secrets', /^\.secrets$/,
|
|
351
|
+
];
|
|
352
|
+
|
|
353
|
+
// ── CLAUDE.md safe copy ───────────────────────────────────────────────────────
|
|
354
|
+
function safeCopyClaude(src, dst, options = {}) {
|
|
355
|
+
const { force = false, verbose = false } = options;
|
|
356
|
+
|
|
357
|
+
if (fsu.exists(dst)) {
|
|
358
|
+
const existing = fsu.read(dst);
|
|
359
|
+
|
|
360
|
+
if (!force) {
|
|
361
|
+
// Back up non-MindForge CLAUDE.md files
|
|
362
|
+
if (!existing.includes('MindForge')) {
|
|
363
|
+
const backup = `${dst}.backup-${Date.now()}`;
|
|
364
|
+
fsu.copy(dst, backup);
|
|
365
|
+
const sizeKb = (existing.length / 1024).toFixed(1);
|
|
366
|
+
console.log(` ⚠️ Backed up existing CLAUDE.md (${sizeKb}KB) → ${path.basename(backup)}`);
|
|
367
|
+
if (existing.length > 5000) {
|
|
368
|
+
console.log(` Large file detected — review the backup for custom instructions`);
|
|
369
|
+
console.log(` to merge into the new CLAUDE.md.`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
fsu.copy(src, dst);
|
|
376
|
+
if (verbose) console.log(` → ${dst}`);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
// ── Install verification ──────────────────────────────────────────────────────
|
|
380
|
+
function verifyInstall(baseDir, cmdsDir, runtime) {
|
|
381
|
+
// Minimum required files for a functional installation
|
|
382
|
+
const required = [
|
|
383
|
+
path.join(baseDir, 'CLAUDE.md'),
|
|
384
|
+
path.join(cmdsDir, 'help.md'),
|
|
385
|
+
path.join(cmdsDir, 'init-project.md'),
|
|
386
|
+
path.join(cmdsDir, 'health.md'),
|
|
387
|
+
path.join(cmdsDir, 'execute-phase.md'),
|
|
388
|
+
path.join(cmdsDir, 'security-scan.md'),
|
|
389
|
+
];
|
|
390
|
+
|
|
391
|
+
const missing = required.filter(f => !fsu.exists(f));
|
|
392
|
+
|
|
393
|
+
if (missing.length > 0) {
|
|
394
|
+
console.error(`\n ❌ Install verification failed — ${missing.length} required file(s) missing:`);
|
|
395
|
+
missing.forEach(f => console.error(` ${f}`));
|
|
396
|
+
console.error(`\n Retry: npx mindforge-cc@latest --${runtime} --${scope} --force`);
|
|
397
|
+
process.exit(1);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ── Install single runtime ────────────────────────────────────────────────────
|
|
402
|
+
async function install(runtime, scope, options = {}) {
|
|
403
|
+
const { dryRun = false, force = false, verbose = false } = options;
|
|
404
|
+
const cfg = RUNTIMES[runtime];
|
|
405
|
+
const baseDir = scope === 'global' ? cfg.globalDir : path.join(process.cwd(), cfg.localDir);
|
|
406
|
+
const cmdsDir = path.join(baseDir, cfg.commandsSubdir);
|
|
407
|
+
const selfInstall = isSelfInstall();
|
|
408
|
+
|
|
409
|
+
console.log(`\n Runtime : ${runtime}`);
|
|
410
|
+
console.log(` Scope : ${scope} → ${baseDir}`);
|
|
411
|
+
if (dryRun) console.log(` Mode : DRY RUN (no changes)`);
|
|
412
|
+
if (selfInstall) console.log(` ⚠️ Self-install detected — skipping framework file copy`);
|
|
413
|
+
|
|
414
|
+
if (dryRun) {
|
|
415
|
+
console.log(`\n Would install:`);
|
|
416
|
+
console.log(` CLAUDE.md → ${path.join(baseDir, 'CLAUDE.md')}`);
|
|
417
|
+
console.log(` ${fsu.listFiles(src('.claude', 'commands', 'mindforge')).length} commands → ${cmdsDir}`);
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ── 1. Install CLAUDE.md ────────────────────────────────────────────────────
|
|
422
|
+
const claudeSrc = runtime === 'claude'
|
|
423
|
+
? src('.claude', 'CLAUDE.md')
|
|
424
|
+
: src('.agent', 'CLAUDE.md');
|
|
425
|
+
|
|
426
|
+
if (fsu.exists(claudeSrc)) {
|
|
427
|
+
safeCopyClaude(claudeSrc, path.join(baseDir, 'CLAUDE.md'), { force, verbose });
|
|
428
|
+
console.log(` ✅ CLAUDE.md`);
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// ── 2. Install commands ─────────────────────────────────────────────────────
|
|
432
|
+
const cmdSrc = runtime === 'claude'
|
|
433
|
+
? src('.claude', 'commands', 'mindforge')
|
|
434
|
+
: src('.agent', 'mindforge');
|
|
435
|
+
|
|
436
|
+
if (fsu.exists(cmdSrc)) {
|
|
437
|
+
fsu.ensureDir(cmdsDir);
|
|
438
|
+
const files = fsu.listFiles(cmdSrc).filter(f => f.endsWith('.md'));
|
|
439
|
+
files.forEach(f => fsu.copy(path.join(cmdSrc, f), path.join(cmdsDir, f)));
|
|
440
|
+
console.log(` ✅ ${files.length} commands`);
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
// ── 3. Framework files (local scope only, non-self-install) ─────────────────
|
|
444
|
+
if (scope === 'local' && !selfInstall) {
|
|
445
|
+
// .mindforge/ — framework engine files
|
|
446
|
+
const forgeSrc = src('.mindforge');
|
|
447
|
+
const forgeDst = path.join(process.cwd(), '.mindforge');
|
|
448
|
+
if (fsu.exists(forgeSrc)) {
|
|
449
|
+
fsu.copyDir(forgeSrc, forgeDst, { excludePatterns: SENSITIVE_EXCLUDE });
|
|
450
|
+
console.log(` ✅ .mindforge/ (framework engine)`);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// .planning/ — create only if it doesn't already exist (preserve project state)
|
|
454
|
+
const planningDst = path.join(process.cwd(), '.planning');
|
|
455
|
+
if (!fsu.exists(planningDst)) {
|
|
456
|
+
const planningSrc = src('.planning');
|
|
457
|
+
if (fsu.exists(planningSrc)) {
|
|
458
|
+
fsu.copyDir(planningSrc, planningDst, { excludePatterns: SENSITIVE_EXCLUDE });
|
|
459
|
+
console.log(` ✅ .planning/ (state templates)`);
|
|
460
|
+
}
|
|
461
|
+
} else {
|
|
462
|
+
console.log(` ⏭️ .planning/ already exists — preserved (run /mindforge:health to verify)`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// MINDFORGE.md — create only if it doesn't already exist
|
|
466
|
+
const mindforgemDst = path.join(process.cwd(), 'MINDFORGE.md');
|
|
467
|
+
const mindforgemSrc = src('MINDFORGE.md');
|
|
468
|
+
if (!fsu.exists(mindforgemDst) && fsu.exists(mindforgemSrc)) {
|
|
469
|
+
fsu.copy(mindforgemSrc, mindforgemDst);
|
|
470
|
+
console.log(` ✅ MINDFORGE.md (project constitution)`);
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
// bin/ utilities (validate-config, wizard)
|
|
474
|
+
const binDst = path.join(process.cwd(), 'bin');
|
|
475
|
+
const binSrc = src('bin');
|
|
476
|
+
if (fsu.exists(binSrc) && !fsu.exists(binDst)) {
|
|
477
|
+
fsu.copyDir(binSrc, binDst, { excludePatterns: SENSITIVE_EXCLUDE });
|
|
478
|
+
console.log(` ✅ bin/ (utilities)`);
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
// ── 4. Verify installation ──────────────────────────────────────────────────
|
|
483
|
+
verifyInstall(baseDir, cmdsDir, runtime);
|
|
484
|
+
console.log(` ✅ Install verified`);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// ── Uninstall ─────────────────────────────────────────────────────────────────
|
|
488
|
+
async function uninstall(runtime, scope, options = {}) {
|
|
489
|
+
const { dryRun = false } = options;
|
|
490
|
+
const cfg = RUNTIMES[runtime];
|
|
491
|
+
const baseDir = scope === 'global' ? cfg.globalDir : path.join(process.cwd(), cfg.localDir);
|
|
492
|
+
const cmdsDir = path.join(baseDir, cfg.commandsSubdir);
|
|
493
|
+
const claudeMd = path.join(baseDir, 'CLAUDE.md');
|
|
494
|
+
|
|
495
|
+
console.log(`\n Uninstalling MindForge (${runtime} / ${scope})...`);
|
|
496
|
+
if (dryRun) {
|
|
497
|
+
console.log(` Would remove: ${cmdsDir}`);
|
|
498
|
+
if (fsu.exists(claudeMd) && fsu.read(claudeMd).includes('MindForge'))
|
|
499
|
+
console.log(` Would remove: ${claudeMd}`);
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
// Remove commands directory
|
|
504
|
+
if (fsu.exists(cmdsDir)) {
|
|
505
|
+
fs.rmSync(cmdsDir, { recursive: true, force: true });
|
|
506
|
+
console.log(` ✅ Removed: ${cmdsDir}`);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
// Remove CLAUDE.md only if it's a MindForge-generated file
|
|
510
|
+
if (fsu.exists(claudeMd) && fsu.read(claudeMd).includes('MindForge')) {
|
|
511
|
+
fs.unlinkSync(claudeMd);
|
|
512
|
+
console.log(` ✅ Removed: ${claudeMd}`);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
// Preserve .planning/ and .mindforge/ — user data, not our files to delete
|
|
516
|
+
console.log(` ℹ️ .planning/ and .mindforge/ preserved (user data)`);
|
|
517
|
+
console.log(` Remove manually if desired.`);
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// ── Main run ──────────────────────────────────────────────────────────────────
|
|
521
|
+
async function run(args) {
|
|
522
|
+
const runtime = args.includes('--antigravity') ? 'antigravity'
|
|
523
|
+
: args.includes('--all') ? 'all'
|
|
524
|
+
: 'claude';
|
|
525
|
+
const scope = args.includes('--global') || args.includes('-g') ? 'global' : 'local';
|
|
526
|
+
const dryRun = args.includes('--dry-run');
|
|
527
|
+
const force = args.includes('--force');
|
|
528
|
+
const verbose = args.includes('--verbose');
|
|
529
|
+
const isUninstall = args.includes('--uninstall');
|
|
530
|
+
const isUpdate = args.includes('--update');
|
|
531
|
+
const isCheck = args.includes('--check');
|
|
532
|
+
const options = { dryRun, force, verbose };
|
|
533
|
+
|
|
534
|
+
console.log(`\n⚡ MindForge v${VERSION} — Enterprise Agentic Framework\n`);
|
|
535
|
+
|
|
536
|
+
// Check for updates only
|
|
537
|
+
if (isCheck) {
|
|
538
|
+
const { checkAndUpdate } = require('./updater/self-update');
|
|
539
|
+
await checkAndUpdate({ apply: false });
|
|
540
|
+
return;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const runtimes = runtime === 'all' ? Object.keys(RUNTIMES) : [runtime];
|
|
544
|
+
|
|
545
|
+
for (const rt of runtimes) {
|
|
546
|
+
if (isUninstall) await uninstall(rt, scope, options);
|
|
547
|
+
else if (isUpdate) await install(rt, scope, { ...options, isUpdate: true });
|
|
548
|
+
else await install(rt, scope, options);
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
if (!isUninstall) {
|
|
552
|
+
console.log(`\n ✅ MindForge v${VERSION} installed (${runtime} / ${scope})\n`);
|
|
553
|
+
console.log(` Next steps:`);
|
|
554
|
+
console.log(` 1. Open Claude Code or Antigravity in your project directory`);
|
|
555
|
+
console.log(` 2. Run: /mindforge:health (verify installation)`);
|
|
556
|
+
console.log(` 3. Run: /mindforge:init-project (new project)`);
|
|
557
|
+
console.log(` OR /mindforge:map-codebase (existing project)\n`);
|
|
558
|
+
} else {
|
|
559
|
+
console.log(`\n ✅ MindForge uninstalled\n`);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
module.exports = { run, install, uninstall };
|
|
564
|
+
```
|
|
565
|
+
|
|
566
|
+
**Commit:**
|
|
567
|
+
```bash
|
|
568
|
+
git add bin/install.js bin/installer-core.js
|
|
569
|
+
git commit -m "feat(installer): complete production-grade installer — all code paths, edge cases, DRY_RUN"
|
|
570
|
+
```
|
|
571
|
+
|
|
572
|
+
---
|
|
573
|
+
|
|
574
|
+
## TASK 3 — Write the Self-Update System
|
|
575
|
+
|
|
576
|
+
### `bin/updater/version-comparator.js`
|
|
577
|
+
|
|
578
|
+
```javascript
|
|
579
|
+
/**
|
|
580
|
+
* MindForge — Version Comparator
|
|
581
|
+
* Pure functions for semver comparison.
|
|
582
|
+
* No external dependencies — must work offline.
|
|
583
|
+
*/
|
|
584
|
+
'use strict';
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* Compare two semver strings (strips leading 'v').
|
|
588
|
+
* Returns: negative if a < b, 0 if a == b, positive if a > b
|
|
589
|
+
*/
|
|
590
|
+
function compareSemver(a, b) {
|
|
591
|
+
const pa = a.replace(/^v/, '').split('.').map(Number);
|
|
592
|
+
const pb = b.replace(/^v/, '').split('.').map(Number);
|
|
593
|
+
for (let i = 0; i < 3; i++) {
|
|
594
|
+
const diff = (pa[i] || 0) - (pb[i] || 0);
|
|
595
|
+
if (diff !== 0) return diff;
|
|
596
|
+
}
|
|
597
|
+
return 0;
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
/**
|
|
601
|
+
* Determine the upgrade type between two versions.
|
|
602
|
+
* Returns 'major' | 'minor' | 'patch' | 'none'
|
|
603
|
+
*/
|
|
604
|
+
function upgradeType(current, latest) {
|
|
605
|
+
const c = current.replace(/^v/, '').split('.').map(Number);
|
|
606
|
+
const l = latest.replace(/^v/, '').split('.').map(Number);
|
|
607
|
+
if (l[0] > c[0]) return 'major';
|
|
608
|
+
if (l[1] > c[1]) return 'minor';
|
|
609
|
+
if (l[2] > c[2]) return 'patch';
|
|
610
|
+
return 'none';
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
/**
|
|
614
|
+
* Fetch the latest published version from the npm registry.
|
|
615
|
+
* Returns null on any error — callers must handle gracefully.
|
|
616
|
+
* Timeout: 5 seconds (respects enterprise proxies that may be slow).
|
|
617
|
+
*/
|
|
618
|
+
async function fetchLatestVersion(packageName = 'mindforge-cc') {
|
|
619
|
+
const https = require('https');
|
|
620
|
+
return new Promise(resolve => {
|
|
621
|
+
const options = {
|
|
622
|
+
hostname: 'registry.npmjs.org',
|
|
623
|
+
path: `/${encodeURIComponent(packageName)}/latest`,
|
|
624
|
+
method: 'GET',
|
|
625
|
+
headers: { 'Accept': 'application/json', 'User-Agent': `mindforge-cc/${require('../../package.json').version}` },
|
|
626
|
+
timeout: 5000,
|
|
627
|
+
};
|
|
628
|
+
|
|
629
|
+
const req = https.request(options, res => {
|
|
630
|
+
if (res.statusCode !== 200) { resolve(null); return; }
|
|
631
|
+
let body = '';
|
|
632
|
+
res.setEncoding('utf8');
|
|
633
|
+
res.on('data', chunk => { body += chunk; });
|
|
634
|
+
res.on('end', () => {
|
|
635
|
+
try { resolve(JSON.parse(body).version || null); }
|
|
636
|
+
catch { resolve(null); }
|
|
637
|
+
});
|
|
638
|
+
});
|
|
639
|
+
|
|
640
|
+
req.on('error', () => resolve(null));
|
|
641
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
642
|
+
req.end();
|
|
643
|
+
});
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
module.exports = { compareSemver, upgradeType, fetchLatestVersion };
|
|
647
|
+
```
|
|
648
|
+
|
|
649
|
+
### `bin/updater/changelog-fetcher.js`
|
|
650
|
+
|
|
651
|
+
```javascript
|
|
652
|
+
/**
|
|
653
|
+
* MindForge — Changelog Fetcher
|
|
654
|
+
* Downloads and parses CHANGELOG.md entries between two versions.
|
|
655
|
+
* Used by /mindforge:update to show what changed.
|
|
656
|
+
*/
|
|
657
|
+
'use strict';
|
|
658
|
+
|
|
659
|
+
const { compareSemver } = require('./version-comparator');
|
|
660
|
+
|
|
661
|
+
const CHANGELOG_URL = 'https://raw.githubusercontent.com/mindforge-dev/mindforge/main/CHANGELOG.md';
|
|
662
|
+
|
|
663
|
+
/**
|
|
664
|
+
* Fetch CHANGELOG.md and extract entries between fromVersion and toVersion.
|
|
665
|
+
* Returns formatted markdown string, or null if unavailable.
|
|
666
|
+
*/
|
|
667
|
+
async function fetchChangelog(fromVersion, toVersion) {
|
|
668
|
+
const raw = await fetchRaw();
|
|
669
|
+
if (!raw) return null;
|
|
670
|
+
return extractEntries(raw, fromVersion, toVersion);
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async function fetchRaw() {
|
|
674
|
+
const https = require('https');
|
|
675
|
+
return new Promise(resolve => {
|
|
676
|
+
const req = https.get(CHANGELOG_URL, { timeout: 8000 }, res => {
|
|
677
|
+
if (res.statusCode !== 200) { resolve(null); return; }
|
|
678
|
+
let body = '';
|
|
679
|
+
res.setEncoding('utf8');
|
|
680
|
+
res.on('data', chunk => { body += chunk; });
|
|
681
|
+
res.on('end', () => resolve(body));
|
|
682
|
+
});
|
|
683
|
+
req.on('error', () => resolve(null));
|
|
684
|
+
req.on('timeout', () => { req.destroy(); resolve(null); });
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Parse CHANGELOG.md and extract version sections in range (from, to].
|
|
690
|
+
*/
|
|
691
|
+
function extractEntries(changelog, fromVersion, toVersion) {
|
|
692
|
+
const sections = [];
|
|
693
|
+
let current = null;
|
|
694
|
+
|
|
695
|
+
for (const line of changelog.split('\n')) {
|
|
696
|
+
const vMatch = line.match(/^## \[?v?(\d+\.\d+\.\d+)/);
|
|
697
|
+
if (vMatch) {
|
|
698
|
+
if (current) sections.push(current);
|
|
699
|
+
const v = vMatch[1];
|
|
700
|
+
const inRange = compareSemver(v, fromVersion) > 0 && compareSemver(v, toVersion) <= 0;
|
|
701
|
+
current = inRange ? { version: v, lines: [line] } : null;
|
|
702
|
+
} else if (current) {
|
|
703
|
+
current.lines.push(line);
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
if (current) sections.push(current);
|
|
707
|
+
|
|
708
|
+
return sections.length
|
|
709
|
+
? sections.map(s => s.lines.join('\n').trimEnd()).join('\n\n')
|
|
710
|
+
: null;
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
module.exports = { fetchChangelog };
|
|
714
|
+
```
|
|
715
|
+
|
|
716
|
+
### `bin/updater/self-update.js`
|
|
717
|
+
|
|
718
|
+
```javascript
|
|
719
|
+
/**
|
|
720
|
+
* MindForge — Self-Update Engine
|
|
721
|
+
* Full update workflow: check → changelog → scope detection → apply → migrate → verify.
|
|
722
|
+
*/
|
|
723
|
+
'use strict';
|
|
724
|
+
|
|
725
|
+
const path = require('path');
|
|
726
|
+
const fs = require('fs');
|
|
727
|
+
const { execSync } = require('child_process');
|
|
728
|
+
const { compareSemver, upgradeType, fetchLatestVersion } = require('./version-comparator');
|
|
729
|
+
const { fetchChangelog } = require('./changelog-fetcher');
|
|
730
|
+
|
|
731
|
+
const CURRENT_VERSION = require('../../package.json').version;
|
|
732
|
+
|
|
733
|
+
/**
|
|
734
|
+
* Detect where MindForge was originally installed.
|
|
735
|
+
* Checks local before global (local installs take precedence).
|
|
736
|
+
* Returns { scope: 'local'|'global', runtime: 'claude'|'antigravity' }
|
|
737
|
+
*/
|
|
738
|
+
function detectInstallScope() {
|
|
739
|
+
const home = process.env.HOME || process.env.USERPROFILE || '';
|
|
740
|
+
const cwd = process.cwd();
|
|
741
|
+
|
|
742
|
+
const locations = [
|
|
743
|
+
{ scope: 'local', runtime: 'claude', file: path.join(cwd, '.claude', 'CLAUDE.md') },
|
|
744
|
+
{ scope: 'local', runtime: 'antigravity', file: path.join(cwd, '.agent', 'CLAUDE.md') },
|
|
745
|
+
{ scope: 'global', runtime: 'claude', file: path.join(home, '.claude', 'CLAUDE.md') },
|
|
746
|
+
{ scope: 'global', runtime: 'antigravity', file: path.join(home, '.gemini', 'antigravity', 'CLAUDE.md') },
|
|
747
|
+
];
|
|
748
|
+
|
|
749
|
+
for (const loc of locations) {
|
|
750
|
+
if (fs.existsSync(loc.file) && fs.readFileSync(loc.file, 'utf8').includes('MindForge')) {
|
|
751
|
+
return { scope: loc.scope, runtime: loc.runtime };
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
// Default: global claude (most common installation)
|
|
756
|
+
return { scope: 'global', runtime: 'claude' };
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
/**
|
|
760
|
+
* Read the schema_version from HANDOFF.json.
|
|
761
|
+
* This is the authoritative "what version are the .planning files" source.
|
|
762
|
+
* Must be read BEFORE the update runs (after update, installer version = new).
|
|
763
|
+
*/
|
|
764
|
+
function readHandoffSchemaVersion() {
|
|
765
|
+
const handoffPath = path.join(process.cwd(), '.planning', 'HANDOFF.json');
|
|
766
|
+
if (!fs.existsSync(handoffPath)) return null;
|
|
767
|
+
try {
|
|
768
|
+
const data = JSON.parse(fs.readFileSync(handoffPath, 'utf8'));
|
|
769
|
+
return data.schema_version || null;
|
|
770
|
+
} catch {
|
|
771
|
+
return null;
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
/**
|
|
776
|
+
* Main update entry point.
|
|
777
|
+
* Called by /mindforge:update command.
|
|
778
|
+
*
|
|
779
|
+
* options:
|
|
780
|
+
* apply {boolean} — actually apply the update (default: false = check only)
|
|
781
|
+
* force {boolean} — skip major-version safety warning
|
|
782
|
+
* skipChangelog {boolean} — skip changelog fetch
|
|
783
|
+
*/
|
|
784
|
+
async function checkAndUpdate(options = {}) {
|
|
785
|
+
const { apply = false, force = false, skipChangelog = false } = options;
|
|
786
|
+
|
|
787
|
+
const TTY = process.stdout.isTTY;
|
|
788
|
+
const bold = s => TTY ? `\x1b[1m${s}\x1b[0m` : s;
|
|
789
|
+
const warn = s => TTY ? `\x1b[33m${s}\x1b[0m` : s;
|
|
790
|
+
const ok = s => TTY ? `\x1b[32m${s}\x1b[0m` : s;
|
|
791
|
+
const err = s => TTY ? `\x1b[31m${s}\x1b[0m` : s;
|
|
792
|
+
|
|
793
|
+
console.log(`\n${bold('⚡ MindForge Update Check')}\n`);
|
|
794
|
+
console.log(` Current : v${CURRENT_VERSION}`);
|
|
795
|
+
process.stdout.write(` Latest : checking npm registry... `);
|
|
796
|
+
|
|
797
|
+
const latestVersion = await fetchLatestVersion();
|
|
798
|
+
if (!latestVersion) {
|
|
799
|
+
console.log(`${warn('unavailable')}`);
|
|
800
|
+
console.log(`\n ${warn('⚠️')} Cannot reach npm registry. Check your internet connection.`);
|
|
801
|
+
console.log(` Manual check: npm info mindforge-cc version\n`);
|
|
802
|
+
return { status: 'check-failed' };
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
const type = upgradeType(CURRENT_VERSION, latestVersion);
|
|
806
|
+
if (type === 'none') {
|
|
807
|
+
console.log(`${ok('up to date')}`);
|
|
808
|
+
console.log(`\n ${ok('✅')} v${CURRENT_VERSION} is the latest version.\n`);
|
|
809
|
+
return { status: 'up-to-date', current: CURRENT_VERSION };
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
console.log(`${ok(`v${latestVersion} available`)}`);
|
|
813
|
+
console.log(` Update : ${CURRENT_VERSION} → ${latestVersion} ${warn(`(${type})`)}`);
|
|
814
|
+
|
|
815
|
+
// Major version safety gate
|
|
816
|
+
if (type === 'major' && !force) {
|
|
817
|
+
console.log(`\n ${warn('⚠️ MAJOR UPDATE')} — may contain breaking changes.`);
|
|
818
|
+
console.log(` Review the changelog before applying.`);
|
|
819
|
+
console.log(` To apply anyway: /mindforge:update --apply --force\n`);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// Fetch and display changelog
|
|
823
|
+
if (!skipChangelog) {
|
|
824
|
+
process.stdout.write(`\n Fetching changelog... `);
|
|
825
|
+
const changelog = await fetchChangelog(CURRENT_VERSION, latestVersion);
|
|
826
|
+
if (changelog) {
|
|
827
|
+
console.log(`done\n`);
|
|
828
|
+
console.log('─'.repeat(62));
|
|
829
|
+
console.log(changelog.slice(0, 3000)); // Max 3000 chars to avoid flooding terminal
|
|
830
|
+
if (changelog.length > 3000) console.log(`\n [changelog truncated — see CHANGELOG.md for full details]`);
|
|
831
|
+
console.log('─'.repeat(62));
|
|
832
|
+
} else {
|
|
833
|
+
console.log(`unavailable\n`);
|
|
834
|
+
}
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
if (!apply) {
|
|
838
|
+
console.log(`\n To apply: /mindforge:update --apply`);
|
|
839
|
+
console.log(` Or directly: npx mindforge-cc@${latestVersion} --update\n`);
|
|
840
|
+
return { status: 'update-available', current: CURRENT_VERSION, latest: latestVersion, type };
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// ── Apply the update ────────────────────────────────────────────────────────
|
|
844
|
+
|
|
845
|
+
// Capture schema version BEFORE updating (critical for correct migration path)
|
|
846
|
+
const fromSchemaVersion = readHandoffSchemaVersion() || CURRENT_VERSION;
|
|
847
|
+
console.log(`\n Schema version: v${fromSchemaVersion}`);
|
|
848
|
+
|
|
849
|
+
// Detect original install scope to preserve it
|
|
850
|
+
const { scope, runtime } = detectInstallScope();
|
|
851
|
+
console.log(` Install scope : ${runtime} / ${scope}`);
|
|
852
|
+
console.log(`\n Applying update...`);
|
|
853
|
+
|
|
854
|
+
try {
|
|
855
|
+
execSync(
|
|
856
|
+
`npx mindforge-cc@${latestVersion} --${runtime} --${scope}`,
|
|
857
|
+
{ stdio: 'inherit', timeout: 120_000 }
|
|
858
|
+
);
|
|
859
|
+
} catch (updateErr) {
|
|
860
|
+
console.error(`\n ${err('❌')} Update failed: ${updateErr.message}`);
|
|
861
|
+
console.error(` Fallback: npx mindforge-cc@${latestVersion} --${runtime} --${scope}`);
|
|
862
|
+
return { status: 'update-failed', error: updateErr.message };
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// Run schema migration with the pre-update schema version
|
|
866
|
+
try {
|
|
867
|
+
const { runMigrations } = require('../migrations/migrate');
|
|
868
|
+
const migResult = await runMigrations(fromSchemaVersion, latestVersion);
|
|
869
|
+
if (migResult.status === 'migrated') {
|
|
870
|
+
console.log(` ✅ Schema migrated: v${fromSchemaVersion} → v${latestVersion}`);
|
|
871
|
+
}
|
|
872
|
+
} catch (migErr) {
|
|
873
|
+
console.warn(` ${warn('⚠️')} Schema migration had an issue: ${migErr.message}`);
|
|
874
|
+
console.warn(` Run /mindforge:migrate manually if state files look wrong.`);
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
console.log(`\n ${ok('✅')} MindForge updated to v${latestVersion}\n`);
|
|
878
|
+
console.log(` Run /mindforge:health to verify the update.\n`);
|
|
879
|
+
return { status: 'updated', from: CURRENT_VERSION, to: latestVersion };
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
module.exports = { checkAndUpdate, detectInstallScope, readHandoffSchemaVersion };
|
|
883
|
+
```
|
|
884
|
+
|
|
885
|
+
**Commit:**
|
|
886
|
+
```bash
|
|
887
|
+
git add bin/updater/
|
|
888
|
+
git commit -m "feat(updater): implement self-update with scope detection, changelog, schema-version-aware migration"
|
|
889
|
+
```
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
|
|
893
|
+
## TASK 4 — Write the Version Migration Engine
|
|
894
|
+
|
|
895
|
+
### `bin/migrations/schema-versions.js`
|
|
896
|
+
|
|
897
|
+
```javascript
|
|
898
|
+
/**
|
|
899
|
+
* MindForge — Schema Version Registry
|
|
900
|
+
* Documents every breaking schema change across all released versions.
|
|
901
|
+
* Used by the migration engine to determine what migrations are needed.
|
|
902
|
+
*/
|
|
903
|
+
'use strict';
|
|
904
|
+
|
|
905
|
+
const SCHEMA_HISTORY = [
|
|
906
|
+
{
|
|
907
|
+
version: '0.1.0',
|
|
908
|
+
date: '2026-01-01',
|
|
909
|
+
description: 'Initial release',
|
|
910
|
+
handoff_fields_added: [
|
|
911
|
+
'schema_version', 'project', 'phase', 'plan', 'next_task',
|
|
912
|
+
'blockers', 'decisions_needed', 'context_refs', '_warning', 'updated_at',
|
|
913
|
+
],
|
|
914
|
+
handoff_fields_removed: [],
|
|
915
|
+
audit_fields_added: ['id', 'timestamp', 'event', 'agent', 'phase'],
|
|
916
|
+
breaking: [],
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
version: '0.5.0',
|
|
920
|
+
date: '2026-01-15',
|
|
921
|
+
description: 'Intelligence layer — smart compaction adds structured fields',
|
|
922
|
+
handoff_fields_added: [
|
|
923
|
+
'decisions_made', 'discoveries', 'implicit_knowledge',
|
|
924
|
+
'quality_signals', 'compaction_level', 'compaction_timestamp',
|
|
925
|
+
],
|
|
926
|
+
handoff_fields_removed: [],
|
|
927
|
+
audit_fields_added: [],
|
|
928
|
+
breaking: [
|
|
929
|
+
'compaction_protocol.md now requires Level 1/2/3 classification',
|
|
930
|
+
],
|
|
931
|
+
},
|
|
932
|
+
{
|
|
933
|
+
version: '0.6.0',
|
|
934
|
+
date: '2026-02-01',
|
|
935
|
+
description: 'Distribution platform — adds per-developer and CI fields',
|
|
936
|
+
handoff_fields_added: [
|
|
937
|
+
'developer_id', 'session_id', 'recent_commits', 'recent_files',
|
|
938
|
+
],
|
|
939
|
+
handoff_fields_removed: [],
|
|
940
|
+
audit_fields_added: ['session_id'],
|
|
941
|
+
breaking: [
|
|
942
|
+
'AUDIT.jsonl entries should now include session_id',
|
|
943
|
+
'INTEGRATIONS-CONFIG.md gains EMERGENCY_APPROVERS field',
|
|
944
|
+
],
|
|
945
|
+
},
|
|
946
|
+
{
|
|
947
|
+
version: '1.0.0',
|
|
948
|
+
date: '2026-03-22',
|
|
949
|
+
description: 'First stable release — plugin system and stable interface contract',
|
|
950
|
+
handoff_fields_added: ['plugin_api_version'],
|
|
951
|
+
handoff_fields_removed: [],
|
|
952
|
+
audit_fields_added: [],
|
|
953
|
+
breaking: [
|
|
954
|
+
'VERIFY_PASS_RATE_WARNING_THRESHOLD in MINDFORGE.md is now 0.0-1.0 (was 0-100)',
|
|
955
|
+
'AUDIT.jsonl session_id is now required (was optional)',
|
|
956
|
+
'HANDOFF.json plugin_api_version field is now required for plugin compatibility',
|
|
957
|
+
],
|
|
958
|
+
},
|
|
959
|
+
];
|
|
960
|
+
|
|
961
|
+
module.exports = { SCHEMA_HISTORY };
|
|
962
|
+
```
|
|
963
|
+
|
|
964
|
+
### `bin/migrations/migrate.js`
|
|
965
|
+
|
|
966
|
+
```javascript
|
|
967
|
+
/**
|
|
968
|
+
* MindForge — Migration Runner
|
|
969
|
+
* Safely upgrades .planning/ file schemas between MindForge versions.
|
|
970
|
+
* Philosophy: never lose data, always back up, always verify after.
|
|
971
|
+
*/
|
|
972
|
+
'use strict';
|
|
973
|
+
|
|
974
|
+
const fs = require('fs');
|
|
975
|
+
const path = require('path');
|
|
976
|
+
|
|
977
|
+
// Re-export for use by self-update.js
|
|
978
|
+
const { compareSemver } = require('../updater/version-comparator');
|
|
979
|
+
module.exports.compareSemver = compareSemver;
|
|
980
|
+
|
|
981
|
+
const PLANNING_DIR = path.join(process.cwd(), '.planning');
|
|
982
|
+
|
|
983
|
+
const PATHS = {
|
|
984
|
+
handoff: path.join(PLANNING_DIR, 'HANDOFF.json'),
|
|
985
|
+
state: path.join(PLANNING_DIR, 'STATE.md'),
|
|
986
|
+
audit: path.join(PLANNING_DIR, 'AUDIT.jsonl'),
|
|
987
|
+
mindforgemd: path.join(process.cwd(), 'MINDFORGE.md'),
|
|
988
|
+
};
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Run all needed migrations from fromVersion to toVersion.
|
|
992
|
+
* Creates a backup first. Restores on failure.
|
|
993
|
+
* Returns { status, from, to, backupDir }
|
|
994
|
+
*/
|
|
995
|
+
async function runMigrations(fromVersion, toVersion) {
|
|
996
|
+
console.log(`\n Migration: v${fromVersion} → v${toVersion}`);
|
|
997
|
+
|
|
998
|
+
if (!fs.existsSync(PLANNING_DIR)) {
|
|
999
|
+
console.log(` ℹ️ No .planning/ directory found — skipping migration`);
|
|
1000
|
+
return { status: 'no-planning-dir' };
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
if (compareSemver(fromVersion, toVersion) >= 0) {
|
|
1004
|
+
console.log(` ✅ No migration needed`);
|
|
1005
|
+
return { status: 'no-migration-needed' };
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
// Determine which migrations to run
|
|
1009
|
+
const allMigrations = [
|
|
1010
|
+
require('./0.1.0-to-0.5.0'),
|
|
1011
|
+
require('./0.5.0-to-0.6.0'),
|
|
1012
|
+
require('./0.6.0-to-1.0.0'),
|
|
1013
|
+
].filter(m =>
|
|
1014
|
+
compareSemver(m.fromVersion, fromVersion) >= 0 &&
|
|
1015
|
+
compareSemver(m.toVersion, toVersion) <= 0
|
|
1016
|
+
);
|
|
1017
|
+
|
|
1018
|
+
if (allMigrations.length === 0) {
|
|
1019
|
+
console.log(` ✅ No applicable migrations`);
|
|
1020
|
+
return { status: 'no-migrations' };
|
|
1021
|
+
}
|
|
1022
|
+
|
|
1023
|
+
console.log(` Migrations to run (${allMigrations.length}):`);
|
|
1024
|
+
allMigrations.forEach(m => console.log(` • v${m.fromVersion} → v${m.toVersion}: ${m.description}`));
|
|
1025
|
+
|
|
1026
|
+
// ── Create backup (abort if backup fails — never migrate without a backup) ──
|
|
1027
|
+
const backupDir = path.join(PLANNING_DIR, `migration-backup-${Date.now()}`);
|
|
1028
|
+
let backupCreated = false;
|
|
1029
|
+
try {
|
|
1030
|
+
fs.mkdirSync(backupDir, { recursive: true });
|
|
1031
|
+
const filesToBackup = Object.values(PATHS).filter(p => fs.existsSync(p));
|
|
1032
|
+
|
|
1033
|
+
if (filesToBackup.length === 0) {
|
|
1034
|
+
console.log(` ℹ️ No files to migrate`);
|
|
1035
|
+
fs.rmdirSync(backupDir);
|
|
1036
|
+
return { status: 'no-files' };
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
for (const f of filesToBackup) {
|
|
1040
|
+
fs.copyFileSync(f, path.join(backupDir, path.basename(f)));
|
|
1041
|
+
}
|
|
1042
|
+
|
|
1043
|
+
// Verify backup: check every backed-up file exists and has content
|
|
1044
|
+
const backed = fs.readdirSync(backupDir);
|
|
1045
|
+
const allBacked = filesToBackup.every(f =>
|
|
1046
|
+
backed.includes(path.basename(f)) &&
|
|
1047
|
+
fs.statSync(path.join(backupDir, path.basename(f))).size > 0
|
|
1048
|
+
);
|
|
1049
|
+
|
|
1050
|
+
if (!allBacked) throw new Error('Backup verification failed — some files missing or empty');
|
|
1051
|
+
|
|
1052
|
+
backupCreated = true;
|
|
1053
|
+
console.log(` 📦 Backup: .planning/${path.basename(backupDir)} (${filesToBackup.length} files)`);
|
|
1054
|
+
|
|
1055
|
+
} catch (backupErr) {
|
|
1056
|
+
// Abort cleanly — no migration is safer than a migration without backup
|
|
1057
|
+
if (fs.existsSync(backupDir)) {
|
|
1058
|
+
try { fs.rmSync(backupDir, { recursive: true, force: true }); } catch {}
|
|
1059
|
+
}
|
|
1060
|
+
throw new Error(
|
|
1061
|
+
`Migration aborted: cannot create backup (${backupErr.message}). ` +
|
|
1062
|
+
`Free disk space and retry.`
|
|
1063
|
+
);
|
|
1064
|
+
}
|
|
1065
|
+
|
|
1066
|
+
// ── Execute migrations in order ───────────────────────────────────────────
|
|
1067
|
+
for (const migration of allMigrations) {
|
|
1068
|
+
console.log(`\n Running: v${migration.fromVersion} → v${migration.toVersion}...`);
|
|
1069
|
+
try {
|
|
1070
|
+
await migration.run(PATHS);
|
|
1071
|
+
console.log(` ✅ Complete`);
|
|
1072
|
+
} catch (migErr) {
|
|
1073
|
+
console.error(` ❌ Failed: ${migErr.message}`);
|
|
1074
|
+
console.log(` Restoring from backup...`);
|
|
1075
|
+
|
|
1076
|
+
// Restore all files from backup
|
|
1077
|
+
for (const f of fs.readdirSync(backupDir)) {
|
|
1078
|
+
const dst = Object.values(PATHS).find(p => path.basename(p) === f) ||
|
|
1079
|
+
path.join(PLANNING_DIR, f);
|
|
1080
|
+
fs.copyFileSync(path.join(backupDir, f), dst);
|
|
1081
|
+
}
|
|
1082
|
+
console.log(` ✅ Restored from backup. No changes applied.`);
|
|
1083
|
+
throw new Error(`Migration failed at v${migration.toVersion}: ${migErr.message}`);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1087
|
+
// ── Update schema_version in HANDOFF.json ────────────────────────────────
|
|
1088
|
+
if (fs.existsSync(PATHS.handoff)) {
|
|
1089
|
+
const handoff = JSON.parse(fs.readFileSync(PATHS.handoff, 'utf8'));
|
|
1090
|
+
handoff.schema_version = toVersion;
|
|
1091
|
+
handoff._migration_completed = new Date().toISOString();
|
|
1092
|
+
fs.writeFileSync(PATHS.handoff, JSON.stringify(handoff, null, 2) + '\n');
|
|
1093
|
+
}
|
|
1094
|
+
|
|
1095
|
+
console.log(`\n ✅ All migrations complete: v${fromVersion} → v${toVersion}`);
|
|
1096
|
+
console.log(` Backup retained: .planning/${path.basename(backupDir)}`);
|
|
1097
|
+
console.log(` Remove when satisfied: rm -rf .planning/${path.basename(backupDir)}\n`);
|
|
1098
|
+
|
|
1099
|
+
return { status: 'migrated', from: fromVersion, to: toVersion, backupDir };
|
|
1100
|
+
}
|
|
1101
|
+
|
|
1102
|
+
module.exports.runMigrations = runMigrations;
|
|
1103
|
+
```
|
|
1104
|
+
|
|
1105
|
+
### `bin/migrations/0.6.0-to-1.0.0.js`
|
|
1106
|
+
|
|
1107
|
+
```javascript
|
|
1108
|
+
/**
|
|
1109
|
+
* MindForge Migration: v0.6.0 → v1.0.0
|
|
1110
|
+
*
|
|
1111
|
+
* Changes:
|
|
1112
|
+
* 1. HANDOFF.json: add `plugin_api_version` field
|
|
1113
|
+
* 2. AUDIT.jsonl: backfill `session_id` for entries missing it
|
|
1114
|
+
* 3. MINDFORGE.md: convert VERIFY_PASS_RATE_WARNING_THRESHOLD if in old 0-100 format
|
|
1115
|
+
* 4. STATE.md: add v1.0.0 compatibility note if it doesn't already have one
|
|
1116
|
+
*/
|
|
1117
|
+
'use strict';
|
|
1118
|
+
|
|
1119
|
+
const fs = require('fs');
|
|
1120
|
+
|
|
1121
|
+
module.exports = {
|
|
1122
|
+
fromVersion: '0.6.0',
|
|
1123
|
+
toVersion: '1.0.0',
|
|
1124
|
+
description: 'Add plugin_api_version; backfill session_id; normalise MINDFORGE.md thresholds',
|
|
1125
|
+
|
|
1126
|
+
async run(paths) {
|
|
1127
|
+
// ── 1. HANDOFF.json ───────────────────────────────────────────────────────
|
|
1128
|
+
if (fs.existsSync(paths.handoff)) {
|
|
1129
|
+
const handoff = JSON.parse(fs.readFileSync(paths.handoff, 'utf8'));
|
|
1130
|
+
|
|
1131
|
+
if (!handoff.plugin_api_version) {
|
|
1132
|
+
handoff.plugin_api_version = '1.0.0';
|
|
1133
|
+
}
|
|
1134
|
+
// Ensure fields added in 0.6.0 are present (for projects that skipped intermediate updates)
|
|
1135
|
+
if (!Array.isArray(handoff.recent_commits)) handoff.recent_commits = [];
|
|
1136
|
+
if (!Array.isArray(handoff.recent_files)) handoff.recent_files = [];
|
|
1137
|
+
if (!handoff.session_id) {
|
|
1138
|
+
handoff.session_id = `migrated-${Date.now()}`;
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
fs.writeFileSync(paths.handoff, JSON.stringify(handoff, null, 2) + '\n');
|
|
1142
|
+
console.log(` • HANDOFF.json: added plugin_api_version, normalised arrays`);
|
|
1143
|
+
}
|
|
1144
|
+
|
|
1145
|
+
// ── 2. AUDIT.jsonl ────────────────────────────────────────────────────────
|
|
1146
|
+
if (fs.existsSync(paths.audit)) {
|
|
1147
|
+
const raw = fs.readFileSync(paths.audit, 'utf8');
|
|
1148
|
+
const lines = raw.split('\n').filter(Boolean);
|
|
1149
|
+
let modified = 0;
|
|
1150
|
+
|
|
1151
|
+
const updated = lines.map(line => {
|
|
1152
|
+
try {
|
|
1153
|
+
const entry = JSON.parse(line);
|
|
1154
|
+
if (!entry.session_id) {
|
|
1155
|
+
entry.session_id = 'migrated-from-pre-1.0';
|
|
1156
|
+
modified++;
|
|
1157
|
+
return JSON.stringify(entry);
|
|
1158
|
+
}
|
|
1159
|
+
return line;
|
|
1160
|
+
} catch {
|
|
1161
|
+
return line; // Preserve unparseable lines exactly as-is (quarantine pattern)
|
|
1162
|
+
}
|
|
1163
|
+
});
|
|
1164
|
+
|
|
1165
|
+
if (modified > 0) {
|
|
1166
|
+
fs.writeFileSync(paths.audit, updated.join('\n') + '\n');
|
|
1167
|
+
console.log(` • AUDIT.jsonl: backfilled session_id in ${modified} of ${lines.length} entries`);
|
|
1168
|
+
} else {
|
|
1169
|
+
console.log(` • AUDIT.jsonl: all entries already have session_id`);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
// ── 3. MINDFORGE.md ───────────────────────────────────────────────────────
|
|
1174
|
+
if (fs.existsSync(paths.mindforgemd)) {
|
|
1175
|
+
let content = fs.readFileSync(paths.mindforgemd, 'utf8');
|
|
1176
|
+
let changed = false;
|
|
1177
|
+
|
|
1178
|
+
// Convert VERIFY_PASS_RATE_WARNING_THRESHOLD from percent (>1) to decimal
|
|
1179
|
+
const pctPattern = /^(VERIFY_PASS_RATE_WARNING_THRESHOLD=)(\d+(?:\.\d+)?)(\s*)$/m;
|
|
1180
|
+
const match = content.match(pctPattern);
|
|
1181
|
+
if (match) {
|
|
1182
|
+
const val = parseFloat(match[2]);
|
|
1183
|
+
if (val > 1) {
|
|
1184
|
+
// Old format: integer like 75 → new format: 0.75
|
|
1185
|
+
const newVal = (val / 100).toFixed(2);
|
|
1186
|
+
content = content.replace(pctPattern, `$1${newVal}$3`);
|
|
1187
|
+
changed = true;
|
|
1188
|
+
console.log(` • MINDFORGE.md: converted VERIFY_PASS_RATE_WARNING_THRESHOLD ${val} → ${newVal}`);
|
|
1189
|
+
}
|
|
1190
|
+
// If val <= 1, it's already in the correct format — no change needed
|
|
1191
|
+
}
|
|
1192
|
+
|
|
1193
|
+
if (changed) fs.writeFileSync(paths.mindforgemd, content);
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
// ── 4. STATE.md ───────────────────────────────────────────────────────────
|
|
1197
|
+
if (fs.existsSync(paths.state)) {
|
|
1198
|
+
const content = fs.readFileSync(paths.state, 'utf8');
|
|
1199
|
+
if (!content.includes('v1.0.0') && !content.includes('MindForge v1')) {
|
|
1200
|
+
fs.appendFileSync(paths.state,
|
|
1201
|
+
`\n\n---\n*Migrated to MindForge v1.0.0 schema on ${new Date().toISOString().slice(0,10)}*\n`
|
|
1202
|
+
);
|
|
1203
|
+
console.log(` • STATE.md: added v1.0.0 migration note`);
|
|
1204
|
+
}
|
|
1205
|
+
}
|
|
1206
|
+
},
|
|
1207
|
+
};
|
|
1208
|
+
```
|
|
1209
|
+
|
|
1210
|
+
### `bin/migrations/0.1.0-to-0.5.0.js` and `0.5.0-to-0.6.0.js`
|
|
1211
|
+
|
|
1212
|
+
```javascript
|
|
1213
|
+
// bin/migrations/0.1.0-to-0.5.0.js
|
|
1214
|
+
'use strict';
|
|
1215
|
+
const fs = require('fs');
|
|
1216
|
+
module.exports = {
|
|
1217
|
+
fromVersion: '0.1.0',
|
|
1218
|
+
toVersion: '0.5.0',
|
|
1219
|
+
description: 'Add decisions_made, discoveries, implicit_knowledge to HANDOFF.json',
|
|
1220
|
+
async run(paths) {
|
|
1221
|
+
if (!fs.existsSync(paths.handoff)) return;
|
|
1222
|
+
const handoff = JSON.parse(fs.readFileSync(paths.handoff, 'utf8'));
|
|
1223
|
+
if (!handoff.decisions_made) handoff.decisions_made = [];
|
|
1224
|
+
if (!handoff.discoveries) handoff.discoveries = [];
|
|
1225
|
+
if (!handoff.implicit_knowledge) handoff.implicit_knowledge = [];
|
|
1226
|
+
if (!handoff.quality_signals) handoff.quality_signals = [];
|
|
1227
|
+
fs.writeFileSync(paths.handoff, JSON.stringify(handoff, null, 2) + '\n');
|
|
1228
|
+
console.log(` • HANDOFF.json: added intelligence layer fields`);
|
|
1229
|
+
},
|
|
1230
|
+
};
|
|
1231
|
+
|
|
1232
|
+
// bin/migrations/0.5.0-to-0.6.0.js
|
|
1233
|
+
'use strict';
|
|
1234
|
+
const fs = require('fs');
|
|
1235
|
+
module.exports = {
|
|
1236
|
+
fromVersion: '0.5.0',
|
|
1237
|
+
toVersion: '0.6.0',
|
|
1238
|
+
description: 'Add developer_id, session_id, recent_commits, recent_files to HANDOFF.json',
|
|
1239
|
+
async run(paths) {
|
|
1240
|
+
if (!fs.existsSync(paths.handoff)) return;
|
|
1241
|
+
const handoff = JSON.parse(fs.readFileSync(paths.handoff, 'utf8'));
|
|
1242
|
+
if (!handoff.developer_id) handoff.developer_id = null;
|
|
1243
|
+
if (!handoff.session_id) handoff.session_id = null;
|
|
1244
|
+
if (!Array.isArray(handoff.recent_commits)) handoff.recent_commits = [];
|
|
1245
|
+
if (!Array.isArray(handoff.recent_files)) handoff.recent_files = [];
|
|
1246
|
+
fs.writeFileSync(paths.handoff, JSON.stringify(handoff, null, 2) + '\n');
|
|
1247
|
+
console.log(` • HANDOFF.json: added distribution platform fields`);
|
|
1248
|
+
},
|
|
1249
|
+
};
|
|
1250
|
+
```
|
|
1251
|
+
|
|
1252
|
+
**Commit:**
|
|
1253
|
+
```bash
|
|
1254
|
+
git add bin/migrations/
|
|
1255
|
+
git commit -m "feat(migration): complete migration engine — all versions 0.1.0→1.0.0 with backup/restore"
|
|
1256
|
+
```
|
|
1257
|
+
|
|
1258
|
+
---
|
|
1259
|
+
|
|
1260
|
+
## TASK 5 — Write the Plugin System
|
|
1261
|
+
|
|
1262
|
+
### `.mindforge/plugins/plugin-schema.md`
|
|
1263
|
+
|
|
1264
|
+
```markdown
|
|
1265
|
+
# MindForge Plugin System — Schema v1.0.0
|
|
1266
|
+
|
|
1267
|
+
## Philosophy
|
|
1268
|
+
Plugins extend MindForge without modifying the core framework files.
|
|
1269
|
+
They are first-class citizens: versioned, validated, injection-guarded, and audited.
|
|
1270
|
+
|
|
1271
|
+
## Package naming convention
|
|
1272
|
+
`mindforge-plugin-[category]-[name]`
|
|
1273
|
+
|
|
1274
|
+
Examples:
|
|
1275
|
+
- `mindforge-plugin-jira-advanced` — Advanced Jira sprint and velocity commands
|
|
1276
|
+
- `mindforge-plugin-testing-playwright` — Playwright E2E testing skill and commands
|
|
1277
|
+
- `mindforge-plugin-cloud-aws` — AWS deployment patterns and runbooks
|
|
1278
|
+
- `mindforge-plugin-design-figma` — Figma design review integration
|
|
1279
|
+
|
|
1280
|
+
## What a plugin can provide
|
|
1281
|
+
|
|
1282
|
+
| Component | Description | File location |
|
|
1283
|
+
|---|---|---|
|
|
1284
|
+
| Commands | New slash commands | `commands/[name].md` |
|
|
1285
|
+
| Skills | New skill packs | `skills/[name]/SKILL.md` |
|
|
1286
|
+
| Personas | New agent personas | `personas/[name].md` |
|
|
1287
|
+
| Hooks | Lifecycle event handlers | `hooks/[hook-name].md` |
|
|
1288
|
+
|
|
1289
|
+
## `plugin.json` manifest (required in every plugin package)
|
|
1290
|
+
|
|
1291
|
+
```json
|
|
1292
|
+
{
|
|
1293
|
+
"name": "mindforge-plugin-jira-advanced",
|
|
1294
|
+
"version": "1.0.0",
|
|
1295
|
+
"description": "Advanced Jira integration with sprint planning and velocity tracking",
|
|
1296
|
+
"mindforge": {
|
|
1297
|
+
"type": "plugin",
|
|
1298
|
+
"min_mindforge_version": "1.0.0",
|
|
1299
|
+
"plugin_api_version": "1.0.0",
|
|
1300
|
+
"provides": {
|
|
1301
|
+
"commands": ["jira-sprint", "jira-velocity", "jira-epics"],
|
|
1302
|
+
"skills": [],
|
|
1303
|
+
"personas": [],
|
|
1304
|
+
"hooks": {
|
|
1305
|
+
"post_phase_complete": "hooks/post-phase.md",
|
|
1306
|
+
"pre_plan_phase": null,
|
|
1307
|
+
"post_execute_phase": null,
|
|
1308
|
+
"post_verify_phase": null,
|
|
1309
|
+
"post_milestone": null,
|
|
1310
|
+
"pre_security_scan": null,
|
|
1311
|
+
"post_security_scan": null
|
|
1312
|
+
}
|
|
1313
|
+
},
|
|
1314
|
+
"permissions": {
|
|
1315
|
+
"read_audit_log": true,
|
|
1316
|
+
"write_audit_log": true,
|
|
1317
|
+
"read_state": true,
|
|
1318
|
+
"write_state": false,
|
|
1319
|
+
"network_access": true,
|
|
1320
|
+
"file_system_write": false
|
|
1321
|
+
},
|
|
1322
|
+
"conflicts_with": []
|
|
1323
|
+
},
|
|
1324
|
+
"keywords": ["mindforge", "mindforge-plugin", "jira"],
|
|
1325
|
+
"license": "MIT"
|
|
1326
|
+
}
|
|
1327
|
+
```
|
|
1328
|
+
|
|
1329
|
+
## Permissions — advisory model
|
|
1330
|
+
|
|
1331
|
+
The permission system is advisory, not OS-enforced. Permissions are:
|
|
1332
|
+
- **Declared** in plugin.json before installation
|
|
1333
|
+
- **Displayed** to the user for review at install time
|
|
1334
|
+
- **Recorded** in AUDIT.jsonl with plugin name as the agent field
|
|
1335
|
+
- **Enforced** through the agent's own governance layer (plan-first, audit requirements)
|
|
1336
|
+
|
|
1337
|
+
A plugin instructing the agent to take actions outside its declared permissions
|
|
1338
|
+
would still be subject to all MindForge governance checks (secret detection,
|
|
1339
|
+
quality gates, Tier classification). The permission declaration is a statement
|
|
1340
|
+
of intent — it enables trust decisions, not OS-level sandboxing.
|
|
1341
|
+
|
|
1342
|
+
**The real security layer is:**
|
|
1343
|
+
1. Injection guard at install time (blocks obvious adversarial instructions)
|
|
1344
|
+
2. Level 1/2/3 validation of all plugin SKILL.md files
|
|
1345
|
+
3. User review of plugin commands before installation
|
|
1346
|
+
4. Complete audit trail with plugin name on every action
|
|
1347
|
+
|
|
1348
|
+
## Available lifecycle hooks
|
|
1349
|
+
|
|
1350
|
+
| Hook name | Trigger point | Typical use case |
|
|
1351
|
+
|---|---|---|
|
|
1352
|
+
| `post_phase_complete` | After /mindforge:verify-phase passes | External notifications, custom reports |
|
|
1353
|
+
| `pre_plan_phase` | Before /mindforge:plan-phase starts | Requirement fetching from external system |
|
|
1354
|
+
| `post_execute_phase` | After all execution waves complete | Custom metrics collection |
|
|
1355
|
+
| `post_verify_phase` | After UAT sign-off | Custom acceptance report |
|
|
1356
|
+
| `post_milestone` | After /mindforge:complete-milestone | Release announcement, changelog distribution |
|
|
1357
|
+
| `pre_security_scan` | Before /mindforge:security-scan | Custom scan tool preparation |
|
|
1358
|
+
| `post_security_scan` | After security scan completes | Custom finding routing, ticketing |
|
|
1359
|
+
|
|
1360
|
+
## Reserved command names
|
|
1361
|
+
|
|
1362
|
+
These 36 command names are permanently reserved for MindForge built-ins.
|
|
1363
|
+
Plugin commands with these names will be prefixed with the plugin name:
|
|
1364
|
+
|
|
1365
|
+
```
|
|
1366
|
+
help, init-project, plan-phase, execute-phase, verify-phase, ship,
|
|
1367
|
+
next, quick, status, debug, skills, review, security-scan, map-codebase,
|
|
1368
|
+
discuss-phase, audit, milestone, complete-milestone, approve, sync-jira,
|
|
1369
|
+
sync-confluence, health, retrospective, profile-team, metrics, init-org,
|
|
1370
|
+
install-skill, publish-skill, pr-review, workspace, benchmark, update,
|
|
1371
|
+
migrate, plugins, tokens, release
|
|
1372
|
+
```
|
|
1373
|
+
```
|
|
1374
|
+
|
|
1375
|
+
---
|
|
1376
|
+
|
|
1377
|
+
### `.mindforge/plugins/plugin-loader.md`
|
|
1378
|
+
|
|
1379
|
+
```markdown
|
|
1380
|
+
# MindForge Plugin System — Loader Protocol
|
|
1381
|
+
|
|
1382
|
+
## Loading sequence (runs at session start)
|
|
1383
|
+
|
|
1384
|
+
### Step 1 — Discover installed plugins
|
|
1385
|
+
```bash
|
|
1386
|
+
MANIFEST=".mindforge/plugins/PLUGINS-MANIFEST.md"
|
|
1387
|
+
[ -f "${MANIFEST}" ] || { echo "No plugins installed"; return; }
|
|
1388
|
+
|
|
1389
|
+
# Extract plugin names from manifest table rows
|
|
1390
|
+
PLUGINS=$(grep "^| " "${MANIFEST}" | grep -v "^| Name" | grep -v "none" | \
|
|
1391
|
+
awk -F'|' '{gsub(/[[:space:]]/, "", $2); print $2}' | grep -v '^$')
|
|
1392
|
+
```
|
|
1393
|
+
|
|
1394
|
+
### Step 2 — Validate each installed plugin
|
|
1395
|
+
|
|
1396
|
+
For each installed plugin directory at `.mindforge/plugins/[plugin-name]/`:
|
|
1397
|
+
|
|
1398
|
+
1. **plugin.json exists and is valid JSON**
|
|
1399
|
+
2. **plugin_api_version compatibility**: read `plugin.json mindforge.plugin_api_version`
|
|
1400
|
+
and verify it matches the current supported API version (`1.0.0`)
|
|
1401
|
+
3. **min_mindforge_version compatibility**: verify current MindForge version satisfies minimum
|
|
1402
|
+
4. **Injection guard**: run against all command, skill, and persona .md files in the plugin
|
|
1403
|
+
- If injection patterns found: do NOT load. Log AUDIT entry, alert user
|
|
1404
|
+
5. **Level 1 + Level 2 validation**: for every SKILL.md in the plugin
|
|
1405
|
+
|
|
1406
|
+
### Step 3 — Load plugin components
|
|
1407
|
+
|
|
1408
|
+
**Commands:**
|
|
1409
|
+
```bash
|
|
1410
|
+
for CMD_FILE in ".mindforge/plugins/[plugin]/commands/"*.md; do
|
|
1411
|
+
CMD_NAME=$(basename "${CMD_FILE}" .md)
|
|
1412
|
+
|
|
1413
|
+
# Check for conflict with reserved names
|
|
1414
|
+
RESERVED_NAMES=(help init-project plan-phase execute-phase verify-phase ship
|
|
1415
|
+
next quick status debug skills review security-scan
|
|
1416
|
+
map-codebase discuss-phase audit milestone complete-milestone
|
|
1417
|
+
approve sync-jira sync-confluence health retrospective
|
|
1418
|
+
profile-team metrics init-org install-skill publish-skill
|
|
1419
|
+
pr-review workspace benchmark update migrate plugins tokens release)
|
|
1420
|
+
|
|
1421
|
+
IS_RESERVED=false
|
|
1422
|
+
for RESERVED in "${RESERVED_NAMES[@]}"; do
|
|
1423
|
+
[ "${CMD_NAME}" = "${RESERVED}" ] && { IS_RESERVED=true; break; }
|
|
1424
|
+
done
|
|
1425
|
+
|
|
1426
|
+
# Prefix if conflict
|
|
1427
|
+
FINAL_NAME="${IS_RESERVED:-true && "${PLUGIN_NAME}-${CMD_NAME}" || "${CMD_NAME}"}"
|
|
1428
|
+
|
|
1429
|
+
cp "${CMD_FILE}" ".claude/commands/mindforge/${FINAL_NAME}.md"
|
|
1430
|
+
cp "${CMD_FILE}" ".agent/mindforge/${FINAL_NAME}.md"
|
|
1431
|
+
done
|
|
1432
|
+
```
|
|
1433
|
+
|
|
1434
|
+
**Skills:** Registered in MANIFEST.md under Tier 2 section (prefix: `[plugin-name]-`)
|
|
1435
|
+
**Personas:** Installed as `.mindforge/personas/[plugin-name]-[persona].md`
|
|
1436
|
+
**Hooks:** Registered in `.mindforge/plugins/hooks-registry.md`
|
|
1437
|
+
|
|
1438
|
+
### Step 4 — Report loaded plugins
|
|
1439
|
+
|
|
1440
|
+
At session start, CLAUDE.md reads the loaded plugins list and reports:
|
|
1441
|
+
```
|
|
1442
|
+
Active plugins (2):
|
|
1443
|
+
jira-advanced v1.0.0 — hooks: post_phase_complete
|
|
1444
|
+
testing-playwright v0.9.0 — skills: playwright-e2e
|
|
1445
|
+
```
|
|
1446
|
+
|
|
1447
|
+
If any plugin fails validation: skip it, report error, continue loading others.
|
|
1448
|
+
Never fail the session start because a plugin is invalid.
|
|
1449
|
+
|
|
1450
|
+
### Step 5 — Write AUDIT entry for plugin load
|
|
1451
|
+
|
|
1452
|
+
```json
|
|
1453
|
+
{
|
|
1454
|
+
"event": "plugins_loaded",
|
|
1455
|
+
"plugins": [
|
|
1456
|
+
{ "name": "mindforge-plugin-jira-advanced", "version": "1.0.0", "hooks": ["post_phase_complete"] }
|
|
1457
|
+
],
|
|
1458
|
+
"failed": []
|
|
1459
|
+
}
|
|
1460
|
+
```
|
|
1461
|
+
```
|
|
1462
|
+
|
|
1463
|
+
---
|
|
1464
|
+
|
|
1465
|
+
### `.mindforge/plugins/PLUGINS-MANIFEST.md`
|
|
1466
|
+
|
|
1467
|
+
```markdown
|
|
1468
|
+
# MindForge Plugins Manifest
|
|
1469
|
+
# Schema version: 1.0.0
|
|
1470
|
+
# This file is managed by /mindforge:plugins install|uninstall
|
|
1471
|
+
|
|
1472
|
+
## Installed plugins
|
|
1473
|
+
|
|
1474
|
+
| Name | Version | Status | Min MindForge | Permissions |
|
|
1475
|
+
|---|---|---|---|---|
|
|
1476
|
+
| (no plugins installed) | | | | |
|
|
1477
|
+
|
|
1478
|
+
## Available plugins (public registry)
|
|
1479
|
+
|
|
1480
|
+
Search: `npm search mindforge-plugin`
|
|
1481
|
+
Install: `/mindforge:plugins install [plugin-name]`
|
|
1482
|
+
|
|
1483
|
+
## Plugin development
|
|
1484
|
+
|
|
1485
|
+
To create a plugin: see `docs/contributing/plugin-authoring.md`
|
|
1486
|
+
To publish: `npm publish --access public`
|
|
1487
|
+
To validate: `node bin/validate-config.js --type plugin ./plugin.json`
|
|
1488
|
+
|
|
1489
|
+
## Hooks registry
|
|
1490
|
+
(populated automatically when plugins with hooks are installed)
|
|
1491
|
+
```
|
|
1492
|
+
|
|
1493
|
+
**Commit:**
|
|
1494
|
+
```bash
|
|
1495
|
+
git add .mindforge/plugins/
|
|
1496
|
+
git commit -m "feat(plugins): implement plugin schema, loader protocol, and manifest — v1.0.0 stable"
|
|
1497
|
+
```
|
|
1498
|
+
|
|
1499
|
+
---
|
|
1500
|
+
|
|
1501
|
+
## TASK 6 — Write the Token Usage Optimiser
|
|
1502
|
+
|
|
1503
|
+
### `.mindforge/production/token-optimiser.md`
|
|
1504
|
+
|
|
1505
|
+
```markdown
|
|
1506
|
+
# MindForge Production — Token Usage Optimiser
|
|
1507
|
+
|
|
1508
|
+
## Purpose
|
|
1509
|
+
Measure, profile, and systematically reduce Claude API token consumption.
|
|
1510
|
+
Token efficiency impacts both cost and session quality — a session that
|
|
1511
|
+
exhausts its context on large file reads has less capacity for reasoning.
|
|
1512
|
+
|
|
1513
|
+
## Token consumption model
|
|
1514
|
+
|
|
1515
|
+
### Where tokens are spent in a typical MindForge session
|
|
1516
|
+
|
|
1517
|
+
| Component | Typical estimate | Notes |
|
|
1518
|
+
|---|---|---|
|
|
1519
|
+
| Session startup (CLAUDE.md + ORG.md + PROJECT.md + STATE.md) | 4,000–9,000 | Fixed per session |
|
|
1520
|
+
| Each skill injected (full) | 2,500–6,000 | Depends on skill complexity |
|
|
1521
|
+
| Each skill injected (summary) | 300–600 | When > 3 skills loaded |
|
|
1522
|
+
| PLAN file per task | 400–1,800 | Lean plans save 800+ tokens |
|
|
1523
|
+
| File reading per task | 1,500–50,000 | Biggest variable cost |
|
|
1524
|
+
| Agent reasoning + response | 1,500–8,000 | |
|
|
1525
|
+
| Verify step + output | 400–1,500 | |
|
|
1526
|
+
|
|
1527
|
+
**Note:** These are estimates based on typical MindForge projects.
|
|
1528
|
+
Actual values depend on file sizes, code complexity, and model verbosity.
|
|
1529
|
+
Run `/mindforge:tokens` to see measured estimates for your project.
|
|
1530
|
+
|
|
1531
|
+
### Token efficiency threshold
|
|
1532
|
+
|
|
1533
|
+
| Efficiency | Meaning | Action |
|
|
1534
|
+
|---|---|---|
|
|
1535
|
+
| > 45% | Excellent — agent spending time on reasoning and output | No action |
|
|
1536
|
+
| 35–45% | Good — healthy balance | No action |
|
|
1537
|
+
| 20–35% | Acceptable — room to optimise | Review high-cost sessions |
|
|
1538
|
+
| < 20% | Poor — most tokens spent on reading, not reasoning | Apply optimisations below |
|
|
1539
|
+
|
|
1540
|
+
```
|
|
1541
|
+
token_efficiency = useful_output_tokens / total_tokens_consumed
|
|
1542
|
+
useful_output_tokens = tokens in SUMMARY files + verified code changes + ADRs written
|
|
1543
|
+
```
|
|
1544
|
+
|
|
1545
|
+
## Optimisation strategies
|
|
1546
|
+
|
|
1547
|
+
### Strategy 1 — Lean PLAN `<action>` fields (highest impact)
|
|
1548
|
+
|
|
1549
|
+
**Anti-pattern:** Over-specified actions that tell the agent HOW to implement,
|
|
1550
|
+
rather than WHAT to implement. These waste tokens on instructions the agent's
|
|
1551
|
+
persona and skills already provide.
|
|
1552
|
+
|
|
1553
|
+
```xml
|
|
1554
|
+
<!-- ❌ Over-specified: 720 tokens — redundant with developer.md persona -->
|
|
1555
|
+
<action>
|
|
1556
|
+
Create a TypeScript interface called UserProfile in src/types/user.ts.
|
|
1557
|
+
The interface should have the following fields: id (string, UUID v4),
|
|
1558
|
+
email (string, validated with RFC 5322 regex), firstName (string, min 1 char),
|
|
1559
|
+
lastName (string, min 1 char), createdAt (Date, UTC), updatedAt (Date, UTC),
|
|
1560
|
+
deletedAt (Date | null), role (string enum: 'admin' | 'editor' | 'viewer'),
|
|
1561
|
+
preferences object with keys: theme ('light' | 'dark'), notifications (boolean),
|
|
1562
|
+
timezone (string). Add JSDoc comments with @param tags for each field.
|
|
1563
|
+
Export as a named export. Use strict TypeScript types throughout.
|
|
1564
|
+
</action>
|
|
1565
|
+
|
|
1566
|
+
<!-- ✅ Lean: 140 tokens — trusts the developer persona -->
|
|
1567
|
+
<action>
|
|
1568
|
+
Create UserProfile interface in src/types/user.ts.
|
|
1569
|
+
Fields: id (UUID), email (RFC 5322 validated), name fields,
|
|
1570
|
+
timestamps (created/updated/deleted), role enum (admin/editor/viewer),
|
|
1571
|
+
preferences (theme, notifications, timezone).
|
|
1572
|
+
Full JSDoc. Named export.
|
|
1573
|
+
</action>
|
|
1574
|
+
```
|
|
1575
|
+
|
|
1576
|
+
**Token saving: ~580 tokens per plan action. For 10 tasks: ~5,800 tokens (≈ one extra skill).**
|
|
1577
|
+
|
|
1578
|
+
### Strategy 2 — Just-in-time file reading
|
|
1579
|
+
|
|
1580
|
+
Current: subagents read ALL files in `<files>` at task start.
|
|
1581
|
+
Optimal: Read files when they are first referenced in the action.
|
|
1582
|
+
|
|
1583
|
+
This is a recommendation for planning — instruct the planner:
|
|
1584
|
+
"Add a note to `<context>` in plans: 'Read files lazily — read each file
|
|
1585
|
+
when first needed in the action, not all upfront.'"
|
|
1586
|
+
|
|
1587
|
+
**Known limitation:** This is advisory. The execution engine update to enforce
|
|
1588
|
+
lazy loading is planned for v1.1.0.
|
|
1589
|
+
|
|
1590
|
+
### Strategy 3 — Selective STATE.md section loading
|
|
1591
|
+
|
|
1592
|
+
STATE.md grows over time as projects accumulate phase summaries and decisions.
|
|
1593
|
+
Loading the full file at session start can cost 3,000–8,000 tokens.
|
|
1594
|
+
|
|
1595
|
+
Optimised loading approach (add to CLAUDE.md session start):
|
|
1596
|
+
```bash
|
|
1597
|
+
# Extract only the sections needed at session start
|
|
1598
|
+
# Full STATE.md: only loaded when the agent needs project history
|
|
1599
|
+
|
|
1600
|
+
# Minimal load — always (200-500 tokens):
|
|
1601
|
+
CURRENT_STATUS_SECTION=$(awk '/^## Current status/,/^## /' .planning/STATE.md | head -30)
|
|
1602
|
+
NEXT_ACTION_SECTION=$(awk '/^## Next action/,/^## /' .planning/STATE.md | head -10)
|
|
1603
|
+
|
|
1604
|
+
# Full load — only when agent explicitly needs history:
|
|
1605
|
+
# (e.g., plan-phase, retrospective, complete-milestone)
|
|
1606
|
+
```
|
|
1607
|
+
|
|
1608
|
+
### Strategy 4 — Code context precision
|
|
1609
|
+
|
|
1610
|
+
For tasks that modify specific functions: provide only the relevant code section.
|
|
1611
|
+
|
|
1612
|
+
```xml
|
|
1613
|
+
<!-- ❌ Reads entire 450-line file for a 10-line change -->
|
|
1614
|
+
<files>src/services/user.service.ts</files>
|
|
1615
|
+
|
|
1616
|
+
<!-- ✅ Agent reads only the relevant section -->
|
|
1617
|
+
<files>src/services/user.service.ts:40-65</files>
|
|
1618
|
+
<!-- (line range specification — a Day 7 planner feature) -->
|
|
1619
|
+
```
|
|
1620
|
+
|
|
1621
|
+
**Note:** Line-range file reading is a planned v1.1.0 feature.
|
|
1622
|
+
For now: add to `<context>`: "Focus on lines 40-65 of user.service.ts."
|
|
1623
|
+
|
|
1624
|
+
### Strategy 5 — Skill summarisation at 4+ skills
|
|
1625
|
+
|
|
1626
|
+
Already implemented in loader.md. This strategy verifies it is working:
|
|
1627
|
+
|
|
1628
|
+
Check skill-usage.jsonl for `tokens_est` field.
|
|
1629
|
+
If any session shows 4+ skills loaded at full injection: loader protocol needs review.
|
|
1630
|
+
Target: never more than 3 skills at full injection per task.
|
|
1631
|
+
|
|
1632
|
+
## Token budget tracking
|
|
1633
|
+
|
|
1634
|
+
Written to `.mindforge/metrics/token-usage.jsonl` after each task:
|
|
1635
|
+
|
|
1636
|
+
```json
|
|
1637
|
+
{
|
|
1638
|
+
"timestamp": "ISO-8601",
|
|
1639
|
+
"session_id": "sess_abc123",
|
|
1640
|
+
"phase": 3,
|
|
1641
|
+
"plan": "02",
|
|
1642
|
+
"task_name": "Implement auth middleware",
|
|
1643
|
+
"token_estimates": {
|
|
1644
|
+
"context_startup": 6200,
|
|
1645
|
+
"skills": { "count": 2, "total": 8400, "summarised": 0 },
|
|
1646
|
+
"plan_file": 620,
|
|
1647
|
+
"code_reading": 12400,
|
|
1648
|
+
"agent_response": 3800,
|
|
1649
|
+
"verify": 720,
|
|
1650
|
+
"total": 32140
|
|
1651
|
+
},
|
|
1652
|
+
"efficiency": 0.34,
|
|
1653
|
+
"flags": []
|
|
1654
|
+
}
|
|
1655
|
+
```
|
|
1656
|
+
|
|
1657
|
+
## MINDFORGE.md token settings
|
|
1658
|
+
|
|
1659
|
+
```
|
|
1660
|
+
# Add to MINDFORGE.md to configure token behaviour:
|
|
1661
|
+
|
|
1662
|
+
# Warn when single task token estimate exceeds this
|
|
1663
|
+
TOKEN_WARN_THRESHOLD=40000
|
|
1664
|
+
|
|
1665
|
+
# Lean mode: agent produces shorter responses where possible
|
|
1666
|
+
TOKEN_LEAN_MODE=false
|
|
1667
|
+
|
|
1668
|
+
# Maximum lines to read per file when no line range specified
|
|
1669
|
+
TOKEN_MAX_FILE_LINES=300
|
|
1670
|
+
```
|
|
1671
|
+
```
|
|
1672
|
+
|
|
1673
|
+
**Commit:**
|
|
1674
|
+
```bash
|
|
1675
|
+
git add .mindforge/production/token-optimiser.md
|
|
1676
|
+
git commit -m "feat(tokens): implement token usage optimiser with profiling, strategies, and JSONL tracking"
|
|
1677
|
+
```
|
|
1678
|
+
|
|
1679
|
+
---
|
|
1680
|
+
|
|
1681
|
+
## TASK 7 — Write the 50-point Production Readiness Checklist
|
|
1682
|
+
|
|
1683
|
+
### `.mindforge/production/production-checklist.md`
|
|
1684
|
+
|
|
1685
|
+
```markdown
|
|
1686
|
+
# MindForge v1.0.0 — Production Readiness Checklist
|
|
1687
|
+
|
|
1688
|
+
## Policy: ALL 50 items must be ✅ before tagging v1.0.0
|
|
1689
|
+
|
|
1690
|
+
This is not a "should" list — it is a hard gate.
|
|
1691
|
+
No item can be marked ✅ without:
|
|
1692
|
+
1. The specific verification step executed successfully
|
|
1693
|
+
2. The verifier's git email recorded
|
|
1694
|
+
3. The date verified recorded
|
|
1695
|
+
|
|
1696
|
+
Document completed checks in `.planning/RELEASE-CHECKLIST.md`.
|
|
1697
|
+
|
|
1698
|
+
---
|
|
1699
|
+
|
|
1700
|
+
## SECTION A — Installation & Upgrade (10 points)
|
|
1701
|
+
|
|
1702
|
+
| # | Check | Verification step | ✅/❌ | Verified by | Date |
|
|
1703
|
+
|---|---|---|---|---|---|
|
|
1704
|
+
| A01 | `npx mindforge-cc@latest` launches interactive wizard on macOS | Run on macOS terminal with TTY | | | |
|
|
1705
|
+
| A02 | `npx mindforge-cc@latest` launches wizard on Linux | Run on Ubuntu 22.04 LTS terminal | | | |
|
|
1706
|
+
| A03 | `npx mindforge-cc --claude --local` installs correctly | Verify files, run /mindforge:health | | | |
|
|
1707
|
+
| A04 | `npx mindforge-cc --all --global` installs both runtimes | Check ~/.claude and ~/.gemini/antigravity | | | |
|
|
1708
|
+
| A05 | `--update` updates and preserves install scope | Install locally, run --update, verify scope preserved | | | |
|
|
1709
|
+
| A06 | `--uninstall` removes only MindForge files (not .planning/) | Run uninstall, verify .planning/ intact | | | |
|
|
1710
|
+
| A07 | `--version` and `--help` exit 0 with correct output | Run both flags, check exit code | | | |
|
|
1711
|
+
| A08 | Install over existing CLAUDE.md backs it up | Create custom CLAUDE.md, install, verify backup | | | |
|
|
1712
|
+
| A09 | Install over existing .planning/ preserves it | Create test .planning/, install, verify preserved | | | |
|
|
1713
|
+
| A10 | Node.js < 18 prints helpful error and exits 1 | Run with node 16, verify error message | | | |
|
|
1714
|
+
|
|
1715
|
+
## SECTION B — Command Coverage (10 points)
|
|
1716
|
+
|
|
1717
|
+
| # | Check | Verification step | ✅/❌ | Verified by | Date |
|
|
1718
|
+
|---|---|---|---|---|---|
|
|
1719
|
+
| B01 | `/mindforge:help` shows all 36 commands | Count commands in output, verify ≥ 36 | | | |
|
|
1720
|
+
| B02 | `/mindforge:init-project` creates all 5 required .planning/ files | Run in empty dir, check file list | | | |
|
|
1721
|
+
| B03 | `/mindforge:health` reports ✅ on a fresh install | Fresh install, run health, zero errors | | | |
|
|
1722
|
+
| B04 | `/mindforge:health --repair` fixes CLAUDE.md drift | Corrupt .agent/CLAUDE.md, run repair, verify fix | | | |
|
|
1723
|
+
| B05 | `/mindforge:skills list` shows all 10 core skills | Count in output, verify all 10 names | | | |
|
|
1724
|
+
| B06 | `/mindforge:skills validate` shows all valid | Run, verify zero errors | | | |
|
|
1725
|
+
| B07 | `/mindforge:security-scan --secrets` detects fake key | Add test key pattern, scan, verify detection | | | |
|
|
1726
|
+
| B08 | `/mindforge:audit --summary` shows metrics dashboard | Run in project with some audit entries | | | |
|
|
1727
|
+
| B09 | `/mindforge:update` checks version without changing files | Run without --apply, verify no file changes | | | |
|
|
1728
|
+
| B10 | `/mindforge:migrate --dry-run` shows plan without changes | Run dry-run on v0.6.0 schema, verify dry-run | | | |
|
|
1729
|
+
|
|
1730
|
+
## SECTION C — Governance Gates (10 points)
|
|
1731
|
+
|
|
1732
|
+
| # | Check | Verification step | ✅/❌ | Verified by | Date |
|
|
1733
|
+
|---|---|---|---|---|---|
|
|
1734
|
+
| C01 | Gate 3 (secret detection) blocks commit with fake API key | Stage file with `FAKE_ghp_abcdef1234567890abcd`, run gate | | | |
|
|
1735
|
+
| C02 | Gate 2 (tests passing) blocks on test failure | Break a test, run gate, verify block | | | |
|
|
1736
|
+
| C03 | Gate 1 (CRITICAL findings) blocks PR creation | Add CRITICAL finding to SECURITY-REVIEW, attempt ship | | | |
|
|
1737
|
+
| C04 | AUDIT.jsonl gains entry for every task start and complete | Execute one quick task, count new AUDIT lines | | | |
|
|
1738
|
+
| C05 | Tier 3 change classifier detects jwt.sign in non-auth path | Create file with jwt.sign, run classifier | | | |
|
|
1739
|
+
| C06 | Tier 3 CI block produces clear error message | Simulate CI run with Tier 3 pending, check output | | | |
|
|
1740
|
+
| C07 | MINDFORGE.md non-overridable keys are silently enforced | Set SECRET_DETECTION=false, verify it has no effect | | | |
|
|
1741
|
+
| C08 | Approval workflow creates APPROVAL-[uuid].json | Request a Tier 2 approval, verify file created | | | |
|
|
1742
|
+
| C09 | Plugin injection guard blocks malicious SKILL.md | Create SKILL.md with IGNORE ALL PREVIOUS, run guard | | | |
|
|
1743
|
+
| C10 | All 5 compliance gates produce GATE-RESULTS-N.md | Complete a full phase, verify GATE-RESULTS file | | | |
|
|
1744
|
+
|
|
1745
|
+
## SECTION D — Documentation (10 points)
|
|
1746
|
+
|
|
1747
|
+
| # | Check | Verification step | ✅/❌ | Verified by | Date |
|
|
1748
|
+
|---|---|---|---|---|---|
|
|
1749
|
+
| D01 | README.md quick start works end-to-end in < 5 minutes | New developer follows README, measures time | | | |
|
|
1750
|
+
| D02 | `docs/reference/commands.md` documents all 36 commands | Count documented commands, verify ≥ 36 | | | |
|
|
1751
|
+
| D03 | `docs/enterprise-setup.md` covers all integration steps | Walk through integration setup docs | | | |
|
|
1752
|
+
| D04 | `docs/governance-guide.md` explains all 3 tiers clearly | Review with someone unfamiliar with governance | | | |
|
|
1753
|
+
| D05 | `docs/security/threat-model.md` covers all 7 threat actors | Count actors, verify controls for each | | | |
|
|
1754
|
+
| D06 | `docs/security/SECURITY.md` has disclosure process | Verify email + 90-day policy present | | | |
|
|
1755
|
+
| D07 | `docs/contributing/CONTRIBUTING.md` has PR guidelines | Check for: fork, branch, test, PR process | | | |
|
|
1756
|
+
| D08 | `docs/contributing/skill-authoring.md` is actionable | Follow guide to create a test skill | | | |
|
|
1757
|
+
| D09 | All 20 ADRs listed in decision-records-index.md | Count ADRs, cross-check index | | | |
|
|
1758
|
+
| D10 | CHANGELOG.md v1.0.0 entry is complete with date | Verify entry has date, all components listed | | | |
|
|
1759
|
+
|
|
1760
|
+
## SECTION E — Test Coverage (10 points)
|
|
1761
|
+
|
|
1762
|
+
| # | Check | Verification step | ✅/❌ | Verified by | Date |
|
|
1763
|
+
|---|---|---|---|---|---|
|
|
1764
|
+
| E01 | All 15 test suites pass with 0 failures (Run 1) | Execute full test battery | | | |
|
|
1765
|
+
| E02 | All 15 test suites pass with 0 failures (Run 2) | Execute full test battery again | | | |
|
|
1766
|
+
| E03 | All 15 test suites pass with 0 failures (Run 3) | Execute full test battery third time | | | |
|
|
1767
|
+
| E04 | No test uses `process.exit(0)` inside a test function | `grep -rn 'process.exit(0)' tests/` | | | |
|
|
1768
|
+
| E05 | E2E tests simulate complete brownfield onboarding | Run tests/e2e.test.js, verify brownfield path | | | |
|
|
1769
|
+
| E06 | Migration tests cover v0.1.0 → v1.0.0 full chain | Run tests/migration.test.js full suite | | | |
|
|
1770
|
+
| E07 | No test has `// TODO` or `// skip` in test body | `grep -rn 'TODO\|skip' tests/` | | | |
|
|
1771
|
+
| E08 | Security penetration test pass (all 7 threat actors) | Run through threat-model.md controls manually | | | |
|
|
1772
|
+
| E09 | CI pipeline runs all tests on PR against main | Create test PR, verify CI passes | | | |
|
|
1773
|
+
| E10 | Version in package.json matches git tag matches npm dist | `node -e "console.log(require('./package.json').version)"` | | | |
|
|
1774
|
+
|
|
1775
|
+
---
|
|
1776
|
+
|
|
1777
|
+
## Release gate procedure
|
|
1778
|
+
|
|
1779
|
+
When ALL 50 items show ✅:
|
|
1780
|
+
|
|
1781
|
+
```bash
|
|
1782
|
+
# 1. Final security scan
|
|
1783
|
+
/mindforge:security-scan --deep --secrets --deps
|
|
1784
|
+
|
|
1785
|
+
# 2. Triple-run test battery
|
|
1786
|
+
node -e "
|
|
1787
|
+
const { execSync } = require('child_process');
|
|
1788
|
+
const suites = ['install','wave-engine','audit','compaction','skills-platform',
|
|
1789
|
+
'integrations','governance','intelligence','metrics',
|
|
1790
|
+
'distribution','ci-mode','sdk','production','migration','e2e'];
|
|
1791
|
+
let allPass = true;
|
|
1792
|
+
for (let run = 1; run <= 3; run++) {
|
|
1793
|
+
for (const suite of suites) {
|
|
1794
|
+
try {
|
|
1795
|
+
execSync('node tests/' + suite + '.test.js', { stdio: 'pipe' });
|
|
1796
|
+
} catch {
|
|
1797
|
+
console.error('FAILED: ' + suite + ' run ' + run);
|
|
1798
|
+
allPass = false;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
console.log(allPass ? 'ALL TESTS PASSED × 3' : 'FAILURES DETECTED');
|
|
1803
|
+
process.exit(allPass ? 0 : 1);
|
|
1804
|
+
"
|
|
1805
|
+
|
|
1806
|
+
# 3. Tag and publish (only after the above pass)
|
|
1807
|
+
git tag -a v1.0.0 -m "MindForge v1.0.0 — First stable public release"
|
|
1808
|
+
git push origin v1.0.0
|
|
1809
|
+
npm publish --access public
|
|
1810
|
+
```
|
|
1811
|
+
```
|
|
1812
|
+
|
|
1813
|
+
**Commit:**
|
|
1814
|
+
```bash
|
|
1815
|
+
git add .mindforge/production/
|
|
1816
|
+
git commit -m "feat(production): 50-point release checklist, token optimiser, migration spec"
|
|
1817
|
+
```
|
|
1818
|
+
|
|
1819
|
+
---
|
|
1820
|
+
|
|
1821
|
+
## TASK 8 — Write all 5 new commands
|
|
1822
|
+
|
|
1823
|
+
### `.claude/commands/mindforge/update.md`
|
|
1824
|
+
|
|
1825
|
+
```markdown
|
|
1826
|
+
# MindForge — Update Command
|
|
1827
|
+
# Usage: /mindforge:update [--apply] [--force] [--check] [--skip-changelog]
|
|
1828
|
+
|
|
1829
|
+
## Purpose
|
|
1830
|
+
Check for and apply MindForge framework updates in a safe, scope-preserving way.
|
|
1831
|
+
|
|
1832
|
+
## Execution flow
|
|
1833
|
+
|
|
1834
|
+
### 1. Version check
|
|
1835
|
+
Execute `bin/updater/self-update.js` `checkAndUpdate()`.
|
|
1836
|
+
Always show: current version, latest version, update type (major/minor/patch).
|
|
1837
|
+
|
|
1838
|
+
### 2. Changelog display (unless --skip-changelog)
|
|
1839
|
+
Fetch and display the relevant CHANGELOG.md section.
|
|
1840
|
+
For major updates: prefix with ⚠️ BREAKING CHANGES notice.
|
|
1841
|
+
Limit display to 3,000 characters — link to full CHANGELOG for the rest.
|
|
1842
|
+
|
|
1843
|
+
### 3. Confirmation gate (unless --apply)
|
|
1844
|
+
Without --apply: show update info, stop. Do not modify any files.
|
|
1845
|
+
Message: "To apply this update: /mindforge:update --apply"
|
|
1846
|
+
|
|
1847
|
+
### 4. Apply (with --apply)
|
|
1848
|
+
a. Detect original install scope (local vs global, claude vs antigravity)
|
|
1849
|
+
b. Read schema_version from HANDOFF.json (captures current version BEFORE update)
|
|
1850
|
+
c. Run `npx mindforge-cc@[latest] --[runtime] --[scope]`
|
|
1851
|
+
d. Run migration: `node bin/migrations/migrate.js` with captured schema_version
|
|
1852
|
+
e. Run /mindforge:health to verify update succeeded
|
|
1853
|
+
|
|
1854
|
+
### 5. Post-update health check
|
|
1855
|
+
If health errors: surface them immediately with specific fix instructions.
|
|
1856
|
+
Common post-update issue: CLAUDE.md and .agent/CLAUDE.md drifted → auto-repair.
|
|
1857
|
+
|
|
1858
|
+
## Error scenarios and recovery
|
|
1859
|
+
|
|
1860
|
+
| Error | Recovery |
|
|
1861
|
+
|---|---|
|
|
1862
|
+
| npm registry unreachable | Message: "Check internet. Manual: npm info mindforge-cc version" |
|
|
1863
|
+
| Update download fails | Retry once, then suggest manual: `npx mindforge-cc@latest` |
|
|
1864
|
+
| Migration fails | Restored from backup automatically. Run /mindforge:migrate manually. |
|
|
1865
|
+
| Install scope detection fails | Prompt user: "Is your install global or local?" |
|
|
1866
|
+
|
|
1867
|
+
## AUDIT entry
|
|
1868
|
+
```json
|
|
1869
|
+
{
|
|
1870
|
+
"event": "framework_updated",
|
|
1871
|
+
"from_version": "0.6.0",
|
|
1872
|
+
"to_version": "1.0.0",
|
|
1873
|
+
"update_type": "major",
|
|
1874
|
+
"scope": "local",
|
|
1875
|
+
"runtime": "claude",
|
|
1876
|
+
"migration_ran": true,
|
|
1877
|
+
"health_check_result": "healthy"
|
|
1878
|
+
}
|
|
1879
|
+
```
|
|
1880
|
+
```
|
|
1881
|
+
|
|
1882
|
+
### `.claude/commands/mindforge/migrate.md`
|
|
1883
|
+
|
|
1884
|
+
```markdown
|
|
1885
|
+
# MindForge — Migrate Command
|
|
1886
|
+
# Usage: /mindforge:migrate [--from X.Y.Z] [--to X.Y.Z] [--dry-run] [--force]
|
|
1887
|
+
|
|
1888
|
+
## Purpose
|
|
1889
|
+
Run explicit schema migrations for .planning/ files.
|
|
1890
|
+
Normally triggered automatically by /mindforge:update.
|
|
1891
|
+
Use this command manually when: auto-migration failed, manual version jump, recovery.
|
|
1892
|
+
|
|
1893
|
+
## Flow
|
|
1894
|
+
|
|
1895
|
+
### Auto-detect migration need
|
|
1896
|
+
Read `schema_version` from HANDOFF.json.
|
|
1897
|
+
Compare against current `package.json` version.
|
|
1898
|
+
Determine migration path.
|
|
1899
|
+
|
|
1900
|
+
### Dry-run mode (--dry-run)
|
|
1901
|
+
Show: which migrations would run, what each changes.
|
|
1902
|
+
Show: breaking changes for the migration path.
|
|
1903
|
+
Make NO changes to any file.
|
|
1904
|
+
|
|
1905
|
+
### Backup first
|
|
1906
|
+
Before any changes: create `.planning/migration-backup-[timestamp]/`
|
|
1907
|
+
Verify backup integrity (file count, non-empty).
|
|
1908
|
+
If backup fails: ABORT. Explain disk space issue.
|
|
1909
|
+
|
|
1910
|
+
### Execute migrations
|
|
1911
|
+
Run `node bin/migrations/migrate.js`.
|
|
1912
|
+
Show progress for each migration.
|
|
1913
|
+
If any migration fails: auto-restore from backup.
|
|
1914
|
+
|
|
1915
|
+
### Verify
|
|
1916
|
+
Run /mindforge:health after migration.
|
|
1917
|
+
If health errors: report with specific fix instructions.
|
|
1918
|
+
Preserve backup until user is satisfied — they must delete it manually.
|
|
1919
|
+
|
|
1920
|
+
## Manual version override
|
|
1921
|
+
`/mindforge:migrate --from 0.1.0 --to 1.0.0` — forces migration between specified versions.
|
|
1922
|
+
Use with care: intended for recovery scenarios where HANDOFF.json schema_version is wrong.
|
|
1923
|
+
|
|
1924
|
+
## AUDIT entry
|
|
1925
|
+
```json
|
|
1926
|
+
{
|
|
1927
|
+
"event": "schema_migrated",
|
|
1928
|
+
"from_version": "0.6.0",
|
|
1929
|
+
"to_version": "1.0.0",
|
|
1930
|
+
"migrations_run": 1,
|
|
1931
|
+
"backup_path": ".planning/migration-backup-1234567890",
|
|
1932
|
+
"result": "success"
|
|
1933
|
+
}
|
|
1934
|
+
```
|
|
1935
|
+
```
|
|
1936
|
+
|
|
1937
|
+
### `.claude/commands/mindforge/plugins.md`
|
|
1938
|
+
|
|
1939
|
+
```markdown
|
|
1940
|
+
# MindForge — Plugins Command
|
|
1941
|
+
# Usage: /mindforge:plugins [list|install|uninstall|info|validate|create] [name]
|
|
1942
|
+
|
|
1943
|
+
## list
|
|
1944
|
+
Read PLUGINS-MANIFEST.md. Display installed plugins with version and permissions.
|
|
1945
|
+
If no plugins: "No plugins installed. Find plugins: npm search mindforge-plugin"
|
|
1946
|
+
|
|
1947
|
+
## install [plugin-name]
|
|
1948
|
+
Full installation protocol per plugin-loader.md:
|
|
1949
|
+
1. Resolve package: `mindforge-plugin-[name]` convention
|
|
1950
|
+
2. Download to chmod 700 temp directory
|
|
1951
|
+
3. Validate plugin.json manifest
|
|
1952
|
+
4. Check plugin_api_version compatibility (1.0.0 required)
|
|
1953
|
+
5. Run injection guard on ALL .md files in the plugin
|
|
1954
|
+
6. Run Level 1 + 2 skill validation on all SKILL.md files
|
|
1955
|
+
7. Display permission list for user approval:
|
|
1956
|
+
```
|
|
1957
|
+
Plugin: mindforge-plugin-jira-advanced v1.0.0
|
|
1958
|
+
Requests these permissions:
|
|
1959
|
+
• read_audit_log: read AUDIT.jsonl ✅ (safe)
|
|
1960
|
+
• write_audit_log: append to AUDIT.jsonl ⚠️
|
|
1961
|
+
• network_access: make HTTP requests ⚠️
|
|
1962
|
+
Install? (yes/no)
|
|
1963
|
+
```
|
|
1964
|
+
8. Install components (commands, skills, personas, hooks)
|
|
1965
|
+
9. Add to PLUGINS-MANIFEST.md
|
|
1966
|
+
10. Write AUDIT entry
|
|
1967
|
+
|
|
1968
|
+
## uninstall [plugin-name]
|
|
1969
|
+
Remove all installed components. Update PLUGINS-MANIFEST.md.
|
|
1970
|
+
Confirm: "This will remove [N] commands, [N] skills from this plugin."
|
|
1971
|
+
|
|
1972
|
+
## info [plugin-name]
|
|
1973
|
+
Display: version, description, author, permissions, commands, skills, personas, hooks.
|
|
1974
|
+
|
|
1975
|
+
## validate
|
|
1976
|
+
Validate all installed plugins for compatibility, injection safety, permission scope.
|
|
1977
|
+
|
|
1978
|
+
## create [plugin-name]
|
|
1979
|
+
Generate a plugin scaffold:
|
|
1980
|
+
```
|
|
1981
|
+
mindforge-plugin-[name]/
|
|
1982
|
+
├── plugin.json (pre-filled template)
|
|
1983
|
+
├── commands/ (empty, ready for .md files)
|
|
1984
|
+
├── skills/ (empty, ready for skill directories)
|
|
1985
|
+
├── hooks/ (empty, ready for hook .md files)
|
|
1986
|
+
└── README.md (template)
|
|
1987
|
+
```
|
|
1988
|
+
See docs/contributing/plugin-authoring.md for authoring guidance.
|
|
1989
|
+
```
|
|
1990
|
+
|
|
1991
|
+
### `.claude/commands/mindforge/tokens.md`
|
|
1992
|
+
|
|
1993
|
+
```markdown
|
|
1994
|
+
# MindForge — Token Usage Command
|
|
1995
|
+
# Usage: /mindforge:tokens [--phase N] [--session ID] [--window short|medium|long] [--optimise]
|
|
1996
|
+
|
|
1997
|
+
## Purpose
|
|
1998
|
+
Display token consumption profile and efficiency analysis.
|
|
1999
|
+
Helps identify where tokens are being spent and how to reduce waste.
|
|
2000
|
+
|
|
2001
|
+
## Default output (no flags — last 5 sessions)
|
|
2002
|
+
|
|
2003
|
+
```
|
|
2004
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
2005
|
+
⚡ Token Usage Profile — [Project Name]
|
|
2006
|
+
Last 5 sessions
|
|
2007
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
2008
|
+
|
|
2009
|
+
Session Estimated Code Read Skills Efficiency
|
|
2010
|
+
────────── ───────── ───────── ────── ──────────
|
|
2011
|
+
sess-01 31,200 17,800 6,400 40% ✅
|
|
2012
|
+
sess-02 62,400 44,100 6,200 22% ⚠️
|
|
2013
|
+
sess-03 28,900 15,400 5,800 43% ✅
|
|
2014
|
+
sess-04 74,800 55,600 7,100 17% 🔴
|
|
2015
|
+
sess-05 33,100 18,600 7,400 39% ✅
|
|
2016
|
+
|
|
2017
|
+
Average 46,080 30,300 6,580 32%
|
|
2018
|
+
Target >35%
|
|
2019
|
+
|
|
2020
|
+
Efficiency flags:
|
|
2021
|
+
🔴 sess-04: code_reading extremely high (55,600) — plan scope too broad
|
|
2022
|
+
⚠️ sess-02: code_reading high (44,100) — consider narrower <files> list
|
|
2023
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
2024
|
+
Run /mindforge:tokens --optimise for specific recommendations.
|
|
2025
|
+
```
|
|
2026
|
+
|
|
2027
|
+
## Data source
|
|
2028
|
+
Read `.mindforge/metrics/token-usage.jsonl`.
|
|
2029
|
+
If file doesn't exist: "No token usage data yet. Data collected after tasks complete."
|
|
2030
|
+
|
|
2031
|
+
## --optimise flag
|
|
2032
|
+
Analyse recent sessions for patterns. Report concrete improvements:
|
|
2033
|
+
- If code_reading consistently > 25,000: "Plans are reading too broadly. Add file line-ranges to `<context>` fields."
|
|
2034
|
+
- If skills > 4 per session: "4+ skills loading frequently. Consider adding specific triggers to reduce auto-loading."
|
|
2035
|
+
- If plan_file > 1,000 tokens: "PLAN `<action>` fields are verbose. Use lean format (see token-optimiser.md Strategy 1)."
|
|
2036
|
+
- Suggest MINDFORGE.md settings that would reduce token use.
|
|
2037
|
+
|
|
2038
|
+
## AUDIT entry
|
|
2039
|
+
```json
|
|
2040
|
+
{ "event": "tokens_viewed", "window": "short", "avg_efficiency": 0.32 }
|
|
2041
|
+
```
|
|
2042
|
+
```
|
|
2043
|
+
|
|
2044
|
+
### `.claude/commands/mindforge/release.md`
|
|
2045
|
+
|
|
2046
|
+
```markdown
|
|
2047
|
+
# MindForge — Release Command
|
|
2048
|
+
# Usage: /mindforge:release [--version X.Y.Z] [--dry-run] [--checklist-only]
|
|
2049
|
+
# ⚠️ This command is for releasing the MindForge framework itself.
|
|
2050
|
+
# For releasing your project phases, use /mindforge:ship instead.
|
|
2051
|
+
|
|
2052
|
+
## Purpose
|
|
2053
|
+
Execute the complete MindForge v1.0.0 (or any version) release pipeline.
|
|
2054
|
+
Intended for the MindForge core team.
|
|
2055
|
+
|
|
2056
|
+
## Gate: Production Readiness Checklist
|
|
2057
|
+
|
|
2058
|
+
```bash
|
|
2059
|
+
# Verify all 50 checklist items are ✅
|
|
2060
|
+
CHECKLIST=".planning/RELEASE-CHECKLIST.md"
|
|
2061
|
+
[ -f "${CHECKLIST}" ] || { echo "❌ Release checklist not found"; exit 1; }
|
|
2062
|
+
|
|
2063
|
+
UNCHECKED=$(grep -c '| ❌\| \| |' "${CHECKLIST}" || true)
|
|
2064
|
+
if [ "${UNCHECKED}" -gt 0 ]; then
|
|
2065
|
+
echo "❌ ${UNCHECKED} checklist item(s) are not verified."
|
|
2066
|
+
echo " Complete the checklist before releasing."
|
|
2067
|
+
exit 1
|
|
2068
|
+
fi
|
|
2069
|
+
echo "✅ All 50 checklist items verified"
|
|
2070
|
+
```
|
|
2071
|
+
|
|
2072
|
+
## Release pipeline (8 stages)
|
|
2073
|
+
|
|
2074
|
+
### Stage 1 — Final test battery (15 suites × 3 runs)
|
|
2075
|
+
```bash
|
|
2076
|
+
SUITES=(install wave-engine audit compaction skills-platform
|
|
2077
|
+
integrations governance intelligence metrics
|
|
2078
|
+
distribution ci-mode sdk production migration e2e)
|
|
2079
|
+
|
|
2080
|
+
for RUN in 1 2 3; do
|
|
2081
|
+
echo "=== Run ${RUN}/3 ==="
|
|
2082
|
+
for SUITE in "${SUITES[@]}"; do
|
|
2083
|
+
node tests/${SUITE}.test.js || { echo "FAILED: ${SUITE}"; exit 1; }
|
|
2084
|
+
done
|
|
2085
|
+
done
|
|
2086
|
+
echo "✅ All 15 suites × 3 runs passed"
|
|
2087
|
+
```
|
|
2088
|
+
|
|
2089
|
+
### Stage 2 — Security final scan
|
|
2090
|
+
```bash
|
|
2091
|
+
/mindforge:security-scan --deep --secrets --deps
|
|
2092
|
+
# Must show: CLEAN — 0 CRITICAL, 0 HIGH findings
|
|
2093
|
+
# Must show: 0 secrets detected
|
|
2094
|
+
# Must show: 0 vulnerable dependencies (HIGH or CRITICAL)
|
|
2095
|
+
```
|
|
2096
|
+
|
|
2097
|
+
### Stage 3 — Version verification
|
|
2098
|
+
```bash
|
|
2099
|
+
PACKAGE_VER=$(node -e "console.log(require('./package.json').version)")
|
|
2100
|
+
[ "${PACKAGE_VER}" = "${TARGET_VERSION}" ] || { echo "❌ Version mismatch"; exit 1; }
|
|
2101
|
+
grep -q "${TARGET_VERSION}" CHANGELOG.md || { echo "❌ No CHANGELOG entry"; exit 1; }
|
|
2102
|
+
echo "✅ Version: ${PACKAGE_VER}"
|
|
2103
|
+
```
|
|
2104
|
+
|
|
2105
|
+
### Stage 4 — SDK build
|
|
2106
|
+
```bash
|
|
2107
|
+
cd sdk && npm run build
|
|
2108
|
+
[ -f "dist/index.js" ] || { echo "❌ SDK build failed"; exit 1; }
|
|
2109
|
+
[ -f "dist/index.d.ts" ] || { echo "❌ SDK types missing"; exit 1; }
|
|
2110
|
+
echo "✅ SDK built"
|
|
2111
|
+
cd ..
|
|
2112
|
+
```
|
|
2113
|
+
|
|
2114
|
+
### Stage 5 — Release commit and tag
|
|
2115
|
+
```bash
|
|
2116
|
+
git add package.json CHANGELOG.md sdk/dist/ .planning/RELEASE-CHECKLIST.md
|
|
2117
|
+
git commit -m "chore(release): v${TARGET_VERSION} — enterprise agentic framework stable release"
|
|
2118
|
+
|
|
2119
|
+
git tag -a "v${TARGET_VERSION}" -m "MindForge v${TARGET_VERSION}
|
|
2120
|
+
|
|
2121
|
+
Enterprise Agentic Framework — First Stable Public Release
|
|
2122
|
+
|
|
2123
|
+
Commands: 36 | Skills: 10 | Personas: 8 | ADRs: 20 | Test suites: 15
|
|
2124
|
+
|
|
2125
|
+
See CHANGELOG.md for full release notes.
|
|
2126
|
+
Install: npx mindforge-cc@latest"
|
|
2127
|
+
```
|
|
2128
|
+
|
|
2129
|
+
### Stage 6 — npm publish
|
|
2130
|
+
```bash
|
|
2131
|
+
# Dry run first — verify what will be published
|
|
2132
|
+
npm publish --dry-run --access public 2>&1 | head -50
|
|
2133
|
+
|
|
2134
|
+
# Actual publish
|
|
2135
|
+
npm publish --access public
|
|
2136
|
+
|
|
2137
|
+
# Verify publication
|
|
2138
|
+
sleep 10
|
|
2139
|
+
PUBLISHED=$(npm info mindforge-cc@${TARGET_VERSION} version 2>/dev/null)
|
|
2140
|
+
[ "${PUBLISHED}" = "${TARGET_VERSION}" ] || { echo "❌ Publish verification failed"; exit 1; }
|
|
2141
|
+
echo "✅ Published: mindforge-cc@${TARGET_VERSION}"
|
|
2142
|
+
```
|
|
2143
|
+
|
|
2144
|
+
### Stage 7 — GitHub release
|
|
2145
|
+
```bash
|
|
2146
|
+
gh release create "v${TARGET_VERSION}" \
|
|
2147
|
+
--title "MindForge v${TARGET_VERSION} — Enterprise Agentic Framework" \
|
|
2148
|
+
--notes-file CHANGELOG.md \
|
|
2149
|
+
--verify-tag
|
|
2150
|
+
```
|
|
2151
|
+
|
|
2152
|
+
### Stage 8 — Post-release verification
|
|
2153
|
+
```bash
|
|
2154
|
+
# Test fresh install from published package
|
|
2155
|
+
TEMP_DIR=$(mktemp -d) && cd "${TEMP_DIR}"
|
|
2156
|
+
npx mindforge-cc@${TARGET_VERSION} --version
|
|
2157
|
+
[ "$?" = "0" ] || { echo "❌ Published package fails --version"; exit 1; }
|
|
2158
|
+
echo "✅ Published package verified"
|
|
2159
|
+
cd - && rm -rf "${TEMP_DIR}"
|
|
2160
|
+
```
|
|
2161
|
+
|
|
2162
|
+
## --dry-run flag
|
|
2163
|
+
Runs all stages except Stage 6 (npm publish) and Stage 7 (GitHub release).
|
|
2164
|
+
Shows what would be published without publishing.
|
|
2165
|
+
|
|
2166
|
+
## AUDIT entry (written after successful release)
|
|
2167
|
+
```json
|
|
2168
|
+
{
|
|
2169
|
+
"event": "framework_released",
|
|
2170
|
+
"version": "1.0.0",
|
|
2171
|
+
"checklist_verified": true,
|
|
2172
|
+
"test_runs": 3,
|
|
2173
|
+
"security_scan": "clean",
|
|
2174
|
+
"npm_url": "https://www.npmjs.com/package/mindforge-cc/v/1.0.0",
|
|
2175
|
+
"github_release": "https://github.com/mindforge-dev/mindforge/releases/tag/v1.0.0"
|
|
2176
|
+
}
|
|
2177
|
+
```
|
|
2178
|
+
```
|
|
2179
|
+
|
|
2180
|
+
**Commit:**
|
|
2181
|
+
```bash
|
|
2182
|
+
for cmd in update migrate plugins tokens release; do
|
|
2183
|
+
cp .claude/commands/mindforge/${cmd}.md .agent/mindforge/${cmd}.md
|
|
2184
|
+
done
|
|
2185
|
+
git add .claude/commands/mindforge/ .agent/mindforge/
|
|
2186
|
+
git commit -m "feat(commands): add update, migrate, plugins, tokens, release — all 36 commands complete"
|
|
2187
|
+
```
|
|
2188
|
+
|
|
2189
|
+
---
|
|
2190
|
+
|
|
2191
|
+
## TASK 9 — Write the complete reference documentation
|
|
2192
|
+
|
|
2193
|
+
### `docs/reference/commands.md` (excerpt — complete the full 36-command table)
|
|
2194
|
+
|
|
2195
|
+
```markdown
|
|
2196
|
+
# MindForge v1.0.0 — Complete Commands Reference
|
|
2197
|
+
|
|
2198
|
+
## All 36 commands
|
|
2199
|
+
|
|
2200
|
+
### Lifecycle commands (core workflow)
|
|
2201
|
+
| Command | Usage | Description | Added |
|
|
2202
|
+
|---|---|---|---|
|
|
2203
|
+
| `/mindforge:init-project` | `init-project` | Guided project setup — creates all .planning/ files | Day 1 |
|
|
2204
|
+
| `/mindforge:discuss-phase` | `discuss-phase [N] [--batch\|--auto]` | Pre-planning interview to capture implementation decisions | Day 3 |
|
|
2205
|
+
| `/mindforge:plan-phase` | `plan-phase [N]` | Research, decompose, and create atomic task plans | Day 1 |
|
|
2206
|
+
| `/mindforge:execute-phase` | `execute-phase [N]` | Wave-based parallel execution of all phase plans | Day 1+2 |
|
|
2207
|
+
| `/mindforge:verify-phase` | `verify-phase [N]` | Automated + human acceptance testing pipeline | Day 1 |
|
|
2208
|
+
| `/mindforge:ship` | `ship [N]` | Create PR, write release notes, push to remote | Day 1 |
|
|
2209
|
+
| `/mindforge:next` | `next` | Auto-detect and execute the correct next workflow step | Day 2 |
|
|
2210
|
+
|
|
2211
|
+
[... 29 more commands documented in same format ...]
|
|
2212
|
+
|
|
2213
|
+
## Command interface contract (v1.0.0 stable)
|
|
2214
|
+
|
|
2215
|
+
As of v1.0.0, the following are part of the stable interface:
|
|
2216
|
+
- All 36 command names (new commands require MINOR bump)
|
|
2217
|
+
- All flags documented here (new flags require MINOR, removed flags require MAJOR)
|
|
2218
|
+
- HANDOFF.json and AUDIT.jsonl schemas (additions: MINOR, removals: MAJOR)
|
|
2219
|
+
- All 10 core skill `name:` values and trigger lists
|
|
2220
|
+
- SDK exported types and functions
|
|
2221
|
+
|
|
2222
|
+
See ADR-020 for the complete stability contract.
|
|
2223
|
+
```
|
|
2224
|
+
|
|
2225
|
+
### `docs/security/SECURITY.md`
|
|
2226
|
+
|
|
2227
|
+
```markdown
|
|
2228
|
+
# MindForge — Security Policy
|
|
2229
|
+
|
|
2230
|
+
## Supported versions
|
|
2231
|
+
|
|
2232
|
+
| Version | Security support |
|
|
2233
|
+
|---|---|
|
|
2234
|
+
| 1.x.x | ✅ Active — patches released for all severity levels |
|
|
2235
|
+
| 0.6.x | ⚠️ Limited — critical fixes only, 90 days from v1.0.0 release |
|
|
2236
|
+
| < 0.6.0 | ❌ No support |
|
|
2237
|
+
|
|
2238
|
+
## Reporting a vulnerability
|
|
2239
|
+
|
|
2240
|
+
**Email:** security@mindforge.dev
|
|
2241
|
+
|
|
2242
|
+
**Required information:**
|
|
2243
|
+
- Description of the vulnerability
|
|
2244
|
+
- Steps to reproduce
|
|
2245
|
+
- Potential impact assessment
|
|
2246
|
+
- Your name / handle (for acknowledgement, if desired)
|
|
2247
|
+
|
|
2248
|
+
**Response timeline:**
|
|
2249
|
+
- Acknowledgement: within 24 hours
|
|
2250
|
+
- Initial assessment: within 7 days
|
|
2251
|
+
- Fix released: within 30 days for HIGH/CRITICAL, 90 days for MEDIUM/LOW
|
|
2252
|
+
- Coordinated disclosure: 90 days from initial report
|
|
2253
|
+
|
|
2254
|
+
**We commit to:**
|
|
2255
|
+
- Not taking legal action against good-faith security researchers
|
|
2256
|
+
- Crediting researchers in the security advisory (with their permission)
|
|
2257
|
+
- Maintaining confidentiality until a fix is released
|
|
2258
|
+
|
|
2259
|
+
## Known security model limitations
|
|
2260
|
+
|
|
2261
|
+
See `docs/security/threat-model.md` for the full threat model.
|
|
2262
|
+
|
|
2263
|
+
Key acknowledged limitations:
|
|
2264
|
+
1. Plugin permission model is advisory (not OS-enforced) — see TA7 in threat model
|
|
2265
|
+
2. The SSE event stream is localhost-only but any local process can connect — see TA6
|
|
2266
|
+
3. Approver identity uses `git config user.email` which is user-controlled — see TA5
|
|
2267
|
+
4. Agent instruction injection via SKILL.md requires review beyond pattern matching — see TA1
|
|
2268
|
+
|
|
2269
|
+
These are known trade-offs, not bugs. They are documented in ADR-020.
|
|
2270
|
+
```
|
|
2271
|
+
|
|
2272
|
+
### `docs/security/threat-model.md`
|
|
2273
|
+
|
|
2274
|
+
```markdown
|
|
2275
|
+
# MindForge v1.0.0 — Threat Model
|
|
2276
|
+
|
|
2277
|
+
## Scope
|
|
2278
|
+
All attack surfaces introduced by MindForge across 7 days of development.
|
|
2279
|
+
Last reviewed: v1.0.0 release (March 2026).
|
|
2280
|
+
|
|
2281
|
+
## Assets being protected
|
|
2282
|
+
|
|
2283
|
+
| Asset | Classification | Location |
|
|
2284
|
+
|---|---|---|
|
|
2285
|
+
| API credentials | CRITICAL | Environment variables only (never in files) |
|
|
2286
|
+
| HANDOFF.json | HIGH — project state, agent notes, decisions | `.planning/HANDOFF.json` |
|
|
2287
|
+
| AUDIT.jsonl | HIGH — complete governance audit trail | `.planning/AUDIT.jsonl` |
|
|
2288
|
+
| Approval files | HIGH — governance records | `.planning/approvals/*.json` |
|
|
2289
|
+
| SECURITY.md | MEDIUM — security policy documentation | `.mindforge/org/SECURITY.md` |
|
|
2290
|
+
| CLAUDE.md | MEDIUM — agent instructions that shape behaviour | `.claude/CLAUDE.md` |
|
|
2291
|
+
| CONVENTIONS.md | LOW — coding standards | `.mindforge/org/CONVENTIONS.md` |
|
|
2292
|
+
|
|
2293
|
+
## Threat Actor 1 — Malicious skill package author
|
|
2294
|
+
|
|
2295
|
+
**Goal:** Inject adversarial instructions via a published `mindforge-skill-*` npm package.
|
|
2296
|
+
**Attack:** SKILL.md contains "IGNORE ALL PREVIOUS INSTRUCTIONS" or similar.
|
|
2297
|
+
**Controls:**
|
|
2298
|
+
- Injection guard in `loader.md` blocks known patterns at both install and load time
|
|
2299
|
+
- Level 1/2/3 skill validation at install time
|
|
2300
|
+
- TOCTOU-safe download (chmod 700 temp dir, tarball size check)
|
|
2301
|
+
- User must explicitly run `/mindforge:install-skill` — no auto-install
|
|
2302
|
+
|
|
2303
|
+
**Residual risk:** MEDIUM — sophisticated injections that avoid simple string matching.
|
|
2304
|
+
**Mitigation:** Community review of public registry skills; organisation vetting of org-tier skills.
|
|
2305
|
+
|
|
2306
|
+
---
|
|
2307
|
+
|
|
2308
|
+
## Threat Actor 2 — MINDFORGE.md governance bypass
|
|
2309
|
+
|
|
2310
|
+
**Goal:** Disable governance primitives via MINDFORGE.md settings.
|
|
2311
|
+
**Attack:** Set `SECRET_DETECTION=false`, `SECURITY_AUTOTRIGGER=false`.
|
|
2312
|
+
**Controls:**
|
|
2313
|
+
- Non-overridable rules enforced in CLAUDE.md session start protocol
|
|
2314
|
+
- MINDFORGE-SCHEMA.json marks these fields as `nonOverridable: true`
|
|
2315
|
+
- `bin/validate-config.js` warns on attempts to override these fields
|
|
2316
|
+
|
|
2317
|
+
**Residual risk:** LOW — enforced at the agent instruction layer, not OS level.
|
|
2318
|
+
**Note:** An agent that ignores its CLAUDE.md is an agent that ignores everything.
|
|
2319
|
+
|
|
2320
|
+
---
|
|
2321
|
+
|
|
2322
|
+
## Threat Actor 3 — Accidental credential exposure in project files
|
|
2323
|
+
|
|
2324
|
+
**Goal:** Not adversarial — developer accidentally commits a credential.
|
|
2325
|
+
**Attack vectors:**
|
|
2326
|
+
- Token pasted into HANDOFF.json
|
|
2327
|
+
- API key in MINDFORGE.md ADDITIONAL_AGENT_INSTRUCTIONS
|
|
2328
|
+
- Secret in AUDIT.jsonl via an error message
|
|
2329
|
+
|
|
2330
|
+
**Controls:**
|
|
2331
|
+
- Gate 3 (secret detection) blocks ANY commit with credential patterns
|
|
2332
|
+
- `_warning` field in every HANDOFF.json schema reminding devs not to store secrets
|
|
2333
|
+
- Health engine (Category 7) scans .planning/ and root files for credential patterns
|
|
2334
|
+
- installer-core.js skips .env and *.key files during copyDir
|
|
2335
|
+
|
|
2336
|
+
**Residual risk:** LOW — multiple detection layers with complementary coverage.
|
|
2337
|
+
|
|
2338
|
+
---
|
|
2339
|
+
|
|
2340
|
+
## Threat Actor 4 — TOCTOU attack on skill installation
|
|
2341
|
+
|
|
2342
|
+
**Goal:** Replace a valid SKILL.md with malicious content in the window between download and validation.
|
|
2343
|
+
**Attack:** Race condition in temp directory.
|
|
2344
|
+
**Controls:**
|
|
2345
|
+
- `chmod 700` on temp directory (user-only access, blocks other OS users)
|
|
2346
|
+
- Tarball size check (detects empty/corrupted downloads)
|
|
2347
|
+
- Download → validate → install is a single-process, single-threaded operation
|
|
2348
|
+
|
|
2349
|
+
**Residual risk:** VERY LOW — requires local machine compromise and precise timing.
|
|
2350
|
+
|
|
2351
|
+
---
|
|
2352
|
+
|
|
2353
|
+
## Threat Actor 5 — Compromised CI environment
|
|
2354
|
+
|
|
2355
|
+
**Goal:** Bypass governance gates in CI to ship malicious code.
|
|
2356
|
+
**Attack:** Modify GitHub Actions workflow or CI runner environment to skip MindForge checks.
|
|
2357
|
+
**Controls:**
|
|
2358
|
+
- Gates run as separate CI jobs with explicit dependencies
|
|
2359
|
+
- Tier 3 changes always fail CI (cannot be configured away)
|
|
2360
|
+
- AUDIT.jsonl writes all gate results — tampering would require audit log manipulation
|
|
2361
|
+
- Branch protection rules on the repository (outside MindForge scope)
|
|
2362
|
+
|
|
2363
|
+
**Residual risk:** HIGH — an attacker with write access to the workflow file or CI secrets
|
|
2364
|
+
can bypass. This is a threat to all CI systems, not MindForge specifically.
|
|
2365
|
+
**Mitigation:** Protect the `main` branch with required status checks.
|
|
2366
|
+
|
|
2367
|
+
---
|
|
2368
|
+
|
|
2369
|
+
## Threat Actor 6 — SSE event stream eavesdropping
|
|
2370
|
+
|
|
2371
|
+
**Goal:** Read sensitive project state from the real-time event stream.
|
|
2372
|
+
**Attack:** Connect to port 7337 from another local process.
|
|
2373
|
+
**Controls:**
|
|
2374
|
+
- localhost-only binding (127.0.0.1) — not accessible from network
|
|
2375
|
+
- IP address check on every connection — non-localhost rejected with 403
|
|
2376
|
+
- CORS exact-origin matching (not wildcard)
|
|
2377
|
+
- Port only opens when the SDK's `MindForgeEventStream.start()` is explicitly called
|
|
2378
|
+
|
|
2379
|
+
**Residual risk:** LOW — any process running as the same OS user can connect to localhost.
|
|
2380
|
+
**Mitigation:** The SSE stream exposes AUDIT entries, not credentials. Risk is information disclosure, not code execution.
|
|
2381
|
+
|
|
2382
|
+
---
|
|
2383
|
+
|
|
2384
|
+
## Threat Actor 7 — Plugin with elevated or undeclared permissions
|
|
2385
|
+
|
|
2386
|
+
**Goal:** Use a MindForge plugin to exfiltrate project state or modify governance.
|
|
2387
|
+
**Attack:** Install a plugin that reads HANDOFF.json and sends it to an external server.
|
|
2388
|
+
**Controls:**
|
|
2389
|
+
- Permission model displayed to user at install time (requires explicit approval)
|
|
2390
|
+
- Injection guard run against all plugin .md files
|
|
2391
|
+
- All plugin-triggered actions logged with plugin name as agent in AUDIT.jsonl
|
|
2392
|
+
- `ELEVATED_PLUGINS` allowlist required for `write_state: true` permission
|
|
2393
|
+
|
|
2394
|
+
**Residual risk:** MEDIUM — a user who installs a malicious plugin and approves its permissions.
|
|
2395
|
+
**Mitigation:** Only install plugins from sources you trust. Review plugin commands before installing.
|
|
2396
|
+
Treat MindForge plugins like VSCode extensions — they have significant project access.
|
|
2397
|
+
|
|
2398
|
+
---
|
|
2399
|
+
|
|
2400
|
+
## Controls summary matrix
|
|
2401
|
+
|
|
2402
|
+
| Control | Threat Actors Mitigated |
|
|
2403
|
+
|---|---|
|
|
2404
|
+
| Injection guard (loader.md) | TA1, TA7 |
|
|
2405
|
+
| TOCTOU-safe download (chmod 700) | TA1, TA4 |
|
|
2406
|
+
| Non-overridable governance primitives | TA2 |
|
|
2407
|
+
| Gate 3 secret detection | TA3 |
|
|
2408
|
+
| Health engine credential scan | TA3 |
|
|
2409
|
+
| CI Tier 3 block | TA5 |
|
|
2410
|
+
| SSE localhost-only binding | TA6 |
|
|
2411
|
+
| Plugin permission model + AUDIT logging | TA7 |
|
|
2412
|
+
|
|
2413
|
+
## Penetration test results
|
|
2414
|
+
|
|
2415
|
+
See `docs/security/penetration-test-results.md` for the adversarial review
|
|
2416
|
+
conducted as part of the v1.0.0 production readiness process.
|
|
2417
|
+
```
|
|
2418
|
+
|
|
2419
|
+
### `docs/architecture/decision-records-index.md`
|
|
2420
|
+
|
|
2421
|
+
```markdown
|
|
2422
|
+
# MindForge — Architecture Decision Records Index
|
|
2423
|
+
|
|
2424
|
+
All 20 ADRs in chronological order.
|
|
2425
|
+
|
|
2426
|
+
| ADR | Title | Status | Day | Key decision |
|
|
2427
|
+
|---|---|---|---|---|
|
|
2428
|
+
| ADR-001 | HANDOFF.json for cross-session state | Accepted | Day 2 | HANDOFF.json as the primary cross-session state artifact |
|
|
2429
|
+
| ADR-002 | Markdown-based commands | Accepted | Day 1 | Slash commands as Markdown files (not code) |
|
|
2430
|
+
| ADR-003 | Keyword-trigger model for skill loading | Accepted | Day 1 | Deterministic keyword matching over AI-decided selection |
|
|
2431
|
+
| ADR-004 | Wave parallelism over full parallelism | Accepted | Day 2 | Wave-based (dependency-ordered) over unconstrained parallel |
|
|
2432
|
+
| ADR-005 | Append-only JSONL for audit log | Accepted | Day 2 | AUDIT.jsonl append-only (never update, never delete) |
|
|
2433
|
+
| ADR-006 | Three-tier skills architecture | Accepted | Day 3 | Core → Org → Project tier hierarchy |
|
|
2434
|
+
| ADR-007 | Keyword-trigger model (reaffirmed at Day 3 scale) | Accepted | Day 3 | Confirmed at 10+ skill scale |
|
|
2435
|
+
| ADR-008 | Just-in-time skill loading | Accepted | Day 3 | Load at task time, not session start |
|
|
2436
|
+
| ADR-009 | Environment-variable-only credential storage | Accepted | Day 4 | Credentials only in env vars, never in config files |
|
|
2437
|
+
| ADR-010 | Compliance gates non-bypassable; approvals allow emergency | Accepted | Day 4 | Gates: never bypass. Approvals: emergency override with audit |
|
|
2438
|
+
| ADR-011 | Integration failures are non-blocking | Accepted | Day 4 | Jira/Slack/GitHub down ≠ phase blocked |
|
|
2439
|
+
| ADR-012 | Intelligence outputs feed back into system behaviour | Accepted | Day 5 | Difficulty → granularity, retro → MINDFORGE.md, quality → behaviour |
|
|
2440
|
+
| ADR-013 | MINDFORGE.md as constitution with non-overridable primitives | Accepted | Day 5 | Non-overridable governance primitives cannot be disabled |
|
|
2441
|
+
| ADR-014 | Metrics are system signals, not developer performance | Accepted | Day 5 | Quality scores improve the system, not evaluate individuals |
|
|
2442
|
+
| ADR-015 | npm as the public skills registry | Accepted | Day 6 | npm ecosystem for skill distribution |
|
|
2443
|
+
| ADR-016 | CI timeout exits with code 0 (soft stop) | Accepted | Day 6 | Timeout = save and resume, not failure |
|
|
2444
|
+
| ADR-017 | SDK event stream is localhost-only | Accepted | Day 6 | SSE binds to 127.0.0.1 only |
|
|
2445
|
+
| ADR-018 | Installer detects and handles self-install | Accepted | Day 7 | Installer running inside its own repo = no-op for framework files |
|
|
2446
|
+
| ADR-019 | Self-update preserves original install scope | Accepted | Day 7 | Update local→local, global→global |
|
|
2447
|
+
| ADR-020 | v1.0.0 stable interface contract | Accepted | Day 7 | Defines what "stable" means for plugins and SDK consumers |
|
|
2448
|
+
```
|
|
2449
|
+
|
|
2450
|
+
**Commit:**
|
|
2451
|
+
```bash
|
|
2452
|
+
git add docs/
|
|
2453
|
+
git commit -m "docs: complete reference documentation, threat model, ADR index, security policy for v1.0.0"
|
|
2454
|
+
```
|
|
2455
|
+
|
|
2456
|
+
---
|
|
2457
|
+
|
|
2458
|
+
## TASK 10 — Write Day 7 test suites
|
|
2459
|
+
|
|
2460
|
+
### `tests/production.test.js`
|
|
2461
|
+
|
|
2462
|
+
```javascript
|
|
2463
|
+
/**
|
|
2464
|
+
* MindForge Day 7 — Production Readiness Tests
|
|
2465
|
+
* Verifies the installer, updater, migration engine, plugin system,
|
|
2466
|
+
* token optimiser, and all 36 commands exist.
|
|
2467
|
+
*
|
|
2468
|
+
* Run: node tests/production.test.js
|
|
2469
|
+
*/
|
|
2470
|
+
'use strict';
|
|
2471
|
+
|
|
2472
|
+
const fs = require('fs');
|
|
2473
|
+
const path = require('path');
|
|
2474
|
+
const assert = require('assert');
|
|
2475
|
+
let passed = 0, failed = 0;
|
|
2476
|
+
|
|
2477
|
+
function test(name, fn) {
|
|
2478
|
+
try { fn(); console.log(` ✅ ${name}`); passed++; }
|
|
2479
|
+
catch(e) { console.error(` ❌ ${name}\n ${e.message}`); failed++; }
|
|
2480
|
+
}
|
|
2481
|
+
|
|
2482
|
+
const read = p => fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : '';
|
|
2483
|
+
const exists = p => fs.existsSync(p);
|
|
2484
|
+
|
|
2485
|
+
// ── Installer completeness ─────────────────────────────────────────────────────
|
|
2486
|
+
console.log('\nMindForge Day 7 — Production Readiness Tests\n');
|
|
2487
|
+
console.log('Installer:');
|
|
2488
|
+
|
|
2489
|
+
test('bin/install.js exists with shebang', () => {
|
|
2490
|
+
const c = read('bin/install.js');
|
|
2491
|
+
assert.ok(c.includes('#!/usr/bin/env node'), 'Missing shebang');
|
|
2492
|
+
assert.ok(c.length > 500, 'install.js seems too short');
|
|
2493
|
+
});
|
|
2494
|
+
|
|
2495
|
+
test('bin/installer-core.js exists and exports run()', () => {
|
|
2496
|
+
const c = read('bin/installer-core.js');
|
|
2497
|
+
assert.ok(c.includes('module.exports'), 'Missing module.exports');
|
|
2498
|
+
assert.ok(c.includes('async function run') || c.includes('function run'), 'Missing run function');
|
|
2499
|
+
});
|
|
2500
|
+
|
|
2501
|
+
test('installer handles --version flag correctly', () => {
|
|
2502
|
+
const c = read('bin/install.js');
|
|
2503
|
+
assert.ok(c.includes("'--version'") || c.includes('"--version"'), 'Missing --version');
|
|
2504
|
+
assert.ok(c.includes('process.exit(0)'), 'Should exit 0 for --version');
|
|
2505
|
+
});
|
|
2506
|
+
|
|
2507
|
+
test('installer has Node.js version gate (≥ 18)', () => {
|
|
2508
|
+
const combined = read('bin/install.js') + read('bin/installer-core.js');
|
|
2509
|
+
assert.ok(combined.includes('18'), 'Should check for Node.js 18');
|
|
2510
|
+
assert.ok(combined.includes('process.exit(1)') || combined.includes('exit(1)'), 'Should exit 1 for old node');
|
|
2511
|
+
});
|
|
2512
|
+
|
|
2513
|
+
test('installer has CI mode detection', () => {
|
|
2514
|
+
const c = read('bin/install.js');
|
|
2515
|
+
assert.ok(c.includes("process.env.CI"), 'Should detect CI environment');
|
|
2516
|
+
assert.ok(c.includes('IS_NON_INTERACTIVE'), 'Should have non-interactive flag');
|
|
2517
|
+
});
|
|
2518
|
+
|
|
2519
|
+
test('installer backs up existing CLAUDE.md', () => {
|
|
2520
|
+
const c = read('bin/installer-core.js');
|
|
2521
|
+
assert.ok(c.includes('backup') || c.includes('.backup-'), 'Should back up CLAUDE.md');
|
|
2522
|
+
});
|
|
2523
|
+
|
|
2524
|
+
test('installer has self-install detection', () => {
|
|
2525
|
+
const c = read('bin/installer-core.js');
|
|
2526
|
+
assert.ok(
|
|
2527
|
+
c.includes('isSelfInstall') || c.includes("'mindforge-cc'"),
|
|
2528
|
+
'Should detect self-install scenario'
|
|
2529
|
+
);
|
|
2530
|
+
});
|
|
2531
|
+
|
|
2532
|
+
test('installer excludes sensitive files (*.env, *.key, *.pem)', () => {
|
|
2533
|
+
const c = read('bin/installer-core.js');
|
|
2534
|
+
assert.ok(
|
|
2535
|
+
c.includes('.env') || c.includes('SENSITIVE_EXCLUDE') || c.includes('.key'),
|
|
2536
|
+
'Should have sensitive file exclusion list'
|
|
2537
|
+
);
|
|
2538
|
+
});
|
|
2539
|
+
|
|
2540
|
+
test('installer verifies install after completing', () => {
|
|
2541
|
+
const c = read('bin/installer-core.js');
|
|
2542
|
+
assert.ok(
|
|
2543
|
+
c.includes('verifyInstall') || c.includes('verification'),
|
|
2544
|
+
'Should verify install after completing'
|
|
2545
|
+
);
|
|
2546
|
+
});
|
|
2547
|
+
|
|
2548
|
+
// ── Self-update system ─────────────────────────────────────────────────────────
|
|
2549
|
+
console.log('\nSelf-update system:');
|
|
2550
|
+
|
|
2551
|
+
['version-comparator.js', 'changelog-fetcher.js', 'self-update.js'].forEach(f => {
|
|
2552
|
+
test(`bin/updater/${f} exists`, () => {
|
|
2553
|
+
assert.ok(exists(`bin/updater/${f}`), `Missing: bin/updater/${f}`);
|
|
2554
|
+
});
|
|
2555
|
+
});
|
|
2556
|
+
|
|
2557
|
+
test('compareSemver: 1.0.0 > 0.6.0', () => {
|
|
2558
|
+
const { compareSemver } = require('../bin/updater/version-comparator');
|
|
2559
|
+
assert.ok(compareSemver('1.0.0', '0.6.0') > 0);
|
|
2560
|
+
});
|
|
2561
|
+
|
|
2562
|
+
test('compareSemver: 0.6.0 < 1.0.0', () => {
|
|
2563
|
+
const { compareSemver } = require('../bin/updater/version-comparator');
|
|
2564
|
+
assert.ok(compareSemver('0.6.0', '1.0.0') < 0);
|
|
2565
|
+
});
|
|
2566
|
+
|
|
2567
|
+
test('compareSemver: 1.0.0 == 1.0.0', () => {
|
|
2568
|
+
const { compareSemver } = require('../bin/updater/version-comparator');
|
|
2569
|
+
assert.strictEqual(compareSemver('1.0.0', '1.0.0'), 0);
|
|
2570
|
+
});
|
|
2571
|
+
|
|
2572
|
+
test('compareSemver handles v prefix', () => {
|
|
2573
|
+
const { compareSemver } = require('../bin/updater/version-comparator');
|
|
2574
|
+
assert.ok(compareSemver('v1.0.0', 'v0.6.0') > 0);
|
|
2575
|
+
});
|
|
2576
|
+
|
|
2577
|
+
test('upgradeType: 0.6.0 → 1.0.0 is major', () => {
|
|
2578
|
+
const { upgradeType } = require('../bin/updater/version-comparator');
|
|
2579
|
+
assert.strictEqual(upgradeType('0.6.0', '1.0.0'), 'major');
|
|
2580
|
+
});
|
|
2581
|
+
|
|
2582
|
+
test('upgradeType: 1.0.0 → 1.1.0 is minor', () => {
|
|
2583
|
+
const { upgradeType } = require('../bin/updater/version-comparator');
|
|
2584
|
+
assert.strictEqual(upgradeType('1.0.0', '1.1.0'), 'minor');
|
|
2585
|
+
});
|
|
2586
|
+
|
|
2587
|
+
test('upgradeType: 1.0.0 → 1.0.1 is patch', () => {
|
|
2588
|
+
const { upgradeType } = require('../bin/updater/version-comparator');
|
|
2589
|
+
assert.strictEqual(upgradeType('1.0.0', '1.0.1'), 'patch');
|
|
2590
|
+
});
|
|
2591
|
+
|
|
2592
|
+
test('upgradeType: 1.0.0 → 1.0.0 is none', () => {
|
|
2593
|
+
const { upgradeType } = require('../bin/updater/version-comparator');
|
|
2594
|
+
assert.strictEqual(upgradeType('1.0.0', '1.0.0'), 'none');
|
|
2595
|
+
});
|
|
2596
|
+
|
|
2597
|
+
test('self-update has scope detection', () => {
|
|
2598
|
+
const c = read('bin/updater/self-update.js');
|
|
2599
|
+
assert.ok(c.includes('detectInstallScope'), 'Should have detectInstallScope()');
|
|
2600
|
+
});
|
|
2601
|
+
|
|
2602
|
+
test('self-update reads schema_version before applying update', () => {
|
|
2603
|
+
const c = read('bin/updater/self-update.js');
|
|
2604
|
+
assert.ok(
|
|
2605
|
+
c.includes('readHandoffSchemaVersion') || c.includes('schema_version'),
|
|
2606
|
+
'Should read schema_version from HANDOFF before updating'
|
|
2607
|
+
);
|
|
2608
|
+
});
|
|
2609
|
+
|
|
2610
|
+
// ── Migration engine ────────────────────────────────────────────────────────────
|
|
2611
|
+
console.log('\nMigration engine:');
|
|
2612
|
+
|
|
2613
|
+
['migrate.js', 'schema-versions.js', '0.1.0-to-0.5.0.js', '0.5.0-to-0.6.0.js', '0.6.0-to-1.0.0.js'].forEach(f => {
|
|
2614
|
+
test(`bin/migrations/${f} exists`, () => {
|
|
2615
|
+
assert.ok(exists(`bin/migrations/${f}`), `Missing: ${f}`);
|
|
2616
|
+
});
|
|
2617
|
+
});
|
|
2618
|
+
|
|
2619
|
+
test('migrate.js creates backup before migrating', () => {
|
|
2620
|
+
const c = read('bin/migrations/migrate.js');
|
|
2621
|
+
assert.ok(c.includes('backup') || c.includes('Backup'), 'Should create backup');
|
|
2622
|
+
});
|
|
2623
|
+
|
|
2624
|
+
test('migrate.js aborts if backup fails', () => {
|
|
2625
|
+
const c = read('bin/migrations/migrate.js');
|
|
2626
|
+
assert.ok(
|
|
2627
|
+
c.includes('backupErr') || c.includes('Migration aborted'),
|
|
2628
|
+
'Should abort if backup creation fails'
|
|
2629
|
+
);
|
|
2630
|
+
});
|
|
2631
|
+
|
|
2632
|
+
test('migrate.js restores from backup on migration failure', () => {
|
|
2633
|
+
const c = read('bin/migrations/migrate.js');
|
|
2634
|
+
assert.ok(
|
|
2635
|
+
c.includes('Restoring') || c.includes('restoreFromBackup') || c.includes('restore'),
|
|
2636
|
+
'Should restore from backup on failure'
|
|
2637
|
+
);
|
|
2638
|
+
});
|
|
2639
|
+
|
|
2640
|
+
test('0.6.0-to-1.0.0 migration adds plugin_api_version', () => {
|
|
2641
|
+
const c = read('bin/migrations/0.6.0-to-1.0.0.js');
|
|
2642
|
+
assert.ok(c.includes('plugin_api_version'), 'Should add plugin_api_version field');
|
|
2643
|
+
});
|
|
2644
|
+
|
|
2645
|
+
test('0.6.0-to-1.0.0 migration backfills session_id in AUDIT.jsonl', () => {
|
|
2646
|
+
const c = read('bin/migrations/0.6.0-to-1.0.0.js');
|
|
2647
|
+
assert.ok(c.includes('session_id'), 'Should backfill session_id');
|
|
2648
|
+
});
|
|
2649
|
+
|
|
2650
|
+
test('0.6.0-to-1.0.0 migration converts VERIFY_PASS_RATE_WARNING_THRESHOLD', () => {
|
|
2651
|
+
const c = read('bin/migrations/0.6.0-to-1.0.0.js');
|
|
2652
|
+
assert.ok(
|
|
2653
|
+
c.includes('VERIFY_PASS_RATE') || c.includes('val / 100'),
|
|
2654
|
+
'Should convert percentage to decimal'
|
|
2655
|
+
);
|
|
2656
|
+
});
|
|
2657
|
+
|
|
2658
|
+
test('migration preserves invalid AUDIT.jsonl lines (no crash)', () => {
|
|
2659
|
+
const c = read('bin/migrations/0.6.0-to-1.0.0.js');
|
|
2660
|
+
assert.ok(c.includes('catch') || c.includes('try'), 'Should handle parse errors gracefully');
|
|
2661
|
+
});
|
|
2662
|
+
|
|
2663
|
+
// ── Plugin system ────────────────────────────────────────────────────────────────
|
|
2664
|
+
console.log('\nPlugin system:');
|
|
2665
|
+
|
|
2666
|
+
['plugin-schema.md', 'plugin-loader.md', 'PLUGINS-MANIFEST.md'].forEach(f => {
|
|
2667
|
+
test(`.mindforge/plugins/${f} exists`, () => {
|
|
2668
|
+
assert.ok(exists(`.mindforge/plugins/${f}`));
|
|
2669
|
+
});
|
|
2670
|
+
});
|
|
2671
|
+
|
|
2672
|
+
test('plugin schema defines permission model', () => {
|
|
2673
|
+
const c = read('.mindforge/plugins/plugin-schema.md');
|
|
2674
|
+
assert.ok(c.includes('permissions'), 'Should define permissions');
|
|
2675
|
+
assert.ok(c.includes('write_state'), 'Should include write_state permission');
|
|
2676
|
+
assert.ok(c.includes('network_access'), 'Should include network_access permission');
|
|
2677
|
+
});
|
|
2678
|
+
|
|
2679
|
+
test('plugin loader has injection guard step', () => {
|
|
2680
|
+
const c = read('.mindforge/plugins/plugin-loader.md');
|
|
2681
|
+
assert.ok(c.includes('injection guard') || c.includes('Injection'), 'Should run injection guard');
|
|
2682
|
+
});
|
|
2683
|
+
|
|
2684
|
+
test('plugin loader documents advisory permission model', () => {
|
|
2685
|
+
const c = read('.mindforge/plugins/plugin-loader.md');
|
|
2686
|
+
assert.ok(
|
|
2687
|
+
c.includes('advisory') || c.includes('not OS-enforced') || c.includes('not enforced'),
|
|
2688
|
+
'Should explain that permissions are advisory'
|
|
2689
|
+
);
|
|
2690
|
+
});
|
|
2691
|
+
|
|
2692
|
+
test('plugin schema lists all 36 reserved command names', () => {
|
|
2693
|
+
const c = read('.mindforge/plugins/plugin-schema.md');
|
|
2694
|
+
assert.ok(
|
|
2695
|
+
c.includes('Reserved command names') || c.includes('reserved'),
|
|
2696
|
+
'Should list reserved command names'
|
|
2697
|
+
);
|
|
2698
|
+
// Check a few specific reserved names are mentioned
|
|
2699
|
+
assert.ok(c.includes('health'), 'Should list health as reserved');
|
|
2700
|
+
assert.ok(c.includes('security-scan'), 'Should list security-scan as reserved');
|
|
2701
|
+
});
|
|
2702
|
+
|
|
2703
|
+
// ── Token optimiser ─────────────────────────────────────────────────────────────
|
|
2704
|
+
console.log('\nToken optimiser:');
|
|
2705
|
+
|
|
2706
|
+
test('token-optimiser.md exists', () => {
|
|
2707
|
+
assert.ok(exists('.mindforge/production/token-optimiser.md'));
|
|
2708
|
+
});
|
|
2709
|
+
|
|
2710
|
+
test('token optimiser defines efficiency formula', () => {
|
|
2711
|
+
const c = read('.mindforge/production/token-optimiser.md');
|
|
2712
|
+
assert.ok(c.includes('token_efficiency') || c.includes('efficiency'), 'Should define efficiency');
|
|
2713
|
+
assert.ok(c.includes('useful_output') || c.includes('output_tokens'), 'Should define useful output');
|
|
2714
|
+
});
|
|
2715
|
+
|
|
2716
|
+
test('token optimiser has lean plan strategy', () => {
|
|
2717
|
+
const c = read('.mindforge/production/token-optimiser.md');
|
|
2718
|
+
assert.ok(c.includes('Strategy 1') || c.includes('Lean'), 'Should have lean plan strategy');
|
|
2719
|
+
});
|
|
2720
|
+
|
|
2721
|
+
// ── Production checklist ────────────────────────────────────────────────────────
|
|
2722
|
+
console.log('\nProduction checklist:');
|
|
2723
|
+
|
|
2724
|
+
test('production-checklist.md has exactly 50 checkbox items', () => {
|
|
2725
|
+
const c = read('.mindforge/production/production-checklist.md');
|
|
2726
|
+
const boxes = (c.match(/- \[ \]/g) || []).length;
|
|
2727
|
+
assert.ok(boxes >= 50, `Expected >= 50 items, found ${boxes}`);
|
|
2728
|
+
});
|
|
2729
|
+
|
|
2730
|
+
// ── Documentation completeness ──────────────────────────────────────────────────
|
|
2731
|
+
console.log('\nDocumentation:');
|
|
2732
|
+
|
|
2733
|
+
const DOC_FILES = [
|
|
2734
|
+
'docs/reference/commands.md',
|
|
2735
|
+
'docs/security/SECURITY.md',
|
|
2736
|
+
'docs/security/threat-model.md',
|
|
2737
|
+
'docs/architecture/decision-records-index.md',
|
|
2738
|
+
'docs/contributing/CONTRIBUTING.md',
|
|
2739
|
+
];
|
|
2740
|
+
DOC_FILES.forEach(f => test(`${f} exists`, () => assert.ok(exists(f), `Missing: ${f}`)));
|
|
2741
|
+
|
|
2742
|
+
test('threat model covers all 7 threat actors', () => {
|
|
2743
|
+
const c = read('docs/security/threat-model.md');
|
|
2744
|
+
for (let i = 1; i <= 7; i++) {
|
|
2745
|
+
assert.ok(c.includes(`Threat Actor ${i}`), `Missing Threat Actor ${i}`);
|
|
2746
|
+
}
|
|
2747
|
+
});
|
|
2748
|
+
|
|
2749
|
+
test('ADR index lists all 20 ADRs', () => {
|
|
2750
|
+
const c = read('docs/architecture/decision-records-index.md');
|
|
2751
|
+
for (let i = 1; i <= 20; i++) {
|
|
2752
|
+
const adrRef = `ADR-${String(i).padStart(3, '0')}`;
|
|
2753
|
+
assert.ok(c.includes(adrRef) || c.includes(`ADR-${i}`), `Missing ${adrRef} in index`);
|
|
2754
|
+
}
|
|
2755
|
+
});
|
|
2756
|
+
|
|
2757
|
+
test('SECURITY.md has responsible disclosure policy', () => {
|
|
2758
|
+
const c = read('docs/security/SECURITY.md');
|
|
2759
|
+
assert.ok(c.includes('disclosure') || c.includes('24 hours'), 'Should have disclosure timeline');
|
|
2760
|
+
});
|
|
2761
|
+
|
|
2762
|
+
// ── All 36 commands ─────────────────────────────────────────────────────────────
|
|
2763
|
+
console.log('\nAll 36 commands:');
|
|
2764
|
+
|
|
2765
|
+
const ALL_COMMANDS = [
|
|
2766
|
+
// Day 1
|
|
2767
|
+
'help', 'init-project', 'plan-phase', 'execute-phase', 'verify-phase', 'ship',
|
|
2768
|
+
// Day 2
|
|
2769
|
+
'next', 'quick', 'status', 'debug',
|
|
2770
|
+
// Day 3
|
|
2771
|
+
'skills', 'review', 'security-scan', 'map-codebase', 'discuss-phase',
|
|
2772
|
+
// Day 4
|
|
2773
|
+
'audit', 'milestone', 'complete-milestone', 'approve', 'sync-jira', 'sync-confluence',
|
|
2774
|
+
// Day 5
|
|
2775
|
+
'health', 'retrospective', 'profile-team', 'metrics',
|
|
2776
|
+
// Day 6
|
|
2777
|
+
'init-org', 'install-skill', 'publish-skill', 'pr-review', 'workspace', 'benchmark',
|
|
2778
|
+
// Day 7
|
|
2779
|
+
'update', 'migrate', 'plugins', 'tokens', 'release',
|
|
2780
|
+
];
|
|
2781
|
+
|
|
2782
|
+
assert.strictEqual(ALL_COMMANDS.length, 36, `Expected 36 commands, have ${ALL_COMMANDS.length}`);
|
|
2783
|
+
console.log(` (verifying all ${ALL_COMMANDS.length} commands)`);
|
|
2784
|
+
|
|
2785
|
+
test('all 36 commands in .claude/commands/mindforge/', () => {
|
|
2786
|
+
const missing = ALL_COMMANDS.filter(cmd => !exists(`.claude/commands/mindforge/${cmd}.md`));
|
|
2787
|
+
assert.strictEqual(missing.length, 0, `Missing: ${missing.join(', ')}`);
|
|
2788
|
+
});
|
|
2789
|
+
|
|
2790
|
+
test('all 36 commands mirrored to .agent/mindforge/', () => {
|
|
2791
|
+
const missing = ALL_COMMANDS.filter(cmd => !exists(`.agent/mindforge/${cmd}.md`));
|
|
2792
|
+
assert.strictEqual(missing.length, 0, `Missing agent mirror: ${missing.join(', ')}`);
|
|
2793
|
+
});
|
|
2794
|
+
|
|
2795
|
+
test('no command file is empty (> 100 chars)', () => {
|
|
2796
|
+
const tiny = ALL_COMMANDS.filter(cmd => {
|
|
2797
|
+
const p = `.claude/commands/mindforge/${cmd}.md`;
|
|
2798
|
+
return exists(p) && fs.statSync(p).size < 100;
|
|
2799
|
+
});
|
|
2800
|
+
assert.strictEqual(tiny.length, 0, `Too small: ${tiny.join(', ')}`);
|
|
2801
|
+
});
|
|
2802
|
+
|
|
2803
|
+
// ── Final version check ────────────────────────────────────────────────────────
|
|
2804
|
+
console.log('\nVersion:');
|
|
2805
|
+
|
|
2806
|
+
test('package.json version is 1.0.0', () => {
|
|
2807
|
+
const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
2808
|
+
assert.strictEqual(pkg.version, '1.0.0', `Expected 1.0.0, got ${pkg.version}`);
|
|
2809
|
+
});
|
|
2810
|
+
|
|
2811
|
+
test('CHANGELOG.md has v1.0.0 entry', () => {
|
|
2812
|
+
const c = read('CHANGELOG.md');
|
|
2813
|
+
assert.ok(c.includes('1.0.0'), 'CHANGELOG.md should have 1.0.0 entry');
|
|
2814
|
+
});
|
|
2815
|
+
|
|
2816
|
+
test('all 20 ADR files present in .planning/decisions/', () => {
|
|
2817
|
+
if (!exists('.planning/decisions/')) return; // Skip if no decisions dir yet
|
|
2818
|
+
const adrs = fs.readdirSync('.planning/decisions/').filter(f => f.startsWith('ADR-') && f.endsWith('.md'));
|
|
2819
|
+
assert.ok(adrs.length >= 20, `Expected >= 20 ADRs, found ${adrs.length}`);
|
|
2820
|
+
});
|
|
2821
|
+
|
|
2822
|
+
// ── Results ─────────────────────────────────────────────────────────────────────
|
|
2823
|
+
console.log(`\n${'─'.repeat(55)}`);
|
|
2824
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
2825
|
+
if (failed > 0) {
|
|
2826
|
+
console.error(`\n❌ ${failed} test(s) failed — not production ready.\n`);
|
|
2827
|
+
process.exit(1);
|
|
2828
|
+
} else {
|
|
2829
|
+
console.log(`\n✅ All production readiness tests passed.\n`);
|
|
2830
|
+
}
|
|
2831
|
+
```
|
|
2832
|
+
|
|
2833
|
+
### `tests/migration.test.js`
|
|
2834
|
+
|
|
2835
|
+
```javascript
|
|
2836
|
+
/**
|
|
2837
|
+
* MindForge Day 7 — Migration Engine Tests
|
|
2838
|
+
* Tests the migration logic without touching real .planning/ files.
|
|
2839
|
+
*
|
|
2840
|
+
* Run: node tests/migration.test.js
|
|
2841
|
+
*/
|
|
2842
|
+
'use strict';
|
|
2843
|
+
|
|
2844
|
+
const fs = require('fs');
|
|
2845
|
+
const path = require('path');
|
|
2846
|
+
const os = require('os');
|
|
2847
|
+
const assert = require('assert');
|
|
2848
|
+
let passed = 0, failed = 0;
|
|
2849
|
+
|
|
2850
|
+
function test(name, fn) {
|
|
2851
|
+
try { fn(); console.log(` ✅ ${name}`); passed++; }
|
|
2852
|
+
catch(e) { console.error(` ❌ ${name}\n ${e.message}`); failed++; }
|
|
2853
|
+
}
|
|
2854
|
+
|
|
2855
|
+
// ── Simulation helpers ─────────────────────────────────────────────────────────
|
|
2856
|
+
|
|
2857
|
+
function simulateHandoffMigration(handoff, toVersion) {
|
|
2858
|
+
const result = JSON.parse(JSON.stringify(handoff));
|
|
2859
|
+
if (toVersion === '0.5.0' || toVersion === '1.0.0') {
|
|
2860
|
+
if (!Array.isArray(result.decisions_made)) result.decisions_made = [];
|
|
2861
|
+
if (!Array.isArray(result.discoveries)) result.discoveries = [];
|
|
2862
|
+
if (!Array.isArray(result.implicit_knowledge)) result.implicit_knowledge = [];
|
|
2863
|
+
if (!Array.isArray(result.quality_signals)) result.quality_signals = [];
|
|
2864
|
+
}
|
|
2865
|
+
if (toVersion === '0.6.0' || toVersion === '1.0.0') {
|
|
2866
|
+
if (!result.developer_id) result.developer_id = null;
|
|
2867
|
+
if (!result.session_id) result.session_id = null;
|
|
2868
|
+
if (!Array.isArray(result.recent_commits)) result.recent_commits = [];
|
|
2869
|
+
if (!Array.isArray(result.recent_files)) result.recent_files = [];
|
|
2870
|
+
}
|
|
2871
|
+
if (toVersion === '1.0.0') {
|
|
2872
|
+
if (!result.plugin_api_version) result.plugin_api_version = '1.0.0';
|
|
2873
|
+
result.schema_version = '1.0.0';
|
|
2874
|
+
}
|
|
2875
|
+
return result;
|
|
2876
|
+
}
|
|
2877
|
+
|
|
2878
|
+
function simulateAuditMigration(lines) {
|
|
2879
|
+
return lines.map(line => {
|
|
2880
|
+
try {
|
|
2881
|
+
const entry = JSON.parse(line);
|
|
2882
|
+
if (!entry.session_id) {
|
|
2883
|
+
return JSON.stringify({ ...entry, session_id: 'migrated-from-pre-1.0' });
|
|
2884
|
+
}
|
|
2885
|
+
return line;
|
|
2886
|
+
} catch {
|
|
2887
|
+
return line; // preserve invalid lines
|
|
2888
|
+
}
|
|
2889
|
+
});
|
|
2890
|
+
}
|
|
2891
|
+
|
|
2892
|
+
function simulateMindforgeMdMigration(content) {
|
|
2893
|
+
return content.replace(
|
|
2894
|
+
/^(VERIFY_PASS_RATE_WARNING_THRESHOLD=)(\d+(?:\.\d+)?)(\s*)$/m,
|
|
2895
|
+
(match, prefix, val, suffix) => {
|
|
2896
|
+
const num = parseFloat(val);
|
|
2897
|
+
return num > 1
|
|
2898
|
+
? `${prefix}${(num / 100).toFixed(2)}${suffix}`
|
|
2899
|
+
: match;
|
|
2900
|
+
}
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
|
|
2904
|
+
// ── Tests ──────────────────────────────────────────────────────────────────────
|
|
2905
|
+
console.log('\nMindForge Day 7 — Migration Tests\n');
|
|
2906
|
+
|
|
2907
|
+
console.log('Version comparator:');
|
|
2908
|
+
|
|
2909
|
+
test('compareSemver works for all comparison cases', () => {
|
|
2910
|
+
const { compareSemver } = require('../bin/updater/version-comparator');
|
|
2911
|
+
assert.ok(compareSemver('1.0.0', '0.9.9') > 0, '1.0.0 > 0.9.9');
|
|
2912
|
+
assert.ok(compareSemver('0.1.0', '1.0.0') < 0, '0.1.0 < 1.0.0');
|
|
2913
|
+
assert.strictEqual(compareSemver('0.5.0', '0.5.0'), 0, '0.5.0 == 0.5.0');
|
|
2914
|
+
assert.ok(compareSemver('2.0.0', '1.99.99') > 0, 'Major beats all minors');
|
|
2915
|
+
});
|
|
2916
|
+
|
|
2917
|
+
console.log('\nHANDOFF.json migrations:');
|
|
2918
|
+
|
|
2919
|
+
test('v0.1.0 → v0.5.0: adds intelligence layer fields', () => {
|
|
2920
|
+
const h = { schema_version: '0.1.0', next_task: 'test', _warning: 'warn' };
|
|
2921
|
+
const m = simulateHandoffMigration(h, '0.5.0');
|
|
2922
|
+
assert.ok(Array.isArray(m.decisions_made), 'decisions_made should be array');
|
|
2923
|
+
assert.ok(Array.isArray(m.discoveries), 'discoveries should be array');
|
|
2924
|
+
assert.ok(Array.isArray(m.implicit_knowledge), 'implicit_knowledge should be array');
|
|
2925
|
+
assert.ok(Array.isArray(m.quality_signals), 'quality_signals should be array');
|
|
2926
|
+
});
|
|
2927
|
+
|
|
2928
|
+
test('v0.5.0 → v0.6.0: adds distribution platform fields', () => {
|
|
2929
|
+
const h = { schema_version: '0.5.0', next_task: 'test', _warning: 'warn' };
|
|
2930
|
+
const m = simulateHandoffMigration(h, '0.6.0');
|
|
2931
|
+
assert.ok(Array.isArray(m.recent_commits), 'recent_commits should be array');
|
|
2932
|
+
assert.ok(Array.isArray(m.recent_files), 'recent_files should be array');
|
|
2933
|
+
assert.ok('developer_id' in m, 'developer_id should exist');
|
|
2934
|
+
assert.ok('session_id' in m, 'session_id should exist');
|
|
2935
|
+
});
|
|
2936
|
+
|
|
2937
|
+
test('v0.6.0 → v1.0.0: adds plugin_api_version', () => {
|
|
2938
|
+
const h = { schema_version: '0.6.0', next_task: 'test', _warning: 'warn' };
|
|
2939
|
+
const m = simulateHandoffMigration(h, '1.0.0');
|
|
2940
|
+
assert.strictEqual(m.plugin_api_version, '1.0.0');
|
|
2941
|
+
assert.strictEqual(m.schema_version, '1.0.0');
|
|
2942
|
+
});
|
|
2943
|
+
|
|
2944
|
+
test('v0.1.0 → v1.0.0 full chain: all fields present', () => {
|
|
2945
|
+
const h = { schema_version: '0.1.0', next_task: 'first task', _warning: 'warn', phase: 1 };
|
|
2946
|
+
const m = simulateHandoffMigration(h, '1.0.0');
|
|
2947
|
+
|
|
2948
|
+
// All fields from all migrations should be present
|
|
2949
|
+
assert.ok(Array.isArray(m.decisions_made), 'decisions_made from 0.5.0 migration');
|
|
2950
|
+
assert.ok(Array.isArray(m.recent_commits), 'recent_commits from 0.6.0 migration');
|
|
2951
|
+
assert.strictEqual(m.plugin_api_version, '1.0.0', 'plugin_api_version from 1.0.0 migration');
|
|
2952
|
+
assert.strictEqual(m.phase, 1, 'Original field preserved');
|
|
2953
|
+
assert.strictEqual(m.next_task, 'first task', 'Original next_task preserved');
|
|
2954
|
+
});
|
|
2955
|
+
|
|
2956
|
+
test('migration does not overwrite existing values', () => {
|
|
2957
|
+
const h = {
|
|
2958
|
+
schema_version: '0.1.0',
|
|
2959
|
+
next_task: 'existing task',
|
|
2960
|
+
_warning: 'original warning',
|
|
2961
|
+
phase: 3,
|
|
2962
|
+
plan: '04',
|
|
2963
|
+
custom_org_field: 'preserved',
|
|
2964
|
+
};
|
|
2965
|
+
const m = simulateHandoffMigration(h, '1.0.0');
|
|
2966
|
+
assert.strictEqual(m.next_task, 'existing task');
|
|
2967
|
+
assert.strictEqual(m.phase, 3);
|
|
2968
|
+
assert.strictEqual(m.plan, '04');
|
|
2969
|
+
assert.strictEqual(m.custom_org_field, 'preserved');
|
|
2970
|
+
assert.strictEqual(m._warning, 'original warning');
|
|
2971
|
+
});
|
|
2972
|
+
|
|
2973
|
+
console.log('\nAUDIT.jsonl migration:');
|
|
2974
|
+
|
|
2975
|
+
test('backfills missing session_id in audit entries', () => {
|
|
2976
|
+
const lines = [
|
|
2977
|
+
JSON.stringify({ id: 'uuid-1', timestamp: '2026-01-01T00:00:00Z', event: 'task_started', agent: 'test' }),
|
|
2978
|
+
JSON.stringify({ id: 'uuid-2', timestamp: '2026-01-01T00:01:00Z', event: 'task_completed', agent: 'test', session_id: 'existing' }),
|
|
2979
|
+
];
|
|
2980
|
+
const migrated = simulateAuditMigration(lines);
|
|
2981
|
+
const first = JSON.parse(migrated[0]);
|
|
2982
|
+
const second = JSON.parse(migrated[1]);
|
|
2983
|
+
assert.ok(first.session_id, 'Missing session_id should be backfilled');
|
|
2984
|
+
assert.strictEqual(first.session_id, 'migrated-from-pre-1.0');
|
|
2985
|
+
assert.strictEqual(second.session_id, 'existing', 'Existing session_id must not be changed');
|
|
2986
|
+
});
|
|
2987
|
+
|
|
2988
|
+
test('preserves invalid JSON lines without crashing', () => {
|
|
2989
|
+
const lines = [
|
|
2990
|
+
JSON.stringify({ id: 'uuid-1', event: 'test', timestamp: 't', agent: 'a' }),
|
|
2991
|
+
'{this is not valid JSON}',
|
|
2992
|
+
JSON.stringify({ id: 'uuid-2', event: 'test', timestamp: 't', agent: 'a' }),
|
|
2993
|
+
];
|
|
2994
|
+
const migrated = simulateAuditMigration(lines);
|
|
2995
|
+
assert.strictEqual(migrated.length, 3, 'All lines preserved');
|
|
2996
|
+
assert.strictEqual(migrated[1], '{this is not valid JSON}', 'Invalid line unchanged');
|
|
2997
|
+
});
|
|
2998
|
+
|
|
2999
|
+
test('does not double-backfill entries that already have session_id', () => {
|
|
3000
|
+
const original = JSON.stringify({ id: 'uuid', event: 'test', timestamp: 't', agent: 'a', session_id: 'my-session' });
|
|
3001
|
+
const [migrated] = simulateAuditMigration([original]);
|
|
3002
|
+
const entry = JSON.parse(migrated);
|
|
3003
|
+
assert.strictEqual(entry.session_id, 'my-session', 'Should not overwrite existing session_id');
|
|
3004
|
+
});
|
|
3005
|
+
|
|
3006
|
+
console.log('\nMINDFORGE.md migration:');
|
|
3007
|
+
|
|
3008
|
+
test('converts VERIFY_PASS_RATE_WARNING_THRESHOLD from 75 to 0.75', () => {
|
|
3009
|
+
const content = 'VERIFY_PASS_RATE_WARNING_THRESHOLD=75\n';
|
|
3010
|
+
const migrated = simulateMindforgeMdMigration(content);
|
|
3011
|
+
assert.ok(migrated.includes('0.75'), `Expected 0.75, got: ${migrated.trim()}`);
|
|
3012
|
+
assert.ok(!migrated.match(/=75(\s|$)/), 'Should not still contain =75');
|
|
3013
|
+
});
|
|
3014
|
+
|
|
3015
|
+
test('converts VERIFY_PASS_RATE_WARNING_THRESHOLD from 80 to 0.80', () => {
|
|
3016
|
+
const content = 'VERIFY_PASS_RATE_WARNING_THRESHOLD=80\nOTHER=value\n';
|
|
3017
|
+
const migrated = simulateMindforgeMdMigration(content);
|
|
3018
|
+
assert.ok(migrated.includes('0.80') || migrated.includes('0.8'), `Expected 0.80`);
|
|
3019
|
+
assert.ok(migrated.includes('OTHER=value'), 'Should preserve other settings');
|
|
3020
|
+
});
|
|
3021
|
+
|
|
3022
|
+
test('does NOT modify values already in decimal format (0.75)', () => {
|
|
3023
|
+
const content = 'VERIFY_PASS_RATE_WARNING_THRESHOLD=0.75\n';
|
|
3024
|
+
const migrated = simulateMindforgeMdMigration(content);
|
|
3025
|
+
assert.ok(migrated.includes('0.75'), 'Should preserve existing decimal format');
|
|
3026
|
+
assert.ok(!migrated.includes('0.0075'), 'Should not double-convert a decimal');
|
|
3027
|
+
});
|
|
3028
|
+
|
|
3029
|
+
test('does NOT modify value of exactly 1 (ambiguous — preserve)', () => {
|
|
3030
|
+
const content = 'VERIFY_PASS_RATE_WARNING_THRESHOLD=1\n';
|
|
3031
|
+
const migrated = simulateMindforgeMdMigration(content);
|
|
3032
|
+
// Value of 1 should be preserved as-is (it's ≤ 1, within decimal range)
|
|
3033
|
+
assert.ok(migrated.includes('=1'), 'Value of 1 should not be converted');
|
|
3034
|
+
assert.ok(!migrated.includes('=0.01'), 'Value of 1 should not become 0.01');
|
|
3035
|
+
});
|
|
3036
|
+
|
|
3037
|
+
console.log('\nMigration infrastructure:');
|
|
3038
|
+
|
|
3039
|
+
test('all migration files have correct fromVersion/toVersion', () => {
|
|
3040
|
+
const files = [
|
|
3041
|
+
{ file: 'bin/migrations/0.1.0-to-0.5.0.js', from: '0.1.0', to: '0.5.0' },
|
|
3042
|
+
{ file: 'bin/migrations/0.5.0-to-0.6.0.js', from: '0.5.0', to: '0.6.0' },
|
|
3043
|
+
{ file: 'bin/migrations/0.6.0-to-1.0.0.js', from: '0.6.0', to: '1.0.0' },
|
|
3044
|
+
];
|
|
3045
|
+
files.forEach(({ file, from, to }) => {
|
|
3046
|
+
const c = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : '';
|
|
3047
|
+
assert.ok(c.includes(from), `${file}: should contain fromVersion ${from}`);
|
|
3048
|
+
assert.ok(c.includes(to), `${file}: should contain toVersion ${to}`);
|
|
3049
|
+
});
|
|
3050
|
+
});
|
|
3051
|
+
|
|
3052
|
+
test('migration chain covers v0.1.0 → v1.0.0 completely', () => {
|
|
3053
|
+
const { compareSemver } = require('../bin/updater/version-comparator');
|
|
3054
|
+
|
|
3055
|
+
// Chain: 0.1.0 → 0.5.0 → 0.6.0 → 1.0.0
|
|
3056
|
+
const chain = ['0.1.0', '0.5.0', '0.6.0', '1.0.0'];
|
|
3057
|
+
for (let i = 0; i < chain.length - 1; i++) {
|
|
3058
|
+
const file = `bin/migrations/${chain[i]}-to-${chain[i+1]}.js`;
|
|
3059
|
+
assert.ok(fs.existsSync(file), `Missing migration: ${file}`);
|
|
3060
|
+
}
|
|
3061
|
+
|
|
3062
|
+
// Verify no gaps: each migration's toVersion = next migration's fromVersion
|
|
3063
|
+
for (let i = 0; i < chain.length - 2; i++) {
|
|
3064
|
+
assert.ok(
|
|
3065
|
+
compareSemver(chain[i + 1], chain[i]) > 0,
|
|
3066
|
+
`Chain gap between ${chain[i]} and ${chain[i+1]}`
|
|
3067
|
+
);
|
|
3068
|
+
}
|
|
3069
|
+
});
|
|
3070
|
+
|
|
3071
|
+
test('migrate.js exports runMigrations function', () => {
|
|
3072
|
+
const { runMigrations } = require('../bin/migrations/migrate');
|
|
3073
|
+
assert.strictEqual(typeof runMigrations, 'function', 'runMigrations should be a function');
|
|
3074
|
+
});
|
|
3075
|
+
|
|
3076
|
+
console.log(`\n${'─'.repeat(55)}`);
|
|
3077
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
3078
|
+
if (failed > 0) {
|
|
3079
|
+
console.error(`\n❌ ${failed} test(s) failed.\n`);
|
|
3080
|
+
process.exit(1);
|
|
3081
|
+
} else {
|
|
3082
|
+
console.log(`\n✅ All migration tests passed.\n`);
|
|
3083
|
+
}
|
|
3084
|
+
```
|
|
3085
|
+
|
|
3086
|
+
### `tests/e2e.test.js`
|
|
3087
|
+
|
|
3088
|
+
```javascript
|
|
3089
|
+
/**
|
|
3090
|
+
* MindForge Day 7 — End-to-End Simulation Tests
|
|
3091
|
+
* Simulates complete project workflows using file system operations.
|
|
3092
|
+
* No actual Claude API calls — tests the state machine, not the AI.
|
|
3093
|
+
*
|
|
3094
|
+
* Run: node tests/e2e.test.js
|
|
3095
|
+
*/
|
|
3096
|
+
'use strict';
|
|
3097
|
+
|
|
3098
|
+
const fs = require('fs');
|
|
3099
|
+
const path = require('path');
|
|
3100
|
+
const os = require('os');
|
|
3101
|
+
const assert = require('assert');
|
|
3102
|
+
let passed = 0, failed = 0;
|
|
3103
|
+
|
|
3104
|
+
function test(name, fn) {
|
|
3105
|
+
try { fn(); console.log(` ✅ ${name}`); passed++; }
|
|
3106
|
+
catch(e) { console.error(` ❌ ${name}\n ${e.message}`); failed++; }
|
|
3107
|
+
}
|
|
3108
|
+
|
|
3109
|
+
// ── Test project factory ───────────────────────────────────────────────────────
|
|
3110
|
+
function createTestProject() {
|
|
3111
|
+
const tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), 'mindforge-e2e-'));
|
|
3112
|
+
|
|
3113
|
+
function write(relPath, content) {
|
|
3114
|
+
const fullPath = path.join(tmpDir, relPath);
|
|
3115
|
+
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
|
3116
|
+
fs.writeFileSync(fullPath, content, 'utf8');
|
|
3117
|
+
return fullPath;
|
|
3118
|
+
}
|
|
3119
|
+
|
|
3120
|
+
function read(relPath) {
|
|
3121
|
+
const p = path.join(tmpDir, relPath);
|
|
3122
|
+
return fs.existsSync(p) ? fs.readFileSync(p, 'utf8') : null;
|
|
3123
|
+
}
|
|
3124
|
+
|
|
3125
|
+
function exists(relPath) {
|
|
3126
|
+
return fs.existsSync(path.join(tmpDir, relPath));
|
|
3127
|
+
}
|
|
3128
|
+
|
|
3129
|
+
function appendAudit(entry) {
|
|
3130
|
+
const auditPath = path.join(tmpDir, '.planning', 'AUDIT.jsonl');
|
|
3131
|
+
const line = JSON.stringify({
|
|
3132
|
+
id: `test-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
|
3133
|
+
timestamp: new Date().toISOString(),
|
|
3134
|
+
session_id: 'test-session-001',
|
|
3135
|
+
agent: 'mindforge-test',
|
|
3136
|
+
...entry,
|
|
3137
|
+
});
|
|
3138
|
+
fs.appendFileSync(auditPath, line + '\n');
|
|
3139
|
+
}
|
|
3140
|
+
|
|
3141
|
+
function cleanup() {
|
|
3142
|
+
try { fs.rmSync(tmpDir, { recursive: true, force: true }); }
|
|
3143
|
+
catch(e) { console.warn(` ⚠️ Cleanup warning: ${e.message} (dir: ${tmpDir})`); }
|
|
3144
|
+
}
|
|
3145
|
+
|
|
3146
|
+
return { tmpDir, write, read, exists, appendAudit, cleanup };
|
|
3147
|
+
}
|
|
3148
|
+
|
|
3149
|
+
// ── Workflow simulation functions ──────────────────────────────────────────────
|
|
3150
|
+
|
|
3151
|
+
function initProject(project) {
|
|
3152
|
+
const { write } = project;
|
|
3153
|
+
|
|
3154
|
+
// Core .planning/ files
|
|
3155
|
+
write('.planning/PROJECT.md', `# E2E Test Project
|
|
3156
|
+
|
|
3157
|
+
## Tech stack
|
|
3158
|
+
- Node.js 20 LTS
|
|
3159
|
+
- TypeScript 5.x
|
|
3160
|
+
- PostgreSQL via Prisma
|
|
3161
|
+
|
|
3162
|
+
## v1 scope — IN
|
|
3163
|
+
- [x] User authentication (email/password)
|
|
3164
|
+
- [x] User profile management
|
|
3165
|
+
|
|
3166
|
+
## Success criteria
|
|
3167
|
+
All acceptance tests passing. Zero HIGH security findings.
|
|
3168
|
+
`);
|
|
3169
|
+
|
|
3170
|
+
write('.planning/REQUIREMENTS.md', `# Requirements
|
|
3171
|
+
|
|
3172
|
+
## Functional requirements
|
|
3173
|
+
| ID | Requirement | Acceptance criterion | Scope |
|
|
3174
|
+
|---|---|---|---|
|
|
3175
|
+
| FR-01 | User login | POST /auth/login returns JWT for valid credentials | v1 |
|
|
3176
|
+
| FR-02 | User logout | POST /auth/logout invalidates session | v1 |
|
|
3177
|
+
| FR-03 | Profile fetch | GET /users/:id returns profile for authenticated user | v1 |
|
|
3178
|
+
`);
|
|
3179
|
+
|
|
3180
|
+
write('.planning/ARCHITECTURE.md', `# Architecture
|
|
3181
|
+
|
|
3182
|
+
## Architectural pattern
|
|
3183
|
+
Modular monolith — Express API with Prisma ORM
|
|
3184
|
+
|
|
3185
|
+
## Technology stack
|
|
3186
|
+
- Runtime: Node.js 20
|
|
3187
|
+
- Framework: Express 4.x
|
|
3188
|
+
- Database: PostgreSQL 15
|
|
3189
|
+
|
|
3190
|
+
## Data model
|
|
3191
|
+
**User**: id (UUID), email, passwordHash, createdAt, updatedAt
|
|
3192
|
+
`);
|
|
3193
|
+
|
|
3194
|
+
write('.planning/STATE.md', `# Project State
|
|
3195
|
+
|
|
3196
|
+
## Status
|
|
3197
|
+
Phase 1 in progress
|
|
3198
|
+
|
|
3199
|
+
## Current phase
|
|
3200
|
+
Phase 1 — Authentication
|
|
3201
|
+
|
|
3202
|
+
## Next action
|
|
3203
|
+
Execute Phase 1 plans
|
|
3204
|
+
`);
|
|
3205
|
+
|
|
3206
|
+
write('.planning/HANDOFF.json', JSON.stringify({
|
|
3207
|
+
schema_version: '1.0.0',
|
|
3208
|
+
plugin_api_version: '1.0.0',
|
|
3209
|
+
project: 'E2E Test Project',
|
|
3210
|
+
phase: 1,
|
|
3211
|
+
plan: null,
|
|
3212
|
+
next_task: 'Execute Phase 1 — Authentication',
|
|
3213
|
+
blockers: [],
|
|
3214
|
+
decisions_needed: [],
|
|
3215
|
+
decisions_made: [],
|
|
3216
|
+
discoveries: [],
|
|
3217
|
+
implicit_knowledge: [],
|
|
3218
|
+
quality_signals: [],
|
|
3219
|
+
context_refs: ['.planning/PROJECT.md', '.planning/STATE.md'],
|
|
3220
|
+
recent_commits: [],
|
|
3221
|
+
recent_files: [],
|
|
3222
|
+
session_id: 'test-session-001',
|
|
3223
|
+
_warning: 'Never store secrets, tokens, or passwords in this file.',
|
|
3224
|
+
updated_at: new Date().toISOString(),
|
|
3225
|
+
}, null, 2));
|
|
3226
|
+
|
|
3227
|
+
write('.planning/AUDIT.jsonl', '');
|
|
3228
|
+
project.appendAudit({ event: 'project_initialised', phase: null });
|
|
3229
|
+
}
|
|
3230
|
+
|
|
3231
|
+
function planPhase(project, phaseNum) {
|
|
3232
|
+
const { write, appendAudit } = project;
|
|
3233
|
+
const phaseDir = `.planning/phases/${phaseNum}`;
|
|
3234
|
+
|
|
3235
|
+
// Difficulty score
|
|
3236
|
+
write(`${phaseDir}/DIFFICULTY-SCORE-${phaseNum}.md`, `# Difficulty Score — Phase ${phaseNum}
|
|
3237
|
+
## Scores
|
|
3238
|
+
| Dimension | Score |
|
|
3239
|
+
|---|---|
|
|
3240
|
+
| Technical | 4/5 |
|
|
3241
|
+
| Risk | 5/5 |
|
|
3242
|
+
| Ambiguity | 2/5 |
|
|
3243
|
+
| Dependencies | 2/5 |
|
|
3244
|
+
## Composite: 3.75 — Challenging
|
|
3245
|
+
## Recommendations: 6 atomic tasks
|
|
3246
|
+
`);
|
|
3247
|
+
|
|
3248
|
+
// Plan file with valid XML
|
|
3249
|
+
write(`${phaseDir}/PLAN-${phaseNum}-01.md`, `<task type="auto">
|
|
3250
|
+
<n>Implement login endpoint with JWT</n>
|
|
3251
|
+
<persona>developer</persona>
|
|
3252
|
+
<phase>${phaseNum}</phase>
|
|
3253
|
+
<plan>01</plan>
|
|
3254
|
+
<dependencies>none</dependencies>
|
|
3255
|
+
<files>
|
|
3256
|
+
src/auth/login.ts
|
|
3257
|
+
src/auth/login.test.ts
|
|
3258
|
+
</files>
|
|
3259
|
+
<context>
|
|
3260
|
+
Skills: security-review (jwt.sign trigger), api-design, testing-standards
|
|
3261
|
+
</context>
|
|
3262
|
+
<action>
|
|
3263
|
+
Create POST /auth/login endpoint. Validate email/password against database.
|
|
3264
|
+
Use argon2 for password verification. Return signed JWT on success.
|
|
3265
|
+
Return 401 for invalid credentials (no information disclosure).
|
|
3266
|
+
</action>
|
|
3267
|
+
<verify>npm test -- --testPathPattern=auth.login</verify>
|
|
3268
|
+
<done>All login tests passing, argon2 verification working, JWT returned</done>
|
|
3269
|
+
</task>`);
|
|
3270
|
+
|
|
3271
|
+
write(`${phaseDir}/PLAN-${phaseNum}-02.md`, `<task type="auto">
|
|
3272
|
+
<n>Implement logout endpoint with session invalidation</n>
|
|
3273
|
+
<persona>developer</persona>
|
|
3274
|
+
<phase>${phaseNum}</phase>
|
|
3275
|
+
<plan>02</plan>
|
|
3276
|
+
<dependencies>01</dependencies>
|
|
3277
|
+
<files>
|
|
3278
|
+
src/auth/logout.ts
|
|
3279
|
+
src/auth/logout.test.ts
|
|
3280
|
+
</files>
|
|
3281
|
+
<context>
|
|
3282
|
+
Skills: security-review, testing-standards
|
|
3283
|
+
</context>
|
|
3284
|
+
<action>
|
|
3285
|
+
Create POST /auth/logout endpoint. Invalidate JWT via blocklist in Redis.
|
|
3286
|
+
Verify that blocklisted tokens return 401 on subsequent requests.
|
|
3287
|
+
</action>
|
|
3288
|
+
<verify>npm test -- --testPathPattern=auth.logout</verify>
|
|
3289
|
+
<done>Logout invalidates tokens, blocklisted tokens rejected</done>
|
|
3290
|
+
</task>`);
|
|
3291
|
+
|
|
3292
|
+
// Dependency graph
|
|
3293
|
+
write(`${phaseDir}/DEPENDENCY-GRAPH-${phaseNum}.md`, `# Dependency Graph — Phase ${phaseNum}
|
|
3294
|
+
|
|
3295
|
+
## Wave 1 (independent)
|
|
3296
|
+
- Plan 01: Implement login endpoint
|
|
3297
|
+
|
|
3298
|
+
## Wave 2 (depends on Wave 1)
|
|
3299
|
+
- Plan 02: Implement logout endpoint (requires login implementation)
|
|
3300
|
+
`);
|
|
3301
|
+
|
|
3302
|
+
appendAudit({ event: 'phase_planned', phase: phaseNum, plans_created: 2, waves: 2 });
|
|
3303
|
+
}
|
|
3304
|
+
|
|
3305
|
+
function executeTask(project, phaseNum, planId, commitSha) {
|
|
3306
|
+
const { write, appendAudit } = project;
|
|
3307
|
+
const phaseDir = `.planning/phases/${phaseNum}`;
|
|
3308
|
+
|
|
3309
|
+
appendAudit({ event: 'task_started', phase: phaseNum, plan: planId });
|
|
3310
|
+
|
|
3311
|
+
write(`${phaseDir}/SUMMARY-${phaseNum}-${planId}.md`, `# Summary — Phase ${phaseNum}, Plan ${planId}
|
|
3312
|
+
## Status: Completed ✅
|
|
3313
|
+
## Commit: ${commitSha}
|
|
3314
|
+
## Verify result: PASS
|
|
3315
|
+
## Files modified:
|
|
3316
|
+
- src/auth/${planId === '01' ? 'login' : 'logout'}.ts (created)
|
|
3317
|
+
- src/auth/${planId === '01' ? 'login' : 'logout'}.test.ts (created, 8 tests)
|
|
3318
|
+
`);
|
|
3319
|
+
|
|
3320
|
+
appendAudit({ event: 'task_completed', phase: phaseNum, plan: planId, commit_sha: commitSha, verify_result: 'pass' });
|
|
3321
|
+
}
|
|
3322
|
+
|
|
3323
|
+
function runSecurityScan(project, phaseNum, findings = []) {
|
|
3324
|
+
const { write, appendAudit } = project;
|
|
3325
|
+
const phaseDir = `.planning/phases/${phaseNum}`;
|
|
3326
|
+
|
|
3327
|
+
const criticalCount = findings.filter(f => f.severity === 'CRITICAL').length;
|
|
3328
|
+
const highCount = findings.filter(f => f.severity === 'HIGH').length;
|
|
3329
|
+
|
|
3330
|
+
write(`${phaseDir}/SECURITY-REVIEW-${phaseNum}.md`, `# Security Review — Phase ${phaseNum}
|
|
3331
|
+
## Scan date: ${new Date().toISOString()}
|
|
3332
|
+
## OWASP A01: ${findings.length === 0 ? 'PASS ✅' : 'FINDINGS'}
|
|
3333
|
+
|
|
3334
|
+
${findings.map(f => `### ${f.severity}: ${f.description}\nFile: ${f.file}:${f.line}\nRemediation: ${f.remediation}`).join('\n\n')}
|
|
3335
|
+
|
|
3336
|
+
## Summary
|
|
3337
|
+
- CRITICAL: ${criticalCount}
|
|
3338
|
+
- HIGH: ${highCount}
|
|
3339
|
+
- Total: ${findings.length}
|
|
3340
|
+
${findings.length === 0 ? '## Verdict: CLEAN ✅' : `## Verdict: ${criticalCount > 0 ? '🔴 BLOCKED' : '⚠️ REVIEW REQUIRED'}`}
|
|
3341
|
+
`);
|
|
3342
|
+
|
|
3343
|
+
appendAudit({ event: 'security_scan_completed', phase: phaseNum, critical: criticalCount, high: highCount });
|
|
3344
|
+
}
|
|
3345
|
+
|
|
3346
|
+
function verifyPhase(project, phaseNum) {
|
|
3347
|
+
const { write, appendAudit } = project;
|
|
3348
|
+
const phaseDir = `.planning/phases/${phaseNum}`;
|
|
3349
|
+
|
|
3350
|
+
write(`${phaseDir}/GATE-RESULTS-${phaseNum}.md`, `# Compliance Gate Results — Phase ${phaseNum}
|
|
3351
|
+
## Run at: ${new Date().toISOString()}
|
|
3352
|
+
| Gate | Status | Detail |
|
|
3353
|
+
|---|---|---|
|
|
3354
|
+
| Secret detection | ✅ PASS | 0 patterns found |
|
|
3355
|
+
| CRITICAL security findings | ✅ PASS | No open CRITICAL findings |
|
|
3356
|
+
| Test suite | ✅ PASS | 16 tests passing |
|
|
3357
|
+
| Dependency CVEs | ✅ PASS | 0 HIGH/CRITICAL |
|
|
3358
|
+
| GDPR retention | ✅ PASS | data-privacy skill not active |
|
|
3359
|
+
## Overall: ✅ ALL BLOCKING GATES PASSED
|
|
3360
|
+
`);
|
|
3361
|
+
|
|
3362
|
+
write(`${phaseDir}/VERIFICATION-${phaseNum}.md`, `# Verification — Phase ${phaseNum}
|
|
3363
|
+
## Stage 1 — Tests: 16/16 passing ✅
|
|
3364
|
+
## Stage 2 — Requirements:
|
|
3365
|
+
| FR-01 | User login | ✅ | src/auth/login.ts:24 |
|
|
3366
|
+
| FR-02 | User logout | ✅ | src/auth/logout.ts:18 |
|
|
3367
|
+
## Stage 3 — Type check: PASS ✅
|
|
3368
|
+
## Stage 4 — Security: PASS ✅
|
|
3369
|
+
## Overall: ✅ PHASE VERIFIED
|
|
3370
|
+
`);
|
|
3371
|
+
|
|
3372
|
+
write(`${phaseDir}/UAT-${phaseNum}.md`, `# UAT — Phase ${phaseNum}
|
|
3373
|
+
## Test results
|
|
3374
|
+
| # | Deliverable | Result | Notes |
|
|
3375
|
+
|---|---|---|---|
|
|
3376
|
+
| 1 | POST /auth/login returns JWT | ✅ | Tested with valid+invalid credentials |
|
|
3377
|
+
| 2 | POST /auth/logout invalidates session | ✅ | Blocklist verified |
|
|
3378
|
+
## Overall: ✅ ALL UAT PASSED
|
|
3379
|
+
`);
|
|
3380
|
+
|
|
3381
|
+
appendAudit({ event: 'phase_completed', phase: phaseNum, uat_pass_rate: 1.0 });
|
|
3382
|
+
}
|
|
3383
|
+
|
|
3384
|
+
// ── Tests ──────────────────────────────────────────────────────────────────────
|
|
3385
|
+
console.log('\nMindForge Day 7 — End-to-End Tests\n');
|
|
3386
|
+
|
|
3387
|
+
// ── Test 1: Complete greenfield workflow ───────────────────────────────────────
|
|
3388
|
+
console.log('Greenfield project workflow:');
|
|
3389
|
+
const gf = createTestProject();
|
|
3390
|
+
|
|
3391
|
+
try {
|
|
3392
|
+
test('init-project creates all required .planning/ files', () => {
|
|
3393
|
+
initProject(gf);
|
|
3394
|
+
const required = ['PROJECT.md', 'REQUIREMENTS.md', 'ARCHITECTURE.md', 'STATE.md', 'HANDOFF.json', 'AUDIT.jsonl'];
|
|
3395
|
+
required.forEach(f => assert.ok(gf.exists(`.planning/${f}`), `Missing: ${f}`));
|
|
3396
|
+
});
|
|
3397
|
+
|
|
3398
|
+
test('HANDOFF.json has v1.0.0 schema_version and plugin_api_version', () => {
|
|
3399
|
+
const handoff = JSON.parse(gf.read('.planning/HANDOFF.json'));
|
|
3400
|
+
assert.strictEqual(handoff.schema_version, '1.0.0');
|
|
3401
|
+
assert.strictEqual(handoff.plugin_api_version, '1.0.0');
|
|
3402
|
+
assert.ok(handoff._warning, 'Should have _warning field');
|
|
3403
|
+
assert.ok(!handoff._warning.toLowerCase().includes('password'), '_warning should not contain password');
|
|
3404
|
+
});
|
|
3405
|
+
|
|
3406
|
+
test('initial AUDIT.jsonl has project_initialised event with session_id', () => {
|
|
3407
|
+
const lines = gf.read('.planning/AUDIT.jsonl').split('\n').filter(Boolean);
|
|
3408
|
+
assert.ok(lines.length >= 1, 'Should have at least one audit entry');
|
|
3409
|
+
const first = JSON.parse(lines[0]);
|
|
3410
|
+
assert.strictEqual(first.event, 'project_initialised');
|
|
3411
|
+
assert.ok(first.session_id, 'Should have session_id');
|
|
3412
|
+
assert.ok(first.id, 'Should have id');
|
|
3413
|
+
assert.ok(first.timestamp, 'Should have timestamp');
|
|
3414
|
+
});
|
|
3415
|
+
|
|
3416
|
+
test('plan-phase creates PLAN files with valid XML structure', () => {
|
|
3417
|
+
planPhase(gf, 1);
|
|
3418
|
+
const plan = gf.read('.planning/phases/1/PLAN-1-01.md');
|
|
3419
|
+
assert.ok(plan, 'PLAN-1-01.md should exist');
|
|
3420
|
+
assert.ok(plan.includes('<task type="auto">'), 'Should have task element');
|
|
3421
|
+
assert.ok(plan.includes('<n>'), 'Should have task name');
|
|
3422
|
+
assert.ok(plan.includes('<persona>'), 'Should specify persona');
|
|
3423
|
+
assert.ok(plan.includes('<dependencies>'), 'Should have dependencies');
|
|
3424
|
+
assert.ok(plan.includes('<verify>'), 'Should have verify step');
|
|
3425
|
+
assert.ok(plan.includes('<done>'), 'Should have definition of done');
|
|
3426
|
+
});
|
|
3427
|
+
|
|
3428
|
+
test('difficulty score file created before plans', () => {
|
|
3429
|
+
assert.ok(gf.exists('.planning/phases/1/DIFFICULTY-SCORE-1.md'));
|
|
3430
|
+
const score = gf.read('.planning/phases/1/DIFFICULTY-SCORE-1.md');
|
|
3431
|
+
assert.ok(score.includes('Composite'), 'Should have composite score');
|
|
3432
|
+
assert.ok(score.includes('Challenging'), 'Should have difficulty label');
|
|
3433
|
+
});
|
|
3434
|
+
|
|
3435
|
+
test('dependency graph created and shows wave structure', () => {
|
|
3436
|
+
const dep = gf.read('.planning/phases/1/DEPENDENCY-GRAPH-1.md');
|
|
3437
|
+
assert.ok(dep, 'Dependency graph should exist');
|
|
3438
|
+
assert.ok(dep.includes('Wave 1'), 'Should define Wave 1');
|
|
3439
|
+
assert.ok(dep.includes('Wave 2'), 'Should define Wave 2');
|
|
3440
|
+
assert.ok(dep.includes('Plan 01'), 'Should reference Plan 01');
|
|
3441
|
+
assert.ok(dep.includes('Plan 02'), 'Should reference Plan 02');
|
|
3442
|
+
});
|
|
3443
|
+
|
|
3444
|
+
test('execute task creates SUMMARY with commit SHA', () => {
|
|
3445
|
+
executeTask(gf, 1, '01', 'abc1234ef');
|
|
3446
|
+
const summary = gf.read('.planning/phases/1/SUMMARY-1-01.md');
|
|
3447
|
+
assert.ok(summary, 'SUMMARY-1-01.md should exist');
|
|
3448
|
+
assert.ok(summary.includes('Completed ✅'), 'Should show completed status');
|
|
3449
|
+
assert.ok(summary.includes('abc1234ef'), 'Should include commit SHA');
|
|
3450
|
+
});
|
|
3451
|
+
|
|
3452
|
+
test('task execution writes task_started and task_completed to AUDIT.jsonl', () => {
|
|
3453
|
+
executeTask(gf, 1, '02', 'def5678ab');
|
|
3454
|
+
const lines = gf.read('.planning/AUDIT.jsonl').split('\n').filter(Boolean);
|
|
3455
|
+
const events = lines.map(l => JSON.parse(l).event);
|
|
3456
|
+
assert.ok(events.includes('task_started'), 'Should have task_started event');
|
|
3457
|
+
assert.ok(events.includes('task_completed'), 'Should have task_completed event');
|
|
3458
|
+
});
|
|
3459
|
+
|
|
3460
|
+
test('security scan writes SECURITY-REVIEW file', () => {
|
|
3461
|
+
runSecurityScan(gf, 1, []);
|
|
3462
|
+
assert.ok(gf.exists('.planning/phases/1/SECURITY-REVIEW-1.md'));
|
|
3463
|
+
const review = gf.read('.planning/phases/1/SECURITY-REVIEW-1.md');
|
|
3464
|
+
assert.ok(review.includes('CLEAN ✅') || review.includes('PASS'), 'Should show passing result');
|
|
3465
|
+
});
|
|
3466
|
+
|
|
3467
|
+
test('verify-phase creates GATE-RESULTS, VERIFICATION, and UAT files', () => {
|
|
3468
|
+
verifyPhase(gf, 1);
|
|
3469
|
+
assert.ok(gf.exists('.planning/phases/1/GATE-RESULTS-1.md'), 'GATE-RESULTS should exist');
|
|
3470
|
+
assert.ok(gf.exists('.planning/phases/1/VERIFICATION-1.md'), 'VERIFICATION should exist');
|
|
3471
|
+
assert.ok(gf.exists('.planning/phases/1/UAT-1.md'), 'UAT should exist');
|
|
3472
|
+
});
|
|
3473
|
+
|
|
3474
|
+
test('GATE-RESULTS shows all 5 gates passing', () => {
|
|
3475
|
+
const gates = gf.read('.planning/phases/1/GATE-RESULTS-1.md');
|
|
3476
|
+
assert.ok(gates.includes('Secret detection'), 'Should have secret detection gate');
|
|
3477
|
+
assert.ok(gates.includes('CRITICAL security'), 'Should have CRITICAL findings gate');
|
|
3478
|
+
assert.ok(gates.includes('Test suite'), 'Should have test suite gate');
|
|
3479
|
+
assert.ok(gates.includes('✅ PASS'), 'Gates should pass');
|
|
3480
|
+
assert.ok(gates.includes('ALL BLOCKING GATES PASSED'), 'Should confirm all gates passed');
|
|
3481
|
+
});
|
|
3482
|
+
|
|
3483
|
+
test('VERIFICATION.md references requirements with traceability', () => {
|
|
3484
|
+
const v = gf.read('.planning/phases/1/VERIFICATION-1.md');
|
|
3485
|
+
assert.ok(v.includes('FR-01'), 'Should reference FR-01');
|
|
3486
|
+
assert.ok(v.includes('FR-02'), 'Should reference FR-02');
|
|
3487
|
+
assert.ok(v.includes('src/auth/'), 'Should reference source files');
|
|
3488
|
+
});
|
|
3489
|
+
|
|
3490
|
+
// Complete audit log validation
|
|
3491
|
+
test('full workflow: all AUDIT.jsonl entries are valid with required fields', () => {
|
|
3492
|
+
const lines = gf.read('.planning/AUDIT.jsonl').split('\n').filter(Boolean);
|
|
3493
|
+
assert.ok(lines.length >= 6, `Should have >= 6 audit entries, got ${lines.length}`);
|
|
3494
|
+
|
|
3495
|
+
lines.forEach((line, i) => {
|
|
3496
|
+
let entry;
|
|
3497
|
+
assert.doesNotThrow(() => { entry = JSON.parse(line); }, `Line ${i+1} is not valid JSON`);
|
|
3498
|
+
assert.ok(entry.id, `Line ${i+1}: missing 'id'`);
|
|
3499
|
+
assert.ok(entry.timestamp, `Line ${i+1}: missing 'timestamp'`);
|
|
3500
|
+
assert.ok(entry.event, `Line ${i+1}: missing 'event'`);
|
|
3501
|
+
assert.ok(entry.session_id, `Line ${i+1}: missing 'session_id'`);
|
|
3502
|
+
assert.ok(entry.agent, `Line ${i+1}: missing 'agent'`);
|
|
3503
|
+
});
|
|
3504
|
+
});
|
|
3505
|
+
|
|
3506
|
+
test('full workflow: AUDIT.jsonl events cover complete lifecycle', () => {
|
|
3507
|
+
const lines = gf.read('.planning/AUDIT.jsonl').split('\n').filter(Boolean);
|
|
3508
|
+
const events = new Set(lines.map(l => JSON.parse(l).event));
|
|
3509
|
+
assert.ok(events.has('project_initialised'), 'Missing: project_initialised');
|
|
3510
|
+
assert.ok(events.has('phase_planned'), 'Missing: phase_planned');
|
|
3511
|
+
assert.ok(events.has('task_started'), 'Missing: task_started');
|
|
3512
|
+
assert.ok(events.has('task_completed'), 'Missing: task_completed');
|
|
3513
|
+
assert.ok(events.has('phase_completed'), 'Missing: phase_completed');
|
|
3514
|
+
});
|
|
3515
|
+
|
|
3516
|
+
} finally {
|
|
3517
|
+
gf.cleanup();
|
|
3518
|
+
}
|
|
3519
|
+
|
|
3520
|
+
// ── Test 2: Brownfield / map-codebase workflow ─────────────────────────────────
|
|
3521
|
+
console.log('\nBrownfield project workflow:');
|
|
3522
|
+
const bf = createTestProject();
|
|
3523
|
+
|
|
3524
|
+
try {
|
|
3525
|
+
// Simulate what /mindforge:map-codebase produces
|
|
3526
|
+
test('map-codebase creates CONVENTIONS.md with DRAFT status marker', () => {
|
|
3527
|
+
bf.write('.mindforge/org/CONVENTIONS.md', `# Coding Conventions — E2E Test Project
|
|
3528
|
+
# Source: Inferred from codebase analysis by MindForge
|
|
3529
|
+
# Status: DRAFT — confirm with team before treating as authoritative
|
|
3530
|
+
|
|
3531
|
+
## IMPORTANT
|
|
3532
|
+
These conventions were inferred from code analysis.
|
|
3533
|
+
Review each section and mark as [CONFIRMED] or [NEEDS REVIEW].
|
|
3534
|
+
|
|
3535
|
+
## Naming conventions [NEEDS REVIEW]
|
|
3536
|
+
- Variables: camelCase
|
|
3537
|
+
- Files: kebab-case
|
|
3538
|
+
- Classes: PascalCase
|
|
3539
|
+
|
|
3540
|
+
## Import order [NEEDS REVIEW]
|
|
3541
|
+
- Node.js built-ins
|
|
3542
|
+
- Third-party libraries
|
|
3543
|
+
- Internal modules
|
|
3544
|
+
`);
|
|
3545
|
+
const content = bf.read('.mindforge/org/CONVENTIONS.md');
|
|
3546
|
+
assert.ok(content.includes('DRAFT'), 'Should be marked as DRAFT');
|
|
3547
|
+
assert.ok(content.includes('NEEDS REVIEW'), 'Should have review markers');
|
|
3548
|
+
});
|
|
3549
|
+
|
|
3550
|
+
test('map-codebase creates ARCHITECTURE.md with inferred stack', () => {
|
|
3551
|
+
bf.write('.planning/ARCHITECTURE.md', `# Architecture — E2E Test Project
|
|
3552
|
+
## MindForge onboarding: Inferred from codebase
|
|
3553
|
+
## Technology stack
|
|
3554
|
+
- Runtime: Node.js 20 (inferred from .nvmrc)
|
|
3555
|
+
- Framework: Express 4.x (inferred from package.json)
|
|
3556
|
+
- Database: PostgreSQL via Prisma (inferred from prisma/schema.prisma)
|
|
3557
|
+
## Quality baseline
|
|
3558
|
+
- Tests: Vitest, ~200 test files
|
|
3559
|
+
- Linting: ESLint configured
|
|
3560
|
+
- CI/CD: GitHub Actions
|
|
3561
|
+
`);
|
|
3562
|
+
const arch = bf.read('.planning/ARCHITECTURE.md');
|
|
3563
|
+
assert.ok(arch, 'ARCHITECTURE.md should exist');
|
|
3564
|
+
assert.ok(arch.includes('inferred') || arch.includes('Inferred'), 'Should note inferred content');
|
|
3565
|
+
});
|
|
3566
|
+
|
|
3567
|
+
test('STATE.md from map-codebase shows ready-for-planning status', () => {
|
|
3568
|
+
bf.write('.planning/STATE.md', `# Project State
|
|
3569
|
+
|
|
3570
|
+
## Status
|
|
3571
|
+
Codebase mapped. Ready to plan first phase.
|
|
3572
|
+
|
|
3573
|
+
## Current phase
|
|
3574
|
+
None — run /mindforge:plan-phase 1 to begin.
|
|
3575
|
+
|
|
3576
|
+
## Last action
|
|
3577
|
+
/mindforge:map-codebase completed — codebase analysis done.
|
|
3578
|
+
`);
|
|
3579
|
+
const state = bf.read('.planning/STATE.md');
|
|
3580
|
+
assert.ok(state.includes('map'), 'STATE.md should reference map-codebase');
|
|
3581
|
+
assert.ok(state.includes('plan'), 'STATE.md should suggest next step');
|
|
3582
|
+
});
|
|
3583
|
+
|
|
3584
|
+
} finally {
|
|
3585
|
+
bf.cleanup();
|
|
3586
|
+
}
|
|
3587
|
+
|
|
3588
|
+
// ── Test 3: Security gate scenarios ───────────────────────────────────────────
|
|
3589
|
+
console.log('\nSecurity gate scenarios:');
|
|
3590
|
+
const sg = createTestProject();
|
|
3591
|
+
|
|
3592
|
+
try {
|
|
3593
|
+
test('CRITICAL security finding in review blocks phase completion indicator', () => {
|
|
3594
|
+
initProject(sg);
|
|
3595
|
+
planPhase(sg, 1);
|
|
3596
|
+
executeTask(sg, 1, '01', 'abc1234');
|
|
3597
|
+
|
|
3598
|
+
// Run security scan with a CRITICAL finding
|
|
3599
|
+
runSecurityScan(sg, 1, [{
|
|
3600
|
+
severity: 'CRITICAL',
|
|
3601
|
+
description: 'SQL injection via unsanitised user input',
|
|
3602
|
+
file: 'src/db/users.ts',
|
|
3603
|
+
line: 47,
|
|
3604
|
+
remediation: 'Use parameterised queries: db.query($1, [id])',
|
|
3605
|
+
}]);
|
|
3606
|
+
|
|
3607
|
+
const review = sg.read('.planning/phases/1/SECURITY-REVIEW-1.md');
|
|
3608
|
+
assert.ok(review.includes('CRITICAL'), 'Should show CRITICAL finding');
|
|
3609
|
+
assert.ok(review.includes('BLOCKED') || review.includes('0 CRITICAL') === false, 'Should indicate blocked state');
|
|
3610
|
+
});
|
|
3611
|
+
|
|
3612
|
+
test('AUDIT.jsonl captures security findings with correct schema', () => {
|
|
3613
|
+
const lines = sg.read('.planning/AUDIT.jsonl').split('\n').filter(Boolean);
|
|
3614
|
+
const secScan = lines.map(l => JSON.parse(l)).find(e => e.event === 'security_scan_completed');
|
|
3615
|
+
assert.ok(secScan, 'Should have security_scan_completed audit entry');
|
|
3616
|
+
assert.strictEqual(secScan.critical, 1, 'Should record 1 critical finding');
|
|
3617
|
+
assert.ok(secScan.session_id, 'Security scan audit entry should have session_id');
|
|
3618
|
+
});
|
|
3619
|
+
|
|
3620
|
+
} finally {
|
|
3621
|
+
sg.cleanup();
|
|
3622
|
+
}
|
|
3623
|
+
|
|
3624
|
+
// ── Results ─────────────────────────────────────────────────────────────────────
|
|
3625
|
+
console.log(`\n${'─'.repeat(55)}`);
|
|
3626
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
3627
|
+
if (failed > 0) {
|
|
3628
|
+
console.error(`\n❌ ${failed} test(s) failed.\n`);
|
|
3629
|
+
process.exit(1);
|
|
3630
|
+
} else {
|
|
3631
|
+
console.log(`\n✅ All E2E tests passed.\n`);
|
|
3632
|
+
}
|
|
3633
|
+
```
|
|
3634
|
+
|
|
3635
|
+
**Commit:**
|
|
3636
|
+
```bash
|
|
3637
|
+
git add tests/
|
|
3638
|
+
git commit -m "test(day7): add production readiness, migration, and E2E test suites"
|
|
3639
|
+
```
|
|
3640
|
+
|
|
3641
|
+
---
|
|
3642
|
+
|
|
3643
|
+
## TASK 11 — Update CLAUDE.md, bump to v1.0.0, final commit
|
|
3644
|
+
|
|
3645
|
+
Add to `.claude/CLAUDE.md` and mirror to `.agent/CLAUDE.md`:
|
|
3646
|
+
|
|
3647
|
+
```markdown
|
|
3648
|
+
---
|
|
3649
|
+
|
|
3650
|
+
## PRODUCTION LAYER (Day 7 — v1.0.0)
|
|
3651
|
+
|
|
3652
|
+
### Plugin awareness at session start
|
|
3653
|
+
On session start: read PLUGINS-MANIFEST.md if it exists.
|
|
3654
|
+
Load all installed plugins per plugin-loader.md protocol.
|
|
3655
|
+
Report: "Active plugins: [list]" or "No plugins installed."
|
|
3656
|
+
If a plugin has a lifecycle hook that applies to the current operation: execute it.
|
|
3657
|
+
Never fail session start because a plugin is invalid — skip and report.
|
|
3658
|
+
|
|
3659
|
+
### Schema version awareness
|
|
3660
|
+
On session start: compare HANDOFF.json schema_version against current package.json version.
|
|
3661
|
+
If schema_version is OLDER than current version by more than a patch:
|
|
3662
|
+
Suggest: "Your .planning/ files are on MindForge v[old]. Run /mindforge:migrate."
|
|
3663
|
+
Do NOT auto-migrate without explicit user command.
|
|
3664
|
+
Old schemas are still readable — don't block execution, just suggest migration.
|
|
3665
|
+
|
|
3666
|
+
### Token efficiency mindset
|
|
3667
|
+
Apply token-optimiser.md strategies in all sessions:
|
|
3668
|
+
- PLAN `<action>` fields: lean (150-400 words), specify WHAT not HOW
|
|
3669
|
+
- Read files when referenced, not all upfront
|
|
3670
|
+
- Load only "Current status" section of STATE.md unless history is needed
|
|
3671
|
+
- Maximum 3 skills at full injection — summarise anything beyond
|
|
3672
|
+
|
|
3673
|
+
### Self-update awareness
|
|
3674
|
+
On session start: if MINDFORGE_AUTO_CHECK_UPDATES=true in MINDFORGE.md,
|
|
3675
|
+
check for updates silently (no output unless update is available).
|
|
3676
|
+
If update available: notify once, do not repeat.
|
|
3677
|
+
|
|
3678
|
+
### v1.0.0 stable interface
|
|
3679
|
+
As of v1.0.0, all 36 commands have stable interfaces.
|
|
3680
|
+
All plugin.json, HANDOFF.json, AUDIT.jsonl schemas are stable.
|
|
3681
|
+
Breaking changes to these require MAJOR version bump.
|
|
3682
|
+
Non-breaking additions (new optional fields, new commands) require MINOR.
|
|
3683
|
+
|
|
3684
|
+
### New commands (Day 7)
|
|
3685
|
+
- /mindforge:update — check for and apply framework updates
|
|
3686
|
+
- /mindforge:migrate — run schema migrations between versions
|
|
3687
|
+
- /mindforge:plugins — manage plugins (install, uninstall, validate)
|
|
3688
|
+
- /mindforge:tokens — token usage profiling and optimisation
|
|
3689
|
+
- /mindforge:release — framework release pipeline (core team only)
|
|
3690
|
+
|
|
3691
|
+
---
|
|
3692
|
+
```
|
|
3693
|
+
|
|
3694
|
+
**Bump to v1.0.0:**
|
|
3695
|
+
|
|
3696
|
+
```bash
|
|
3697
|
+
node -e "
|
|
3698
|
+
const fs = require('fs');
|
|
3699
|
+
const p = JSON.parse(fs.readFileSync('package.json', 'utf8'));
|
|
3700
|
+
p.version = '1.0.0';
|
|
3701
|
+
fs.writeFileSync('package.json', JSON.stringify(p, null, 2) + '\n');
|
|
3702
|
+
console.log('Bumped to v1.0.0');
|
|
3703
|
+
"
|
|
3704
|
+
|
|
3705
|
+
git add package.json .claude/CLAUDE.md .agent/CLAUDE.md
|
|
3706
|
+
git commit -m "feat(core): v1.0.0 stable — production CLAUDE.md with plugin, migration, token awareness"
|
|
3707
|
+
```
|
|
3708
|
+
|
|
3709
|
+
---
|
|
3710
|
+
|
|
3711
|
+
## TASK 12 — Run all 15 suites × 3 consecutive runs
|
|
3712
|
+
|
|
3713
|
+
```bash
|
|
3714
|
+
#!/usr/bin/env bash
|
|
3715
|
+
# MindForge v1.0.0 Pre-Release Verification
|
|
3716
|
+
# ALL 15 suites × 3 runs must pass
|
|
3717
|
+
|
|
3718
|
+
set -euo pipefail
|
|
3719
|
+
|
|
3720
|
+
SUITES=(install wave-engine audit compaction skills-platform
|
|
3721
|
+
integrations governance intelligence metrics
|
|
3722
|
+
distribution ci-mode sdk production migration e2e)
|
|
3723
|
+
|
|
3724
|
+
TOTAL_PASS=0
|
|
3725
|
+
TOTAL_FAIL=0
|
|
3726
|
+
ALL_OK=true
|
|
3727
|
+
|
|
3728
|
+
echo ""
|
|
3729
|
+
echo "⚡ MindForge v1.0.0 Pre-Release Test Battery"
|
|
3730
|
+
echo " 15 suites × 3 runs = $((${#SUITES[@]} * 3)) expected passes"
|
|
3731
|
+
echo ""
|
|
3732
|
+
|
|
3733
|
+
for RUN in 1 2 3; do
|
|
3734
|
+
echo "═══ Run ${RUN} / 3 ════════════════════════════════════════════════"
|
|
3735
|
+
for SUITE in "${SUITES[@]}"; do
|
|
3736
|
+
printf " %-30s" "${SUITE}..."
|
|
3737
|
+
if OUTPUT=$(node tests/${SUITE}.test.js 2>&1); then
|
|
3738
|
+
LAST=$(echo "${OUTPUT}" | tail -1)
|
|
3739
|
+
echo "✅ ${LAST}"
|
|
3740
|
+
((TOTAL_PASS++))
|
|
3741
|
+
else
|
|
3742
|
+
echo "❌ FAILED"
|
|
3743
|
+
echo "${OUTPUT}" | grep -E "❌|Error" | head -5
|
|
3744
|
+
((TOTAL_FAIL++))
|
|
3745
|
+
ALL_OK=false
|
|
3746
|
+
fi
|
|
3747
|
+
done
|
|
3748
|
+
done
|
|
3749
|
+
|
|
3750
|
+
echo ""
|
|
3751
|
+
echo "═══════════════════════════════════════════════════════════════"
|
|
3752
|
+
echo "Results: ${TOTAL_PASS} passed / ${TOTAL_FAIL} failed"
|
|
3753
|
+
|
|
3754
|
+
if "${ALL_OK}"; then
|
|
3755
|
+
echo "✅ ALL $((${#SUITES[@]} * 3)) TESTS PASSED × 3 RUNS"
|
|
3756
|
+
echo ""
|
|
3757
|
+
echo "Ready to tag v1.0.0:"
|
|
3758
|
+
echo " git tag -a v1.0.0 -m 'MindForge v1.0.0 — First stable release'"
|
|
3759
|
+
echo " git push origin v1.0.0"
|
|
3760
|
+
echo " npm publish --access public"
|
|
3761
|
+
else
|
|
3762
|
+
echo "❌ ${TOTAL_FAIL} FAILURES — NOT READY FOR RELEASE"
|
|
3763
|
+
exit 1
|
|
3764
|
+
fi
|
|
3765
|
+
```
|
|
3766
|
+
|
|
3767
|
+
**Final commit and push:**
|
|
3768
|
+
|
|
3769
|
+
```bash
|
|
3770
|
+
git add .
|
|
3771
|
+
git commit -m "feat(day7): complete production hardening — v1.0.0 release ready"
|
|
3772
|
+
git push origin feat/mindforge-production-release
|
|
3773
|
+
```
|
|
3774
|
+
|
|
3775
|
+
---
|
|
3776
|
+
|
|
3777
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
3778
|
+
# PART 2 — REVIEW PROMPT
|
|
3779
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
3780
|
+
|
|
3781
|
+
---
|
|
3782
|
+
|
|
3783
|
+
## DAY 7 REVIEW — Run after implementation is complete
|
|
3784
|
+
|
|
3785
|
+
Activate **`architect.md` + `qa-engineer.md` + `security-reviewer.md`** simultaneously.
|
|
3786
|
+
|
|
3787
|
+
Day 7 is the v1.0.0 release sprint. The review standard is higher than any
|
|
3788
|
+
previous day — every issue found here is an issue that ships to production users.
|
|
3789
|
+
|
|
3790
|
+
Three adversarial lenses for this review:
|
|
3791
|
+
1. **Reliability lens**: what breaks under realistic production conditions?
|
|
3792
|
+
2. **Security lens**: what attack surfaces were not hardened?
|
|
3793
|
+
3. **User experience lens**: what fails silently or produces confusing errors?
|
|
3794
|
+
|
|
3795
|
+
---
|
|
3796
|
+
|
|
3797
|
+
## REVIEW PASS 1 — Installer: All code paths
|
|
3798
|
+
|
|
3799
|
+
Read `bin/install.js` and `bin/installer-core.js`.
|
|
3800
|
+
|
|
3801
|
+
- [ ] **Windows path handling**: The installer uses `os.homedir()` and `path.join()`.
|
|
3802
|
+
On Windows, paths use backslashes. The runtime directories (`.claude/`, `.agent/`)
|
|
3803
|
+
use forward slashes everywhere. Does `path.join()` produce correct paths on Windows?
|
|
3804
|
+
Specifically: `path.join(os.homedir(), '.claude')` → `C:\Users\john\.claude` on Windows.
|
|
3805
|
+
Does Claude Code on Windows use `C:\Users\john\.claude` or `C:\Users\john\.claude\`?
|
|
3806
|
+
Add: "For Windows compatibility, normalize all paths with `path.normalize()` before use."
|
|
3807
|
+
|
|
3808
|
+
- [ ] **copyDir excludePatterns**: The exclude list uses `{ excludePatterns: SENSITIVE_EXCLUDE }`.
|
|
3809
|
+
But `SENSITIVE_EXCLUDE` contains strings like `'.env'` and regex like `/^\.env\..*/`.
|
|
3810
|
+
The copyDir function checks:
|
|
3811
|
+
`excludePatterns.some(pat => typeof pat === 'string' ? entry.name === pat : pat.test(entry.name))`
|
|
3812
|
+
But `'*.key'` is a glob pattern string, not a filename — `entry.name === '*.key'` will never match.
|
|
3813
|
+
Fix: replace glob patterns with regex equivalents: `/\.key$/` not `'*.key'`.
|
|
3814
|
+
|
|
3815
|
+
- [ ] **`--all` scope variable**: In the `run()` function, when `runtime === 'all'`,
|
|
3816
|
+
the code iterates `runtimes.forEach(rt => install(rt, scope, options))`.
|
|
3817
|
+
But `scope` is not in scope at this point in the function — it was destructured
|
|
3818
|
+
from args before the `isUpdate` check, so it should be available. Verify this is correct.
|
|
3819
|
+
|
|
3820
|
+
---
|
|
3821
|
+
|
|
3822
|
+
## REVIEW PASS 2 — Self-Update: Edge cases
|
|
3823
|
+
|
|
3824
|
+
Read all three updater files.
|
|
3825
|
+
|
|
3826
|
+
- [ ] **`checkAndUpdate` with CI=true**: In CI mode, the update should not prompt
|
|
3827
|
+
the user for confirmation before applying. But the current code checks `apply` flag only.
|
|
3828
|
+
Add: "In CI mode (`process.env.CI === 'true'`): if `--apply` is passed, apply without confirmation.
|
|
3829
|
+
If `--apply` is NOT passed in CI: check only (don't apply silently)."
|
|
3830
|
+
|
|
3831
|
+
- [ ] **changelog extraction**: `extractEntries` splits the changelog on `\n` and looks for
|
|
3832
|
+
`## [v?X.Y.Z` pattern. But if the CHANGELOG.md uses a different date format
|
|
3833
|
+
(e.g., `## v1.0.0 — 2026-03-22`) the regex `## \[?v?(\d+\.\d+\.\d+)` should still match
|
|
3834
|
+
(the `\[?` allows the optional bracket). Verify this works for:
|
|
3835
|
+
- `## [1.0.0] — 2026-03-22`
|
|
3836
|
+
- `## v1.0.0 — 2026-03-22`
|
|
3837
|
+
- `## 1.0.0 (2026-03-22)`
|
|
3838
|
+
|
|
3839
|
+
- [ ] **detectInstallScope**: The function checks file existence to determine scope.
|
|
3840
|
+
But what if BOTH local and global installs exist?
|
|
3841
|
+
The function returns local first (checked first) — this is correct per ADR-019.
|
|
3842
|
+
But the decision should be documented in the code with a comment for maintainability.
|
|
3843
|
+
Add: `// Per ADR-019: local takes precedence over global`
|
|
3844
|
+
|
|
3845
|
+
---
|
|
3846
|
+
|
|
3847
|
+
## REVIEW PASS 3 — Migration Engine: Correctness
|
|
3848
|
+
|
|
3849
|
+
Read all migration files.
|
|
3850
|
+
|
|
3851
|
+
- [ ] **Migration backup retention**: After successful migration, the backup is retained
|
|
3852
|
+
with a note: "Remove when satisfied." But in CI environments, accumulated migration
|
|
3853
|
+
backups will fill disk space over time. Add: "In CI mode: auto-delete backup on successful
|
|
3854
|
+
migration. In interactive mode: retain backup."
|
|
3855
|
+
|
|
3856
|
+
- [ ] **The `0.6.0-to-1.0.0.js` migration** converts `VERIFY_PASS_RATE_WARNING_THRESHOLD`.
|
|
3857
|
+
The test `test('does NOT modify value of exactly 1')` verifies that `=1` is preserved.
|
|
3858
|
+
But what about `VERIFY_PASS_RATE_WARNING_THRESHOLD=1.0` (explicit decimal)?
|
|
3859
|
+
The regex `/^(VERIFY_PASS_RATE_WARNING_THRESHOLD=)(\d+(?:\.\d+)?)(\s*)$/m` matches `1.0`.
|
|
3860
|
+
`parseFloat('1.0') > 1` is `false` — so `1.0` would be correctly preserved.
|
|
3861
|
+
Verify this edge case is tested.
|
|
3862
|
+
|
|
3863
|
+
- [ ] **Migration chain verification**: If someone has MindForge v0.3.0 (a version between
|
|
3864
|
+
our migration files), there is no migration file for `0.3.0-to-0.5.0`.
|
|
3865
|
+
The migrate.js uses a filter:
|
|
3866
|
+
`compareSemver(m.fromVersion, fromVersion) >= 0 && compareSemver(m.toVersion, toVersion) <= 0`
|
|
3867
|
+
For fromVersion=0.3.0 and toVersion=1.0.0:
|
|
3868
|
+
- `0.1.0-to-0.5.0.js`: `compareSemver('0.1.0', '0.3.0') >= 0` → `0.1.0 >= 0.3.0` → false ❌
|
|
3869
|
+
This means the migration from 0.3.0 would SKIP the 0.1.0-to-0.5.0 migration,
|
|
3870
|
+
potentially leaving HANDOFF.json without the intelligence layer fields.
|
|
3871
|
+
Fix: the filter should use `< toVersion` not `>= fromVersion`.
|
|
3872
|
+
Or: use a range check: `m applies if m.toVersion is BETWEEN fromVersion and toVersion`.
|
|
3873
|
+
The correct logic is: run migration if `m.toVersion > fromVersion AND m.toVersion <= toVersion`.
|
|
3874
|
+
|
|
3875
|
+
---
|
|
3876
|
+
|
|
3877
|
+
## REVIEW PASS 4 — Plugin System: Conflict detection
|
|
3878
|
+
|
|
3879
|
+
Read `plugin-schema.md` and `plugin-loader.md`.
|
|
3880
|
+
|
|
3881
|
+
- [ ] **Reserved command conflict detection**: The loader checks a hardcoded list of
|
|
3882
|
+
36 reserved command names. But this list will become stale as MindForge adds new
|
|
3883
|
+
commands in future versions. When a new built-in command is added, the plugin loader's
|
|
3884
|
+
reserved list must also be updated.
|
|
3885
|
+
Better approach: derive the reserved list dynamically at runtime:
|
|
3886
|
+
```bash
|
|
3887
|
+
RESERVED_NAMES=$(ls .claude/commands/mindforge/ 2>/dev/null | sed 's/\.md$//')
|
|
3888
|
+
```
|
|
3889
|
+
This always reflects the actual installed commands, not a hardcoded list.
|
|
3890
|
+
|
|
3891
|
+
- [ ] **Multiple plugins providing the same hook**: If `plugin-a` and `plugin-b` both
|
|
3892
|
+
declare `post_phase_complete` hooks, what is the execution order?
|
|
3893
|
+
The spec doesn't define this. Add: "Multiple plugins with the same hook:
|
|
3894
|
+
execute in PLUGINS-MANIFEST.md installation order (first installed, first executed).
|
|
3895
|
+
Each hook is independent — failure of one hook does not prevent others from running."
|
|
3896
|
+
|
|
3897
|
+
---
|
|
3898
|
+
|
|
3899
|
+
## REVIEW PASS 5 — Token Optimiser: Signal Accuracy
|
|
3900
|
+
|
|
3901
|
+
Read `token-optimiser.md`.
|
|
3902
|
+
|
|
3903
|
+
- [ ] **Estimates vs. actual measurement**: The token budget tracking writes to
|
|
3904
|
+
`token-usage.jsonl` with `token_estimates`. But these are estimates, not actual
|
|
3905
|
+
measured token counts. The spec doesn't explain how to compute `code_reading` tokens
|
|
3906
|
+
from file content.
|
|
3907
|
+
Add: "Estimate code_reading tokens as: `sum(file_sizes_read / 4)`.
|
|
3908
|
+
(Average 4 chars per token is a reasonable estimate for code.)
|
|
3909
|
+
Mark all values in token-usage.jsonl as estimates with `"measured": false`."
|
|
3910
|
+
|
|
3911
|
+
- [ ] **Efficiency formula definition of "useful output"**: The formula says:
|
|
3912
|
+
`useful_output_tokens = tokens in SUMMARY files + verified code changes + ADRs written`
|
|
3913
|
+
But how are "verified code changes" measured in tokens?
|
|
3914
|
+
Clarify: "Estimate verified code changes as: `sum(SUMMARY file sizes / 4)`.
|
|
3915
|
+
SUMMARY files contain the output description — this is a proxy for the value produced."
|
|
3916
|
+
|
|
3917
|
+
---
|
|
3918
|
+
|
|
3919
|
+
## REVIEW PASS 6 — E2E Tests: Coverage Gaps
|
|
3920
|
+
|
|
3921
|
+
Read `tests/e2e.test.js`.
|
|
3922
|
+
|
|
3923
|
+
- [ ] **Multi-developer scenario not tested**: The E2E tests cover single-developer
|
|
3924
|
+
workflows but not the multi-developer HANDOFF system (HANDOFF-[dev-id].json,
|
|
3925
|
+
active_developers field, file conflict detection). Add at minimum:
|
|
3926
|
+
"A test that simulates two developers working on different plans and verifies
|
|
3927
|
+
that their per-developer HANDOFF files are distinct and non-conflicting."
|
|
3928
|
+
|
|
3929
|
+
- [ ] **Context compaction scenario not tested**: The E2E tests don't test the
|
|
3930
|
+
compaction protocol (Level 2 structured extraction). Add:
|
|
3931
|
+
"A test that simulates a Level 2 compaction and verifies the HANDOFF.json
|
|
3932
|
+
gains decisions_made and implicit_knowledge fields."
|
|
3933
|
+
|
|
3934
|
+
- [ ] **Cleanup failure is too quiet**: The `catch(e) { console.warn(...) }` in cleanup
|
|
3935
|
+
is good — but the test still passes even if cleanup fails.
|
|
3936
|
+
Add: "Track cleanup failures in a global counter. Report at the end:
|
|
3937
|
+
'N test directories were not cleaned up: [paths]'"
|
|
3938
|
+
|
|
3939
|
+
---
|
|
3940
|
+
|
|
3941
|
+
## REVIEW PASS 7 — Production Checklist: Release Gate
|
|
3942
|
+
|
|
3943
|
+
Read `production-checklist.md`.
|
|
3944
|
+
|
|
3945
|
+
- [ ] **Missing version consistency checks**: The checklist does not verify that:
|
|
3946
|
+
- `package.json` version === v1.0.0
|
|
3947
|
+
- Git tag === `v1.0.0`
|
|
3948
|
+
- npm published version === v1.0.0
|
|
3949
|
+
- SDK `package.json` version matches root `package.json` version
|
|
3950
|
+
These are classic "wrong version shipped" bugs. Add items E09 and E10 expansions
|
|
3951
|
+
or add Section F (Release Artifacts, 5 items) to bring total to 55 and include:
|
|
3952
|
+
- F01: package.json version === target release version
|
|
3953
|
+
- F02: SDK package.json version matches root version
|
|
3954
|
+
- F03: Git tag v1.0.0 exists and points to the correct commit
|
|
3955
|
+
- F04: CHANGELOG.md has a complete entry for v1.0.0 with today's date
|
|
3956
|
+
- F05: `npm publish --dry-run` shows no unexpected files
|
|
3957
|
+
|
|
3958
|
+
---
|
|
3959
|
+
|
|
3960
|
+
## REVIEW SUMMARY TABLE
|
|
3961
|
+
|
|
3962
|
+
```
|
|
3963
|
+
## Day 7 Review Summary
|
|
3964
|
+
|
|
3965
|
+
| Category | BLOCKING | MAJOR | MINOR | SUGGESTION |
|
|
3966
|
+
|-----------------------|----------|-------|-------|------------|
|
|
3967
|
+
| Installer | | | | |
|
|
3968
|
+
| Self-Update | | | | |
|
|
3969
|
+
| Migration Engine | | | | |
|
|
3970
|
+
| Plugin System | | | | |
|
|
3971
|
+
| Token Optimiser | | | | |
|
|
3972
|
+
| E2E Tests | | | | |
|
|
3973
|
+
| Production Checklist | | | | |
|
|
3974
|
+
| **TOTAL** | | | | |
|
|
3975
|
+
|
|
3976
|
+
## Verdict
|
|
3977
|
+
[ ] ✅ APPROVED — Proceed to HARDEN section
|
|
3978
|
+
[ ] ⚠️ APPROVED WITH CONDITIONS — Fix MAJOR findings first
|
|
3979
|
+
[ ] ❌ NOT APPROVED — BLOCKING findings prevent v1.0.0
|
|
3980
|
+
```
|
|
3981
|
+
|
|
3982
|
+
---
|
|
3983
|
+
|
|
3984
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
3985
|
+
# PART 3 — HARDENING PROMPT
|
|
3986
|
+
# ═══════════════════════════════════════════════════════════════════
|
|
3987
|
+
|
|
3988
|
+
---
|
|
3989
|
+
|
|
3990
|
+
## DAY 7 HARDENING — Run after REVIEW is APPROVED
|
|
3991
|
+
|
|
3992
|
+
Confirm all review findings resolved:
|
|
3993
|
+
|
|
3994
|
+
```bash
|
|
3995
|
+
for suite in install wave-engine audit compaction skills-platform \
|
|
3996
|
+
integrations governance intelligence metrics \
|
|
3997
|
+
distribution ci-mode sdk production migration e2e; do
|
|
3998
|
+
printf " %-30s" "${suite}..."
|
|
3999
|
+
node tests/${suite}.test.js 2>&1 | tail -1
|
|
4000
|
+
done
|
|
4001
|
+
```
|
|
4002
|
+
|
|
4003
|
+
---
|
|
4004
|
+
|
|
4005
|
+
## HARDEN 1 — Fix installer glob patterns in excludePatterns
|
|
4006
|
+
|
|
4007
|
+
The review found `'*.key'` is a string, not a glob — it never matches.
|
|
4008
|
+
|
|
4009
|
+
Update `bin/installer-core.js`:
|
|
4010
|
+
|
|
4011
|
+
```javascript
|
|
4012
|
+
// Replace glob strings with correct regex equivalents:
|
|
4013
|
+
const SENSITIVE_EXCLUDE = [
|
|
4014
|
+
'.env', // exact filename match
|
|
4015
|
+
/^\.env\..*/, // .env.local, .env.production, etc.
|
|
4016
|
+
/\.key$/, // anything ending in .key (was '*.key' — broken)
|
|
4017
|
+
/\.pem$/, // anything ending in .pem (was '*.pem' — broken)
|
|
4018
|
+
'secrets', // exact directory name
|
|
4019
|
+
'.secrets', // exact directory name
|
|
4020
|
+
/^secrets$/, // exact match at directory level
|
|
4021
|
+
];
|
|
4022
|
+
```
|
|
4023
|
+
|
|
4024
|
+
Add a unit test to `tests/production.test.js`:
|
|
4025
|
+
|
|
4026
|
+
```javascript
|
|
4027
|
+
test('SENSITIVE_EXCLUDE properly excludes .env and .key files', () => {
|
|
4028
|
+
const SENSITIVE_EXCLUDE = [
|
|
4029
|
+
'.env', /^\.env\..*/, /\.key$/, /\.pem$/, 'secrets', /^secrets$/
|
|
4030
|
+
];
|
|
4031
|
+
const shouldExclude = (name) =>
|
|
4032
|
+
SENSITIVE_EXCLUDE.some(p => typeof p === 'string' ? p === name : p.test(name));
|
|
4033
|
+
|
|
4034
|
+
assert.ok(shouldExclude('.env'), '.env should be excluded');
|
|
4035
|
+
assert.ok(shouldExclude('.env.local'), '.env.local should be excluded');
|
|
4036
|
+
assert.ok(shouldExclude('private.key'), 'private.key should be excluded');
|
|
4037
|
+
assert.ok(shouldExclude('certificate.pem'), 'certificate.pem should be excluded');
|
|
4038
|
+
assert.ok(shouldExclude('secrets'), 'secrets directory should be excluded');
|
|
4039
|
+
assert.ok(!shouldExclude('package.json'), 'package.json should NOT be excluded');
|
|
4040
|
+
assert.ok(!shouldExclude('.mindforge'), '.mindforge should NOT be excluded');
|
|
4041
|
+
assert.ok(!shouldExclude('src'), 'src should NOT be excluded');
|
|
4042
|
+
});
|
|
4043
|
+
```
|
|
4044
|
+
|
|
4045
|
+
**Commit:**
|
|
4046
|
+
```bash
|
|
4047
|
+
git add bin/installer-core.js tests/production.test.js
|
|
4048
|
+
git commit -m "harden(installer): fix SENSITIVE_EXCLUDE glob patterns — use regex, not glob strings"
|
|
4049
|
+
```
|
|
4050
|
+
|
|
4051
|
+
---
|
|
4052
|
+
|
|
4053
|
+
## HARDEN 2 — Fix migration chain filter logic
|
|
4054
|
+
|
|
4055
|
+
The review found the filter breaks for intermediate versions (e.g., v0.3.0).
|
|
4056
|
+
|
|
4057
|
+
Update `bin/migrations/migrate.js` — replace the getMigrationsToRun filter:
|
|
4058
|
+
|
|
4059
|
+
```javascript
|
|
4060
|
+
function getMigrationsToRun(fromVersion, toVersion) {
|
|
4061
|
+
const allMigrations = [
|
|
4062
|
+
require('./0.1.0-to-0.5.0'),
|
|
4063
|
+
require('./0.5.0-to-0.6.0'),
|
|
4064
|
+
require('./0.6.0-to-1.0.0'),
|
|
4065
|
+
];
|
|
4066
|
+
|
|
4067
|
+
// A migration should run if its DESTINATION VERSION falls within the range:
|
|
4068
|
+
// (fromVersion, toVersion] — i.e., greater than fromVersion AND at most toVersion
|
|
4069
|
+
//
|
|
4070
|
+
// Example: upgrading 0.3.0 → 1.0.0:
|
|
4071
|
+
// - 0.1.0→0.5.0: toVersion=0.5.0 > fromVersion=0.3.0 ✅ run this
|
|
4072
|
+
// - 0.5.0→0.6.0: toVersion=0.6.0 > fromVersion=0.3.0 ✅ run this
|
|
4073
|
+
// - 0.6.0→1.0.0: toVersion=1.0.0 > fromVersion=0.3.0 ✅ run this
|
|
4074
|
+
//
|
|
4075
|
+
// Example: upgrading 0.6.0 → 1.0.0:
|
|
4076
|
+
// - 0.1.0→0.5.0: toVersion=0.5.0 > fromVersion=0.6.0? ❌ skip
|
|
4077
|
+
// - 0.5.0→0.6.0: toVersion=0.6.0 > fromVersion=0.6.0? ❌ skip (equal, not greater)
|
|
4078
|
+
// - 0.6.0→1.0.0: toVersion=1.0.0 > fromVersion=0.6.0? ✅ run this
|
|
4079
|
+
|
|
4080
|
+
return allMigrations.filter(m =>
|
|
4081
|
+
compareSemver(m.toVersion, fromVersion) > 0 && // migration destination > current
|
|
4082
|
+
compareSemver(m.toVersion, toVersion) <= 0 // migration destination ≤ target
|
|
4083
|
+
);
|
|
4084
|
+
}
|
|
4085
|
+
```
|
|
4086
|
+
|
|
4087
|
+
Add migration chain tests to `tests/migration.test.js`:
|
|
4088
|
+
|
|
4089
|
+
```javascript
|
|
4090
|
+
test('migration chain for v0.3.0 → v1.0.0 includes ALL 3 migrations', () => {
|
|
4091
|
+
// Simulate the filter logic
|
|
4092
|
+
const { compareSemver } = require('../bin/updater/version-comparator');
|
|
4093
|
+
const fromVersion = '0.3.0';
|
|
4094
|
+
const toVersion = '1.0.0';
|
|
4095
|
+
|
|
4096
|
+
const migrations = [
|
|
4097
|
+
{ fromVersion: '0.1.0', toVersion: '0.5.0' },
|
|
4098
|
+
{ fromVersion: '0.5.0', toVersion: '0.6.0' },
|
|
4099
|
+
{ fromVersion: '0.6.0', toVersion: '1.0.0' },
|
|
4100
|
+
].filter(m =>
|
|
4101
|
+
compareSemver(m.toVersion, fromVersion) > 0 &&
|
|
4102
|
+
compareSemver(m.toVersion, toVersion) <= 0
|
|
4103
|
+
);
|
|
4104
|
+
|
|
4105
|
+
assert.strictEqual(migrations.length, 3,
|
|
4106
|
+
`Expected 3 migrations for 0.3.0→1.0.0, got ${migrations.length}`);
|
|
4107
|
+
});
|
|
4108
|
+
|
|
4109
|
+
test('migration chain for v0.6.0 → v1.0.0 includes only 1 migration', () => {
|
|
4110
|
+
const { compareSemver } = require('../bin/updater/version-comparator');
|
|
4111
|
+
const fromVersion = '0.6.0';
|
|
4112
|
+
const toVersion = '1.0.0';
|
|
4113
|
+
|
|
4114
|
+
const migrations = [
|
|
4115
|
+
{ fromVersion: '0.1.0', toVersion: '0.5.0' },
|
|
4116
|
+
{ fromVersion: '0.5.0', toVersion: '0.6.0' },
|
|
4117
|
+
{ fromVersion: '0.6.0', toVersion: '1.0.0' },
|
|
4118
|
+
].filter(m =>
|
|
4119
|
+
compareSemver(m.toVersion, fromVersion) > 0 &&
|
|
4120
|
+
compareSemver(m.toVersion, toVersion) <= 0
|
|
4121
|
+
);
|
|
4122
|
+
|
|
4123
|
+
assert.strictEqual(migrations.length, 1,
|
|
4124
|
+
`Expected 1 migration for 0.6.0→1.0.0, got ${migrations.length}: ${migrations.map(m=>m.toVersion)}`);
|
|
4125
|
+
assert.strictEqual(migrations[0].toVersion, '1.0.0');
|
|
4126
|
+
});
|
|
4127
|
+
|
|
4128
|
+
test('migration chain for same version returns 0 migrations', () => {
|
|
4129
|
+
const { compareSemver } = require('../bin/updater/version-comparator');
|
|
4130
|
+
const fromVersion = '1.0.0';
|
|
4131
|
+
const toVersion = '1.0.0';
|
|
4132
|
+
|
|
4133
|
+
const migrations = [
|
|
4134
|
+
{ fromVersion: '0.1.0', toVersion: '0.5.0' },
|
|
4135
|
+
{ fromVersion: '0.5.0', toVersion: '0.6.0' },
|
|
4136
|
+
{ fromVersion: '0.6.0', toVersion: '1.0.0' },
|
|
4137
|
+
].filter(m =>
|
|
4138
|
+
compareSemver(m.toVersion, fromVersion) > 0 &&
|
|
4139
|
+
compareSemver(m.toVersion, toVersion) <= 0
|
|
4140
|
+
);
|
|
4141
|
+
|
|
4142
|
+
assert.strictEqual(migrations.length, 0, 'No migrations needed for same version');
|
|
4143
|
+
});
|
|
4144
|
+
```
|
|
4145
|
+
|
|
4146
|
+
**Commit:**
|
|
4147
|
+
```bash
|
|
4148
|
+
git add bin/migrations/migrate.js tests/migration.test.js
|
|
4149
|
+
git commit -m "harden(migration): fix migration chain filter — use toVersion range check, not fromVersion"
|
|
4150
|
+
```
|
|
4151
|
+
|
|
4152
|
+
---
|
|
4153
|
+
|
|
4154
|
+
## HARDEN 3 — Add CI backup auto-deletion and dynamic reserved names
|
|
4155
|
+
|
|
4156
|
+
### Fix CI migration backup cleanup in `migrate.js`
|
|
4157
|
+
|
|
4158
|
+
```javascript
|
|
4159
|
+
// ── Post-migration: clean up in CI, retain in interactive ─────────────────
|
|
4160
|
+
if (process.env.CI === 'true') {
|
|
4161
|
+
try {
|
|
4162
|
+
fs.rmSync(backupDir, { recursive: true, force: true });
|
|
4163
|
+
console.log(` 🗑️ CI mode: backup auto-deleted (disk space)`);
|
|
4164
|
+
} catch {
|
|
4165
|
+
// Silent failure on cleanup — migration succeeded, cleanup is optional
|
|
4166
|
+
}
|
|
4167
|
+
} else {
|
|
4168
|
+
console.log(` Backup retained: .planning/${path.basename(backupDir)}`);
|
|
4169
|
+
console.log(` Remove when satisfied: rm -rf .planning/${path.basename(backupDir)}`);
|
|
4170
|
+
}
|
|
4171
|
+
```
|
|
4172
|
+
|
|
4173
|
+
### Fix plugin loader to use dynamic reserved names
|
|
4174
|
+
|
|
4175
|
+
Update `plugin-loader.md`:
|
|
4176
|
+
|
|
4177
|
+
```markdown
|
|
4178
|
+
### Dynamic reserved command name detection
|
|
4179
|
+
|
|
4180
|
+
Instead of a hardcoded list, detect reserved names at plugin install time:
|
|
4181
|
+
|
|
4182
|
+
```bash
|
|
4183
|
+
# Detect currently installed built-in command names
|
|
4184
|
+
get_reserved_command_names() {
|
|
4185
|
+
ls ".claude/commands/mindforge/"*.md 2>/dev/null | \
|
|
4186
|
+
xargs -I{} basename {} .md | \
|
|
4187
|
+
sort
|
|
4188
|
+
}
|
|
4189
|
+
|
|
4190
|
+
RESERVED_NAMES=$(get_reserved_command_names)
|
|
4191
|
+
```
|
|
4192
|
+
|
|
4193
|
+
For a plugin command that matches a reserved name:
|
|
4194
|
+
```bash
|
|
4195
|
+
# Check conflict
|
|
4196
|
+
CMD_NAME="health"
|
|
4197
|
+
PLUGIN_NAME="my-plugin"
|
|
4198
|
+
|
|
4199
|
+
if echo "${RESERVED_NAMES}" | grep -q "^${CMD_NAME}$"; then
|
|
4200
|
+
FINAL_NAME="${PLUGIN_NAME}-${CMD_NAME}"
|
|
4201
|
+
echo " ⚠️ Command '${CMD_NAME}' conflicts with built-in — renaming to '${FINAL_NAME}'"
|
|
4202
|
+
else
|
|
4203
|
+
FINAL_NAME="${CMD_NAME}"
|
|
4204
|
+
fi
|
|
4205
|
+
```
|
|
4206
|
+
|
|
4207
|
+
This approach always reflects actual installed commands rather than a hardcoded list
|
|
4208
|
+
that becomes stale with new MindForge releases.
|
|
4209
|
+
```
|
|
4210
|
+
|
|
4211
|
+
**Commit:**
|
|
4212
|
+
```bash
|
|
4213
|
+
git add bin/migrations/migrate.js .mindforge/plugins/plugin-loader.md
|
|
4214
|
+
git commit -m "harden(migration,plugins): CI backup auto-delete, dynamic reserved command detection"
|
|
4215
|
+
```
|
|
4216
|
+
|
|
4217
|
+
---
|
|
4218
|
+
|
|
4219
|
+
## HARDEN 4 — Write the final 3 ADRs
|
|
4220
|
+
|
|
4221
|
+
### `.planning/decisions/ADR-018-installer-self-install-detection.md`
|
|
4222
|
+
|
|
4223
|
+
```markdown
|
|
4224
|
+
# ADR-018: Installer detects and handles self-install scenario
|
|
4225
|
+
|
|
4226
|
+
**Status:** Accepted | **Date:** v1.0.0 | **Day:** 7
|
|
4227
|
+
|
|
4228
|
+
## Context
|
|
4229
|
+
Running `npx mindforge-cc --claude --local` inside the MindForge repo itself
|
|
4230
|
+
would copy `.mindforge/` to `.mindforge/` (source = destination).
|
|
4231
|
+
|
|
4232
|
+
## Decision
|
|
4233
|
+
Detect self-install by checking `package.json.name === 'mindforge-cc'`.
|
|
4234
|
+
If self-install: skip framework file copies. Only install commands.
|
|
4235
|
+
|
|
4236
|
+
## Rationale
|
|
4237
|
+
Core team runs the installer locally for testing frequently.
|
|
4238
|
+
Silent no-op with a clear warning is better than a cryptic error or accidental self-overwrite.
|
|
4239
|
+
```
|
|
4240
|
+
|
|
4241
|
+
### `.planning/decisions/ADR-019-self-update-scope-preservation.md`
|
|
4242
|
+
|
|
4243
|
+
```markdown
|
|
4244
|
+
# ADR-019: Self-update preserves the original installation scope
|
|
4245
|
+
|
|
4246
|
+
**Status:** Accepted | **Date:** v1.0.0 | **Day:** 7
|
|
4247
|
+
|
|
4248
|
+
## Context
|
|
4249
|
+
`/mindforge:update --apply` must update the correct installation.
|
|
4250
|
+
|
|
4251
|
+
## Decision
|
|
4252
|
+
Detect original scope from filesystem (local before global per priority).
|
|
4253
|
+
Apply update using the detected scope. Per ADR-019.
|
|
4254
|
+
|
|
4255
|
+
## Rationale
|
|
4256
|
+
Principle of least surprise. A local install user should get a local update.
|
|
4257
|
+
Unexpected global install is confusing and may affect other projects.
|
|
4258
|
+
```
|
|
4259
|
+
|
|
4260
|
+
### `.planning/decisions/ADR-020-v1.0.0-stable-interface-contract.md`
|
|
4261
|
+
|
|
4262
|
+
```markdown
|
|
4263
|
+
# ADR-020: v1.0.0 stable interface contract
|
|
4264
|
+
|
|
4265
|
+
**Status:** Accepted | **Date:** v1.0.0 | **Day:** 7
|
|
4266
|
+
|
|
4267
|
+
## Context
|
|
4268
|
+
MindForge reaches v1.0.0. "Stable" must be precisely defined.
|
|
4269
|
+
|
|
4270
|
+
## Decision
|
|
4271
|
+
Stable public interfaces (additions require MINOR, removals/changes require MAJOR):
|
|
4272
|
+
- All 36 command names and their flag interfaces
|
|
4273
|
+
- HANDOFF.json schema fields
|
|
4274
|
+
- AUDIT.jsonl event types and required fields
|
|
4275
|
+
- All 10 core skill name values
|
|
4276
|
+
- MINDFORGE.md setting keys
|
|
4277
|
+
- @mindforge/sdk exported types and functions
|
|
4278
|
+
- plugin.json manifest format
|
|
4279
|
+
|
|
4280
|
+
Governance primitives are permanently fixed and cannot become configurable
|
|
4281
|
+
in any future version without a MAJOR bump and explicit RFC process.
|
|
4282
|
+
|
|
4283
|
+
## Consequences
|
|
4284
|
+
Plugin authors and SDK consumers can build on v1.0.0 with confidence.
|
|
4285
|
+
The MindForge team is committed to backwards compatibility in 1.x.x releases.
|
|
4286
|
+
```
|
|
4287
|
+
|
|
4288
|
+
**Commit:**
|
|
4289
|
+
```bash
|
|
4290
|
+
git add .planning/decisions/
|
|
4291
|
+
git commit -m "docs(adr): add ADR-018, ADR-019, ADR-020 — complete 20 ADRs for v1.0.0"
|
|
4292
|
+
```
|
|
4293
|
+
|
|
4294
|
+
---
|
|
4295
|
+
|
|
4296
|
+
## HARDEN 5 — Final CHANGELOG.md v1.0.0 entry
|
|
4297
|
+
|
|
4298
|
+
```markdown
|
|
4299
|
+
## [1.0.0] — v1.0.0 First Stable Public Release — 2026-03-22
|
|
4300
|
+
|
|
4301
|
+
🎉 **MindForge v1.0.0 — Enterprise Agentic Framework — First Stable Release**
|
|
4302
|
+
|
|
4303
|
+
Built over 7 focused sprints, MindForge transforms Claude Code and Antigravity
|
|
4304
|
+
from powerful-but-unstructured AI tools into production-grade engineering
|
|
4305
|
+
partners with full governance, observability, and enterprise integration.
|
|
4306
|
+
|
|
4307
|
+
### What ships in v1.0.0
|
|
4308
|
+
|
|
4309
|
+
**36 commands** across 7 workflow categories
|
|
4310
|
+
**10 core skill packs** with three-tier registry (Core/Org/Project)
|
|
4311
|
+
**8 specialised agent personas** covering all engineering roles
|
|
4312
|
+
**Wave-based parallel execution** with dependency graph and automatic compaction
|
|
4313
|
+
**Enterprise integrations**: Jira, Confluence, Slack, GitHub, GitLab
|
|
4314
|
+
**Three-tier governance**: Tier 1 (auto) / Tier 2 (peer review) / Tier 3 (compliance)
|
|
4315
|
+
**Five non-bypassable compliance gates** (secret detection, CRITICAL findings, tests, CVEs, GDPR)
|
|
4316
|
+
**Intelligence layer**: health engine, difficulty scorer, anti-pattern detector, team profiling
|
|
4317
|
+
**Public skills registry**: npm-based `mindforge-skill-*` ecosystem
|
|
4318
|
+
**CI/CD integration**: GitHub Actions, GitLab CI, Jenkins adapters
|
|
4319
|
+
**@mindforge/sdk**: TypeScript SDK with client, event stream, and command builders
|
|
4320
|
+
**Monorepo support**: npm/pnpm/Nx/Turborepo/Lerna workspace detection
|
|
4321
|
+
**AI PR Review**: Claude API-powered code review with context loading
|
|
4322
|
+
**Self-update mechanism**: version check, changelog diff, scope-preserving apply
|
|
4323
|
+
**Version migration engine**: schema migration from v0.1.0 through v1.0.0
|
|
4324
|
+
**Plugin system**: extensible via `mindforge-plugin-*` npm namespace
|
|
4325
|
+
**Token usage optimiser**: profiling and efficiency strategies
|
|
4326
|
+
**50-point production readiness checklist**: fully verified before this release
|
|
4327
|
+
|
|
4328
|
+
**20 Architecture Decision Records** documenting every major design choice
|
|
4329
|
+
**15 test suites** with 3× consecutive run requirement
|
|
4330
|
+
**Complete reference documentation**: commands, security, ADR index, threat model
|
|
4331
|
+
|
|
4332
|
+
### Stable interface contract
|
|
4333
|
+
See ADR-020. All 36 commands, HANDOFF.json schema, AUDIT event types,
|
|
4334
|
+
@mindforge/sdk exports, and plugin.json format are stable in 1.x.x.
|
|
4335
|
+
|
|
4336
|
+
### Breaking changes from 0.6.0
|
|
4337
|
+
- VERIFY_PASS_RATE_WARNING_THRESHOLD in MINDFORGE.md is now 0.0-1.0 (was 0-100)
|
|
4338
|
+
Run `/mindforge:migrate` to auto-convert
|
|
4339
|
+
- AUDIT.jsonl session_id field is now required (auto-backfilled by migration)
|
|
4340
|
+
- HANDOFF.json plugin_api_version field required for plugin compatibility
|
|
4341
|
+
|
|
4342
|
+
### Installation
|
|
4343
|
+
```bash
|
|
4344
|
+
npx mindforge-cc@latest
|
|
4345
|
+
# or
|
|
4346
|
+
npx mindforge-cc@1.0.0 --claude --global
|
|
4347
|
+
```
|
|
4348
|
+
```
|
|
4349
|
+
|
|
4350
|
+
**Commit:**
|
|
4351
|
+
```bash
|
|
4352
|
+
git add CHANGELOG.md package.json
|
|
4353
|
+
git commit -m "chore(release): v1.0.0 final CHANGELOG and version bump"
|
|
4354
|
+
```
|
|
4355
|
+
|
|
4356
|
+
---
|
|
4357
|
+
|
|
4358
|
+
## HARDEN 6 — Final hardening test additions
|
|
4359
|
+
|
|
4360
|
+
Add to relevant test suites:
|
|
4361
|
+
|
|
4362
|
+
```javascript
|
|
4363
|
+
// In tests/production.test.js — add after existing tests:
|
|
4364
|
+
console.log('\nHardening tests:');
|
|
4365
|
+
|
|
4366
|
+
test('SENSITIVE_EXCLUDE uses regex for .key and .pem (not glob strings)', () => {
|
|
4367
|
+
const c = fs.readFileSync('bin/installer-core.js', 'utf8');
|
|
4368
|
+
// Should use regex pattern /\.key$/ not string '*.key'
|
|
4369
|
+
assert.ok(!c.includes("'*.key'"), 'Should not use glob string for .key');
|
|
4370
|
+
assert.ok(!c.includes("'*.pem'"), 'Should not use glob string for .pem');
|
|
4371
|
+
assert.ok(c.includes('/\\.key$/') || c.includes('/.key$/'), 'Should use regex for .key');
|
|
4372
|
+
});
|
|
4373
|
+
|
|
4374
|
+
test('migration filter uses toVersion range check (not fromVersion)', () => {
|
|
4375
|
+
const c = fs.readFileSync('bin/migrations/migrate.js', 'utf8');
|
|
4376
|
+
// The correct filter uses compareSemver(m.toVersion, fromVersion) > 0
|
|
4377
|
+
assert.ok(
|
|
4378
|
+
c.includes('m.toVersion') && c.includes('> 0'),
|
|
4379
|
+
'Should use toVersion range check for migration filter'
|
|
4380
|
+
);
|
|
4381
|
+
});
|
|
4382
|
+
|
|
4383
|
+
test('migration has CI auto-delete of backup', () => {
|
|
4384
|
+
const c = fs.readFileSync('bin/migrations/migrate.js', 'utf8');
|
|
4385
|
+
assert.ok(
|
|
4386
|
+
c.includes('CI') && (c.includes('auto-deleted') || c.includes('rmSync')),
|
|
4387
|
+
'Should auto-delete backup in CI mode'
|
|
4388
|
+
);
|
|
4389
|
+
});
|
|
4390
|
+
|
|
4391
|
+
// In tests/migration.test.js — add after existing tests:
|
|
4392
|
+
test('MINDFORGE.md value 1.0 (explicit decimal) is not converted', () => {
|
|
4393
|
+
const content = 'VERIFY_PASS_RATE_WARNING_THRESHOLD=1.0\n';
|
|
4394
|
+
const migrated = simulateMindforgeMdMigration(content);
|
|
4395
|
+
assert.ok(migrated.includes('=1.0'), 'Should preserve 1.0 format without conversion');
|
|
4396
|
+
});
|
|
4397
|
+
```
|
|
4398
|
+
|
|
4399
|
+
**Commit:**
|
|
4400
|
+
```bash
|
|
4401
|
+
git add tests/production.test.js tests/migration.test.js
|
|
4402
|
+
git commit -m "test(day7): add hardening verification tests"
|
|
4403
|
+
```
|
|
4404
|
+
|
|
4405
|
+
---
|
|
4406
|
+
|
|
4407
|
+
## HARDEN 7 — Pre-release final verification and tag
|
|
4408
|
+
|
|
4409
|
+
```bash
|
|
4410
|
+
#!/usr/bin/env bash
|
|
4411
|
+
# MindForge v1.0.0 Final Pre-Release Verification
|
|
4412
|
+
|
|
4413
|
+
echo ""
|
|
4414
|
+
echo "⚡ MindForge v1.0.0 Pre-Release Verification"
|
|
4415
|
+
echo "═══════════════════════════════════════════"
|
|
4416
|
+
|
|
4417
|
+
PASS=true
|
|
4418
|
+
|
|
4419
|
+
# 1. Version check
|
|
4420
|
+
PKGVER=$(node -e "console.log(require('./package.json').version)")
|
|
4421
|
+
[ "${PKGVER}" = "1.0.0" ] || { echo "❌ package.json version = ${PKGVER} (expected 1.0.0)"; PASS=false; }
|
|
4422
|
+
echo " package.json version: ${PKGVER} ✅"
|
|
4423
|
+
|
|
4424
|
+
# 2. CHANGELOG entry
|
|
4425
|
+
grep -q "1.0.0" CHANGELOG.md && echo " CHANGELOG.md: v1.0.0 entry ✅" || { echo "❌ Missing CHANGELOG v1.0.0"; PASS=false; }
|
|
4426
|
+
|
|
4427
|
+
# 3. ADR count
|
|
4428
|
+
ADR_COUNT=$(ls .planning/decisions/ADR-*.md 2>/dev/null | wc -l | tr -d ' ')
|
|
4429
|
+
[ "${ADR_COUNT}" -ge 20 ] && echo " ADRs: ${ADR_COUNT} ✅" || { echo "❌ Only ${ADR_COUNT} ADRs (need 20)"; PASS=false; }
|
|
4430
|
+
|
|
4431
|
+
# 4. Command count
|
|
4432
|
+
CMD_COUNT=$(ls .claude/commands/mindforge/*.md 2>/dev/null | wc -l | tr -d ' ')
|
|
4433
|
+
[ "${CMD_COUNT}" -ge 36 ] && echo " Commands: ${CMD_COUNT} ✅" || { echo "❌ Only ${CMD_COUNT} commands (need 36)"; PASS=false; }
|
|
4434
|
+
|
|
4435
|
+
# 5. Command parity
|
|
4436
|
+
diff <(ls .claude/commands/mindforge/ | sort) <(ls .agent/mindforge/ | sort) > /dev/null 2>&1 \
|
|
4437
|
+
&& echo " Command parity: ✅" \
|
|
4438
|
+
|| { echo "❌ Command mismatch between runtimes"; PASS=false; }
|
|
4439
|
+
|
|
4440
|
+
# 6. No secrets
|
|
4441
|
+
SECRETS=$(grep -rE "(password|api_key|token)\s*=\s*['\"][^'\"]{8,}" \
|
|
4442
|
+
--include="*.md" --include="*.js" --include="*.json" --include="*.ts" \
|
|
4443
|
+
--exclude-dir=node_modules --exclude-dir=.git . 2>/dev/null | \
|
|
4444
|
+
grep -v "placeholder\|example\|your-\|TEST_ONLY\|comment" || true)
|
|
4445
|
+
[ -z "${SECRETS}" ] && echo " Secret scan: clean ✅" || { echo "❌ Potential credentials detected"; echo "${SECRETS}"; PASS=false; }
|
|
4446
|
+
|
|
4447
|
+
# 7. All 15 test suites × 3 runs
|
|
4448
|
+
SUITES=(install wave-engine audit compaction skills-platform
|
|
4449
|
+
integrations governance intelligence metrics
|
|
4450
|
+
distribution ci-mode sdk production migration e2e)
|
|
4451
|
+
FAIL_COUNT=0
|
|
4452
|
+
for RUN in 1 2 3; do
|
|
4453
|
+
for SUITE in "${SUITES[@]}"; do
|
|
4454
|
+
node tests/${SUITE}.test.js 2>&1 | grep -q "All.*passed" || { ((FAIL_COUNT++)); }
|
|
4455
|
+
done
|
|
4456
|
+
done
|
|
4457
|
+
[ "${FAIL_COUNT}" -eq 0 ] && echo " Tests: 15 suites × 3 runs ✅" || { echo "❌ ${FAIL_COUNT} test failures"; PASS=false; }
|
|
4458
|
+
|
|
4459
|
+
echo ""
|
|
4460
|
+
if "${PASS}"; then
|
|
4461
|
+
echo "✅ ALL PRE-RELEASE CHECKS PASSED"
|
|
4462
|
+
echo ""
|
|
4463
|
+
echo "Release commands:"
|
|
4464
|
+
echo " git tag -a v1.0.0 -m 'MindForge v1.0.0 — First stable release'"
|
|
4465
|
+
echo " git push origin v1.0.0"
|
|
4466
|
+
echo " npm publish --access public"
|
|
4467
|
+
echo " gh release create v1.0.0 --title 'MindForge v1.0.0' --notes-file CHANGELOG.md"
|
|
4468
|
+
else
|
|
4469
|
+
echo "❌ PRE-RELEASE CHECKS FAILED — do not release"
|
|
4470
|
+
exit 1
|
|
4471
|
+
fi
|
|
4472
|
+
```
|
|
4473
|
+
|
|
4474
|
+
**Final commit and push:**
|
|
4475
|
+
|
|
4476
|
+
```bash
|
|
4477
|
+
git add .
|
|
4478
|
+
git commit -m "harden(day7): complete all production hardening — v1.0.0 release ready"
|
|
4479
|
+
git push origin feat/mindforge-production-release
|
|
4480
|
+
|
|
4481
|
+
# After PR review, merge, and all checks pass:
|
|
4482
|
+
git checkout main && git pull
|
|
4483
|
+
git tag -a v1.0.0 -m "MindForge v1.0.0 — Enterprise Agentic Framework, First Stable Release"
|
|
4484
|
+
git push origin v1.0.0
|
|
4485
|
+
npm publish --access public
|
|
4486
|
+
```
|
|
4487
|
+
|
|
4488
|
+
---
|
|
4489
|
+
|
|
4490
|
+
## MINDFORGE v1.0.0 — THE COMPLETE BUILD SUMMARY
|
|
4491
|
+
|
|
4492
|
+
| Day | Branch | Core delivery | Commands |
|
|
4493
|
+
|---|---|---|---|
|
|
4494
|
+
| **1** | `feat/mindforge-core-scaffold` | Foundation: personas, skills, commands, state management | 6 |
|
|
4495
|
+
| **2** | `feat/mindforge-wave-engine` | Wave execution, compaction protocol, AUDIT pipeline | +4 = 10 |
|
|
4496
|
+
| **3** | `feat/mindforge-skills-platform` | Skills registry (3 tiers), 5 new skills, 5 commands | +5 = 15 |
|
|
4497
|
+
| **4** | `feat/mindforge-enterprise-integrations` | Jira/Confluence/Slack/GitHub, governance (Tier 1/2/3) | +6 = 21 |
|
|
4498
|
+
| **5** | `feat/mindforge-intelligence-layer` | Health engine, difficulty scorer, anti-patterns, metrics | +4 = 25 |
|
|
4499
|
+
| **6** | `feat/mindforge-distribution-platform` | npm registry, CI/CD, SDK, monorepo, AI PR review | +6 = 31 |
|
|
4500
|
+
| **7** | `feat/mindforge-production-release` | Complete installer, self-update, migration, plugins, tokens | +5 = **36** |
|
|
4501
|
+
|
|
4502
|
+
### Final state: MindForge v1.0.0
|
|
4503
|
+
|
|
4504
|
+
```
|
|
4505
|
+
36 commands 10 skills 8 personas
|
|
4506
|
+
20 ADRs 15 test suites 50-point checklist ✅
|
|
4507
|
+
```
|
|
4508
|
+
|
|
4509
|
+
```bash
|
|
4510
|
+
npx mindforge-cc@latest
|
|
4511
|
+
# ⚡ MindForge — Enterprise Agentic Framework
|
|
4512
|
+
# The best agentic framework. Now shipped.
|
|
4513
|
+
```
|