forge-workflow 0.0.1

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 (105) hide show
  1. package/.claude/commands/dev.md +314 -0
  2. package/.claude/commands/plan.md +389 -0
  3. package/.claude/commands/premerge.md +179 -0
  4. package/.claude/commands/research.md +42 -0
  5. package/.claude/commands/review.md +442 -0
  6. package/.claude/commands/rollback.md +721 -0
  7. package/.claude/commands/ship.md +134 -0
  8. package/.claude/commands/sonarcloud.md +152 -0
  9. package/.claude/commands/status.md +77 -0
  10. package/.claude/commands/validate.md +237 -0
  11. package/.claude/commands/verify.md +221 -0
  12. package/.claude/rules/greptile-review-process.md +285 -0
  13. package/.claude/rules/workflow.md +105 -0
  14. package/.claude/scripts/greptile-resolve.sh +526 -0
  15. package/.claude/scripts/load-env.sh +32 -0
  16. package/.forge/hooks/check-tdd.js +240 -0
  17. package/.github/PLUGIN_TEMPLATE.json +32 -0
  18. package/.mcp.json.example +12 -0
  19. package/AGENTS.md +169 -0
  20. package/CLAUDE.md +99 -0
  21. package/LICENSE +21 -0
  22. package/README.md +414 -0
  23. package/bin/forge-cmd.js +313 -0
  24. package/bin/forge-validate.js +303 -0
  25. package/bin/forge.js +4228 -0
  26. package/docs/AGENT_INSTALL_PROMPT.md +342 -0
  27. package/docs/ENHANCED_ONBOARDING.md +602 -0
  28. package/docs/EXAMPLES.md +482 -0
  29. package/docs/GREPTILE_SETUP.md +400 -0
  30. package/docs/MANUAL_REVIEW_GUIDE.md +106 -0
  31. package/docs/ROADMAP.md +359 -0
  32. package/docs/SETUP.md +632 -0
  33. package/docs/TOOLCHAIN.md +849 -0
  34. package/docs/VALIDATION.md +363 -0
  35. package/docs/WORKFLOW.md +400 -0
  36. package/docs/planning/PROGRESS.md +396 -0
  37. package/docs/plans/.gitkeep +0 -0
  38. package/docs/plans/2026-02-27-forge-test-suite-v2-decisions.md +21 -0
  39. package/docs/plans/2026-02-27-forge-test-suite-v2-design.md +362 -0
  40. package/docs/plans/2026-02-27-forge-test-suite-v2-tasks.md +343 -0
  41. package/docs/plans/2026-03-02-superpowers-gaps-decisions.md +26 -0
  42. package/docs/plans/2026-03-02-superpowers-gaps-design.md +239 -0
  43. package/docs/plans/2026-03-02-superpowers-gaps-tasks.md +260 -0
  44. package/docs/plans/2026-03-04-agent-command-parity-design.md +163 -0
  45. package/docs/plans/2026-03-04-verify-worktree-cleanup-decisions.md +7 -0
  46. package/docs/plans/2026-03-04-verify-worktree-cleanup-design.md +165 -0
  47. package/docs/plans/2026-03-05-forge-uto-decisions.md +6 -0
  48. package/docs/plans/2026-03-05-forge-uto-design.md +116 -0
  49. package/docs/plans/2026-03-05-forge-uto-tasks.md +244 -0
  50. package/docs/plans/2026-03-10-command-creator-and-eval-decisions.md +52 -0
  51. package/docs/plans/2026-03-10-command-creator-and-eval-design.md +350 -0
  52. package/docs/plans/2026-03-10-command-creator-and-eval-tasks.md +426 -0
  53. package/docs/plans/2026-03-10-stale-workflow-refs-decisions.md +8 -0
  54. package/docs/plans/2026-03-10-stale-workflow-refs-design.md +80 -0
  55. package/docs/plans/2026-03-10-stale-workflow-refs-tasks.md +90 -0
  56. package/docs/plans/2026-03-14-beads-plan-context-decisions.md +9 -0
  57. package/docs/plans/2026-03-14-beads-plan-context-design.md +171 -0
  58. package/docs/plans/2026-03-14-beads-plan-context-tasks.md +160 -0
  59. package/docs/plans/2026-03-14-skill-eval-loop-decisions.md +33 -0
  60. package/docs/plans/2026-03-14-skill-eval-loop-design.md +118 -0
  61. package/docs/plans/2026-03-14-skill-eval-loop-results.md +78 -0
  62. package/docs/plans/2026-03-14-skill-eval-loop-tasks.md +160 -0
  63. package/docs/plans/2026-03-15-agent-command-parity-v2-decisions.md +11 -0
  64. package/docs/plans/2026-03-15-agent-command-parity-v2-design.md +145 -0
  65. package/docs/plans/2026-03-15-agent-command-parity-v2-tasks.md +211 -0
  66. package/docs/research/TEMPLATE.md +292 -0
  67. package/docs/research/advanced-testing.md +297 -0
  68. package/docs/research/agent-permissions.md +167 -0
  69. package/docs/research/dependency-chain.md +328 -0
  70. package/docs/research/forge-workflow-v2.md +550 -0
  71. package/docs/research/plugin-architecture.md +772 -0
  72. package/docs/research/pr4-cli-automation.md +326 -0
  73. package/docs/research/premerge-verify-restructure.md +205 -0
  74. package/docs/research/skills-restructure.md +508 -0
  75. package/docs/research/sonarcloud-perfection-plan.md +166 -0
  76. package/docs/research/sonarcloud-quality-gate.md +184 -0
  77. package/docs/research/superpowers-integration.md +403 -0
  78. package/docs/research/superpowers.md +319 -0
  79. package/docs/research/test-environment.md +519 -0
  80. package/install.sh +1062 -0
  81. package/lefthook.yml +39 -0
  82. package/lib/agents/README.md +198 -0
  83. package/lib/agents/claude.plugin.json +28 -0
  84. package/lib/agents/cline.plugin.json +22 -0
  85. package/lib/agents/codex.plugin.json +19 -0
  86. package/lib/agents/copilot.plugin.json +24 -0
  87. package/lib/agents/cursor.plugin.json +25 -0
  88. package/lib/agents/kilocode.plugin.json +22 -0
  89. package/lib/agents/opencode.plugin.json +20 -0
  90. package/lib/agents/roo.plugin.json +23 -0
  91. package/lib/agents-config.js +2112 -0
  92. package/lib/commands/dev.js +513 -0
  93. package/lib/commands/plan.js +696 -0
  94. package/lib/commands/recommend.js +119 -0
  95. package/lib/commands/ship.js +377 -0
  96. package/lib/commands/status.js +378 -0
  97. package/lib/commands/validate.js +602 -0
  98. package/lib/context-merge.js +359 -0
  99. package/lib/plugin-catalog.js +360 -0
  100. package/lib/plugin-manager.js +166 -0
  101. package/lib/plugin-recommender.js +141 -0
  102. package/lib/project-discovery.js +491 -0
  103. package/lib/setup.js +118 -0
  104. package/lib/workflow-profiles.js +203 -0
  105. package/package.json +115 -0
@@ -0,0 +1,359 @@
1
+ /**
2
+ * Semantic Merge for Context Files
3
+ *
4
+ * Intelligently merges existing CLAUDE.md/AGENTS.md files with Forge workflow templates
5
+ * by understanding the semantic meaning of markdown sections.
6
+ */
7
+
8
+ const { distance: levenshteinDistance } = require('fastest-levenshtein');
9
+
10
+ // Section category definitions
11
+ const SECTION_CATEGORIES = {
12
+ preserve: [
13
+ 'Project Description',
14
+ 'Project Instructions',
15
+ 'Project Overview',
16
+ 'Project Background',
17
+ 'Domain Knowledge',
18
+ 'Domain Concepts',
19
+ 'Coding Standards',
20
+ 'Code Standards',
21
+ 'Architecture',
22
+ 'Tech Stack',
23
+ 'Technology Stack',
24
+ 'Build Commands',
25
+ 'Team Conventions',
26
+ 'Migration Strategy',
27
+ 'Setup',
28
+ 'Installation',
29
+ 'Quick Start',
30
+ 'Getting Started'
31
+ ],
32
+
33
+ replace: [
34
+ 'Workflow',
35
+ 'Development Workflow',
36
+ 'Our Workflow',
37
+ 'Workflow Process',
38
+ 'Development Process',
39
+ 'Process',
40
+ 'TDD',
41
+ 'Test-Driven Development',
42
+ 'TDD Approach',
43
+ 'Testing Approach',
44
+ 'Git Workflow',
45
+ 'Git Conventions',
46
+ 'Commit Conventions',
47
+ 'Git Strategy',
48
+ 'Forge Workflow',
49
+ 'Core Principles',
50
+ 'Development Principles'
51
+ ],
52
+
53
+ merge: [
54
+ 'Toolchain',
55
+ 'Tools',
56
+ 'MCP Servers',
57
+ 'Integrations',
58
+ 'Dependencies',
59
+ 'Libraries'
60
+ ]
61
+ };
62
+
63
+ /**
64
+ * Parse markdown content into semantic sections
65
+ * @param {string} markdownContent - Raw markdown content
66
+ * @returns {Array} Array of section objects with structure:
67
+ * { level, header, content, raw, startLine, endLine }
68
+ */
69
+ function parseSemanticSections(markdownContent) {
70
+ if (!markdownContent || typeof markdownContent !== 'string') {
71
+ return [];
72
+ }
73
+
74
+ const lines = markdownContent.split('\n');
75
+ const sections = [];
76
+ let currentSection = null;
77
+
78
+ for (let i = 0; i < lines.length; i++) {
79
+ const line = lines[i];
80
+ // Use RegExp.exec() instead of String.match() per S5852 recommendation
81
+ const headerMatch = /^(#{1,6})\s+([^\r\n]+)$/.exec(line); // NOSONAR S5852 - uses [^\r\n]+ (bounded, no backtracking), developer tool context
82
+
83
+ if (headerMatch) {
84
+ // Save previous section if exists
85
+ if (currentSection) {
86
+ currentSection.endLine = i - 1;
87
+ currentSection.content = currentSection.content.trim();
88
+ sections.push(currentSection);
89
+ }
90
+
91
+ // Start new section
92
+ currentSection = {
93
+ level: headerMatch[1].length,
94
+ header: headerMatch[2].trim(),
95
+ content: '',
96
+ raw: line,
97
+ startLine: i
98
+ };
99
+ } else if (currentSection) {
100
+ // Add content to current section
101
+ currentSection.content += line + '\n';
102
+ currentSection.raw += '\n' + line;
103
+ } else if (line.trim() !== '') {
104
+ // Content before first header (preamble)
105
+ sections.push({
106
+ level: 0,
107
+ header: null,
108
+ content: line,
109
+ raw: line,
110
+ startLine: i,
111
+ endLine: i
112
+ });
113
+ }
114
+ }
115
+
116
+ // Save last section
117
+ if (currentSection) {
118
+ currentSection.endLine = lines.length - 1;
119
+ currentSection.content = currentSection.content.trim();
120
+ sections.push(currentSection);
121
+ }
122
+
123
+ return sections;
124
+ }
125
+
126
+ /**
127
+ * Calculate similarity between normalized text and keyword
128
+ * Reduces cognitive complexity by extracting matching logic (S3776)
129
+ * @param {string} normalized - Normalized header text
130
+ * @param {string} keywordNorm - Normalized keyword
131
+ * @returns {number} - Similarity score 0-1
132
+ */
133
+ function calculateKeywordSimilarity(normalized, keywordNorm) {
134
+ // Fuzzy match using Levenshtein distance
135
+ const distance = levenshteinDistance(normalized, keywordNorm);
136
+ const maxLen = Math.max(normalized.length, keywordNorm.length);
137
+ const similarity = 1 - (distance / maxLen);
138
+
139
+ // Also check if normalized contains the keyword
140
+ if (normalized.includes(keywordNorm) || keywordNorm.includes(normalized)) {
141
+ const containsSimilarity = Math.min(normalized.length, keywordNorm.length) / maxLen;
142
+ return Math.max(similarity, containsSimilarity);
143
+ }
144
+
145
+ return similarity;
146
+ }
147
+
148
+ /**
149
+ * Detect the category of a section based on its header
150
+ * @param {string} headerText - Section header text
151
+ * @returns {Object} { category: 'preserve'|'replace'|'merge'|'unknown', confidence: 0-1 }
152
+ */
153
+ function detectCategory(headerText) {
154
+ if (!headerText || typeof headerText !== 'string') {
155
+ return { category: 'unknown', confidence: 0 };
156
+ }
157
+
158
+ const normalized = headerText.toLowerCase().trim();
159
+ let bestMatch = { category: 'unknown', confidence: 0 };
160
+
161
+ // Check each category
162
+ for (const [category, keywords] of Object.entries(SECTION_CATEGORIES)) {
163
+ for (const keyword of keywords) {
164
+ const keywordNorm = keyword.toLowerCase().trim();
165
+
166
+ // Exact match = highest confidence
167
+ if (normalized === keywordNorm) {
168
+ return { category, confidence: 1 };
169
+ }
170
+
171
+ // Calculate similarity and update best match
172
+ const similarity = calculateKeywordSimilarity(normalized, keywordNorm);
173
+ if (similarity > bestMatch.confidence) {
174
+ bestMatch = { category, confidence: similarity };
175
+ }
176
+ }
177
+ }
178
+
179
+ return bestMatch;
180
+ }
181
+
182
+ /**
183
+ * Build merged document from categorized sections
184
+ * @param {Array} existingSections - Sections from existing file
185
+ * @param {Array} forgeSections - Sections from Forge template
186
+ * @param {Object} options - Merge options
187
+ * @returns {string} Merged markdown content
188
+ */
189
+ function buildMergedDocument(existingSections, forgeSections, _options = {}) {
190
+ const result = [];
191
+ const processedExisting = new Set();
192
+
193
+ // Process forge sections first to establish structure
194
+ forgeSections.forEach(forgeSection => {
195
+ if (!forgeSection.header) {
196
+ return; // Skip preamble from forge
197
+ }
198
+
199
+ const forgeCategory = detectCategory(forgeSection.header);
200
+
201
+ if (forgeCategory.category === 'replace' && forgeCategory.confidence > 0.6) {
202
+ // This is a workflow/TDD section - use forge version
203
+ result.push(forgeSection.raw);
204
+
205
+ // Mark any similar existing sections as processed
206
+ existingSections.forEach((existingSection, idx) => {
207
+ if (existingSection.header) {
208
+ const existingCategory = detectCategory(existingSection.header);
209
+ if (existingCategory.category === 'replace' && existingCategory.confidence > 0.6) {
210
+ // Check if headers are similar enough
211
+ const normalized1 = forgeSection.header.toLowerCase();
212
+ const normalized2 = existingSection.header.toLowerCase();
213
+ const distance = levenshteinDistance(normalized1, normalized2);
214
+ const similarity = 1 - (distance / Math.max(normalized1.length, normalized2.length));
215
+
216
+ if (similarity > 0.5) {
217
+ processedExisting.add(idx);
218
+ }
219
+ }
220
+ }
221
+ });
222
+ } else if (forgeCategory.category === 'merge' && forgeCategory.confidence > 0.6) {
223
+ // Merge section - combine both
224
+ result.push(forgeSection.raw);
225
+
226
+ // Find and add corresponding existing section
227
+ existingSections.forEach((existingSection, idx) => {
228
+ if (existingSection.header) {
229
+ const existingCategory = detectCategory(existingSection.header);
230
+ if (existingCategory.category === 'merge' && existingCategory.confidence > 0.6) {
231
+ const normalized1 = forgeSection.header.toLowerCase();
232
+ const normalized2 = existingSection.header.toLowerCase();
233
+ const distance = levenshteinDistance(normalized1, normalized2);
234
+ const similarity = 1 - (distance / Math.max(normalized1.length, normalized2.length));
235
+
236
+ if (similarity > 0.5) {
237
+ // Add existing content under forge header
238
+ result.push('\n' + existingSection.content);
239
+ processedExisting.add(idx);
240
+ }
241
+ }
242
+ }
243
+ });
244
+ }
245
+ });
246
+
247
+ // Add preserved sections from existing file
248
+ existingSections.forEach((section, idx) => {
249
+ if (processedExisting.has(idx)) {
250
+ return; // Already processed
251
+ }
252
+
253
+ if (!section.header) {
254
+ // Preserve preamble content
255
+ if (section.content && section.content.trim() !== '') {
256
+ result.unshift(section.raw); // Add to beginning
257
+ }
258
+ return;
259
+ }
260
+
261
+ const category = detectCategory(section.header);
262
+
263
+ // Preserve sections unless explicitly marked for replacement with high confidence
264
+ // Categories: preserve, merge, unknown all get preserved (safety first)
265
+ const shouldPreserve = category.category !== 'replace' || category.confidence <= 0.6;
266
+
267
+ if (shouldPreserve) {
268
+ result.push(section.raw);
269
+ }
270
+ });
271
+
272
+ return result.join('\n\n');
273
+ }
274
+
275
+ /**
276
+ * Semantic merge of existing and forge content
277
+ * @param {string} existingContent - Existing file content
278
+ * @param {string} forgeContent - Forge template content
279
+ * @param {Object} options - { addMarkers: boolean }
280
+ * @returns {string} Merged content
281
+ */
282
+ function semanticMerge(existingContent, forgeContent, options = {}) {
283
+ // Normalize line endings to LF for consistent parsing across platforms (Windows CRLF vs Unix LF)
284
+ const normalizeLineEndings = (str) => str ? str.replaceAll('\r\n', '\n').replaceAll('\r', '\n') : str;
285
+
286
+ existingContent = normalizeLineEndings(existingContent);
287
+ forgeContent = normalizeLineEndings(forgeContent);
288
+
289
+ // Handle empty cases
290
+ if (!existingContent || existingContent.trim() === '') {
291
+ return forgeContent || '';
292
+ }
293
+
294
+ if (!forgeContent || forgeContent.trim() === '') {
295
+ return existingContent;
296
+ }
297
+
298
+ // Parse both documents
299
+ const existingSections = parseSemanticSections(existingContent);
300
+ const forgeSections = parseSemanticSections(forgeContent);
301
+
302
+ // Build merged document
303
+ const merged = buildMergedDocument(existingSections, forgeSections, options);
304
+
305
+ // Add markers if requested
306
+ if (options.addMarkers) {
307
+ // Separate preserved (user) and forge sections
308
+ const userSections = existingSections.filter(s => {
309
+ if (!s.header) return false;
310
+ const category = detectCategory(s.header);
311
+ return category.category === 'preserve' && category.confidence > 0.6;
312
+ });
313
+
314
+ const forgeSectionsFiltered = forgeSections.filter(s => {
315
+ if (!s.header) return false;
316
+ const category = detectCategory(s.header);
317
+ return category.category === 'replace' && category.confidence > 0.6;
318
+ });
319
+
320
+ return wrapWithMarkers({
321
+ user: userSections.map(s => s.raw).join('\n\n'),
322
+ forge: forgeSectionsFiltered.map(s => s.raw).join('\n\n')
323
+ });
324
+ }
325
+
326
+ return merged;
327
+ }
328
+
329
+ /**
330
+ * Wrap content with USER and FORGE markers
331
+ * @param {Object} content - { user: string, forge: string }
332
+ * @returns {string} Content wrapped with markers
333
+ */
334
+ function wrapWithMarkers(content) {
335
+ const parts = [];
336
+
337
+ if (content.forge && content.forge.trim() !== '') {
338
+ parts.push('<!-- FORGE:START -->', content.forge.trim(), '<!-- FORGE:END -->');
339
+ }
340
+
341
+ if (content.user && content.user.trim() !== '') {
342
+ parts.push('', '<!-- USER:START -->', content.user.trim(), '<!-- USER:END -->');
343
+ }
344
+
345
+ return parts.join('\n');
346
+ }
347
+
348
+ module.exports = {
349
+ parseSemanticSections,
350
+ detectCategory,
351
+ semanticMerge,
352
+ wrapWithMarkers,
353
+ // Export for testing
354
+ __internal: {
355
+ levenshteinDistance,
356
+ buildMergedDocument,
357
+ SECTION_CATEGORIES
358
+ }
359
+ };