Skip to content
Merged
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
25 changes: 18 additions & 7 deletions docs/progress.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,13 +72,24 @@ root zone). Per-zone `checks` are the enabled catalog ids the zone owns (`clippy
(the only non-JS check in v2); go zones are recorded with no checks yet. The
generated polyglot config validates against the v2 schema (asserted in tests).

### Still deferred: `pnpm test:e2e` (out of this scope, spec task TV4.3)

`pnpm test:e2e` (`packages/core/test/e2e/full-flow.test.ts`) runs the built CLI
against `examples/demo-project`, whose `sentiness.config.json` is still v1 and
whose checks resolve from `node_modules`. Migrating the demo config + the harness
to the v2 cache/`install` model is a larger rewrite that belongs to the V4 E2E
plan; left as-is here to keep this change focused on `init`.
### E2E migrated to v2 (TV4.3) — done (2026-06-16)

`pnpm test:e2e` (`packages/core/test/e2e/full-flow.test.ts`) and
`examples/demo-project` were migrated to the v2 model. The demo config is now
`schemaVersion: '2.0'` with a **path-linked** biome check; the harness writes v2
configs (`buildV2Config`) that path-link each check to its built package under
`packages/checks/*` via a project-relative path, instead of symlinking into
`node_modules/@sentiness`. This needs **no cache, no `sentiness install`, and no
project node_modules**: the engine resolves path-linked checks directly from the
repo, and each check's external tool (e.g. biome) still resolves from the
inherited PATH (`cliEnv` prepends the repo `node_modules/.bin`). The non-
interactive `init` E2E assertion was updated to the v2 output
(`{ biome: { version: '*', tier: 'fast' } }`).

This unblocked `main` CI, which regressed to red after v2 landed because the v1
demo config was rejected by the v2 loader. All 14 E2E cases pass, alongside 209
unit tests, typecheck, lint, `check:release-packages` (15 packages), and the
dogfood `check --tier=fast`.

## Implementation approach

Expand Down
8 changes: 3 additions & 5 deletions examples/demo-project/sentiness.config.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
{
"schemaVersion": "1.0",
"schemaVersion": "2.0",
"engine": "0.1.4",
"checks": {
"biome": {
"enabled": true,
"tier": "fast"
}
"biome": { "path": "../../packages/checks/biome", "tier": "fast" }
}
}
78 changes: 39 additions & 39 deletions packages/core/test/e2e/full-flow.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { execFile, spawn } from 'node:child_process';
import { existsSync } from 'node:fs';
import { cp, mkdir, mkdtemp, readFile, rm, symlink, writeFile } from 'node:fs/promises';
import { cp, mkdir, mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { dirname, join, resolve } from 'node:path';
import { dirname, join, relative, resolve } from 'node:path';
import process from 'node:process';
import { fileURLToPath } from 'node:url';
import { promisify } from 'node:util';
Expand Down Expand Up @@ -141,21 +141,45 @@ function parseJson(stdout: string): unknown {
return JSON.parse(stdout.trim());
}

// v2 config: each check is path-linked directly to the repo's built check
// package (relative to the project dir), so the engine resolves it without a
// cache or any project node_modules. The check's external tool (e.g. biome)
// still resolves from the inherited PATH via `cliEnv` (rootBinPath).
function buildV2Config(
projectDir: string,
checks: readonly CheckPackageId[],
perCheck: Partial<Record<CheckPackageId, Record<string, unknown>>> = {},
): Record<string, unknown> {
return {
schemaVersion: '2.0',
engine: '0.1.4',
checks: Object.fromEntries(
checks.map((id) => [
id,
{ path: relative(projectDir, checkPackages[id]), ...(perCheck[id] ?? {}) },
]),
),
};
}

async function writeConfig(projectDir: string, config: Record<string, unknown>): Promise<void> {
await writeFile(
join(projectDir, 'sentiness.config.json'),
`${JSON.stringify(config, null, 2)}\n`,
);
}

async function createDemoCopy(
source: string,
checks: readonly CheckPackageId[] = ['biome'],
perCheck: Partial<Record<CheckPackageId, Record<string, unknown>>> = {},
): Promise<string> {
const tempRoot = await mkdtemp(join(tmpdir(), 'sentiness-e2e-'));
cleanupPaths.push(tempRoot);
const projectDir = join(tempRoot, 'demo-project');
await cp(demoProject, projectDir, { recursive: true });
await writeFile(join(projectDir, 'src/index.ts'), source);

const sentinessScope = join(projectDir, 'node_modules/@sentiness');
await mkdir(sentinessScope, { recursive: true });
for (const check of checks) {
await symlink(checkPackages[check], join(sentinessScope, `check-${check}`), 'dir');
}
await writeConfig(projectDir, buildV2Config(projectDir, checks, perCheck));

return projectDir;
}
Expand Down Expand Up @@ -251,17 +275,6 @@ describe('Sentiness CLI E2E full flow', () => {

it('flags a missing tool config in doctor and writes it through init-config', async () => {
const projectDir = await createDemoCopy('export const value = 1;\n', ['dependency-cruiser']);
await writeFile(
join(projectDir, 'sentiness.config.json'),
`${JSON.stringify(
{
schemaVersion: '1.0',
checks: { 'dependency-cruiser': { enabled: true } },
},
null,
2,
)}\n`,
);

const before = await runCli(projectDir, ['doctor']);
const beforeDoctor = DoctorResultSchema.parse(parseJson(before.stdout));
Expand Down Expand Up @@ -458,25 +471,10 @@ describe('Sentiness CLI E2E full flow', () => {
});

it('ratchets metric baselines through baseline update', async () => {
const projectDir = await createDemoCopy('export const value = 1;\nexport const other = 2;\n', [
'coverage',
]);
await writeFile(
join(projectDir, 'sentiness.config.json'),
`${JSON.stringify(
{
schemaVersion: '1.0',
checks: {
coverage: {
enabled: true,
tier: 'slow',
thresholds: { lineCoverage: 0 },
},
},
},
null,
2,
)}\n`,
const projectDir = await createDemoCopy(
'export const value = 1;\nexport const other = 2;\n',
['coverage'],
{ coverage: { tier: 'slow', thresholds: { lineCoverage: 0 } } },
);
await writeCoverageReport(projectDir, [1, 0]);
await initGitRepo(projectDir);
Expand Down Expand Up @@ -565,7 +563,9 @@ describe('Sentiness CLI E2E full flow', () => {
const gitignore = await readFile(join(projectDir, '.gitignore'), 'utf8');

expect(result.exitCode).toBe(0);
expect(config.checks).toEqual({ biome: { enabled: true, tier: 'fast' } });
expect(config.schemaVersion).toBe('2.0');
expect(typeof config.engine).toBe('string');
expect(config.checks).toEqual({ biome: { version: '*', tier: 'fast' } });
expect(config.reporting.omitOk).toBe(true);
expect(gitignore).toContain('.sentiness/jobs/');
expect(gitignore).toContain('.sentiness/pending-feedback.json');
Expand Down