Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
ca52446
docs(specs): analyze repository and define spec for prototype explora…
arielshad Apr 2, 2026
da6ecd2
docs(specs): define requirements and product questions for prototype …
arielshad Apr 2, 2026
3a3ac78
docs(specs): research technical decisions for prototype exploration mode
arielshad Apr 2, 2026
ca4c083
docs(specs): add implementation plan for prototype exploration mode
arielshad Apr 2, 2026
a276270
feat(domain): replace boolean fast field with feature-mode enum
arielshad Apr 2, 2026
59bc36f
feat(tsp): add exploring state to sdlc lifecycle enum
arielshad Apr 2, 2026
4bc7d4d
feat(tsp): add iteration count fields to feature entity
arielshad Apr 2, 2026
c8b9171
feat(domain): add exploring lifecycle transition gates
arielshad Apr 2, 2026
770a7c8
feat(domain): add migration replacing fast boolean with mode enum
arielshad Apr 2, 2026
cf41ba5
feat(agents): add exploration state channels to feature agent annotation
arielshad Apr 2, 2026
43188eb
feat(agents): add exploration prompt builders for prototype generation
arielshad Apr 2, 2026
13d0dcb
feat(agents): add prototype-generate and apply-feedback exploration n…
arielshad Apr 2, 2026
42007bb
feat(agents): add exploration agent graph factory with feedback loop
arielshad Apr 2, 2026
d6ffd74
feat(agents): add exploration graph routing to worker and lifecycle c…
arielshad Apr 2, 2026
2cd913a
feat(agents): extend create feature use case for exploration mode
arielshad Apr 2, 2026
fc99bbb
feat(agents): add exploration mode to spec initializer
arielshad Apr 2, 2026
cc9437c
feat(agents): add promote exploration use case for mode transition
arielshad Apr 2, 2026
8634d5d
feat(agents): extend delete use case with checkpoint cleanup
arielshad Apr 2, 2026
a72c91f
feat(cli): add --explore flag to feat new command
arielshad Apr 2, 2026
1eeb865
feat(cli): add feat feedback and feat promote commands
arielshad Apr 2, 2026
c445aa2
feat(web): replace fast mode toggle with three-way mode selector
arielshad Apr 2, 2026
3be1e5f
feat(web): add exploring lifecycle phase to canvas node configuration
arielshad Apr 2, 2026
48beac3
feat(web): add prototype review tab for exploration features
arielshad Apr 2, 2026
1a67286
feat(web): wire server actions for exploration feedback, promote, and…
arielshad Apr 2, 2026
838e81a
feat(config): replace defaultfastmode boolean with defaultmode enum i…
arielshad Apr 2, 2026
f9dc2a5
feat(config): add explorationmaxiterations setting for exploration fe…
arielshad Apr 2, 2026
e3c5996
chore(specs): capture evidence for prototype exploration mode impleme…
arielshad Apr 2, 2026
9b43db5
chore(specs): recapture evidence for prototype exploration mode imple…
arielshad Apr 2, 2026
87501f4
chore(specs): recapture evidence with fresh test runs and screenshots
arielshad Apr 2, 2026
ff3ea8a
chore(specs): mark evidence phase complete for prototype exploration …
arielshad Apr 2, 2026
f1f3cc3
chore(specs): update evidence phase timestamp for prototype explorati…
arielshad Apr 2, 2026
98e2b35
chore(specs): add rejection feedback to prototype exploration mode spec
arielshad Apr 2, 2026
6d3878a
chore(specs): update last updated timestamp for prototype exploration…
arielshad Apr 3, 2026
a5a637e
fix(domain): use correct default_mode column name in settings reposit…
arielshad Apr 3, 2026
05d1c91
fix(web): update e2e web test seed functions to use mode column inste…
arielshad Apr 3, 2026
08299b5
fix(web): make mode selector show clear visual selection state on click
arielshad Apr 3, 2026
814ddc2
chore(specs): add iteration 3 rejection feedback to exploration mode …
arielshad Apr 5, 2026
c98e248
Merge remote-tracking branch 'origin/main' into feat/prototype-explor…
arielshad Apr 5, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 17 additions & 5 deletions apis/json-schema/Feature.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -50,10 +50,10 @@ properties:
repositoryId:
$ref: UUID.yaml
description: ID of the Repository entity this feature belongs to
fast:
type: boolean
default: false
description: When true, SDLC phases were skipped and the feature was implemented directly from the prompt
mode:
$ref: FeatureMode.yaml
default: Regular
description: "Execution mode determining the workflow: Regular (full SDLC), Fast (direct implementation), or Exploration (iterative prototyping)"
push:
type: boolean
default: false
Expand Down Expand Up @@ -92,6 +92,17 @@ properties:
worktreePath:
type: string
description: Absolute path to the git worktree for this feature
iterationCount:
type: integer
minimum: -2147483648
maximum: 2147483647
default: 0
description: Current feedback iteration count in exploration mode (0 when not exploring)
maxIterations:
type: integer
minimum: -2147483648
maximum: 2147483647
description: Maximum allowed iterations for exploration mode (only set when mode is Exploration)
pr:
$ref: PullRequest.yaml
description: Pull request data (null until PR created)
Expand All @@ -116,7 +127,7 @@ required:
- lifecycle
- messages
- relatedArtifacts
- fast
- mode
- push
- openPr
- forkAndPr
Expand All @@ -126,6 +137,7 @@ required:
- injectSkills
- commitEvidence
- approvalGates
- iterationCount
allOf:
- $ref: SoftDeletableEntity.yaml
description: Central entity tracking a piece of work through the SDLC lifecycle (Aggregate Root)
8 changes: 8 additions & 0 deletions apis/json-schema/FeatureMode.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
$schema: https://json-schema.org/draft/2020-12/schema
$id: FeatureMode.yaml
type: string
enum:
- Regular
- Fast
- Exploration
description: Execution mode determining the feature's workflow and agent graph
1 change: 1 addition & 0 deletions apis/json-schema/SdlcLifecycle.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ enum:
- Maintain
- Blocked
- Pending
- Exploring
- Deleting
- AwaitingUpstream
- Archived
Expand Down
15 changes: 10 additions & 5 deletions apis/json-schema/WorkflowConfig.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -55,10 +55,15 @@ properties:
hideCiStatus:
type: boolean
description: "Hide CI status badges from UI (default: true)"
defaultFastMode:
type: boolean
default: true
description: "Default new features to fast mode (default: true)"
defaultMode:
type: string
default: Fast
description: "Default feature mode for new features: 'Regular', 'Fast', or 'Exploration' (default: 'Fast')"
explorationMaxIterations:
type: integer
minimum: -2147483648
maximum: 2147483647
description: "Maximum exploration feedback iterations (default: 10, 0 = unlimited)"
autoArchiveDelayMinutes:
type: integer
minimum: -2147483648
Expand All @@ -73,5 +78,5 @@ required:
- ciWatchEnabled
- enableEvidence
- commitEvidence
- defaultFastMode
- defaultMode
description: Global workflow configuration defaults
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
* - Infrastructure layer provides concrete implementation
*/

import type { ApprovalGates, AgentType } from '../../../../domain/generated/output.js';
import type { ApprovalGates, AgentType, FeatureMode } from '../../../../domain/generated/output.js';

/**
* Service interface for feature agent background process management.
Expand Down Expand Up @@ -45,7 +45,7 @@ export interface IFeatureAgentProcessService {
commitEvidence?: boolean;
resumePayload?: string;
agentType?: AgentType;
fast?: boolean;
mode?: FeatureMode;
model?: string;
resumeReason?: string;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,20 +24,21 @@ export interface ISpecInitializerService {
* - tasks.yaml
* - feature.yaml
*
* When mode is 'fast', only feature.yaml is created (no spec/research/plan/tasks).
* When mode is 'fast', only feature.yaml and spec.yaml are created.
* When mode is 'exploration', only feature.yaml is created.
*
* @param basePath - Directory to create specs/ in (typically the worktree path)
* @param slug - Feature slug (kebab-case, e.g., "user-authentication")
* @param featureNumber - Sequential feature number (will be zero-padded to 3 digits)
* @param description - Feature description for template substitution
* @param mode - Optional mode; when 'fast', only feature.yaml is created
* @param mode - Optional mode; controls which template files are created
* @returns The spec directory path and feature number used
*/
initialize(
basePath: string,
slug: string,
featureNumber: number,
description: string,
mode?: 'fast'
mode?: 'fast' | 'exploration'
): Promise<SpecInitializerResult>;
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import type { IAgentRunRepository } from '../../ports/output/agents/agent-run-re
import type { IFeatureAgentProcessService } from '../../ports/output/agents/feature-agent-process.interface.js';
import type { IPhaseTimingRepository } from '../../ports/output/agents/phase-timing-repository.interface.js';
import type { IFeatureRepository } from '../../ports/output/repositories/feature-repository.interface.js';
import { AgentRunStatus } from '../../../domain/generated/output.js';
import { AgentRunStatus, FeatureMode } from '../../../domain/generated/output.js';
import type { PrdApprovalPayload } from '../../../domain/generated/output.js';
import {
writeSpecFileAtomic,
Expand Down Expand Up @@ -139,7 +139,7 @@ export class ApproveAgentRunUseCase {
...(payload ? { resumePayload: JSON.stringify(payload) } : {}),
agentType: run.agentType,
...(run.modelId ? { model: run.modelId } : {}),
...(feature?.fast ? { fast: true } : {}),
...(feature?.mode && feature.mode !== FeatureMode.Regular ? { mode: feature.mode } : {}),
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import type { IAgentRunRepository } from '../../ports/output/agents/agent-run-re
import type { IFeatureAgentProcessService } from '../../ports/output/agents/feature-agent-process.interface.js';
import type { IPhaseTimingRepository } from '../../ports/output/agents/phase-timing-repository.interface.js';
import type { IFeatureRepository } from '../../ports/output/repositories/feature-repository.interface.js';
import { AgentRunStatus } from '../../../domain/generated/output.js';
import { AgentRunStatus, FeatureMode } from '../../../domain/generated/output.js';
import type {
PrdRejectionPayload,
RejectionFeedbackEntry,
Expand Down Expand Up @@ -167,7 +167,7 @@ export class RejectAgentRunUseCase {
resumePayload: JSON.stringify(rejectionPayload),
agentType: run.agentType,
...(run.modelId ? { model: run.modelId } : {}),
...(feature.fast ? { fast: true } : {}),
...(feature.mode !== FeatureMode.Regular ? { mode: feature.mode } : {}),
}
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import { injectable, inject } from 'tsyringe';
import { randomUUID } from 'node:crypto';
import type { Feature, PullRequest } from '../../../domain/generated/output.js';
import { SdlcLifecycle, PrStatus } from '../../../domain/generated/output.js';
import { SdlcLifecycle, PrStatus, FeatureMode } from '../../../domain/generated/output.js';
import type { IFeatureRepository } from '../../ports/output/repositories/feature-repository.interface.js';
import type { IRepositoryRepository } from '../../ports/output/repositories/repository-repository.interface.js';
import type { IWorktreeService } from '../../ports/output/services/worktree-service.interface.js';
Expand Down Expand Up @@ -114,7 +114,8 @@ export class AdoptBranchUseCase {
lifecycle,
messages: [],
relatedArtifacts: [],
fast: false,
mode: FeatureMode.Regular,
iterationCount: 0,
push: false,
openPr: hasOpenPr,
forkAndPr: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
*/

import { injectable, inject } from 'tsyringe';
import { SdlcLifecycle } from '../../../domain/generated/output.js';
import { SdlcLifecycle, FeatureMode } from '../../../domain/generated/output.js';
import type { IFeatureRepository } from '../../ports/output/repositories/feature-repository.interface.js';
import type { IFeatureAgentProcessService } from '../../ports/output/agents/feature-agent-process.interface.js';
import { POST_IMPLEMENTATION } from '../../../domain/lifecycle-gates.js';
Expand Down Expand Up @@ -72,7 +72,7 @@ export class CheckAndUnblockFeaturesUseCase {
ciWatchEnabled: child.ciWatchEnabled,
enableEvidence: child.enableEvidence,
commitEvidence: child.commitEvidence,
...(child.fast ? { fast: true } : {}),
...(child.mode !== FeatureMode.Regular ? { mode: child.mode } : {}),
}
);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import type { Feature } from '../../../../domain/generated/output.js';
import {
SdlcLifecycle,
AgentRunStatus,
FeatureMode,
type AgentType,
} from '../../../../domain/generated/output.js';
import type { IFeatureRepository } from '../../../ports/output/repositories/feature-repository.interface.js';
Expand Down Expand Up @@ -86,9 +87,13 @@ export class CreateFeatureUseCase {
* No AI calls, no git operations — just DB writes.
*/
async createRecord(input: CreateFeatureInput): Promise<CreateRecordResult> {
let initialLifecycle: SdlcLifecycle = input.fast
? SdlcLifecycle.Implementation
: SdlcLifecycle.Requirements;
const effectiveMode = input.mode ?? FeatureMode.Regular;
let initialLifecycle: SdlcLifecycle =
effectiveMode === FeatureMode.Exploration
? SdlcLifecycle.Exploring
: effectiveMode === FeatureMode.Fast
? SdlcLifecycle.Implementation
: SdlcLifecycle.Requirements;
let shouldSpawn = true;
let effectiveRepoPath = input.repositoryPath.replace(/\\/g, '/');

Expand Down Expand Up @@ -173,7 +178,7 @@ export class CreateFeatureUseCase {
lifecycle: initialLifecycle,
messages: [],
relatedArtifacts: [],
fast: input.fast ?? false,
mode: effectiveMode,
push: input.push ?? false,
openPr: input.openPr ?? false,
forkAndPr: input.forkAndPr ?? false,
Expand All @@ -187,6 +192,10 @@ export class CreateFeatureUseCase {
allowPlan: false,
allowMerge: false,
},
iterationCount: 0,
...(effectiveMode === FeatureMode.Exploration && {
maxIterations: getSettings().workflow.explorationMaxIterations ?? 10,
}),
agentRunId: runId,
specPath: '',
repositoryId: repository.id,
Expand Down Expand Up @@ -278,7 +287,11 @@ export class CreateFeatureUseCase {
slug,
featureNumber,
input.userInput,
input.fast ? 'fast' : undefined
feature.mode === FeatureMode.Fast
? 'fast'
: feature.mode === FeatureMode.Exploration
? 'exploration'
: undefined
);

// Commit pending attachments if sessionId was provided (web UI flow)
Expand Down Expand Up @@ -387,7 +400,7 @@ export class CreateFeatureUseCase {
ciWatchEnabled: input.ciWatchEnabled ?? true,
enableEvidence: input.enableEvidence ?? false,
commitEvidence: input.commitEvidence ?? false,
...(input.fast ? { fast: true } : {}),
...(feature.mode !== FeatureMode.Regular ? { mode: feature.mode } : {}),
...(input.agentType ? { agentType: input.agentType as AgentType } : {}),
...(input.model ? { model: input.model } : {}),
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import type { ApprovalGates, Attachment, Feature } from '../../../../domain/generated/output.js';
import { type FeatureMode } from '../../../../domain/generated/output.js';

export interface CreateFeatureInput {
userInput: string;
Expand All @@ -12,8 +13,8 @@ export interface CreateFeatureInput {
name?: string;
/** Pre-supplied description (skips AI metadata extraction for description). */
description?: string;
/** When true, skip SDLC phases and implement directly from the user prompt. */
fast?: boolean;
/** Execution mode: Regular (full SDLC), Fast (direct implementation), or Exploration (iterative prototyping). */
mode?: FeatureMode;
/** Fork repo and create PR to upstream at merge time (default: false). */
forkAndPr?: boolean;
/** Commit specs/evidences into the repo (default: true, auto-false when forkAndPr). */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@
*/

import { injectable, inject } from 'tsyringe';
import { homedir } from 'node:os';
import { join } from 'node:path';
import { unlink } from 'node:fs/promises';
import type { Feature } from '../../../domain/generated/output.js';
import { AgentRunStatus, PrStatus, SdlcLifecycle } from '../../../domain/generated/output.js';
import type { IFeatureRepository } from '../../ports/output/repositories/feature-repository.interface.js';
Expand Down Expand Up @@ -137,6 +140,16 @@ export class DeleteFeatureUseCase {
}
await this.runRepo.updateStatus(run.id, AgentRunStatus.cancelled);
}

// Clean up checkpoint database file (used by LangGraph for state persistence)
if (run?.threadId) {
const checkpointPath = join(homedir(), '.shep', 'checkpoints', `${run.threadId}.db`);
try {
await unlink(checkpointPath);
} catch {
// Checkpoint file may not exist or already be removed
}
}
}

// Cleanup worktree and branches directly using the feature data we already
Expand Down
Loading
Loading