edsger 0.39.0 → 0.39.2

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.
@@ -1,7 +1,7 @@
1
1
  /**
2
2
  * AI prompts for app store screenshot and listing content generation.
3
3
  */
4
- import { resolveSkill, processConditionals, } from '../../services/skill-resolver.js';
4
+ import { processConditionals, resolveSkill, } from '../../services/skill-resolver.js';
5
5
  import { OUTPUT_CONTRACTS } from '../output-contracts.js';
6
6
  export const STORE_SCREENSHOT_SIZES = {
7
7
  apple: {
@@ -20,7 +20,7 @@ export const createAppStoreSystemPrompt = async (hasCodebase = false, projectDir
20
20
  if (!skill) {
21
21
  throw new Error('Failed to load skill: phase/app-store-generation');
22
22
  }
23
- let prompt = skill.prompt;
23
+ let { prompt } = skill;
24
24
  prompt = processConditionals(prompt, {
25
25
  hasCodebase,
26
26
  });
@@ -10,7 +10,7 @@ export const createAutonomousSystemPrompt = async (featureId, projectDir) => {
10
10
  if (!skill) {
11
11
  throw new Error('Failed to load skill: phase/autonomous');
12
12
  }
13
- let prompt = skill.prompt;
13
+ let { prompt } = skill;
14
14
  prompt = substituteVariables(prompt, { FEATURE_ID: featureId });
15
15
  return `${prompt}
16
16
 
@@ -10,7 +10,7 @@ export async function createBranchPlanningSystemPrompt(config, featureId, projec
10
10
  if (!skill) {
11
11
  throw new Error('Failed to load skill: phase/branch-planning');
12
12
  }
13
- let prompt = skill.prompt;
13
+ let { prompt } = skill;
14
14
  prompt = substituteVariables(prompt, {
15
15
  BASE_BRANCH: 'main',
16
16
  FEATURE_ID: featureId,
@@ -7,7 +7,7 @@ export const createCodeImplementationSystemPrompt = async (_config, baseBranch,
7
7
  if (!skill) {
8
8
  throw new Error('Failed to load skill: phase/code-implementation');
9
9
  }
10
- let prompt = skill.prompt;
10
+ let { prompt } = skill;
11
11
  prompt = substituteVariables(prompt, { BASE_BRANCH: baseBranch });
12
12
  return `${prompt}\n\n${OUTPUT_CONTRACTS['code-implementation']}`;
13
13
  };
@@ -29,4 +29,4 @@ export interface VerifyCodeImplementationOptions {
29
29
  /**
30
30
  * Verify code implementation against checklist requirements by reviewing git diff
31
31
  */
32
- export declare function verifyCodeImplementationCompliance(options: VerifyCodeImplementationOptions, config: EdsgerConfig): Promise<ChecklistVerificationResult>;
32
+ export declare function verifyCodeImplementationCompliance(options: VerifyCodeImplementationOptions, _config: EdsgerConfig): Promise<ChecklistVerificationResult>;
@@ -137,7 +137,7 @@ function processAssistantContent(content, responseAccumulator, verbose) {
137
137
  /**
138
138
  * Verify code implementation against checklist requirements by reviewing git diff
139
139
  */
140
- export async function verifyCodeImplementationCompliance(options, config) {
140
+ export async function verifyCodeImplementationCompliance(options, _config) {
141
141
  const { featureId, branchName, baseBranch, featureName, featureDescription, checklistContext, verbose, } = options;
142
142
  if (verbose) {
143
143
  logInfo(`\nšŸ” Starting code implementation verification for feature: ${featureName}`);
@@ -1,5 +1,5 @@
1
- import { resolveSkill, processConditionals, } from '../../services/skill-resolver.js';
2
1
  import { formatChecklistsForContext, } from '../../services/checklist.js';
2
+ import { processConditionals, resolveSkill, } from '../../services/skill-resolver.js';
3
3
  import { OUTPUT_CONTRACTS } from '../output-contracts.js';
4
4
  import { formatContextForPrompt } from './context.js';
5
5
  export async function createSystemPrompt(checklistContext, projectDir) {
@@ -1,11 +1,11 @@
1
- import { resolveSkill, processConditionals, } from '../../services/skill-resolver.js';
1
+ import { processConditionals, resolveSkill, } from '../../services/skill-resolver.js';
2
2
  import { OUTPUT_CONTRACTS } from '../output-contracts.js';
3
3
  export const createGrowthAnalysisSystemPrompt = async (hasCodebase = false, projectDir) => {
4
4
  const skill = await resolveSkill('phase/growth-analysis', { projectDir });
5
5
  if (!skill) {
6
6
  throw new Error('Failed to load skill: phase/growth-analysis');
7
7
  }
8
- let prompt = skill.prompt;
8
+ let { prompt } = skill;
9
9
  prompt = processConditionals(prompt, {
10
10
  hasCodebase,
11
11
  });
@@ -1,4 +1,4 @@
1
- import { resolveSkill, processConditionals, } from '../../services/skill-resolver.js';
1
+ import { processConditionals, resolveSkill, } from '../../services/skill-resolver.js';
2
2
  import { OUTPUT_CONTRACTS } from '../output-contracts.js';
3
3
  export const createIntelligenceSystemPrompt = async (needsDiscovery, hasCodebase = false, projectDir) => {
4
4
  const skill = await resolveSkill('phase/intelligence-analysis', {
@@ -7,7 +7,7 @@ export const createIntelligenceSystemPrompt = async (needsDiscovery, hasCodebase
7
7
  if (!skill) {
8
8
  throw new Error('Failed to load skill: phase/intelligence-analysis');
9
9
  }
10
- let prompt = skill.prompt;
10
+ let { prompt } = skill;
11
11
  prompt = processConditionals(prompt, {
12
12
  needsDiscovery,
13
13
  hasCodebase,
@@ -128,6 +128,13 @@ export const executeFeaturePRs = async (options, config) => {
128
128
  skippedBranches: [],
129
129
  failedBranches: [],
130
130
  };
131
+ // Build PR id → branch name map for stacked PR base resolution
132
+ const prIdToBranch = new Map();
133
+ for (const pr of context.pullRequests) {
134
+ if (pr.branch_name) {
135
+ prIdToBranch.set(pr.id, pr.branch_name);
136
+ }
137
+ }
131
138
  for (const pr of context.pullRequests) {
132
139
  if (!pr.branch_name) {
133
140
  if (verbose) {
@@ -156,8 +163,12 @@ export const executeFeaturePRs = async (options, config) => {
156
163
  else {
157
164
  executionSummary.branchesCreated++;
158
165
  }
159
- // Push branch and build compare URL
160
- const result = pushBranchAndBuildUrl(executionConfig, pr.branch_name);
166
+ // Resolve base branch for stacked PRs
167
+ const baseBranch = pr.base_pr_id
168
+ ? prIdToBranch.get(pr.base_pr_id) || 'main'
169
+ : 'main';
170
+ // Push branch and build compare URL (using stacked base)
171
+ const result = pushBranchAndBuildUrl(executionConfig, pr.branch_name, baseBranch);
161
172
  if (result.success) {
162
173
  // Update database record with compare URL and sync commit
163
174
  await updatePRDatabaseRecord(pr.id, result.compareUrl, context.devBranchHeadSha, verbose).catch((error) => {
@@ -33,8 +33,9 @@ export declare function getLocalBranchSha(branchName: string): string;
33
33
  export declare function buildCompareUrl(forkInfo: RepoForkInfo, owner: string, repo: string, branchName: string, base?: string): string;
34
34
  /**
35
35
  * Push a branch and build the compare URL
36
+ * For stacked PRs, baseBranch is the dependency PR's branch instead of 'main'
36
37
  */
37
- export declare function pushBranchAndBuildUrl(config: PRExecutionConfig, branchName: string): PRBranchResult;
38
+ export declare function pushBranchAndBuildUrl(config: PRExecutionConfig, branchName: string, baseBranch?: string): PRBranchResult;
38
39
  /**
39
40
  * Update a PR record in the database after branch push
40
41
  */
@@ -37,13 +37,14 @@ export function buildCompareUrl(forkInfo, owner, repo, branchName, base = 'main'
37
37
  }
38
38
  /**
39
39
  * Push a branch and build the compare URL
40
+ * For stacked PRs, baseBranch is the dependency PR's branch instead of 'main'
40
41
  */
41
- export function pushBranchAndBuildUrl(config, branchName) {
42
+ export function pushBranchAndBuildUrl(config, branchName, baseBranch = 'main') {
42
43
  const { owner, repo, forkInfo, verbose, token } = config;
43
44
  const headSha = getLocalBranchSha(branchName);
44
45
  try {
45
46
  pushBranch(branchName, verbose, token);
46
- const compareUrl = buildCompareUrl(forkInfo, owner, repo, branchName);
47
+ const compareUrl = buildCompareUrl(forkInfo, owner, repo, branchName, baseBranch);
47
48
  if (verbose) {
48
49
  logInfo(`šŸ”— Compare URL: ${compareUrl}`);
49
50
  }
@@ -10,7 +10,7 @@ export async function createPRExecutionSystemPrompt(featureId, devBranchName, pr
10
10
  if (!skill) {
11
11
  throw new Error('Failed to load skill: phase/pr-execution');
12
12
  }
13
- let prompt = skill.prompt;
13
+ let { prompt } = skill;
14
14
  prompt = substituteVariables(prompt, {
15
15
  FEATURE_ID: featureId,
16
16
  DEV_BRANCH: devBranchName,
@@ -27,13 +27,23 @@ export async function createIncrementalSyncSystemPrompt(featureId, devBranchName
27
27
  if (!skill) {
28
28
  throw new Error('Failed to load skill: phase/incremental-sync');
29
29
  }
30
- let prompt = skill.prompt;
30
+ let { prompt } = skill;
31
31
  prompt = substituteVariables(prompt, {
32
32
  FEATURE_ID: featureId,
33
33
  DEV_BRANCH: devBranchName,
34
34
  });
35
35
  return `${prompt}\n\n${OUTPUT_CONTRACTS['incremental-sync']}`;
36
36
  }
37
+ /**
38
+ * Resolve the base branch for a PR based on its dependency chain
39
+ */
40
+ function resolveBaseBranch(pr, allPRs) {
41
+ if (!pr.base_pr_id) {
42
+ return 'main';
43
+ }
44
+ const basePR = allPRs.find((p) => p.id === pr.base_pr_id);
45
+ return basePR?.branch_name || 'main';
46
+ }
37
47
  /**
38
48
  * Create the user prompt for first-time branch creation
39
49
  */
@@ -43,27 +53,29 @@ export function createPRExecutionPrompt(featureId, devBranchName, pullRequests)
43
53
  const files = pr.files
44
54
  ? pr.files.map((f) => ` - ${f.path} (${f.change_type})`).join('\n')
45
55
  : ' (no files specified)';
56
+ const baseBranch = resolveBaseBranch(pr, pullRequests);
46
57
  return `### PR ${pr.sequence}: ${pr.name}
47
58
  - Branch: \`${pr.branch_name}\`
59
+ - Base: \`${baseBranch}\`
48
60
  - Description: ${pr.description}
49
61
  - Files:
50
62
  ${files}`;
51
63
  })
52
64
  .join('\n\n');
53
- return `# Create PR Branches
65
+ return `# Create PR Branches (Stacked)
54
66
 
55
- Create the following PR branches from \`main\`, applying the relevant changes from \`${devBranchName}\`:
67
+ Create the following PR branches as a stacked chain, applying the relevant changes from \`${devBranchName}\`:
56
68
 
57
69
  ${prList}
58
70
 
59
71
  ## Instructions
60
72
 
61
73
  For each PR above (in sequence order):
62
- 1. \`git checkout main\`
74
+ 1. Switch to the **base branch** specified in "Base" above (either \`main\` or a previous PR's branch)
63
75
  2. Create the branch: \`git checkout -b <branch_name>\`
64
76
  3. Apply only the listed files from \`${devBranchName}\`
65
77
  4. Commit with a descriptive message
66
- 5. Verify with \`git diff --stat main...<branch_name>\`
78
+ 5. Verify with \`git diff --stat <base>...<branch_name>\`
67
79
 
68
80
  After all branches are created, switch back to \`main\` and provide the execution result JSON.`;
69
81
  }
@@ -74,13 +86,15 @@ export function createIncrementalSyncPrompt(opts) {
74
86
  const files = pr.files
75
87
  ? pr.files.map((f) => ` - ${f.path} (${f.change_type})`).join('\n')
76
88
  : ' (no files specified)';
89
+ const baseBranch = resolveBaseBranch(pr, pullRequests);
77
90
  return `### PR ${pr.sequence}: ${pr.name}
78
91
  - Branch: \`${pr.branch_name}\`
92
+ - Base: \`${baseBranch}\`
79
93
  - Files:
80
94
  ${files}`;
81
95
  })
82
96
  .join('\n\n');
83
- return `# Sync PR Branches with Latest Changes
97
+ return `# Sync PR Branches with Latest Changes (Stacked)
84
98
 
85
99
  New changes have been made on \`${devBranchName}\` since the last sync at commit \`${lastSyncedCommit}\`.
86
100
 
@@ -94,19 +108,22 @@ ${diffStat || 'No changes detected'}
94
108
  ### Changed Files (${changedFiles.length} files)
95
109
  ${changedFiles.map((f) => `- ${f}`).join('\n') || 'No files changed'}
96
110
 
97
- ## Existing PR Branches
111
+ ## Existing PR Branches (Stacked)
98
112
 
99
113
  ${prList}
100
114
 
101
115
  ## Instructions
102
116
 
117
+ These branches are stacked — each PR branch is based on its dependency. Update them **in sequence order** (base PRs first):
118
+
103
119
  For each PR branch that has files affected by the new changes:
104
120
  1. Switch to the PR branch
105
- 2. Apply the relevant new changes from \`${devBranchName}\`
106
- 3. Commit with a sync message
107
- 4. Verify the branch state
121
+ 2. If this PR has a base PR that was just updated, first merge the base branch into this branch: \`git merge <base-branch> --no-edit\`
122
+ 3. Apply the relevant new changes from \`${devBranchName}\` (checkout the updated files)
123
+ 4. Commit with a sync message
124
+ 5. Verify the branch state
108
125
 
109
- If a PR branch has no files affected by the new changes, skip it.
126
+ If a PR branch has no files affected by the new changes but its base PR was updated, still merge the base branch to keep the stack consistent.
110
127
 
111
128
  After all branches are updated, switch back to \`main\` and provide the execution result JSON.`;
112
129
  }
@@ -176,8 +176,8 @@ export async function loadSkillFile(pluginName, skillName, verbose, cacheDir) {
176
176
  return result;
177
177
  }
178
178
  }
179
- logWarning(`Skill file not found for plugin "${pluginName}", skill "${skillName}" ` +
180
- `in cache dir "${resolvedCacheDir}"`);
179
+ logDebug(`Skill file not found for plugin "${pluginName}", skill "${skillName}" ` +
180
+ `in cache dir "${resolvedCacheDir}"`, true);
181
181
  return null;
182
182
  }
183
183
  /**
@@ -4,7 +4,7 @@
4
4
  * 1. Project override: {projectDir}/.claude/skills/edsger/{skillRef}/SKILL.md
5
5
  * 2. User override: ~/.claude/skills/edsger/{skillRef}/SKILL.md
6
6
  * 3. Plugin cache: edsger-skills plugin in ~/.claude/plugins/cache/
7
- * 4. Fallback: hardcoded prompt from prompts.ts (backward compatibility)
7
+ * 4. Bundled: shipped SKILL.md files in dist/skills/
8
8
  *
9
9
  * This enables users to customize phase prompts at project or personal level
10
10
  * while keeping a default from the edsger-skills plugin.
@@ -4,7 +4,7 @@
4
4
  * 1. Project override: {projectDir}/.claude/skills/edsger/{skillRef}/SKILL.md
5
5
  * 2. User override: ~/.claude/skills/edsger/{skillRef}/SKILL.md
6
6
  * 3. Plugin cache: edsger-skills plugin in ~/.claude/plugins/cache/
7
- * 4. Fallback: hardcoded prompt from prompts.ts (backward compatibility)
7
+ * 4. Bundled: shipped SKILL.md files in dist/skills/
8
8
  *
9
9
  * This enables users to customize phase prompts at project or personal level
10
10
  * while keeping a default from the edsger-skills plugin.
@@ -7,20 +7,29 @@ variables:
7
7
  - DEV_BRANCH
8
8
  ---
9
9
 
10
- You are a git operations expert. Your task is to update existing PR branches with new changes from the dev branch.
10
+ You are a git operations expert. Your task is to update existing stacked PR branches with new changes from the dev branch.
11
11
 
12
12
  ## Task
13
13
 
14
- The PR branches already exist from a previous run. New changes have been made on `$DEV_BRANCH` since the last sync. You need to update each PR branch with the relevant new changes.
14
+ The PR branches already exist from a previous run as a stacked chain (each dependent PR was branched from its base PR). New changes have been made on `$DEV_BRANCH` since the last sync. You need to update each PR branch with the relevant new changes, maintaining the stack.
15
15
 
16
- ## Strategy for Updating Branches
16
+ ## Strategy for Updating Stacked Branches
17
17
 
18
- For each existing PR branch:
18
+ **Process branches in sequence order** (base PRs first, then dependent PRs):
19
19
 
20
- 1. Switch to the PR branch: `git checkout pr/$FEATURE_ID/N-description`
20
+ ### For a base PR (no dependency, based on main):
21
+
22
+ 1. Switch to the PR branch: `git checkout pr/$FEATURE_ID/1-description`
21
23
  2. Apply the relevant NEW changes from `$DEV_BRANCH`
22
24
  3. Commit the changes
23
25
 
26
+ ### For a dependent PR (stacked on another PR):
27
+
28
+ 1. Switch to the PR branch: `git checkout pr/$FEATURE_ID/2-description`
29
+ 2. **First merge the updated base branch**: `git merge pr/$FEATURE_ID/1-description --no-edit`
30
+ 3. Apply the relevant NEW changes from `$DEV_BRANCH`
31
+ 4. Commit the changes
32
+
24
33
  ### Option A: Selective file checkout (for files entirely owned by this PR)
25
34
 
26
35
  ```bash
@@ -40,9 +49,11 @@ git commit -m "sync: update with latest changes from dev"
40
49
 
41
50
  ## Rules
42
51
 
43
- 1. **Only update files that belong to each PR** according to the file assignments
44
- 2. **Do NOT push branches** — just update them locally. Pushing is handled separately.
45
- 3. **If a branch doesn't exist locally**, check remote and create a tracking branch
46
- 4. **Verify after each update**: Check that the branch has the expected changes
47
- 5. **Commit all changes** before switching branches
48
- 6. **After all branches are updated**, switch back to `main`
52
+ 1. **Process in sequence order** — base PRs must be updated before their dependents
53
+ 2. **Merge base branch into dependent PRs** — even if the dependent PR has no new changes, merge the updated base to keep the stack consistent
54
+ 3. **Only update/checkout files that belong to each PR** according to the file assignments
55
+ 4. **Do NOT push branches** — just update them locally. Pushing is handled separately.
56
+ 5. **If a branch doesn't exist locally**, check remote and create a tracking branch
57
+ 6. **Verify after each update**: Check that the branch has the expected changes
58
+ 7. **Commit all changes** before switching branches
59
+ 8. **After all branches are updated**, switch back to `main`
@@ -11,13 +11,21 @@ You are a git operations expert. Your task is to create PR branches and move the
11
11
 
12
12
  ## Task
13
13
 
14
- You will receive a PR plan with file assignments. For each PR in sequence order, you must:
14
+ You will receive a PR plan with file assignments. Each PR specifies a **base branch** — either `main` or a previous PR's branch. For each PR in sequence order, you must:
15
15
 
16
- 1. Switch to `main` branch
17
- 2. Create a new branch `pr/$FEATURE_ID/N-description` from `main`
16
+ 1. Switch to the PR's **base branch** (specified in the plan)
17
+ 2. Create a new branch `pr/$FEATURE_ID/N-description` from that base
18
18
  3. Apply ONLY the relevant file changes from the `$DEV_BRANCH` branch to this new branch
19
19
  4. Commit the changes with a meaningful commit message
20
20
 
21
+ ## Stacked Branches
22
+
23
+ PRs form a dependency chain (stacked PRs):
24
+ - **First PR** (no dependency): branches from `main`
25
+ - **Dependent PRs**: branch from the PR they depend on
26
+
27
+ This ensures each branch compiles correctly — a dependent PR inherits all changes from its base PR.
28
+
21
29
  ## Strategy for Moving Code
22
30
 
23
31
  Choose the best approach for each PR:
@@ -25,28 +33,35 @@ Choose the best approach for each PR:
25
33
  ### Option A: Selective file checkout (recommended for most cases)
26
34
 
27
35
  ```bash
36
+ # First PR: base is main
28
37
  git checkout main
29
- git checkout -b pr/$FEATURE_ID/1-description
38
+ git checkout -b pr/$FEATURE_ID/1-foundation
30
39
  git checkout $DEV_BRANCH -- path/to/file1 path/to/file2
31
40
  git commit -m "feat: descriptive message"
41
+
42
+ # Second PR: base is the first PR's branch (stacked)
43
+ git checkout pr/$FEATURE_ID/1-foundation
44
+ git checkout -b pr/$FEATURE_ID/2-feature
45
+ git checkout $DEV_BRANCH -- path/to/file3 path/to/file4
46
+ git commit -m "feat: descriptive message"
32
47
  ```
33
48
 
34
49
  ### Option B: Using git diff + apply (for partial file changes)
35
50
 
36
51
  ```bash
37
- git checkout main
38
- git checkout -b pr/$FEATURE_ID/1-description
39
- git diff main...$DEV_BRANCH -- path/to/file | git apply
52
+ git checkout <base-branch>
53
+ git checkout -b pr/$FEATURE_ID/N-description
54
+ git diff <base-branch>...$DEV_BRANCH -- path/to/file | git apply
40
55
  git add -A
41
56
  git commit -m "feat: descriptive message"
42
57
  ```
43
58
 
44
59
  ## Rules
45
60
 
46
- 1. **Always start each PR branch from `main`** — never from another PR branch
47
- 2. **Each branch must be independent** — it should compile/build on its own when merged to main
61
+ 1. **Always start each PR branch from its specified base** — `main` for the first PR, or the dependency PR's branch for stacked PRs
62
+ 2. **Each branch must compile/build** when merged to its base branch
48
63
  3. **Do NOT push branches** — just create them locally. Pushing is handled separately.
49
- 4. **Verify after each branch**: Run `git diff --stat main...pr/$FEATURE_ID/N-description` to confirm only the expected files changed
64
+ 4. **Verify after each branch**: Run `git diff --stat <base>...pr/$FEATURE_ID/N-description` to confirm only the expected files changed
50
65
  5. **Commit all changes** before switching branches
51
66
  6. **Handle new files**: For newly added files, use `git checkout $DEV_BRANCH -- path/to/new/file`
52
67
  7. **Handle deleted files**: For deleted files, use `git rm path/to/deleted/file`
@@ -37,13 +37,21 @@ Branch names MUST follow this pattern:
37
37
  - `pr/$FEATURE_ID/2-{short-description}`
38
38
  - etc.
39
39
 
40
- ## PR Dependencies
40
+ ## PR Dependencies (Stacked PRs)
41
+
42
+ PRs are created as **stacked branches** — each dependent PR's branch is created from its base PR's branch, so it inherits all the base PR's changes. This means:
43
+
44
+ - A dependent PR can safely import/use code introduced by its base PR
45
+ - On GitHub, a dependent PR targets its base PR's branch (not main)
46
+ - When the base PR is merged, GitHub automatically retargets the dependent PR to main
41
47
 
42
48
  Use `depends_on_branch_name` to specify which PR must be merged before this one:
43
49
 
44
50
  - First PR: `"depends_on_branch_name": null` (merges directly to main)
45
51
  - Subsequent PRs: `"depends_on_branch_name": "pr/$FEATURE_ID/1-database"` (depends on previous PR)
46
52
 
53
+ **Important**: If file A imports from file B, and both are changed files, file B must be in the same PR as file A or in an earlier PR in the dependency chain. This ensures each PR compiles correctly.
54
+
47
55
  ## File Assignment Guidelines
48
56
 
49
57
  - **Database migrations and schema**: First PR
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "edsger",
3
- "version": "0.39.0",
3
+ "version": "0.39.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "edsger": "dist/index.js"