Merge remote-tracking branch 'origin/main' into feat/pi-harness-migration

# Conflicts:
#	CLAUDE.md
#	README.md
#	apps/cli/src/commands/start.ts
#	apps/cli/src/commands/uninstall.ts
#	apps/worker/src/services/agent-execution.ts
#	apps/worker/src/services/preflight.ts
#	apps/worker/src/session-manager.ts
#	apps/worker/src/temporal/activities.ts
#	apps/worker/src/temporal/shared.ts
#	apps/worker/src/temporal/workflows.ts
#	docs/ai-providers.md
#	llms-full.txt
This commit is contained in:
ezl-keygraph
2026-07-16 18:58:18 +05:30
22 changed files with 246 additions and 288 deletions
+7 -40
View File
@@ -12,7 +12,7 @@
* time and API costs compared to failing mid-pipeline.
*
* Checks run sequentially, cheapest first:
* 1. Repository path exists and contains .git
* 1. Repository path exists and is a directory
* 2. Config file parses and validates (if provided)
* 3. code_path rules match real entries in the repo (filesystem only)
* 4. Credentials validate via a minimal pi session (API key, OAuth, or Bedrock)
@@ -85,14 +85,12 @@ function pinnedLookup(addresses: LookupAddress[]): LookupFunction {
// === Repository Validation ===
async function validateRepo(
repoPath: string,
logger: ActivityLogger,
skipGitCheck?: boolean,
): Promise<Result<void, PentestError>> {
async function validateRepo(repoPath: string, logger: ActivityLogger): Promise<Result<void, PentestError>> {
logger.info('Checking repository path...', { repoPath });
// 1. Check repo directory exists
// Check repo directory exists. The repo is not required to be a git repository:
// multi-repo targets (a parent directory containing several repos) have no top-level
// .git, and git-based checkpoint/rollback in git-manager already no-ops on non-git dirs.
try {
const stats = await fs.stat(repoPath);
if (!stats.isDirectory()) {
@@ -118,36 +116,6 @@ async function validateRepo(
);
}
// 2. Check .git directory exists (skipped when consumer removes .git after clone)
if (!skipGitCheck) {
try {
const gitStats = await fs.stat(`${repoPath}/.git`);
if (!gitStats.isDirectory()) {
return err(
new PentestError(
`Not a git repository (no .git directory): ${repoPath}`,
'config',
false,
{ repoPath },
ErrorCode.REPO_NOT_FOUND,
),
);
}
} catch {
return err(
new PentestError(
`Not a git repository (no .git directory): ${repoPath}`,
'config',
false,
{ repoPath },
ErrorCode.REPO_NOT_FOUND,
),
);
}
} else {
logger.info('Skipping .git check (skipGitCheck enabled)');
}
logger.info('Repository path OK');
return ok(undefined);
}
@@ -540,7 +508,7 @@ async function validateTargetUrl(targetUrl: string, logger: ActivityLogger): Pro
/**
* Run all preflight checks sequentially (cheapest first).
*
* 1. Repository path exists and contains .git
* 1. Repository path exists and is a directory
* 2. Config file parses and validates (if configPath provided)
* 3. code_path rules match at least one entry in the repo (skipped without config)
* 4. Credentials validate (API key, OAuth, or Bedrock)
@@ -553,10 +521,9 @@ export async function runPreflightChecks(
repoPath: string,
configPath: string | undefined,
logger: ActivityLogger,
skipGitCheck?: boolean,
): Promise<Result<void, PentestError>> {
// 1. Repository check (free — filesystem only)
const repoResult = await validateRepo(repoPath, logger, skipGitCheck);
const repoResult = await validateRepo(repoPath, logger);
if (!repoResult.ok) {
return repoResult;
}
+2 -3
View File
@@ -76,7 +76,6 @@ export interface ActivityInput {
auditDir?: string;
promptDir?: string;
sastSarifPath?: string;
skipGitCheck?: boolean;
}
/**
@@ -441,7 +440,7 @@ export async function runReportAgent(input: ActivityInput): Promise<AgentMetrics
* Preflight validation activity.
*
* Runs cheap checks before any agent execution:
* 1. Repository path exists with .git
* 1. Repository path exists and is a directory
* 2. Config file validates (if provided)
* 3. Credential validation (API key, OAuth, or Bedrock)
* 4. Target URL reachable from the container
@@ -461,7 +460,7 @@ export async function runPreflightValidation(input: ActivityInput): Promise<void
const logger = createActivityLogger();
logger.info('Running preflight validation...', { attempt: attemptNumber });
const result = await runPreflightChecks(input.webUrl, input.repoPath, input.configPath, logger, input.skipGitCheck);
const result = await runPreflightChecks(input.webUrl, input.repoPath, input.configPath, logger);
if (isErr(result)) {
const classified = classifyErrorForTemporal(result.error);
-1
View File
@@ -26,7 +26,6 @@ export interface PipelineInput {
promptDir?: string; // Override prompt template directory
sastSarifPath?: string; // Optional path for consumer-supplied findings input
checkpointsEnabled?: boolean; // Enable checkpoint activities (default: false)
skipGitCheck?: boolean; // Skip .git directory validation in preflight (e.g. when .git is removed after clone)
vulnClasses?: VulnClass[]; // omitted = all five
exploit?: boolean; // false skips the exploitation phase
}
-1
View File
@@ -253,7 +253,6 @@ export async function pentestPipeline(input: PipelineInput): Promise<PipelineSta
...(input.auditDir !== undefined && { auditDir: input.auditDir }),
...(input.promptDir !== undefined && { promptDir: input.promptDir }),
...(input.sastSarifPath !== undefined && { sastSarifPath: input.sastSarifPath }),
...(input.skipGitCheck !== undefined && { skipGitCheck: input.skipGitCheck }),
};
const selectedVulnClasses: readonly VulnClass[] =