Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 6 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ on:
push:
branches: [main]
pull_request:
branches: [main]
branches: [main, mobile-provider-parity-1d93-s*]

# Cancel in-progress runs when a new commit is pushed to the same PR
concurrency:
Expand Down Expand Up @@ -51,6 +51,11 @@ jobs:
with:
filters: |
kilocode_backend:
- '.github/workflows/ci.yml'
- '.github/workflows/kilo-app-ci.yml'
- 'apps/mobile/src/components/pr-review/**'
- 'apps/mobile/src/lib/pr-review/**'
- 'apps/mobile/src/app/\(app\)/provider-review/**'
- 'apps/web/src/**'
- 'apps/web/.env'
- 'apps/web/.env.test'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/kilo-app-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ on:
- 'pnpm-lock.yaml'
- '.github/workflows/kilo-app-ci.yml'
pull_request:
branches: [main]
branches: [main, mobile-provider-parity-1d93-s*]
paths:
- 'apps/mobile/**'
- 'packages/trpc/**'
Expand Down
4 changes: 4 additions & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@
"@chat-adapter/state-redis": "4.36.0",
"@emoji-mart/data": "1.2.1",
"@emoji-mart/react": "1.1.1",
"@gitbeaker/requester-utils": "43.8.0",
"@gitbeaker/rest": "43.8.0",
"@kilocode/app-shared": "workspace:*",
"@kilocode/auto-routing-contracts": "workspace:*",
"@kilocode/cloud-agent-profile": "workspace:*",
Expand Down Expand Up @@ -190,6 +192,7 @@
"@types/linkify-it": "5.0.0",
"@types/node": "catalog:",
"@types/pg": "8.18.0",
"@types/picomatch": "2.3.4",
"@types/react": "19.2.14",
"@types/react-dom": "19.2.3",
"@typescript/native-preview": "catalog:",
Expand All @@ -201,6 +204,7 @@
"jest": "30.3.0",
"knip": "5.86.0",
"madge": "8.0.0",
"picomatch": "2.3.2",
"postcss": "8.5.23",
"tailwindcss": "4.2.4",
"ts-jest": "29.4.9",
Expand Down
8 changes: 3 additions & 5 deletions apps/web/src/lib/integrations/core/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,9 @@ export function requireNumericPlatformRepositories(
return repositories;
}

/**
* Represents ownership of an integration
* Can be either a user or an organization
*/
export type Owner = { type: 'user'; id: string } | { type: 'org'; id: string };
// Compatibility: retain the old web import path until all Owner imports migrate.
// Database helpers and exports stay here; the shared Owner has no database dependency.
export type { Owner } from '@kilocode/app-shared/code-review/repository-identity';

export type WebhookEvent = {
platform: string;
Expand Down
131 changes: 131 additions & 0 deletions apps/web/src/lib/stack-ci-triggers.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { readFileSync } from 'node:fs';
import { resolve } from 'node:path';
import { load } from 'js-yaml';
import picomatch from 'picomatch';

// Match the pinned dorny/paths-filter action's picomatch 2.x behavior, including dotfiles.
type Step = { name?: string; id?: string; run?: string; with?: { filters?: string } };
type Workflow = {
on: {
push: { branches: string[] };
pull_request: { branches: string[]; paths?: string[] };
pull_request_target?: unknown;
};
jobs: Record<
string,
{ if?: string; needs?: string[]; steps: Step[]; outputs?: Record<string, string> }
>;
};
const workflowFiles = ['ci.yml', 'kilo-app-ci.yml'];
const stackPattern = 'mobile-provider-parity-1d93-s*';
const stackBranch = 'mobile-provider-parity-1d93-s2';
const routePattern = String.raw`apps/mobile/src/app/\(app\)/provider-review/**`;
const routes = [
'apps/mobile/src/app/(app)/provider-review/_layout.tsx',
'apps/mobile/src/app/(app)/provider-review/[identity]/(surface)/index.tsx',
];
const source = (file: string) =>
readFileSync(resolve(__dirname, '../../../../.github/workflows', file), 'utf8');
const parse = (text: string) => load(text) as Workflow;
const ciSource = source('ci.yml');
const ci = parse(ciSource);
const matches = (patterns: string[], value: string) => picomatch(patterns, { dot: true })(value);

function matchesPullRequest(workflow: Workflow, branch: string, file: string) {
const trigger = workflow.on.pull_request;
return matches(trigger.branches, branch) && (!trigger.paths || matches(trigger.paths, file));
}

function backendPatterns(workflow: Workflow) {
const filters = workflow.jobs.changes.steps.find(step => step.id === 'filter')?.with?.filters;
if (typeof filters !== 'string') throw new Error('Missing paths-filter configuration');
return (load(filters) as { kilocode_backend: string[] }).kilocode_backend;
}

function selectsGuard(workflow: Workflow, branch: string, file: string) {
return matchesPullRequest(workflow, branch, file) && matches(backendPatterns(workflow), file);
}

function assertTargets(workflow: Workflow) {
for (const branch of [
'main',
...Array.from({ length: 21 }, (_, index) => `mobile-provider-parity-1d93-s${index + 1}`),
]) {
expect(matchesPullRequest(workflow, branch, routes[0])).toBe(true);
}
}

// Each row is a separate changed-path fixture. Server roots share the existing web pattern.
const monitoredPaths = [
['.github/workflows/ci.yml', '.github/workflows/ci.yml'],
['.github/workflows/kilo-app-ci.yml', '.github/workflows/kilo-app-ci.yml'],
[
'apps/mobile/src/components/pr-review/new-consumer.tsx',
'apps/mobile/src/components/pr-review/**',
],
['apps/mobile/src/lib/pr-review/nested/new-consumer.ts', 'apps/mobile/src/lib/pr-review/**'],
...routes.map(path => [path, routePattern]),
['apps/web/src/lib/provider-review/new-consumer.ts', 'apps/web/src/**'],
['apps/web/src/routers/provider-review-router.ts', 'apps/web/src/**'],
['apps/web/src/lib/provider-review-boundary.test.ts', 'apps/web/src/**'],
['apps/web/src/lib/stack-ci-triggers.test.ts', 'apps/web/src/**'],
];

describe('standard stack CI guard execution', () => {
it.each(workflowFiles)(
'%s retains main and all stack targets without privileged triggers',
file => {
const workflow = parse(source(file));
assertTargets(workflow);
expect(workflow.on.push.branches).toEqual(['main']);
expect(workflow.on).not.toHaveProperty('pull_request_target');
}
);

it.each(monitoredPaths)('%s selects the guard and requires %s', (file, pattern) => {
for (const branch of ['main', stackBranch]) {
expect(selectsGuard(ci, branch, file)).toBe(true);
const removed = ciSource.replace(`- '${pattern}'`, '');
expect(removed).not.toBe(ciSource);
expect(selectsGuard(parse(removed), branch, file)).toBe(false);
}
});

it.each(workflowFiles)('a workflow-only removal of the %s stack target fails the guard', file => {
expect(selectsGuard(ci, stackBranch, `.github/workflows/${file}`)).toBe(true);
const removed = parse(source(file).replace(stackPattern, 'main'));
expect(() => assertTargets(removed)).toThrow();
});

it('preserves literal Expo parentheses through both YAML parses', () => {
expect(
selectsGuard(ci, stackBranch, 'apps/mobile/src/app/app/provider-review/_layout.tsx')
).toBe(false);
const unescaped = parse(
ciSource.replace(routePattern, 'apps/mobile/src/app/(app)/provider-review/**')
);
for (const file of routes) expect(selectsGuard(unescaped, stackBranch, file)).toBe(false);
});

it('keeps the existing prerequisites and root test command on the backend filter', () => {
expect(ci.jobs.changes.outputs?.kilocode_backend).toBe(
'${{ steps.filter.outputs.kilocode_backend }}'
);
expect(ci.jobs.test.if).toBe("needs.changes.outputs.kilocode_backend == 'true'");
expect(ci.jobs.test.needs).toEqual([
'changes',
'typecheck',
'lint',
'format-check',
'drizzle-check',
]);
for (const prerequisite of ['typecheck', 'lint', 'format-check', 'drizzle-check']) {
expect(ci.jobs[prerequisite]).toBeDefined();
expect(ci.jobs[prerequisite].if).toBeUndefined();
}
expect(ci.jobs.test.steps.find(step => step.name === 'Run tests')).toEqual({
name: 'Run tests',
run: 'pnpm run test',
});
});
});
3 changes: 3 additions & 0 deletions packages/app-shared/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,9 @@
"./utils": "./src/utils.ts",
"./security-agent": "./src/security-agent/index.ts",
"./code-review": "./src/code-review/index.ts",
"./code-review/repository-identity": "./src/code-review/repository-identity.ts",
"./provider-review": "./src/provider-review/index.ts",
"./provider-review/*": "./src/provider-review/*.ts",
"./code-reviews": "./src/code-reviews/index.ts",
"./organizations": "./src/organizations/index.ts",
"./platforms": "./src/platforms.ts",
Expand Down
Loading