mindforge-cc 2.0.0-alpha.9 → 2.1.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.
Files changed (157) hide show
  1. package/.agent/CLAUDE.md +26 -0
  2. package/.agent/mindforge/add-backlog.md +32 -0
  3. package/.agent/mindforge/agent.md +31 -0
  4. package/.agent/mindforge/do.md +31 -0
  5. package/.agent/mindforge/execute-phase.md +23 -0
  6. package/.agent/mindforge/install-skill.md +20 -11
  7. package/.agent/mindforge/learn.md +142 -0
  8. package/.agent/mindforge/marketplace.md +120 -0
  9. package/.agent/mindforge/new-runtime.md +19 -0
  10. package/.agent/mindforge/note.md +35 -0
  11. package/.agent/mindforge/plant-seed.md +31 -0
  12. package/.agent/mindforge/remember.md +16 -4
  13. package/.agent/mindforge/review-backlog.md +34 -0
  14. package/.agent/mindforge/session-report.md +39 -0
  15. package/.agent/mindforge/ui-phase.md +34 -0
  16. package/.agent/mindforge/ui-review.md +36 -0
  17. package/.agent/mindforge/validate-phase.md +31 -0
  18. package/.agent/mindforge/workstreams.md +35 -0
  19. package/.claude/CLAUDE.md +26 -0
  20. package/.claude/commands/mindforge/add-backlog.md +32 -0
  21. package/.claude/commands/mindforge/agent.md +31 -0
  22. package/.claude/commands/mindforge/approve.md +27 -15
  23. package/.claude/commands/mindforge/audit.md +30 -26
  24. package/.claude/commands/mindforge/auto.md +29 -18
  25. package/.claude/commands/mindforge/benchmark.md +26 -29
  26. package/.claude/commands/mindforge/browse.md +24 -22
  27. package/.claude/commands/mindforge/complete-milestone.md +28 -14
  28. package/.claude/commands/mindforge/costs.md +26 -9
  29. package/.claude/commands/mindforge/cross-review.md +27 -13
  30. package/.claude/commands/mindforge/dashboard.md +35 -98
  31. package/.claude/commands/mindforge/debug.md +34 -126
  32. package/.claude/commands/mindforge/discuss-phase.md +36 -138
  33. package/.claude/commands/mindforge/do.md +31 -0
  34. package/.claude/commands/mindforge/execute-phase.md +37 -167
  35. package/.claude/commands/mindforge/health.md +27 -17
  36. package/.claude/commands/mindforge/help.md +25 -19
  37. package/.claude/commands/mindforge/init-org.md +37 -131
  38. package/.claude/commands/mindforge/init-project.md +40 -155
  39. package/.claude/commands/mindforge/install-skill.md +32 -15
  40. package/.claude/commands/mindforge/learn.md +36 -0
  41. package/.claude/commands/mindforge/map-codebase.md +36 -298
  42. package/.claude/commands/mindforge/marketplace.md +33 -0
  43. package/.claude/commands/mindforge/metrics.md +29 -18
  44. package/.claude/commands/mindforge/migrate.md +33 -40
  45. package/.claude/commands/mindforge/milestone.md +35 -12
  46. package/.claude/commands/mindforge/new-runtime.md +29 -0
  47. package/.claude/commands/mindforge/next.md +34 -105
  48. package/.claude/commands/mindforge/note.md +35 -0
  49. package/.claude/commands/mindforge/plan-phase.md +34 -125
  50. package/.claude/commands/mindforge/plant-seed.md +31 -0
  51. package/.claude/commands/mindforge/plugins.md +30 -36
  52. package/.claude/commands/mindforge/pr-review.md +32 -41
  53. package/.claude/commands/mindforge/profile-team.md +26 -19
  54. package/.claude/commands/mindforge/publish-skill.md +28 -17
  55. package/.claude/commands/mindforge/qa.md +27 -12
  56. package/.claude/commands/mindforge/quick.md +35 -135
  57. package/.claude/commands/mindforge/release.md +27 -8
  58. package/.claude/commands/mindforge/remember.md +25 -10
  59. package/.claude/commands/mindforge/research.md +27 -9
  60. package/.claude/commands/mindforge/retrospective.md +28 -22
  61. package/.claude/commands/mindforge/review-backlog.md +34 -0
  62. package/.claude/commands/mindforge/review.md +37 -157
  63. package/.claude/commands/mindforge/security-scan.md +34 -233
  64. package/.claude/commands/mindforge/session-report.md +39 -0
  65. package/.claude/commands/mindforge/ship.md +34 -100
  66. package/.claude/commands/mindforge/skills.md +36 -141
  67. package/.claude/commands/mindforge/status.md +30 -104
  68. package/.claude/commands/mindforge/steer.md +25 -10
  69. package/.claude/commands/mindforge/sync-confluence.md +28 -9
  70. package/.claude/commands/mindforge/sync-jira.md +32 -12
  71. package/.claude/commands/mindforge/tokens.md +25 -6
  72. package/.claude/commands/mindforge/ui-phase.md +34 -0
  73. package/.claude/commands/mindforge/ui-review.md +36 -0
  74. package/.claude/commands/mindforge/update.md +33 -42
  75. package/.claude/commands/mindforge/validate-phase.md +31 -0
  76. package/.claude/commands/mindforge/verify-phase.md +30 -62
  77. package/.claude/commands/mindforge/workspace.md +28 -25
  78. package/.claude/commands/mindforge/workstreams.md +35 -0
  79. package/.mindforge/distribution/marketplace.md +53 -0
  80. package/.mindforge/org/skills/MANIFEST.md +1 -0
  81. package/.mindforge/personas/advisor-researcher.md +89 -0
  82. package/.mindforge/personas/analyst.md +112 -52
  83. package/.mindforge/personas/architect.md +100 -67
  84. package/.mindforge/personas/assumptions-analyzer-extend.md +87 -0
  85. package/.mindforge/personas/assumptions-analyzer.md +109 -0
  86. package/.mindforge/personas/codebase-mapper-extend.md +93 -0
  87. package/.mindforge/personas/codebase-mapper.md +770 -0
  88. package/.mindforge/personas/coverage-specialist.md +104 -0
  89. package/.mindforge/personas/debug-specialist.md +118 -52
  90. package/.mindforge/personas/debugger.md +97 -0
  91. package/.mindforge/personas/decision-architect.md +102 -0
  92. package/.mindforge/personas/developer.md +97 -85
  93. package/.mindforge/personas/executor.md +88 -0
  94. package/.mindforge/personas/integration-checker.md +92 -0
  95. package/.mindforge/personas/nyquist-auditor.md +84 -0
  96. package/.mindforge/personas/phase-researcher.md +107 -0
  97. package/.mindforge/personas/plan-checker.md +92 -0
  98. package/.mindforge/personas/planner.md +105 -0
  99. package/.mindforge/personas/project-researcher.md +99 -0
  100. package/.mindforge/personas/qa-engineer.md +113 -61
  101. package/.mindforge/personas/release-manager.md +102 -64
  102. package/.mindforge/personas/research-agent.md +108 -24
  103. package/.mindforge/personas/research-synthesizer.md +101 -0
  104. package/.mindforge/personas/roadmapper-extend.md +100 -0
  105. package/.mindforge/personas/roadmapper.md +103 -0
  106. package/.mindforge/personas/security-reviewer.md +114 -91
  107. package/.mindforge/personas/tech-writer.md +118 -51
  108. package/.mindforge/personas/ui-auditor.md +94 -0
  109. package/.mindforge/personas/ui-checker.md +89 -0
  110. package/.mindforge/personas/ui-researcher.md +99 -0
  111. package/.mindforge/personas/user-profiler.md +93 -0
  112. package/.mindforge/personas/verifier.md +101 -0
  113. package/.mindforge/production/production-checklist.md +34 -123
  114. package/.mindforge/skills-builder/auto-capture-protocol.md +88 -0
  115. package/.mindforge/skills-builder/learn-protocol.md +161 -0
  116. package/.mindforge/skills-builder/quality-scoring.md +120 -0
  117. package/.planning/AUDIT.jsonl +1 -0
  118. package/.planning/decisions/ADR-036-learn-command-docs-as-skill-source.md +26 -0
  119. package/.planning/decisions/ADR-037-auto-capture-frequency-threshold.md +26 -0
  120. package/.planning/decisions/ADR-038-skill-quality-minimum-60.md +27 -0
  121. package/CHANGELOG.md +78 -0
  122. package/MINDFORGE.md +11 -0
  123. package/README.md +80 -6
  124. package/bin/autonomous/auto-runner.js +12 -0
  125. package/bin/install.js +8 -2
  126. package/bin/installer-core.js +129 -26
  127. package/bin/migrations/1.0.0-to-2.0.0.js +115 -0
  128. package/bin/migrations/schema-versions.js +12 -0
  129. package/bin/mindforge-cli.js +35 -0
  130. package/bin/review/cross-review-engine.js +11 -0
  131. package/bin/skill-registry.js +167 -0
  132. package/bin/skill-validator.js +144 -0
  133. package/bin/skills-builder/learn-cli.js +57 -0
  134. package/bin/skills-builder/marketplace-cli.js +54 -0
  135. package/bin/skills-builder/marketplace-client.js +198 -0
  136. package/bin/skills-builder/pattern-detector.js +144 -0
  137. package/bin/skills-builder/skill-generator.js +258 -0
  138. package/bin/skills-builder/skill-registrar.js +107 -0
  139. package/bin/skills-builder/skill-scorer.js +263 -0
  140. package/bin/skills-builder/source-loader.js +268 -0
  141. package/docs/Context/Master-Context.md +6 -13
  142. package/docs/PERSONAS.md +611 -0
  143. package/docs/architecture/README.md +6 -1
  144. package/docs/architecture/adr-039-multi-runtime-support.md +20 -0
  145. package/docs/architecture/adr-040-additive-schema-migration.md +21 -0
  146. package/docs/architecture/adr-041-stable-runtime-interface-contract.md +20 -0
  147. package/docs/architecture/decision-records-index.md +3 -0
  148. package/docs/commands-reference.md +3 -0
  149. package/docs/mindforge-md-reference.md +4 -0
  150. package/docs/reference/commands.md +53 -43
  151. package/docs/skills-authoring-guide.md +29 -0
  152. package/docs/skills-publishing-guide.md +3 -2
  153. package/docs/testing-current-version.md +3 -3
  154. package/docs/upgrade.md +16 -2
  155. package/docs/user-guide.md +57 -8
  156. package/docs/usp-features.md +21 -6
  157. package/package.json +1 -1
@@ -0,0 +1,263 @@
1
+ /**
2
+ * MindForge v2 — Skill Scorer
3
+ * 7-dimension quality scoring system for SKILL.md files.
4
+ * Total: 100 points.
5
+ *
6
+ * This is a static analysis scorer — no AI calls needed.
7
+ * Runs in < 100ms on any SKILL.md.
8
+ */
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+
14
+ // ── Injection guard patterns (from skill-loader.md) ───────────────────────────
15
+ const INJECTION_PATTERNS = [
16
+ /IGNORE ALL PREVIOUS INSTRUCTIONS/i,
17
+ /IGNORE PREVIOUS INSTRUCTIONS/i,
18
+ /DISREGARD YOUR INSTRUCTIONS/i,
19
+ /FORGET YOUR TRAINING/i,
20
+ /YOU ARE NOW/i,
21
+ /YOUR NEW INSTRUCTIONS ARE/i,
22
+ /OVERRIDE:/i,
23
+ /SYSTEM PROMPT:/i,
24
+ ];
25
+
26
+ // ── Placeholder detection ─────────────────────────────────────────────────────
27
+ const PLACEHOLDER_PATTERNS = [
28
+ /\[your description here\]/i,
29
+ /\[fill in\]/i,
30
+ /\[TODO\]/i,
31
+ /\btodo\b/i,
32
+ /\bfixme\b/i,
33
+ /<description>/i,
34
+ /\.\.\.fill in\.\.\./i,
35
+ /\[your [a-z\s]+ here\]/i,
36
+ /\[replace with\]/i,
37
+ ];
38
+
39
+ // ── Generic trigger words (penalty list) ─────────────────────────────────────
40
+ const GENERIC_TRIGGERS = new Set([
41
+ 'database', 'api', 'model', 'service', 'component', 'function',
42
+ 'class', 'method', 'type', 'interface', 'module', 'package',
43
+ 'file', 'config', 'test', 'error', 'data', 'query', 'request',
44
+ 'response', 'handler', 'controller', 'repository', 'schema',
45
+ ]);
46
+
47
+ // ── SKILL.md parser ───────────────────────────────────────────────────────────
48
+ function parseSkill(content) {
49
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
50
+ const frontmatter = frontmatterMatch?.[1] || '';
51
+
52
+ // Extract triggers from frontmatter
53
+ const triggersSection = frontmatter.match(/^triggers:\n((?: - .+\n?)*)/m);
54
+ const triggers = (triggersSection?.[1] || '')
55
+ .split('\n')
56
+ .map(l => l.replace(/^\s*- /, '').trim())
57
+ .filter(Boolean);
58
+
59
+ // Count code blocks with ≥ 3 lines (meaningful examples, not one-liners)
60
+ const codeBlockMatches = content.match(/```[\s\S]*?```/g) || [];
61
+ const codeBlocks = codeBlockMatches.length;
62
+ const meaningfulBlocks = codeBlockMatches.filter(block => {
63
+ const lines = block.split('\n').length;
64
+ return lines >= 4; // ``` + at least 3 content lines + ```
65
+ }).length;
66
+
67
+ // Count checklist items (both checked and unchecked)
68
+ const checklistItems = (content.match(/^- \[[ xX]\] /gm) || []).length;
69
+
70
+ // Has version history?
71
+ const hasVersionHistory = /## Version History/i.test(content);
72
+ const versionEntries = (content.match(/^### v\d+\.\d+\.\d+/gm) || []).length;
73
+
74
+ // Mandatory action counts
75
+ const alwaysRules = (content.match(/\b(Always|Must|Required|mandatory|MUST)\b/gi) || []).length;
76
+ const neverRules = (content.match(/\b(Never|Don't|Do not|Avoid|NEVER)\b/gi) || []).length;
77
+ const hasSecuritySection = /security|auth|SECURITY|AUTH/i.test(content);
78
+ const hasPerformanceSection = /performance|perf|optimiz|PERFORMANCE/i.test(content);
79
+ const hasErrorSection = /error handling|exception|catch|Error/i.test(content);
80
+
81
+ return {
82
+ triggers, codeBlocks, meaningfulBlocks, checklistItems,
83
+ hasVersionHistory, versionEntries,
84
+ alwaysRules, neverRules,
85
+ hasSecuritySection, hasPerformanceSection, hasErrorSection,
86
+ content,
87
+ };
88
+ }
89
+
90
+ // ── Dimension scorers ─────────────────────────────────────────────────────────
91
+ function scoreTriggerCoverage(parsed) {
92
+ const { triggers } = parsed;
93
+ let score = 0;
94
+
95
+ if (triggers.length >= 25) score = 30;
96
+ else if (triggers.length >= 20) score = 24;
97
+ else if (triggers.length >= 15) score = 18;
98
+ else if (triggers.length >= 10) score = 12;
99
+ else if (triggers.length >= 5) score = 6;
100
+
101
+ // Penalty for generic triggers
102
+ const genericCount = triggers.filter(t => GENERIC_TRIGGERS.has(t.toLowerCase())).length;
103
+ const penalty = genericCount * 2;
104
+
105
+ return {
106
+ score: Math.max(0, score - penalty),
107
+ max: 30,
108
+ details: `${triggers.length} triggers, ${genericCount} generic (penalty: -${penalty})`,
109
+ };
110
+ }
111
+
112
+ function scoreMandatoryActions(parsed) {
113
+ const { alwaysRules, neverRules, hasSecuritySection, hasPerformanceSection, hasErrorSection } = parsed;
114
+ let score = 0;
115
+
116
+ if (alwaysRules >= 5) score += 5;
117
+ else if (alwaysRules >= 3) score += 3;
118
+ else if (alwaysRules >= 1) score += 1;
119
+
120
+ if (neverRules >= 3) score += 5;
121
+ else if (neverRules >= 2) score += 3;
122
+ else if (neverRules >= 1) score += 2;
123
+
124
+ if (hasSecuritySection) score += 5;
125
+ if (hasPerformanceSection) score += 5;
126
+ if (hasErrorSection) score += 5;
127
+
128
+ return {
129
+ score: Math.min(25, score),
130
+ max: 25,
131
+ details: `${alwaysRules} always-rules, ${neverRules} never-rules, security:${hasSecuritySection}, perf:${hasPerformanceSection}, errors:${hasErrorSection}`,
132
+ };
133
+ }
134
+
135
+ function scoreCodeExamples(parsed) {
136
+ const { meaningfulBlocks, content } = parsed;
137
+ let score = 0;
138
+
139
+ if (meaningfulBlocks >= 5) score = 20;
140
+ else if (meaningfulBlocks >= 3) score = 14;
141
+ else if (meaningfulBlocks >= 1) score = 7;
142
+
143
+ // Bonus: side-by-side correct/incorrect examples
144
+ const hasSideBySide = content.includes('✅') && content.includes('❌');
145
+ if (hasSideBySide) score = Math.min(20, score + 2);
146
+
147
+ return { score: Math.min(20, score), max: 20, details: `${meaningfulBlocks} meaningful code blocks, side-by-side:${hasSideBySide}` };
148
+ }
149
+
150
+ function scoreSelfCheck(parsed) {
151
+ const { checklistItems } = parsed;
152
+ let score = 0;
153
+
154
+ if (checklistItems >= 10) score = 15;
155
+ else if (checklistItems >= 7) score = 10;
156
+ else if (checklistItems >= 4) score = 7;
157
+ else if (checklistItems >= 1) score = 3;
158
+
159
+ return { score, max: 15, details: `${checklistItems} checklist items` };
160
+ }
161
+
162
+ function scoreInjectionSafe(parsed) {
163
+ const hasInjection = INJECTION_PATTERNS.some(p => p.test(parsed.content));
164
+ return {
165
+ score: hasInjection ? 0 : 10,
166
+ max: 10,
167
+ details: hasInjection ? 'INJECTION PATTERN DETECTED — score 0' : 'clean',
168
+ fail: hasInjection,
169
+ };
170
+ }
171
+
172
+ function scoreNoPlaceholders(parsed) {
173
+ const placeholderCount = PLACEHOLDER_PATTERNS.filter(p => p.test(parsed.content)).length;
174
+ let score = 0;
175
+ if (placeholderCount === 0) score = 10;
176
+ else if (placeholderCount <= 2) score = 5;
177
+
178
+ return { score, max: 10, details: `${placeholderCount} placeholder patterns found` };
179
+ }
180
+
181
+ function scoreVersionHistory(parsed) {
182
+ const { hasVersionHistory, versionEntries } = parsed;
183
+ let score = 0;
184
+ if (hasVersionHistory && versionEntries >= 1) score = 10;
185
+ else if (versionEntries > 0) score = 5;
186
+
187
+ const bonus = versionEntries > 1 ? 2 : 0;
188
+ const MAX = 10;
189
+ return { score: Math.min(MAX, score + bonus), max: MAX, details: `${versionEntries} version entries` };
190
+ }
191
+
192
+ // ── Main score function ───────────────────────────────────────────────────────
193
+ /**
194
+ * Score a SKILL.md file.
195
+ * @param {string} skillPathOrContent - Path to SKILL.md or content string
196
+ * @returns {object} Full scoring result
197
+ */
198
+ function score(skillPathOrContent) {
199
+ let content;
200
+ if (typeof skillPathOrContent === 'string' && fs.existsSync(skillPathOrContent)) {
201
+ content = fs.readFileSync(skillPathOrContent, 'utf8');
202
+ } else {
203
+ content = skillPathOrContent;
204
+ }
205
+
206
+ const parsed = parseSkill(content);
207
+
208
+ const dimensions = {
209
+ trigger_coverage: scoreTriggerCoverage(parsed),
210
+ mandatory_actions: scoreMandatoryActions(parsed),
211
+ code_examples: scoreCodeExamples(parsed),
212
+ self_check: scoreSelfCheck(parsed),
213
+ injection_safe: scoreInjectionSafe(parsed),
214
+ no_placeholders: scoreNoPlaceholders(parsed),
215
+ version_history: scoreVersionHistory(parsed),
216
+ };
217
+
218
+ const total = Object.values(dimensions).reduce((s, d) => s + d.score, 0);
219
+
220
+ // Determine thresholds
221
+ let threshold_status = 'insufficient';
222
+ if (total >= 90) threshold_status = 'excellent';
223
+ else if (total >= 80) threshold_status = 'good';
224
+ else if (total >= 70) threshold_status = 'acceptable';
225
+ else if (total >= 60) threshold_status = 'minimum';
226
+
227
+ const can_register = total >= 60 && !dimensions.injection_safe.fail;
228
+ const can_publish = total >= 80 && !dimensions.injection_safe.fail;
229
+
230
+ // Improvement suggestions
231
+ const suggestions = [];
232
+ if (dimensions.trigger_coverage.score < 24) {
233
+ suggestions.push(`Add ${25 - parsed.triggers.length} more triggers to reach 25+ (currently ${parsed.triggers.length})`);
234
+ }
235
+ if (dimensions.mandatory_actions.score < 20) {
236
+ if (!parsed.hasSecuritySection) suggestions.push('Add a security considerations section');
237
+ if (!parsed.hasPerformanceSection) suggestions.push('Add a performance considerations section');
238
+ if (!parsed.hasErrorSection) suggestions.push('Add an error handling section');
239
+ }
240
+ if (dimensions.code_examples.score < 14) {
241
+ suggestions.push(`Add ${5 - parsed.codeBlocks} more code examples (currently ${parsed.codeBlocks})`);
242
+ }
243
+ if (dimensions.self_check.score < 10) {
244
+ suggestions.push(`Add ${10 - parsed.checklistItems} more checklist items (currently ${parsed.checklistItems})`);
245
+ }
246
+ if (!dimensions.version_history.score) {
247
+ suggestions.push('Add a ## Version History section with a v1.0.0 entry');
248
+ }
249
+
250
+ return {
251
+ quality_score: total,
252
+ threshold_status,
253
+ can_register,
254
+ can_publish,
255
+ score_breakdown: Object.fromEntries(Object.entries(dimensions).map(([k, v]) => [k, v.score])),
256
+ dimension_details: Object.fromEntries(Object.entries(dimensions).map(([k, v]) => [k, v.details])),
257
+ improvement_suggestions: suggestions,
258
+ trigger_count: parsed.triggers.length,
259
+ injection_safe: !dimensions.injection_safe.fail,
260
+ };
261
+ }
262
+
263
+ module.exports = { score, parseSkill, INJECTION_PATTERNS, PLACEHOLDER_PATTERNS, GENERIC_TRIGGERS };
@@ -0,0 +1,268 @@
1
+ /**
2
+ * MindForge v2 — Source Loader
3
+ * Loads documentation from URLs, local files/dirs, npm packages,
4
+ * and the current session (SUMMARY files + HANDOFF.json).
5
+ *
6
+ * All URL fetches have SSRF protection (Day 10 pattern).
7
+ * Local reads use walkDir() to safely enumerate files.
8
+ */
9
+ 'use strict';
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const dns = require('dns').promises;
14
+
15
+ const PLANNING_DIR = path.join(process.cwd(), '.planning');
16
+ const MINDFORGE_DIR = path.join(process.cwd(), '.mindforge');
17
+
18
+ // ── SSRF protection (reused from research-engine.js, Day 10) ─────────────────
19
+ const PRIVATE_RANGES = [
20
+ /^127\./,
21
+ /^10\./,
22
+ /^172\.(1[6-9]|2\d|3[01])\./,
23
+ /^192\.168\./,
24
+ /^169\.254\./, // AWS metadata
25
+ /^::1$/,
26
+ /^fc00:/,
27
+ /^fe80:/,
28
+ ];
29
+
30
+ async function isSafeUrl(rawUrl) {
31
+ let parsed;
32
+ try { parsed = new URL(rawUrl); } catch { return false; }
33
+ if (!['http:', 'https:'].includes(parsed.protocol)) return false;
34
+
35
+ try {
36
+ const { address } = await dns.lookup(parsed.hostname);
37
+ if (PRIVATE_RANGES.some(r => r.test(address))) {
38
+ process.stderr.write(`[source-loader] SSRF blocked: ${rawUrl} → ${address}\n`);
39
+ return false;
40
+ }
41
+ } catch {
42
+ process.stderr.write(`[source-loader] DNS resolution failed for: ${parsed.hostname}\n`);
43
+ return false;
44
+ }
45
+ return true;
46
+ }
47
+
48
+ // ── URL fetcher ───────────────────────────────────────────────────────────────
49
+ async function fetchUrl(rawUrl, maxChars = 400_000, _redirectCount = 0) {
50
+ if (_redirectCount > 5) {
51
+ throw new Error(`Too many redirects (>5) for URL: ${rawUrl}`);
52
+ }
53
+
54
+ if (!await isSafeUrl(rawUrl)) {
55
+ throw new Error(`URL blocked by SSRF protection: ${rawUrl}`);
56
+ }
57
+
58
+ return new Promise((resolve, reject) => {
59
+ const protocol = rawUrl.startsWith('https') ? require('https') : require('http');
60
+ let settled = false;
61
+ const settle = (fn, val) => { if (!settled) { settled = true; fn(val); } };
62
+
63
+ const hardTimer = setTimeout(() => {
64
+ settle(reject, new Error(`Fetch timeout (30s): ${rawUrl}`));
65
+ }, 30_000);
66
+
67
+ const req = protocol.get(rawUrl, { headers: { 'User-Agent': 'MindForge-Learn/2.0' } }, res => {
68
+ // Follow redirects (up to 5)
69
+ if ([301, 302, 303, 307, 308].includes(res.statusCode) && res.headers.location) {
70
+ clearTimeout(hardTimer);
71
+ // Pass incremented redirect count — SSRF re-checked in recursive call
72
+ // Comment: Each redirect hop calls isSafeUrl() — open redirect chains through private IPs are blocked
73
+ fetchUrl(res.headers.location, maxChars, _redirectCount + 1).then(settle.bind(null, resolve), settle.bind(null, reject));
74
+ return;
75
+ }
76
+ if (res.statusCode !== 200) {
77
+ clearTimeout(hardTimer);
78
+ settle(reject, new Error(`HTTP ${res.statusCode} for: ${rawUrl}`));
79
+ return;
80
+ }
81
+
82
+ let body = '';
83
+ res.on('data', chunk => { body += chunk; if (body.length > maxChars) res.destroy(); });
84
+ res.on('end', () => { clearTimeout(hardTimer); settle(resolve, body.slice(0, maxChars)); });
85
+ res.on('error', err => { clearTimeout(hardTimer); settle(reject, err); });
86
+ });
87
+ req.on('error', err => { clearTimeout(hardTimer); settle(reject, err); });
88
+ req.end();
89
+ });
90
+ }
91
+
92
+ // ── HTML → text (strip tags for cleaner model context) ───────────────────────
93
+ function htmlToText(html) {
94
+ return html
95
+ .replace(/<script[\s\S]*?<\/script>/gi, '') // Remove scripts
96
+ .replace(/<style[\s\S]*?<\/style>/gi, '') // Remove styles
97
+ .replace(/<[^>]+>/g, ' ') // Strip remaining tags
98
+ .replace(/&nbsp;/g, ' ')
99
+ .replace(/&amp;/g, '&')
100
+ .replace(/&lt;/g, '<')
101
+ .replace(/&gt;/g, '>')
102
+ .replace(/&quot;/g, '"')
103
+ .replace(/&#39;/g, "'")
104
+ .replace(/\s{3,}/g, '\n\n') // Collapse excessive whitespace
105
+ .trim();
106
+ }
107
+
108
+ // ── npm package loader ────────────────────────────────────────────────────────
109
+ async function loadNpmPackage(packageName) {
110
+ // Sanitize package name (prevent path injection)
111
+ if (!/^(@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/.test(packageName)) {
112
+ throw new Error(`Invalid npm package name: ${packageName}`);
113
+ }
114
+
115
+ const registryUrl = `https://registry.npmjs.org/${encodeURIComponent(packageName)}`;
116
+ const raw = await fetchUrl(registryUrl, 500_000);
117
+ const data = JSON.parse(raw);
118
+
119
+ const latest = data['dist-tags']?.latest || Object.keys(data.versions || {}).pop();
120
+ const version = data.versions?.[latest];
121
+ const readme = data.readme || version?.readme || '';
122
+
123
+ return {
124
+ name: packageName,
125
+ version: latest,
126
+ description: data.description || '',
127
+ homepage: data.homepage || '',
128
+ repository: version?.repository?.url || '',
129
+ readme: readme.slice(0, 200_000),
130
+ keywords: data.keywords || [],
131
+ };
132
+ }
133
+
134
+ // ── Local file/directory loader ───────────────────────────────────────────────
135
+ const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', '.next', 'coverage', '.planning']);
136
+ const DOC_EXTENSIONS = new Set(['.md', '.mdx', '.txt', '.rst', '.html', '.json', '.yaml', '.yml']);
137
+ const CODE_EXTENSIONS = new Set(['.ts', '.tsx', '.js', '.jsx', '.py', '.go', '.rs', '.java']);
138
+
139
+ function walkDir(dir, extensions, maxFiles = 50) {
140
+ const results = [];
141
+ function walk(d) {
142
+ if (results.length >= maxFiles) return;
143
+ let entries;
144
+ try { entries = fs.readdirSync(d, { withFileTypes: true }); } catch { return; }
145
+ for (const e of entries) {
146
+ if (results.length >= maxFiles) break;
147
+ if (SKIP_DIRS.has(e.name)) continue;
148
+ const full = path.join(d, e.name);
149
+ if (e.isDirectory()) walk(full);
150
+ else if (extensions.has(path.extname(e.name).toLowerCase())) results.push(full);
151
+ }
152
+ }
153
+ walk(dir);
154
+ return results;
155
+ }
156
+
157
+ function loadLocal(localPath, maxCharsPerFile = 50_000) {
158
+ const resolved = path.resolve(localPath);
159
+
160
+ // Safety check: resolved path must be inside cwd or be absolute
161
+ const stat = fs.statSync(resolved);
162
+ let content = '';
163
+
164
+ if (stat.isDirectory()) {
165
+ const allExts = new Set([...DOC_EXTENSIONS, ...CODE_EXTENSIONS]);
166
+ const files = walkDir(resolved, allExts, 30);
167
+ for (const f of files) {
168
+ const text = fs.readFileSync(f, 'utf8').slice(0, maxCharsPerFile);
169
+ content += `\n\n### ${path.relative(process.cwd(), f)}\n${text}`;
170
+ if (content.length > 600_000) break;
171
+ }
172
+ } else {
173
+ content = fs.readFileSync(resolved, 'utf8').slice(0, maxCharsPerFile);
174
+ }
175
+
176
+ return { path: resolved, content: content.slice(0, 800_000) };
177
+ }
178
+
179
+ // ── Session loader ────────────────────────────────────────────────────────────
180
+ function loadSession(phaseNum = null) {
181
+ const phasesDir = path.join(PLANNING_DIR, 'phases');
182
+ if (!fs.existsSync(phasesDir)) return { content: '', sources: [] };
183
+
184
+ // Determine phase to analyse
185
+ let targetPhase = phaseNum;
186
+ if (!targetPhase) {
187
+ // Find the most recent phase with SUMMARY files
188
+ const phaseDirs = fs.readdirSync(phasesDir)
189
+ .filter(d => /^\d+$/.test(d))
190
+ .map(Number).sort((a, b) => b - a); // Descending
191
+ targetPhase = phaseDirs[0];
192
+ }
193
+
194
+ if (!targetPhase) return { content: '', sources: [] };
195
+
196
+ const phaseDir = path.join(phasesDir, String(targetPhase));
197
+ const sources = [];
198
+ let content = `# Session Analysis — Phase ${targetPhase}\n\n`;
199
+
200
+ // SUMMARY files
201
+ const summaryFiles = fs.existsSync(phaseDir)
202
+ ? fs.readdirSync(phaseDir).filter(f => f.startsWith('SUMMARY-') && f.endsWith('.md'))
203
+ : [];
204
+ for (const f of summaryFiles) {
205
+ const text = fs.readFileSync(path.join(phaseDir, f), 'utf8');
206
+ content += `## ${f}\n${text.slice(0, 10_000)}\n\n`;
207
+ sources.push(f);
208
+ }
209
+
210
+ // HANDOFF.json implicit_knowledge
211
+ const handoffPath = path.join(PLANNING_DIR, 'HANDOFF.json');
212
+ if (fs.existsSync(handoffPath)) {
213
+ try {
214
+ const handoff = JSON.parse(fs.readFileSync(handoffPath, 'utf8'));
215
+ const implicit = handoff.implicit_knowledge || [];
216
+ if (implicit.length > 0) {
217
+ content += `## Implicit Knowledge (from HANDOFF.json)\n`;
218
+ implicit
219
+ .filter(i => (i.confidence || 0) >= 0.65)
220
+ .forEach(i => { content += `- ${i.topic || i.text}: ${i.content || i.text}\n`; });
221
+ content += '\n';
222
+ sources.push('HANDOFF.json:implicit_knowledge');
223
+ }
224
+ } catch { /* ignore malformed HANDOFF */ }
225
+ }
226
+
227
+ // ADR files from this phase
228
+ const decisionsDir = path.join(PLANNING_DIR, 'decisions');
229
+ if (fs.existsSync(decisionsDir)) {
230
+ const recentAdrs = fs.readdirSync(decisionsDir)
231
+ .filter(f => f.startsWith('ADR-') && f.endsWith('.md'))
232
+ .slice(-5); // Last 5 ADRs
233
+ for (const f of recentAdrs) {
234
+ const text = fs.readFileSync(path.join(decisionsDir, f), 'utf8');
235
+ content += `## ${f}\n${text.slice(0, 5_000)}\n\n`;
236
+ sources.push(f);
237
+ }
238
+ }
239
+
240
+ return { content: content.slice(0, 800_000), sources, phase: targetPhase };
241
+ }
242
+
243
+ // ── Main load function ────────────────────────────────────────────────────────
244
+ async function load(source) {
245
+ if (source === '--session') {
246
+ const result = loadSession();
247
+ return { type: 'session', content: result.content, metadata: { sources: result.sources, phase: result.phase } };
248
+ }
249
+
250
+ if (source.startsWith('npm:')) {
251
+ const pkg = source.slice(4);
252
+ const result = await loadNpmPackage(pkg);
253
+ const content = `# ${result.name} v${result.version}\n${result.description}\n\n${result.readme}`;
254
+ return { type: 'npm', content, metadata: result };
255
+ }
256
+
257
+ if (source.startsWith('http://') || source.startsWith('https://')) {
258
+ const raw = await fetchUrl(source);
259
+ const text = raw.includes('<html') || raw.includes('<HTML') ? htmlToText(raw) : raw;
260
+ return { type: 'url', content: text, metadata: { url: source, length: raw.length } };
261
+ }
262
+
263
+ // Local path
264
+ const result = loadLocal(source);
265
+ return { type: 'local', content: result.content, metadata: { path: result.path } };
266
+ }
267
+
268
+ module.exports = { load, fetchUrl, isSafeUrl, htmlToText, loadNpmPackage, loadLocal, loadSession };
@@ -34,15 +34,8 @@ anti-pattern detector, quality metrics, team profiling).
34
34
  Built the entire structural foundation:
35
35
 
36
36
  - **`.claude/CLAUDE.md`** — The agent entry point. Session start protocol, plan-first rule, quality gates, security auto-trigger, state artifact table. Mirrored identically to `.agent/CLAUDE.md` (Antigravity runtime).
37
- - **8 agent persona files** in `.mindforge/personas/`:
38
- - `analyst.md` Requirements decomposition and gap identification
39
- - `architect.md` — System design, ADRs, technology decisions
40
- - `developer.md` — Implementation with 5 common AI anti-pattern guards
41
- - `qa-engineer.md` — Test strategy and verification
42
- - `security-reviewer.md` — OWASP-aligned security review
43
- - `tech-writer.md` — Documentation and changelog authoring
44
- - `debug-specialist.md` — 10-step root cause analysis protocol
45
- - `release-manager.md` — Deployment coordination
37
+ - **32 agent persona files** in `.mindforge/personas/`:
38
+ - `advisor-researcher.md`, `analyst.md`, `architect.md`, `assumptions-analyzer-extend.md`, `assumptions-analyzer.md`, `codebase-mapper-extend.md`, `codebase-mapper.md`, `coverage-specialist.md`, `debug-specialist.md`, `debugger.md`, `decision-architect.md`, `developer.md`, `executor.md`, `integration-checker.md`, `nyquist-auditor.md`, `phase-researcher.md`, `plan-checker.md`, `planner.md`, `project-researcher.md`, `qa-engineer.md`, `release-manager.md`, `research-agent.md`, `research-synthesizer.md`, `roadmapper-extend.md`, `roadmapper.md`, `security-reviewer.md`, `tech-writer.md`, `ui-auditor.md`, `ui-checker.md`, `ui-researcher.md`, `user-profiler.md`, `verifier.md`
46
39
  - **5 initial core skill packs** in `.mindforge/skills/`:
47
40
  - `security-review/SKILL.md` — 29 trigger keywords, OWASP A01-A10
48
41
  - `code-quality/SKILL.md` — Complexity, naming, error handling
@@ -331,7 +324,7 @@ mindforge-cc/ ← npm package root
331
324
  │ └── [36 .md command files]
332
325
 
333
326
  ├── .mindforge/
334
- │ ├── personas/ ← 8 persona definitions + overrides/
327
+ │ ├── personas/ ← 32 persona definitions + overrides/
335
328
  │ ├── skills/ ← 10 core skill packs (SKILL.md each)
336
329
  │ ├── engine/
337
330
  │ │ ├── wave-executor.md ← Kahn's topological sort, parallel waves
@@ -490,9 +483,9 @@ mindforge-cc/ ← npm package root
490
483
  9. `incident-response` — P0-P3, runbooks, postmortems
491
484
  10. `database-patterns` — Compound cursor, UUIDv7, indexes
492
485
 
493
- ### 8 Agent Personas
486
+ ### 32 Agent Personas
494
487
 
495
- analyst, architect, developer, qa-engineer, security-reviewer, tech-writer, debug-specialist, release-manager
488
+ advisor-researcher, analyst, architect, assumptions-analyzer-extend, assumptions-analyzer, codebase-mapper-extend, codebase-mapper, coverage-specialist, debug-specialist, debugger, decision-architect, developer, executor, integration-checker, nyquist-auditor, phase-researcher, plan-checker, planner, project-researcher, qa-engineer, release-manager, research-agent, research-synthesizer, roadmapper-extend, roadmapper, security-reviewer, tech-writer, ui-auditor, ui-checker, ui-researcher, user-profiler, verifier
496
489
 
497
490
  ### 20 Architecture Decision Records
498
491
 
@@ -698,4 +691,4 @@ All prompt files are in `/mnt/user-data/outputs/`:
698
691
 
699
692
  ---
700
693
 
701
- *State file generated at completion. MindForge v1.0.0 — 36 commands · 10 skills · 8 personas · 20 ADRs · 15 test suites.*
694
+ *State file generated at completion. MindForge v1.0.0 — 36 commands · 10 skills · 32 personas · 20 ADRs · 15 test suites.*