lee-spec-kit 0.9.9 → 0.9.11
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/README.en.md +6 -1
- package/README.md +8 -1
- package/dist/{hooks-4LYFAIA7.js → hooks-P7CYYJYH.js} +86 -21
- package/dist/hooks-P7CYYJYH.js.map +1 -0
- package/dist/index.js +2732 -649
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/README.md +9 -3
- package/templates/en/common/agents/agents.md +5 -4
- package/templates/en/common/agents/custom.md +3 -2
- package/templates/en/common/features/README.md +2 -2
- package/templates/en/common/features/feature-base/plan.md +22 -3
- package/templates/ko/common/README.md +9 -3
- package/templates/ko/common/agents/agents.md +5 -4
- package/templates/ko/common/agents/custom.md +3 -2
- package/templates/ko/common/features/README.md +2 -2
- package/templates/ko/common/features/feature-base/plan.md +22 -3
- package/dist/hooks-4LYFAIA7.js.map +0 -1
package/README.en.md
CHANGED
|
@@ -88,6 +88,7 @@ The overall approach is influenced by [spec-kit](https://github.com/github/spec-
|
|
|
88
88
|
- `commit-audit --json`
|
|
89
89
|
- `workflow-audit --json`
|
|
90
90
|
- `knowledge doctor|sync|audit <feature-ref> --json`: prepare, synchronize, and verify the experimental OpenWiki onboarding layer
|
|
91
|
+
- `knowledge migrate [--apply] --json`: dry-run or explicitly grandfather safe completed Features at the policy cutover
|
|
91
92
|
- `local verify <feature-ref> --json`: run checks in the local Feature worktree and bind the result to its exact tip/tree
|
|
92
93
|
- `local merge <feature-ref> --json`: integrate a verified local Feature using its configured fast-forward or squash strategy
|
|
93
94
|
- `local cleanup <feature-ref> --json`: remove the managed worktree and optionally delete the integrated Feature branch
|
|
@@ -103,7 +104,11 @@ Enable the experimental OpenWiki Knowledge layer with one flag:
|
|
|
103
104
|
npx lee-spec-kit config --openwiki true
|
|
104
105
|
```
|
|
105
106
|
|
|
106
|
-
When enabled, OpenWiki synchronization and a dedicated Knowledge commit are required after task commits and before Feature review.
|
|
107
|
+
When enabled, OpenWiki synchronization and a dedicated Knowledge commit are required after task commits and before Feature review. Authority is claim-specific: PRD owns durable requirements, the active Feature SDD owns the current change scope and decisions, human-maintained project-wide docs own explanations and policy, and tracked code, schemas, and configuration own executable facts. `openwiki/` is derived onboarding evidence that must be checked against those sources. Every Plan's Schema 2 `Curated Documentation Impact` assesses the four core surfaces plus any applicable typed additional surfaces, then reconciles declared targets with the completed Feature diff. Existing projects require one manual baseline reconciliation before relying on per-Feature checks. The current contract is OpenWiki CLI `>=0.5.0 <0.6.0`, OKF 0.2, and Node.js 22+. `knowledge doctor` checks the OpenWiki-owned provider, model, and required credential-field presence from the process environment and `~/.openwiki/.env` (or `OPENWIKI_CONFIG_DIR/.env`) without returning secret values. lee-spec-kit never installs OpenWiki or copies credentials. `false` or an absent flag adds no OpenWiki stage or gate.
|
|
108
|
+
|
|
109
|
+
Synchronization preserves OpenWiki's durable `.run.json` and observes progress. Defaults are 30 seconds for lock acquisition, 10 minutes without observable progress, 90 minutes absolute for bootstrap, and 30 minutes absolute for updates. A single run can override them with `knowledge sync --lock-timeout-ms`, `--idle-timeout-ms`, and `--absolute-timeout-ms`; the project config remains the single `experimental.openwiki` boolean.
|
|
110
|
+
|
|
111
|
+
Inspect generated Knowledge as a graph and document reader from the project root with `openwiki visualize ./openwiki`. This is a read-only visualization command and may run directly; generation and updates must still use `lee-spec-kit knowledge sync`. Add `--no-open` to suppress automatic browser launch or `--port 4400` to select a port. Because `visualize --export` writes files, review its destination and commit policy and run it separately from a trusted terminal.
|
|
107
112
|
|
|
108
113
|
OpenWiki is an external agent with access to the project working directory and configured provider credentials. lee-spec-kit validates changed paths, protected files, and high-confidence secret patterns in output, but it is not an OS sandbox. Enable it only for trusted repositories in an appropriately isolated runtime; operators remain responsible for local and ignored secrets.
|
|
109
114
|
|
package/README.md
CHANGED
|
@@ -88,6 +88,7 @@ npx lee-spec-kit init --workflow local --task-agent on --reviews plan,feature --
|
|
|
88
88
|
- `commit-audit --json`: hooks용 commit-time docs path + canonical commit subject validator
|
|
89
89
|
- `workflow-audit --json`: hooks용 docs sync validator
|
|
90
90
|
- `knowledge doctor|sync|audit <feature-ref> --json`: 실험적 OpenWiki 온보딩 계층 준비·동기화·검증
|
|
91
|
+
- `knowledge migrate [--apply] --json`: 기존 Feature의 문서 영향 판정 도입 상태를 dry-run하고, 안전한 대상만 명시적으로 grandfather 처리
|
|
91
92
|
- `local verify <feature-ref> --json`: local Feature worktree에서 검사를 실행하고 결과를 정확한 tip/tree에 결속
|
|
92
93
|
- `local merge <feature-ref> --json`: 검증된 local Feature를 설정된 fast-forward 또는 squash 전략으로 base branch에 통합
|
|
93
94
|
- `local cleanup <feature-ref> --json`: managed worktree 제거 및 설정에 따른 통합 완료 Feature 브랜치 삭제
|
|
@@ -103,7 +104,13 @@ npx lee-spec-kit init --workflow local --task-agent on --reviews plan,feature --
|
|
|
103
104
|
npx lee-spec-kit config --openwiki true
|
|
104
105
|
```
|
|
105
106
|
|
|
106
|
-
활성화하면 task 커밋 이후 Feature 리뷰 전에 OpenWiki 동기화와 전용 Knowledge 커밋이 필수가 됩니다. SDD
|
|
107
|
+
활성화하면 task 커밋 이후 Feature 리뷰 전에 OpenWiki 동기화와 전용 Knowledge 커밋이 필수가 됩니다. 권한은 주장 유형별로 나뉩니다. PRD는 장기 요구사항, 활성 Feature SDD는 현재 변경의 범위와 결정, 사람이 관리하는 상위 문서는 프로젝트 전체 설명과 정책, tracked 코드·스키마·설정은 실행 사실의 기준이며 `openwiki/`는 이를 바탕으로 다시 검증하는 파생 온보딩 자료입니다. 모든 Plan의 Schema 2 `Curated Documentation Impact`는 네 기본 영역과 필요한 추가 유형을 판정하고, 완료 시 실제 Feature diff와 선언 대상을 대조합니다. 현재 계약은 OpenWiki CLI `>=0.5.0 <0.6.0`, OKF 0.2, Node.js 22 이상입니다. 실행 파일은 package manifest로 식별합니다. `knowledge doctor`는 OpenWiki가 소유하는 `~/.openwiki/.env`(또는 `OPENWIKI_CONFIG_DIR/.env`)와 현재 프로세스 환경에서 provider, model, 필수 credential의 존재 여부만 확인하며 값은 출력하지 않습니다. lee-spec-kit은 OpenWiki나 credential을 자동 설치·복제하지 않습니다. `false` 또는 플래그 누락 시 OpenWiki 관련 stage와 gate는 전혀 추가되지 않습니다.
|
|
108
|
+
|
|
109
|
+
OpenWiki 도입만으로 기존 문서의 낡은 내용이 자동 복구되지는 않습니다. 기존 프로젝트는 `knowledge migrate`로 workflow 호환 대상을 분류하는 것과 별개로, PRD·아키텍처·온보딩·운영·디자인·에이전트 정책 문서를 현재 코드와 한 번 수동 대조해 기준선을 맞춰야 합니다.
|
|
110
|
+
|
|
111
|
+
동기화는 OpenWiki의 durable `.run.json`을 보존하고 진행 상태를 관찰합니다. 기본값은 lock 획득 30초, 무진행 10분, 최초 생성 절대 상한 90분, 증분 갱신 절대 상한 30분입니다. 필요할 때 `knowledge sync`의 `--lock-timeout-ms`, `--idle-timeout-ms`, `--absolute-timeout-ms`로 한 번만 덮어쓸 수 있습니다. 설정 파일의 기능 제어는 계속 `experimental.openwiki` boolean 하나뿐입니다.
|
|
112
|
+
|
|
113
|
+
생성된 Knowledge는 프로젝트 루트에서 `openwiki visualize ./openwiki`로 그래프와 문서 리더를 열어 확인할 수 있습니다. 이 명령은 read-only 시각화이므로 직접 실행해도 되지만, 생성·갱신은 계속 `lee-spec-kit knowledge sync`를 사용합니다. 브라우저 자동 실행을 막으려면 `--no-open`, 포트를 고정하려면 `--port 4400`을 추가합니다. `visualize --export`는 파일을 쓰므로 출력 위치와 커밋 정책을 검토한 뒤 신뢰할 수 있는 터미널에서 별도로 실행합니다.
|
|
107
114
|
|
|
108
115
|
OpenWiki는 프로젝트 작업 디렉터리와 설정된 provider credential에 접근하는 외부 에이전트입니다. lee-spec-kit은 변경 경로·보호 파일·출력 내 고신뢰 secret 패턴을 검증하지만 OS sandbox는 제공하지 않으므로, 신뢰할 수 있는 저장소와 격리된 실행 환경에서만 활성화하고 로컬·ignored secret 관리는 운영자가 책임져야 합니다.
|
|
109
116
|
|
|
@@ -934,8 +934,16 @@ if (!(detected?.status === 'ok' && detected?.isLeeSpecKitProject === true)) {
|
|
|
934
934
|
process.exit(0);
|
|
935
935
|
}
|
|
936
936
|
|
|
937
|
-
function
|
|
938
|
-
if (depth > 4) return
|
|
937
|
+
function classifyOpenWikiInvocation(value, depth = 0) {
|
|
938
|
+
if (depth > 4) return 'blocked';
|
|
939
|
+
const source = String(value || '');
|
|
940
|
+
if (!/\\bopenwiki\\b/iu.test(source)) return 'none';
|
|
941
|
+
if (
|
|
942
|
+
/\\$\\(|\\x60|\\beval\\b|\\bxargs\\b|\\bfind\\b[\\s\\S]{0,80}-exec\\b|\\bpowershell\\b|\\bcmd(?:\\.exe)?\\s+\\/c\\b/iu.test(source) ||
|
|
943
|
+
/(?:^|\\s)[A-Za-z_][A-Za-z0-9_]*\\s*=\\s*openwiki\\b/iu.test(source)
|
|
944
|
+
) {
|
|
945
|
+
return 'blocked';
|
|
946
|
+
}
|
|
939
947
|
const isOpenWikiPackage = (token) => {
|
|
940
948
|
const normalized = String(token || '')
|
|
941
949
|
.replace(/^['"]|['"]$/gu, '')
|
|
@@ -943,7 +951,40 @@ function invokesOpenWikiDirectly(value, depth = 0) {
|
|
|
943
951
|
.pop();
|
|
944
952
|
return /^openwiki(?:@[^\\s]+)?$/iu.test(normalized || '');
|
|
945
953
|
};
|
|
946
|
-
const
|
|
954
|
+
const isSafeArgs = (args) => {
|
|
955
|
+
const normalized = args
|
|
956
|
+
.map((token) => String(token || '').replace(/^['"]|['"]$/gu, ''))
|
|
957
|
+
.filter(Boolean);
|
|
958
|
+
if (normalized.length === 1 && /^(?:--help|-h|--version)$/u.test(normalized[0])) {
|
|
959
|
+
return true;
|
|
960
|
+
}
|
|
961
|
+
if (normalized[0] === 'help') return true;
|
|
962
|
+
if (normalized[0] === 'visualize') {
|
|
963
|
+
let index = 1;
|
|
964
|
+
if (normalized[index] && !normalized[index].startsWith('-')) {
|
|
965
|
+
if (!/^(?:\\.\\/)?openwiki\\/?$/u.test(normalized[index])) return false;
|
|
966
|
+
index += 1;
|
|
967
|
+
}
|
|
968
|
+
while (index < normalized.length) {
|
|
969
|
+
if (normalized[index] === '--no-open') {
|
|
970
|
+
index += 1;
|
|
971
|
+
continue;
|
|
972
|
+
}
|
|
973
|
+
if (
|
|
974
|
+
normalized[index] === '--port' &&
|
|
975
|
+
/^\\d{1,5}$/u.test(normalized[index + 1] || '')
|
|
976
|
+
) {
|
|
977
|
+
index += 2;
|
|
978
|
+
continue;
|
|
979
|
+
}
|
|
980
|
+
return false;
|
|
981
|
+
}
|
|
982
|
+
return true;
|
|
983
|
+
}
|
|
984
|
+
return normalized[0] === 'auth';
|
|
985
|
+
};
|
|
986
|
+
const segments = source.split(/&&|\\|\\||[;|]/u);
|
|
987
|
+
let found = false;
|
|
947
988
|
for (const segment of segments) {
|
|
948
989
|
const tokens = segment.trim().split(/\\s+/u).filter(Boolean);
|
|
949
990
|
let index = 0;
|
|
@@ -978,13 +1019,26 @@ function invokesOpenWikiDirectly(value, depth = 0) {
|
|
|
978
1019
|
while (index < tokens.length && tokens[index].startsWith('-')) index += 1;
|
|
979
1020
|
executable = readExecutable();
|
|
980
1021
|
}
|
|
981
|
-
if (executable === 'openwiki')
|
|
1022
|
+
if (executable === 'openwiki') {
|
|
1023
|
+
found = true;
|
|
1024
|
+
if (!isSafeArgs(tokens.slice(index + 1))) return 'blocked';
|
|
1025
|
+
continue;
|
|
1026
|
+
}
|
|
982
1027
|
if (executable === 'npx' || executable === 'bunx') {
|
|
983
1028
|
index += 1;
|
|
984
1029
|
while (index < tokens.length && tokens[index].startsWith('-')) {
|
|
1030
|
+
const option = tokens[index];
|
|
985
1031
|
index += 1;
|
|
1032
|
+
if (/^(?:--package|-p)$/u.test(option)) {
|
|
1033
|
+
if (!isOpenWikiPackage(tokens[index])) return 'blocked';
|
|
1034
|
+
index += 1;
|
|
1035
|
+
}
|
|
1036
|
+
}
|
|
1037
|
+
if (isOpenWikiPackage(tokens[index])) {
|
|
1038
|
+
found = true;
|
|
1039
|
+
if (!isSafeArgs(tokens.slice(index + 1))) return 'blocked';
|
|
1040
|
+
continue;
|
|
986
1041
|
}
|
|
987
|
-
if (isOpenWikiPackage(tokens[index])) return true;
|
|
988
1042
|
}
|
|
989
1043
|
if (executable === 'npm' || executable === 'pnpm' || executable === 'yarn') {
|
|
990
1044
|
index += 1;
|
|
@@ -993,40 +1047,51 @@ function invokesOpenWikiDirectly(value, depth = 0) {
|
|
|
993
1047
|
if (runner === 'exec' || runner === 'x' || runner === 'dlx') {
|
|
994
1048
|
index += 1;
|
|
995
1049
|
while (index < tokens.length && tokens[index].startsWith('-')) index += 1;
|
|
996
|
-
if (isOpenWikiPackage(tokens[index]))
|
|
1050
|
+
if (isOpenWikiPackage(tokens[index])) {
|
|
1051
|
+
found = true;
|
|
1052
|
+
if (!isSafeArgs(tokens.slice(index + 1))) return 'blocked';
|
|
1053
|
+
continue;
|
|
1054
|
+
}
|
|
997
1055
|
}
|
|
998
1056
|
if (runner === 'run') {
|
|
999
1057
|
index += 1;
|
|
1000
1058
|
while (index < tokens.length && tokens[index] === '--') index += 1;
|
|
1001
|
-
if (isOpenWikiPackage(tokens[index])) return
|
|
1059
|
+
if (isOpenWikiPackage(tokens[index])) return 'blocked';
|
|
1002
1060
|
}
|
|
1003
1061
|
}
|
|
1004
1062
|
if (executable === 'bash' || executable === 'sh' || executable === 'zsh') {
|
|
1005
1063
|
const commandIndex = tokens.findIndex(
|
|
1006
|
-
(token, tokenIndex) =>
|
|
1064
|
+
(token, tokenIndex) =>
|
|
1065
|
+
tokenIndex > index && isShellCommandFlag(token.replace(/^['"]|['"]$/gu, ''))
|
|
1007
1066
|
);
|
|
1008
1067
|
if (
|
|
1009
1068
|
commandIndex >= 0 &&
|
|
1010
|
-
|
|
1069
|
+
classifyOpenWikiInvocation(tokens.slice(commandIndex + 1).join(' '), depth + 1) !== 'none'
|
|
1011
1070
|
) {
|
|
1012
|
-
return
|
|
1071
|
+
return 'blocked';
|
|
1013
1072
|
}
|
|
1014
1073
|
}
|
|
1074
|
+
if (/^(?:node|deno|tsx|ts-node)$/u.test(executable) && /openwiki/iu.test(segment)) {
|
|
1075
|
+
return 'blocked';
|
|
1076
|
+
}
|
|
1015
1077
|
}
|
|
1016
1078
|
if (
|
|
1017
|
-
/\\b(?:bash|sh|zsh)(?:\\.exe)?\\b[\\s\\S]{0,80}\\s-c
|
|
1079
|
+
/\\b(?:bash|sh|zsh)(?:\\.exe)?\\b[\\s\\S]{0,80}\\s-[a-z]*c[a-z]*\\s+["'][^"']*\\bopenwiki(?:\\.cmd|\\.exe)?\\b/iu.test(String(value || ''))
|
|
1018
1080
|
) {
|
|
1019
|
-
return
|
|
1081
|
+
return 'blocked';
|
|
1020
1082
|
}
|
|
1021
|
-
return
|
|
1083
|
+
return found ? 'safe' : 'none';
|
|
1022
1084
|
}
|
|
1023
1085
|
|
|
1024
|
-
if (
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
)
|
|
1028
|
-
|
|
1029
|
-
|
|
1086
|
+
if (detected?.experimentalOpenwiki === true) {
|
|
1087
|
+
const openWikiPolicy = classifyOpenWikiInvocation(command);
|
|
1088
|
+
if (openWikiPolicy === 'blocked') {
|
|
1089
|
+
printBlock('OpenWiki repository generation must run through the exact lee-spec-kit knowledge sync nextAction. Only simple --help, auth, and read-only visualize commands for ./openwiki are allowed directly.');
|
|
1090
|
+
process.exit(0);
|
|
1091
|
+
}
|
|
1092
|
+
if (openWikiPolicy === 'safe' && !isDangerousCommandWithoutOpenWiki) {
|
|
1093
|
+
process.exit(0);
|
|
1094
|
+
}
|
|
1030
1095
|
}
|
|
1031
1096
|
|
|
1032
1097
|
if (isPotentialOpenWikiCommand && !isDangerousCommandWithoutOpenWiki) {
|
|
@@ -1484,5 +1549,5 @@ async function removeLeeSpecKitCodexHooks(repoRoot = process.cwd()) {
|
|
|
1484
1549
|
}
|
|
1485
1550
|
|
|
1486
1551
|
export { getRepoCodexDir, getRepoHooksConfigPath, getRepoHooksDir, removeLeeSpecKitCodexHooks, resolveCodexHooksRepoRoot, upsertLeeSpecKitCodexHooks };
|
|
1487
|
-
//# sourceMappingURL=hooks-
|
|
1488
|
-
//# sourceMappingURL=hooks-
|
|
1552
|
+
//# sourceMappingURL=hooks-P7CYYJYH.js.map
|
|
1553
|
+
//# sourceMappingURL=hooks-P7CYYJYH.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/integrations/codex/hooks.ts"],"names":[],"mappings":"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A;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;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","file":"hooks-P7CYYJYH.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 'subagent_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 =\n | 'SessionStart'\n | 'SubagentStart'\n | 'UserPromptSubmit'\n | 'PreToolUse'\n | '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[]>> &\n Record<string, unknown>;\n [key: string]: unknown;\n}\n\nfunction getHookScriptContent(\n fileName: ManagedHookFileName,\n repoRoot: string,\n workflowRoot: string\n): string {\n switch (fileName) {\n case '_lee_spec_kit_hook_utils.mjs':\n return `#!/usr/bin/env node\nimport fs from 'node:fs';\nimport path from 'node:path';\nimport { spawnSync } from 'node:child_process';\nimport { fileURLToPath } from 'node:url';\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())};\nconst HOOK_REPO_ROOT = path.resolve(\n path.dirname(fileURLToPath(import.meta.url)),\n '..',\n '..'\n);\nconst WORKFLOW_ROOT_RELATIVE = ${JSON.stringify(\n path.relative(path.resolve(repoRoot), path.resolve(workflowRoot))\n )};\n\nexport function getWorkflowCwd() {\n return path.resolve(HOOK_REPO_ROOT, WORKFLOW_ROOT_RELATIVE);\n}\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 { getWorkflowCwd, 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 = getWorkflowCwd();\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 'Primary agents start by reading npx lee-spec-kit docs get agents --json and the active feature docs.',\n 'Delegated subagents skip the primary-agent bootstrap and follow the exact delegationContext and workerContract returned by workflow-stage.',\n 'Primary agents 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 'subagent_start_lee_spec_kit.mjs':\n return `#!/usr/bin/env node\nimport { getWorkflowCwd, 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 = getWorkflowCwd();\nconst detectedResult = runLeeSpecKitJson(['detect', '--json'], cwd);\nconst detected = detectedResult.ok ? detectedResult.data : null;\n\nif (detected?.status === 'ok' && detected?.isLeeSpecKitProject === true) {\n const agentType =\n typeof input.agent_type === 'string' && input.agent_type.trim()\n ? input.agent_type.trim().replace(/[\\u0000-\\u001f\\u007f]/g, ' ')\n : 'unknown';\n const lines = [\n 'lee-spec-kit delegated subagent detected.',\n \\`Codex agent type/profile: \\${agentType}\\`,\n 'Skip the primary-agent startup bootstrap: do not run detect, docs get agents, load every requiredDocs entry, or run workflow-stage.',\n 'Follow the exact delegationContext and workerContract supplied by the parent agent. Read the requiredDocuments in that contract and use referenceDocuments only under their stated conditions.',\n 'Do not load built-in requiredDocs, unrelated Features, or additional Feature documents outside the delegationContext. Ask the parent agent if the contract is missing or insufficient before expanding scope.',\n 'The agent type/profile is informative; delegationContext, the delegation prompt, and workerContract define the exact implementation or review role and its permissions.',\n 'Continue to follow applicable repository safety instructions and the explicit delegation boundaries.',\n ];\n printAdditionalContext('SubagentStart', lines.join('\\\\n'));\n}\n`;\n case 'user_prompt_submit_lee_spec_kit.mjs':\n return `#!/usr/bin/env node\nimport { getWorkflowCwd, 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 = getWorkflowCwd();\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 'Primary agents prefer docs get plus feature-local docs as the primary context source.',\n 'Delegated subagents follow the exact delegationContext and workerContract instead of running the primary-agent bootstrap.',\n 'Primary agents 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 { getWorkflowCwd, 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 workflowCwd = getWorkflowCwd();\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 isPotentialOpenWikiCommand = /\\\\bopenwiki\\\\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 isDangerousCommandWithoutOpenWiki =\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;\nconst isDangerousCommand =\n isDangerousCommandWithoutOpenWiki || isPotentialOpenWikiCommand;\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'], workflowCwd);\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\nfunction classifyOpenWikiInvocation(value, depth = 0) {\n if (depth > 4) return 'blocked';\n const source = String(value || '');\n if (!/\\\\bopenwiki\\\\b/iu.test(source)) return 'none';\n if (\n /\\\\$\\\\(|\\\\x60|\\\\beval\\\\b|\\\\bxargs\\\\b|\\\\bfind\\\\b[\\\\s\\\\S]{0,80}-exec\\\\b|\\\\bpowershell\\\\b|\\\\bcmd(?:\\\\.exe)?\\\\s+\\\\/c\\\\b/iu.test(source) ||\n /(?:^|\\\\s)[A-Za-z_][A-Za-z0-9_]*\\\\s*=\\\\s*openwiki\\\\b/iu.test(source)\n ) {\n return 'blocked';\n }\n const isOpenWikiPackage = (token) => {\n const normalized = String(token || '')\n .replace(/^['\"]|['\"]$/gu, '')\n .split('/')\n .pop();\n return /^openwiki(?:@[^\\\\s]+)?$/iu.test(normalized || '');\n };\n const isSafeArgs = (args) => {\n const normalized = args\n .map((token) => String(token || '').replace(/^['\"]|['\"]$/gu, ''))\n .filter(Boolean);\n if (normalized.length === 1 && /^(?:--help|-h|--version)$/u.test(normalized[0])) {\n return true;\n }\n if (normalized[0] === 'help') return true;\n if (normalized[0] === 'visualize') {\n let index = 1;\n if (normalized[index] && !normalized[index].startsWith('-')) {\n if (!/^(?:\\\\.\\\\/)?openwiki\\\\/?$/u.test(normalized[index])) return false;\n index += 1;\n }\n while (index < normalized.length) {\n if (normalized[index] === '--no-open') {\n index += 1;\n continue;\n }\n if (\n normalized[index] === '--port' &&\n /^\\\\d{1,5}$/u.test(normalized[index + 1] || '')\n ) {\n index += 2;\n continue;\n }\n return false;\n }\n return true;\n }\n return normalized[0] === 'auth';\n };\n const segments = source.split(/&&|\\\\|\\\\||[;|]/u);\n let found = false;\n for (const segment of segments) {\n const tokens = segment.trim().split(/\\\\s+/u).filter(Boolean);\n let index = 0;\n while (index < tokens.length && /^[A-Za-z_][A-Za-z0-9_]*=/u.test(tokens[index])) {\n index += 1;\n }\n while (tokens[index] === 'env' || tokens[index] === 'command') {\n index += 1;\n while (index < tokens.length && (/^[A-Za-z_][A-Za-z0-9_]*=/u.test(tokens[index]) || tokens[index].startsWith('-'))) {\n index += 1;\n }\n }\n const readExecutable = () => (tokens[index] || '')\n .replace(/^['\"]|['\"]$/gu, '')\n .split('/')\n .pop()\n .replace(/\\\\.(?:cmd|exe)$/iu, '');\n let executable = readExecutable();\n while (executable === 'sudo') {\n index += 1;\n while (index < tokens.length && tokens[index].startsWith('-')) {\n const option = tokens[index];\n index += 1;\n if (/^(?:-u|-g|-h|-p|-C|--user|--group|--host|--prompt|--chdir)$/u.test(option)) {\n index += 1;\n }\n }\n executable = readExecutable();\n }\n if (executable === 'corepack') {\n index += 1;\n while (index < tokens.length && tokens[index].startsWith('-')) index += 1;\n executable = readExecutable();\n }\n if (executable === 'openwiki') {\n found = true;\n if (!isSafeArgs(tokens.slice(index + 1))) return 'blocked';\n continue;\n }\n if (executable === 'npx' || executable === 'bunx') {\n index += 1;\n while (index < tokens.length && tokens[index].startsWith('-')) {\n const option = tokens[index];\n index += 1;\n if (/^(?:--package|-p)$/u.test(option)) {\n if (!isOpenWikiPackage(tokens[index])) return 'blocked';\n index += 1;\n }\n }\n if (isOpenWikiPackage(tokens[index])) {\n found = true;\n if (!isSafeArgs(tokens.slice(index + 1))) return 'blocked';\n continue;\n }\n }\n if (executable === 'npm' || executable === 'pnpm' || executable === 'yarn') {\n index += 1;\n while (index < tokens.length && tokens[index].startsWith('-')) index += 1;\n const runner = (tokens[index] || '').replace(/^['\"]|['\"]$/gu, '');\n if (runner === 'exec' || runner === 'x' || runner === 'dlx') {\n index += 1;\n while (index < tokens.length && tokens[index].startsWith('-')) index += 1;\n if (isOpenWikiPackage(tokens[index])) {\n found = true;\n if (!isSafeArgs(tokens.slice(index + 1))) return 'blocked';\n continue;\n }\n }\n if (runner === 'run') {\n index += 1;\n while (index < tokens.length && tokens[index] === '--') index += 1;\n if (isOpenWikiPackage(tokens[index])) return 'blocked';\n }\n }\n if (executable === 'bash' || executable === 'sh' || executable === 'zsh') {\n const commandIndex = tokens.findIndex(\n (token, tokenIndex) =>\n tokenIndex > index && isShellCommandFlag(token.replace(/^['\"]|['\"]$/gu, ''))\n );\n if (\n commandIndex >= 0 &&\n classifyOpenWikiInvocation(tokens.slice(commandIndex + 1).join(' '), depth + 1) !== 'none'\n ) {\n return 'blocked';\n }\n }\n if (/^(?:node|deno|tsx|ts-node)$/u.test(executable) && /openwiki/iu.test(segment)) {\n return 'blocked';\n }\n }\n if (\n /\\\\b(?:bash|sh|zsh)(?:\\\\.exe)?\\\\b[\\\\s\\\\S]{0,80}\\\\s-[a-z]*c[a-z]*\\\\s+[\"'][^\"']*\\\\bopenwiki(?:\\\\.cmd|\\\\.exe)?\\\\b/iu.test(String(value || ''))\n ) {\n return 'blocked';\n }\n return found ? 'safe' : 'none';\n}\n\nif (detected?.experimentalOpenwiki === true) {\n const openWikiPolicy = classifyOpenWikiInvocation(command);\n if (openWikiPolicy === 'blocked') {\n printBlock('OpenWiki repository generation must run through the exact lee-spec-kit knowledge sync nextAction. Only simple --help, auth, and read-only visualize commands for ./openwiki are allowed directly.');\n process.exit(0);\n }\n if (openWikiPolicy === 'safe' && !isDangerousCommandWithoutOpenWiki) {\n process.exit(0);\n }\n}\n\nif (isPotentialOpenWikiCommand && !isDangerousCommandWithoutOpenWiki) {\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 (isGitCommit) {\n const commitStageArgs = commandFeatureRef\n ? ['workflow-stage', commandFeatureRef, '--json']\n : ['workflow-stage', '--json'];\n const commitStageResult = runLeeSpecKitJson(commitStageArgs, workflowCwd);\n const commitStage = commitStageResult.ok ? commitStageResult.data : null;\n if (\n commitStage?.status === 'ok' &&\n commitStage?.nextAction?.category === 'task_execute'\n ) {\n printBlock('Commits are not allowed while task_execute is active. The task implementation worker must return code and verification results without committing; the main agent must synchronize docs and task state before the task_commit checkpoint.');\n process.exit(0);\n }\n}\nif (stageBoundAction || isPotentialMergeCleanupCommand || hasUnsupportedShellWrappedDangerousCommand) {\n const stageArgs = commandFeatureRef\n ? ['workflow-stage', commandFeatureRef, '--json']\n : ['workflow-stage', '--json'];\n const stageResult = runLeeSpecKitJson(stageArgs, workflowCwd);\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, workflowCwd);\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'], workflowCwd);\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 { getWorkflowCwd, 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 = getWorkflowCwd();\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\nif (detected?.experimentalOpenwiki === true) {\n const stageResult = runLeeSpecKitJson(['workflow-stage', '--json'], cwd);\n const category = stageResult.ok ? stageResult.data?.nextAction?.category : null;\n if (category === 'knowledge_setup' || category === 'knowledge_sync' || category === 'knowledge_commit') {\n printBlock('Complete the required OpenWiki Knowledge nextAction before stopping. Partial or stale generated Knowledge cannot pass the workflow.');\n process.exit(0);\n }\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(\n getManagedHookRelativePath(fileName)\n );\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(\n toPortableHookCommand(fileName)\n ).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|clear|compact',\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 SubagentStart: [\n {\n hooks: [\n {\n type: 'command',\n command: commandFor('subagent_start_lee_spec_kit.mjs'),\n statusMessage: 'Loading scoped lee-spec-kit subagent 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(\n groups: HookMatcherGroup[] | undefined\n): 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'\n ? current.hooks\n : {}),\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'\n ? current.hooks\n : {}),\n };\n\n for (const eventName of [\n 'SessionStart',\n 'SubagentStart',\n 'UserPromptSubmit',\n 'PreToolUse',\n 'Stop',\n ]) {\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 workflowRoot = repoRoot\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(\n targetPath,\n getHookScriptContent(fileName, repoRoot, workflowRoot),\n {\n encoding: 'utf-8',\n mode: 0o755,\n }\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"]}
|