@prodcycle/prodcycle 0.6.8 → 0.6.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli.js +41 -4
- package/dist/utils/fs.js +7 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -558,14 +558,51 @@ async function collectHookFiles(filePath) {
|
|
|
558
558
|
const candidate = payload?.tool_input ?? payload;
|
|
559
559
|
const hookFilePath = candidate?.file_path ?? candidate?.path;
|
|
560
560
|
const hookContent = candidate?.content ?? candidate?.new_string;
|
|
561
|
-
|
|
562
|
-
|
|
561
|
+
// Coding agents (Claude Code's PostToolUse in particular) pass the file
|
|
562
|
+
// path as an absolute path. The compliance API rejects absolute paths
|
|
563
|
+
// (`File path must be relative`), so we relativise here against cwd's
|
|
564
|
+
// realpath — same code path as `--file <path>`, just driven by the stdin
|
|
565
|
+
// payload. Without this fix every PostToolUse hook call from Claude Code
|
|
566
|
+
// failed with "File path must be relative".
|
|
567
|
+
let hookFileKey = hookFilePath;
|
|
568
|
+
if (hookFilePath && path.isAbsolute(hookFilePath)) {
|
|
569
|
+
try {
|
|
570
|
+
const realpathFile = fs.realpathSync(path.resolve(hookFilePath));
|
|
571
|
+
const realpathCwd = fs.realpathSync(process.cwd());
|
|
572
|
+
const resolved = resolveHookFileKey(hookFilePath, realpathFile, realpathCwd);
|
|
573
|
+
if (!resolved.ok) {
|
|
574
|
+
// The helper's message references `--file`; rewrite for the stdin call site.
|
|
575
|
+
console.error(resolved.error.replace('hook: --file ', 'hook: file_path '));
|
|
576
|
+
process.exit(2);
|
|
577
|
+
}
|
|
578
|
+
hookFileKey = resolved.key;
|
|
579
|
+
}
|
|
580
|
+
catch (err) {
|
|
581
|
+
// File may not exist on disk yet (e.g. a Write event mid-creation) —
|
|
582
|
+
// ONLY ENOENT triggers the lexical fallback. Re-throw permission /
|
|
583
|
+
// symlink-loop / other fs errors so a real problem isn't silently
|
|
584
|
+
// converted into a degraded lexical-only key.
|
|
585
|
+
const code = err?.code;
|
|
586
|
+
if (code !== 'ENOENT') {
|
|
587
|
+
throw err;
|
|
588
|
+
}
|
|
589
|
+
const rel = path.relative(process.cwd(), hookFilePath);
|
|
590
|
+
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
591
|
+
console.error(`hook: file_path ${hookFilePath} is outside the current directory ` +
|
|
592
|
+
`(${process.cwd()}). Pass a path relative to the repo root.`);
|
|
593
|
+
process.exit(2);
|
|
594
|
+
}
|
|
595
|
+
hookFileKey = rel;
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
if (hookFileKey && typeof hookContent === 'string') {
|
|
599
|
+
return { files: { [hookFileKey]: hookContent }, claudeHook };
|
|
563
600
|
}
|
|
564
|
-
if (hookFilePath && fs.existsSync(hookFilePath)) {
|
|
601
|
+
if (hookFilePath && hookFileKey && fs.existsSync(hookFilePath)) {
|
|
565
602
|
// Only a path was given — read from disk so post-edit hooks still work
|
|
566
603
|
// when the agent doesn't ship the content inline.
|
|
567
604
|
const content = fs.readFileSync(hookFilePath, 'utf8');
|
|
568
|
-
return { files: { [
|
|
605
|
+
return { files: { [hookFileKey]: content }, claudeHook };
|
|
569
606
|
}
|
|
570
607
|
console.error('hook: stdin payload not recognized. Expected one of:\n' +
|
|
571
608
|
' {"files": {"path": "content"}}\n' +
|
package/dist/utils/fs.js
CHANGED
|
@@ -335,7 +335,13 @@ async function collectFiles(baseDir, includePatterns, excludePatterns) {
|
|
|
335
335
|
const files = {};
|
|
336
336
|
const state = { count: 0, limitReached: false };
|
|
337
337
|
walk(repoRoot, repoRoot, gitignores, prodcycleIgnores, includePatterns, excludePatterns, files, state);
|
|
338
|
-
|
|
338
|
+
// Canonical sort by path. Object iteration order is insertion order, which
|
|
339
|
+
// reflects directory-walk order — that differs between Node's `readdirSync`
|
|
340
|
+
// and Python's `os.walk`, so the two CLIs would otherwise chunk the same
|
|
341
|
+
// file set into different request shapes and the server-side per-chunk
|
|
342
|
+
// evaluation produced subtle finding divergences (CLAUDE.md "Node and
|
|
343
|
+
// Python must stay symmetric"). Sorting here makes the wire shape identical.
|
|
344
|
+
return Object.fromEntries(Object.entries(files).sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0)));
|
|
339
345
|
}
|
|
340
346
|
function walk(dir, repoRoot, gitignores, prodcycleIgnores, includePatterns, userExcludes, files, state) {
|
|
341
347
|
if (state.limitReached)
|
package/package.json
CHANGED