lee-spec-kit 0.8.5 → 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 +108 -60
- 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
|
|
|
@@ -5433,10 +5434,10 @@ function resolvePrClosingIssueNumber(tasksContent, featureIssueNumber, lang) {
|
|
|
5433
5434
|
return issueNumber;
|
|
5434
5435
|
}
|
|
5435
5436
|
function assertRemoteIssueExists(issueNumber, cwd, lang) {
|
|
5436
|
-
if (!issueNumber) return;
|
|
5437
|
+
if (!issueNumber) return null;
|
|
5437
5438
|
const result = runProcess(
|
|
5438
5439
|
"gh",
|
|
5439
|
-
["issue", "view", issueNumber, "--json", "number,state"],
|
|
5440
|
+
["issue", "view", issueNumber, "--json", "number,state,title"],
|
|
5440
5441
|
cwd
|
|
5441
5442
|
);
|
|
5442
5443
|
if (result.code !== 0) {
|
|
@@ -5473,6 +5474,7 @@ function assertRemoteIssueExists(issueNumber, cwd, lang) {
|
|
|
5473
5474
|
})
|
|
5474
5475
|
);
|
|
5475
5476
|
}
|
|
5477
|
+
return payload;
|
|
5476
5478
|
}
|
|
5477
5479
|
function getRequiredIssueSections(lang) {
|
|
5478
5480
|
return getGithubDraftRequiredSections("issue", lang);
|
|
@@ -5530,7 +5532,7 @@ function extractIssueNumberFromUrl(issueUrl) {
|
|
|
5530
5532
|
const match = value.match(/\/issues\/(\d+)(?:[/?#]|$)/);
|
|
5531
5533
|
return match?.[1];
|
|
5532
5534
|
}
|
|
5533
|
-
function syncTasksIssueMetadata(tasksPath, issueNumber, lang) {
|
|
5535
|
+
function syncTasksIssueMetadata(tasksPath, issueNumber, lang, featureSlug) {
|
|
5534
5536
|
if (!fs.existsSync(tasksPath)) {
|
|
5535
5537
|
throw createCliError(
|
|
5536
5538
|
"DOCS_NOT_FOUND",
|
|
@@ -5553,6 +5555,22 @@ function syncTasksIssueMetadata(tasksPath, issueNumber, lang) {
|
|
|
5553
5555
|
next = inserted.content;
|
|
5554
5556
|
changed = changed || inserted.changed;
|
|
5555
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
|
+
}
|
|
5556
5574
|
if (changed) {
|
|
5557
5575
|
fs.writeFileSync(tasksPath, next, "utf-8");
|
|
5558
5576
|
}
|
|
@@ -6014,7 +6032,8 @@ function githubCommand(program2) {
|
|
|
6014
6032
|
const synced = syncTasksIssueMetadata(
|
|
6015
6033
|
path8.join(config.docsDir, paths.tasksPath),
|
|
6016
6034
|
syncedIssueNumber,
|
|
6017
|
-
config.lang
|
|
6035
|
+
config.lang,
|
|
6036
|
+
feature.slug
|
|
6018
6037
|
);
|
|
6019
6038
|
const draftSynced = syncIssueDraftMetadata(
|
|
6020
6039
|
path8.join(config.docsDir, paths.issuePath),
|
|
@@ -6227,16 +6246,18 @@ function githubCommand(program2) {
|
|
|
6227
6246
|
feature.issueNumber ? String(feature.issueNumber) : void 0,
|
|
6228
6247
|
config.lang
|
|
6229
6248
|
);
|
|
6230
|
-
|
|
6231
|
-
assertRemoteIssueExists(
|
|
6249
|
+
const remoteIssue = assertRemoteIssueExists(
|
|
6232
6250
|
closingIssueNumber,
|
|
6233
6251
|
projectGitCwd,
|
|
6234
6252
|
config.lang
|
|
6235
6253
|
);
|
|
6236
|
-
|
|
6254
|
+
const linkedIssueTitle = remoteIssue?.title?.trim() || "";
|
|
6255
|
+
const issueLinkedDefaultTitle = linkedIssueTitle ? `feat(#${closingIssueNumber}): ${linkedIssueTitle}` : defaultTitle;
|
|
6256
|
+
title = closingIssueNumber && closingIssueNumber.trim() ? issueLinkedDefaultTitle : requestedTitle || defaultTitle;
|
|
6257
|
+
if (closingIssueNumber && options.title?.trim() && options.title.trim() !== issueLinkedDefaultTitle) {
|
|
6237
6258
|
throw createCliError(
|
|
6238
6259
|
"PRECONDITION_FAILED",
|
|
6239
|
-
`PR title must
|
|
6260
|
+
`PR title must match the linked issue conventional title: "${issueLinkedDefaultTitle}".`
|
|
6240
6261
|
);
|
|
6241
6262
|
}
|
|
6242
6263
|
const normalizedBody = ensureIssueClosingLine(
|
|
@@ -6916,56 +6937,66 @@ async function runTaskAdd(featureName, options) {
|
|
|
6916
6937
|
const ref = normalizeTaskRef(options.ref);
|
|
6917
6938
|
const acceptanceItems = normalizeRequiredItems(options.acceptance, "--acceptance");
|
|
6918
6939
|
const checklistItems = normalizeRequiredItems(options.check, "--check");
|
|
6919
|
-
const
|
|
6920
|
-
|
|
6921
|
-
|
|
6922
|
-
|
|
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
|
-
|
|
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" }
|
|
6950
6999
|
);
|
|
6951
|
-
await fs.writeFile(target.path, normalizeMarkdownEnd(lines.join("\n")), "utf-8");
|
|
6952
|
-
const payload = {
|
|
6953
|
-
status: "ok",
|
|
6954
|
-
reasonCode: "TASK_ADDED",
|
|
6955
|
-
feature: target.feature.folderName,
|
|
6956
|
-
taskId,
|
|
6957
|
-
title,
|
|
6958
|
-
ref,
|
|
6959
|
-
tasksUpdated: true,
|
|
6960
|
-
tasksPath: target.path,
|
|
6961
|
-
recordedAt
|
|
6962
|
-
};
|
|
6963
|
-
if (options.json) {
|
|
6964
|
-
console.log(JSON.stringify(payload, null, 2));
|
|
6965
|
-
return;
|
|
6966
|
-
}
|
|
6967
|
-
console.log(chalk.green(`Added task ${taskId} to ${target.feature.folderName}.`));
|
|
6968
|
-
console.log(chalk.gray(`- tasks.md updated: ${target.path}`));
|
|
6969
7000
|
}
|
|
6970
7001
|
function taskCommand(program2) {
|
|
6971
7002
|
const task = program2.command("task").description("Patch feature task docs");
|
|
@@ -7322,7 +7353,7 @@ function registerCodexHooksIntegration(parent) {
|
|
|
7322
7353
|
removeLeeSpecKitCodexHooks,
|
|
7323
7354
|
resolveCodexHooksRepoRoot,
|
|
7324
7355
|
upsertLeeSpecKitCodexHooks
|
|
7325
|
-
} = await import('./hooks-
|
|
7356
|
+
} = await import('./hooks-UUAAVDS3.js');
|
|
7326
7357
|
const repoRoot = config.docsRepo === "standalone" ? resolveConfiguredStandaloneWorkspaceRoot(config) : resolveCodexHooksRepoRoot(process.cwd());
|
|
7327
7358
|
if (!repoRoot) {
|
|
7328
7359
|
throw createCliError(
|
|
@@ -8056,6 +8087,9 @@ function resolveRemotePrReviewState(prRef, feature) {
|
|
|
8056
8087
|
}
|
|
8057
8088
|
return "changes_requested";
|
|
8058
8089
|
}
|
|
8090
|
+
if (reviewDecision.length === 0 && codeRabbitCheckSucceeded && codeRabbitThreadState !== "open" && hasCodeRabbitNoActionableComment(parsed.comments)) {
|
|
8091
|
+
return mergeStateStatus === "CLEAN" || mergeStateStatus === "HAS_HOOKS" ? "approved" : "merge_blocked";
|
|
8092
|
+
}
|
|
8059
8093
|
if (reviewDecision.length === 0 && codeRabbitThreadState === "resolved" && codeRabbitCheckSucceeded) {
|
|
8060
8094
|
return mergeStateStatus === "CLEAN" || mergeStateStatus === "HAS_HOOKS" ? "approved" : "merge_blocked";
|
|
8061
8095
|
}
|
|
@@ -8486,9 +8520,10 @@ async function collectWorkflowStage(cwd, selector, component) {
|
|
|
8486
8520
|
};
|
|
8487
8521
|
}
|
|
8488
8522
|
const committedTaskGate = taskCommitGatePolicy !== "off" && lastDoneTask ? checkTaskCommitGate(feature, effectiveProjectGitCwd, lastDoneTask) : { pass: true };
|
|
8523
|
+
const committedTaskGateRequiresCheckpoint = taskCommitGatePolicy === "strict" || committedTaskGate.reason === "DONE_TRANSITIONS_COUNT";
|
|
8489
8524
|
if (!allTasksDone(tasks)) {
|
|
8490
8525
|
const currentTask = nextTodoTask(tasks);
|
|
8491
|
-
if (
|
|
8526
|
+
if (committedTaskGateRequiresCheckpoint && !committedTaskGate.pass) {
|
|
8492
8527
|
return {
|
|
8493
8528
|
status: "ok",
|
|
8494
8529
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -8531,7 +8566,7 @@ Task commit boundary warning: ${describeTaskCommitGateFailure(committedTaskGate)
|
|
|
8531
8566
|
};
|
|
8532
8567
|
}
|
|
8533
8568
|
if (!tasks.completion.allTasksChecked || !tasks.completion.testsChecked || !tasks.completion.finalOutcomeChecked) {
|
|
8534
|
-
if (
|
|
8569
|
+
if (committedTaskGateRequiresCheckpoint && !committedTaskGate.pass) {
|
|
8535
8570
|
return {
|
|
8536
8571
|
status: "ok",
|
|
8537
8572
|
reasonCode: "WORKFLOW_STAGE_RESOLVED",
|
|
@@ -8864,6 +8899,19 @@ function hasCodeRabbitActionableReview(reviewsValue) {
|
|
|
8864
8899
|
return actionableMatch ? Number(actionableMatch[1]) > 0 : false;
|
|
8865
8900
|
});
|
|
8866
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
|
+
}
|
|
8867
8915
|
function extractNestedArray(value, pathSegments) {
|
|
8868
8916
|
let current = value;
|
|
8869
8917
|
for (const segment of pathSegments) {
|