mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-11 21:57:14 +03:00
Sync generated provider output
This commit is contained in:
@@ -27,6 +27,7 @@ import {
|
||||
readCache,
|
||||
readConfig,
|
||||
renderTemplate,
|
||||
resolveCacheCwd,
|
||||
resolveProjectCwd,
|
||||
truthy,
|
||||
writeAuditLog,
|
||||
@@ -379,9 +380,12 @@ async function main() {
|
||||
return allow({ skipped: 'stdin-empty' });
|
||||
}
|
||||
|
||||
const cwd = resolveProjectCwd(event);
|
||||
const sessionCwd = resolveProjectCwd(event);
|
||||
const started = Date.now();
|
||||
const filePath = proposedFilePath(event, cwd);
|
||||
const filePath = proposedFilePath(event, sessionCwd);
|
||||
// Re-key config/cache to the edited file's project root when the session
|
||||
// was launched from a non-project umbrella directory (issue #305).
|
||||
const cwd = resolveCacheCwd(filePath, sessionCwd);
|
||||
const audit = {
|
||||
harness: 'cursor',
|
||||
cwd,
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* truthy(value)
|
||||
* readConfig(cwd) / DEFAULT_CONFIG / getConfigPath(cwd) / getLocalConfigPath(cwd)
|
||||
* normalizeIgnoreValue(value)
|
||||
* readCache(cwd) / persistCache(cwd, cache)
|
||||
* readCache(cwd) / persistCache(cwd, cache) / resolveCacheCwd(primaryFile, sessionCwd)
|
||||
* bumpEditCount(cache, sessionId, filePath) -> number
|
||||
* suppressionNotice(filePath)
|
||||
* filterFindings(findings, content, ext, config)
|
||||
@@ -35,6 +35,7 @@
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { pathToFileURL, fileURLToPath } from 'node:url';
|
||||
|
||||
@@ -134,6 +135,39 @@ export function resolveProjectCwd(event, fallback = process.cwd()) {
|
||||
|| fallback;
|
||||
}
|
||||
|
||||
function looksLikeProjectRoot(dir) {
|
||||
return ['.git', 'package.json', '.impeccable'].some((marker) => {
|
||||
try { return fs.existsSync(path.join(dir, marker)); } catch { return false; }
|
||||
});
|
||||
}
|
||||
|
||||
// Where `.impeccable/` (cache + config) lives for this event. Normally the
|
||||
// session cwd, untouched. But when the agent was launched from an umbrella
|
||||
// directory that is not itself a project (no .git, package.json, or
|
||||
// .impeccable), key to the edited file's nearest project root instead, so a
|
||||
// multi-project launch dir doesn't accumulate a shared cross-project cache
|
||||
// (issue #305). Climbing stops at the home dir, falling back to the session
|
||||
// cwd when no marker is found.
|
||||
export function resolveCacheCwd(primaryFile, sessionCwd) {
|
||||
const base = path.resolve(sessionCwd || process.cwd());
|
||||
if (!primaryFile || typeof primaryFile !== 'string' || hasPathTraversal(primaryFile)) return base;
|
||||
if (looksLikeProjectRoot(base)) return base;
|
||||
let dir;
|
||||
try {
|
||||
dir = path.dirname(path.resolve(primaryFile));
|
||||
} catch {
|
||||
return base;
|
||||
}
|
||||
const home = path.resolve(os.homedir());
|
||||
while (true) {
|
||||
if (dir === home) return base;
|
||||
if (looksLikeProjectRoot(dir)) return dir;
|
||||
const parent = path.dirname(dir);
|
||||
if (parent === dir) return base;
|
||||
dir = parent;
|
||||
}
|
||||
}
|
||||
|
||||
export function readConfig(cwd) {
|
||||
const config = cloneDefaultConfig();
|
||||
// Hook runtime settings live under `hook`; detector filters live under
|
||||
@@ -1402,9 +1436,10 @@ export async function runHook({ stdinJson, env = {}, cwd = process.cwd(), now =
|
||||
event = normalizeHookEvent(event, cwd, harness);
|
||||
audit.harness = harness;
|
||||
|
||||
const projectCwd = event.cwd || cwd;
|
||||
const sessionCwd = event.cwd || cwd;
|
||||
const primaryFiles = normalizeScanTargets(resolveTargetFiles(event, sessionCwd), sessionCwd);
|
||||
const projectCwd = resolveCacheCwd(primaryFiles[0], sessionCwd);
|
||||
audit.cwd = projectCwd;
|
||||
const primaryFiles = normalizeScanTargets(resolveTargetFiles(event, projectCwd), projectCwd);
|
||||
const primaryFileSet = new Set(primaryFiles);
|
||||
const targetFiles = expandScanTargets(primaryFiles, projectCwd);
|
||||
audit.session = event.session_id || null;
|
||||
@@ -1423,7 +1458,7 @@ export async function runHook({ stdinJson, env = {}, cwd = process.cwd(), now =
|
||||
const sessionId = event.session_id || 'unknown';
|
||||
const det = detector || await loadDetector();
|
||||
if (!det || typeof det.detectText !== 'function') {
|
||||
persistCache(projectCwd, cache);
|
||||
// Cache is not mutated yet at this point; nothing to persist.
|
||||
return result({ skipped: 'detector-missing', durationMs: Date.now() - started });
|
||||
}
|
||||
const scanOptions = designSystemOptions(config, det, projectCwd);
|
||||
@@ -1435,6 +1470,7 @@ export async function runHook({ stdinJson, env = {}, cwd = process.cwd(), now =
|
||||
let detectorThrewAny = false;
|
||||
let lastSkip = 'no-scannable-file';
|
||||
let suppressedHit = false;
|
||||
let cacheDirty = false;
|
||||
|
||||
for (const filePath of targetFiles) {
|
||||
audit.file = filePath;
|
||||
@@ -1467,6 +1503,7 @@ export async function runHook({ stdinJson, env = {}, cwd = process.cwd(), now =
|
||||
|
||||
if (primaryFileSet.has(filePath)) {
|
||||
const editCount = bumpEditCount(cache, sessionId, filePath);
|
||||
cacheDirty = true;
|
||||
audit.editCount = editCount;
|
||||
|
||||
if (editCount > EDIT_COUNT_THRESHOLD) {
|
||||
@@ -1496,6 +1533,7 @@ export async function runHook({ stdinJson, env = {}, cwd = process.cwd(), now =
|
||||
|
||||
if (fresh.length > 0) {
|
||||
rememberFindings(cache, sessionId, filePath, fresh);
|
||||
cacheDirty = true;
|
||||
freshGroups.push({ filePath, findings: fresh });
|
||||
continue;
|
||||
}
|
||||
@@ -1513,7 +1551,15 @@ export async function runHook({ stdinJson, env = {}, cwd = process.cwd(), now =
|
||||
}
|
||||
}
|
||||
|
||||
persistCache(projectCwd, cache);
|
||||
// Persist only when the write is earned: fresh findings justify creating
|
||||
// `.impeccable/` (dedup and suppression need it), and an already-present
|
||||
// `.impeccable/` dir marks a project that opted in. A non-UI edit, or a
|
||||
// clean UI edit in a project with no Impeccable footprint, must be a
|
||||
// no-op on disk (issues #344, #305).
|
||||
if (freshGroups.length > 0
|
||||
|| (cacheDirty && fs.existsSync(path.join(projectCwd, '.impeccable')))) {
|
||||
persistCache(projectCwd, cache);
|
||||
}
|
||||
|
||||
if (freshGroups.length > 0) {
|
||||
const firstGroup = freshGroups[0];
|
||||
|
||||
Reference in New Issue
Block a user