@pcircle/memesh 2.9.2 → 2.9.3

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 (122) hide show
  1. package/README.de.md +119 -78
  2. package/README.es.md +116 -75
  3. package/README.fr.md +116 -75
  4. package/README.id.md +115 -74
  5. package/README.ja.md +111 -70
  6. package/README.ko.md +116 -75
  7. package/README.md +113 -72
  8. package/README.th.md +118 -77
  9. package/README.vi.md +116 -75
  10. package/README.zh-CN.md +120 -79
  11. package/README.zh-TW.md +116 -75
  12. package/dist/core/GitCommandParser.d.ts +11 -0
  13. package/dist/core/GitCommandParser.d.ts.map +1 -0
  14. package/dist/core/GitCommandParser.js +43 -0
  15. package/dist/core/GitCommandParser.js.map +1 -0
  16. package/dist/core/HookIntegration.d.ts +0 -3
  17. package/dist/core/HookIntegration.d.ts.map +1 -1
  18. package/dist/core/HookIntegration.js +6 -30
  19. package/dist/core/HookIntegration.js.map +1 -1
  20. package/dist/embeddings/EmbeddingService.d.ts +3 -0
  21. package/dist/embeddings/EmbeddingService.d.ts.map +1 -1
  22. package/dist/embeddings/EmbeddingService.js +32 -6
  23. package/dist/embeddings/EmbeddingService.js.map +1 -1
  24. package/dist/embeddings/InMemoryVectorAdapter.d.ts +15 -0
  25. package/dist/embeddings/InMemoryVectorAdapter.d.ts.map +1 -0
  26. package/dist/embeddings/InMemoryVectorAdapter.js +58 -0
  27. package/dist/embeddings/InMemoryVectorAdapter.js.map +1 -0
  28. package/dist/embeddings/SqliteVecAdapter.d.ts +15 -0
  29. package/dist/embeddings/SqliteVecAdapter.d.ts.map +1 -0
  30. package/dist/embeddings/SqliteVecAdapter.js +107 -0
  31. package/dist/embeddings/SqliteVecAdapter.js.map +1 -0
  32. package/dist/embeddings/VectorExtension.d.ts +2 -4
  33. package/dist/embeddings/VectorExtension.d.ts.map +1 -1
  34. package/dist/embeddings/VectorExtension.js.map +1 -1
  35. package/dist/embeddings/VectorSearchAdapter.d.ts +17 -0
  36. package/dist/embeddings/VectorSearchAdapter.d.ts.map +1 -0
  37. package/dist/embeddings/VectorSearchAdapter.js +2 -0
  38. package/dist/embeddings/VectorSearchAdapter.js.map +1 -0
  39. package/dist/embeddings/index.d.ts +6 -1
  40. package/dist/embeddings/index.d.ts.map +1 -1
  41. package/dist/embeddings/index.js +4 -1
  42. package/dist/embeddings/index.js.map +1 -1
  43. package/dist/knowledge-graph/ContentHasher.d.ts +4 -0
  44. package/dist/knowledge-graph/ContentHasher.d.ts.map +1 -0
  45. package/dist/knowledge-graph/ContentHasher.js +8 -0
  46. package/dist/knowledge-graph/ContentHasher.js.map +1 -0
  47. package/dist/knowledge-graph/KGSearchEngine.d.ts +36 -0
  48. package/dist/knowledge-graph/KGSearchEngine.d.ts.map +1 -0
  49. package/dist/knowledge-graph/KGSearchEngine.js +257 -0
  50. package/dist/knowledge-graph/KGSearchEngine.js.map +1 -0
  51. package/dist/knowledge-graph/index.d.ts +18 -7
  52. package/dist/knowledge-graph/index.d.ts.map +1 -1
  53. package/dist/knowledge-graph/index.js +147 -242
  54. package/dist/knowledge-graph/index.js.map +1 -1
  55. package/dist/mcp/ServerInitializer.d.ts.map +1 -1
  56. package/dist/mcp/ServerInitializer.js +1 -1
  57. package/dist/mcp/ServerInitializer.js.map +1 -1
  58. package/dist/mcp/StdinBufferManager.d.ts +11 -0
  59. package/dist/mcp/StdinBufferManager.d.ts.map +1 -0
  60. package/dist/mcp/StdinBufferManager.js +62 -0
  61. package/dist/mcp/StdinBufferManager.js.map +1 -0
  62. package/dist/mcp/daemon/StdioProxyClient.d.ts.map +1 -1
  63. package/dist/mcp/daemon/StdioProxyClient.js +6 -0
  64. package/dist/mcp/daemon/StdioProxyClient.js.map +1 -1
  65. package/dist/mcp/handlers/HookToolHandler.d.ts +10 -0
  66. package/dist/mcp/handlers/HookToolHandler.d.ts.map +1 -0
  67. package/dist/mcp/handlers/HookToolHandler.js +92 -0
  68. package/dist/mcp/handlers/HookToolHandler.js.map +1 -0
  69. package/dist/mcp/handlers/MemoryToolHandler.d.ts +21 -0
  70. package/dist/mcp/handlers/MemoryToolHandler.d.ts.map +1 -0
  71. package/dist/mcp/handlers/MemoryToolHandler.js +430 -0
  72. package/dist/mcp/handlers/MemoryToolHandler.js.map +1 -0
  73. package/dist/mcp/handlers/SystemToolHandler.d.ts +14 -0
  74. package/dist/mcp/handlers/SystemToolHandler.d.ts.map +1 -0
  75. package/dist/mcp/handlers/SystemToolHandler.js +224 -0
  76. package/dist/mcp/handlers/SystemToolHandler.js.map +1 -0
  77. package/dist/mcp/handlers/ToolHandlers.d.ts +4 -17
  78. package/dist/mcp/handlers/ToolHandlers.d.ts.map +1 -1
  79. package/dist/mcp/handlers/ToolHandlers.js +19 -689
  80. package/dist/mcp/handlers/ToolHandlers.js.map +1 -1
  81. package/dist/mcp/handlers/index.d.ts +3 -0
  82. package/dist/mcp/handlers/index.d.ts.map +1 -1
  83. package/dist/mcp/handlers/index.js +3 -0
  84. package/dist/mcp/handlers/index.js.map +1 -1
  85. package/dist/mcp/server-bootstrap.js +24 -59
  86. package/dist/mcp/server-bootstrap.js.map +1 -1
  87. package/dist/mcp/tools/create-entities.d.ts.map +1 -1
  88. package/dist/mcp/tools/create-entities.js +18 -24
  89. package/dist/mcp/tools/create-entities.js.map +1 -1
  90. package/dist/memory/MemorySearchEngine.d.ts +17 -0
  91. package/dist/memory/MemorySearchEngine.d.ts.map +1 -0
  92. package/dist/memory/MemorySearchEngine.js +88 -0
  93. package/dist/memory/MemorySearchEngine.js.map +1 -0
  94. package/dist/memory/ProactiveRecaller.d.ts +26 -0
  95. package/dist/memory/ProactiveRecaller.d.ts.map +1 -0
  96. package/dist/memory/ProactiveRecaller.js +96 -0
  97. package/dist/memory/ProactiveRecaller.js.map +1 -0
  98. package/dist/memory/UnifiedMemoryStore.d.ts +1 -0
  99. package/dist/memory/UnifiedMemoryStore.d.ts.map +1 -1
  100. package/dist/memory/UnifiedMemoryStore.js +7 -63
  101. package/dist/memory/UnifiedMemoryStore.js.map +1 -1
  102. package/dist/memory/index.d.ts +3 -0
  103. package/dist/memory/index.d.ts.map +1 -1
  104. package/dist/memory/index.js +2 -0
  105. package/dist/memory/index.js.map +1 -1
  106. package/dist/utils/index.d.ts +0 -2
  107. package/dist/utils/index.d.ts.map +1 -1
  108. package/dist/utils/index.js +0 -2
  109. package/dist/utils/index.js.map +1 -1
  110. package/dist/utils/tracing/index.d.ts +0 -1
  111. package/dist/utils/tracing/index.d.ts.map +1 -1
  112. package/dist/utils/tracing/index.js +0 -1
  113. package/dist/utils/tracing/index.js.map +1 -1
  114. package/package.json +2 -11
  115. package/plugin.json +1 -1
  116. package/scripts/hooks/__tests__/post-tool-use-recall.test.js +192 -0
  117. package/scripts/hooks/__tests__/session-start-recall.test.js +86 -0
  118. package/scripts/hooks/post-tool-use-recall-utils.js +74 -0
  119. package/scripts/hooks/post-tool-use.js +79 -0
  120. package/scripts/hooks/session-start-recall-utils.js +40 -0
  121. package/scripts/hooks/session-start.js +66 -0
  122. package/scripts/hooks/templates/planning-template.md +46 -0
@@ -0,0 +1,86 @@
1
+ import { describe, it, expect } from 'vitest';
2
+ import { buildSessionRecallQuery, formatRecallOutput } from '../session-start-recall-utils.js';
3
+
4
+ describe('buildSessionRecallQuery', () => {
5
+ it('combines project name and commits', () => {
6
+ const result = buildSessionRecallQuery('my-project', ['add login page', 'fix header bug']);
7
+ expect(result).toBe('my-project add login page fix header bug');
8
+ });
9
+
10
+ it('handles empty commits array', () => {
11
+ const result = buildSessionRecallQuery('my-project', []);
12
+ expect(result).toBe('my-project');
13
+ });
14
+
15
+ it('handles undefined commits', () => {
16
+ const result = buildSessionRecallQuery('my-project');
17
+ expect(result).toBe('my-project');
18
+ });
19
+
20
+ it('strips conventional commit prefixes', () => {
21
+ const commits = [
22
+ 'fix: resolve null pointer',
23
+ 'feat(auth): add OAuth support',
24
+ 'chore(deps): update dependencies',
25
+ 'refactor: simplify logic',
26
+ ];
27
+ const result = buildSessionRecallQuery('app', commits);
28
+ expect(result).toBe('app resolve null pointer add OAuth support update dependencies simplify logic');
29
+ });
30
+
31
+ it('filters out empty strings after stripping prefixes', () => {
32
+ const commits = ['fix:', 'feat(scope): '];
33
+ const result = buildSessionRecallQuery('app', commits);
34
+ expect(result).toBe('app');
35
+ });
36
+ });
37
+
38
+ describe('formatRecallOutput', () => {
39
+ it('formats results with similarity percentage', () => {
40
+ const results = [
41
+ { name: 'Entity1', observations: ['obs1', 'obs2'], similarity: 0.85 },
42
+ ];
43
+ const output = formatRecallOutput(results);
44
+ expect(output).toBe(' - Entity1 (85%): obs1; obs2');
45
+ });
46
+
47
+ it('returns empty string for empty results', () => {
48
+ expect(formatRecallOutput([])).toBe('');
49
+ });
50
+
51
+ it('returns empty string for null results', () => {
52
+ expect(formatRecallOutput(null)).toBe('');
53
+ });
54
+
55
+ it('returns empty string for undefined results', () => {
56
+ expect(formatRecallOutput(undefined)).toBe('');
57
+ });
58
+
59
+ it('limits observations to 2', () => {
60
+ const results = [
61
+ { name: 'Entity1', observations: ['obs1', 'obs2', 'obs3', 'obs4'], similarity: 0.7 },
62
+ ];
63
+ const output = formatRecallOutput(results);
64
+ expect(output).toBe(' - Entity1 (70%): obs1; obs2');
65
+ });
66
+
67
+ it('formats multiple results on separate lines', () => {
68
+ const results = [
69
+ { name: 'A', observations: ['a1'], similarity: 0.9 },
70
+ { name: 'B', observations: ['b1', 'b2'], similarity: 0.6 },
71
+ ];
72
+ const output = formatRecallOutput(results);
73
+ const lines = output.split('\n');
74
+ expect(lines).toHaveLength(2);
75
+ expect(lines[0]).toBe(' - A (90%): a1');
76
+ expect(lines[1]).toBe(' - B (60%): b1; b2');
77
+ });
78
+
79
+ it('rounds similarity percentage', () => {
80
+ const results = [
81
+ { name: 'X', observations: ['x1'], similarity: 0.456 },
82
+ ];
83
+ const output = formatRecallOutput(results);
84
+ expect(output).toBe(' - X (46%): x1');
85
+ });
86
+ });
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Post-Tool-Use Recall Utilities
3
+ *
4
+ * Pure functions for detecting test failures and errors in tool output,
5
+ * used by the proactive recall system in post-tool-use.js.
6
+ */
7
+
8
+ const TEST_PATTERNS = [
9
+ /vitest\s*(run|watch)?/i,
10
+ /jest\b/i,
11
+ /npm\s+test/i,
12
+ /npm\s+run\s+test/i,
13
+ /npx\s+vitest/i,
14
+ /npx\s+jest/i,
15
+ /pytest\b/i,
16
+ /bun\s+test/i,
17
+ /mocha\b/i,
18
+ ];
19
+
20
+ /**
21
+ * Check if a command string is a test runner invocation.
22
+ * @param {string} command - Shell command to check
23
+ * @returns {boolean}
24
+ */
25
+ export function isTestCommand(command) {
26
+ if (!command) return false;
27
+ return TEST_PATTERNS.some(p => p.test(command));
28
+ }
29
+
30
+ /**
31
+ * Extract test failure context (test name + error message) from test output.
32
+ * Returns null if no failure is detected.
33
+ * @param {string} output - Test runner stdout/stderr
34
+ * @returns {{ testName: string, errorMessage: string } | null}
35
+ */
36
+ export function extractTestFailureContext(output) {
37
+ if (!output) return null;
38
+ const hasFailure = /FAIL|failed|failing|\u2715|\u2717|error/i.test(output);
39
+ if (!hasFailure) return null;
40
+
41
+ const fileMatch = output.match(/FAIL\s+(\S+\.(?:test|spec)\.\S+)/i)
42
+ || output.match(/(\S+\.(?:test|spec)\.\S+)/i);
43
+ const testName = fileMatch ? fileMatch[1] : 'unknown test';
44
+
45
+ const errorMatch = output.match(/(?:Error|AssertionError|AssertError|expect).*$/m)
46
+ || output.match(/(?:\u2715|\u2717)\s*(.+)$/m);
47
+ const errorMessage = errorMatch ? errorMatch[0].trim() : 'test failed';
48
+
49
+ return { testName, errorMessage };
50
+ }
51
+
52
+ /**
53
+ * Build a search query from a test failure context.
54
+ * Strips path and test/spec suffix from the test file name.
55
+ * @param {string} testName - Test file name or path
56
+ * @param {string} errorMessage - Error message
57
+ * @returns {string}
58
+ */
59
+ export function buildTestFailureQuery(testName, errorMessage) {
60
+ const shortName = testName.replace(/^.*[/\\]/, '').replace(/\.(test|spec)\.\w+$/, '');
61
+ return `${shortName} ${errorMessage}`.trim();
62
+ }
63
+
64
+ /**
65
+ * Build a search query from an error type and message.
66
+ * Uses only the first line of the error message.
67
+ * @param {string} errorType - Error class name (e.g. "TypeError")
68
+ * @param {string} errorMessage - Error message (may be multiline)
69
+ * @returns {string}
70
+ */
71
+ export function buildErrorQuery(errorType, errorMessage) {
72
+ const firstLine = (errorMessage || '').split('\n')[0].trim();
73
+ return `${errorType || 'Error'} ${firstLine}`.trim();
74
+ }
@@ -34,6 +34,7 @@ import {
34
34
  updateEntityMetadata,
35
35
  addObservation,
36
36
  } from './hook-utils.js';
37
+ import { isTestCommand, extractTestFailureContext, buildTestFailureQuery, buildErrorQuery } from './post-tool-use-recall-utils.js';
37
38
  import fs from 'fs';
38
39
  import path from 'path';
39
40
 
@@ -729,6 +730,81 @@ function detectPlanFile(toolData) {
729
730
  }
730
731
  }
731
732
 
733
+ // ============================================================================
734
+ // Proactive Recall on Test Failure / Error Detection
735
+ // ============================================================================
736
+
737
+ /**
738
+ * Trigger proactive recall on test failure or error detection.
739
+ * Writes results to proactive-recall.json for HookToolHandler.
740
+ * @param {Object} toolData - Normalized tool data
741
+ */
742
+ function triggerProactiveRecall(toolData) {
743
+ try {
744
+ const recallFile = path.join(STATE_DIR, 'proactive-recall.json');
745
+ let query = null;
746
+ let trigger = null;
747
+
748
+ // Test failure detection
749
+ if (toolData.toolName === 'Bash' && toolData.arguments?.command) {
750
+ if (isTestCommand(toolData.arguments.command) && !toolData.success) {
751
+ const ctx = extractTestFailureContext(toolData._raw?.output || '');
752
+ if (ctx) {
753
+ query = buildTestFailureQuery(ctx.testName, ctx.errorMessage);
754
+ trigger = 'test-failure';
755
+ }
756
+ }
757
+ }
758
+
759
+ // Error detection (non-test failures)
760
+ if (!trigger && !toolData.success && toolData._raw?.output) {
761
+ const errorMatch = toolData._raw.output.match(/(\w*Error):\s*(.+)/);
762
+ if (errorMatch) {
763
+ query = buildErrorQuery(errorMatch[1], errorMatch[2]);
764
+ trigger = 'error-detection';
765
+ }
766
+ }
767
+
768
+ if (!query || !trigger) return;
769
+
770
+ // Build FTS5 query
771
+ const ftsTokens = query.split(/\s+/)
772
+ .filter(t => t.length > 2)
773
+ .slice(0, 8)
774
+ .map(t => `"${t.replace(/"/g, '""')}"*`)
775
+ .join(' OR ');
776
+
777
+ if (!ftsTokens) return;
778
+
779
+ const sql = `
780
+ SELECT e.name,
781
+ (SELECT json_group_array(content) FROM observations o WHERE o.entity_id = e.id) as observations_json
782
+ FROM entities e
783
+ JOIN entities_fts ON entities_fts.rowid = e.id
784
+ WHERE entities_fts MATCH ?
785
+ ORDER BY bm25(entities_fts, 10.0, 5.0)
786
+ LIMIT 3
787
+ `;
788
+
789
+ const result = sqliteQueryJSON(MEMESH_DB_PATH, sql, [ftsTokens]);
790
+ if (!result || result.length === 0) return;
791
+
792
+ const recallData = {
793
+ trigger,
794
+ query,
795
+ timestamp: Date.now(),
796
+ results: result.map(r => ({
797
+ name: r.name,
798
+ observations: JSON.parse(r.observations_json || '[]').filter(Boolean).slice(0, 2),
799
+ })),
800
+ };
801
+
802
+ writeJSONFile(recallFile, recallData);
803
+ } catch (error) {
804
+ logError('proactive-recall-trigger', error);
805
+ }
806
+ }
807
+
732
808
  // ============================================================================
733
809
  // Main PostToolUse Logic
734
810
  // ============================================================================
@@ -778,6 +854,9 @@ async function postToolUse() {
778
854
  // Detect anomalies
779
855
  const anomalies = detectAnomalies(toolData, sessionContext);
780
856
 
857
+ // Trigger proactive recall on test failure or error
858
+ triggerProactiveRecall(toolData);
859
+
781
860
  // Fire async writes in parallel
782
861
  const pendingWrites = [contextWritePromise];
783
862
 
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Session-Start Recall Utilities
3
+ *
4
+ * Utility functions for proactive memory recall during session start.
5
+ * Builds search queries from project context and formats recall results.
6
+ */
7
+
8
+ /**
9
+ * Build FTS5 search query from project name and recent commits.
10
+ * Strips conventional commit prefixes (fix:, feat(scope):, etc.)
11
+ * @param {string} projectName
12
+ * @param {string[]} recentCommits
13
+ * @returns {string}
14
+ */
15
+ export function buildSessionRecallQuery(projectName, recentCommits = []) {
16
+ const parts = [projectName];
17
+ if (recentCommits.length > 0) {
18
+ const cleaned = recentCommits
19
+ .map(msg => msg.replace(/^(fix|feat|chore|refactor|perf|docs|test|style|ci)\(?[^)]*\)?:\s*/i, '').trim())
20
+ .filter(msg => msg.length > 0);
21
+ parts.push(...cleaned);
22
+ }
23
+ return parts.join(' ').trim();
24
+ }
25
+
26
+ /**
27
+ * Format recall results for hook output display.
28
+ * Shows max 2 observations per entity.
29
+ * @param {Array<{name: string, observations: string[], similarity: number}>} results
30
+ * @returns {string} Formatted output (empty string if no results)
31
+ */
32
+ export function formatRecallOutput(results) {
33
+ if (!results || results.length === 0) return '';
34
+ const lines = results.map(r => {
35
+ const pct = Math.round(r.similarity * 100);
36
+ const obs = r.observations.slice(0, 2).join('; ');
37
+ return ` - ${r.name} (${pct}%): ${obs}`;
38
+ });
39
+ return lines.join('\n');
40
+ }
@@ -27,8 +27,10 @@ import {
27
27
  queryActivePlans,
28
28
  renderTimelineCompact,
29
29
  } from './hook-utils.js';
30
+ import { buildSessionRecallQuery, formatRecallOutput } from './session-start-recall-utils.js';
30
31
  import fs from 'fs';
31
32
  import path from 'path';
33
+ import { execFileSync } from 'child_process';
32
34
 
33
35
  // ============================================================================
34
36
  // File Paths
@@ -444,6 +446,67 @@ function displayActivePlans() {
444
446
  }
445
447
  }
446
448
 
449
+ /**
450
+ * Proactive memory recall — queries KG for memories related to current project.
451
+ * Uses project name + last 3 git commits as search query.
452
+ */
453
+ function recallProactiveMemories() {
454
+ try {
455
+ const projectName = path.basename(process.cwd());
456
+
457
+ let recentCommits = [];
458
+ try {
459
+ const gitOutput = execFileSync('git', ['log', '--oneline', '-3', '--format=%s'], {
460
+ timeout: 3000,
461
+ encoding: 'utf-8',
462
+ cwd: process.cwd(),
463
+ });
464
+ recentCommits = gitOutput.trim().split('\n').filter(Boolean);
465
+ } catch {
466
+ // Not a git repo or no commits
467
+ }
468
+
469
+ const query = buildSessionRecallQuery(projectName, recentCommits);
470
+ if (!query) return;
471
+
472
+ // Build FTS5 tokens
473
+ const ftsTokens = query.split(/\s+/)
474
+ .filter(t => t.length > 2)
475
+ .slice(0, 10)
476
+ .map(t => `"${t.replace(/"/g, '""')}"*`)
477
+ .join(' OR ');
478
+
479
+ if (!ftsTokens) return;
480
+
481
+ const sql = `
482
+ SELECT e.name, e.type,
483
+ (SELECT json_group_array(content) FROM observations o WHERE o.entity_id = e.id) as observations_json
484
+ FROM entities e
485
+ JOIN entities_fts ON entities_fts.rowid = e.id
486
+ WHERE entities_fts MATCH ?
487
+ ORDER BY bm25(entities_fts, 10.0, 5.0)
488
+ LIMIT 5
489
+ `;
490
+
491
+ const result = sqliteQueryJSON(MEMESH_DB_PATH, sql, [ftsTokens]);
492
+ if (!result || result.length === 0) return;
493
+
494
+ const formatted = result.map(r => ({
495
+ name: r.name,
496
+ observations: JSON.parse(r.observations_json || '[]').filter(Boolean).slice(0, 3),
497
+ similarity: 0.5,
498
+ }));
499
+
500
+ const output = formatRecallOutput(formatted);
501
+ if (output) {
502
+ console.log('\n Proactive Memory Recall:');
503
+ console.log(output);
504
+ }
505
+ } catch (error) {
506
+ logError('proactive-recall', error);
507
+ }
508
+ }
509
+
447
510
  function sessionStart() {
448
511
  console.log('\nšŸš€ Smart-Agents Session Started\n');
449
512
 
@@ -458,6 +521,9 @@ function sessionStart() {
458
521
  const recalledMemory = recallRecentKeyPoints();
459
522
  displayRecalledMemory(recalledMemory);
460
523
 
524
+ // Proactive memory recall (project context + recent commits)
525
+ recallProactiveMemories();
526
+
461
527
  // Display active plans (beta)
462
528
  displayActivePlans();
463
529
 
@@ -0,0 +1,46 @@
1
+ ## Required Plan Sections
2
+
3
+ Your plan MUST include all of the following sections. Incomplete plans will be rejected.
4
+
5
+ ### 1. System Design Description (SDD)
6
+ - Component architecture and responsibilities
7
+ - Data flow between components
8
+ - Interface contracts (input/output types)
9
+ - Dependencies and integration points
10
+
11
+ ### 2. Behavior-Driven Design (BDD)
12
+ For EACH feature, write Gherkin scenarios:
13
+
14
+ ```gherkin
15
+ Scenario: [descriptive name]
16
+ Given [precondition]
17
+ When [action]
18
+ Then [expected outcome]
19
+ ```
20
+
21
+ Cover: happy path, error path, edge cases.
22
+
23
+ ### 3. Edge Case Handling
24
+
25
+ | Edge Case | How Handled | Test Coverage |
26
+ |-----------|------------|---------------|
27
+ | [case] | [strategy] | [yes/no] |
28
+
29
+ Include at minimum: empty input, null/undefined, boundary values, concurrent access, timeout, large data.
30
+
31
+ ### 4. Dry-Run Test Plan
32
+ Before dispatching heavy tasks, verify:
33
+ - [ ] Core function compiles (`tsc --noEmit` or `node --check`)
34
+ - [ ] Unit test for critical path passes
35
+ - [ ] Integration point verified with real call
36
+ - [ ] No regressions in existing tests
37
+
38
+ ### 5. Risk Assessment
39
+
40
+ | Risk | Probability | Impact | Mitigation |
41
+ |------|------------|--------|------------|
42
+ | [risk] | High/Med/Low | High/Med/Low | [strategy] |
43
+
44
+ ---
45
+
46
+ **IMPORTANT**: After completing this plan, present it to the user and wait for explicit approval before proceeding to implementation. Do NOT auto-execute.