edsger 0.24.0 → 0.25.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 (30) hide show
  1. package/dist/api/growth.d.ts +59 -0
  2. package/dist/api/growth.js +50 -0
  3. package/dist/commands/growth-analysis/index.d.ts +7 -0
  4. package/dist/commands/growth-analysis/index.js +52 -0
  5. package/dist/commands/workflow/feature-coordinator.js +3 -1
  6. package/dist/index.js +7 -0
  7. package/dist/phases/feature-analysis/index.js +16 -9
  8. package/dist/phases/feature-analysis/outcome.d.ts +14 -0
  9. package/dist/phases/feature-analysis/outcome.js +51 -1
  10. package/dist/phases/feature-analysis/prompts.js +2 -1
  11. package/dist/phases/growth-analysis/agent.d.ts +2 -0
  12. package/dist/phases/growth-analysis/agent.js +105 -0
  13. package/dist/phases/growth-analysis/context.d.ts +21 -0
  14. package/dist/phases/growth-analysis/context.js +68 -0
  15. package/dist/phases/growth-analysis/index.d.ts +24 -0
  16. package/dist/phases/growth-analysis/index.js +53 -0
  17. package/dist/phases/growth-analysis/prompts.d.ts +2 -0
  18. package/dist/phases/growth-analysis/prompts.js +88 -0
  19. package/dist/phases/test-cases-analysis/index.js +15 -7
  20. package/dist/phases/test-cases-analysis/outcome.d.ts +2 -0
  21. package/dist/phases/test-cases-analysis/outcome.js +24 -0
  22. package/dist/phases/test-cases-analysis/prompts.js +1 -0
  23. package/dist/phases/user-stories-analysis/index.js +15 -7
  24. package/dist/phases/user-stories-analysis/outcome.d.ts +2 -0
  25. package/dist/phases/user-stories-analysis/outcome.js +24 -0
  26. package/dist/phases/user-stories-analysis/prompts.js +1 -0
  27. package/dist/types/features.d.ts +2 -2
  28. package/dist/types/index.d.ts +3 -2
  29. package/dist/utils/formatters.js +2 -2
  30. package/package.json +1 -1
@@ -0,0 +1,59 @@
1
+ export interface GrowthCampaign {
2
+ id: string;
3
+ product_id: string;
4
+ analysis_id: string | null;
5
+ channel: string;
6
+ title: string;
7
+ content: string;
8
+ status: string;
9
+ published_url: string | null;
10
+ published_at: string | null;
11
+ created_at: string;
12
+ updated_at: string;
13
+ }
14
+ export interface GrowthAnalysis {
15
+ id: string;
16
+ product_id: string;
17
+ analysis_content: string;
18
+ target_channels: Array<{
19
+ name: string;
20
+ reason: string;
21
+ audience: string;
22
+ priority: string;
23
+ }>;
24
+ content_suggestions: Array<{
25
+ channel: string;
26
+ title: string;
27
+ content: string;
28
+ rationale: string;
29
+ }>;
30
+ search_context: string | null;
31
+ status: string;
32
+ created_at: string;
33
+ updated_at: string;
34
+ }
35
+ /**
36
+ * Get growth campaigns for a product via MCP
37
+ */
38
+ export declare function getGrowthCampaigns(productId: string, verbose?: boolean): Promise<GrowthCampaign[]>;
39
+ /**
40
+ * Save a growth analysis result via MCP
41
+ */
42
+ export declare function saveGrowthAnalysis(analysis: {
43
+ product_id: string;
44
+ analysis_content: string;
45
+ target_channels: Array<{
46
+ name: string;
47
+ reason: string;
48
+ audience: string;
49
+ priority: string;
50
+ }>;
51
+ content_suggestions: Array<{
52
+ channel: string;
53
+ title: string;
54
+ content: string;
55
+ rationale: string;
56
+ }>;
57
+ search_context: string | null;
58
+ status: string;
59
+ }, verbose?: boolean): Promise<GrowthAnalysis | null>;
@@ -0,0 +1,50 @@
1
+ import { logInfo, logError } from '../utils/logger.js';
2
+ import { callMcpEndpoint } from './mcp-client.js';
3
+ /**
4
+ * Get growth campaigns for a product via MCP
5
+ */
6
+ export async function getGrowthCampaigns(productId, verbose) {
7
+ if (verbose) {
8
+ logInfo(`Fetching growth campaigns for product: ${productId}`);
9
+ }
10
+ try {
11
+ const result = (await callMcpEndpoint('growth/campaigns', {
12
+ product_id: productId,
13
+ }));
14
+ const text = result.content?.[0]?.text || '[]';
15
+ try {
16
+ return JSON.parse(text);
17
+ }
18
+ catch {
19
+ return [];
20
+ }
21
+ }
22
+ catch (error) {
23
+ if (verbose) {
24
+ logError(`Failed to fetch growth campaigns: ${error instanceof Error ? error.message : String(error)}`);
25
+ }
26
+ return [];
27
+ }
28
+ }
29
+ /**
30
+ * Save a growth analysis result via MCP
31
+ */
32
+ export async function saveGrowthAnalysis(analysis, verbose) {
33
+ if (verbose) {
34
+ logInfo(`Saving growth analysis for product: ${analysis.product_id}`);
35
+ }
36
+ try {
37
+ const result = (await callMcpEndpoint('growth/save_analysis', analysis));
38
+ const text = result.content?.[0]?.text || 'null';
39
+ try {
40
+ return JSON.parse(text);
41
+ }
42
+ catch {
43
+ return null;
44
+ }
45
+ }
46
+ catch (error) {
47
+ logError(`Failed to save growth analysis: ${error instanceof Error ? error.message : String(error)}`);
48
+ return null;
49
+ }
50
+ }
@@ -0,0 +1,7 @@
1
+ import { CliOptions } from '../../types/index.js';
2
+ /**
3
+ * Run AI-powered growth analysis for a product.
4
+ * Analyzes the product, reviews previous campaigns, and generates
5
+ * content suggestions for different channels.
6
+ */
7
+ export declare const runGrowthAnalysis: (options: CliOptions) => Promise<void>;
@@ -0,0 +1,52 @@
1
+ import { logInfo, logError, logSuccess } from '../../utils/logger.js';
2
+ import { validateConfiguration } from '../../utils/validation.js';
3
+ import { analyseGrowth, } from '../../phases/growth-analysis/index.js';
4
+ /**
5
+ * Run AI-powered growth analysis for a product.
6
+ * Analyzes the product, reviews previous campaigns, and generates
7
+ * content suggestions for different channels.
8
+ */
9
+ export const runGrowthAnalysis = async (options) => {
10
+ const productId = options.growthAnalysis;
11
+ if (!productId) {
12
+ throw new Error('Product ID is required for growth analysis');
13
+ }
14
+ const config = validateConfiguration(options);
15
+ logInfo(`Starting growth analysis for product: ${productId}`);
16
+ try {
17
+ const result = await analyseGrowth({
18
+ productId,
19
+ verbose: options.verbose,
20
+ }, config);
21
+ if (result.status === 'success') {
22
+ logSuccess('Growth analysis completed successfully!');
23
+ logInfo(` Recommended channels: ${result.targetChannels.length}`);
24
+ logInfo(` Content suggestions: ${result.contentSuggestions.length}`);
25
+ if (result.analysisId) {
26
+ logInfo(` Analysis saved with ID: ${result.analysisId}`);
27
+ }
28
+ // Display summary
29
+ if (result.targetChannels.length > 0) {
30
+ logInfo('\nRecommended Channels:');
31
+ result.targetChannels.forEach((ch) => {
32
+ logInfo(` [${ch.priority.toUpperCase()}] ${ch.name} - ${ch.reason}`);
33
+ });
34
+ }
35
+ if (result.contentSuggestions.length > 0) {
36
+ logInfo('\nContent Suggestions:');
37
+ result.contentSuggestions.forEach((s, i) => {
38
+ logInfo(` ${i + 1}. [${s.channel}] ${s.title}`);
39
+ });
40
+ }
41
+ logInfo('\nView full results in the Growth tab of your product dashboard.');
42
+ }
43
+ else {
44
+ logError(`Growth analysis failed: ${result.summary}`);
45
+ process.exit(1);
46
+ }
47
+ }
48
+ catch (error) {
49
+ logError(`Growth analysis failed: ${error instanceof Error ? error.message : String(error)}`);
50
+ process.exit(1);
51
+ }
52
+ };
@@ -13,13 +13,15 @@ import { getFeature } from '../../api/features/get-feature.js';
13
13
  import { markWorkflowPhaseCompleted } from '../../api/features/update-feature.js';
14
14
  import { logError, logInfo, logWarning } from '../../utils/logger.js';
15
15
  import { logPhaseResult } from '../../utils/pipeline-logger.js';
16
- import { runFeatureAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeRefinePhase, runCodeReviewPhase, runAutonomousPhase, } from './executors/phase-executor.js';
16
+ import { runFeatureAnalysisPhase, runUserStoriesAnalysisPhase, runTestCasesAnalysisPhase, runTechnicalDesignPhase, runBranchPlanningPhase, runCodeImplementationPhase, runFunctionalTestingPhase, runCodeRefinePhase, runCodeReviewPhase, runAutonomousPhase, } from './executors/phase-executor.js';
17
17
  /**
18
18
  * Map workflow phase names (underscore format) to phase runner functions
19
19
  * Note: code_refine includes built-in verification loop (similar to technical_design)
20
20
  */
21
21
  const PHASE_RUNNERS = {
22
22
  feature_analysis: runFeatureAnalysisPhase,
23
+ user_stories_analysis: runUserStoriesAnalysisPhase,
24
+ test_cases_analysis: runTestCasesAnalysisPhase,
23
25
  technical_design: runTechnicalDesignPhase,
24
26
  branch_planning: runBranchPlanningPhase,
25
27
  code_implementation: runCodeImplementationPhase,
package/dist/index.js CHANGED
@@ -9,6 +9,7 @@ import { runWorkflow } from './commands/workflow/index.js';
9
9
  import { runCodeReview } from './commands/code-review/index.js';
10
10
  import { runRefactor } from './commands/refactor/refactor.js';
11
11
  import { runInit } from './commands/init/index.js';
12
+ import { runGrowthAnalysis } from './commands/growth-analysis/index.js';
12
13
  // Get package.json version dynamically
13
14
  const __filename = fileURLToPath(import.meta.url);
14
15
  const __dirname = dirname(__filename);
@@ -28,6 +29,7 @@ program
28
29
  .option('-f, --files <patterns...>', 'Review specific file patterns')
29
30
  .option('--refactor', 'Refactor code in current directory')
30
31
  .option('--init', 'Initialize .edsger directory with project templates')
32
+ .option('--growth-analysis <productId>', 'Run AI-powered growth analysis for a product')
31
33
  .option('-c, --config <path>', 'Path to config file')
32
34
  .option('-v, --verbose', 'Verbose output');
33
35
  program.action(async (options) => {
@@ -45,6 +47,11 @@ export const runEdsger = async (options) => {
45
47
  await runInit(options);
46
48
  return;
47
49
  }
50
+ // Handle growth analysis mode
51
+ if (options.growthAnalysis) {
52
+ await runGrowthAnalysis(options);
53
+ return;
54
+ }
48
55
  // Handle refactor mode
49
56
  if (options.refactor) {
50
57
  await runRefactor(options);
@@ -3,7 +3,7 @@ import { prepareAnalysisContext } from './context.js';
3
3
  import { createFeatureAnalysisSystemPrompt } from './prompts.js';
4
4
  import { executeAnalysisQuery } from './agent.js';
5
5
  import { performVerificationCycle } from '../feature-analysis-verification/index.js';
6
- import { deleteArtifacts, deleteSpecificArtifacts, updateArtifactsToReady, saveAnalysisArtifactsAsDraft, buildAnalysisResult, } from './outcome.js';
6
+ import { deleteArtifacts, deleteSpecificArtifacts, updateArtifactsToReady, saveAnalysisArtifactsAsDraft, buildAnalysisResult, resetReadyArtifactsToDraft, getAllDraftArtifactIds, } from './outcome.js';
7
7
  import { logFeaturePhaseEvent } from '../../services/audit-logs.js';
8
8
  export const analyseFeature = async (options, config, checklistContext) => {
9
9
  const { featureId, verbose } = options;
@@ -11,6 +11,11 @@ export const analyseFeature = async (options, config, checklistContext) => {
11
11
  logInfo(`Starting feature analysis for feature ID: ${featureId}`);
12
12
  }
13
13
  try {
14
+ // Reset ready artifacts to draft so AI can manage them on re-run
15
+ const resetResult = await resetReadyArtifactsToDraft(featureId, verbose);
16
+ if (verbose && (resetResult.resetUserStories > 0 || resetResult.resetTestCases > 0)) {
17
+ logInfo(`✅ Reset ${resetResult.resetUserStories} user stories and ${resetResult.resetTestCases} test cases to draft for re-analysis`);
18
+ }
14
19
  const context = await prepareAnalysisContext(featureId, checklistContext, verbose);
15
20
  const systemPrompt = createFeatureAnalysisSystemPrompt();
16
21
  const initialAnalysisPrompt = context.analysisPrompt;
@@ -81,12 +86,13 @@ export const analyseFeature = async (options, config, checklistContext) => {
81
86
  // Perform verification cycle
82
87
  const verificationCycle = await performVerificationCycle(structuredAnalysisResult, checklistContext || null, context.featureContext, config, currentIteration, maxIterations, featureId, verbose);
83
88
  verificationResult = verificationCycle.verificationResult;
84
- // If verification passed, update artifacts to ready and exit
89
+ // If verification passed, update ALL remaining draft artifacts to ready and exit
85
90
  if (verificationCycle.passed) {
86
91
  if (verbose) {
87
- logInfo('✅ Verification passed! Updating artifacts to ready status...');
92
+ logInfo('✅ Verification passed! Updating all draft artifacts to ready status...');
88
93
  }
89
- await updateArtifactsToReady(currentDraftUserStoryIds, currentDraftTestCaseIds, verbose);
94
+ const allDrafts = await getAllDraftArtifactIds(featureId, verbose);
95
+ await updateArtifactsToReady(allDrafts.userStoryIds, allDrafts.testCaseIds, verbose);
90
96
  break;
91
97
  }
92
98
  // Verification failed
@@ -112,16 +118,17 @@ export const analyseFeature = async (options, config, checklistContext) => {
112
118
  if (!structuredAnalysisResult) {
113
119
  throw new Error('No analysis results received');
114
120
  }
115
- // If no checklist was used, update draft artifacts to ready now
121
+ // If no checklist was used, update all draft artifacts to ready now
116
122
  if (!checklistContext ||
117
123
  checklistContext.checklists.length === 0 ||
118
124
  !verificationResult) {
119
- if (currentDraftUserStoryIds.length > 0 ||
120
- currentDraftTestCaseIds.length > 0) {
125
+ const allDrafts = await getAllDraftArtifactIds(featureId, verbose);
126
+ if (allDrafts.userStoryIds.length > 0 ||
127
+ allDrafts.testCaseIds.length > 0) {
121
128
  if (verbose) {
122
- logInfo('✅ No checklist verification needed. Updating artifacts to ready status...');
129
+ logInfo('✅ No checklist verification needed. Updating all draft artifacts to ready status...');
123
130
  }
124
- await updateArtifactsToReady(currentDraftUserStoryIds, currentDraftTestCaseIds, verbose);
131
+ await updateArtifactsToReady(allDrafts.userStoryIds, allDrafts.testCaseIds, verbose);
125
132
  }
126
133
  }
127
134
  // Check if verification failed after all iterations
@@ -1,5 +1,19 @@
1
1
  import { FeatureAnalysisResult } from '../../types/index.js';
2
2
  import { FeatureAnalysisContext } from './context.js';
3
+ /**
4
+ * Reset ready user stories and test cases to draft for re-analysis
5
+ */
6
+ export declare function resetReadyArtifactsToDraft(featureId: string, verbose?: boolean): Promise<{
7
+ resetUserStories: number;
8
+ resetTestCases: number;
9
+ }>;
10
+ /**
11
+ * Get all draft artifact IDs for a feature
12
+ */
13
+ export declare function getAllDraftArtifactIds(featureId: string, verbose?: boolean): Promise<{
14
+ userStoryIds: string[];
15
+ testCaseIds: string[];
16
+ }>;
3
17
  /**
4
18
  * Delete artifacts after verification failure
5
19
  */
@@ -1,6 +1,56 @@
1
1
  import { logInfo, logError } from '../../utils/logger.js';
2
2
  import { batchUpdateUserStoryStatus, batchUpdateTestCaseStatus, batchDeleteUserStories, batchDeleteTestCases, getUserStories, getTestCases, } from '../../api/features/index.js';
3
3
  import { callMcpEndpoint } from '../../api/mcp-client.js';
4
+ /**
5
+ * Reset ready user stories and test cases to draft for re-analysis
6
+ */
7
+ export async function resetReadyArtifactsToDraft(featureId, verbose) {
8
+ const [stories, testCases] = await Promise.all([
9
+ getUserStories(featureId, false),
10
+ getTestCases(featureId, false),
11
+ ]);
12
+ const readyStoryIds = stories
13
+ .filter((s) => s.status === 'ready')
14
+ .map((s) => s.id);
15
+ const readyTestCaseIds = testCases
16
+ .filter((tc) => tc.status === 'ready')
17
+ .map((tc) => tc.id);
18
+ if (readyStoryIds.length > 0) {
19
+ if (verbose) {
20
+ logInfo(`🔄 Resetting ${readyStoryIds.length} ready user stories to draft for re-analysis...`);
21
+ }
22
+ await batchUpdateUserStoryStatus(readyStoryIds, 'draft', verbose);
23
+ }
24
+ if (readyTestCaseIds.length > 0) {
25
+ if (verbose) {
26
+ logInfo(`🔄 Resetting ${readyTestCaseIds.length} ready test cases to draft for re-analysis...`);
27
+ }
28
+ await batchUpdateTestCaseStatus(readyTestCaseIds, 'draft', verbose);
29
+ }
30
+ return {
31
+ resetUserStories: readyStoryIds.length,
32
+ resetTestCases: readyTestCaseIds.length,
33
+ };
34
+ }
35
+ /**
36
+ * Get all draft artifact IDs for a feature
37
+ */
38
+ export async function getAllDraftArtifactIds(featureId, verbose) {
39
+ const [stories, testCases] = await Promise.all([
40
+ getUserStories(featureId, false),
41
+ getTestCases(featureId, false),
42
+ ]);
43
+ const userStoryIds = stories
44
+ .filter((s) => s.status === 'draft')
45
+ .map((s) => s.id);
46
+ const testCaseIds = testCases
47
+ .filter((tc) => tc.status === 'draft')
48
+ .map((tc) => tc.id);
49
+ if (verbose) {
50
+ logInfo(`Found ${userStoryIds.length} draft user stories and ${testCaseIds.length} draft test cases`);
51
+ }
52
+ return { userStoryIds, testCaseIds };
53
+ }
4
54
  /**
5
55
  * Delete artifacts after verification failure
6
56
  */
@@ -180,7 +230,7 @@ export function buildAnalysisResult(featureId, context, structuredAnalysisResult
180
230
  featureInfo: context.feature,
181
231
  existingUserStories: context.existing_user_stories.map((story) => ({
182
232
  ...story,
183
- status: (story.status === 'ready' ? 'ready' : 'draft'),
233
+ status: (story.status === 'ready' ? 'ready' : story.status === 'approved' ? 'approved' : 'draft'),
184
234
  created_at: story.created_at || new Date().toISOString(),
185
235
  updated_at: story.updated_at || new Date().toISOString(),
186
236
  })),
@@ -79,9 +79,10 @@ When you receive feedback requesting deletion (e.g., "Remove duplicated test cas
79
79
 
80
80
  **IMPORTANT DELETION RESTRICTIONS**:
81
81
  - You can ONLY delete artifacts with status 'draft'
82
- - Artifacts with other statuses (ready, in_progress, done, cancelled) CANNOT be deleted
82
+ - Artifacts with other statuses (ready, approved, in_progress, done, cancelled) CANNOT be deleted
83
83
  - Only artifacts marked with [DELETABLE] in the context can be deleted
84
84
  - Each deletable artifact includes an "ID:" field with the UUID
85
+ - You MUST NOT delete, recreate, or modify artifacts marked with [APPROVED - DO NOT MODIFY]. These have been human-approved and are immutable. Do not create new items that duplicate approved ones.
85
86
 
86
87
  To delete artifacts:
87
88
  1. Find the exact UUID from the "ID:" field in the context (e.g., "a1b2c3d4-e5f6-4a7b-8c9d-0e1f2a3b4c5d")
@@ -0,0 +1,2 @@
1
+ import { EdsgerConfig } from '../../types/index.js';
2
+ export declare function executeGrowthAnalysisQuery(currentPrompt: string, systemPrompt: string, config: EdsgerConfig, verbose?: boolean): Promise<any | null>;
@@ -0,0 +1,105 @@
1
+ import { query } from '@anthropic-ai/claude-agent-sdk';
2
+ import { DEFAULT_MODEL } from '../../constants.js';
3
+ import { logInfo, logError } from '../../utils/logger.js';
4
+ /**
5
+ * Parse JSON result from Claude Code response
6
+ */
7
+ function parseGrowthResult(responseText) {
8
+ try {
9
+ let jsonResult = null;
10
+ // First try to extract JSON from markdown code block
11
+ const jsonBlockMatch = responseText.match(/```json\s*\n([\s\S]*?)\n\s*```/);
12
+ if (jsonBlockMatch) {
13
+ jsonResult = JSON.parse(jsonBlockMatch[1]);
14
+ }
15
+ else {
16
+ // Try to parse the entire response as JSON
17
+ jsonResult = JSON.parse(responseText);
18
+ }
19
+ if (jsonResult && jsonResult.analysis) {
20
+ return { analysis: jsonResult.analysis };
21
+ }
22
+ else {
23
+ return { error: 'Invalid JSON structure' };
24
+ }
25
+ }
26
+ catch (error) {
27
+ return {
28
+ error: `JSON parsing failed: ${error instanceof Error ? error.message : String(error)}`,
29
+ };
30
+ }
31
+ }
32
+ function userMessage(content) {
33
+ return {
34
+ type: 'user',
35
+ message: { role: 'user', content: content },
36
+ };
37
+ }
38
+ async function* prompt(analysisPrompt) {
39
+ yield userMessage(analysisPrompt);
40
+ }
41
+ export async function executeGrowthAnalysisQuery(currentPrompt, systemPrompt, config, verbose) {
42
+ let lastAssistantResponse = '';
43
+ let structuredResult = null;
44
+ for await (const message of query({
45
+ prompt: prompt(currentPrompt),
46
+ options: {
47
+ systemPrompt: {
48
+ type: 'preset',
49
+ preset: 'claude_code',
50
+ append: systemPrompt,
51
+ },
52
+ model: DEFAULT_MODEL,
53
+ maxTurns: 1000,
54
+ permissionMode: 'bypassPermissions',
55
+ },
56
+ })) {
57
+ if (verbose) {
58
+ logInfo(`Received message type: ${message.type}`);
59
+ }
60
+ if (message.type === 'assistant' && message.message?.content) {
61
+ for (const content of message.message.content) {
62
+ if (content.type === 'text') {
63
+ lastAssistantResponse += content.text + '\n';
64
+ if (verbose) {
65
+ console.log(`\n${content.text}`);
66
+ }
67
+ }
68
+ else if (content.type === 'tool_use') {
69
+ if (verbose) {
70
+ console.log(`\n${content.name}: ${content.input.description || 'Running...'}`);
71
+ }
72
+ }
73
+ }
74
+ }
75
+ if (message.type === 'result') {
76
+ if (message.subtype === 'success') {
77
+ logInfo('\nGrowth analysis completed, parsing results...');
78
+ const responseText = message.result || lastAssistantResponse;
79
+ const parsed = parseGrowthResult(responseText);
80
+ if (parsed.error) {
81
+ logError(`Failed to parse growth analysis result: ${parsed.error}`);
82
+ structuredResult = {
83
+ status: 'error',
84
+ analysis_content: 'Failed to parse analysis results',
85
+ target_channels: [],
86
+ content_suggestions: [],
87
+ };
88
+ }
89
+ else {
90
+ structuredResult = parsed.analysis;
91
+ }
92
+ }
93
+ else {
94
+ logError(`\nAnalysis incomplete: ${message.subtype}`);
95
+ if (lastAssistantResponse) {
96
+ const parsed = parseGrowthResult(lastAssistantResponse);
97
+ if (!parsed.error) {
98
+ structuredResult = parsed.analysis;
99
+ }
100
+ }
101
+ }
102
+ }
103
+ }
104
+ return structuredResult;
105
+ }
@@ -0,0 +1,21 @@
1
+ import { type ProductInfo } from '../../api/products.js';
2
+ import { type GrowthCampaign } from '../../api/growth.js';
3
+ export interface GrowthAnalysisContext {
4
+ product: ProductInfo;
5
+ previousCampaigns: GrowthCampaign[];
6
+ }
7
+ /**
8
+ * Fetch all context needed for growth analysis via MCP endpoints
9
+ */
10
+ export declare function fetchGrowthAnalysisContext(productId: string, verbose?: boolean): Promise<GrowthAnalysisContext>;
11
+ /**
12
+ * Format context into a prompt string for the AI
13
+ */
14
+ export declare function formatContextForPrompt(context: GrowthAnalysisContext): string;
15
+ /**
16
+ * Prepare the full analysis prompt with all context
17
+ */
18
+ export declare function prepareGrowthAnalysisContext(productId: string, verbose?: boolean): Promise<{
19
+ context: GrowthAnalysisContext;
20
+ analysisPrompt: string;
21
+ }>;
@@ -0,0 +1,68 @@
1
+ import { logInfo, logError } from '../../utils/logger.js';
2
+ import { getProduct } from '../../api/products.js';
3
+ import { getGrowthCampaigns, } from '../../api/growth.js';
4
+ import { createGrowthAnalysisPromptWithContext } from './prompts.js';
5
+ /**
6
+ * Fetch all context needed for growth analysis via MCP endpoints
7
+ */
8
+ export async function fetchGrowthAnalysisContext(productId, verbose) {
9
+ try {
10
+ if (verbose) {
11
+ logInfo(`Fetching growth analysis context for product: ${productId}`);
12
+ }
13
+ const [product, previousCampaigns] = await Promise.all([
14
+ getProduct(productId, verbose),
15
+ getGrowthCampaigns(productId, verbose),
16
+ ]);
17
+ if (verbose) {
18
+ logInfo(`Growth analysis context fetched:`);
19
+ logInfo(` Product: ${product.name}`);
20
+ logInfo(` Previous campaigns: ${previousCampaigns.length}`);
21
+ }
22
+ return { product, previousCampaigns };
23
+ }
24
+ catch (error) {
25
+ const errorMessage = error instanceof Error ? error.message : String(error);
26
+ logError(`Failed to fetch growth analysis context: ${errorMessage}`);
27
+ throw new Error(`Context fetch failed: ${errorMessage}`);
28
+ }
29
+ }
30
+ /**
31
+ * Format context into a prompt string for the AI
32
+ */
33
+ export function formatContextForPrompt(context) {
34
+ const { product, previousCampaigns } = context;
35
+ const campaignsList = previousCampaigns.length > 0
36
+ ? previousCampaigns
37
+ .map((c, i) => `${i + 1}. **${c.title}** [${c.channel}] (${c.status})
38
+ ${c.content.substring(0, 300)}${c.content.length > 300 ? '...' : ''}
39
+ ${c.published_url ? `Published: ${c.published_url}` : 'Not yet published'}
40
+ Created: ${c.created_at}`)
41
+ .join('\n\n')
42
+ : 'No previous campaigns.';
43
+ return `# Growth Analysis Context
44
+
45
+ ## Product Information
46
+ - **Name**: ${product.name}
47
+ - **Product ID**: ${product.id}
48
+ - **Description**: ${product.description || 'No description provided'}
49
+
50
+ ## Previous Growth Campaigns (${previousCampaigns.length})
51
+ ${campaignsList}
52
+
53
+ ---
54
+
55
+ **Important**: Analyze the product above and create a growth strategy. Each content suggestion must be DIFFERENT from all previous campaigns listed above.`;
56
+ }
57
+ /**
58
+ * Prepare the full analysis prompt with all context
59
+ */
60
+ export async function prepareGrowthAnalysisContext(productId, verbose) {
61
+ if (verbose) {
62
+ logInfo('Fetching growth analysis context via MCP endpoints...');
63
+ }
64
+ const context = await fetchGrowthAnalysisContext(productId, verbose);
65
+ const contextInfo = formatContextForPrompt(context);
66
+ const analysisPrompt = createGrowthAnalysisPromptWithContext(productId, contextInfo);
67
+ return { context, analysisPrompt };
68
+ }
@@ -0,0 +1,24 @@
1
+ import { EdsgerConfig } from '../../types/index.js';
2
+ export interface GrowthAnalysisOptions {
3
+ productId: string;
4
+ verbose?: boolean;
5
+ }
6
+ export interface GrowthAnalysisResult {
7
+ productId: string;
8
+ status: 'success' | 'error';
9
+ summary: string;
10
+ analysisId?: string;
11
+ targetChannels: Array<{
12
+ name: string;
13
+ reason: string;
14
+ audience: string;
15
+ priority: string;
16
+ }>;
17
+ contentSuggestions: Array<{
18
+ channel: string;
19
+ title: string;
20
+ content: string;
21
+ rationale: string;
22
+ }>;
23
+ }
24
+ export declare const analyseGrowth: (options: GrowthAnalysisOptions, config: EdsgerConfig) => Promise<GrowthAnalysisResult>;
@@ -0,0 +1,53 @@
1
+ import { logInfo, logError } from '../../utils/logger.js';
2
+ import { prepareGrowthAnalysisContext } from './context.js';
3
+ import { createGrowthAnalysisSystemPrompt } from './prompts.js';
4
+ import { executeGrowthAnalysisQuery } from './agent.js';
5
+ import { saveGrowthAnalysis } from '../../api/growth.js';
6
+ export const analyseGrowth = async (options, config) => {
7
+ const { productId, verbose } = options;
8
+ if (verbose) {
9
+ logInfo(`Starting growth analysis for product ID: ${productId}`);
10
+ }
11
+ try {
12
+ const { context, analysisPrompt } = await prepareGrowthAnalysisContext(productId, verbose);
13
+ const systemPrompt = createGrowthAnalysisSystemPrompt();
14
+ if (verbose) {
15
+ logInfo('Starting AI query for growth analysis...');
16
+ }
17
+ const analysisResult = await executeGrowthAnalysisQuery(analysisPrompt, systemPrompt, config, verbose);
18
+ if (!analysisResult) {
19
+ throw new Error('No analysis results received');
20
+ }
21
+ // Save analysis result via MCP
22
+ if (verbose) {
23
+ logInfo('Saving growth analysis results...');
24
+ }
25
+ const savedAnalysis = await saveGrowthAnalysis({
26
+ product_id: productId,
27
+ analysis_content: analysisResult.analysis_content || '',
28
+ target_channels: analysisResult.target_channels || [],
29
+ content_suggestions: analysisResult.content_suggestions || [],
30
+ search_context: analysisResult.search_context || null,
31
+ status: 'completed',
32
+ }, verbose);
33
+ logInfo(`Growth analysis completed: ${analysisResult.target_channels?.length || 0} channels, ${analysisResult.content_suggestions?.length || 0} suggestions`);
34
+ return {
35
+ productId,
36
+ status: 'success',
37
+ summary: analysisResult.analysis_content || 'Analysis completed',
38
+ analysisId: savedAnalysis?.id,
39
+ targetChannels: analysisResult.target_channels || [],
40
+ contentSuggestions: analysisResult.content_suggestions || [],
41
+ };
42
+ }
43
+ catch (error) {
44
+ logError(`Growth analysis failed: ${error instanceof Error ? error.message : String(error)}`);
45
+ return {
46
+ productId,
47
+ status: 'error',
48
+ summary: `Analysis failed: ${error instanceof Error ? error.message : String(error)}`,
49
+ targetChannels: [],
50
+ contentSuggestions: [],
51
+ };
52
+ }
53
+ };
@@ -0,0 +1,2 @@
1
+ export declare const createGrowthAnalysisSystemPrompt: () => string;
2
+ export declare const createGrowthAnalysisPromptWithContext: (productId: string, contextInfo: string) => string;
@@ -0,0 +1,88 @@
1
+ export const createGrowthAnalysisSystemPrompt = () => {
2
+ return `You are an expert growth marketer and product strategist. Your task is to analyze a product and generate a comprehensive growth strategy with specific, actionable content recommendations for different channels.
3
+
4
+ **Your Role**: Analyze the product, understand its target audience, review previous campaigns to avoid repetition, and create specific content pieces ready to be published on different platforms.
5
+
6
+ **Analysis Process**:
7
+ 1. **Understand the Product**: Analyze the product's purpose, target audience, unique value proposition, and competitive positioning
8
+ 2. **Review Previous Campaigns**: Study past campaigns to understand what has been done, which channels were used, and avoid creating duplicate content
9
+ 3. **Research Current Trends**: Consider current industry trends, news, and timely topics relevant to the product
10
+ 4. **Identify Target Channels**: Recommend the best channels for growth based on the product's audience
11
+ 5. **Create Content Suggestions**: Generate specific, ready-to-publish content pieces for each recommended channel
12
+
13
+ **Channel Expertise**:
14
+ - Twitter/X: Short-form content, threads, engagement posts
15
+ - Reddit: Community discussions, valuable contributions, AMAs
16
+ - Hacker News: Technical deep-dives, Show HN posts, thoughtful comments
17
+ - LinkedIn: Professional insights, case studies, thought leadership
18
+ - Blog: SEO-optimized articles, tutorials, product updates
19
+ - Product Hunt: Launch posts, feature announcements
20
+ - Dev.to: Technical tutorials, developer-focused content
21
+ - Discord/Slack: Community engagement, support channels
22
+ - Email Newsletter: Subscriber updates, exclusive content
23
+ - YouTube: Video tutorials, demos, product showcases
24
+
25
+ **Content Quality Standards**:
26
+ - Each content piece should be specific, not generic
27
+ - Content should provide genuine value to the audience
28
+ - Adapt tone and format to each channel's culture
29
+ - Include specific hooks, headlines, or opening lines
30
+ - Consider timing and relevance to current events
31
+ - Content must be different from all previous campaigns
32
+
33
+ **CRITICAL - Result Format**:
34
+ You MUST return ONLY a JSON object. Do NOT include any text before or after the JSON.
35
+
36
+ \`\`\`json
37
+ {
38
+ "analysis": {
39
+ "product_id": "PRODUCT_ID",
40
+ "status": "success",
41
+ "analysis_content": "Comprehensive analysis summary including product positioning, target audience insights, and growth strategy overview",
42
+ "target_channels": [
43
+ {
44
+ "name": "Channel name (e.g., twitter, reddit, hackernews, linkedin, blog, producthunt, devto, discord, email, youtube)",
45
+ "reason": "Why this channel is recommended for this product",
46
+ "audience": "Description of the target audience on this channel",
47
+ "priority": "high|medium|low"
48
+ }
49
+ ],
50
+ "content_suggestions": [
51
+ {
52
+ "channel": "channel_name",
53
+ "title": "Content title or headline",
54
+ "content": "The full content piece ready to be published or adapted",
55
+ "rationale": "Why this content will resonate with the audience on this channel"
56
+ }
57
+ ],
58
+ "search_context": "Summary of current trends and context that informed this analysis"
59
+ }
60
+ }
61
+ \`\`\`
62
+
63
+ **Important Guidelines**:
64
+ - Generate at least 3-5 content suggestions across different channels
65
+ - Prioritize channels with high-priority ratings
66
+ - Each content piece should be substantially different from previous campaigns
67
+ - Include a mix of content types (educational, promotional, community-building)
68
+ - Consider the product's current stage and adjust strategy accordingly`;
69
+ };
70
+ export const createGrowthAnalysisPromptWithContext = (productId, contextInfo) => {
71
+ return `Please conduct comprehensive growth analysis for product ID: ${productId}
72
+
73
+ ${contextInfo}
74
+
75
+ ## Analysis Instructions
76
+
77
+ Analyze the product above and create a growth strategy:
78
+
79
+ 1. **Product Analysis**: Understand the product's value proposition, target users, and competitive advantages
80
+ 2. **Previous Campaigns Review**: Study the previous campaigns listed above - DO NOT suggest content that overlaps with what has already been published
81
+ 3. **Channel Strategy**: Identify the most effective channels for reaching this product's target audience
82
+ 4. **Content Creation**: Generate specific, ready-to-publish content pieces for each recommended channel
83
+ 5. **Differentiation**: Ensure every suggested content piece is fresh and different from previous campaigns
84
+
85
+ Focus on actionable, specific content that can be published immediately. Avoid generic advice.
86
+
87
+ Return ONLY the JSON response as specified in your instructions.`;
88
+ };
@@ -3,7 +3,7 @@ import { prepareTestCasesAnalysisContext } from './context.js';
3
3
  import { createTestCasesAnalysisSystemPrompt } from './prompts.js';
4
4
  import { executeTestCasesAnalysisQuery } from './agent.js';
5
5
  import { performVerificationCycle } from '../feature-analysis-verification/index.js';
6
- import { deleteTestCaseArtifacts, deleteSpecificTestCases, updateTestCasesToReady, saveTestCasesAsDraft, buildTestCasesAnalysisResult, } from './outcome.js';
6
+ import { deleteTestCaseArtifacts, deleteSpecificTestCases, updateTestCasesToReady, saveTestCasesAsDraft, buildTestCasesAnalysisResult, resetReadyTestCasesToDraft, getAllDraftTestCaseIds, } from './outcome.js';
7
7
  import { logFeaturePhaseEvent } from '../../services/audit-logs.js';
8
8
  export const analyseTestCases = async (options, config, checklistContext) => {
9
9
  const { featureId, verbose } = options;
@@ -11,6 +11,11 @@ export const analyseTestCases = async (options, config, checklistContext) => {
11
11
  logInfo(`Starting test cases analysis for feature ID: ${featureId}`);
12
12
  }
13
13
  try {
14
+ // Reset ready test cases to draft so AI can manage them on re-run
15
+ const resetCount = await resetReadyTestCasesToDraft(featureId, verbose);
16
+ if (verbose && resetCount > 0) {
17
+ logInfo(`✅ Reset ${resetCount} ready test cases to draft for re-analysis`);
18
+ }
14
19
  const context = await prepareTestCasesAnalysisContext(featureId, checklistContext, verbose);
15
20
  const systemPrompt = createTestCasesAnalysisSystemPrompt();
16
21
  const initialAnalysisPrompt = context.analysisPrompt;
@@ -78,9 +83,11 @@ export const analyseTestCases = async (options, config, checklistContext) => {
78
83
  verificationResult = verificationCycle.verificationResult;
79
84
  if (verificationCycle.passed) {
80
85
  if (verbose) {
81
- logInfo('✅ Verification passed! Updating test cases to ready status...');
86
+ logInfo('✅ Verification passed! Updating all draft test cases to ready status...');
82
87
  }
83
- await updateTestCasesToReady(currentDraftTestCaseIds, verbose);
88
+ // Update ALL remaining draft test cases (both kept old ones and newly created)
89
+ const allDraftIds = await getAllDraftTestCaseIds(featureId, verbose);
90
+ await updateTestCasesToReady(allDraftIds, verbose);
84
91
  break;
85
92
  }
86
93
  if (currentIteration < maxIterations && verificationCycle.nextPrompt) {
@@ -100,15 +107,16 @@ export const analyseTestCases = async (options, config, checklistContext) => {
100
107
  if (!structuredAnalysisResult) {
101
108
  throw new Error('No analysis results received');
102
109
  }
103
- // If no checklist was used, update draft artifacts to ready
110
+ // If no checklist was used, update all draft artifacts to ready
104
111
  if (!checklistContext ||
105
112
  checklistContext.checklists.length === 0 ||
106
113
  !verificationResult) {
107
- if (currentDraftTestCaseIds.length > 0) {
114
+ const allDraftIds = await getAllDraftTestCaseIds(featureId, verbose);
115
+ if (allDraftIds.length > 0) {
108
116
  if (verbose) {
109
- logInfo('✅ No checklist verification needed. Updating test cases to ready status...');
117
+ logInfo('✅ No checklist verification needed. Updating all draft test cases to ready status...');
110
118
  }
111
- await updateTestCasesToReady(currentDraftTestCaseIds, verbose);
119
+ await updateTestCasesToReady(allDraftIds, verbose);
112
120
  }
113
121
  }
114
122
  if (verificationResult &&
@@ -1,4 +1,6 @@
1
1
  import { TestCasesAnalysisContext } from './context.js';
2
+ export declare function resetReadyTestCasesToDraft(featureId: string, verbose?: boolean): Promise<number>;
3
+ export declare function getAllDraftTestCaseIds(featureId: string, verbose?: boolean): Promise<string[]>;
2
4
  export declare function deleteTestCaseArtifacts(testCaseIds: string[], verbose?: boolean): Promise<void>;
3
5
  export declare function deleteSpecificTestCases(featureId: string, deletedTestCaseIds: string[], deletionReasons: Record<string, string>, verbose?: boolean): Promise<void>;
4
6
  export declare function updateTestCasesToReady(testCaseIds: string[], verbose?: boolean): Promise<void>;
@@ -1,6 +1,30 @@
1
1
  import { logInfo, logError } from '../../utils/logger.js';
2
2
  import { batchUpdateTestCaseStatus, batchDeleteTestCases, getTestCases, } from '../../api/features/index.js';
3
3
  import { callMcpEndpoint } from '../../api/mcp-client.js';
4
+ export async function resetReadyTestCasesToDraft(featureId, verbose) {
5
+ const testCases = await getTestCases(featureId, false);
6
+ const readyIds = testCases
7
+ .filter((tc) => tc.status === 'ready')
8
+ .map((tc) => tc.id);
9
+ if (readyIds.length === 0) {
10
+ return 0;
11
+ }
12
+ if (verbose) {
13
+ logInfo(`🔄 Resetting ${readyIds.length} ready test cases to draft for re-analysis...`);
14
+ }
15
+ await batchUpdateTestCaseStatus(readyIds, 'draft', verbose);
16
+ return readyIds.length;
17
+ }
18
+ export async function getAllDraftTestCaseIds(featureId, verbose) {
19
+ const testCases = await getTestCases(featureId, false);
20
+ const draftIds = testCases
21
+ .filter((tc) => tc.status === 'draft')
22
+ .map((tc) => tc.id);
23
+ if (verbose) {
24
+ logInfo(`Found ${draftIds.length} draft test cases for feature`);
25
+ }
26
+ return draftIds;
27
+ }
4
28
  export async function deleteTestCaseArtifacts(testCaseIds, verbose) {
5
29
  if (testCaseIds.length > 0) {
6
30
  if (verbose) {
@@ -66,6 +66,7 @@ You can delete existing draft test cases that are:
66
66
  - You can ONLY delete artifacts with status 'draft'
67
67
  - Only artifacts marked with [DELETABLE] can be deleted
68
68
  - Each deletable artifact includes an "ID:" field with the UUID
69
+ - You MUST NOT delete, recreate, or modify artifacts marked with [APPROVED - DO NOT MODIFY]. These have been human-approved and are immutable. Do not create new items that duplicate approved ones.
69
70
 
70
71
  **CRITICAL - Result Format**:
71
72
  You MUST return ONLY a JSON object. Do NOT include any explanatory text. Return ONLY the JSON:
@@ -3,7 +3,7 @@ import { prepareUserStoriesAnalysisContext } from './context.js';
3
3
  import { createUserStoriesAnalysisSystemPrompt } from './prompts.js';
4
4
  import { executeUserStoriesAnalysisQuery } from './agent.js';
5
5
  import { performVerificationCycle } from '../feature-analysis-verification/index.js';
6
- import { deleteUserStoryArtifacts, deleteSpecificUserStories, updateUserStoriesToReady, saveUserStoriesAsDraft, buildUserStoriesAnalysisResult, } from './outcome.js';
6
+ import { deleteUserStoryArtifacts, deleteSpecificUserStories, updateUserStoriesToReady, saveUserStoriesAsDraft, buildUserStoriesAnalysisResult, resetReadyUserStoriesToDraft, getAllDraftUserStoryIds, } from './outcome.js';
7
7
  import { logFeaturePhaseEvent } from '../../services/audit-logs.js';
8
8
  export const analyseUserStories = async (options, config, checklistContext) => {
9
9
  const { featureId, verbose } = options;
@@ -11,6 +11,11 @@ export const analyseUserStories = async (options, config, checklistContext) => {
11
11
  logInfo(`Starting user stories analysis for feature ID: ${featureId}`);
12
12
  }
13
13
  try {
14
+ // Reset ready user stories to draft so AI can manage them on re-run
15
+ const resetCount = await resetReadyUserStoriesToDraft(featureId, verbose);
16
+ if (verbose && resetCount > 0) {
17
+ logInfo(`✅ Reset ${resetCount} ready user stories to draft for re-analysis`);
18
+ }
14
19
  const context = await prepareUserStoriesAnalysisContext(featureId, checklistContext, verbose);
15
20
  const systemPrompt = createUserStoriesAnalysisSystemPrompt();
16
21
  const initialAnalysisPrompt = context.analysisPrompt;
@@ -78,9 +83,11 @@ export const analyseUserStories = async (options, config, checklistContext) => {
78
83
  verificationResult = verificationCycle.verificationResult;
79
84
  if (verificationCycle.passed) {
80
85
  if (verbose) {
81
- logInfo('✅ Verification passed! Updating user stories to ready status...');
86
+ logInfo('✅ Verification passed! Updating all draft user stories to ready status...');
82
87
  }
83
- await updateUserStoriesToReady(currentDraftUserStoryIds, verbose);
88
+ // Update ALL remaining draft stories (both kept old ones and newly created)
89
+ const allDraftIds = await getAllDraftUserStoryIds(featureId, verbose);
90
+ await updateUserStoriesToReady(allDraftIds, verbose);
84
91
  break;
85
92
  }
86
93
  if (currentIteration < maxIterations && verificationCycle.nextPrompt) {
@@ -100,15 +107,16 @@ export const analyseUserStories = async (options, config, checklistContext) => {
100
107
  if (!structuredAnalysisResult) {
101
108
  throw new Error('No analysis results received');
102
109
  }
103
- // If no checklist was used, update draft artifacts to ready
110
+ // If no checklist was used, update all draft artifacts to ready
104
111
  if (!checklistContext ||
105
112
  checklistContext.checklists.length === 0 ||
106
113
  !verificationResult) {
107
- if (currentDraftUserStoryIds.length > 0) {
114
+ const allDraftIds = await getAllDraftUserStoryIds(featureId, verbose);
115
+ if (allDraftIds.length > 0) {
108
116
  if (verbose) {
109
- logInfo('✅ No checklist verification needed. Updating user stories to ready status...');
117
+ logInfo('✅ No checklist verification needed. Updating all draft user stories to ready status...');
110
118
  }
111
- await updateUserStoriesToReady(currentDraftUserStoryIds, verbose);
119
+ await updateUserStoriesToReady(allDraftIds, verbose);
112
120
  }
113
121
  }
114
122
  if (verificationResult &&
@@ -1,4 +1,6 @@
1
1
  import { UserStoriesAnalysisContext } from './context.js';
2
+ export declare function resetReadyUserStoriesToDraft(featureId: string, verbose?: boolean): Promise<number>;
3
+ export declare function getAllDraftUserStoryIds(featureId: string, verbose?: boolean): Promise<string[]>;
2
4
  export declare function deleteUserStoryArtifacts(userStoryIds: string[], verbose?: boolean): Promise<void>;
3
5
  export declare function deleteSpecificUserStories(featureId: string, deletedUserStoryIds: string[], deletionReasons: Record<string, string>, verbose?: boolean): Promise<void>;
4
6
  export declare function updateUserStoriesToReady(userStoryIds: string[], verbose?: boolean): Promise<void>;
@@ -1,6 +1,30 @@
1
1
  import { logInfo, logError } from '../../utils/logger.js';
2
2
  import { batchUpdateUserStoryStatus, batchDeleteUserStories, getUserStories, } from '../../api/features/index.js';
3
3
  import { callMcpEndpoint } from '../../api/mcp-client.js';
4
+ export async function resetReadyUserStoriesToDraft(featureId, verbose) {
5
+ const stories = await getUserStories(featureId, false);
6
+ const readyStoryIds = stories
7
+ .filter((story) => story.status === 'ready')
8
+ .map((story) => story.id);
9
+ if (readyStoryIds.length === 0) {
10
+ return 0;
11
+ }
12
+ if (verbose) {
13
+ logInfo(`🔄 Resetting ${readyStoryIds.length} ready user stories to draft for re-analysis...`);
14
+ }
15
+ await batchUpdateUserStoryStatus(readyStoryIds, 'draft', verbose);
16
+ return readyStoryIds.length;
17
+ }
18
+ export async function getAllDraftUserStoryIds(featureId, verbose) {
19
+ const stories = await getUserStories(featureId, false);
20
+ const draftIds = stories
21
+ .filter((story) => story.status === 'draft')
22
+ .map((story) => story.id);
23
+ if (verbose) {
24
+ logInfo(`Found ${draftIds.length} draft user stories for feature`);
25
+ }
26
+ return draftIds;
27
+ }
4
28
  export async function deleteUserStoryArtifacts(userStoryIds, verbose) {
5
29
  if (userStoryIds.length > 0) {
6
30
  if (verbose) {
@@ -86,6 +86,7 @@ You can delete existing draft user stories that are:
86
86
  - You can ONLY delete artifacts with status 'draft'
87
87
  - Only artifacts marked with [DELETABLE] in the context can be deleted
88
88
  - Each deletable artifact includes an "ID:" field with the UUID
89
+ - You MUST NOT delete, recreate, or modify artifacts marked with [APPROVED - DO NOT MODIFY]. These have been human-approved and are immutable. Do not create new stories that duplicate approved ones.
89
90
 
90
91
  To delete artifacts:
91
92
  1. Find the exact UUID from the "ID:" field
@@ -12,8 +12,8 @@ export interface FeatureInfo {
12
12
  created_at?: string;
13
13
  updated_at?: string;
14
14
  }
15
- export type UserStoryStatus = 'draft' | 'ready';
16
- export type TestCaseStatus = 'draft' | 'ready';
15
+ export type UserStoryStatus = 'draft' | 'ready' | 'approved';
16
+ export type TestCaseStatus = 'draft' | 'ready' | 'approved';
17
17
  export interface UserStory {
18
18
  id: string;
19
19
  title: string;
@@ -26,6 +26,7 @@ export interface CliOptions {
26
26
  verbose?: boolean;
27
27
  refactor?: boolean;
28
28
  init?: boolean;
29
+ growthAnalysis?: string;
29
30
  }
30
31
  export type ReviewSeverity = 'error' | 'warning';
31
32
  export type ExitCode = 0 | 1;
@@ -96,8 +97,8 @@ export interface FeatureData {
96
97
  };
97
98
  };
98
99
  }
99
- export type UserStoryStatus = 'draft' | 'ready';
100
- export type TestCaseStatus = 'draft' | 'ready';
100
+ export type UserStoryStatus = 'draft' | 'ready' | 'approved';
101
+ export type TestCaseStatus = 'draft' | 'ready' | 'approved';
101
102
  export interface UserStory {
102
103
  id: string;
103
104
  title: string;
@@ -7,7 +7,7 @@ export function formatUserStories(stories) {
7
7
  return 'No user stories defined.';
8
8
  return stories
9
9
  .map((story, index) => `${index + 1}. **${story.title}** (Status: ${story.status})
10
- ID: ${story.id}${story.status === 'draft' ? ' [DELETABLE]' : ''}
10
+ ID: ${story.id}${story.status === 'draft' ? ' [DELETABLE]' : ''}${story.status === 'approved' ? ' [APPROVED - DO NOT MODIFY]' : ''}
11
11
  ${story.description}`)
12
12
  .join('\n\n');
13
13
  }
@@ -19,7 +19,7 @@ export function formatTestCases(cases) {
19
19
  return 'No test cases defined.';
20
20
  return cases
21
21
  .map((testCase, index) => `${index + 1}. **${testCase.name}** ${testCase.is_critical ? '[CRITICAL]' : '[OPTIONAL]'} (Status: ${testCase.status || 'unknown'})
22
- ID: ${testCase.id}${testCase.status === 'draft' ? ' [DELETABLE]' : ''}
22
+ ID: ${testCase.id}${testCase.status === 'draft' ? ' [DELETABLE]' : ''}${testCase.status === 'approved' ? ' [APPROVED - DO NOT MODIFY]' : ''}
23
23
  ${testCase.description}`)
24
24
  .join('\n\n');
25
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.24.0",
3
+ "version": "0.25.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "edsger": "dist/index.js"