lee-spec-kit 0.7.11 → 0.8.1
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 +32 -54
- package/README.md +32 -54
- package/dist/bootstrap-G37N6RGB.js +5 -0
- package/dist/bootstrap-G37N6RGB.js.map +1 -0
- package/dist/chunk-7V7RMGEU.js +11 -0
- package/dist/chunk-7V7RMGEU.js.map +1 -0
- package/dist/chunk-GR7JQBWF.js +26 -0
- package/dist/chunk-GR7JQBWF.js.map +1 -0
- package/dist/chunk-LYFRLOFQ.js +275 -0
- package/dist/chunk-LYFRLOFQ.js.map +1 -0
- package/dist/hooks-4S33YUIB.js +1082 -0
- package/dist/hooks-4S33YUIB.js.map +1 -0
- package/dist/index.js +4689 -15581
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/templates/en/common/README.md +12 -10
- package/templates/en/common/agents/agents.md +35 -89
- package/templates/en/common/agents/skills/create-feature.md +32 -57
- package/templates/en/common/agents/skills/create-issue.md +9 -10
- package/templates/en/common/agents/skills/create-pr.md +6 -11
- package/templates/en/common/agents/skills/execute-task.md +35 -96
- package/templates/en/common/features/README.md +1 -1
- package/templates/en/common/features/feature-base/tasks.md +5 -5
- package/templates/ko/common/README.md +12 -10
- package/templates/ko/common/agents/agents.md +34 -87
- package/templates/ko/common/agents/skills/create-feature.md +32 -58
- package/templates/ko/common/agents/skills/create-issue.md +9 -10
- package/templates/ko/common/agents/skills/create-pr.md +6 -11
- package/templates/ko/common/agents/skills/execute-task.md +35 -102
- package/templates/ko/common/features/README.md +1 -1
- package/templates/ko/common/features/feature-base/tasks.md +5 -5
package/README.en.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<strong>
|
|
10
|
+
<strong>Document-centered harness engineering toolkit for AI agent development</strong>
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
20
|
<a href="#quick-start">Quick Start</a> •
|
|
21
|
-
<a href="#
|
|
22
|
-
<a href="#commands
|
|
21
|
+
<a href="#why-it-exists">Why</a> •
|
|
22
|
+
<a href="#main-commands">Commands</a> •
|
|
23
23
|
<a href="#docs">Docs</a>
|
|
24
24
|
</p>
|
|
25
25
|
|
|
@@ -36,82 +36,60 @@
|
|
|
36
36
|
|
|
37
37
|
## Quick Start
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
`lee-spec-kit` creates PRD, idea, and feature docs, then helps agents work from those documents.
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
npx lee-spec-kit init
|
|
43
|
+
npx lee-spec-kit integrations codex-hooks
|
|
43
44
|
npx lee-spec-kit idea improve-auth-flow
|
|
44
45
|
npx lee-spec-kit feature user-auth
|
|
45
|
-
npx lee-spec-kit context
|
|
46
|
-
npx lee-spec-kit flow
|
|
47
46
|
```
|
|
48
47
|
|
|
48
|
+
After that, the human can keep using normal natural-language requests.
|
|
49
|
+
|
|
49
50
|
## Why It Exists
|
|
50
51
|
|
|
51
52
|
This CLI was built to keep documents and actual execution flow together when working with an AI agent.
|
|
52
53
|
|
|
53
|
-
It is not just a tool that creates a docs folder.
|
|
54
|
+
It is not just a tool that creates a docs folder. It is closer to a harness that helps the agent handle the active feature, the next action, and the points where user approval is required under the same set of rules.
|
|
54
55
|
|
|
55
|
-
The project structure follows `PRD → idea → feature`. PRD is where top-level requirements are written under `docs/prd/`, idea is for candidate approaches or experiments, and feature is the stage where actual work is managed through `spec.md`, `plan.md`, and `
|
|
56
|
+
The project structure follows an SDD (spec-driven development) flow: `PRD → idea → feature`. PRD is where top-level requirements are written under `docs/prd/`, idea is for candidate approaches or experiments, and feature is the stage where actual work is managed through `spec.md`, `plan.md`, `tasks.md`, and `decisions.md`.
|
|
56
57
|
|
|
57
|
-
The overall approach is influenced by [spec-kit](https://github.com/github/spec-kit) and [OpenSpec](https://github.com/Fission-AI/OpenSpec).
|
|
58
|
+
The overall approach is influenced by [spec-kit](https://github.com/github/spec-kit) and [OpenSpec](https://github.com/Fission-AI/OpenSpec).
|
|
58
59
|
|
|
59
|
-
##
|
|
60
|
+
## Humans Usually Ask Like This
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
- "Organize ideas from these requirements."
|
|
63
|
+
- "Promote this idea into a feature and move it forward."
|
|
64
|
+
- "Draft the issue from the current feature docs."
|
|
65
|
+
- "Continue the next feature according to the rules."
|
|
66
|
+
- "Check the docs and code together before we finish."
|
|
62
67
|
|
|
63
|
-
|
|
64
|
-
- The human-facing surface stays small: `init`, `idea`, `feature`, `context`, `flow`.
|
|
65
|
-
- In practice, the main agent runs `detect`, `context`, and `flow` first.
|
|
66
|
-
- Deeper operational commands still exist, but they are no longer front-loaded in the default help output.
|
|
68
|
+
## Main Commands
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
- `init`
|
|
71
|
+
- `idea`
|
|
72
|
+
- `feature`
|
|
73
|
+
- `docs`
|
|
74
|
+
- `detect`
|
|
75
|
+
- `github`
|
|
76
|
+
- `integrations codex-hooks`
|
|
77
|
+
- `integrations codex`
|
|
78
|
+
- `commit-audit --json`
|
|
79
|
+
- `workflow-audit --json`
|
|
69
80
|
|
|
70
|
-
|
|
71
|
-
2. Define top-level requirements in `docs/prd/`.
|
|
72
|
-
3. Create work with `idea` or `feature`.
|
|
73
|
-
4. Let the main agent read `detect` and `context`.
|
|
74
|
-
5. Humans step in for approvals, exceptions, and direction changes.
|
|
75
|
-
6. Use `context` for the current action and `flow` as the default workflow runner.
|
|
81
|
+
Supported modes:
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
- "Read this doc and help me start the project structure."
|
|
80
|
-
- "Organize ideas from these requirements."
|
|
81
|
-
- "Promote this idea into a feature and move it forward."
|
|
82
|
-
- "What is the next action right now?"
|
|
83
|
-
- "Check the overall project state."
|
|
84
|
-
|
|
85
|
-
## Commands
|
|
86
|
-
|
|
87
|
-
- The core agent-facing commands are the three commands below.
|
|
88
|
-
- `detect`: detect whether the workspace uses lee-spec-kit
|
|
89
|
-
- `context`: read the current feature state and next actions
|
|
90
|
-
- `flow`: run the default workflow auto-loop and pause at selection/approval/manual/resume boundaries
|
|
91
|
-
- The public human-facing commands are the five commands below.
|
|
92
|
-
- `init`: initialize docs/workflow scaffolding
|
|
93
|
-
- `idea`: create a pre-feature idea document
|
|
94
|
-
- `feature`: create a concrete execution unit
|
|
95
|
-
- `context`: show the current state and next action
|
|
96
|
-
- `flow`: run the default workflow auto-loop and pause at selection/approval/manual/resume boundaries
|
|
97
|
-
|
|
98
|
-
## Agent Kickoff Prompt
|
|
99
|
-
|
|
100
|
-
```text
|
|
101
|
-
Start procedure:
|
|
102
|
-
1) Run npx lee-spec-kit detect --json
|
|
103
|
-
2) If isLeeSpecKitProject === true, run npx lee-spec-kit context --json-compact
|
|
104
|
-
3) Use context as the read-only state probe, and use flow as the default execution/resume entrypoint
|
|
105
|
-
4) If approvalRequest.required=true, briefly restate the current stage from matchedFeature.currentSubstate* when available, then show approvalRequest.userFacingLines exactly as provided and wait for user approval
|
|
106
|
-
5) Do not execute before approval; for command execution, default to npx lee-spec-kit flow <featureRef> --approve <LABEL> --execute
|
|
107
|
-
6) If isLeeSpecKitProject === false, skip lee-spec-kit-specific flow and continue with normal workflow
|
|
108
|
-
```
|
|
83
|
+
- `embedded`: keep `docs/` inside the project repository.
|
|
84
|
+
- `standalone`: keep the docs repo and project repo separate under a shared workspace root.
|
|
109
85
|
|
|
110
86
|
## Docs
|
|
111
87
|
|
|
112
88
|
- [Public CLI Reference](./docs/reference/public-cli.md)
|
|
113
89
|
- [Agent CLI Reference](./docs/reference/agent-cli.md)
|
|
114
90
|
- [Internal CLI Reference](./docs/reference/internal-cli.md)
|
|
91
|
+
- [Codex Hooks Integration](./docs/reference/codex-hooks.md)
|
|
92
|
+
- [Migration Guide](./docs/reference/migration-codex-hooks.md)
|
|
115
93
|
- [Reference Index](./docs/reference/README.md)
|
|
116
94
|
|
|
117
95
|
## License
|
package/README.md
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
</div>
|
|
8
8
|
|
|
9
9
|
<p align="center">
|
|
10
|
-
<strong>AI 에이전트
|
|
10
|
+
<strong>AI 에이전트 개발을 위한 문서 중심 하네스 엔지니어링 툴킷</strong>
|
|
11
11
|
</p>
|
|
12
12
|
|
|
13
13
|
<p align="center">
|
|
@@ -18,8 +18,8 @@
|
|
|
18
18
|
|
|
19
19
|
<p align="center">
|
|
20
20
|
<a href="#quick-start">Quick Start</a> •
|
|
21
|
-
<a href="
|
|
22
|
-
<a href="
|
|
21
|
+
<a href="#왜-만들었나">Why</a> •
|
|
22
|
+
<a href="#주요-명령">Commands</a> •
|
|
23
23
|
<a href="#docs">Docs</a>
|
|
24
24
|
</p>
|
|
25
25
|
|
|
@@ -36,82 +36,60 @@
|
|
|
36
36
|
|
|
37
37
|
## Quick Start
|
|
38
38
|
|
|
39
|
-
|
|
39
|
+
`lee-spec-kit`은 PRD, idea, feature 문서를 만들고, 에이전트가 그 문서를 기준으로 작업하도록 돕는 도구입니다.
|
|
40
40
|
|
|
41
41
|
```bash
|
|
42
42
|
npx lee-spec-kit init
|
|
43
|
+
npx lee-spec-kit integrations codex-hooks
|
|
43
44
|
npx lee-spec-kit idea improve-auth-flow
|
|
44
45
|
npx lee-spec-kit feature user-auth
|
|
45
|
-
npx lee-spec-kit context
|
|
46
|
-
npx lee-spec-kit flow
|
|
47
46
|
```
|
|
48
47
|
|
|
48
|
+
그 다음부터는 자연어로 요청하면 됩니다.
|
|
49
|
+
|
|
49
50
|
## 왜 만들었나
|
|
50
51
|
|
|
51
52
|
이 CLI는 AI 에이전트와 함께 프로젝트를 진행할 때, 문서와 실제 작업 흐름이 따로 놀지 않게 하려고 만들었습니다.
|
|
52
53
|
|
|
53
|
-
단순히 문서 폴더만 만드는 것이 아니라,
|
|
54
|
+
단순히 문서 폴더만 만드는 것이 아니라, 에이전트가 지금 어떤 feature를 보고 있는지, 다음에 무엇을 해야 하는지, 어디서 사용자 확인이 필요한지를 같은 규칙 안에서 다루도록 만드는 쪽에 더 가깝습니다.
|
|
54
55
|
|
|
55
|
-
작업 구조는 `PRD → idea → feature` 흐름을 따릅니다. PRD는 `docs/prd/`에서 상위 요구사항을 정리하는 공간이고, idea는 후보나 실험을 적어두는 단계이며, feature는 실제로 실행할 단위를 `spec.md`, `plan.md`, `tasks.md`로 내려 관리하는 단계입니다.
|
|
56
|
+
작업 구조는 SDD(spec-driven development) 기반의 `PRD → idea → feature` 흐름을 따릅니다. PRD는 `docs/prd/`에서 상위 요구사항을 정리하는 공간이고, idea는 후보나 실험을 적어두는 단계이며, feature는 실제로 실행할 단위를 `spec.md`, `plan.md`, `tasks.md`, `decisions.md`로 내려 관리하는 단계입니다.
|
|
56
57
|
|
|
57
|
-
구조적으로는 [spec-kit](https://github.com/github/spec-kit)과 [OpenSpec](https://github.com/Fission-AI/OpenSpec)의 접근을 참고했습니다.
|
|
58
|
+
구조적으로는 [spec-kit](https://github.com/github/spec-kit)과 [OpenSpec](https://github.com/Fission-AI/OpenSpec)의 접근을 참고했습니다.
|
|
58
59
|
|
|
59
|
-
##
|
|
60
|
+
## 사람은 보통 이렇게 요청합니다
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
- "이 요구사항 기준으로 idea 정리해줘."
|
|
63
|
+
- "이 idea를 feature로 올려서 진행해줘."
|
|
64
|
+
- "현재 feature 기준으로 issue 초안 만들어줘."
|
|
65
|
+
- "규칙에 따라 다음 feature 진행해줘."
|
|
66
|
+
- "작업 끝났으니 문서랑 같이 점검해줘."
|
|
62
67
|
|
|
63
|
-
|
|
64
|
-
- 사람용으로는 `init`, `idea`, `feature`, `context`, `flow` 정도의 작은 표면을 제공합니다.
|
|
65
|
-
- 실제 에이전트 실행은 `detect`, `context`, `flow` 3개 명령을 기준으로 움직입니다.
|
|
66
|
-
- 더 깊은 운영 명령은 여전히 지원되지만 기본 help에는 전면 노출하지 않습니다.
|
|
68
|
+
## 주요 명령
|
|
67
69
|
|
|
68
|
-
|
|
70
|
+
- `init`: docs/workflow 구조 초기화
|
|
71
|
+
- `idea`: 구현 전 idea 문서 생성
|
|
72
|
+
- `feature`: 실제 작업 단위 생성
|
|
73
|
+
- `docs`: 내장 agent policy 문서 조회
|
|
74
|
+
- `detect`: 현재 워크스페이스가 lee-spec-kit 프로젝트인지 감지
|
|
75
|
+
- `github`: issue/pr 본문 생성 및 검증
|
|
76
|
+
- `integrations codex-hooks`: 현재 workspace용 Codex hooks 스캐폴드 생성/제거
|
|
77
|
+
- `integrations codex`: 전역 Codex hooks flag 설치/제거
|
|
78
|
+
- `commit-audit --json`: hooks용 commit-time docs path validator
|
|
79
|
+
- `workflow-audit --json`: hooks용 docs sync validator
|
|
69
80
|
|
|
70
|
-
|
|
71
|
-
2. `docs/prd/`에서 상위 요구사항을 정리합니다.
|
|
72
|
-
3. `idea`로 후보/실험을 정리하거나, 바로 `feature`로 실행 단위를 만듭니다.
|
|
73
|
-
4. 메인 에이전트가 `detect`와 `context`를 읽고 진행합니다.
|
|
74
|
-
5. 사람은 승인, 예외 처리, 방향 수정 시점에 개입합니다.
|
|
75
|
-
6. 현재 다음 액션과 승인 대기 상태는 `context`, 기본 workflow 실행/재개는 `flow`로 진행합니다.
|
|
81
|
+
지원 모드:
|
|
76
82
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
- "이 문서 읽고 프로젝트 구조 시작하려고 해."
|
|
80
|
-
- "이 요구사항 기준으로 idea 정리해줘."
|
|
81
|
-
- "이 idea를 feature로 올려서 진행해줘."
|
|
82
|
-
- "지금 다음 액션이 뭐야?"
|
|
83
|
-
- "전체 상태 한번 점검해줘."
|
|
84
|
-
|
|
85
|
-
## 에이전트가 주로 실행하는 명령
|
|
86
|
-
|
|
87
|
-
- 실제 에이전트 실행 기준 명령은 아래 3개입니다.
|
|
88
|
-
- `detect`: 현재 워크스페이스가 lee-spec-kit 프로젝트인지 감지합니다.
|
|
89
|
-
- `context`: 현재 feature 상태와 다음 액션을 읽습니다.
|
|
90
|
-
- `flow`: 기본 workflow auto-run을 진행하고 선택/승인/수동/재개 경계에서 멈춥니다.
|
|
91
|
-
- 사람용 public 명령은 아래 다섯 개입니다.
|
|
92
|
-
- `init`: docs/workflow 구조를 초기화합니다.
|
|
93
|
-
- `idea`: 구현 전 아이디어 문서를 생성합니다.
|
|
94
|
-
- `feature`: 실제 작업 단위를 생성합니다.
|
|
95
|
-
- `context`: 현재 feature 상태와 다음 액션을 읽습니다.
|
|
96
|
-
- `flow`: 기본 workflow auto-run을 진행하고 선택/승인/수동/재개 경계에서 멈춥니다.
|
|
97
|
-
|
|
98
|
-
## 에이전트 킥오프 프롬프트
|
|
99
|
-
|
|
100
|
-
```text
|
|
101
|
-
작업 시작 절차:
|
|
102
|
-
1) npx lee-spec-kit detect --json
|
|
103
|
-
2) isLeeSpecKitProject === true 이면 npx lee-spec-kit context --json-compact 실행
|
|
104
|
-
3) 상태 확인은 context를 read-only probe로 사용하고, 실제 실행/재개는 flow를 기본 엔트리포인트로 사용
|
|
105
|
-
4) approvalRequest.required=true 이면 matchedFeature.currentSubstate* 기반 현재 단계 한 줄 요약을 먼저 짧게 말하고 approvalRequest.userFacingLines를 그대로 사용자에게 제시한 뒤 승인 대기
|
|
106
|
-
5) 승인 전에는 실행하지 말고, 명령 실행은 기본적으로 npx lee-spec-kit flow <featureRef> --approve <LABEL> --execute 사용
|
|
107
|
-
6) isLeeSpecKitProject === false 이면 lee-spec-kit 전용 절차를 건너뛰고 일반 워크플로우로 진행
|
|
108
|
-
```
|
|
83
|
+
- `embedded`: 프로젝트 안에 `docs/`를 함께 둡니다.
|
|
84
|
+
- `standalone`: workspace root 아래에서 docs repo와 project repo를 따로 관리합니다.
|
|
109
85
|
|
|
110
86
|
## Docs
|
|
111
87
|
|
|
112
88
|
- [Public CLI Reference](./docs/reference/public-cli.md)
|
|
113
89
|
- [Agent CLI Reference](./docs/reference/agent-cli.md)
|
|
114
90
|
- [Internal CLI Reference](./docs/reference/internal-cli.md)
|
|
91
|
+
- [Codex Hooks Integration](./docs/reference/codex-hooks.md)
|
|
92
|
+
- [Migration Guide](./docs/reference/migration-codex-hooks.md)
|
|
115
93
|
- [Reference Index](./docs/reference/README.md)
|
|
116
94
|
|
|
117
95
|
## License
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export { LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN, LEE_SPEC_KIT_CODEX_BOOTSTRAP_END, getCodexConfigPath, getCodexHome, hasLeeSpecKitCodexBootstrap, removeLeeSpecKitCodexBootstrap, upsertLeeSpecKitCodexBootstrap } from './chunk-LYFRLOFQ.js';
|
|
3
|
+
import './chunk-7V7RMGEU.js';
|
|
4
|
+
//# sourceMappingURL=bootstrap-G37N6RGB.js.map
|
|
5
|
+
//# sourceMappingURL=bootstrap-G37N6RGB.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":[],"names":[],"mappings":"","file":"bootstrap-G37N6RGB.js"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
|
|
5
|
+
var getFilename = () => fileURLToPath(import.meta.url);
|
|
6
|
+
var getDirname = () => path.dirname(getFilename());
|
|
7
|
+
var __dirname$1 = /* @__PURE__ */ getDirname();
|
|
8
|
+
|
|
9
|
+
export { __dirname$1 as __dirname };
|
|
10
|
+
//# sourceMappingURL=chunk-7V7RMGEU.js.map
|
|
11
|
+
//# sourceMappingURL=chunk-7V7RMGEU.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../node_modules/.pnpm/tsup@8.5.1_postcss@8.5.6_typescript@5.9.3/node_modules/tsup/assets/esm_shims.js"],"names":["__dirname"],"mappings":";;;;AAIA,IAAM,WAAA,GAAc,MAAM,aAAA,CAAc,MAAA,CAAA,IAAA,CAAY,GAAG,CAAA;AACvD,IAAM,UAAA,GAAa,MAAM,IAAA,CAAK,OAAA,CAAQ,aAAa,CAAA;AAE5C,IAAMA,8BAA4B,UAAA","file":"chunk-7V7RMGEU.js","sourcesContent":["// Shim globals in esm bundle\nimport path from 'node:path'\nimport { fileURLToPath } from 'node:url'\n\nconst getFilename = () => fileURLToPath(import.meta.url)\nconst getDirname = () => path.dirname(getFilename())\n\nexport const __dirname = /* @__PURE__ */ getDirname()\nexport const __filename = /* @__PURE__ */ getFilename()\n"]}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { execFileSync } from 'child_process';
|
|
3
|
+
|
|
4
|
+
function runGitOrThrow(args, cwd, options = {}) {
|
|
5
|
+
const encoding = options.encoding ?? "utf-8";
|
|
6
|
+
const stdio = options.stdio ?? "ignore";
|
|
7
|
+
const out = execFileSync("git", args, {
|
|
8
|
+
cwd,
|
|
9
|
+
encoding,
|
|
10
|
+
stdio
|
|
11
|
+
});
|
|
12
|
+
if (typeof out === "string") return out.trim();
|
|
13
|
+
if (Buffer.isBuffer(out)) return out.toString(encoding).trim();
|
|
14
|
+
return "";
|
|
15
|
+
}
|
|
16
|
+
function runGitCapture(args, cwd) {
|
|
17
|
+
try {
|
|
18
|
+
return runGitOrThrow(args, cwd, { stdio: ["ignore", "pipe", "pipe"] });
|
|
19
|
+
} catch {
|
|
20
|
+
return void 0;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export { runGitCapture, runGitOrThrow };
|
|
25
|
+
//# sourceMappingURL=chunk-GR7JQBWF.js.map
|
|
26
|
+
//# sourceMappingURL=chunk-GR7JQBWF.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/utils/git-run.ts"],"names":[],"mappings":";;;AAGO,SAAS,aAAA,CACd,IAAA,EACA,GAAA,EACA,OAAA,GAOI,EAAC,EACG;AACR,EAAA,MAAM,QAAA,GAAW,QAAQ,QAAA,IAAY,OAAA;AACrC,EAAA,MAAM,KAAA,GAAQ,QAAQ,KAAA,IAAS,QAAA;AAC/B,EAAA,MAAM,GAAA,GAAM,YAAA,CAAa,KAAA,EAAO,IAAA,EAAM;AAAA,IACpC,GAAA;AAAA,IACA,QAAA;AAAA,IACA;AAAA,GACD,CAAA;AAED,EAAA,IAAI,OAAO,GAAA,KAAQ,QAAA,EAAU,OAAO,IAAI,IAAA,EAAK;AAC7C,EAAA,IAAI,MAAA,CAAO,SAAS,GAAG,CAAA,SAAU,GAAA,CAAI,QAAA,CAAS,QAAQ,CAAA,CAAE,IAAA,EAAK;AAC7D,EAAA,OAAO,EAAA;AACT;AAEO,SAAS,aAAA,CAAc,MAAgB,GAAA,EAAiC;AAC7E,EAAA,IAAI;AACF,IAAA,OAAO,aAAA,CAAc,IAAA,EAAM,GAAA,EAAK,EAAE,KAAA,EAAO,CAAC,QAAA,EAAU,MAAA,EAAQ,MAAM,CAAA,EAAG,CAAA;AAAA,EACvE,CAAA,CAAA,MAAQ;AACN,IAAA,OAAO,MAAA;AAAA,EACT;AACF","file":"chunk-GR7JQBWF.js","sourcesContent":["/* eslint-disable no-undef */\nimport { execFileSync } from 'child_process';\n\nexport function runGitOrThrow(\n args: string[],\n cwd: string,\n options: {\n encoding?: BufferEncoding;\n stdio?:\n | 'pipe'\n | 'ignore'\n | ['ignore', 'pipe', 'pipe']\n | ['ignore', 'pipe', 'ignore'];\n } = {}\n): string {\n const encoding = options.encoding ?? 'utf-8';\n const stdio = options.stdio ?? 'ignore';\n const out = execFileSync('git', args, {\n cwd,\n encoding,\n stdio,\n }) as string | Buffer | null;\n\n if (typeof out === 'string') return out.trim();\n if (Buffer.isBuffer(out)) return out.toString(encoding).trim();\n return '';\n}\n\nexport function runGitCapture(args: string[], cwd: string): string | undefined {\n try {\n return runGitOrThrow(args, cwd, { stdio: ['ignore', 'pipe', 'pipe'] });\n } catch {\n return undefined;\n }\n}\n"]}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'fs-extra';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
|
|
6
|
+
var LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN = "# lee-spec-kit:codex-bootstrap:begin";
|
|
7
|
+
var LEE_SPEC_KIT_CODEX_BOOTSTRAP_END = "# lee-spec-kit:codex-bootstrap:end";
|
|
8
|
+
var REQUIRED_HOOKS_FLAG_LINE = "codex_hooks = true";
|
|
9
|
+
function renderManagedSegment() {
|
|
10
|
+
return [
|
|
11
|
+
LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN,
|
|
12
|
+
REQUIRED_HOOKS_FLAG_LINE,
|
|
13
|
+
LEE_SPEC_KIT_CODEX_BOOTSTRAP_END
|
|
14
|
+
].join("\n");
|
|
15
|
+
}
|
|
16
|
+
function renderManagedBlock() {
|
|
17
|
+
return `${renderManagedSegment()}
|
|
18
|
+
|
|
19
|
+
`;
|
|
20
|
+
}
|
|
21
|
+
function sanitizeTomlScanContent(content) {
|
|
22
|
+
let result = "";
|
|
23
|
+
let index = 0;
|
|
24
|
+
let state = "normal";
|
|
25
|
+
while (index < content.length) {
|
|
26
|
+
const nextThree = content.slice(index, index + 3);
|
|
27
|
+
const char = content[index] || "";
|
|
28
|
+
if (state === "normal") {
|
|
29
|
+
if (nextThree === '"""') {
|
|
30
|
+
result += " ";
|
|
31
|
+
index += 3;
|
|
32
|
+
state = "multibasic";
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (nextThree === "'''") {
|
|
36
|
+
result += " ";
|
|
37
|
+
index += 3;
|
|
38
|
+
state = "multiliteral";
|
|
39
|
+
continue;
|
|
40
|
+
}
|
|
41
|
+
if (char === '"') {
|
|
42
|
+
result += " ";
|
|
43
|
+
index += 1;
|
|
44
|
+
state = "basic";
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (char === "'") {
|
|
48
|
+
result += " ";
|
|
49
|
+
index += 1;
|
|
50
|
+
state = "literal";
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
result += char;
|
|
54
|
+
index += 1;
|
|
55
|
+
continue;
|
|
56
|
+
}
|
|
57
|
+
if (state === "basic") {
|
|
58
|
+
if (char === "\\") {
|
|
59
|
+
result += " ";
|
|
60
|
+
index += 2;
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
result += char === "\n" ? "\n" : " ";
|
|
64
|
+
index += 1;
|
|
65
|
+
if (char === '"') state = "normal";
|
|
66
|
+
continue;
|
|
67
|
+
}
|
|
68
|
+
if (state === "literal") {
|
|
69
|
+
result += char === "\n" ? "\n" : " ";
|
|
70
|
+
index += 1;
|
|
71
|
+
if (char === "'") state = "normal";
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (state === "multibasic") {
|
|
75
|
+
if (nextThree === '"""') {
|
|
76
|
+
result += " ";
|
|
77
|
+
index += 3;
|
|
78
|
+
state = "normal";
|
|
79
|
+
continue;
|
|
80
|
+
}
|
|
81
|
+
result += char === "\n" ? "\n" : " ";
|
|
82
|
+
index += 1;
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (nextThree === "'''") {
|
|
86
|
+
result += " ";
|
|
87
|
+
index += 3;
|
|
88
|
+
state = "normal";
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
result += char === "\n" ? "\n" : " ";
|
|
92
|
+
index += 1;
|
|
93
|
+
}
|
|
94
|
+
return result;
|
|
95
|
+
}
|
|
96
|
+
function stripManagedBlock(content) {
|
|
97
|
+
const beginIndex = content.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN);
|
|
98
|
+
const endIndex = content.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_END);
|
|
99
|
+
if (beginIndex === -1 || endIndex === -1 || beginIndex > endIndex) {
|
|
100
|
+
return content;
|
|
101
|
+
}
|
|
102
|
+
const replaceEnd = endIndex + LEE_SPEC_KIT_CODEX_BOOTSTRAP_END.length;
|
|
103
|
+
return `${content.slice(0, beginIndex)}${content.slice(replaceEnd)}`;
|
|
104
|
+
}
|
|
105
|
+
function findFirstTableHeaderIndex(content) {
|
|
106
|
+
const sanitized = sanitizeTomlScanContent(content);
|
|
107
|
+
const match = sanitized.match(/^\s*\[[^\]]+\](?:\s*#.*)?$/m);
|
|
108
|
+
return match?.index ?? -1;
|
|
109
|
+
}
|
|
110
|
+
function insertManagedBlockAtTopLevel(content, block) {
|
|
111
|
+
const normalizedBlock = block.trimEnd();
|
|
112
|
+
const firstTableIndex = findFirstTableHeaderIndex(content);
|
|
113
|
+
if (firstTableIndex === -1) {
|
|
114
|
+
let next2 = content;
|
|
115
|
+
if (next2.length > 0 && !next2.endsWith("\n")) next2 += "\n";
|
|
116
|
+
if (next2.trim().length > 0 && !next2.endsWith("\n\n")) next2 += "\n";
|
|
117
|
+
next2 += `${normalizedBlock}
|
|
118
|
+
`;
|
|
119
|
+
return next2;
|
|
120
|
+
}
|
|
121
|
+
const before = content.slice(0, firstTableIndex).trimEnd();
|
|
122
|
+
const after = content.slice(firstTableIndex).replace(/^\n+/, "");
|
|
123
|
+
let next = "";
|
|
124
|
+
if (before.length > 0) {
|
|
125
|
+
next += before;
|
|
126
|
+
if (!next.endsWith("\n\n")) next += next.endsWith("\n") ? "\n" : "\n\n";
|
|
127
|
+
}
|
|
128
|
+
next += `${normalizedBlock}
|
|
129
|
+
|
|
130
|
+
${after}`;
|
|
131
|
+
return next;
|
|
132
|
+
}
|
|
133
|
+
function getCodexHome() {
|
|
134
|
+
const explicit = String(process.env.CODEX_HOME || "").trim();
|
|
135
|
+
if (explicit) return explicit;
|
|
136
|
+
return path.join(os.homedir(), ".codex");
|
|
137
|
+
}
|
|
138
|
+
function getCodexConfigPath() {
|
|
139
|
+
return path.join(getCodexHome(), "config.toml");
|
|
140
|
+
}
|
|
141
|
+
function contentIncludesRequiredBootstrap(content) {
|
|
142
|
+
return hasEnabledTopLevelCodexHooksKey(content) || hasEnabledFeaturesTableCodexHooksKey(content) || hasEnabledFeaturesInlineTableCodexHooksKey(content);
|
|
143
|
+
}
|
|
144
|
+
function hasConflictingTopLevelKey(content, key) {
|
|
145
|
+
const sanitized = sanitizeTomlScanContent(content);
|
|
146
|
+
const escaped = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
147
|
+
const keyPattern = new RegExp(`^\\s*${escaped}\\s*=`, "m");
|
|
148
|
+
return keyPattern.test(sanitized);
|
|
149
|
+
}
|
|
150
|
+
function hasEnabledTopLevelCodexHooksKey(content) {
|
|
151
|
+
return /^\s*codex_hooks\s*=\s*true\b/m.test(
|
|
152
|
+
sanitizeTomlScanContent(content)
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
function hasConflictingFeaturesTableKey(content, key) {
|
|
156
|
+
const lines = sanitizeTomlScanContent(content).split("\n");
|
|
157
|
+
let inFeaturesTable = false;
|
|
158
|
+
for (const rawLine of lines) {
|
|
159
|
+
const line = rawLine.trim();
|
|
160
|
+
if (!line || line.startsWith("#")) continue;
|
|
161
|
+
const tableMatch = line.match(/^\[([^\]]+)\](?:\s*#.*)?$/);
|
|
162
|
+
if (tableMatch) {
|
|
163
|
+
inFeaturesTable = tableMatch[1]?.trim() === "features";
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
if (!inFeaturesTable) continue;
|
|
167
|
+
if (new RegExp(`^${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*=`).test(line)) {
|
|
168
|
+
return true;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return false;
|
|
172
|
+
}
|
|
173
|
+
function hasEnabledFeaturesTableCodexHooksKey(content) {
|
|
174
|
+
const lines = sanitizeTomlScanContent(content).split("\n");
|
|
175
|
+
let inFeaturesTable = false;
|
|
176
|
+
for (const rawLine of lines) {
|
|
177
|
+
const line = rawLine.trim();
|
|
178
|
+
if (!line || line.startsWith("#")) continue;
|
|
179
|
+
const tableMatch = line.match(/^\[([^\]]+)\](?:\s*#.*)?$/);
|
|
180
|
+
if (tableMatch) {
|
|
181
|
+
inFeaturesTable = tableMatch[1]?.trim() === "features";
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (!inFeaturesTable) continue;
|
|
185
|
+
if (/^codex_hooks\s*=\s*true\b/.test(line)) {
|
|
186
|
+
return true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return false;
|
|
190
|
+
}
|
|
191
|
+
function hasConflictingFeaturesInlineTableKey(content, key) {
|
|
192
|
+
const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
193
|
+
const lines = sanitizeTomlScanContent(content).split("\n");
|
|
194
|
+
for (const rawLine of lines) {
|
|
195
|
+
const line = rawLine.trim();
|
|
196
|
+
if (!line || line.startsWith("#")) continue;
|
|
197
|
+
if (!/^features\s*=\s*\{/.test(line)) continue;
|
|
198
|
+
if (new RegExp(`\\b${escapedKey}\\s*=`).test(line)) {
|
|
199
|
+
return true;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return false;
|
|
203
|
+
}
|
|
204
|
+
function hasEnabledFeaturesInlineTableCodexHooksKey(content) {
|
|
205
|
+
const lines = sanitizeTomlScanContent(content).split("\n");
|
|
206
|
+
for (const rawLine of lines) {
|
|
207
|
+
const line = rawLine.trim();
|
|
208
|
+
if (!line || line.startsWith("#")) continue;
|
|
209
|
+
if (!/^features\s*=\s*\{/.test(line)) continue;
|
|
210
|
+
if (/\bcodex_hooks\s*=\s*true\b/.test(line)) {
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
return false;
|
|
215
|
+
}
|
|
216
|
+
async function hasLeeSpecKitCodexBootstrap(filePath = getCodexConfigPath()) {
|
|
217
|
+
if (!await fs.pathExists(filePath)) return false;
|
|
218
|
+
const content = await fs.readFile(filePath, "utf-8");
|
|
219
|
+
return content.includes(LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN) && content.includes(LEE_SPEC_KIT_CODEX_BOOTSTRAP_END) || contentIncludesRequiredBootstrap(content);
|
|
220
|
+
}
|
|
221
|
+
async function upsertLeeSpecKitCodexBootstrap(filePath = getCodexConfigPath()) {
|
|
222
|
+
const block = renderManagedBlock();
|
|
223
|
+
const segment = renderManagedSegment();
|
|
224
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
225
|
+
const exists = await fs.pathExists(filePath);
|
|
226
|
+
if (!exists) {
|
|
227
|
+
await fs.writeFile(filePath, block, "utf-8");
|
|
228
|
+
return { changed: true, action: "created", filePath };
|
|
229
|
+
}
|
|
230
|
+
const current = await fs.readFile(filePath, "utf-8");
|
|
231
|
+
const externalContent = stripManagedBlock(current);
|
|
232
|
+
const beginIndex = current.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN);
|
|
233
|
+
const endIndex = current.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_END);
|
|
234
|
+
if (hasConflictingTopLevelKey(externalContent, "codex_hooks") || hasConflictingTopLevelKey(externalContent, "features.codex_hooks") || hasConflictingFeaturesTableKey(externalContent, "codex_hooks") || hasConflictingFeaturesInlineTableKey(externalContent, "codex_hooks")) {
|
|
235
|
+
throw new Error(
|
|
236
|
+
`Codex config already defines codex_hooks outside lee-spec-kit managed block: ${filePath}`
|
|
237
|
+
);
|
|
238
|
+
}
|
|
239
|
+
if (beginIndex !== -1 && endIndex !== -1 && beginIndex <= endIndex) {
|
|
240
|
+
const replaceEnd = endIndex + LEE_SPEC_KIT_CODEX_BOOTSTRAP_END.length;
|
|
241
|
+
const next2 = `${current.slice(0, beginIndex)}${segment}${current.slice(replaceEnd)}`;
|
|
242
|
+
if (next2 === current) {
|
|
243
|
+
return { changed: false, action: "noop", filePath };
|
|
244
|
+
}
|
|
245
|
+
await fs.writeFile(filePath, next2, "utf-8");
|
|
246
|
+
return { changed: true, action: "updated", filePath };
|
|
247
|
+
}
|
|
248
|
+
if (contentIncludesRequiredBootstrap(current)) {
|
|
249
|
+
return { changed: false, action: "noop", filePath };
|
|
250
|
+
}
|
|
251
|
+
const next = insertManagedBlockAtTopLevel(current, block);
|
|
252
|
+
await fs.writeFile(filePath, next, "utf-8");
|
|
253
|
+
return { changed: true, action: "appended", filePath };
|
|
254
|
+
}
|
|
255
|
+
async function removeLeeSpecKitCodexBootstrap(filePath = getCodexConfigPath()) {
|
|
256
|
+
if (!await fs.pathExists(filePath)) {
|
|
257
|
+
return { changed: false, filePath };
|
|
258
|
+
}
|
|
259
|
+
const current = await fs.readFile(filePath, "utf-8");
|
|
260
|
+
const beginIndex = current.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN);
|
|
261
|
+
const endIndex = current.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_END);
|
|
262
|
+
if (beginIndex === -1 || endIndex === -1 || beginIndex > endIndex) {
|
|
263
|
+
return { changed: false, filePath };
|
|
264
|
+
}
|
|
265
|
+
const replaceEnd = endIndex + LEE_SPEC_KIT_CODEX_BOOTSTRAP_END.length;
|
|
266
|
+
let next = `${current.slice(0, beginIndex)}${current.slice(replaceEnd)}`;
|
|
267
|
+
next = next.replace(/\n{3,}/g, "\n\n").trimEnd();
|
|
268
|
+
if (next.length > 0) next += "\n";
|
|
269
|
+
await fs.writeFile(filePath, next, "utf-8");
|
|
270
|
+
return { changed: true, filePath };
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
export { LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN, LEE_SPEC_KIT_CODEX_BOOTSTRAP_END, getCodexConfigPath, getCodexHome, hasLeeSpecKitCodexBootstrap, removeLeeSpecKitCodexBootstrap, upsertLeeSpecKitCodexBootstrap };
|
|
274
|
+
//# sourceMappingURL=chunk-LYFRLOFQ.js.map
|
|
275
|
+
//# sourceMappingURL=chunk-LYFRLOFQ.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/integrations/codex/bootstrap.ts"],"names":["next"],"mappings":"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","file":"chunk-LYFRLOFQ.js","sourcesContent":["import fs from 'fs-extra';\nimport os from 'node:os';\nimport path from 'node:path';\n\nexport const LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN =\n '# lee-spec-kit:codex-bootstrap:begin';\nexport const LEE_SPEC_KIT_CODEX_BOOTSTRAP_END =\n '# lee-spec-kit:codex-bootstrap:end';\n\nconst REQUIRED_HOOKS_FLAG_LINE = 'codex_hooks = true';\n\nfunction renderManagedSegment(): string {\n return [\n LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN,\n REQUIRED_HOOKS_FLAG_LINE,\n LEE_SPEC_KIT_CODEX_BOOTSTRAP_END,\n ].join('\\n');\n}\n\nfunction renderManagedBlock(): string {\n return `${renderManagedSegment()}\\n\\n`;\n}\n\nfunction sanitizeTomlScanContent(content: string): string {\n let result = '';\n let index = 0;\n let state: 'normal' | 'basic' | 'literal' | 'multibasic' | 'multiliteral' = 'normal';\n\n while (index < content.length) {\n const nextThree = content.slice(index, index + 3);\n const char = content[index] || '';\n\n if (state === 'normal') {\n if (nextThree === '\"\"\"') {\n result += ' ';\n index += 3;\n state = 'multibasic';\n continue;\n }\n if (nextThree === \"'''\") {\n result += ' ';\n index += 3;\n state = 'multiliteral';\n continue;\n }\n if (char === '\"') {\n result += ' ';\n index += 1;\n state = 'basic';\n continue;\n }\n if (char === \"'\") {\n result += ' ';\n index += 1;\n state = 'literal';\n continue;\n }\n result += char;\n index += 1;\n continue;\n }\n\n if (state === 'basic') {\n if (char === '\\\\') {\n result += ' ';\n index += 2;\n continue;\n }\n result += char === '\\n' ? '\\n' : ' ';\n index += 1;\n if (char === '\"') state = 'normal';\n continue;\n }\n\n if (state === 'literal') {\n result += char === '\\n' ? '\\n' : ' ';\n index += 1;\n if (char === \"'\") state = 'normal';\n continue;\n }\n\n if (state === 'multibasic') {\n if (nextThree === '\"\"\"') {\n result += ' ';\n index += 3;\n state = 'normal';\n continue;\n }\n result += char === '\\n' ? '\\n' : ' ';\n index += 1;\n continue;\n }\n\n if (nextThree === \"'''\") {\n result += ' ';\n index += 3;\n state = 'normal';\n continue;\n }\n result += char === '\\n' ? '\\n' : ' ';\n index += 1;\n }\n\n return result;\n}\n\nfunction stripManagedBlock(content: string): string {\n const beginIndex = content.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN);\n const endIndex = content.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_END);\n if (beginIndex === -1 || endIndex === -1 || beginIndex > endIndex) {\n return content;\n }\n const replaceEnd = endIndex + LEE_SPEC_KIT_CODEX_BOOTSTRAP_END.length;\n return `${content.slice(0, beginIndex)}${content.slice(replaceEnd)}`;\n}\n\nfunction findFirstTableHeaderIndex(content: string): number {\n const sanitized = sanitizeTomlScanContent(content);\n const match = sanitized.match(/^\\s*\\[[^\\]]+\\](?:\\s*#.*)?$/m);\n return match?.index ?? -1;\n}\n\nfunction insertManagedBlockAtTopLevel(content: string, block: string): string {\n const normalizedBlock = block.trimEnd();\n const firstTableIndex = findFirstTableHeaderIndex(content);\n\n if (firstTableIndex === -1) {\n let next = content;\n if (next.length > 0 && !next.endsWith('\\n')) next += '\\n';\n if (next.trim().length > 0 && !next.endsWith('\\n\\n')) next += '\\n';\n next += `${normalizedBlock}\\n`;\n return next;\n }\n\n const before = content.slice(0, firstTableIndex).trimEnd();\n const after = content.slice(firstTableIndex).replace(/^\\n+/, '');\n\n let next = '';\n if (before.length > 0) {\n next += before;\n if (!next.endsWith('\\n\\n')) next += next.endsWith('\\n') ? '\\n' : '\\n\\n';\n }\n next += `${normalizedBlock}\\n\\n${after}`;\n return next;\n}\n\nexport function getCodexHome(): string {\n const explicit = String(process.env.CODEX_HOME || '').trim();\n if (explicit) return explicit;\n return path.join(os.homedir(), '.codex');\n}\n\nexport function getCodexConfigPath(): string {\n return path.join(getCodexHome(), 'config.toml');\n}\n\nfunction contentIncludesRequiredBootstrap(content: string): boolean {\n return (\n hasEnabledTopLevelCodexHooksKey(content) ||\n hasEnabledFeaturesTableCodexHooksKey(content) ||\n hasEnabledFeaturesInlineTableCodexHooksKey(content)\n );\n}\n\nfunction hasConflictingTopLevelKey(content: string, key: string): boolean {\n const sanitized = sanitizeTomlScanContent(content);\n const escaped = key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const keyPattern = new RegExp(`^\\\\s*${escaped}\\\\s*=`, 'm');\n return keyPattern.test(sanitized);\n}\n\nfunction hasEnabledTopLevelCodexHooksKey(content: string): boolean {\n return /^\\s*codex_hooks\\s*=\\s*true\\b/m.test(\n sanitizeTomlScanContent(content)\n );\n}\n\nfunction hasConflictingFeaturesTableKey(content: string, key: string): boolean {\n const lines = sanitizeTomlScanContent(content).split('\\n');\n let inFeaturesTable = false;\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n if (!line || line.startsWith('#')) continue;\n\n const tableMatch = line.match(/^\\[([^\\]]+)\\](?:\\s*#.*)?$/);\n if (tableMatch) {\n inFeaturesTable = tableMatch[1]?.trim() === 'features';\n continue;\n }\n\n if (!inFeaturesTable) continue;\n if (new RegExp(`^${key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\\\s*=`).test(line)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction hasEnabledFeaturesTableCodexHooksKey(content: string): boolean {\n const lines = sanitizeTomlScanContent(content).split('\\n');\n let inFeaturesTable = false;\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n if (!line || line.startsWith('#')) continue;\n\n const tableMatch = line.match(/^\\[([^\\]]+)\\](?:\\s*#.*)?$/);\n if (tableMatch) {\n inFeaturesTable = tableMatch[1]?.trim() === 'features';\n continue;\n }\n\n if (!inFeaturesTable) continue;\n if (/^codex_hooks\\s*=\\s*true\\b/.test(line)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction hasConflictingFeaturesInlineTableKey(content: string, key: string): boolean {\n const escapedKey = key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&');\n const lines = sanitizeTomlScanContent(content).split('\\n');\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n if (!line || line.startsWith('#')) continue;\n if (!/^features\\s*=\\s*\\{/.test(line)) continue;\n if (new RegExp(`\\\\b${escapedKey}\\\\s*=`).test(line)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction hasEnabledFeaturesInlineTableCodexHooksKey(content: string): boolean {\n const lines = sanitizeTomlScanContent(content).split('\\n');\n\n for (const rawLine of lines) {\n const line = rawLine.trim();\n if (!line || line.startsWith('#')) continue;\n if (!/^features\\s*=\\s*\\{/.test(line)) continue;\n if (/\\bcodex_hooks\\s*=\\s*true\\b/.test(line)) {\n return true;\n }\n }\n\n return false;\n}\n\nexport async function hasLeeSpecKitCodexBootstrap(\n filePath = getCodexConfigPath()\n): Promise<boolean> {\n if (!(await fs.pathExists(filePath))) return false;\n const content = await fs.readFile(filePath, 'utf-8');\n return (\n (content.includes(LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN) &&\n content.includes(LEE_SPEC_KIT_CODEX_BOOTSTRAP_END)) ||\n contentIncludesRequiredBootstrap(content)\n );\n}\n\nexport async function upsertLeeSpecKitCodexBootstrap(\n filePath = getCodexConfigPath()\n): Promise<{\n changed: boolean;\n action: 'created' | 'appended' | 'updated' | 'noop';\n filePath: string;\n}> {\n const block = renderManagedBlock();\n const segment = renderManagedSegment();\n await fs.ensureDir(path.dirname(filePath));\n\n const exists = await fs.pathExists(filePath);\n if (!exists) {\n await fs.writeFile(filePath, block, 'utf-8');\n return { changed: true, action: 'created', filePath };\n }\n\n const current = await fs.readFile(filePath, 'utf-8');\n const externalContent = stripManagedBlock(current);\n const beginIndex = current.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN);\n const endIndex = current.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_END);\n\n if (\n hasConflictingTopLevelKey(externalContent, 'codex_hooks') ||\n hasConflictingTopLevelKey(externalContent, 'features.codex_hooks') ||\n hasConflictingFeaturesTableKey(externalContent, 'codex_hooks') ||\n hasConflictingFeaturesInlineTableKey(externalContent, 'codex_hooks')\n ) {\n throw new Error(\n `Codex config already defines codex_hooks outside lee-spec-kit managed block: ${filePath}`\n );\n }\n\n if (beginIndex !== -1 && endIndex !== -1 && beginIndex <= endIndex) {\n const replaceEnd = endIndex + LEE_SPEC_KIT_CODEX_BOOTSTRAP_END.length;\n const next = `${current.slice(0, beginIndex)}${segment}${current.slice(replaceEnd)}`;\n if (next === current) {\n return { changed: false, action: 'noop', filePath };\n }\n await fs.writeFile(filePath, next, 'utf-8');\n return { changed: true, action: 'updated', filePath };\n }\n\n if (contentIncludesRequiredBootstrap(current)) {\n return { changed: false, action: 'noop', filePath };\n }\n\n const next = insertManagedBlockAtTopLevel(current, block);\n\n await fs.writeFile(filePath, next, 'utf-8');\n return { changed: true, action: 'appended', filePath };\n}\n\nexport async function removeLeeSpecKitCodexBootstrap(\n filePath = getCodexConfigPath()\n): Promise<{ changed: boolean; filePath: string }> {\n if (!(await fs.pathExists(filePath))) {\n return { changed: false, filePath };\n }\n\n const current = await fs.readFile(filePath, 'utf-8');\n const beginIndex = current.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN);\n const endIndex = current.indexOf(LEE_SPEC_KIT_CODEX_BOOTSTRAP_END);\n if (beginIndex === -1 || endIndex === -1 || beginIndex > endIndex) {\n return { changed: false, filePath };\n }\n\n const replaceEnd = endIndex + LEE_SPEC_KIT_CODEX_BOOTSTRAP_END.length;\n let next = `${current.slice(0, beginIndex)}${current.slice(replaceEnd)}`;\n next = next.replace(/\\n{3,}/g, '\\n\\n').trimEnd();\n if (next.length > 0) next += '\\n';\n await fs.writeFile(filePath, next, 'utf-8');\n return { changed: true, filePath };\n}\n"]}
|