lee-spec-kit 0.8.6 → 0.8.7
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/bootstrap-G37N6RGB.js +0 -0
- package/dist/chunk-7V7RMGEU.js +0 -0
- package/dist/chunk-GR7JQBWF.js +0 -0
- package/dist/chunk-LYFRLOFQ.js +0 -0
- package/dist/{hooks-43P4YKHY.js → hooks-UUAAVDS3.js} +110 -10
- package/dist/hooks-UUAAVDS3.js.map +1 -0
- package/dist/index.js +101 -56
- package/dist/index.js.map +1 -1
- package/package.json +15 -13
- package/templates/en/common/agents/agents.md +1 -0
- package/templates/ko/common/agents/agents.md +1 -0
- package/dist/hooks-43P4YKHY.js.map +0 -1
|
File without changes
|
package/dist/chunk-7V7RMGEU.js
CHANGED
|
File without changes
|
package/dist/chunk-GR7JQBWF.js
CHANGED
|
File without changes
|
package/dist/chunk-LYFRLOFQ.js
CHANGED
|
File without changes
|
|
@@ -660,6 +660,88 @@ function normalizeCommandText(value) {
|
|
|
660
660
|
return String(value || '').replace(/[ \\t\\r\\n]+/g, ' ').trim();
|
|
661
661
|
}
|
|
662
662
|
|
|
663
|
+
function extractLeeSpecKitFeatureRef(value) {
|
|
664
|
+
const tokens = tokenizeShellCommand(value);
|
|
665
|
+
const cliIndex = tokens.findIndex((token) => /(?:^|[\\\\/])lee-spec-kit(?:\\.cmd|\\.exe)?$/i.test(token));
|
|
666
|
+
if (cliIndex === -1) return null;
|
|
667
|
+
for (let index = cliIndex + 1; index < tokens.length; index += 1) {
|
|
668
|
+
const token = tokens[index];
|
|
669
|
+
if (!token || token === 'npx' || token === '--yes' || token === '-y') continue;
|
|
670
|
+
if (token === 'github' && tokens[index + 1] === 'issue') return tokens[index + 2] || null;
|
|
671
|
+
if (token === 'github' && tokens[index + 1] === 'pr') return tokens[index + 2] || null;
|
|
672
|
+
if (token === 'workflow-stage') {
|
|
673
|
+
const candidate = tokens[index + 1] || null;
|
|
674
|
+
return candidate && !candidate.startsWith('-') ? candidate : null;
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
return null;
|
|
678
|
+
}
|
|
679
|
+
|
|
680
|
+
function extractBranchCreateTarget(value) {
|
|
681
|
+
const tokens = tokenizeShellCommand(unwrapShellCommand(value));
|
|
682
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
683
|
+
const token = tokens[index] || '';
|
|
684
|
+
if (token === '-b' || token === '-c' || token === '--create') {
|
|
685
|
+
const candidate = tokens[index + 1] || '';
|
|
686
|
+
if (candidate && !candidate.startsWith('-')) return candidate;
|
|
687
|
+
}
|
|
688
|
+
if (token.startsWith('-b') && token.length > 2) return token.slice(2);
|
|
689
|
+
if (token.startsWith('-c') && token.length > 2) return token.slice(2);
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
const worktreeAddIndex = tokens.findIndex((token, index) => {
|
|
693
|
+
return token === 'add' && index > 0 && tokens.slice(0, index).some((item) => normalizeExecutableToken(item) === 'git' || item === 'worktree');
|
|
694
|
+
});
|
|
695
|
+
if (worktreeAddIndex !== -1) {
|
|
696
|
+
const nonOptionArgs = [];
|
|
697
|
+
for (let index = worktreeAddIndex + 1; index < tokens.length; index += 1) {
|
|
698
|
+
const token = tokens[index];
|
|
699
|
+
if (!token || token.startsWith('-')) continue;
|
|
700
|
+
nonOptionArgs.push(token);
|
|
701
|
+
}
|
|
702
|
+
return nonOptionArgs.length >= 2 ? nonOptionArgs[1] : null;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const branchNameMatch = String(value).match(/\\bfeat\\/[A-Za-z0-9][A-Za-z0-9._/-]*/);
|
|
706
|
+
return branchNameMatch?.[0] || null;
|
|
707
|
+
}
|
|
708
|
+
|
|
709
|
+
function escapeRegExp(value) {
|
|
710
|
+
return String(value || '').replace(/[\\^$.*+?()[\\]{}|]/g, '\\\\$&');
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
function readFeatureRefFromTasksBranch(docsDir, branchName) {
|
|
714
|
+
if (!docsDir || !branchName) return null;
|
|
715
|
+
const featuresRoot = path.join(docsDir, 'features');
|
|
716
|
+
const stack = [featuresRoot];
|
|
717
|
+
while (stack.length > 0) {
|
|
718
|
+
const current = stack.pop();
|
|
719
|
+
let entries = [];
|
|
720
|
+
try {
|
|
721
|
+
entries = fs.readdirSync(current, { withFileTypes: true });
|
|
722
|
+
} catch {
|
|
723
|
+
continue;
|
|
724
|
+
}
|
|
725
|
+
for (const entry of entries) {
|
|
726
|
+
const entryPath = path.join(current, entry.name);
|
|
727
|
+
if (entry.isDirectory()) {
|
|
728
|
+
stack.push(entryPath);
|
|
729
|
+
continue;
|
|
730
|
+
}
|
|
731
|
+
if (entry.name !== 'tasks.md') continue;
|
|
732
|
+
try {
|
|
733
|
+
const content = fs.readFileSync(entryPath, 'utf8');
|
|
734
|
+
if (new RegExp('^-\\\\s+\\\\*\\\\*(?:Branch|\uBE0C\uB79C\uCE58)\\\\*\\\\*:\\\\s+\`?' + escapeRegExp(branchName) + '\`?\\\\s*$', 'm').test(content)) {
|
|
735
|
+
return path.basename(path.dirname(entryPath));
|
|
736
|
+
}
|
|
737
|
+
} catch {
|
|
738
|
+
// Ignore unreadable feature docs and keep scanning.
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
}
|
|
742
|
+
return null;
|
|
743
|
+
}
|
|
744
|
+
|
|
663
745
|
function hasUnsupportedGitTargetOptions(value) {
|
|
664
746
|
const unwrappedValue = unwrapShellCommand(value);
|
|
665
747
|
const tokens = tokenizeShellCommand(unwrappedValue);
|
|
@@ -786,11 +868,6 @@ if (isAlwaysBlockedGhOperation) {
|
|
|
786
868
|
process.exit(0);
|
|
787
869
|
}
|
|
788
870
|
|
|
789
|
-
if (hasUnsupportedShellWrappedDangerousCommand) {
|
|
790
|
-
printBlock('lee-spec-kit hooks do not support this shell wrapper for git or gh commands. Re-run the command from a supported shell or the target repo root instead.');
|
|
791
|
-
process.exit(0);
|
|
792
|
-
}
|
|
793
|
-
|
|
794
871
|
if (hasUnsupportedGitTarget || hasGitTargetEnvOverride) {
|
|
795
872
|
printBlock('Git commands using --git-dir, --work-tree, GIT_DIR, or GIT_WORK_TREE are not supported by lee-spec-kit hooks. Re-run the command from the target repo root instead.');
|
|
796
873
|
process.exit(0);
|
|
@@ -833,8 +910,18 @@ const isPotentialMergeCleanupCommand =
|
|
|
833
910
|
command.includes('branch -D') ||
|
|
834
911
|
command.includes('push origin --delete')
|
|
835
912
|
);
|
|
836
|
-
|
|
837
|
-
|
|
913
|
+
const commandFeatureRef =
|
|
914
|
+
extractLeeSpecKitFeatureRef(command) ||
|
|
915
|
+
readFeatureRefFromTasksBranch(docsDir, extractBranchCreateTarget(command));
|
|
916
|
+
if (hasUnsupportedShellWrappedDangerousCommand && !commandFeatureRef) {
|
|
917
|
+
printBlock('lee-spec-kit hooks do not support this shell wrapper for git or gh commands. Re-run the command from a supported shell or the target repo root instead.');
|
|
918
|
+
process.exit(0);
|
|
919
|
+
}
|
|
920
|
+
if (stageBoundAction || isPotentialMergeCleanupCommand || hasUnsupportedShellWrappedDangerousCommand) {
|
|
921
|
+
const stageArgs = commandFeatureRef
|
|
922
|
+
? ['workflow-stage', commandFeatureRef, '--json']
|
|
923
|
+
: ['workflow-stage', '--json'];
|
|
924
|
+
const stageResult = runLeeSpecKitJson(stageArgs, cwd);
|
|
838
925
|
if (!stageResult.ok) {
|
|
839
926
|
printBlock('lee-spec-kit workflow-stage failed inside the Codex hook. Resolve the workflow stage before running this stage-bound command.');
|
|
840
927
|
process.exit(0);
|
|
@@ -844,7 +931,17 @@ if (stageBoundAction || isPotentialMergeCleanupCommand) {
|
|
|
844
931
|
printBlock('Resolve feature selection and workflow stage before running this stage-bound command.');
|
|
845
932
|
process.exit(0);
|
|
846
933
|
}
|
|
847
|
-
|
|
934
|
+
const isExactNextActionCommand =
|
|
935
|
+
normalizeCommandText(stage?.nextAction?.command) === normalizeCommandText(command);
|
|
936
|
+
if (hasUnsupportedShellWrappedDangerousCommand && !isExactNextActionCommand) {
|
|
937
|
+
printBlock('lee-spec-kit hooks do not support this shell wrapper for git or gh commands. Re-run the command from a supported shell or the target repo root instead.');
|
|
938
|
+
process.exit(0);
|
|
939
|
+
}
|
|
940
|
+
if (
|
|
941
|
+
stageBoundAction &&
|
|
942
|
+
stage?.nextAction?.category !== stageBoundAction &&
|
|
943
|
+
!isExactNextActionCommand
|
|
944
|
+
) {
|
|
848
945
|
printBlock(
|
|
849
946
|
\`Current workflow stage is \${stage?.stage || 'unknown'} and only \${stage?.nextAction?.category || 'the current nextAction'} is allowed next. Do not jump ahead to \${stageBoundAction}.\`
|
|
850
947
|
);
|
|
@@ -855,10 +952,13 @@ if (stageBoundAction || isPotentialMergeCleanupCommand) {
|
|
|
855
952
|
const isExactMergeCleanupCommand =
|
|
856
953
|
stage?.nextAction?.category === 'merge_cleanup' &&
|
|
857
954
|
normalizeCommandText(stage?.nextAction?.command) === normalizeCommandText(command);
|
|
955
|
+
const isExactNextActionCommand =
|
|
956
|
+
normalizeCommandText(stage?.nextAction?.command) === normalizeCommandText(command);
|
|
858
957
|
|
|
859
958
|
if (
|
|
860
959
|
path.resolve(gitCommandCwd) !== path.resolve(cwd) &&
|
|
861
960
|
!isGitCommit &&
|
|
961
|
+
!isExactNextActionCommand &&
|
|
862
962
|
!isExactMergeCleanupCommand &&
|
|
863
963
|
!(stageBoundAction === 'branch_create' && (isGitCreateBranch || isGitWorktreeAdd))
|
|
864
964
|
) {
|
|
@@ -1186,5 +1286,5 @@ async function removeLeeSpecKitCodexHooks(repoRoot = process.cwd()) {
|
|
|
1186
1286
|
}
|
|
1187
1287
|
|
|
1188
1288
|
export { getRepoCodexDir, getRepoHooksConfigPath, getRepoHooksDir, removeLeeSpecKitCodexHooks, resolveCodexHooksRepoRoot, upsertLeeSpecKitCodexHooks };
|
|
1189
|
-
//# sourceMappingURL=hooks-
|
|
1190
|
-
//# sourceMappingURL=hooks-
|
|
1289
|
+
//# sourceMappingURL=hooks-UUAAVDS3.js.map
|
|
1290
|
+
//# sourceMappingURL=hooks-UUAAVDS3.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/integrations/codex/hooks.ts"],"names":[],"mappings":";;;;;;;AAKA,IAAM,sBAAA,GAAyB;AAAA,EAC7B,8BAAA;AAAA,EACA,gCAAA;AAAA,EACA,qCAAA;AAAA,EACA,yBAAA;AAAA,EACA;AACF,CAAA;AAsBA,SAAS,qBAAqB,QAAA,EAAuC;AACnE,EAAA,QAAQ,QAAA;AAAU,IAChB,KAAK,8BAAA;AACH,MAAA,OAAO,CAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA,uBAAA,EAsBY,IAAA,CAAK,SAAA,CAAU,yBAAA,EAA2B,CAAC,CAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAAA,IA4EhE,KAAK,gCAAA;AACH,MAAA,OAAO,CAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAAA,IAmDT,KAAK,qCAAA;AACH,MAAA,OAAO,CAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,CAAA;AAAA,IA6CT,KAAK,yBAAA;AACH,MAAA,OAAO,CAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AA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","file":"hooks-UUAAVDS3.js","sourcesContent":["import fs from 'fs-extra';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { runGitCapture } from '../../utils/git-run.js';\n\nconst MANAGED_HOOK_FILENAMES = [\n '_lee_spec_kit_hook_utils.mjs',\n 'session_start_lee_spec_kit.mjs',\n 'user_prompt_submit_lee_spec_kit.mjs',\n 'pre_tool_use_policy.mjs',\n 'stop_workflow_audit.mjs',\n] as const;\n\ntype ManagedHookFileName = (typeof MANAGED_HOOK_FILENAMES)[number];\ntype HookEventName = 'SessionStart' | 'UserPromptSubmit' | 'PreToolUse' | 'Stop';\n\ninterface HookHandler {\n type: 'command';\n command: string;\n statusMessage?: string;\n timeout?: number;\n}\n\ninterface HookMatcherGroup {\n matcher?: string;\n hooks: HookHandler[];\n}\n\ninterface HooksConfigFile {\n hooks?: Partial<Record<HookEventName, HookMatcherGroup[]>> & Record<string, unknown>;\n [key: string]: unknown;\n}\n\nfunction getHookScriptContent(fileName: ManagedHookFileName): string {\n switch (fileName) {\n case '_lee_spec_kit_hook_utils.mjs':\n return `#!/usr/bin/env node\nimport fs from 'node:fs';\nimport { spawnSync } from 'node:child_process';\n\nexport function readHookInput() {\n try {\n const raw = fs.readFileSync(0, 'utf8').trim();\n if (!raw) return { ok: true, value: {} };\n return {\n ok: true,\n value: JSON.parse(raw),\n };\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Invalid Codex hook payload';\n return {\n ok: false,\n error: message,\n };\n }\n}\n\nconst CLI_ENTRYPOINT = ${JSON.stringify(getInstalledCliEntrypoint())};\n\nexport function runLeeSpecKit(args, cwd = process.cwd()) {\n return spawnSync(process.execPath, [CLI_ENTRYPOINT, ...args], {\n cwd,\n encoding: 'utf8',\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n}\n\nexport function runLeeSpecKitJson(args, cwd = process.cwd()) {\n const result = runLeeSpecKit(args, cwd);\n const stdout = String(result.stdout || '').trim();\n const stderr = String(result.stderr || '').trim();\n\n if (result.error) {\n return {\n ok: false,\n error: result.error.message || String(result.error),\n status: result.status ?? 1,\n };\n }\n\n if (result.status !== 0) {\n return {\n ok: false,\n error: stderr || stdout || \\`lee-spec-kit \\${args.join(' ')} failed\\`,\n status: result.status ?? 1,\n };\n }\n\n if (!stdout) {\n return {\n ok: false,\n error: \\`lee-spec-kit \\${args.join(' ')} returned empty JSON output\\`,\n status: result.status ?? 0,\n };\n }\n\n try {\n return {\n ok: true,\n data: JSON.parse(stdout),\n status: result.status ?? 0,\n };\n } catch (error) {\n const message =\n error instanceof Error ? error.message : 'Invalid JSON output from lee-spec-kit';\n return {\n ok: false,\n error: \\`\\${message}: \\${stdout.slice(0, 200)}\\`,\n status: result.status ?? 0,\n };\n }\n}\n\nexport function printAdditionalContext(hookEventName, additionalContext) {\n process.stdout.write(\n JSON.stringify({\n hookSpecificOutput: {\n hookEventName,\n additionalContext,\n },\n })\n );\n}\n\nexport function printBlock(reason) {\n process.stdout.write(\n JSON.stringify({\n decision: 'block',\n reason,\n })\n );\n}\n`;\n case 'session_start_lee_spec_kit.mjs':\n return `#!/usr/bin/env node\nimport { printAdditionalContext, readHookInput, runLeeSpecKitJson } from './_lee_spec_kit_hook_utils.mjs';\n\n// Equivalent CLI probe: npx lee-spec-kit detect --json\nconst inputResult = readHookInput();\nif (!inputResult.ok) {\n process.exit(0);\n}\nconst input = inputResult.value;\nconst cwd = typeof input.cwd === 'string' && input.cwd ? input.cwd : process.cwd();\nconst detectedResult = runLeeSpecKitJson(['detect', '--json'], cwd);\nconst detected = detectedResult.ok ? detectedResult.data : null;\n\nif (detected?.status === 'ok' && detected?.isLeeSpecKitProject === true) {\n const docsDir = detected.docsDir || '(unknown docs dir)';\n const stageResult = runLeeSpecKitJson(['workflow-stage', '--json'], cwd);\n const lines = [\n 'lee-spec-kit project detected.',\n 'Use lee-spec-kit docs and workflow policy only when explicitly detected.',\n 'Prefer Codex native execution with workspace-scoped AGENTS.md plus official hooks for the default runtime path.',\n 'If the user gives a generic request such as continuing the next feature according to the rules, interpret it through this workflow automatically.',\n 'infer the workflow automatically even for generic rule-following requests.',\n \\`Docs dir: \\${docsDir}\\`,\n 'Start by reading npx lee-spec-kit docs get agents --json and the active feature docs.',\n 'Run npx lee-spec-kit workflow-stage --json before the next stage and only follow its nextAction.',\n 'Keep docs as the SSOT and treat workflow-audit as the end-of-turn sync guard.',\n ];\n if (stageResult.ok && stageResult.data?.status === 'ok') {\n lines.push(\n \\`Current workflow stage: \\${stageResult.data.stage}\\`,\n \\`Next allowed action: \\${stageResult.data.nextAction?.category || 'none'}\\`,\n \\`Approval required: \\${stageResult.data.approvalRequired ? 'yes' : 'no'}\\`,\n \\`Implementation allowed: \\${stageResult.data.implementationAllowed ? 'yes' : 'no'}\\`\n );\n if (stageResult.data.primaryActionLabel && Array.isArray(stageResult.data.actionOptions)) {\n lines.push(\n \\`Primary reply label: \\${stageResult.data.primaryActionLabel}\\`,\n ...stageResult.data.actionOptions.map(\n (option) => \\`Option \\${option.label} -> reply \\${option.reply}: \\${option.summary}\\`\n )\n );\n }\n } else if (stageResult.ok && stageResult.data?.status === 'error') {\n lines.push(\n \\`Workflow stage is unresolved: \\${stageResult.data.reasonCode}\\`,\n 'Resolve feature selection or create/select the target feature before continuing.'\n );\n }\n printAdditionalContext('SessionStart', lines.join('\\\\n'));\n}\n`;\n case 'user_prompt_submit_lee_spec_kit.mjs':\n return `#!/usr/bin/env node\nimport { printAdditionalContext, readHookInput, runLeeSpecKitJson } from './_lee_spec_kit_hook_utils.mjs';\n\nconst inputResult = readHookInput();\nif (!inputResult.ok) {\n process.exit(0);\n}\nconst input = inputResult.value;\nconst cwd = typeof input.cwd === 'string' && input.cwd ? input.cwd : process.cwd();\nconst detectedResult = runLeeSpecKitJson(['detect', '--json'], cwd);\nconst detected = detectedResult.ok ? detectedResult.data : null;\n\nif (detected?.status === 'ok' && detected?.isLeeSpecKitProject === true) {\n const stageResult = runLeeSpecKitJson(['workflow-stage', '--json'], cwd);\n const lines = [\n 'This prompt is inside a lee-spec-kit workspace.',\n 'Interpret generic rule-following requests through the lee-spec-kit docs workflow automatically.',\n 'Prefer docs get plus feature-local docs as the primary context source.',\n 'Use workflow-stage --json to determine the next allowed stage before implementation.',\n ];\n if (stageResult.ok && stageResult.data?.status === 'ok') {\n lines.push(\n \\`Current workflow stage: \\${stageResult.data.stage}\\`,\n \\`Next allowed action: \\${stageResult.data.nextAction?.category || 'none'}\\`,\n \\`Approval required: \\${stageResult.data.approvalRequired ? 'yes' : 'no'}\\`,\n \\`Implementation allowed: \\${stageResult.data.implementationAllowed ? 'yes' : 'no'}\\`,\n 'Do not jump ahead of the reported nextAction.'\n );\n if (stageResult.data.primaryActionLabel && Array.isArray(stageResult.data.actionOptions)) {\n lines.push(\n 'If labeled action options are present, keep the option labels but ask the user to reply with the exact reply token shown for that option.',\n ...stageResult.data.actionOptions.map(\n (option) => \\`Option \\${option.label} -> reply \\${option.reply}: \\${option.summary}\\`\n )\n );\n }\n } else if (stageResult.ok && stageResult.data?.status === 'error') {\n lines.push(\n \\`Workflow stage is unresolved: \\${stageResult.data.reasonCode}\\`,\n 'Resolve feature selection before attempting implementation.'\n );\n }\n printAdditionalContext('UserPromptSubmit', lines.join('\\\\n'));\n}\n`;\n case 'pre_tool_use_policy.mjs':\n return `#!/usr/bin/env node\nimport { printBlock, readHookInput, runLeeSpecKitJson } from './_lee_spec_kit_hook_utils.mjs';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nfunction normalizeResolvedPath(value) {\n try {\n return fs.realpathSync.native(value);\n } catch {\n return path.resolve(value);\n }\n}\n\nconst inputResult = readHookInput();\nif (!inputResult.ok) {\n printBlock('Codex hook input was malformed. Resolve the local hook setup before continuing.');\n process.exit(0);\n}\nconst input = inputResult.value;\nconst cwd = typeof input.cwd === 'string' && input.cwd ? input.cwd : process.cwd();\nconst command = String(input?.tool_input?.command || '').trim();\n\nfunction tokenizeShellCommand(value) {\n const matches = value.match(/\"(?:\\\\\\\\.|[^\"])*\"|'(?:\\\\\\\\.|[^'])*'|\\\\S+/g) || [];\n return matches.map((token) => {\n if (\n (token.startsWith('\"') && token.endsWith('\"')) ||\n (token.startsWith(\"'\") && token.endsWith(\"'\"))\n ) {\n return token.slice(1, -1);\n }\n return token;\n });\n}\n\nfunction normalizeExecutableToken(token) {\n const base = token.split(/[\\\\\\\\/]/).pop() || token;\n return base.replace(/\\\\.(?:bat|cmd|exe)$/i, '').toLowerCase();\n}\n\nfunction stripEnvWrapper(tokens) {\n let index = 1;\n while (index < tokens.length) {\n const token = tokens[index];\n if (!token) {\n index += 1;\n continue;\n }\n if (token === '--') {\n return tokens.slice(index + 1);\n }\n if (token.startsWith('-')) {\n index += 1;\n continue;\n }\n if (/^[A-Za-z_][A-Za-z0-9_]*=.*/.test(token)) {\n index += 1;\n continue;\n }\n return tokens.slice(index);\n }\n\n return tokens;\n}\n\nfunction stripSudoWrapper(tokens) {\n let index = 1;\n while (index < tokens.length) {\n const token = tokens[index];\n if (!token) {\n index += 1;\n continue;\n }\n if (token === '--') {\n return tokens.slice(index + 1);\n }\n if (token === '-u' || token === '-g' || token === '-h' || token === '-p') {\n index += 2;\n continue;\n }\n if (token.startsWith('-')) {\n index += 1;\n continue;\n }\n return tokens.slice(index);\n }\n\n return tokens;\n}\n\nconst KNOWN_SHELL_EXECUTABLES = new Set([\n 'ash',\n 'bash',\n 'cmd',\n 'dash',\n 'fish',\n 'ksh',\n 'powershell',\n 'pwsh',\n 'sh',\n 'zsh',\n]);\nconst DIRECT_GIT_OR_GH_EXECUTABLES = new Set(['git', 'gh']);\n\nfunction isShellCommandFlag(token) {\n const lower = token.toLowerCase();\n if (lower === '-c' || lower === '/c' || lower === '-command') {\n return true;\n }\n if (token === lower && /^-[a-z]*c[a-z]*$/.test(token)) {\n return true;\n }\n return false;\n}\n\nfunction isExecutablePayloadFlag(token) {\n const lower = token.toLowerCase();\n if (isShellCommandFlag(token)) {\n return true;\n }\n return lower === '-e' || lower === '-r' || lower === '--eval' || lower === '--execute';\n}\n\nfunction findShellCommandFlagIndex(tokens) {\n return tokens.findIndex((token, index) => index > 0 && isShellCommandFlag(token));\n}\n\nfunction findExecutablePayloadFlagIndex(tokens) {\n return tokens.findIndex((token, index) => index > 0 && isExecutablePayloadFlag(token));\n}\n\nfunction containsDangerousGitOrGhPayload(value) {\n return (\n /\\\\bgit(?:\\\\.cmd|\\\\.exe)?\\\\b[\\\\s\\\\S]{0,80}\\\\b(?:commit|push|checkout|switch|restore|clean|rebase|merge|cherry-pick|revert|stash|reset|branch|tag)\\\\b/i.test(\n value\n ) ||\n /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\b[\\\\s\\\\S]{0,80}\\\\b(?:issue|pr|repo|release)\\\\b/i.test(\n value\n )\n );\n}\n\nfunction containsProcessExecutionPayload(value) {\n return (\n /\\\\bchild_process\\\\b/i.test(value) ||\n /\\\\bspawn(?:Sync)?\\\\s*\\\\(/i.test(value) ||\n /\\\\bexec(?:Sync|FileSync|File)?\\\\s*\\\\(/i.test(value) ||\n /\\\\bfork\\\\s*\\\\(/i.test(value) ||\n /\\\\bsubprocess\\\\b/i.test(value) ||\n /\\\\bos\\\\.system\\\\s*\\\\(/i.test(value) ||\n /\\\\bsystem\\\\s*\\\\(/i.test(value) ||\n /\\\\bpopen\\\\s*\\\\(/i.test(value) ||\n /\\\\bcreateprocess\\\\b/i.test(value) ||\n /\\\\bstart-process\\\\b/i.test(value)\n );\n}\n\nconst KNOWN_EXECUTABLE_WRAPPERS = new Set([\n 'bun',\n 'deno',\n 'node',\n 'nodejs',\n 'perl',\n 'php',\n 'python',\n 'python2',\n 'python3',\n 'ruby',\n]);\n\nconst KNOWN_WRAPPER_LAUNCHERS = new Set(['uv', 'uvx']);\n\nconst EXECUTABLE_WRAPPER_OPTIONS_WITH_VALUE = new Set([\n '--experimental-loader',\n '--import',\n '--loader',\n '--require',\n '-m',\n '-r',\n]);\n\nconst UNSUPPORTED_WRAPPER_PAYLOAD = '__LEE_SPEC_KIT_UNSUPPORTED_WRAPPER_PAYLOAD__';\n\nfunction readWrapperScriptPayload(executable, tokens, rawValue, baseCwd) {\n if (KNOWN_WRAPPER_LAUNCHERS.has(executable)) {\n return UNSUPPORTED_WRAPPER_PAYLOAD;\n }\n\n if (!KNOWN_EXECUTABLE_WRAPPERS.has(executable)) {\n const flagIndex = findExecutablePayloadFlagIndex(tokens);\n return flagIndex === -1 || flagIndex + 1 >= tokens.length\n ? null\n : UNSUPPORTED_WRAPPER_PAYLOAD;\n }\n\n if (rawValue.includes('<<')) {\n return UNSUPPORTED_WRAPPER_PAYLOAD;\n }\n\n const flagIndex = findExecutablePayloadFlagIndex(tokens);\n if (flagIndex !== -1 && flagIndex + 1 < tokens.length) {\n return UNSUPPORTED_WRAPPER_PAYLOAD;\n }\n return resolveScriptToken(tokens) ? UNSUPPORTED_WRAPPER_PAYLOAD : null;\n}\n\nfunction resolveScriptToken(tokens) {\n for (let index = 1; index < tokens.length; index += 1) {\n const token = tokens[index];\n if (!token) continue;\n if (token === '--') {\n return tokens[index + 1] || null;\n }\n if (token === '-') {\n return token;\n }\n if (token.startsWith('-')) {\n if (\n EXECUTABLE_WRAPPER_OPTIONS_WITH_VALUE.has(token.toLowerCase()) &&\n index + 1 < tokens.length\n ) {\n index += 1;\n }\n continue;\n }\n return token;\n }\n return null;\n}\n\nfunction resolvesToExistingFile(token, baseCwd) {\n if (!token || token.startsWith('-')) {\n return false;\n }\n\n const resolvedPath = path.resolve(baseCwd, token);\n try {\n return fs.statSync(resolvedPath).isFile();\n } catch {\n return false;\n }\n}\n\nfunction unwrapShellCommand(value) {\n let currentValue = value;\n\n for (let depth = 0; depth < 6; depth += 1) {\n const tokens = tokenizeShellCommand(currentValue);\n const executable = normalizeExecutableToken(tokens[0] || '');\n\n if (executable === 'sudo') {\n const stripped = stripSudoWrapper(tokens);\n currentValue = stripped.join(' ');\n continue;\n }\n\n if (executable === 'command' || executable === 'nohup') {\n if (tokens.length <= 1) return currentValue;\n currentValue = tokens.slice(1).join(' ');\n continue;\n }\n\n if (executable === 'env') {\n const stripped = stripEnvWrapper(tokens);\n currentValue = stripped.join(' ');\n continue;\n }\n\n if (!KNOWN_SHELL_EXECUTABLES.has(executable)) {\n return currentValue;\n }\n\n const flagIndex = findShellCommandFlagIndex(tokens);\n if (flagIndex === -1 || flagIndex + 1 >= tokens.length) {\n return currentValue;\n }\n\n currentValue = tokens.slice(flagIndex + 1).join(' ');\n }\n\n return currentValue;\n}\n\nfunction hasUnsupportedDangerousShellWrapper(value, baseCwd) {\n let currentValue = value;\n\n for (let depth = 0; depth < 6; depth += 1) {\n const tokens = tokenizeShellCommand(currentValue);\n const executable = normalizeExecutableToken(tokens[0] || '');\n\n if (executable === 'sudo') {\n currentValue = stripSudoWrapper(tokens).join(' ');\n continue;\n }\n\n if (executable === 'command' || executable === 'nohup') {\n if (tokens.length <= 1) return false;\n currentValue = tokens.slice(1).join(' ');\n continue;\n }\n\n if (executable === 'env') {\n currentValue = stripEnvWrapper(tokens).join(' ');\n continue;\n }\n\n if (DIRECT_GIT_OR_GH_EXECUTABLES.has(executable)) {\n return false;\n }\n\n const flagIndex = findExecutablePayloadFlagIndex(tokens);\n if (!KNOWN_SHELL_EXECUTABLES.has(executable)) {\n const payload = readWrapperScriptPayload(\n executable,\n tokens,\n currentValue,\n baseCwd\n );\n if (payload === UNSUPPORTED_WRAPPER_PAYLOAD) {\n return true;\n }\n if (!payload) {\n return false;\n }\n return containsDangerousGitOrGhPayload(payload) || containsProcessExecutionPayload(payload);\n }\n\n if (flagIndex === -1 || flagIndex + 1 >= tokens.length) {\n if (currentValue.includes('<<') || resolveScriptToken(tokens)) {\n return true;\n }\n return false;\n }\n\n const payload = tokens.slice(flagIndex + 1).join(' ');\n const payloadTokens = tokenizeShellCommand(payload);\n if (resolvesToExistingFile(payloadTokens[0] || '', baseCwd)) {\n return true;\n }\n currentValue = payload;\n }\n\n return false;\n}\n\nconst GIT_OPTIONS_WITH_VALUE = new Set([\n '-C',\n '-c',\n '--exec-path',\n '--git-dir',\n '--namespace',\n '--super-prefix',\n '--work-tree',\n '--config-env',\n]);\n\nfunction getGitSubcommand(value) {\n const unwrappedValue = unwrapShellCommand(value);\n const tokens = tokenizeShellCommand(unwrappedValue);\n const gitIndex = tokens.findIndex(\n (token) => normalizeExecutableToken(token) === 'git'\n );\n if (gitIndex === -1) return null;\n\n for (let index = gitIndex + 1; index < tokens.length; index += 1) {\n const token = tokens[index];\n if (!token) continue;\n if (token === '--') {\n return tokens[index + 1] || null;\n }\n if (!token.startsWith('-')) {\n return token;\n }\n if (GIT_OPTIONS_WITH_VALUE.has(token) && index + 1 < tokens.length) {\n index += 1;\n }\n }\n\n return null;\n}\n\nfunction getGitCommandCwd(value, baseCwd) {\n const unwrappedValue = unwrapShellCommand(value);\n const tokens = tokenizeShellCommand(unwrappedValue);\n const gitIndex = tokens.findIndex(\n (token) => normalizeExecutableToken(token) === 'git'\n );\n if (gitIndex === -1) return baseCwd;\n\n let currentCwd = baseCwd;\n for (let index = gitIndex + 1; index < tokens.length; index += 1) {\n const token = tokens[index];\n if (!token) continue;\n if (token === '--') break;\n if (!token.startsWith('-')) break;\n if (token === '-C' && index + 1 < tokens.length) {\n currentCwd = path.resolve(currentCwd, tokens[index + 1]);\n index += 1;\n continue;\n }\n if (GIT_OPTIONS_WITH_VALUE.has(token) && index + 1 < tokens.length) {\n index += 1;\n }\n }\n\n return currentCwd;\n}\n\nfunction getGitCommitMessage(value) {\n const unwrappedValue = unwrapShellCommand(value);\n const tokens = tokenizeShellCommand(unwrappedValue);\n const gitIndex = tokens.findIndex(\n (token) => normalizeExecutableToken(token) === 'git'\n );\n if (gitIndex === -1) return null;\n\n let sawCommit = false;\n for (let index = gitIndex + 1; index < tokens.length; index += 1) {\n const token = tokens[index];\n if (!token) continue;\n if (token === '--') break;\n\n if (!sawCommit) {\n if (token === 'commit') {\n sawCommit = true;\n continue;\n }\n if (token.startsWith('-')) {\n if (GIT_OPTIONS_WITH_VALUE.has(token) && index + 1 < tokens.length) {\n index += 1;\n }\n continue;\n }\n break;\n }\n\n if (token === '-m' || token === '--message') {\n return index + 1 < tokens.length ? tokens[index + 1] : null;\n }\n }\n\n return null;\n}\n\nfunction normalizeCommandText(value) {\n return String(value || '').replace(/[ \\\\t\\\\r\\\\n]+/g, ' ').trim();\n}\n\nfunction extractLeeSpecKitFeatureRef(value) {\n const tokens = tokenizeShellCommand(value);\n const cliIndex = tokens.findIndex((token) => /(?:^|[\\\\\\\\/])lee-spec-kit(?:\\\\.cmd|\\\\.exe)?$/i.test(token));\n if (cliIndex === -1) return null;\n for (let index = cliIndex + 1; index < tokens.length; index += 1) {\n const token = tokens[index];\n if (!token || token === 'npx' || token === '--yes' || token === '-y') continue;\n if (token === 'github' && tokens[index + 1] === 'issue') return tokens[index + 2] || null;\n if (token === 'github' && tokens[index + 1] === 'pr') return tokens[index + 2] || null;\n if (token === 'workflow-stage') {\n const candidate = tokens[index + 1] || null;\n return candidate && !candidate.startsWith('-') ? candidate : null;\n }\n }\n return null;\n}\n\nfunction extractBranchCreateTarget(value) {\n const tokens = tokenizeShellCommand(unwrapShellCommand(value));\n for (let index = 0; index < tokens.length; index += 1) {\n const token = tokens[index] || '';\n if (token === '-b' || token === '-c' || token === '--create') {\n const candidate = tokens[index + 1] || '';\n if (candidate && !candidate.startsWith('-')) return candidate;\n }\n if (token.startsWith('-b') && token.length > 2) return token.slice(2);\n if (token.startsWith('-c') && token.length > 2) return token.slice(2);\n }\n\n const worktreeAddIndex = tokens.findIndex((token, index) => {\n return token === 'add' && index > 0 && tokens.slice(0, index).some((item) => normalizeExecutableToken(item) === 'git' || item === 'worktree');\n });\n if (worktreeAddIndex !== -1) {\n const nonOptionArgs = [];\n for (let index = worktreeAddIndex + 1; index < tokens.length; index += 1) {\n const token = tokens[index];\n if (!token || token.startsWith('-')) continue;\n nonOptionArgs.push(token);\n }\n return nonOptionArgs.length >= 2 ? nonOptionArgs[1] : null;\n }\n\n const branchNameMatch = String(value).match(/\\\\bfeat\\\\/[A-Za-z0-9][A-Za-z0-9._/-]*/);\n return branchNameMatch?.[0] || null;\n}\n\nfunction escapeRegExp(value) {\n return String(value || '').replace(/[\\\\^$.*+?()[\\\\]{}|]/g, '\\\\\\\\$&');\n}\n\nfunction readFeatureRefFromTasksBranch(docsDir, branchName) {\n if (!docsDir || !branchName) return null;\n const featuresRoot = path.join(docsDir, 'features');\n const stack = [featuresRoot];\n while (stack.length > 0) {\n const current = stack.pop();\n let entries = [];\n try {\n entries = fs.readdirSync(current, { withFileTypes: true });\n } catch {\n continue;\n }\n for (const entry of entries) {\n const entryPath = path.join(current, entry.name);\n if (entry.isDirectory()) {\n stack.push(entryPath);\n continue;\n }\n if (entry.name !== 'tasks.md') continue;\n try {\n const content = fs.readFileSync(entryPath, 'utf8');\n if (new RegExp('^-\\\\\\\\s+\\\\\\\\*\\\\\\\\*(?:Branch|브랜치)\\\\\\\\*\\\\\\\\*:\\\\\\\\s+\\`?' + escapeRegExp(branchName) + '\\`?\\\\\\\\s*$', 'm').test(content)) {\n return path.basename(path.dirname(entryPath));\n }\n } catch {\n // Ignore unreadable feature docs and keep scanning.\n }\n }\n }\n return null;\n}\n\nfunction hasUnsupportedGitTargetOptions(value) {\n const unwrappedValue = unwrapShellCommand(value);\n const tokens = tokenizeShellCommand(unwrappedValue);\n return tokens.some((token) => {\n const normalized = String(token || '').toLowerCase();\n return (\n normalized === '--git-dir' ||\n normalized.startsWith('--git-dir=') ||\n normalized === '--work-tree' ||\n normalized.startsWith('--work-tree=')\n );\n });\n}\n\nfunction hasGitTargetEnvOverrides(value) {\n const tokens = tokenizeShellCommand(value);\n return tokens.some((token) => {\n const normalized = String(token || '').trim().toUpperCase();\n return (\n normalized.startsWith('GIT_DIR=') ||\n normalized.startsWith('GIT_WORK_TREE=')\n );\n });\n}\n\nconst normalizedCommand = unwrapShellCommand(command);\nconst hasUnsupportedShellWrappedDangerousCommand =\n hasUnsupportedDangerousShellWrapper(command, cwd);\nconst gitSubcommand = getGitSubcommand(command);\nconst gitCommandCwd = getGitCommandCwd(command, cwd);\nconst hasUnsupportedGitTarget = hasUnsupportedGitTargetOptions(command);\nconst hasGitTargetEnvOverride = hasGitTargetEnvOverrides(command);\nconst isGitCommit = gitSubcommand === 'commit';\nconst isGitPush = gitSubcommand === 'push';\nconst isGitCheckout = gitSubcommand === 'checkout';\nconst isGitSwitch = gitSubcommand === 'switch';\nconst isGitWorktree = gitSubcommand === 'worktree';\nconst isGitRestore = gitSubcommand === 'restore';\nconst isGitClean = gitSubcommand === 'clean';\nconst isGitRebase = gitSubcommand === 'rebase';\nconst isGitMerge = gitSubcommand === 'merge';\nconst isGitCherryPick = gitSubcommand === 'cherry-pick';\nconst isGitRevert = gitSubcommand === 'revert';\nconst isGitStash = gitSubcommand === 'stash';\nconst isGitBranchDelete =\n gitSubcommand === 'branch' &&\n /(^|\\\\s)(?:-D|-d|--delete)(\\\\s|$)/.test(normalizedCommand);\nconst isGitTagDelete =\n gitSubcommand === 'tag' &&\n /(^|\\\\s)-d(\\\\s|$)/.test(normalizedCommand);\nconst isGitResetHard =\n gitSubcommand === 'reset' && /(^|\\\\s)--hard(\\\\s|$)/.test(normalizedCommand);\nconst isAlwaysBlockedGhCommand =\n /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\s+repo\\\\s+delete\\\\b/i.test(command) ||\n /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\s+release\\\\s+delete\\\\b/i.test(command) ||\n /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\s+api\\\\b[\\\\s\\\\S]{0,160}(?:--method=DELETE|(?:-X|--method)\\\\s+DELETE)\\\\b/i.test(command) ||\n /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\s+api\\\\b[\\\\s\\\\S]{0,120}\\\\bgraphql\\\\b/i.test(command);\nconst isAlwaysBlockedGhOperation =\n isAlwaysBlockedGhCommand;\nconst isDangerousGhCommand =\n /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\s+issue\\\\s+(?:create|delete|edit|close|reopen)\\\\b/i.test(command) ||\n /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\s+pr\\\\s+(?:create|merge|close|reopen|review|ready)\\\\b/i.test(command) ||\n /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\s+repo\\\\s+(?:delete|archive|rename|edit)\\\\b/i.test(command) ||\n /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\s+release\\\\s+(?:create|delete|edit)\\\\b/i.test(command) ||\n /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\s+api\\\\b[\\\\s\\\\S]{0,160}(?:--method=(?:DELETE|PATCH|POST|PUT)|(?:-X|--method)\\\\s+(?:DELETE|PATCH|POST|PUT))\\\\b/i.test(command);\nconst isGitCreateBranch =\n (isGitCheckout && /(^|\\\\s)-b(\\\\s|$)/.test(normalizedCommand)) ||\n (isGitSwitch && /(^|\\\\s)(?:-c|--create)(\\\\s|$)/.test(normalizedCommand));\nconst isGitWorktreeAdd =\n isGitWorktree && /(^|\\\\s)add(\\\\s|$)/.test(normalizedCommand);\nconst isLeeSpecKitIssueCreate =\n /\\\\blee-spec-kit\\\\b[\\\\s\\\\S]{0,120}\\\\bgithub\\\\s+issue\\\\b[\\\\s\\\\S]{0,160}\\\\b--create\\\\b/i.test(command);\nconst isLeeSpecKitPrCreate =\n /\\\\blee-spec-kit\\\\b[\\\\s\\\\S]{0,120}\\\\bgithub\\\\s+pr\\\\b[\\\\s\\\\S]{0,160}\\\\b--create\\\\b/i.test(command);\nconst isLeeSpecKitPrMerge =\n /\\\\blee-spec-kit\\\\b[\\\\s\\\\S]{0,120}\\\\bgithub\\\\s+pr\\\\b[\\\\s\\\\S]{0,160}\\\\b--merge\\\\b/i.test(command);\nconst isGhIssueCreate =\n isDangerousGhCommand && /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\s+issue\\\\s+create\\\\b/i.test(command);\nconst isGhPrCreate =\n isDangerousGhCommand && /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\s+pr\\\\s+create\\\\b/i.test(command);\nconst isGhPrMerge =\n isDangerousGhCommand && /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\s+pr\\\\s+merge\\\\b/i.test(command);\nlet stageBoundAction = null;\nif (isGitCreateBranch) {\n stageBoundAction = 'branch_create';\n} else if (isGitWorktreeAdd) {\n stageBoundAction = 'branch_create';\n} else if (isGhIssueCreate || isLeeSpecKitIssueCreate) {\n stageBoundAction = 'issue_create';\n} else if (isGhPrCreate || isLeeSpecKitPrCreate) {\n stageBoundAction = 'pr_create';\n} else if (isGhPrMerge || isLeeSpecKitPrMerge) {\n stageBoundAction = 'pr_merge';\n}\nconst isDangerousCommand =\n isAlwaysBlockedGhOperation ||\n hasUnsupportedShellWrappedDangerousCommand ||\n isGitCommit ||\n isGitPush ||\n isGitCheckout ||\n isGitSwitch ||\n isGitWorktree ||\n isGitRestore ||\n isGitClean ||\n isGitRebase ||\n isGitMerge ||\n isGitCherryPick ||\n isGitRevert ||\n isGitStash ||\n isGitBranchDelete ||\n isGitTagDelete ||\n isGitResetHard ||\n isDangerousGhCommand ||\n isLeeSpecKitIssueCreate ||\n isLeeSpecKitPrCreate ||\n isLeeSpecKitPrMerge;\n\nif (!command || !isDangerousCommand) {\n process.exit(0);\n}\n\nif (isAlwaysBlockedGhOperation) {\n printBlock('Destructive GitHub CLI commands such as repo or release deletion are not supported by lee-spec-kit hooks. Re-run them manually after explicit review.');\n process.exit(0);\n}\n\nif (hasUnsupportedGitTarget || hasGitTargetEnvOverride) {\n printBlock('Git commands using --git-dir, --work-tree, GIT_DIR, or GIT_WORK_TREE are not supported by lee-spec-kit hooks. Re-run the command from the target repo root instead.');\n process.exit(0);\n}\n\nconst detectedResult = runLeeSpecKitJson(['detect', '--json'], cwd);\nif (!detectedResult.ok) {\n printBlock('lee-spec-kit detection failed inside the Codex hook. Fix the local CLI or hook setup before continuing.');\n process.exit(0);\n}\nconst detected = detectedResult.data;\nif (!(detected?.status === 'ok' && detected?.isLeeSpecKitProject === true)) {\n process.exit(0);\n}\n\nconst docsDir = typeof detected?.docsDir === 'string' ? detected.docsDir : '';\nconst gitTargetIsDocsRepo =\n !!docsDir &&\n normalizeResolvedPath(gitCommandCwd) === normalizeResolvedPath(docsDir);\n\nif (\n gitTargetIsDocsRepo &&\n (isGitCheckout ||\n isGitSwitch ||\n isGitCreateBranch ||\n isGitWorktreeAdd ||\n gitSubcommand === 'branch')\n) {\n printBlock('Standalone docs repos stay on their docs branch and must not be switched into feature branches or worktrees.');\n process.exit(0);\n}\n\nlet stage = null;\nconst isPotentialMergeCleanupCommand =\n !stageBoundAction &&\n !isGitCommit &&\n path.resolve(gitCommandCwd) !== path.resolve(cwd) &&\n (\n command.includes('worktree remove') ||\n command.includes('branch -D') ||\n command.includes('push origin --delete')\n );\nconst commandFeatureRef =\n extractLeeSpecKitFeatureRef(command) ||\n readFeatureRefFromTasksBranch(docsDir, extractBranchCreateTarget(command));\nif (hasUnsupportedShellWrappedDangerousCommand && !commandFeatureRef) {\n printBlock('lee-spec-kit hooks do not support this shell wrapper for git or gh commands. Re-run the command from a supported shell or the target repo root instead.');\n process.exit(0);\n}\nif (stageBoundAction || isPotentialMergeCleanupCommand || hasUnsupportedShellWrappedDangerousCommand) {\n const stageArgs = commandFeatureRef\n ? ['workflow-stage', commandFeatureRef, '--json']\n : ['workflow-stage', '--json'];\n const stageResult = runLeeSpecKitJson(stageArgs, cwd);\n if (!stageResult.ok) {\n printBlock('lee-spec-kit workflow-stage failed inside the Codex hook. Resolve the workflow stage before running this stage-bound command.');\n process.exit(0);\n }\n stage = stageResult.data;\n if (stage?.status !== 'ok') {\n printBlock('Resolve feature selection and workflow stage before running this stage-bound command.');\n process.exit(0);\n }\n const isExactNextActionCommand =\n normalizeCommandText(stage?.nextAction?.command) === normalizeCommandText(command);\n if (hasUnsupportedShellWrappedDangerousCommand && !isExactNextActionCommand) {\n printBlock('lee-spec-kit hooks do not support this shell wrapper for git or gh commands. Re-run the command from a supported shell or the target repo root instead.');\n process.exit(0);\n }\n if (\n stageBoundAction &&\n stage?.nextAction?.category !== stageBoundAction &&\n !isExactNextActionCommand\n ) {\n printBlock(\n \\`Current workflow stage is \\${stage?.stage || 'unknown'} and only \\${stage?.nextAction?.category || 'the current nextAction'} is allowed next. Do not jump ahead to \\${stageBoundAction}.\\`\n );\n process.exit(0);\n }\n}\n\nconst isExactMergeCleanupCommand =\n stage?.nextAction?.category === 'merge_cleanup' &&\n normalizeCommandText(stage?.nextAction?.command) === normalizeCommandText(command);\nconst isExactNextActionCommand =\n normalizeCommandText(stage?.nextAction?.command) === normalizeCommandText(command);\n\nif (\n path.resolve(gitCommandCwd) !== path.resolve(cwd) &&\n !isGitCommit &&\n !isExactNextActionCommand &&\n !isExactMergeCleanupCommand &&\n !(stageBoundAction === 'branch_create' && (isGitCreateBranch || isGitWorktreeAdd))\n) {\n printBlock('Git commands targeting another repo via -C are only supported for git commit. Re-run the command from the target repo root instead.');\n process.exit(0);\n}\n\nif (isGitCommit) {\n const commitAuditArgs = ['commit-audit', '--json', '--git-root', gitCommandCwd];\n const commitMessage = getGitCommitMessage(command);\n if (commitMessage) {\n commitAuditArgs.push('--message', commitMessage);\n }\n const commitAuditResult = runLeeSpecKitJson(commitAuditArgs, cwd);\n if (!commitAuditResult.ok) {\n printBlock('lee-spec-kit commit-audit failed inside the Codex hook. Resolve the docs guardrail failure before committing.');\n process.exit(0);\n }\n const commitAudit = commitAuditResult.data;\n if (commitAudit?.status === 'blocked') {\n if (commitAudit?.reasonCode === 'UNSUPPORTED_GIT_TARGET') {\n printBlock('Git commit targets outside the current lee-spec-kit project topology are not supported. Re-run the command from the active workspace or target repo root instead.');\n process.exit(0);\n }\n printBlock('Normalize or allowlist non-canonical docs paths before committing.');\n process.exit(0);\n }\n if (!(commitAudit?.status === 'ok' || commitAudit?.status === 'skipped')) {\n printBlock('lee-spec-kit commit-audit returned a non-ok status inside the Codex hook. Resolve the docs guardrail failure before committing.');\n process.exit(0);\n }\n}\n\nconst auditResult = runLeeSpecKitJson(['workflow-audit', '--json'], cwd);\nif (!auditResult.ok) {\n printBlock('lee-spec-kit workflow-audit failed inside the Codex hook. Resolve the docs sync guardrail failure before continuing.');\n process.exit(0);\n}\nconst audit = auditResult.data;\nif (audit?.status === 'needs_sync') {\n printBlock('Sync the active feature docs before running remote or destructive commands.');\n process.exit(0);\n}\nif (!(audit?.status === 'ok' || audit?.status === 'skipped')) {\n printBlock('lee-spec-kit workflow-audit returned a non-ok status inside the Codex hook. Resolve the docs sync guardrail failure before continuing.');\n}\n`;\n case 'stop_workflow_audit.mjs':\n return `#!/usr/bin/env node\nimport { printBlock, readHookInput, runLeeSpecKitJson } from './_lee_spec_kit_hook_utils.mjs';\n\n// Equivalent CLI probe: npx lee-spec-kit workflow-audit --json\nconst inputResult = readHookInput();\nif (!inputResult.ok) {\n printBlock('Codex stop hook input was malformed. Resolve the local hook setup before stopping.');\n process.exit(0);\n}\nconst input = inputResult.value;\nif (input?.stop_hook_active === true) {\n process.stdout.write(JSON.stringify({ continue: true }));\n process.exit(0);\n}\n\nconst cwd = typeof input.cwd === 'string' && input.cwd ? input.cwd : process.cwd();\nconst detectedResult = runLeeSpecKitJson(['detect', '--json'], cwd);\nif (!detectedResult.ok) {\n printBlock('lee-spec-kit detection failed inside the stop hook. Resolve the local CLI or hook setup before stopping.');\n process.exit(0);\n}\nconst detected = detectedResult.data;\nif (!(detected?.status === 'ok' && detected?.isLeeSpecKitProject === true)) {\n process.stdout.write(JSON.stringify({ continue: true }));\n process.exit(0);\n}\n\nconst auditResult = runLeeSpecKitJson(['workflow-audit', '--json'], cwd);\nif (!auditResult.ok) {\n printBlock('lee-spec-kit workflow-audit failed inside the stop hook. Resolve the docs sync guardrail failure before stopping.');\n process.exit(0);\n}\nconst audit = auditResult.data;\nif (audit?.status === 'needs_sync') {\n printBlock('Run one more pass and sync the active feature docs before stopping.');\n process.exit(0);\n}\nif (!(audit?.status === 'ok' || audit?.status === 'skipped')) {\n printBlock('lee-spec-kit workflow-audit returned a non-ok status inside the stop hook. Resolve the docs sync guardrail failure before stopping.');\n process.exit(0);\n}\n\nprocess.stdout.write(JSON.stringify({ continue: true }));\n`;\n default:\n return '';\n }\n}\n\nfunction getManagedHookRelativePath(fileName: ManagedHookFileName): string {\n return path.posix.join('.codex', 'hooks', fileName);\n}\n\nfunction normalizePathSlashes(value: string): string {\n return value.replace(/\\\\/g, '/');\n}\n\nfunction escapeRegExp(value: string): string {\n return value.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n}\n\nfunction getPortableHookCommandSuffix(fileName: ManagedHookFileName): string {\n const relativeHookPath = normalizePathSlashes(getManagedHookRelativePath(fileName));\n const loaderSource = [\n '(async () => {',\n \" const fs = require('node:fs');\",\n \" const path = require('node:path');\",\n \" const { pathToFileURL } = require('node:url');\",\n ` const relativeHookPath = ${JSON.stringify(relativeHookPath)};`,\n ' let dir = process.cwd();',\n ' while (true) {',\n ' const candidate = path.join(dir, relativeHookPath);',\n ' if (fs.existsSync(candidate)) {',\n ' await import(pathToFileURL(candidate).href);',\n ' return;',\n ' }',\n ' const parent = path.dirname(dir);',\n ' if (parent === dir) {',\n \" throw new Error('lee-spec-kit hook script not found: ' + relativeHookPath);\",\n ' }',\n ' dir = parent;',\n ' }',\n '})().catch((error) => {',\n \" console.error(error && error.stack ? error.stack : String(error));\",\n ' process.exit(1);',\n '});',\n ].join(' ');\n return ` -e ${JSON.stringify(loaderSource)}`;\n}\n\nfunction isManagedCommand(command: string): boolean {\n const normalized = normalizePathSlashes(command).trim();\n return MANAGED_HOOK_FILENAMES.some((fileName) => {\n const currentCommand = normalizePathSlashes(toPortableHookCommand(fileName)).trim();\n const portableSuffix = normalizePathSlashes(\n getPortableHookCommandSuffix(fileName)\n ).trim();\n if (normalized === currentCommand || normalized.endsWith(portableSuffix)) {\n return true;\n }\n\n const relativeHookPath = escapeRegExp(getManagedHookRelativePath(fileName));\n const legacyAbsolutePattern = new RegExp(\n `^node\\\\s+[\"']?.*${relativeHookPath}[\"']?$`\n );\n return legacyAbsolutePattern.test(normalized);\n });\n}\n\nfunction toPortableHookCommand(fileName: ManagedHookFileName): string {\n const nodeCommand = JSON.stringify(process.execPath);\n return `${nodeCommand}${getPortableHookCommandSuffix(fileName)}`;\n}\n\nfunction getManagedHooksConfig(): Record<HookEventName, HookMatcherGroup[]> {\n const commandFor = (fileName: ManagedHookFileName) =>\n toPortableHookCommand(fileName);\n\n return {\n SessionStart: [\n {\n matcher: 'startup|resume',\n hooks: [\n {\n type: 'command',\n command: commandFor('session_start_lee_spec_kit.mjs'),\n statusMessage: 'Loading lee-spec-kit workflow context',\n },\n ],\n },\n ],\n UserPromptSubmit: [\n {\n hooks: [\n {\n type: 'command',\n command: commandFor('user_prompt_submit_lee_spec_kit.mjs'),\n },\n ],\n },\n ],\n PreToolUse: [\n {\n matcher: 'Bash',\n hooks: [\n {\n type: 'command',\n command: commandFor('pre_tool_use_policy.mjs'),\n statusMessage: 'Checking lee-spec-kit workflow guardrails',\n },\n ],\n },\n ],\n Stop: [\n {\n hooks: [\n {\n type: 'command',\n command: commandFor('stop_workflow_audit.mjs'),\n timeout: 30,\n },\n ],\n },\n ],\n };\n}\n\nfunction getInstalledCliEntrypoint(): string {\n return path.join(path.dirname(fileURLToPath(import.meta.url)), 'index.js');\n}\n\nfunction pruneManagedGroups(groups: HookMatcherGroup[] | undefined): HookMatcherGroup[] {\n if (!Array.isArray(groups)) return [];\n return groups\n .map((group) => {\n if (!group || typeof group !== 'object' || !Array.isArray(group.hooks)) {\n return group;\n }\n\n const hooks = group.hooks.filter(\n (hook) =>\n !(\n hook &&\n typeof hook === 'object' &&\n typeof hook.command === 'string' &&\n isManagedCommand(hook.command)\n )\n );\n\n if (hooks.length === 0) {\n return null;\n }\n\n return {\n ...group,\n hooks,\n };\n })\n .filter((group): group is HookMatcherGroup => !!group);\n}\n\nfunction mergeManagedGroups(\n current: HooksConfigFile,\n managedHooks: Record<HookEventName, HookMatcherGroup[]>\n): HooksConfigFile {\n const nextHooks: Record<string, unknown> = {\n ...(current.hooks && typeof current.hooks === 'object' ? current.hooks : {}),\n };\n\n for (const eventName of Object.keys(managedHooks) as HookEventName[]) {\n const existing = pruneManagedGroups(\n Array.isArray(nextHooks[eventName])\n ? (nextHooks[eventName] as HookMatcherGroup[])\n : undefined\n );\n nextHooks[eventName] = [...existing, ...managedHooks[eventName]];\n }\n\n return {\n ...current,\n hooks: nextHooks,\n };\n}\n\nfunction removeManagedGroups(current: HooksConfigFile): HooksConfigFile {\n const nextHooks: Record<string, unknown> = {\n ...(current.hooks && typeof current.hooks === 'object' ? current.hooks : {}),\n };\n\n for (const eventName of ['SessionStart', 'UserPromptSubmit', 'PreToolUse', 'Stop']) {\n const pruned = pruneManagedGroups(\n Array.isArray(nextHooks[eventName])\n ? (nextHooks[eventName] as HookMatcherGroup[])\n : undefined\n );\n if (pruned.length > 0) {\n nextHooks[eventName] = pruned;\n } else {\n delete nextHooks[eventName];\n }\n }\n\n return {\n ...current,\n hooks: nextHooks,\n };\n}\n\nexport function getRepoCodexDir(repoRoot = process.cwd()): string {\n return path.join(repoRoot, '.codex');\n}\n\nexport function resolveCodexHooksRepoRoot(cwd = process.cwd()): string {\n return runGitCapture(['rev-parse', '--show-toplevel'], cwd) || cwd;\n}\n\nexport function getRepoHooksDir(repoRoot = process.cwd()): string {\n return path.join(getRepoCodexDir(repoRoot), 'hooks');\n}\n\nexport function getRepoHooksConfigPath(repoRoot = process.cwd()): string {\n return path.join(getRepoCodexDir(repoRoot), 'hooks.json');\n}\n\nexport async function upsertLeeSpecKitCodexHooks(\n repoRoot = process.cwd()\n): Promise<{\n changed: boolean;\n action: 'created' | 'updated' | 'noop';\n hooksJsonPath: string;\n}> {\n const hooksDir = getRepoHooksDir(repoRoot);\n const hooksJsonPath = getRepoHooksConfigPath(repoRoot);\n await fs.ensureDir(hooksDir);\n\n for (const fileName of MANAGED_HOOK_FILENAMES) {\n const targetPath = path.join(hooksDir, fileName);\n await fs.writeFile(targetPath, getHookScriptContent(fileName), {\n encoding: 'utf-8',\n mode: 0o755,\n });\n }\n\n const managedHooks = getManagedHooksConfig();\n const exists = await fs.pathExists(hooksJsonPath);\n const current = exists\n ? ((await fs.readJson(hooksJsonPath)) as HooksConfigFile)\n : ({ hooks: {} } as HooksConfigFile);\n const next = mergeManagedGroups(current, managedHooks);\n const nextJson = `${JSON.stringify(next, null, 2)}\\n`;\n const currentJson = exists ? `${JSON.stringify(current, null, 2)}\\n` : null;\n\n if (currentJson === nextJson) {\n return { changed: false, action: 'noop', hooksJsonPath };\n }\n\n await fs.writeFile(hooksJsonPath, nextJson, 'utf-8');\n return {\n changed: true,\n action: exists ? 'updated' : 'created',\n hooksJsonPath,\n };\n}\n\nexport async function removeLeeSpecKitCodexHooks(\n repoRoot = process.cwd()\n): Promise<{ changed: boolean; hooksJsonPath: string }> {\n const hooksDir = getRepoHooksDir(repoRoot);\n const hooksJsonPath = getRepoHooksConfigPath(repoRoot);\n let changed = false;\n\n if (await fs.pathExists(hooksJsonPath)) {\n const current = (await fs.readJson(hooksJsonPath)) as HooksConfigFile;\n const next = removeManagedGroups(current);\n const currentJson = `${JSON.stringify(current, null, 2)}\\n`;\n const nextJson = `${JSON.stringify(next, null, 2)}\\n`;\n if (currentJson !== nextJson) {\n await fs.writeFile(hooksJsonPath, nextJson, 'utf-8');\n changed = true;\n }\n }\n\n for (const fileName of MANAGED_HOOK_FILENAMES) {\n const targetPath = path.join(hooksDir, fileName);\n if (await fs.pathExists(targetPath)) {\n await fs.remove(targetPath);\n changed = true;\n }\n }\n\n return { changed, hooksJsonPath };\n}\n"]}
|
package/dist/index.js
CHANGED
|
@@ -1274,6 +1274,7 @@ Default runtime path:
|
|
|
1274
1274
|
- Treat lee-spec-kit as the docs schema, workflow policy, and validation toolkit.
|
|
1275
1275
|
- If the user gives a generic request such as continuing the next feature according to the rules, interpret it through this workflow automatically.
|
|
1276
1276
|
- Infer the workflow automatically even for generic rule-following requests.
|
|
1277
|
+
- Avoid launching the first \`npx lee-spec-kit ...\` calls in parallel in a fresh environment; let one initial command finish so the npx cache install does not race.
|
|
1277
1278
|
|
|
1278
1279
|
On session start or after context compression/reset:
|
|
1279
1280
|
|
|
@@ -5531,7 +5532,7 @@ function extractIssueNumberFromUrl(issueUrl) {
|
|
|
5531
5532
|
const match = value.match(/\/issues\/(\d+)(?:[/?#]|$)/);
|
|
5532
5533
|
return match?.[1];
|
|
5533
5534
|
}
|
|
5534
|
-
function syncTasksIssueMetadata(tasksPath, issueNumber, lang) {
|
|
5535
|
+
function syncTasksIssueMetadata(tasksPath, issueNumber, lang, featureSlug) {
|
|
5535
5536
|
if (!fs.existsSync(tasksPath)) {
|
|
5536
5537
|
throw createCliError(
|
|
5537
5538
|
"DOCS_NOT_FOUND",
|
|
@@ -5554,6 +5555,22 @@ function syncTasksIssueMetadata(tasksPath, issueNumber, lang) {
|
|
|
5554
5555
|
next = inserted.content;
|
|
5555
5556
|
changed = changed || inserted.changed;
|
|
5556
5557
|
}
|
|
5558
|
+
const normalizedSlug = (featureSlug || "").trim();
|
|
5559
|
+
if (normalizedSlug) {
|
|
5560
|
+
const branchValue = `feat/${issueNumber}-${normalizedSlug}`;
|
|
5561
|
+
const branchReplaced = replaceListField(
|
|
5562
|
+
next,
|
|
5563
|
+
["Branch", "\uBE0C\uB79C\uCE58"],
|
|
5564
|
+
branchValue
|
|
5565
|
+
);
|
|
5566
|
+
next = branchReplaced.content;
|
|
5567
|
+
changed = changed || branchReplaced.changed;
|
|
5568
|
+
if (!branchReplaced.found) {
|
|
5569
|
+
const inserted = insertFieldInGithubIssueSection(next, "Branch", branchValue);
|
|
5570
|
+
next = inserted.content;
|
|
5571
|
+
changed = changed || inserted.changed;
|
|
5572
|
+
}
|
|
5573
|
+
}
|
|
5557
5574
|
if (changed) {
|
|
5558
5575
|
fs.writeFileSync(tasksPath, next, "utf-8");
|
|
5559
5576
|
}
|
|
@@ -6015,7 +6032,8 @@ function githubCommand(program2) {
|
|
|
6015
6032
|
const synced = syncTasksIssueMetadata(
|
|
6016
6033
|
path8.join(config.docsDir, paths.tasksPath),
|
|
6017
6034
|
syncedIssueNumber,
|
|
6018
|
-
config.lang
|
|
6035
|
+
config.lang,
|
|
6036
|
+
feature.slug
|
|
6019
6037
|
);
|
|
6020
6038
|
const draftSynced = syncIssueDraftMetadata(
|
|
6021
6039
|
path8.join(config.docsDir, paths.issuePath),
|
|
@@ -6234,12 +6252,12 @@ function githubCommand(program2) {
|
|
|
6234
6252
|
config.lang
|
|
6235
6253
|
);
|
|
6236
6254
|
const linkedIssueTitle = remoteIssue?.title?.trim() || "";
|
|
6237
|
-
const issueLinkedDefaultTitle = linkedIssueTitle
|
|
6255
|
+
const issueLinkedDefaultTitle = linkedIssueTitle ? `feat(#${closingIssueNumber}): ${linkedIssueTitle}` : defaultTitle;
|
|
6238
6256
|
title = closingIssueNumber && closingIssueNumber.trim() ? issueLinkedDefaultTitle : requestedTitle || defaultTitle;
|
|
6239
6257
|
if (closingIssueNumber && options.title?.trim() && options.title.trim() !== issueLinkedDefaultTitle) {
|
|
6240
6258
|
throw createCliError(
|
|
6241
6259
|
"PRECONDITION_FAILED",
|
|
6242
|
-
`PR title must match the linked issue title: "${issueLinkedDefaultTitle}".`
|
|
6260
|
+
`PR title must match the linked issue conventional title: "${issueLinkedDefaultTitle}".`
|
|
6243
6261
|
);
|
|
6244
6262
|
}
|
|
6245
6263
|
const normalizedBody = ensureIssueClosingLine(
|
|
@@ -6919,56 +6937,66 @@ async function runTaskAdd(featureName, options) {
|
|
|
6919
6937
|
const ref = normalizeTaskRef(options.ref);
|
|
6920
6938
|
const acceptanceItems = normalizeRequiredItems(options.acceptance, "--acceptance");
|
|
6921
6939
|
const checklistItems = normalizeRequiredItems(options.check, "--check");
|
|
6922
|
-
const
|
|
6923
|
-
|
|
6924
|
-
|
|
6925
|
-
|
|
6926
|
-
|
|
6927
|
-
"
|
|
6928
|
-
|
|
6929
|
-
|
|
6930
|
-
|
|
6931
|
-
|
|
6932
|
-
|
|
6933
|
-
|
|
6934
|
-
|
|
6935
|
-
|
|
6936
|
-
|
|
6937
|
-
|
|
6938
|
-
|
|
6939
|
-
|
|
6940
|
-
|
|
6941
|
-
|
|
6942
|
-
|
|
6943
|
-
|
|
6944
|
-
|
|
6945
|
-
|
|
6946
|
-
|
|
6947
|
-
|
|
6948
|
-
|
|
6949
|
-
|
|
6950
|
-
|
|
6951
|
-
|
|
6952
|
-
|
|
6940
|
+
const docsDir = target.feature.git.docsGitCwd;
|
|
6941
|
+
return withFileLock(
|
|
6942
|
+
getDocsLockPath(docsDir),
|
|
6943
|
+
async () => {
|
|
6944
|
+
const content = await fs.readFile(target.path, "utf-8");
|
|
6945
|
+
const lines = content.split("\n");
|
|
6946
|
+
const taskListIndex = findSecondLevelHeadingIndex(lines, [
|
|
6947
|
+
"Task List",
|
|
6948
|
+
"\uD0DC\uC2A4\uD06C \uBAA9\uB85D"
|
|
6949
|
+
]);
|
|
6950
|
+
if (taskListIndex < 0) {
|
|
6951
|
+
throw createCliError(
|
|
6952
|
+
"PRECONDITION_FAILED",
|
|
6953
|
+
"tasks.md is missing a `Task List` section."
|
|
6954
|
+
);
|
|
6955
|
+
}
|
|
6956
|
+
const sectionEnd = findNextSecondLevelHeadingIndex(lines, taskListIndex);
|
|
6957
|
+
const insertIndex = findTaskInsertIndex(lines, taskListIndex + 1, sectionEnd);
|
|
6958
|
+
const taskId = `T-${target.feature.folderName}-${String(
|
|
6959
|
+
nextTaskSequence(content, target.feature.folderName)
|
|
6960
|
+
).padStart(2, "0")}`;
|
|
6961
|
+
const recordedAt = localDate();
|
|
6962
|
+
const block = formatTaskBlock({
|
|
6963
|
+
ref,
|
|
6964
|
+
taskId,
|
|
6965
|
+
title,
|
|
6966
|
+
date: recordedAt,
|
|
6967
|
+
acceptanceItems,
|
|
6968
|
+
checklistItems
|
|
6969
|
+
});
|
|
6970
|
+
const shouldPrefixBlank = insertIndex > taskListIndex + 1 && (lines[insertIndex - 1] || "").trim() !== "";
|
|
6971
|
+
const shouldSuffixBlank = insertIndex < lines.length && (lines[insertIndex] || "").trim() !== "";
|
|
6972
|
+
lines.splice(
|
|
6973
|
+
insertIndex,
|
|
6974
|
+
0,
|
|
6975
|
+
...shouldPrefixBlank ? [""] : [],
|
|
6976
|
+
...block,
|
|
6977
|
+
...shouldSuffixBlank ? [""] : []
|
|
6978
|
+
);
|
|
6979
|
+
await fs.writeFile(target.path, normalizeMarkdownEnd(lines.join("\n")), "utf-8");
|
|
6980
|
+
const payload = {
|
|
6981
|
+
status: "ok",
|
|
6982
|
+
reasonCode: "TASK_ADDED",
|
|
6983
|
+
feature: target.feature.folderName,
|
|
6984
|
+
taskId,
|
|
6985
|
+
title,
|
|
6986
|
+
ref,
|
|
6987
|
+
tasksUpdated: true,
|
|
6988
|
+
tasksPath: target.path,
|
|
6989
|
+
recordedAt
|
|
6990
|
+
};
|
|
6991
|
+
if (options.json) {
|
|
6992
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
6993
|
+
return;
|
|
6994
|
+
}
|
|
6995
|
+
console.log(chalk.green(`Added task ${taskId} to ${target.feature.folderName}.`));
|
|
6996
|
+
console.log(chalk.gray(`- tasks.md updated: ${target.path}`));
|
|
6997
|
+
},
|
|
6998
|
+
{ owner: "task add" }
|
|
6953
6999
|
);
|
|
6954
|
-
await fs.writeFile(target.path, normalizeMarkdownEnd(lines.join("\n")), "utf-8");
|
|
6955
|
-
const payload = {
|
|
6956
|
-
status: "ok",
|
|
6957
|
-
reasonCode: "TASK_ADDED",
|
|
6958
|
-
feature: target.feature.folderName,
|
|
6959
|
-
taskId,
|
|
6960
|
-
title,
|
|
6961
|
-
ref,
|
|
6962
|
-
tasksUpdated: true,
|
|
6963
|
-
tasksPath: target.path,
|
|
6964
|
-
recordedAt
|
|
6965
|
-
};
|
|
6966
|
-
if (options.json) {
|
|
6967
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
6968
|
-
return;
|
|
6969
|
-
}
|
|
6970
|
-
console.log(chalk.green(`Added task ${taskId} to ${target.feature.folderName}.`));
|
|
6971
|
-
console.log(chalk.gray(`- tasks.md updated: ${target.path}`));
|
|
6972
7000
|
}
|
|
6973
7001
|
function taskCommand(program2) {
|
|
6974
7002
|
const task = program2.command("task").description("Patch feature task docs");
|
|
@@ -7325,7 +7353,7 @@ function registerCodexHooksIntegration(parent) {
|
|
|
7325
7353
|
removeLeeSpecKitCodexHooks,
|
|
7326
7354
|
resolveCodexHooksRepoRoot,
|
|
7327
7355
|
upsertLeeSpecKitCodexHooks
|
|
7328
|
-
} = await import('./hooks-
|
|
7356
|
+
} = await import('./hooks-UUAAVDS3.js');
|
|
7329
7357
|
const repoRoot = config.docsRepo === "standalone" ? resolveConfiguredStandaloneWorkspaceRoot(config) : resolveCodexHooksRepoRoot(process.cwd());
|
|
7330
7358
|
if (!repoRoot) {
|
|
7331
7359
|
throw createCliError(
|
|
@@ -8059,6 +8087,9 @@ function resolveRemotePrReviewState(prRef, feature) {
|
|
|
8059
8087
|
}
|
|
8060
8088
|
return "changes_requested";
|
|
8061
8089
|
}
|
|
8090
|
+
if (reviewDecision.length === 0 && codeRabbitCheckSucceeded && codeRabbitThreadState !== "open" && hasCodeRabbitNoActionableComment(parsed.comments)) {
|
|
8091
|
+
return mergeStateStatus === "CLEAN" || mergeStateStatus === "HAS_HOOKS" ? "approved" : "merge_blocked";
|
|
8092
|
+
}
|
|
8062
8093
|
if (reviewDecision.length === 0 && codeRabbitThreadState === "resolved" && codeRabbitCheckSucceeded) {
|
|
8063
8094
|
return mergeStateStatus === "CLEAN" || mergeStateStatus === "HAS_HOOKS" ? "approved" : "merge_blocked";
|
|
8064
8095
|
}
|
|
@@ -8489,9 +8520,10 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
8489
8520
|
};
|
|
8490
8521
|
}
|
|
8491
8522
|
const committedTaskGate = taskCommitGatePolicy !== "off" && lastDoneTask ? checkTaskCommitGate(feature, effectiveProjectGitCwd, lastDoneTask) : { pass: true };
|
|
8523
|
+
const committedTaskGateRequiresCheckpoint = taskCommitGatePolicy === "strict" || committedTaskGate.reason === "DONE_TRANSITIONS_COUNT";
|
|
8492
8524
|
if (!allTasksDone(tasks)) {
|
|
8493
8525
|
const currentTask = nextTodoTask(tasks);
|
|
8494
|
-
if (
|
|
8526
|
+
if (committedTaskGateRequiresCheckpoint && !committedTaskGate.pass) {
|
|
8495
8527
|
return {
|
|
8496
8528
|
status: "ok",
|
|
8497
8529
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -8534,7 +8566,7 @@ Task commit boundary warning: ${describeTaskCommitGateFailure(committedTaskGate)
|
|
|
8534
8566
|
};
|
|
8535
8567
|
}
|
|
8536
8568
|
if (!tasks.completion.allTasksChecked || !tasks.completion.testsChecked || !tasks.completion.finalOutcomeChecked) {
|
|
8537
|
-
if (
|
|
8569
|
+
if (committedTaskGateRequiresCheckpoint && !committedTaskGate.pass) {
|
|
8538
8570
|
return {
|
|
8539
8571
|
status: "ok",
|
|
8540
8572
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -8867,6 +8899,19 @@ function hasCodeRabbitActionableReview(reviewsValue) {
|
|
|
8867
8899
|
return actionableMatch ? Number(actionableMatch[1]) > 0 : false;
|
|
8868
8900
|
});
|
|
8869
8901
|
}
|
|
8902
|
+
function hasCodeRabbitNoActionableComment(commentsValue) {
|
|
8903
|
+
if (!Array.isArray(commentsValue)) {
|
|
8904
|
+
return false;
|
|
8905
|
+
}
|
|
8906
|
+
return commentsValue.some((entry) => {
|
|
8907
|
+
if (!entry || typeof entry !== "object") return false;
|
|
8908
|
+
const authorLogin = extractNestedString(entry, ["author", "login"]).toLowerCase();
|
|
8909
|
+
if (!authorLogin.startsWith("coderabbitai")) return false;
|
|
8910
|
+
const body = String(entry.body || "");
|
|
8911
|
+
if (/Actionable comments posted:\s*0\b/i.test(body)) return true;
|
|
8912
|
+
return /no actionable comments (?:were )?(?:generated|found|posted)/i.test(body);
|
|
8913
|
+
});
|
|
8914
|
+
}
|
|
8870
8915
|
function extractNestedArray(value, pathSegments) {
|
|
8871
8916
|
let current = value;
|
|
8872
8917
|
for (const segment of pathSegments) {
|