@fermindi/pwn-cli 0.9.8 → 0.9.10

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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fermindi/pwn-cli",
3
- "version": "0.9.8",
3
+ "version": "0.9.10",
4
4
  "description": "Professional AI Workspace - Inject structured memory and automation into any project for AI-powered development",
5
5
  "type": "module",
6
6
  "bin": {
@@ -534,7 +534,7 @@ export async function runBatch(options = {}, cwd = process.cwd()) {
534
534
  console.log(chalk.yellow(` Retry ${retry}/${MAX_RETRIES}`));
535
535
  }
536
536
 
537
- const prompt = buildPrompt(task.id, batchWorktreePath, mainCwd, errorContext);
537
+ const prompt = buildPrompt(task.id, batchWorktreePath, mainCwd, errorContext, retry);
538
538
  if (!prompt) {
539
539
  console.log(chalk.red(` Cannot build prompt for ${task.id} — skipping`));
540
540
  break;
@@ -626,6 +626,9 @@ export async function runBatch(options = {}, cwd = process.cwd()) {
626
626
  markStoryDone(task.id, taskWorktreePath);
627
627
  appendProgress(progressPath, task.id, 'All quality gates passed');
628
628
 
629
+ // Remove symlinks before commit so git add . doesn't stage them
630
+ unprepareWorktree(taskWorktreePath);
631
+
629
632
  const config = loadConfig(mainCwd);
630
633
  if (config.auto_commit) {
631
634
  await commitTask(task, {}, taskWorktreePath);
@@ -926,12 +929,14 @@ async function runGatesWithStatus(taskCwd, mainCwd = taskCwd) {
926
929
 
927
930
  /**
928
931
  * Build the prompt from template, substituting placeholders.
932
+ * On retries, generates a focused "fix" prompt instead of the full template.
929
933
  * @param {string} storyId - Story ID
930
934
  * @param {string} batchWorktreePath - Batch worktree (for prd.json, tracked file)
931
935
  * @param {string} mainCwd - Main repo (for prompt.md, gitignored file)
932
936
  * @param {string} extraContext - Error context from previous attempt
937
+ * @param {number} retry - Retry number (0 = first attempt)
933
938
  */
934
- function buildPrompt(storyId, batchWorktreePath, mainCwd, extraContext) {
939
+ function buildPrompt(storyId, batchWorktreePath, mainCwd, extraContext, retry = 0) {
935
940
  const prdPath = join(batchWorktreePath, '.ai', 'tasks', 'prd.json');
936
941
  const promptPath = join(mainCwd, '.ai', 'batch', 'prompt.md');
937
942
 
@@ -942,9 +947,36 @@ function buildPrompt(storyId, batchWorktreePath, mainCwd, extraContext) {
942
947
  return '';
943
948
  }
944
949
 
945
- const doneIds = prd.stories.filter(s => s.passes).map(s => s.id);
946
950
  const acList = (story.acceptance_criteria || []).map(ac => `- ${ac}`).join('\n') || 'None';
947
951
 
952
+ // --- Retry: focused fix prompt ---
953
+ if (retry > 0 && extraContext) {
954
+ return `You are fixing story ${storyId}: ${story.title}
955
+
956
+ ## Context
957
+ A previous attempt already implemented this story. The working tree contains the implementation.
958
+ Read the modified files to understand what was done.
959
+
960
+ ## Acceptance criteria
961
+ ${acList}
962
+
963
+ ## Quality gate failures
964
+ \`\`\`
965
+ ${extraContext}
966
+ \`\`\`
967
+
968
+ ## Instructions
969
+ 1. Read the existing code in the working tree
970
+ 2. Analyze the quality gate errors above
971
+ 3. Fix ONLY what's needed to make the gates pass
972
+ 4. If the errors indicate a deeper design problem that can't be patched, you may rewrite the implementation — but try fixing first
973
+ 5. Make sure the fix still satisfies all acceptance criteria above
974
+ 6. Commit your changes when done`;
975
+ }
976
+
977
+ // --- First attempt: full template ---
978
+ const doneIds = prd.stories.filter(s => s.passes).map(s => s.id);
979
+
948
980
  let depsList = 'None';
949
981
  if (story.dependencies?.length > 0) {
950
982
  depsList = story.dependencies
@@ -963,10 +995,6 @@ function buildPrompt(storyId, batchWorktreePath, mainCwd, extraContext) {
963
995
  prompt = prompt.replaceAll('{NOTES}', story.notes || '');
964
996
  prompt = prompt.replaceAll('{DEPENDENCIES}', depsList);
965
997
 
966
- if (extraContext) {
967
- prompt += `\n\n## Previous Attempt Failed\nThe previous attempt failed quality gates. Here is the error output:\n\n\`\`\`\n${extraContext}\n\`\`\`\n\nFix these issues before committing.`;
968
- }
969
-
970
998
  return prompt;
971
999
  }
972
1000
 
@@ -809,7 +809,18 @@ export async function runQualityGate(gate, cwd = process.cwd(), { configCwd } =
809
809
  command
810
810
  };
811
811
  } catch (error) {
812
- // If command not found, try next variant
812
+ // Custom commands must not be silently skipped — "not found" is a real failure
813
+ if (customCmd) {
814
+ return {
815
+ success: false,
816
+ error: error.message,
817
+ output: error.stdout,
818
+ stderr: error.stderr,
819
+ command
820
+ };
821
+ }
822
+
823
+ // For default/fallback commands: if command not found, try next variant
813
824
  if (error.message?.includes('not found') ||
814
825
  error.message?.includes('ENOENT') ||
815
826
  error.message?.includes('missing script')) {
@@ -827,7 +838,7 @@ export async function runQualityGate(gate, cwd = process.cwd(), { configCwd } =
827
838
  }
828
839
  }
829
840
 
830
- // No command variant worked
841
+ // No default command variant worked (only reached for non-custom gates)
831
842
  return {
832
843
  success: true, // Skip if no command available
833
844
  skipped: true,