@shepai/cli 1.17.0 → 1.18.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 (81) hide show
  1. package/dist/eslint.config.mjs +8 -1
  2. package/dist/src/application/ports/output/agents/agent-executor.interface.d.ts +4 -0
  3. package/dist/src/application/ports/output/agents/agent-executor.interface.d.ts.map +1 -1
  4. package/dist/src/infrastructure/services/agents/common/executors/claude-code-executor.service.d.ts.map +1 -1
  5. package/dist/src/infrastructure/services/agents/common/executors/claude-code-executor.service.js +4 -0
  6. package/dist/src/infrastructure/services/agents/feature-agent/nodes/implement.node.d.ts.map +1 -1
  7. package/dist/src/infrastructure/services/agents/feature-agent/nodes/implement.node.js +13 -3
  8. package/dist/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.d.ts +35 -1
  9. package/dist/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.d.ts.map +1 -1
  10. package/dist/src/infrastructure/services/agents/feature-agent/nodes/node-helpers.js +96 -1
  11. package/dist/src/presentation/web/components/common/sidebar-section-header/sidebar-section-header.js +1 -1
  12. package/dist/src/presentation/web/components/ui/alert.js +1 -1
  13. package/dist/src/presentation/web/components/ui/badge.js +4 -4
  14. package/dist/src/presentation/web/components/ui/button.js +3 -3
  15. package/dist/src/presentation/web/components/ui/sidebar.js +2 -2
  16. package/dist/tsconfig.build.tsbuildinfo +1 -1
  17. package/package.json +1 -1
  18. package/web/.next/BUILD_ID +1 -1
  19. package/web/.next/build-manifest.json +2 -2
  20. package/web/.next/cache/.previewinfo +1 -1
  21. package/web/.next/cache/.rscinfo +1 -1
  22. package/web/.next/cache/.tsbuildinfo +1 -1
  23. package/web/.next/cache/config.json +3 -3
  24. package/web/.next/fallback-build-manifest.json +2 -2
  25. package/web/.next/prerender-manifest.json +3 -3
  26. package/web/.next/required-server-files.js +1 -1
  27. package/web/.next/required-server-files.json +1 -1
  28. package/web/.next/server/app/_global-error.html +2 -2
  29. package/web/.next/server/app/_global-error.rsc +1 -1
  30. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  31. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  32. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  33. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  34. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  35. package/web/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  36. package/web/.next/server/app/_not-found.html +2 -2
  37. package/web/.next/server/app/_not-found.rsc +8 -8
  38. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +8 -8
  39. package/web/.next/server/app/_not-found.segments/_head.segment.rsc +3 -3
  40. package/web/.next/server/app/_not-found.segments/_index.segment.rsc +4 -4
  41. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +2 -2
  42. package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +3 -3
  43. package/web/.next/server/app/_not-found.segments/_tree.segment.rsc +1 -1
  44. package/web/.next/server/app/index.html +2 -2
  45. package/web/.next/server/app/index.rsc +3 -3
  46. package/web/.next/server/app/index.segments/__PAGE__.segment.rsc +2 -2
  47. package/web/.next/server/app/index.segments/_full.segment.rsc +3 -3
  48. package/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  49. package/web/.next/server/app/index.segments/_index.segment.rsc +2 -2
  50. package/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  51. package/web/.next/server/app/page_client-reference-manifest.js +1 -1
  52. package/web/.next/server/app/version/page_client-reference-manifest.js +1 -1
  53. package/web/.next/server/app/version.html +2 -2
  54. package/web/.next/server/app/version.rsc +3 -3
  55. package/web/.next/server/app/version.segments/_full.segment.rsc +3 -3
  56. package/web/.next/server/app/version.segments/_head.segment.rsc +1 -1
  57. package/web/.next/server/app/version.segments/_index.segment.rsc +2 -2
  58. package/web/.next/server/app/version.segments/_tree.segment.rsc +1 -1
  59. package/web/.next/server/app/version.segments/version/__PAGE__.segment.rsc +2 -2
  60. package/web/.next/server/app/version.segments/version.segment.rsc +1 -1
  61. package/web/.next/server/chunks/ssr/_0f02ab66._.js +1 -1
  62. package/web/.next/server/chunks/ssr/_0f02ab66._.js.map +1 -1
  63. package/web/.next/server/chunks/ssr/_19be0743._.js +1 -1
  64. package/web/.next/server/chunks/ssr/_4582f3c3._.js +1 -1
  65. package/web/.next/server/chunks/ssr/_4582f3c3._.js.map +1 -1
  66. package/web/.next/server/chunks/ssr/_a0c04017._.js +1 -1
  67. package/web/.next/server/chunks/ssr/_a0c04017._.js.map +1 -1
  68. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_version_version-page-client_tsx_fe9db753._.js +1 -1
  69. package/web/.next/server/chunks/ssr/b1a17_presentation_web_components_features_version_version-page-client_tsx_fe9db753._.js.map +1 -1
  70. package/web/.next/server/pages/404.html +2 -2
  71. package/web/.next/server/pages/500.html +2 -2
  72. package/web/.next/server/server-reference-manifest.js +1 -1
  73. package/web/.next/server/server-reference-manifest.json +1 -1
  74. package/web/.next/static/chunks/{5130d7093ad39f4a.js → 26d260496abf3ed8.js} +1 -1
  75. package/web/.next/static/chunks/{ef1296dbe67d5aae.js → 8353f5690707d9a5.js} +1 -1
  76. package/web/.next/static/chunks/{523c5cfe0570712c.js → f74cb59a5a6c94f1.js} +1 -1
  77. package/web/.next/trace +1 -1
  78. package/web/.next/trace-build +1 -1
  79. /package/web/.next/static/{Wgd1DC_QfVhpAi62LjpwI → iV3kEDpfUq_ttVqv3E57o}/_buildManifest.js +0 -0
  80. /package/web/.next/static/{Wgd1DC_QfVhpAi62LjpwI → iV3kEDpfUq_ttVqv3E57o}/_clientMiddlewareManifest.json +0 -0
  81. /package/web/.next/static/{Wgd1DC_QfVhpAi62LjpwI → iV3kEDpfUq_ttVqv3E57o}/_ssgManifest.js +0 -0
@@ -8,6 +8,8 @@
8
8
  * @see https://eslint.org/docs/latest/use/configure/configuration-files
9
9
  * @see https://typescript-eslint.io/getting-started/
10
10
  */
11
+ import { dirname } from 'node:path';
12
+ import { fileURLToPath } from 'node:url';
11
13
  import eslint from '@eslint/js';
12
14
  import tseslint from 'typescript-eslint';
13
15
  import prettierConfig from 'eslint-config-prettier';
@@ -16,6 +18,7 @@ import reactHooksPlugin from 'eslint-plugin-react-hooks';
16
18
  import nextPlugin from '@next/eslint-plugin-next';
17
19
  import tailwindPlugin from 'eslint-plugin-tailwindcss';
18
20
  import storybookPlugin from 'eslint-plugin-storybook';
21
+ const __dirname = dirname(fileURLToPath(import.meta.url));
19
22
  export default tseslint.config(
20
23
  // =============================================================================
21
24
  // Global ignores (replaces .eslintignore)
@@ -193,6 +196,9 @@ eslint.configs.recommended,
193
196
  react: {
194
197
  version: 'detect',
195
198
  },
199
+ tailwindcss: {
200
+ config: `${__dirname}/src/presentation/web/app/globals.css`,
201
+ },
196
202
  },
197
203
  rules: {
198
204
  // React rules
@@ -216,7 +222,8 @@ eslint.configs.recommended,
216
222
  '@next/next/no-img-element': 'warn',
217
223
  '@next/next/no-sync-scripts': 'error',
218
224
  // Tailwind CSS rules
219
- 'tailwindcss/classnames-order': 'warn',
225
+ // Class sorting is handled by prettier-plugin-tailwindcss
226
+ 'tailwindcss/classnames-order': 'off',
220
227
  'tailwindcss/no-contradicting-classname': 'error',
221
228
  'tailwindcss/no-unnecessary-arbitrary-value': 'warn',
222
229
  'tailwindcss/enforces-shorthand': 'warn',
@@ -70,6 +70,10 @@ export interface AgentExecutionOptions {
70
70
  timeout?: number;
71
71
  /** Suppress executor debug logging (e.g. for quick foreground calls) */
72
72
  silent?: boolean;
73
+ /** When true, adds --strict-mcp-config to disable all MCP tools */
74
+ disableMcp?: boolean;
75
+ /** Restrict available built-in tools via --tools flag */
76
+ tools?: string[];
73
77
  }
74
78
  /**
75
79
  * Port interface for executing prompts against an AI agent.
@@ -1 +1 @@
1
- {"version":3,"file":"agent-executor.interface.d.ts","sourceRoot":"","sources":["../../../../../../src/application/ports/output/agents/agent-executor.interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAC;AAEtF;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6BAA6B;IAC7B,KAAK,CAAC,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,2DAA2D;IAC3D,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAC;IACtC,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,sCAAsC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,cAAc;IAC7B,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAE9B;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAExF;;;;;;OAMG;IACH,aAAa,CACX,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,qBAAqB,GAC9B,aAAa,CAAC,yBAAyB,CAAC,CAAC;IAE5C;;;;;OAKG;IACH,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC;CACjD"}
1
+ {"version":3,"file":"agent-executor.interface.d.ts","sourceRoot":"","sources":["../../../../../../src/application/ports/output/agents/agent-executor.interface.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,wCAAwC,CAAC;AAEtF;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,8BAA8B;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,4CAA4C;IAC5C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,6BAA6B;IAC7B,KAAK,CAAC,EAAE;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAC;IACtD,yCAAyC;IACzC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,yBAAyB;IACxC,2DAA2D;IAC3D,IAAI,EAAE,UAAU,GAAG,QAAQ,GAAG,OAAO,CAAC;IACtC,yBAAyB;IACzB,OAAO,EAAE,MAAM,CAAC;IAChB,iCAAiC;IACjC,SAAS,EAAE,IAAI,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,sCAAsC;IACtC,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,mDAAmD;IACnD,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oCAAoC;IACpC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iCAAiC;IACjC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,+BAA+B;IAC/B,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,wCAAwC;IACxC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,mEAAmE;IACnE,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,yDAAyD;IACzD,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;CAClB;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,WAAW,cAAc;IAC7B,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,EAAE,SAAS,CAAC;IAE9B;;;;;;OAMG;IACH,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAAC;IAExF;;;;;;OAMG;IACH,aAAa,CACX,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,qBAAqB,GAC9B,aAAa,CAAC,yBAAyB,CAAC,CAAC;IAE5C;;;;;OAKG;IACH,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC;CACjD"}
@@ -1 +1 @@
1
- {"version":3,"file":"claude-code-executor.service.d.ts","sourceRoot":"","sources":["../../../../../../../src/infrastructure/services/agents/common/executors/claude-code-executor.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AACzF,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,yBAAyB,EAC1B,MAAM,4EAA4E,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAUjD;;;GAGG;AACH,qBAAa,yBAA0B,YAAW,cAAc;IAMlD,OAAO,CAAC,QAAQ,CAAC,KAAK;IALlC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAA8B;IAE3D,4EAA4E;IAC5E,OAAO,CAAC,MAAM,CAAS;gBAEM,KAAK,EAAE,aAAa;IAEjD,4EAA4E;IAC5E,OAAO,CAAC,GAAG;IAML,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAmHtF,aAAa,CAClB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,qBAAqB,GAC9B,aAAa,CAAC,yBAAyB,CAAC;IA+F3C;;;OAGG;IACH,OAAO,CAAC,cAAc;IAmCtB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO;IAI/C,OAAO,CAAC,SAAS;IAWjB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,eAAe;IAiCvB,OAAO,CAAC,eAAe;CAkExB"}
1
+ {"version":3,"file":"claude-code-executor.service.d.ts","sourceRoot":"","sources":["../../../../../../../src/infrastructure/services/agents/common/executors/claude-code-executor.service.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,MAAM,2CAA2C,CAAC;AACzF,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACpB,yBAAyB,EAC1B,MAAM,4EAA4E,CAAC;AACpF,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAUjD;;;GAGG;AACH,qBAAa,yBAA0B,YAAW,cAAc;IAMlD,OAAO,CAAC,QAAQ,CAAC,KAAK;IALlC,QAAQ,CAAC,SAAS,EAAE,SAAS,CAA8B;IAE3D,4EAA4E;IAC5E,OAAO,CAAC,MAAM,CAAS;gBAEM,KAAK,EAAE,aAAa;IAEjD,4EAA4E;IAC5E,OAAO,CAAC,GAAG;IAML,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,qBAAqB,GAAG,OAAO,CAAC,oBAAoB,CAAC;IAmHtF,aAAa,CAClB,MAAM,EAAE,MAAM,EACd,OAAO,CAAC,EAAE,qBAAqB,GAC9B,aAAa,CAAC,yBAAyB,CAAC;IA+F3C;;;OAGG;IACH,OAAO,CAAC,cAAc;IAmCtB,eAAe,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO;IAI/C,OAAO,CAAC,SAAS;IAajB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,iBAAiB;IAMzB,OAAO,CAAC,eAAe;IAiCvB,OAAO,CAAC,eAAe;CAkExB"}
@@ -269,6 +269,10 @@ export class ClaudeCodeExecutorService {
269
269
  args.push('--json-schema', JSON.stringify(options.outputSchema));
270
270
  if (options?.maxTurns)
271
271
  args.push('--max-turns', String(options.maxTurns));
272
+ if (options?.disableMcp)
273
+ args.push('--strict-mcp-config');
274
+ if (options?.tools?.length)
275
+ args.push('--tools', options.tools.join(','));
272
276
  return args;
273
277
  }
274
278
  buildStreamArgs(prompt, options) {
@@ -1 +1 @@
1
- {"version":3,"file":"implement.node.d.ts","sourceRoot":"","sources":["../../../../../../../src/infrastructure/services/agents/feature-agent/nodes/implement.node.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+DAA+D,CAAC;AACpG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AA4DrD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,IAG5C,OAAO,iBAAiB,KAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAoJ7E"}
1
+ {"version":3,"file":"implement.node.d.ts","sourceRoot":"","sources":["../../../../../../../src/infrastructure/services/agents/feature-agent/nodes/implement.node.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,+DAA+D,CAAC;AACpG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AA+DrD,wBAAgB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,IAG5C,OAAO,iBAAiB,KAAG,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAgK7E"}
@@ -10,7 +10,7 @@ import yaml from 'js-yaml';
10
10
  import { writeFileSync } from 'node:fs';
11
11
  import { join } from 'node:path';
12
12
  import { interrupt, isGraphBubbleUp } from '@langchain/langgraph';
13
- import { createNodeLogger, readSpecFile, buildExecutorOptions, shouldInterrupt, safeYamlLoad, } from './node-helpers.js';
13
+ import { createNodeLogger, readSpecFile, buildExecutorOptions, shouldInterrupt, safeYamlLoad, retryExecute, getCompletedPhases, markPhaseComplete, } from './node-helpers.js';
14
14
  import { reportNodeStart } from '../heartbeat.js';
15
15
  import { buildImplementPhasePrompt, } from './prompts/implement.prompt.js';
16
16
  /**
@@ -71,6 +71,9 @@ export function createImplementNode(executor) {
71
71
  const totalTasks = tasksData.tasks.length;
72
72
  let completedTasks = 0;
73
73
  const totalPhases = planData.phases.length;
74
+ // --- Check for completed phases (skip on resume) ---
75
+ const completedPhaseIds = getCompletedPhases(state.specDir);
76
+ const retryOpts = { logger: log };
74
77
  // --- Execute phases in order ---
75
78
  for (let i = 0; i < totalPhases; i++) {
76
79
  const phase = planData.phases[i];
@@ -81,6 +84,12 @@ export function createImplementNode(executor) {
81
84
  .map((id) => taskMap.get(id))
82
85
  .filter((t) => t !== undefined)
83
86
  : tasksData.tasks.filter((t) => t.phaseId === phase.id);
87
+ // Skip already-completed phases
88
+ if (completedPhaseIds.includes(phase.id)) {
89
+ completedTasks += phaseTasks.length;
90
+ log.info(`Phase ${phase.id} "${phase.name}" — already complete, skipping`);
91
+ continue;
92
+ }
84
93
  if (phaseTasks.length === 0) {
85
94
  log.info(`Phase ${phase.id} "${phase.name}" — no tasks, skipping`);
86
95
  continue;
@@ -99,7 +108,7 @@ export function createImplementNode(executor) {
99
108
  };
100
109
  const prompt = buildImplementPhasePrompt(state, singleTaskPhase, [task], promptContext);
101
110
  log.info(` [parallel] Task ${task.id}: "${task.title}" — ${prompt.length} chars`);
102
- return executor.execute(prompt, options);
111
+ return retryExecute(executor, prompt, options, retryOpts);
103
112
  }));
104
113
  for (let j = 0; j < results.length; j++) {
105
114
  log.info(` [parallel] Task ${phaseTasks[j].id} complete (${results[j].result.length} chars)`);
@@ -109,10 +118,11 @@ export function createImplementNode(executor) {
109
118
  // Sequential: single executor call with all phase tasks
110
119
  const prompt = buildImplementPhasePrompt(state, phase, phaseTasks, promptContext);
111
120
  log.info(`Executing phase prompt — ${prompt.length} chars`);
112
- const result = await executor.execute(prompt, options);
121
+ const result = await retryExecute(executor, prompt, options, retryOpts);
113
122
  log.info(`Phase complete (${result.result.length} chars)`);
114
123
  }
115
124
  completedTasks += phaseTasks.length;
125
+ markPhaseComplete(state.specDir, phase.id, log);
116
126
  const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
117
127
  messages.push(`[implement] Phase ${phase.id} "${phase.name}" — ${phaseTasks.length} task(s) done (${elapsed}s)`);
118
128
  updateFeatureProgress(state.specDir, completedTasks, totalTasks, `implementing-${phase.id}`, null, log);
@@ -4,7 +4,7 @@
4
4
  * Provides consistent logging, spec file reading, executor invocation,
5
5
  * and error handling so every node follows the same patterns.
6
6
  */
7
- import type { IAgentExecutor, AgentExecutionOptions } from '../../../../../application/ports/output/agents/agent-executor.interface.js';
7
+ import type { IAgentExecutor, AgentExecutionOptions, AgentExecutionResult } from '../../../../../application/ports/output/agents/agent-executor.interface.js';
8
8
  import type { FeatureAgentState } from '../state.js';
9
9
  /**
10
10
  * Create a scoped logger that prefixes messages with the node name.
@@ -34,6 +34,40 @@ export declare function safeYamlLoad(content: string): unknown;
34
34
  * for human approval given the configured approval mode.
35
35
  */
36
36
  export declare function shouldInterrupt(nodeName: string, approvalMode: string | undefined): boolean;
37
+ export type ErrorCategory = 'retryable-api' | 'retryable-network' | 'non-retryable' | 'unknown';
38
+ /**
39
+ * Classify an error message into a retry category.
40
+ *
41
+ * - **retryable-api**: Transient API errors (rate-limit, overload, bad request)
42
+ * - **retryable-network**: Network-level failures (DNS, timeouts, connection refused)
43
+ * - **non-retryable**: Logic / filesystem / syntax errors that won't resolve on retry
44
+ * - **unknown**: Anything unrecognised (callers may choose to retry cautiously)
45
+ */
46
+ export declare function classifyError(errorMessage: string): ErrorCategory;
47
+ export interface RetryOptions {
48
+ /** Maximum number of execution attempts (default 3). */
49
+ maxAttempts?: number;
50
+ /** Base delay in ms before the first retry (default 2000). Doubles each retry. */
51
+ baseDelayMs?: number;
52
+ /** Optional logger for retry messages. */
53
+ logger?: NodeLogger;
54
+ }
55
+ /**
56
+ * Execute a prompt via the given executor with automatic retry and
57
+ * exponential back-off for transient errors.
58
+ *
59
+ * Non-retryable errors are thrown immediately. Unknown errors are
60
+ * retried (conservative stance: could be transient).
61
+ */
62
+ export declare function retryExecute(executor: IAgentExecutor, prompt: string, options: AgentExecutionOptions, retryOpts?: RetryOptions): Promise<AgentExecutionResult>;
63
+ /**
64
+ * Read completed phases from feature.yaml.
65
+ */
66
+ export declare function getCompletedPhases(specDir: string): string[];
67
+ /**
68
+ * Mark a phase as complete in feature.yaml by adding its ID to completedPhases.
69
+ */
70
+ export declare function markPhaseComplete(specDir: string, phaseId: string, log?: NodeLogger): void;
37
71
  /**
38
72
  * Execute a node with consistent logging and error handling.
39
73
  *
@@ -1 +1 @@
1
- {"version":3,"file":"node-helpers.d.ts","sourceRoot":"","sources":["../../../../../../../src/infrastructure/services/agents/feature-agent/nodes/node-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACtB,MAAM,+DAA+D,CAAC;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGrD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM;kBAE/B,MAAM,GAAG,IAAI;mBAIZ,MAAM,GAAG,IAAI;EAK/B;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE7D;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMtE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,qBAAqB,CAIpF;AAeD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQrD;AAUD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAI3F;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,cAAc,EACxB,WAAW,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,UAAU,KAAK,MAAM,GACjE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAgDnE"}
1
+ {"version":3,"file":"node-helpers.d.ts","sourceRoot":"","sources":["../../../../../../../src/infrastructure/services/agents/feature-agent/nodes/node-helpers.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,KAAK,EACV,cAAc,EACd,qBAAqB,EACrB,oBAAoB,EACrB,MAAM,+DAA+D,CAAC;AACvE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAGrD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM;kBAE/B,MAAM,GAAG,IAAI;mBAIZ,MAAM,GAAG,IAAI;EAK/B;AAED,MAAM,MAAM,UAAU,GAAG,UAAU,CAAC,OAAO,gBAAgB,CAAC,CAAC;AAE7D;;GAEG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAMtE;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,iBAAiB,GAAG,qBAAqB,CAMpF;AAeD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAQrD;AAUD;;;GAGG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,CAI3F;AAMD,MAAM,MAAM,aAAa,GAAG,eAAe,GAAG,mBAAmB,GAAG,eAAe,GAAG,SAAS,CAAC;AAMhG;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,YAAY,EAAE,MAAM,GAAG,aAAa,CAKjE;AAED,MAAM,WAAW,YAAY;IAC3B,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED;;;;;;GAMG;AACH,wBAAsB,YAAY,CAChC,QAAQ,EAAE,cAAc,EACxB,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,qBAAqB,EAC9B,SAAS,CAAC,EAAE,YAAY,GACvB,OAAO,CAAC,oBAAoB,CAAC,CA6B/B;AAED;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAW5D;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,GAAG,CAAC,EAAE,UAAU,GAAG,IAAI,CAwB1F;AAED;;;;;;;GAOG;AACH,wBAAgB,WAAW,CACzB,QAAQ,EAAE,MAAM,EAChB,QAAQ,EAAE,cAAc,EACxB,WAAW,EAAE,CAAC,KAAK,EAAE,iBAAiB,EAAE,GAAG,EAAE,UAAU,KAAK,MAAM,GACjE,CAAC,KAAK,EAAE,iBAAiB,KAAK,OAAO,CAAC,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAgDnE"}
@@ -5,7 +5,7 @@
5
5
  * and error handling so every node follows the same patterns.
6
6
  */
7
7
  import yaml from 'js-yaml';
8
- import { readFileSync } from 'node:fs';
8
+ import { readFileSync, writeFileSync } from 'node:fs';
9
9
  import { join } from 'node:path';
10
10
  import { interrupt, isGraphBubbleUp } from '@langchain/langgraph';
11
11
  import { reportNodeStart } from '../heartbeat.js';
@@ -42,6 +42,8 @@ export function readSpecFile(specDir, filename) {
42
42
  export function buildExecutorOptions(state) {
43
43
  return {
44
44
  cwd: state.worktreePath || state.repositoryPath,
45
+ maxTurns: 50,
46
+ disableMcp: true,
45
47
  };
46
48
  }
47
49
  /**
@@ -85,6 +87,99 @@ export function shouldInterrupt(nodeName, approvalMode) {
85
87
  const autoApproved = AUTO_APPROVED_NODES[approvalMode] ?? [];
86
88
  return !autoApproved.includes(nodeName);
87
89
  }
90
+ const API_ERROR_RE = /API Error: (400|429|5\d{2})/;
91
+ const NETWORK_ERROR_RE = /ECONNREFUSED|ETIMEDOUT|ENOTFOUND|timed out/i;
92
+ const NON_RETRYABLE_RE = /Process exited with code|ENOENT|SyntaxError/;
93
+ /**
94
+ * Classify an error message into a retry category.
95
+ *
96
+ * - **retryable-api**: Transient API errors (rate-limit, overload, bad request)
97
+ * - **retryable-network**: Network-level failures (DNS, timeouts, connection refused)
98
+ * - **non-retryable**: Logic / filesystem / syntax errors that won't resolve on retry
99
+ * - **unknown**: Anything unrecognised (callers may choose to retry cautiously)
100
+ */
101
+ export function classifyError(errorMessage) {
102
+ if (API_ERROR_RE.test(errorMessage))
103
+ return 'retryable-api';
104
+ if (NETWORK_ERROR_RE.test(errorMessage))
105
+ return 'retryable-network';
106
+ if (NON_RETRYABLE_RE.test(errorMessage))
107
+ return 'non-retryable';
108
+ return 'unknown';
109
+ }
110
+ /**
111
+ * Execute a prompt via the given executor with automatic retry and
112
+ * exponential back-off for transient errors.
113
+ *
114
+ * Non-retryable errors are thrown immediately. Unknown errors are
115
+ * retried (conservative stance: could be transient).
116
+ */
117
+ export async function retryExecute(executor, prompt, options, retryOpts) {
118
+ const maxAttempts = retryOpts?.maxAttempts ?? 3;
119
+ const baseDelayMs = retryOpts?.baseDelayMs ?? 2000;
120
+ const log = retryOpts?.logger;
121
+ let lastError;
122
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
123
+ try {
124
+ return await executor.execute(prompt, options);
125
+ }
126
+ catch (err) {
127
+ lastError = err instanceof Error ? err : new Error(String(err));
128
+ const category = classifyError(lastError.message);
129
+ if (category === 'non-retryable') {
130
+ throw lastError;
131
+ }
132
+ if (attempt < maxAttempts) {
133
+ const delayMs = baseDelayMs * Math.pow(2, attempt - 1);
134
+ log?.info(`Attempt ${attempt}/${maxAttempts} failed (${category}), retrying in ${delayMs}ms`);
135
+ await new Promise((resolve) => setTimeout(resolve, delayMs));
136
+ }
137
+ }
138
+ }
139
+ throw lastError;
140
+ }
141
+ /**
142
+ * Read completed phases from feature.yaml.
143
+ */
144
+ export function getCompletedPhases(specDir) {
145
+ const content = readSpecFile(specDir, 'feature.yaml');
146
+ if (!content)
147
+ return [];
148
+ try {
149
+ const data = yaml.load(content);
150
+ const status = (data?.status ?? {});
151
+ const phases = status.completedPhases;
152
+ return Array.isArray(phases) ? phases : [];
153
+ }
154
+ catch {
155
+ return [];
156
+ }
157
+ }
158
+ /**
159
+ * Mark a phase as complete in feature.yaml by adding its ID to completedPhases.
160
+ */
161
+ export function markPhaseComplete(specDir, phaseId, log) {
162
+ try {
163
+ const content = readSpecFile(specDir, 'feature.yaml');
164
+ if (!content)
165
+ return;
166
+ const data = yaml.load(content);
167
+ if (!data || typeof data !== 'object')
168
+ return;
169
+ const status = (data.status ?? {});
170
+ const completedPhases = Array.isArray(status.completedPhases)
171
+ ? [...status.completedPhases]
172
+ : [];
173
+ if (!completedPhases.includes(phaseId)) {
174
+ completedPhases.push(phaseId);
175
+ }
176
+ data.status = { ...status, completedPhases };
177
+ writeFileSync(join(specDir, 'feature.yaml'), yaml.dump(data, { indent: 2, lineWidth: -1 }), 'utf-8');
178
+ }
179
+ catch (err) {
180
+ log?.error(`Failed to mark phase complete: ${err instanceof Error ? err.message : String(err)}`);
181
+ }
182
+ }
88
183
  /**
89
184
  * Execute a node with consistent logging and error handling.
90
185
  *
@@ -7,5 +7,5 @@ import { useSidebar } from '../../ui/sidebar';
7
7
  import { cn } from '../../../lib/utils';
8
8
  export function SidebarSectionHeader({ label, onFolderClick, onMenuClick, className, }) {
9
9
  const { state, isMobile } = useSidebar();
10
- return (_jsx(SidebarGroup, { "data-testid": "sidebar-section-header", className: cn('px-2 py-1', className), children: _jsxs("div", { className: "text-sidebar-foreground flex h-7 w-full shrink-0 items-center px-2 text-sm font-medium", children: [_jsx("span", { className: "min-w-0 flex-1 truncate", children: label }), _jsxs("span", { className: "text-sidebar-foreground/70 flex shrink-0 items-center gap-0.5", children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: onFolderClick, className: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground ring-sidebar-ring flex size-7 items-center justify-center rounded-md p-0 outline-hidden transition-colors focus-visible:ring-2 [&>svg]:size-4", "aria-label": "Open features folder", children: _jsx(Folder, { className: "size-4" }) }) }), _jsx(TooltipContent, { side: "right", align: "center", hidden: state !== 'collapsed' || isMobile, children: "Open features folder" })] }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: onMenuClick, className: "hover:bg-sidebar-accent hover:text-sidebar-accent-foreground ring-sidebar-ring flex size-7 items-center justify-center rounded-md p-0 outline-hidden transition-colors focus-visible:ring-2 [&>svg]:size-4", "aria-label": "Features options", children: _jsx(GripHorizontal, { className: "size-4" }) }) }), _jsx(TooltipContent, { side: "right", align: "center", hidden: state !== 'collapsed' || isMobile, children: "Features options" })] })] })] }) }));
10
+ return (_jsx(SidebarGroup, { "data-testid": "sidebar-section-header", className: cn('px-2 py-1', className), children: _jsxs("div", { className: "text-sidebar-foreground flex h-7 w-full shrink-0 items-center px-2 text-sm font-medium", children: [_jsx("span", { className: "min-w-0 flex-1 truncate", children: label }), _jsxs("span", { className: "text-sidebar-foreground/70 flex shrink-0 items-center gap-0.5", children: [_jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: onFolderClick, className: "ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex size-7 items-center justify-center rounded-md p-0 outline-hidden transition-colors focus-visible:ring-2 [&>svg]:size-4", "aria-label": "Open features folder", children: _jsx(Folder, { className: "size-4" }) }) }), _jsx(TooltipContent, { side: "right", align: "center", hidden: state !== 'collapsed' || isMobile, children: "Open features folder" })] }), _jsxs(Tooltip, { children: [_jsx(TooltipTrigger, { asChild: true, children: _jsx("button", { type: "button", onClick: onMenuClick, className: "ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground flex size-7 items-center justify-center rounded-md p-0 outline-hidden transition-colors focus-visible:ring-2 [&>svg]:size-4", "aria-label": "Features options", children: _jsx(GripHorizontal, { className: "size-4" }) }) }), _jsx(TooltipContent, { side: "right", align: "center", hidden: state !== 'collapsed' || isMobile, children: "Features options" })] })] })] }) }));
11
11
  }
@@ -2,7 +2,7 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import * as React from 'react';
3
3
  import { cva } from 'class-variance-authority';
4
4
  import { cn } from '../../lib/utils';
5
- const alertVariants = cva('[&>svg]:text-foreground relative w-full rounded-lg border px-4 py-3 text-sm [&>svg]:absolute [&>svg]:top-4 [&>svg]:left-4 [&>svg+div]:translate-y-[-3px] [&>svg~*]:pl-7', {
5
+ const alertVariants = cva('relative w-full rounded-lg border px-4 py-3 text-sm [&>svg]:absolute [&>svg]:top-4 [&>svg]:left-4 [&>svg]:text-foreground [&>svg+div]:translate-y-[-3px] [&>svg~*]:pl-7', {
6
6
  variants: {
7
7
  variant: {
8
8
  default: 'bg-background text-foreground',
@@ -1,12 +1,12 @@
1
1
  import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cva } from 'class-variance-authority';
3
3
  import { cn } from '../../lib/utils';
4
- const badgeVariants = cva('focus:ring-ring inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:ring-2 focus:ring-offset-2 focus:outline-none', {
4
+ const badgeVariants = cva('inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-none', {
5
5
  variants: {
6
6
  variant: {
7
- default: 'bg-primary text-primary-foreground hover:bg-primary/80 border-transparent shadow',
8
- secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80 border-transparent',
9
- destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/80 border-transparent shadow',
7
+ default: 'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
8
+ secondary: 'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
9
+ destructive: 'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
10
10
  outline: 'text-foreground',
11
11
  },
12
12
  },
@@ -2,12 +2,12 @@ import { jsx as _jsx } from "react/jsx-runtime";
2
2
  import { cva } from 'class-variance-authority';
3
3
  import { Slot } from 'radix-ui';
4
4
  import { cn } from '../../lib/utils';
5
- const buttonVariants = cva("focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:ring-[3px] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
5
+ const buttonVariants = cva("inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", {
6
6
  variants: {
7
7
  variant: {
8
8
  default: 'bg-primary text-primary-foreground hover:bg-primary/90',
9
- destructive: 'bg-destructive hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60 text-white',
10
- outline: 'bg-background hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 border shadow-xs',
9
+ destructive: 'bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40',
10
+ outline: 'border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50',
11
11
  secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
12
12
  ghost: 'hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50',
13
13
  link: 'text-primary underline-offset-4 hover:underline',
@@ -148,11 +148,11 @@ function SidebarMenu({ className, ...props }) {
148
148
  function SidebarMenuItem({ className, ...props }) {
149
149
  return (_jsx("li", { "data-slot": "sidebar-menu-item", "data-sidebar": "menu-item", className: cn('group/menu-item relative', className), ...props }));
150
150
  }
151
- const sidebarMenuButtonVariants = cva('peer/menu-button ring-sidebar-ring hover:bg-sidebar-accent hover:text-sidebar-accent-foreground active:bg-sidebar-accent active:text-sidebar-accent-foreground data-[active=true]:bg-sidebar-accent data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! focus-visible:ring-2 disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:font-medium [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0', {
151
+ const sidebarMenuButtonVariants = cva('peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm ring-sidebar-ring outline-hidden transition-[width,height,padding] group-has-data-[sidebar=menu-action]/menu-item:pr-8 group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-2! hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0', {
152
152
  variants: {
153
153
  variant: {
154
154
  default: 'hover:bg-sidebar-accent hover:text-sidebar-accent-foreground',
155
- outline: 'bg-background hover:bg-sidebar-accent hover:text-sidebar-accent-foreground shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
155
+ outline: 'bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]',
156
156
  },
157
157
  size: {
158
158
  default: 'h-8 text-sm',