@shepai/cli 1.20.0 → 1.20.1

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 (70) hide show
  1. package/dist/eslint.config.mjs +1 -0
  2. package/dist/src/application/use-cases/features/create/create-feature.use-case.d.ts +37 -0
  3. package/dist/src/application/use-cases/features/create/create-feature.use-case.d.ts.map +1 -0
  4. package/dist/src/application/use-cases/features/{create-feature.use-case.js → create/create-feature.use-case.js} +17 -100
  5. package/dist/src/application/use-cases/features/create/metadata-generator.d.ts +28 -0
  6. package/dist/src/application/use-cases/features/create/metadata-generator.d.ts.map +1 -0
  7. package/dist/src/application/use-cases/features/create/metadata-generator.js +109 -0
  8. package/dist/src/application/use-cases/features/create/slug-resolver.d.ts +24 -0
  9. package/dist/src/application/use-cases/features/create/slug-resolver.d.ts.map +1 -0
  10. package/dist/src/application/use-cases/features/create/slug-resolver.js +66 -0
  11. package/dist/src/application/use-cases/features/create/types.d.ts +16 -0
  12. package/dist/src/application/use-cases/features/create/types.d.ts.map +1 -0
  13. package/dist/src/application/use-cases/features/create/types.js +1 -0
  14. package/dist/src/infrastructure/di/container.d.ts.map +1 -1
  15. package/dist/src/infrastructure/di/container.js +5 -1
  16. package/dist/src/infrastructure/services/agents/common/executors/mock-executor.service.js +2 -2
  17. package/dist/src/presentation/cli/commands/feat/new.command.js +1 -1
  18. package/dist/tsconfig.build.tsbuildinfo +1 -1
  19. package/package.json +3 -1
  20. package/web/.next/BUILD_ID +1 -1
  21. package/web/.next/build-manifest.json +2 -2
  22. package/web/.next/cache/.previewinfo +1 -1
  23. package/web/.next/cache/.rscinfo +1 -1
  24. package/web/.next/cache/config.json +3 -3
  25. package/web/.next/fallback-build-manifest.json +2 -2
  26. package/web/.next/prerender-manifest.json +3 -3
  27. package/web/.next/required-server-files.js +1 -1
  28. package/web/.next/required-server-files.json +1 -1
  29. package/web/.next/server/app/_global-error.html +2 -2
  30. package/web/.next/server/app/_global-error.rsc +1 -1
  31. package/web/.next/server/app/_global-error.segments/__PAGE__.segment.rsc +1 -1
  32. package/web/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  33. package/web/.next/server/app/_global-error.segments/_head.segment.rsc +1 -1
  34. package/web/.next/server/app/_global-error.segments/_index.segment.rsc +1 -1
  35. package/web/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  36. package/web/.next/server/app/_not-found.html +2 -2
  37. package/web/.next/server/app/_not-found.rsc +1 -1
  38. package/web/.next/server/app/_not-found.segments/_full.segment.rsc +1 -1
  39. package/web/.next/server/app/_not-found.segments/_head.segment.rsc +1 -1
  40. package/web/.next/server/app/_not-found.segments/_index.segment.rsc +1 -1
  41. package/web/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +1 -1
  42. package/web/.next/server/app/_not-found.segments/_not-found.segment.rsc +1 -1
  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 +1 -1
  46. package/web/.next/server/app/index.segments/__PAGE__.segment.rsc +1 -1
  47. package/web/.next/server/app/index.segments/_full.segment.rsc +1 -1
  48. package/web/.next/server/app/index.segments/_head.segment.rsc +1 -1
  49. package/web/.next/server/app/index.segments/_index.segment.rsc +1 -1
  50. package/web/.next/server/app/index.segments/_tree.segment.rsc +1 -1
  51. package/web/.next/server/app/version.html +2 -2
  52. package/web/.next/server/app/version.rsc +1 -1
  53. package/web/.next/server/app/version.segments/_full.segment.rsc +1 -1
  54. package/web/.next/server/app/version.segments/_head.segment.rsc +1 -1
  55. package/web/.next/server/app/version.segments/_index.segment.rsc +1 -1
  56. package/web/.next/server/app/version.segments/_tree.segment.rsc +1 -1
  57. package/web/.next/server/app/version.segments/version/__PAGE__.segment.rsc +1 -1
  58. package/web/.next/server/app/version.segments/version.segment.rsc +1 -1
  59. package/web/.next/server/chunks/ssr/_19be0743._.js +1 -1
  60. package/web/.next/server/pages/404.html +2 -2
  61. package/web/.next/server/pages/500.html +2 -2
  62. package/web/.next/server/server-reference-manifest.js +1 -1
  63. package/web/.next/server/server-reference-manifest.json +1 -1
  64. package/web/.next/trace +1 -1
  65. package/web/.next/trace-build +1 -1
  66. package/dist/src/application/use-cases/features/create-feature.use-case.d.ts +0 -53
  67. package/dist/src/application/use-cases/features/create-feature.use-case.d.ts.map +0 -1
  68. /package/web/.next/static/{u-tyZUMnsIne6K_0rPmPO → xKJulVMYO_ZRGoqa6eM7G}/_buildManifest.js +0 -0
  69. /package/web/.next/static/{u-tyZUMnsIne6K_0rPmPO → xKJulVMYO_ZRGoqa6eM7G}/_clientMiddlewareManifest.json +0 -0
  70. /package/web/.next/static/{u-tyZUMnsIne6K_0rPmPO → xKJulVMYO_ZRGoqa6eM7G}/_ssgManifest.js +0 -0
@@ -53,6 +53,7 @@ export default tseslint.config(
53
53
  '*.config.js',
54
54
  '*.config.mjs',
55
55
  '*.config.cjs',
56
+ '*.config.ts',
56
57
  ],
57
58
  },
58
59
  // =============================================================================
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Create Feature Use Case
3
+ *
4
+ * Creates a new feature entity with a git worktree for isolated development,
5
+ * initializes the spec directory with YAML templates, then spawns the
6
+ * feature agent to begin autonomous SDLC processing.
7
+ *
8
+ * Business Rules:
9
+ * - Metadata generation delegated to MetadataGenerator service
10
+ * - Slug uniqueness delegated to SlugResolver service
11
+ * - Full user input is preserved as-is in spec.yaml
12
+ * - A git worktree is created at ~/.shep/repos/HASH/wt/SLUG/
13
+ * - Spec YAML files are scaffolded at WORKTREE/specs/NNN-SLUG/
14
+ * - Feature agent is spawned with the spec dir path
15
+ * - specPath is persisted on the Feature record
16
+ * - Initial lifecycle is Requirements
17
+ */
18
+ import type { IFeatureRepository } from '../../../ports/output/repositories/feature-repository.interface.js';
19
+ import type { IWorktreeService } from '../../../ports/output/services/worktree-service.interface.js';
20
+ import type { IFeatureAgentProcessService } from '../../../ports/output/agents/feature-agent-process.interface.js';
21
+ import type { IAgentRunRepository } from '../../../ports/output/agents/agent-run-repository.interface.js';
22
+ import type { ISpecInitializerService } from '../../../ports/output/services/spec-initializer.interface.js';
23
+ import { MetadataGenerator } from './metadata-generator.js';
24
+ import { SlugResolver } from './slug-resolver.js';
25
+ import type { CreateFeatureInput, CreateFeatureResult } from './types.js';
26
+ export declare class CreateFeatureUseCase {
27
+ private readonly featureRepo;
28
+ private readonly worktreeService;
29
+ private readonly agentProcess;
30
+ private readonly runRepository;
31
+ private readonly specInitializer;
32
+ private readonly metadataGenerator;
33
+ private readonly slugResolver;
34
+ constructor(featureRepo: IFeatureRepository, worktreeService: IWorktreeService, agentProcess: IFeatureAgentProcessService, runRepository: IAgentRunRepository, specInitializer: ISpecInitializerService, metadataGenerator: MetadataGenerator, slugResolver: SlugResolver);
35
+ execute(input: CreateFeatureInput): Promise<CreateFeatureResult>;
36
+ }
37
+ //# sourceMappingURL=create-feature.use-case.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"create-feature.use-case.d.ts","sourceRoot":"","sources":["../../../../../../src/application/use-cases/features/create/create-feature.use-case.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAMH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oEAAoE,CAAC;AAC7G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8DAA8D,CAAC;AACrG,OAAO,KAAK,EAAE,2BAA2B,EAAE,MAAM,iEAAiE,CAAC;AACnH,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,gEAAgE,CAAC;AAC1G,OAAO,KAAK,EAAE,uBAAuB,EAAE,MAAM,8DAA8D,CAAC;AAE5G,OAAO,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAC5D,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,YAAY,CAAC;AAE1E,qBACa,oBAAoB;IAG7B,OAAO,CAAC,QAAQ,CAAC,WAAW;IAE5B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAEhC,OAAO,CAAC,QAAQ,CAAC,YAAY;IAE7B,OAAO,CAAC,QAAQ,CAAC,aAAa;IAE9B,OAAO,CAAC,QAAQ,CAAC,eAAe;IAEhC,OAAO,CAAC,QAAQ,CAAC,iBAAiB;IAElC,OAAO,CAAC,QAAQ,CAAC,YAAY;gBAZZ,WAAW,EAAE,kBAAkB,EAE/B,eAAe,EAAE,gBAAgB,EAEjC,YAAY,EAAE,2BAA2B,EAEzC,aAAa,EAAE,mBAAmB,EAElC,eAAe,EAAE,uBAAuB,EAExC,iBAAiB,EAAE,iBAAiB,EAEpC,YAAY,EAAE,YAAY;IAGvC,OAAO,CAAC,KAAK,EAAE,kBAAkB,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAyEvE"}
@@ -6,10 +6,9 @@
6
6
  * feature agent to begin autonomous SDLC processing.
7
7
  *
8
8
  * Business Rules:
9
- * - AI generates slug/name/description from user's free-text input
10
- * - Falls back to regex-based slug if AI fails
9
+ * - Metadata generation delegated to MetadataGenerator service
10
+ * - Slug uniqueness delegated to SlugResolver service
11
11
  * - Full user input is preserved as-is in spec.yaml
12
- * - Slug must be unique within the repository
13
12
  * - A git worktree is created at ~/.shep/repos/HASH/wt/SLUG/
14
13
  * - Spec YAML files are scaffolded at WORKTREE/specs/NNN-SLUG/
15
14
  * - Feature agent is spawned with the spec dir path
@@ -30,31 +29,32 @@ var __param = (this && this.__param) || function (paramIndex, decorator) {
30
29
  };
31
30
  import { injectable, inject } from 'tsyringe';
32
31
  import { randomUUID } from 'node:crypto';
33
- import { SdlcLifecycle } from '../../../domain/generated/output.js';
34
- import { AgentRunStatus } from '../../../domain/generated/output.js';
35
- import { getSettings } from '../../../infrastructure/services/settings.service.js';
36
- /** Maximum characters of user input sent to the AI for metadata generation. */
37
- const MAX_INPUT_FOR_AI = 500;
32
+ import { SdlcLifecycle, AgentRunStatus } from '../../../../domain/generated/output.js';
33
+ import { getSettings } from '../../../../infrastructure/services/settings.service.js';
34
+ import { MetadataGenerator } from './metadata-generator.js';
35
+ import { SlugResolver } from './slug-resolver.js';
38
36
  let CreateFeatureUseCase = class CreateFeatureUseCase {
39
37
  featureRepo;
40
38
  worktreeService;
41
39
  agentProcess;
42
40
  runRepository;
43
41
  specInitializer;
44
- executorProvider;
45
- constructor(featureRepo, worktreeService, agentProcess, runRepository, specInitializer, executorProvider) {
42
+ metadataGenerator;
43
+ slugResolver;
44
+ constructor(featureRepo, worktreeService, agentProcess, runRepository, specInitializer, metadataGenerator, slugResolver) {
46
45
  this.featureRepo = featureRepo;
47
46
  this.worktreeService = worktreeService;
48
47
  this.agentProcess = agentProcess;
49
48
  this.runRepository = runRepository;
50
49
  this.specInitializer = specInitializer;
51
- this.executorProvider = executorProvider;
50
+ this.metadataGenerator = metadataGenerator;
51
+ this.slugResolver = slugResolver;
52
52
  }
53
53
  async execute(input) {
54
- const metadata = await this.generateMetadata(input.userInput);
54
+ const metadata = await this.metadataGenerator.generateMetadata(input.userInput);
55
55
  const originalSlug = metadata.slug;
56
56
  // Find a unique slug — the branch may exist from a previous (deleted) feature
57
- const { slug, branch, warning } = await this.resolveUniqueSlug(originalSlug, input.repositoryPath);
57
+ const { slug, branch, warning } = await this.slugResolver.resolveUniqueSlug(originalSlug, input.repositoryPath);
58
58
  // Determine next feature number for this repo
59
59
  const existingFeatures = await this.featureRepo.list({
60
60
  repositoryPath: input.repositoryPath,
@@ -105,91 +105,6 @@ let CreateFeatureUseCase = class CreateFeatureUseCase {
105
105
  });
106
106
  return { feature, warning };
107
107
  }
108
- /**
109
- * Find a unique slug by checking both the feature DB and git branches.
110
- * If the original slug conflicts, appends -2, -3, etc. until a free one is found.
111
- */
112
- async resolveUniqueSlug(originalSlug, repositoryPath) {
113
- const MAX_SUFFIX = 10;
114
- for (let suffix = 0; suffix <= MAX_SUFFIX; suffix++) {
115
- const slug = suffix === 0 ? originalSlug : `${originalSlug}-${suffix + 1}`;
116
- const branch = `feat/${slug}`;
117
- // Check if a feature with this slug already exists in the DB
118
- const existing = await this.featureRepo.findBySlug(slug, repositoryPath);
119
- if (existing) {
120
- if (suffix === 0)
121
- continue; // try suffixed version
122
- continue;
123
- }
124
- // Check if the git branch already exists (leftover from deleted feature)
125
- const branchInUse = await this.worktreeService.exists(repositoryPath, branch);
126
- if (branchInUse)
127
- continue;
128
- if (suffix > 0) {
129
- return {
130
- slug,
131
- branch,
132
- warning: `Branch "feat/${originalSlug}" already exists, using "${branch}" instead`,
133
- };
134
- }
135
- return { slug, branch };
136
- }
137
- throw new Error(`Could not find a unique slug for "${originalSlug}" after ${MAX_SUFFIX} attempts`);
138
- }
139
- async generateMetadata(userInput) {
140
- try {
141
- const executor = this.executorProvider.getExecutor();
142
- const truncated = userInput.length > MAX_INPUT_FOR_AI
143
- ? `${userInput.slice(0, MAX_INPUT_FOR_AI)}...`
144
- : userInput;
145
- const prompt = `Generate feature metadata from this user request:
146
- "${truncated}"
147
-
148
- Return ONLY a JSON object with these fields:
149
- - slug: kebab-case identifier, 2-4 words max (e.g., "github-oauth-login")
150
- - name: short human-readable title (e.g., "GitHub OAuth Login")
151
- - description: refined 1-2 sentence description
152
-
153
- JSON only, no markdown fences.`;
154
- const result = await executor.execute(prompt, {
155
- maxTurns: 1,
156
- allowedTools: [],
157
- silent: true,
158
- });
159
- const parsed = JSON.parse(result.result);
160
- if (!parsed.slug || !parsed.name || !parsed.description) {
161
- throw new Error('Missing required fields in AI response');
162
- }
163
- return {
164
- slug: this.toSlug(parsed.slug),
165
- name: parsed.name,
166
- description: parsed.description,
167
- };
168
- }
169
- catch {
170
- // Fallback to regex-based slug generation
171
- return {
172
- slug: this.toSlug(userInput),
173
- name: userInput,
174
- description: userInput,
175
- };
176
- }
177
- }
178
- toSlug(text) {
179
- const slug = text
180
- .toLowerCase()
181
- .replace(/[^a-z0-9\s-]/g, '')
182
- .replace(/\s+/g, '-')
183
- .replace(/-+/g, '-')
184
- .replace(/^-|-$/g, '');
185
- // Limit to ~50 chars, cutting at a word boundary
186
- if (slug.length <= 50) {
187
- return slug;
188
- }
189
- const truncated = slug.slice(0, 50);
190
- const lastDash = truncated.lastIndexOf('-');
191
- return lastDash > 10 ? truncated.slice(0, lastDash) : truncated;
192
- }
193
108
  };
194
109
  CreateFeatureUseCase = __decorate([
195
110
  injectable(),
@@ -198,7 +113,9 @@ CreateFeatureUseCase = __decorate([
198
113
  __param(2, inject('IFeatureAgentProcessService')),
199
114
  __param(3, inject('IAgentRunRepository')),
200
115
  __param(4, inject('ISpecInitializerService')),
201
- __param(5, inject('IAgentExecutorProvider')),
202
- __metadata("design:paramtypes", [Object, Object, Object, Object, Object, Object])
116
+ __param(5, inject(MetadataGenerator)),
117
+ __param(6, inject(SlugResolver)),
118
+ __metadata("design:paramtypes", [Object, Object, Object, Object, Object, MetadataGenerator,
119
+ SlugResolver])
203
120
  ], CreateFeatureUseCase);
204
121
  export { CreateFeatureUseCase };
@@ -0,0 +1,28 @@
1
+ /**
2
+ * MetadataGenerator
3
+ *
4
+ * Generates feature metadata (slug, name, description) from user input
5
+ * via AI call through IAgentExecutorProvider.
6
+ * Errors are propagated to the caller.
7
+ */
8
+ import type { IAgentExecutorProvider } from '../../../ports/output/agents/agent-executor-provider.interface.js';
9
+ import type { FeatureMetadata } from './types.js';
10
+ export declare class MetadataGenerator {
11
+ private readonly executorProvider;
12
+ constructor(executorProvider: IAgentExecutorProvider);
13
+ /**
14
+ * Generate feature metadata from user input via AI.
15
+ * Errors are propagated to the caller.
16
+ */
17
+ generateMetadata(userInput: string): Promise<FeatureMetadata>;
18
+ /**
19
+ * Strip markdown code fence wrappers (```lang ... ```) if present at start/end.
20
+ */
21
+ private stripCodeFence;
22
+ /**
23
+ * Convert text to kebab-case slug with 50-char limit.
24
+ * Removes special characters, collapses spaces, limits length.
25
+ */
26
+ private toSlug;
27
+ }
28
+ //# sourceMappingURL=metadata-generator.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metadata-generator.d.ts","sourceRoot":"","sources":["../../../../../../src/application/use-cases/features/create/metadata-generator.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAGH,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,mEAAmE,CAAC;AAChH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAKlD,qBACa,iBAAiB;IAG1B,OAAO,CAAC,QAAQ,CAAC,gBAAgB;gBAAhB,gBAAgB,EAAE,sBAAsB;IAG3D;;;OAGG;IACG,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAyCnE;;OAEG;IACH,OAAO,CAAC,cAAc;IAgBtB;;;OAGG;IACH,OAAO,CAAC,MAAM;CAgBf"}
@@ -0,0 +1,109 @@
1
+ /**
2
+ * MetadataGenerator
3
+ *
4
+ * Generates feature metadata (slug, name, description) from user input
5
+ * via AI call through IAgentExecutorProvider.
6
+ * Errors are propagated to the caller.
7
+ */
8
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
9
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
10
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
11
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
12
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
13
+ };
14
+ var __metadata = (this && this.__metadata) || function (k, v) {
15
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
16
+ };
17
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
18
+ return function (target, key) { decorator(target, key, paramIndex); }
19
+ };
20
+ import { injectable, inject } from 'tsyringe';
21
+ /** Maximum characters of user input sent to the AI for metadata generation. */
22
+ const MAX_INPUT_FOR_AI = 500;
23
+ let MetadataGenerator = class MetadataGenerator {
24
+ executorProvider;
25
+ constructor(executorProvider) {
26
+ this.executorProvider = executorProvider;
27
+ }
28
+ /**
29
+ * Generate feature metadata from user input via AI.
30
+ * Errors are propagated to the caller.
31
+ */
32
+ async generateMetadata(userInput) {
33
+ const executor = this.executorProvider.getExecutor();
34
+ const truncated = userInput.length > MAX_INPUT_FOR_AI
35
+ ? `${userInput.slice(0, MAX_INPUT_FOR_AI)}...`
36
+ : userInput;
37
+ const prompt = `Analyze this user request and extract the CORE feature intent. Condense to essential concepts.
38
+
39
+ User request:
40
+ "${truncated}"
41
+
42
+ IMPORTANT: Don't use the request as-is. Extract the key feature being requested and improve the wording.
43
+
44
+ Return ONLY a JSON object with these fields:
45
+ - slug: kebab-case identifier, 2-4 words MAX. Extract the core feature name (e.g., "github-oauth-login" from "add github oauth")
46
+ - name: polished, professional title (improve upon user's wording)
47
+ - description: refined 1-2 sentence description that captures the feature essence (not the request verbatim)
48
+
49
+ JSON only, no markdown fences.`;
50
+ const result = await executor.execute(prompt, {
51
+ maxTurns: 1,
52
+ allowedTools: [],
53
+ silent: true,
54
+ });
55
+ const cleaned = this.stripCodeFence(result.result);
56
+ const parsed = JSON.parse(cleaned);
57
+ if (!parsed.slug || !parsed.name || !parsed.description) {
58
+ throw new Error('Missing required fields in AI response');
59
+ }
60
+ return {
61
+ slug: this.toSlug(parsed.slug),
62
+ name: parsed.name,
63
+ description: parsed.description,
64
+ };
65
+ }
66
+ /**
67
+ * Strip markdown code fence wrappers (```lang ... ```) if present at start/end.
68
+ */
69
+ stripCodeFence(text) {
70
+ const trimmed = text.trim();
71
+ if (!trimmed.startsWith('```')) {
72
+ return trimmed;
73
+ }
74
+ // Find end of first line (```json or ```anything)
75
+ const firstNewline = trimmed.indexOf('\n');
76
+ if (firstNewline === -1) {
77
+ return trimmed;
78
+ }
79
+ if (!trimmed.endsWith('```')) {
80
+ return trimmed;
81
+ }
82
+ return trimmed.slice(firstNewline + 1, trimmed.length - 3).trim();
83
+ }
84
+ /**
85
+ * Convert text to kebab-case slug with 50-char limit.
86
+ * Removes special characters, collapses spaces, limits length.
87
+ */
88
+ toSlug(text) {
89
+ const slug = text
90
+ .toLowerCase()
91
+ .replace(/[^a-z0-9\s-]/g, '')
92
+ .replace(/\s+/g, '-')
93
+ .replace(/-+/g, '-')
94
+ .replace(/^-|-$/g, '');
95
+ // Limit to ~50 chars, cutting at a word boundary
96
+ if (slug.length <= 50) {
97
+ return slug;
98
+ }
99
+ const truncated = slug.slice(0, 50);
100
+ const lastDash = truncated.lastIndexOf('-');
101
+ return lastDash > 10 ? truncated.slice(0, lastDash) : truncated;
102
+ }
103
+ };
104
+ MetadataGenerator = __decorate([
105
+ injectable(),
106
+ __param(0, inject('IAgentExecutorProvider')),
107
+ __metadata("design:paramtypes", [Object])
108
+ ], MetadataGenerator);
109
+ export { MetadataGenerator };
@@ -0,0 +1,24 @@
1
+ /**
2
+ * SlugResolver
3
+ *
4
+ * Resolves unique slugs by checking against database and git branches.
5
+ * Ensures no slug collision by appending numeric suffixes (-2, -3, etc.)
6
+ */
7
+ import type { IFeatureRepository } from '../../../ports/output/repositories/feature-repository.interface.js';
8
+ import type { IWorktreeService } from '../../../ports/output/services/worktree-service.interface.js';
9
+ export interface SlugResolutionResult {
10
+ slug: string;
11
+ branch: string;
12
+ warning?: string;
13
+ }
14
+ export declare class SlugResolver {
15
+ private readonly featureRepo;
16
+ private readonly worktreeService;
17
+ constructor(featureRepo: IFeatureRepository, worktreeService: IWorktreeService);
18
+ /**
19
+ * Find a unique slug by checking both the feature DB and git branches.
20
+ * If the original slug conflicts, appends -2, -3, etc. until a free one is found.
21
+ */
22
+ resolveUniqueSlug(originalSlug: string, repositoryPath: string): Promise<SlugResolutionResult>;
23
+ }
24
+ //# sourceMappingURL=slug-resolver.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"slug-resolver.d.ts","sourceRoot":"","sources":["../../../../../../src/application/use-cases/features/create/slug-resolver.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oEAAoE,CAAC;AAC7G,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,8DAA8D,CAAC;AAKrG,MAAM,WAAW,oBAAoB;IACnC,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBACa,YAAY;IAGrB,OAAO,CAAC,QAAQ,CAAC,WAAW;IAE5B,OAAO,CAAC,QAAQ,CAAC,eAAe;gBAFf,WAAW,EAAE,kBAAkB,EAE/B,eAAe,EAAE,gBAAgB;IAGpD;;;OAGG;IACG,iBAAiB,CACrB,YAAY,EAAE,MAAM,EACpB,cAAc,EAAE,MAAM,GACrB,OAAO,CAAC,oBAAoB,CAAC;CA+BjC"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * SlugResolver
3
+ *
4
+ * Resolves unique slugs by checking against database and git branches.
5
+ * Ensures no slug collision by appending numeric suffixes (-2, -3, etc.)
6
+ */
7
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
8
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
9
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
10
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
11
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
12
+ };
13
+ var __metadata = (this && this.__metadata) || function (k, v) {
14
+ if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
15
+ };
16
+ var __param = (this && this.__param) || function (paramIndex, decorator) {
17
+ return function (target, key) { decorator(target, key, paramIndex); }
18
+ };
19
+ import { injectable, inject } from 'tsyringe';
20
+ /** Maximum number of suffix attempts before giving up */
21
+ const MAX_SUFFIX = 10;
22
+ let SlugResolver = class SlugResolver {
23
+ featureRepo;
24
+ worktreeService;
25
+ constructor(featureRepo, worktreeService) {
26
+ this.featureRepo = featureRepo;
27
+ this.worktreeService = worktreeService;
28
+ }
29
+ /**
30
+ * Find a unique slug by checking both the feature DB and git branches.
31
+ * If the original slug conflicts, appends -2, -3, etc. until a free one is found.
32
+ */
33
+ async resolveUniqueSlug(originalSlug, repositoryPath) {
34
+ for (let suffix = 0; suffix <= MAX_SUFFIX; suffix++) {
35
+ const slug = suffix === 0 ? originalSlug : `${originalSlug}-${suffix + 1}`;
36
+ const branch = `feat/${slug}`;
37
+ // Check if a feature with this slug already exists in the DB
38
+ const existing = await this.featureRepo.findBySlug(slug, repositoryPath);
39
+ if (existing) {
40
+ if (suffix === 0)
41
+ continue; // try suffixed version
42
+ continue;
43
+ }
44
+ // Check if the git branch already exists (leftover from deleted feature)
45
+ const branchInUse = await this.worktreeService.exists(repositoryPath, branch);
46
+ if (branchInUse)
47
+ continue;
48
+ if (suffix > 0) {
49
+ return {
50
+ slug,
51
+ branch,
52
+ warning: `Branch "feat/${originalSlug}" already exists, using "${branch}" instead`,
53
+ };
54
+ }
55
+ return { slug, branch };
56
+ }
57
+ throw new Error(`Could not find a unique slug for "${originalSlug}" after ${MAX_SUFFIX} attempts`);
58
+ }
59
+ };
60
+ SlugResolver = __decorate([
61
+ injectable(),
62
+ __param(0, inject('IFeatureRepository')),
63
+ __param(1, inject('IWorktreeService')),
64
+ __metadata("design:paramtypes", [Object, Object])
65
+ ], SlugResolver);
66
+ export { SlugResolver };
@@ -0,0 +1,16 @@
1
+ import type { ApprovalGates, Feature } from '../../../../domain/generated/output.js';
2
+ export interface CreateFeatureInput {
3
+ userInput: string;
4
+ repositoryPath: string;
5
+ approvalGates?: ApprovalGates;
6
+ }
7
+ export interface CreateFeatureResult {
8
+ feature: Feature;
9
+ warning?: string;
10
+ }
11
+ export interface FeatureMetadata {
12
+ slug: string;
13
+ name: string;
14
+ description: string;
15
+ }
16
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../../../src/application/use-cases/features/create/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,wCAAwC,CAAC;AAErF,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,aAAa,CAAC;CAC/B;AAED,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,OAAO,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB"}
@@ -1 +1 @@
1
- {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../../../src/infrastructure/di/container.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAoErC;;;;;GAKG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,SAAS,CAAC,CA4HrE;AAED;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,CAAC"}
1
+ {"version":3,"file":"container.d.ts","sourceRoot":"","sources":["../../../../src/infrastructure/di/container.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,kBAAkB,CAAC;AAC1B,OAAO,EAAE,SAAS,EAAE,MAAM,UAAU,CAAC;AAsErC;;;;;GAKG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,OAAO,SAAS,CAAC,CA8HrE;AAED;;;GAGG;AACH,OAAO,EAAE,SAAS,EAAE,CAAC"}
@@ -44,7 +44,9 @@ import { StopAgentRunUseCase } from '../../application/use-cases/agents/stop-age
44
44
  import { DeleteAgentRunUseCase } from '../../application/use-cases/agents/delete-agent-run.use-case.js';
45
45
  import { ApproveAgentRunUseCase } from '../../application/use-cases/agents/approve-agent-run.use-case.js';
46
46
  import { RejectAgentRunUseCase } from '../../application/use-cases/agents/reject-agent-run.use-case.js';
47
- import { CreateFeatureUseCase } from '../../application/use-cases/features/create-feature.use-case.js';
47
+ import { CreateFeatureUseCase } from '../../application/use-cases/features/create/create-feature.use-case.js';
48
+ import { MetadataGenerator } from '../../application/use-cases/features/create/metadata-generator.js';
49
+ import { SlugResolver } from '../../application/use-cases/features/create/slug-resolver.js';
48
50
  import { ListFeaturesUseCase } from '../../application/use-cases/features/list-features.use-case.js';
49
51
  import { ShowFeatureUseCase } from '../../application/use-cases/features/show-feature.use-case.js';
50
52
  import { DeleteFeatureUseCase } from '../../application/use-cases/features/delete-feature.use-case.js';
@@ -160,6 +162,8 @@ export async function initializeContainer() {
160
162
  container.registerSingleton(DeleteAgentRunUseCase);
161
163
  container.registerSingleton(ApproveAgentRunUseCase);
162
164
  container.registerSingleton(RejectAgentRunUseCase);
165
+ container.registerSingleton(MetadataGenerator);
166
+ container.registerSingleton(SlugResolver);
163
167
  container.registerSingleton(CreateFeatureUseCase);
164
168
  container.registerSingleton(ListFeaturesUseCase);
165
169
  container.registerSingleton(ShowFeatureUseCase);
@@ -19,10 +19,10 @@ function toTitleCase(text) {
19
19
  }
20
20
  /**
21
21
  * Extract the quoted user input from the metadata generation prompt.
22
- * Prompt format: `Generate feature metadata from this user request:\n"<input>"\n...`
22
+ * Prompt format: `...User request:\n"<input>"\n...`
23
23
  */
24
24
  function extractUserInput(prompt) {
25
- const match = prompt.match(/user request:\n"(.+?)"\n/s);
25
+ const match = prompt.match(/User request:\n"(.+?)"\n/s);
26
26
  return match ? match[1] : null;
27
27
  }
28
28
  export class MockAgentExecutorService {
@@ -13,7 +13,7 @@ import { Command } from 'commander';
13
13
  import { createHash } from 'node:crypto';
14
14
  import { join } from 'node:path';
15
15
  import { container } from '../../../../infrastructure/di/container.js';
16
- import { CreateFeatureUseCase } from '../../../../application/use-cases/features/create-feature.use-case.js';
16
+ import { CreateFeatureUseCase } from '../../../../application/use-cases/features/create/create-feature.use-case.js';
17
17
  import { colors, messages, spinner } from '../../ui/index.js';
18
18
  import { SHEP_HOME_DIR } from '../../../../infrastructure/services/filesystem/shep-directory.service.js';
19
19
  /**