lee-spec-kit 0.9.8 → 0.9.9
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 +11 -0
- package/README.md +11 -0
- package/dist/{hooks-67CU3GZC.js → hooks-4LYFAIA7.js} +163 -7
- package/dist/hooks-4LYFAIA7.js.map +1 -0
- package/dist/index.js +2636 -660
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/templates/en/common/README.md +18 -0
- package/templates/en/common/agents/agents.md +22 -5
- package/templates/en/common/agents/skills/execute-task.md +1 -1
- package/templates/en/common/features/README.md +5 -1
- package/templates/en/common/features/feature-base/plan.md +21 -0
- package/templates/en/common/features/feature-base/tasks.md +10 -1
- package/templates/ko/common/README.md +18 -0
- package/templates/ko/common/agents/agents.md +22 -5
- package/templates/ko/common/agents/skills/execute-task.md +1 -1
- package/templates/ko/common/features/README.md +5 -1
- package/templates/ko/common/features/feature-base/plan.md +21 -0
- package/templates/ko/common/features/feature-base/tasks.md +10 -1
- package/dist/hooks-67CU3GZC.js.map +0 -1
package/package.json
CHANGED
|
@@ -54,6 +54,18 @@ npx lee-spec-kit docs get agents --json
|
|
|
54
54
|
|
|
55
55
|
Keep product roadmaps in `prd/`, but manage implementation sequencing and work plans in the active Feature's `plan.md` and `tasks.md`. The `designs/` directory is reserved for UX and visual design, not technical design.
|
|
56
56
|
|
|
57
|
+
### Knowledge Architecture and authority
|
|
58
|
+
|
|
59
|
+
1. Feature SDD (`spec.md`, `plan.md`, `tasks.md`, `decisions.md`) is normative for requirements, scope, decisions, and acceptance.
|
|
60
|
+
2. Human-owned PRD, architecture, onboarding, and operations documents are the curated project-wide current state.
|
|
61
|
+
3. `openwiki/` is a derived onboarding and code-navigation layer. Verify its claims against tracked source, tests, schemas, and curated docs.
|
|
62
|
+
|
|
63
|
+
Every Plan must complete `Curated Documentation Impact`, including explicit `NONE` decisions. Every `UPDATE` or `ADD` target must be attached to at least one task through its `Docs` list and committed with the active Feature scope before completion. This propagation rule applies whether OpenWiki is enabled or not.
|
|
64
|
+
|
|
65
|
+
When `experimental.openwiki` is `true`, task checkpoints are followed by required Knowledge setup/sync/commit stages and a Feature review. Missing or `false` adds none of those stages. Use `lee-spec-kit knowledge sync`; never hand-edit generated pages or invoke OpenWiki directly from the workflow.
|
|
66
|
+
|
|
67
|
+
OpenWiki is an external agent, not a sandboxed renderer. Enable it only for trusted repositories in an appropriately isolated runtime, and keep local or ignored secrets outside its accessible environment.
|
|
68
|
+
|
|
57
69
|
---
|
|
58
70
|
|
|
59
71
|
## SSOT Relationship (PRD / Ideas / Features)
|
|
@@ -124,6 +136,8 @@ Non-interactive setup exposes the same choices through `--task-agent`,
|
|
|
124
136
|
Existing projects can change the same settings with `lee-spec-kit config
|
|
125
137
|
--interactive` or the corresponding non-interactive flags.
|
|
126
138
|
|
|
139
|
+
The OpenWiki experiment is controlled separately with the single `--openwiki true|false` option.
|
|
140
|
+
|
|
127
141
|
- Used by `lee-spec-kit feature`, `config`, `update`, `detect`, and workflow validators to resolve docs location / project type / language.
|
|
128
142
|
- `docsRepo`, `pushDocs`, `docsRemote` are metadata for the CLI-managed **Docs Push policy** (the CLI does not auto-push).
|
|
129
143
|
|
|
@@ -136,6 +150,7 @@ Existing projects can change the same settings with `lee-spec-kit config
|
|
|
136
150
|
- `docsRepo` ("embedded" | "standalone"): How docs are managed
|
|
137
151
|
- `pushDocs` (boolean, optional): Only written when `docsRepo: "standalone"` (whether to push to remote)
|
|
138
152
|
- `docsRemote` (string, optional): Only written when `pushDocs: true` (remote repo URL)
|
|
153
|
+
- `experimental.openwiki` (boolean): one switch for the complete required OpenWiki Knowledge flow; missing/`false` disables it, and `true` requires Node.js 22+, OpenWiki `>=0.5.0 <1.0.0`, Knowledge sync/receipt/commit, and Feature review
|
|
139
154
|
- `workflow.agentExecution.task` (object): task implementation delegation settings
|
|
140
155
|
- `enabled`: delegates each `task_execute` action to a subagent; new projects default to `true`, while projects created before this setting existed keep it disabled until explicitly enabled
|
|
141
156
|
- `type`: currently only `"subagent"` is supported
|
|
@@ -184,6 +199,9 @@ Existing projects can change the same settings with `lee-spec-kit config
|
|
|
184
199
|
"lang": "en",
|
|
185
200
|
"createdAt": "{{date}}",
|
|
186
201
|
"docsRepo": "embedded",
|
|
202
|
+
"experimental": {
|
|
203
|
+
"openwiki": false
|
|
204
|
+
},
|
|
187
205
|
"workflow": {
|
|
188
206
|
"mode": "local",
|
|
189
207
|
"agentAutomationConfigured": true,
|
|
@@ -5,6 +5,12 @@ This document defines workflow policy, not a custom runtime loop.
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## Codex Lifecycle Scope
|
|
9
|
+
|
|
10
|
+
- <!-- lee-spec-kit:delegation-context-v1 -->
|
|
11
|
+
- Detection, built-in-doc startup, active Feature resolution, and `workflow-stage` are primary-agent responsibilities.
|
|
12
|
+
- A delegated subagent identified by Codex `SubagentStart` skips the primary-agent bootstrap and `workflow-stage`. It follows the exact `delegationContext` and `workerContract` supplied by the primary agent, reads `requiredDocuments`, uses `referenceDocuments` only under their stated conditions, and asks the parent before expanding an insufficient scope.
|
|
13
|
+
|
|
8
14
|
## Detection Gate
|
|
9
15
|
|
|
10
16
|
- Run `npx lee-spec-kit detect --json` first.
|
|
@@ -19,7 +25,8 @@ This document defines workflow policy, not a custom runtime loop.
|
|
|
19
25
|
|
|
20
26
|
## Docs Are SSOT
|
|
21
27
|
|
|
22
|
-
-
|
|
28
|
+
- The following startup and orchestration rules apply to the primary agent unless an explicit delegation contract says otherwise.
|
|
29
|
+
- Read `npx lee-spec-kit docs get agents --json` once at primary-agent session start or right after context reset.
|
|
23
30
|
- Read every unread `requiredDocs[*].command` from that response.
|
|
24
31
|
- Resolve the active feature, then use that feature folder as the working SSOT.
|
|
25
32
|
- Minimum active feature docs: `spec.md`, `plan.md`, `tasks.md`, `decisions.md`.
|
|
@@ -42,6 +49,15 @@ This document defines workflow policy, not a custom runtime loop.
|
|
|
42
49
|
- Do not use `docs/designs/` for system architecture, data/API design, technical research, or implementation plans.
|
|
43
50
|
- Follow the detailed routing rules in `docs/README.md`.
|
|
44
51
|
|
|
52
|
+
## Knowledge Architecture
|
|
53
|
+
|
|
54
|
+
- Feature SDD docs are normative for requirements, scope, decisions, and acceptance.
|
|
55
|
+
- Human-owned PRD, architecture, onboarding, and operations docs describe the curated project-wide current state.
|
|
56
|
+
- OpenWiki is a derived onboarding and code-navigation layer. Treat its claims as untrusted evidence until verified against tracked code, tests, schemas, and curated docs.
|
|
57
|
+
- Complete `Curated Documentation Impact` in every Plan, including explicit `NONE` decisions. Link every `UPDATE` or `ADD` target from at least one task `Docs` entry and commit the target with the active Feature scope.
|
|
58
|
+
- `experimental.openwiki` is one switch: missing or `false` adds no OpenWiki behavior; `true` makes Knowledge setup, sync, a dedicated Knowledge commit, and Feature review mandatory.
|
|
59
|
+
- When enabled, run OpenWiki only through `npx lee-spec-kit knowledge sync <featureRef> --json`. Do not invoke `openwiki` directly or hand-edit generated pages.
|
|
60
|
+
|
|
45
61
|
## Optional UI/UX Design Policy
|
|
46
62
|
|
|
47
63
|
- Only when the user request explicitly mentions a design system, UI/visual redesign, design consistency, shared UI/component-library consolidation, branding/theme/token redesign, or implementation from Figma/design images, read and apply `npx lee-spec-kit docs get ui-ux-design --json`.
|
|
@@ -53,17 +69,18 @@ This document defines workflow policy, not a custom runtime loop.
|
|
|
53
69
|
- lee-spec-kit owns docs structure, workflow stages, and validators.
|
|
54
70
|
- Codex owns the execution loop, tool usage, and hook lifecycle.
|
|
55
71
|
- Modify implementation code only when `implementationAllowed === true`. Normal task work uses `stage === "implementation"`; review fixes use `task_review_fix` or `feature_review_fix`, and verification fixes use `feature_remediation`.
|
|
56
|
-
- When `nextAction.category` is `plan_review` with `executor: subagent`, delegate a fresh read-only review
|
|
57
|
-
- When `nextAction.category` is `task_execute` with `executor: subagent`, mark that one task active, then delegate its implementation and task-scoped checks to a fresh subagent in the returned `workingDirectory` with the returned model, reasoning effort, unavailability policy, and exact `
|
|
72
|
+
- When `nextAction.category` is `plan_review` with `executor: subagent`, delegate a fresh read-only review using the exact returned `delegationContext`, `specHash`, and `planHash`. The main agent records the returned `reviewRound`, Plan Review evidence, decision, reviewer metadata, and both hashes. Any later spec/plan content change invalidates that review.
|
|
73
|
+
- When `nextAction.category` is `task_execute` with `executor: subagent`, mark that one task active, then delegate its implementation and task-scoped checks to a fresh subagent in the returned `workingDirectory` with the returned model, reasoning effort, unavailability policy, exact `workerContract`, and exact `delegationContext`. Do not reconstruct, omit, or broaden that context. No named execution skill is required.
|
|
58
74
|
- The implementation worker executes directly, follows the approved Verification Contract, and does not add unplanned durable tests. It must not run `workflow-stage` or spawn another subagent. It may edit project code and run scoped checks, but it must not edit lee-spec-kit docs, change task state, commit, request approvals, or perform remote/destructive actions. The main agent inspects the result and owns docs synchronization, task transitions, commits, and workflow continuation; official hooks block commits while `task_execute` remains active.
|
|
59
|
-
- When `nextAction.category` is `task_review` with `executor: subagent`, delegate a fresh read-only review
|
|
60
|
-
- When `nextAction.category` is `pre_pr_review` with `executor: subagent`, run a fresh read-only Feature review using the returned model, reasoning effort, `reviewRound`, and SHA/tree range. Do not select or require a named review skill.
|
|
75
|
+
- When `nextAction.category` is `task_review` with `executor: subagent`, delegate a fresh read-only review using the exact returned `delegationContext`, task ID, and SHA/tree range, then record the returned `reviewRound`.
|
|
76
|
+
- When `nextAction.category` is `pre_pr_review` with `executor: subagent`, run a fresh read-only Feature review using the exact returned `delegationContext`, model, reasoning effort, `reviewRound`, and SHA/tree range. Do not select or require a named review skill.
|
|
61
77
|
- Review subagents return findings without modifying code. The main agent remediates findings and records reviewer metadata, reviewed scope, evidence, decision, and exact hash/SHA/tree target metadata.
|
|
62
78
|
- After delegating to a subagent, wait until it returns a terminal outcome: completed, explicit failure, cancellation, or an approval/user-input request that requires action.
|
|
63
79
|
- While the subagent remains running, use repeated bounded waits, preferably longer waits. A bounded wait that returns no update, a lack of status messages, or a lack of file changes means only that the subagent is still pending; none is evidence of failure or stalled work. Read-only review subagents are expected not to modify files.
|
|
64
80
|
- Do not interrupt, replace, or abandon a running subagent solely because it has been quiet or has not changed files. Stop it only after an explicit user request, a terminal failure/cancellation, or an unrecoverable runtime status.
|
|
65
81
|
- `workflow.agentReview.maxRounds` is the maximum number of fresh reviews for each Plan/task/Feature gate. A `changes_requested` decision on the final allowed review is remediated once, but the changed target is not reviewed again; preserve remaining findings and the post-review target change as residual risks and automatically complete the gate without asking for a user review-approval token. For example, `maxRounds=1` means review round 1, remediate once, then continue with no round 2. A `blocked` decision never auto-completes.
|
|
66
82
|
- Treat spec/plan/tasks approval, issue creation, and branch creation as hard gates before implementation.
|
|
83
|
+
- Follow `knowledge_setup`, `knowledge_sync`, and `knowledge_commit` exactly when returned. Commit only the verified Knowledge surface with the exact returned subject before Feature review.
|
|
67
84
|
- In standalone mode, do not hand-write `git worktree add`; run the exact `nextAction.command` from `workflow-stage` so the managed workspace path, stale directory cleanup, and `.env`/`.env.*` copy step stay consistent.
|
|
68
85
|
- In local mode, do not stop after implementation approval. Follow the exact `local verify`, `local merge`, and `local cleanup` commands returned by `workflow-stage` until verified integration and cleanup produce `done`. A `feature_remediation` stage explicitly permits fixes in the Feature worktree.
|
|
69
86
|
- In a `local-ff` or `local-squash` workflow, keep implementation approval and local merge approval distinct when `local_merge` is required: the first accepts the implementation, and the second authorizes the configured integration strategy, post-merge checks, and local cleanup.
|
|
@@ -13,7 +13,7 @@ Use the active feature folder as the execution SSOT.
|
|
|
13
13
|
- continue the single `[DOING]` task, or
|
|
14
14
|
- move the next highest-priority `[TODO]` task to `[DOING]`
|
|
15
15
|
- Work one task at a time. Do not batch-complete multiple tasks in one pass.
|
|
16
|
-
- When `nextAction.executor === "subagent"`, delegate that task's implementation and task-scoped verification to a fresh subagent in `nextAction.workingDirectory` using the returned `model`, `reasoningEffort`, `onUnavailable`, and exact `
|
|
16
|
+
- When `nextAction.executor === "subagent"`, delegate that task's implementation and task-scoped verification to a fresh subagent in `nextAction.workingDirectory` using the returned `model`, `reasoningEffort`, `onUnavailable`, exact `workerContract`, and exact `delegationContext`. Do not reconstruct, omit, or broaden that context. If a requested model is unavailable, `inherit` means retry with the current model inherited and `error` means stop and report the failure.
|
|
17
17
|
- The implementation worker executes the assigned task directly. It must not run `workflow-stage`, delegate again, edit lee-spec-kit docs, change task state, commit, request approval, or perform remote/destructive actions. It may edit project code and run task-scoped checks only.
|
|
18
18
|
- After delegation, the main agent waits for the worker's terminal outcome. While it remains running, use repeated bounded waits, preferably longer waits; a wait with no update, no status message, or no file change is not evidence of failure or stalled work.
|
|
19
19
|
- Do not interrupt, replace, or abandon the running worker solely because it has been quiet or has not changed files. Stop only after an explicit user request, a terminal failure/cancellation, or an unrecoverable runtime status.
|
|
@@ -57,13 +57,17 @@ npx lee-spec-kit workflow-stage <feature-ref> --json
|
|
|
57
57
|
|
|
58
58
|
Use the returned `stage`, `nextAction`, and `implementationAllowed` values as the current workflow state.
|
|
59
59
|
|
|
60
|
+
Before Plan review or approval, complete `Curated Documentation Impact`. Explicit `NONE` values prove the surface was reviewed; every `UPDATE` or `ADD` target must appear under `Docs` in at least one task.
|
|
61
|
+
|
|
62
|
+
When `experimental.openwiki=true`, all completed task checkpoints advance through required `knowledge_setup`, `knowledge_sync`, and `knowledge_commit` stages before Feature review. The Feature reviewer receives `openwiki/index.md`, the verified receipt, and every Plan-declared curated target; SDD and curated project docs remain authoritative. The Knowledge surface includes a final lee-spec-kit protection block in `.openwikiignore`.
|
|
63
|
+
|
|
60
64
|
With Plan review enabled, planning follows `plan Review → fresh read-only Plan review → plan approval`. The review is bound to the returned `specHash` and `planHash`; changing either document's content invalidates the prior evidence. The reviewer checks the Verification Contract and test decisions without editing docs.
|
|
61
65
|
|
|
62
66
|
The three final completion checkboxes in `tasks.md` carry `lee-spec-kit:completion:*` HTML markers. You may customize their visible wording, but preserve the marker on each checkbox line; `workflow-stage` uses the marker as the machine-readable identity and falls back to the legacy canonical wording for older projects.
|
|
63
67
|
|
|
64
68
|
With Feature agent review enabled, local completion is `feature review → implementation_approve → feature_verify → local_merge → local_cleanup → done`. With task review enabled, every task follows `DOING → REVIEW → task review → DONE`. Failed checks enter `feature_remediation` with implementation enabled. `local-ff` moves only the verified Feature SHA; `local-squash` requires the integration tree to match the verified Feature tree. Both require cleanup before `done`. After cleanup, a Feature remains `done` while its recorded integration commit is still an ancestor of the current base, even when later Features advance that base.
|
|
65
69
|
|
|
66
|
-
When `workflow.agentExecution.task.enabled=true`, each `task_execute` action carries the configured subagent model, reasoning effort, stable task ID, implementation working directory,
|
|
70
|
+
When `workflow.agentExecution.task.enabled=true`, each `task_execute` action carries the configured subagent model, reasoning effort, stable task ID, implementation working directory, a machine-readable `workerContract`, and a versioned `delegationContext`. The context includes the exact task block, acceptance criteria, Verification Contract, required Feature documents, and conditional references; pass it unchanged to the worker. The worker executes directly without calling `workflow-stage` or delegating again. It does not add unplanned durable tests, edits project code, and runs task-scoped checks only; the main agent owns docs synchronization, task transitions, commits, approvals, and remote actions. Official hooks block commits until the main agent advances the workflow to `task_commit`.
|
|
67
71
|
|
|
68
72
|
A remediation commit invalidates verification and the prior local-merge confirmation. Refresh review evidence for the changed diff when Pre-PR review is enabled, verify the new tip, and obtain local-merge approval again.
|
|
69
73
|
|
|
@@ -49,6 +49,27 @@ src/
|
|
|
49
49
|
|
|
50
50
|
---
|
|
51
51
|
|
|
52
|
+
## Curated Documentation Impact
|
|
53
|
+
|
|
54
|
+
> Complete this assessment even when every decision is `NONE`. `NONE` means the surface was reviewed and no human-owned project document needs to change. Generated OpenWiki synchronization is evaluated separately.
|
|
55
|
+
|
|
56
|
+
- **Assessment**: Pending
|
|
57
|
+
- Values: Pending | Complete
|
|
58
|
+
- **Product requirements**: -
|
|
59
|
+
- Values: NONE | UPDATE | ADD
|
|
60
|
+
- **System architecture**: -
|
|
61
|
+
- Values: NONE | UPDATE | ADD
|
|
62
|
+
- **Onboarding entrypoint**: -
|
|
63
|
+
- Values: NONE | UPDATE | ADD
|
|
64
|
+
- **Operational/runtime contract**: -
|
|
65
|
+
- Values: NONE | UPDATE | ADD
|
|
66
|
+
- **Reason**: -
|
|
67
|
+
- **Targets**: -
|
|
68
|
+
- Use comma-separated `docs:<path>` and `project:<path>` targets when any decision is UPDATE or ADD.
|
|
69
|
+
- Every target must be linked from a task `Docs` list and committed with the active Feature scope before Knowledge sync or Feature review.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
52
73
|
## Verification Contract
|
|
53
74
|
|
|
54
75
|
### Change Classification
|
|
@@ -67,6 +67,8 @@
|
|
|
67
67
|
- (verification condition)
|
|
68
68
|
- Checklist:
|
|
69
69
|
- [ ] (subtask)
|
|
70
|
+
- Docs:
|
|
71
|
+
- (use a `docs:<path>` or `project:<path>` target, or omit this section)
|
|
70
72
|
- Review Evidence: -
|
|
71
73
|
- Review Decision: -
|
|
72
74
|
- Review Round: -
|
|
@@ -83,13 +85,20 @@
|
|
|
83
85
|
|
|
84
86
|
> Add tasks below. **At least 1 task is required.**
|
|
85
87
|
> Keep tasks as one ordered list. The list order itself is the execution priority.
|
|
86
|
-
> Prefer `npx lee-spec-kit task add <feature-ref> --title "..." --ref NON-PRD --acceptance "..." --check "..."` for appending new tasks.
|
|
88
|
+
> Prefer `npx lee-spec-kit task add <feature-ref> --title "..." --ref NON-PRD --acceptance "..." --check "..." --doc "docs:prd/system-architecture.md"` for appending new tasks.
|
|
87
89
|
> To add a new task, append a complete task block below the last existing task. Use an existing PRD key such as `PRD-FR-001` or `PRD-SCOPE-V1-DESKTOP-EDITOR`, or `[NON-PRD]` for internal work.
|
|
88
90
|
> Do not leave placeholder `Acceptance` / `Checklist` content in place; implementation should not start until those items are concrete.
|
|
89
91
|
> If you must edit manually, append it below the last existing task block in `Task List` instead of inserting it near the current task or right before `Completion Criteria`.
|
|
90
92
|
|
|
91
93
|
---
|
|
92
94
|
|
|
95
|
+
## Knowledge Sync
|
|
96
|
+
|
|
97
|
+
- **Policy**: Derived from `.lee-spec-kit.json` `experimental.openwiki`
|
|
98
|
+
- **Receipt**: `.lee-spec-kit/openwiki-sync.json`
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
93
102
|
## Completion Criteria
|
|
94
103
|
|
|
95
104
|
> ⚠️ This is a **final verification checklist**. Only check after you actually verified.
|
|
@@ -54,6 +54,18 @@ npx lee-spec-kit docs get agents --json
|
|
|
54
54
|
|
|
55
55
|
제품 로드맵은 `prd/`에 두지만 구현 순서와 작업 계획은 활성 Feature의 `plan.md`와 `tasks.md`에서 관리합니다. `designs/`는 UX와 시각 디자인 전용이며 기술 설계 문서를 두지 않습니다.
|
|
56
56
|
|
|
57
|
+
### Knowledge Architecture와 권한
|
|
58
|
+
|
|
59
|
+
1. Feature SDD(`spec.md`, `plan.md`, `tasks.md`, `decisions.md`)는 요구사항, 범위, 결정, 인수 조건의 규범입니다.
|
|
60
|
+
2. 사람이 관리하는 PRD, 아키텍처, 온보딩, 운영 문서는 프로젝트 전체의 curated 현재 상태입니다.
|
|
61
|
+
3. `openwiki/`는 파생된 온보딩·코드 탐색 계층입니다. 그 내용은 tracked 소스, 테스트, 스키마, curated 문서로 다시 검증합니다.
|
|
62
|
+
|
|
63
|
+
모든 Plan은 명시적인 `NONE`까지 포함해 `Curated Documentation Impact`를 완료해야 합니다. 모든 `UPDATE` 또는 `ADD` 대상은 하나 이상의 task `Docs` 목록에 연결하고 완료 전에 활성 Feature scope로 커밋합니다. 이 전파 규칙은 OpenWiki 활성화 여부와 무관하게 적용됩니다.
|
|
64
|
+
|
|
65
|
+
`experimental.openwiki`가 `true`이면 task checkpoint 뒤에 Knowledge 준비·동기화·커밋 stage와 Feature 리뷰가 필수로 붙습니다. 누락 또는 `false`면 이 stage들은 전혀 추가되지 않습니다. `lee-spec-kit knowledge sync`만 사용하고 생성 페이지를 손으로 수정하거나 workflow에서 OpenWiki를 직접 호출하지 않습니다.
|
|
66
|
+
|
|
67
|
+
OpenWiki는 sandboxed renderer가 아니라 외부 에이전트입니다. 신뢰할 수 있는 저장소와 적절히 격리한 실행 환경에서만 활성화하고, 로컬·ignored secret이 접근 가능한 환경에 남지 않도록 관리합니다.
|
|
68
|
+
|
|
57
69
|
---
|
|
58
70
|
|
|
59
71
|
## SSOT 관계 (PRD / Ideas / Features)
|
|
@@ -123,6 +135,8 @@ Local 통합 방식을 직접 선택할 수 있습니다. 비대화형 실행에
|
|
|
123
135
|
기존 프로젝트도 `lee-spec-kit config --interactive` 또는 같은 config 플래그로
|
|
124
136
|
이 설정을 변경할 수 있습니다.
|
|
125
137
|
|
|
138
|
+
OpenWiki 실험 기능은 별도의 단일 옵션 `--openwiki true|false`로 제어합니다.
|
|
139
|
+
|
|
126
140
|
- `lee-spec-kit feature`, `config`, `update`, `detect`, workflow validator에서 문서 위치/프로젝트 타입/언어를 해석하는 용도로 사용됩니다.
|
|
127
141
|
- `docsRepo`, `pushDocs`, `docsRemote`는 CLI 관리 **Docs Push 정책**을 위한 메타데이터입니다. (자동 push는 하지 않습니다)
|
|
128
142
|
|
|
@@ -135,6 +149,7 @@ Local 통합 방식을 직접 선택할 수 있습니다. 비대화형 실행에
|
|
|
135
149
|
- `docsRepo` ("embedded" | "standalone"): Docs 관리 방식
|
|
136
150
|
- `pushDocs` (boolean, optional): `docsRepo: "standalone"`일 때만 생성 (원격 push 여부)
|
|
137
151
|
- `docsRemote` (string, optional): `pushDocs: true`일 때만 생성 (원격 레포 URL)
|
|
152
|
+
- `experimental.openwiki` (boolean): 완전한 필수 OpenWiki Knowledge 흐름을 켜는 단일 스위치. 누락/`false`면 비활성화되고, `true`면 Node.js 22+, OpenWiki `>=0.5.0 <1.0.0`, Knowledge 동기화·receipt·커밋·Feature 리뷰를 요구
|
|
138
153
|
- `workflow.agentExecution.task` (object): 태스크 구현 위임 설정
|
|
139
154
|
- `enabled`: 각 `task_execute`를 서브에이전트에게 위임할지 여부. 새 프로젝트의 기본값은 `true`이며, 이 설정이 생기기 전 프로젝트는 명시적으로 켜기 전까지 꺼진 상태를 유지
|
|
140
155
|
- `type`: 현재 `"subagent"`만 지원
|
|
@@ -183,6 +198,9 @@ Local 통합 방식을 직접 선택할 수 있습니다. 비대화형 실행에
|
|
|
183
198
|
"lang": "ko",
|
|
184
199
|
"createdAt": "{{date}}",
|
|
185
200
|
"docsRepo": "embedded",
|
|
201
|
+
"experimental": {
|
|
202
|
+
"openwiki": false
|
|
203
|
+
},
|
|
186
204
|
"workflow": {
|
|
187
205
|
"mode": "local",
|
|
188
206
|
"agentAutomationConfigured": true,
|
|
@@ -5,6 +5,12 @@
|
|
|
5
5
|
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
+
## Codex lifecycle 범위
|
|
9
|
+
|
|
10
|
+
- <!-- lee-spec-kit:delegation-context-v1 -->
|
|
11
|
+
- 감지, built-in 문서 부트스트랩, 활성 Feature 결정, `workflow-stage`는 메인 에이전트의 책임입니다.
|
|
12
|
+
- Codex `SubagentStart`가 식별한 위임 서브에이전트는 메인 에이전트 부트스트랩과 `workflow-stage`를 생략합니다. 메인 에이전트가 전달한 정확한 `delegationContext`와 `workerContract`를 따르고, `requiredDocuments`를 읽으며, `referenceDocuments`는 명시된 조건에서만 사용하고, 범위가 부족하면 확장 전에 메인 에이전트에 요청합니다.
|
|
13
|
+
|
|
8
14
|
## 감지 게이트
|
|
9
15
|
|
|
10
16
|
- 항상 먼저 `npx lee-spec-kit detect --json`를 실행합니다.
|
|
@@ -19,7 +25,8 @@
|
|
|
19
25
|
|
|
20
26
|
## 문서가 SSOT
|
|
21
27
|
|
|
22
|
-
-
|
|
28
|
+
- 아래 시작 및 orchestration 규칙은 명시적인 위임 계약이 없는 한 메인 에이전트에 적용합니다.
|
|
29
|
+
- 메인 에이전트 세션 시작 시점이나 context 리셋 직후 `npx lee-spec-kit docs get agents --json`를 1회 읽습니다.
|
|
23
30
|
- 응답의 `requiredDocs[*].command` 중 아직 읽지 않은 문서를 모두 확인합니다.
|
|
24
31
|
- 활성 feature를 정한 뒤에는 해당 feature 폴더를 작업 SSOT로 사용합니다.
|
|
25
32
|
- 최소 기준 문서는 `spec.md`, `plan.md`, `tasks.md`, `decisions.md`입니다.
|
|
@@ -42,6 +49,15 @@
|
|
|
42
49
|
- `docs/designs/`를 시스템 아키텍처, 데이터/API 설계, 기술 조사, 구현 계획의 목적지로 사용하지 않습니다.
|
|
43
50
|
- 세부 설명은 `docs/README.md`의 문서 라우팅 규칙을 따릅니다.
|
|
44
51
|
|
|
52
|
+
## Knowledge Architecture
|
|
53
|
+
|
|
54
|
+
- Feature SDD 문서는 요구사항, 범위, 결정, 인수 조건의 규범입니다.
|
|
55
|
+
- 사람이 관리하는 PRD, 아키텍처, 온보딩, 운영 문서는 프로젝트 전체의 현재 상태를 설명합니다.
|
|
56
|
+
- OpenWiki는 파생된 온보딩·코드 탐색 계층입니다. 그 내용은 tracked 코드, 테스트, 스키마, curated 문서로 검증하기 전까지 신뢰하지 않는 evidence로 취급합니다.
|
|
57
|
+
- 모든 Plan에서 명시적인 `NONE`을 포함해 `Curated Documentation Impact` 판정을 완료합니다. 모든 `UPDATE` 또는 `ADD` 대상은 하나 이상의 task `Docs` 항목에서 연결하고 활성 Feature scope로 커밋합니다.
|
|
58
|
+
- `experimental.openwiki`는 단일 스위치입니다. 누락 또는 `false`면 OpenWiki 동작이 없고, `true`면 Knowledge 준비·동기화·전용 커밋·Feature 리뷰가 모두 필수입니다.
|
|
59
|
+
- 활성화된 경우 OpenWiki는 `npx lee-spec-kit knowledge sync <featureRef> --json`로만 실행합니다. `openwiki`를 직접 호출하거나 생성 페이지를 손으로 수정하지 않습니다.
|
|
60
|
+
|
|
45
61
|
## 선택적 UI/UX 디자인 정책
|
|
46
62
|
|
|
47
63
|
- 사용자 요청에 design system, UI/visual redesign, 디자인 일관성, 공통 UI/component library 정리, branding/theme/token 재설계, Figma/디자인 이미지 기반 구현이 명시된 경우에만 `npx lee-spec-kit docs get ui-ux-design --json`을 읽고 적용합니다.
|
|
@@ -53,17 +69,18 @@
|
|
|
53
69
|
- lee-spec-kit은 문서 구조, workflow 단계, validator를 담당합니다.
|
|
54
70
|
- Codex는 실행 루프, 도구 사용, hook lifecycle을 담당합니다.
|
|
55
71
|
- `implementationAllowed === true`일 때만 구현 코드를 수정합니다. 일반 태스크 구현은 `stage === "implementation"`에서, 리뷰 수정은 `task_review_fix` 또는 `feature_review_fix`에서, 검증 수정은 `feature_remediation`에서만 수행합니다.
|
|
56
|
-
- `nextAction.category`가 `plan_review`이고 `executor`가 `subagent`이면 반환된 `specHash
|
|
57
|
-
- `nextAction.category`가 `task_execute`이고 `executor`가 `subagent`이면 해당 태스크 하나를 활성화한 뒤, 반환된 `workingDirectory`에서 fresh 서브에이전트에게 반환된 모델·추론도·unavailability
|
|
72
|
+
- `nextAction.category`가 `plan_review`이고 `executor`가 `subagent`이면 정확히 반환된 `delegationContext`, `specHash`, `planHash`로 fresh 읽기 전용 서브에이전트에게 검수를 위임합니다. 메인 에이전트가 반환된 `reviewRound`, Plan 검수 evidence, decision, reviewer metadata와 두 hash를 기록하며 이후 spec/plan 내용 변경은 기존 검수를 무효화합니다.
|
|
73
|
+
- `nextAction.category`가 `task_execute`이고 `executor`가 `subagent`이면 해당 태스크 하나를 활성화한 뒤, 반환된 `workingDirectory`에서 fresh 서브에이전트에게 반환된 모델·추론도·unavailability 정책, 정확한 `workerContract`, 정확한 `delegationContext`로 구현과 태스크 범위 검증을 위임합니다. 컨텍스트를 재구성하거나 누락하거나 넓히지 않습니다. 특정 이름의 실행 스킬은 요구하지 않습니다.
|
|
58
74
|
- 구현 worker는 승인된 Verification Contract를 따르고 계획되지 않은 영구 테스트를 추가하지 않으며 직접 실행합니다. `workflow-stage`를 호출하거나 다른 서브에이전트를 생성하지 않습니다. 프로젝트 코드 수정과 범위 내 검사는 수행할 수 있지만 lee-spec-kit 문서 수정, 태스크 상태 변경, 커밋, 승인 요청, 원격/파괴적 작업은 하지 않습니다. 메인 에이전트가 결과를 확인하고 문서 동기화, 태스크 전환, 커밋, 후속 workflow를 소유하며, 공식 hook은 `task_execute`가 활성화된 동안 커밋을 차단합니다.
|
|
59
|
-
- `nextAction.category`가 `task_review`이고 `executor`가 `subagent`이면 반환된 task ID
|
|
60
|
-
- `nextAction.category`가 `pre_pr_review`이고 `executor`가 `subagent`이면 반환된 모델·추론도·`reviewRound`·SHA/tree 범위로 fresh context의 읽기 전용 Feature 리뷰를 실행합니다. 리뷰 스킬 이름을 선택하거나 요구하지 않습니다.
|
|
75
|
+
- `nextAction.category`가 `task_review`이고 `executor`가 `subagent`이면 정확히 반환된 `delegationContext`, task ID, SHA/tree 범위로 fresh context의 읽기 전용 리뷰를 위임하고 반환된 `reviewRound`를 기록합니다.
|
|
76
|
+
- `nextAction.category`가 `pre_pr_review`이고 `executor`가 `subagent`이면 정확히 반환된 `delegationContext`, 모델·추론도·`reviewRound`·SHA/tree 범위로 fresh context의 읽기 전용 Feature 리뷰를 실행합니다. 리뷰 스킬 이름을 선택하거나 요구하지 않습니다.
|
|
61
77
|
- 리뷰 서브에이전트는 finding만 반환하고 코드를 수정하지 않습니다. 메인 에이전트가 finding을 반영하고 reviewer metadata, reviewed scope, evidence, decision, 정확한 hash/SHA/tree target metadata를 기록합니다.
|
|
62
78
|
- 서브에이전트에게 위임한 뒤에는 완료, 명시적 실패, 취소, 또는 조치가 필요한 승인·사용자 입력 요청 중 하나의 종결 상태를 반환할 때까지 기다립니다.
|
|
63
79
|
- 서브에이전트가 실행 중이면 가급적 긴 bounded wait를 반복합니다. 한 번의 대기가 새 소식 없이 끝난 것, 상태 메시지가 없는 것, 파일 변경이 없는 것은 아직 실행 중이라는 뜻일 뿐 실패나 정체의 증거가 아닙니다. 읽기 전용 리뷰 서브에이전트는 파일을 변경하지 않는 것이 정상입니다.
|
|
64
80
|
- 조용하거나 파일을 변경하지 않았다는 이유만으로 실행 중인 서브에이전트를 중단·교체·포기하지 않습니다. 사용자의 명시적 중단 요청, 종결 실패·취소, 또는 복구 불가능한 런타임 상태가 있을 때만 중단합니다.
|
|
65
81
|
- `workflow.agentReview.maxRounds`는 Plan/task/Feature 게이트별 fresh 리뷰의 최대 실행 횟수입니다. 마지막 허용 리뷰가 `changes_requested`이면 지적을 한 번 반영하지만 변경된 target을 다시 리뷰하지 않으며, 남은 finding과 리뷰 이후 target 변경을 잔여 위험으로 보존하고 사용자 리뷰 승인 토큰 없이 게이트를 자동 완료합니다. 예를 들어 `maxRounds=1`이면 Round 1 리뷰와 지적 반영 후 Round 2 없이 계속합니다. `blocked` 결정은 자동 완료하지 않습니다.
|
|
66
82
|
- spec / plan / tasks 승인, issue 생성, branch 생성은 구현 전 하드 게이트로 취급합니다.
|
|
83
|
+
- `knowledge_setup`, `knowledge_sync`, `knowledge_commit`이 반환되면 그대로 수행합니다. Feature 리뷰 전에 검증된 Knowledge surface만 반환된 정확한 subject로 커밋합니다.
|
|
67
84
|
- standalone 모드에서는 `git worktree add`를 직접 만들지 말고 `workflow-stage`의 정확한 `nextAction.command`를 실행해 managed workspace 경로, stale 디렉터리 정리, `.env`/`.env.*` 복사 단계가 일관되게 유지되도록 합니다.
|
|
68
85
|
- local 모드에서는 구현 승인 직후 종료하지 않습니다. `workflow-stage`가 반환하는 정확한 `local verify`, `local merge`, `local cleanup` 명령을 따라 검증·통합·정리가 확인되어 `done`이 될 때까지 진행합니다. `feature_remediation` 단계에서는 Feature worktree 수정이 명시적으로 허용됩니다.
|
|
69
86
|
- `local-ff` 또는 `local-squash` workflow에서 `local_merge` 승인이 필요하면 구현 승인과 local merge 승인을 구분합니다. 첫 번째 승인은 구현 결과를 수락하고, 두 번째 승인은 설정된 통합 전략, post-merge 검사, local cleanup을 허가합니다.
|
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
- 이미 `[DOING]`인 태스크가 하나 있으면 그것을 이어서 수행하고
|
|
14
14
|
- 없으면 가장 우선순위가 높은 `[TODO]` 태스크를 `[DOING]`으로 바꿉니다
|
|
15
15
|
- 한 번에 하나의 태스크만 진행합니다.
|
|
16
|
-
- `nextAction.executor === "subagent"`이면 반환된 `workingDirectory`에서 해당 태스크의 구현과 태스크 범위 검증을 fresh 서브에이전트에게 반환된 `model`, `reasoningEffort`, `onUnavailable`
|
|
16
|
+
- `nextAction.executor === "subagent"`이면 반환된 `workingDirectory`에서 해당 태스크의 구현과 태스크 범위 검증을 fresh 서브에이전트에게 반환된 `model`, `reasoningEffort`, `onUnavailable` 설정, 정확한 `workerContract`, 정확한 `delegationContext`로 위임합니다. 컨텍스트를 재구성하거나 누락하거나 넓히지 않습니다. 지정 모델을 사용할 수 없을 때 `inherit`은 현재 모델을 상속해 다시 위임하고, `error`는 중단 후 실패를 보고한다는 뜻입니다.
|
|
17
17
|
- 구현 worker는 할당된 태스크를 직접 실행합니다. `workflow-stage` 재호출, 재위임, lee-spec-kit 문서 수정, 태스크 상태 변경, 커밋, 승인 요청, 원격/파괴적 작업은 하지 않습니다. 프로젝트 코드 수정과 태스크 범위 검사만 수행합니다.
|
|
18
18
|
- 위임한 뒤 메인 에이전트는 worker의 종결 상태를 기다립니다. worker가 실행 중이면 가급적 긴 bounded wait를 반복하며, 한 번의 대기가 새 소식 없이 끝난 것, 상태 메시지가 없는 것, 파일 변경이 없는 것은 실패나 정체의 증거가 아닙니다.
|
|
19
19
|
- 조용하거나 파일을 변경하지 않았다는 이유만으로 실행 중인 worker를 중단·교체·포기하지 않습니다. 사용자의 명시적 중단 요청, 종결 실패·취소, 또는 복구 불가능한 런타임 상태가 있을 때만 중단합니다.
|
|
@@ -57,13 +57,17 @@ npx lee-spec-kit workflow-stage <feature-ref> --json
|
|
|
57
57
|
|
|
58
58
|
반환되는 `stage`, `nextAction`, `implementationAllowed` 값을 현재 워크플로우 상태로 사용하세요.
|
|
59
59
|
|
|
60
|
+
Plan 검수 또는 승인 전에 `Curated Documentation Impact`를 완료합니다. 명시적인 `NONE`은 해당 영역을 검토했다는 증거이며, 모든 `UPDATE` 또는 `ADD` 대상은 하나 이상의 task `Docs` 아래에 있어야 합니다.
|
|
61
|
+
|
|
62
|
+
`experimental.openwiki=true`이면 완료된 모든 task checkpoint 뒤에 필수 `knowledge_setup`, `knowledge_sync`, `knowledge_commit` stage를 거쳐 Feature 리뷰로 이동합니다. Feature reviewer는 `openwiki/index.md`, 검증 receipt, Plan이 선언한 모든 curated target을 받으며, SDD와 curated 프로젝트 문서가 계속 상위 권한을 가집니다. Knowledge surface에는 `.openwikiignore`의 마지막 lee-spec-kit 보호 블록도 포함됩니다.
|
|
63
|
+
|
|
60
64
|
Plan 검수가 활성화되면 계획 단계는 `plan Review → fresh 읽기 전용 Plan 검수 → plan 승인` 순서로 진행됩니다. 검수는 반환된 `specHash`와 `planHash`에 묶이며 두 문서 중 하나의 내용이 바뀌면 기존 evidence가 무효입니다. reviewer는 문서를 수정하지 않고 Verification Contract와 테스트 결정을 점검합니다.
|
|
61
65
|
|
|
62
66
|
`tasks.md`의 최종 완료 체크박스 3개에는 `lee-spec-kit:completion:*` HTML marker가 있습니다. 사용자에게 보이는 문구는 바꿔도 되지만 각 체크박스 라인의 marker는 유지하세요. `workflow-stage`는 marker를 machine-readable identity로 우선 사용하고, 기존 프로젝트 호환을 위해 marker가 없으면 이전 canonical 문구를 fallback으로 인식합니다.
|
|
63
67
|
|
|
64
68
|
Feature agent review가 활성화된 local workflow의 완료 흐름은 `feature review → implementation_approve → feature_verify → local_merge → local_cleanup → done`입니다. 태스크 리뷰가 활성화되면 각 태스크는 `DOING → REVIEW → task review → DONE`을 거칩니다. 검사 실패 시 구현이 허용된 `feature_remediation`으로 이동합니다. `local-ff`는 검증된 Feature SHA만 옮기고, `local-squash`는 통합 tree가 검증된 Feature tree와 같아야 합니다. 둘 다 cleanup 후에만 `done`입니다. cleanup이 끝난 Feature는 기록된 통합 커밋이 현재 base의 조상으로 남아 있는 한 후속 Feature가 base를 전진시켜도 `done`을 유지합니다.
|
|
65
69
|
|
|
66
|
-
`workflow.agentExecution.task.enabled=true`이면 각 `task_execute`가 설정된 서브에이전트 모델·추론도·안정적인 태스크 ID·구현 작업 경로·machine-readable `workerContract`를 반환합니다. worker는 `workflow-stage`를 재호출하거나 다시 위임하지 않고 직접 실행합니다.
|
|
70
|
+
`workflow.agentExecution.task.enabled=true`이면 각 `task_execute`가 설정된 서브에이전트 모델·추론도·안정적인 태스크 ID·구현 작업 경로·machine-readable `workerContract`와 versioned `delegationContext`를 반환합니다. 이 컨텍스트에는 정확한 태스크 블록, Acceptance, Verification Contract, 필수 Feature 문서와 조건부 참조가 들어 있으며 worker에게 변경 없이 전달합니다. worker는 `workflow-stage`를 재호출하거나 다시 위임하지 않고 직접 실행합니다. 계획되지 않은 영구 테스트를 추가하지 않으며 프로젝트 코드와 태스크 범위 검사만 담당합니다. 메인 에이전트가 문서 동기화, 태스크 전환, 커밋, 승인, 원격 작업을 소유하고 공식 hook은 메인 에이전트가 workflow를 `task_commit`으로 전진시키기 전까지 커밋을 차단합니다.
|
|
67
71
|
|
|
68
72
|
remediation 커밋이 추가되면 기존 검증과 local merge 승인은 무효입니다. Pre-PR review가 활성화되어 있다면 변경된 diff의 review evidence를 갱신하고 새 tip을 검증한 뒤 local merge 승인을 다시 받습니다.
|
|
69
73
|
|
|
@@ -49,6 +49,27 @@ src/
|
|
|
49
49
|
|
|
50
50
|
---
|
|
51
51
|
|
|
52
|
+
## Curated Documentation Impact
|
|
53
|
+
|
|
54
|
+
> 모든 결정이 `NONE`이어도 영향 판정을 완료합니다. `NONE`은 사람이 관리하는 상위 문서를 검토했지만 변경할 필요가 없다는 뜻입니다. 생성형 OpenWiki 동기화는 별도로 판정합니다.
|
|
55
|
+
|
|
56
|
+
- **Assessment**: Pending
|
|
57
|
+
- 값: Pending | Complete
|
|
58
|
+
- **Product requirements**: -
|
|
59
|
+
- 값: NONE | UPDATE | ADD
|
|
60
|
+
- **System architecture**: -
|
|
61
|
+
- 값: NONE | UPDATE | ADD
|
|
62
|
+
- **Onboarding entrypoint**: -
|
|
63
|
+
- 값: NONE | UPDATE | ADD
|
|
64
|
+
- **Operational/runtime contract**: -
|
|
65
|
+
- 값: NONE | UPDATE | ADD
|
|
66
|
+
- **Reason**: -
|
|
67
|
+
- **Targets**: -
|
|
68
|
+
- UPDATE 또는 ADD가 하나라도 있으면 쉼표로 구분한 `docs:<path>`와 `project:<path>` 대상을 기록합니다.
|
|
69
|
+
- 모든 대상은 task `Docs` 목록에 연결하고 Knowledge 동기화 또는 Feature 리뷰 전에 활성 Feature scope로 커밋합니다.
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
52
73
|
## Verification Contract
|
|
53
74
|
|
|
54
75
|
### 변경 분류
|
|
@@ -67,6 +67,8 @@
|
|
|
67
67
|
- (검증 조건)
|
|
68
68
|
- Checklist:
|
|
69
69
|
- [ ] (서브 태스크)
|
|
70
|
+
- Docs:
|
|
71
|
+
- (`docs:<path>` 또는 `project:<path>` 대상을 사용하거나 이 섹션을 생략)
|
|
70
72
|
- Review Evidence: -
|
|
71
73
|
- Review Decision: -
|
|
72
74
|
- Review Round: -
|
|
@@ -83,13 +85,20 @@
|
|
|
83
85
|
|
|
84
86
|
> 아래에 태스크를 추가하세요. **최소 1개가 필요**합니다.
|
|
85
87
|
> 태스크는 하나의 순차 리스트로 유지하고, 위에서 아래 순서 자체를 실행 우선순위로 취급하세요.
|
|
86
|
-
> 새 태스크 append에는 `npx lee-spec-kit task add <feature-ref> --title "..." --ref NON-PRD --acceptance "..." --check "..."` 사용을 우선하세요.
|
|
88
|
+
> 새 태스크 append에는 `npx lee-spec-kit task add <feature-ref> --title "..." --ref NON-PRD --acceptance "..." --check "..." --doc "docs:prd/system-architecture.md"` 사용을 우선하세요.
|
|
87
89
|
> 새 태스크는 마지막 기존 태스크 아래에 완전한 태스크 블록으로 추가하세요. `PRD-FR-001`이나 `PRD-SCOPE-V1-DESKTOP-EDITOR`처럼 이미 정의된 PRD key를 사용하거나, 내부 작업이면 `[NON-PRD]`를 사용합니다.
|
|
88
90
|
> placeholder 상태의 `Acceptance` / `Checklist`를 그대로 두지 마세요. 구체 항목이 아니면 구현을 시작하지 않습니다.
|
|
89
91
|
> 수동 편집이 필요하면 현재 태스크 근처가 아니라 `태스크 목록`의 마지막 기존 태스크 block 아래에만 append 하세요.
|
|
90
92
|
|
|
91
93
|
---
|
|
92
94
|
|
|
95
|
+
## Knowledge Sync
|
|
96
|
+
|
|
97
|
+
- **Policy**: `.lee-spec-kit.json`의 `experimental.openwiki`에서 파생
|
|
98
|
+
- **Receipt**: `.lee-spec-kit/openwiki-sync.json`
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
93
102
|
## 완료 조건
|
|
94
103
|
|
|
95
104
|
> ⚠️ 아래 항목은 **최종 확인 체크리스트**입니다. 실제로 확인/실행한 뒤에만 체크하세요.
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/integrations/codex/hooks.ts"],"names":[],"mappings":"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AAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;AAAA;AAAA;AAAA;AAAA;AAAA;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-67CU3GZC.js","sourcesContent":["import fs from 'fs-extra';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport { runGitCapture } from '../../utils/git-run.js';\n\nconst MANAGED_HOOK_FILENAMES = [\n '_lee_spec_kit_hook_utils.mjs',\n 'session_start_lee_spec_kit.mjs',\n 'user_prompt_submit_lee_spec_kit.mjs',\n 'pre_tool_use_policy.mjs',\n 'stop_workflow_audit.mjs',\n] as const;\n\ntype ManagedHookFileName = (typeof MANAGED_HOOK_FILENAMES)[number];\ntype HookEventName =\n | 'SessionStart'\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 'Start by reading npx lee-spec-kit docs get agents --json and the active feature docs.',\n 'Run npx lee-spec-kit workflow-stage --json before the next stage and only follow its nextAction.',\n 'Keep docs as the SSOT and treat workflow-audit as the end-of-turn sync guard.',\n ];\n if (stageResult.ok && stageResult.data?.status === 'ok') {\n lines.push(\n \\`Current workflow stage: \\${stageResult.data.stage}\\`,\n \\`Next allowed action: \\${stageResult.data.nextAction?.category || 'none'}\\`,\n \\`Approval required: \\${stageResult.data.approvalRequired ? 'yes' : 'no'}\\`,\n \\`Implementation allowed: \\${stageResult.data.implementationAllowed ? 'yes' : 'no'}\\`\n );\n if (stageResult.data.primaryActionLabel && Array.isArray(stageResult.data.actionOptions)) {\n lines.push(\n \\`Primary reply label: \\${stageResult.data.primaryActionLabel}\\`,\n ...stageResult.data.actionOptions.map(\n (option) => \\`Option \\${option.label} -> reply \\${option.reply}: \\${option.summary}\\`\n )\n );\n }\n } else if (stageResult.ok && stageResult.data?.status === 'error') {\n lines.push(\n \\`Workflow stage is unresolved: \\${stageResult.data.reasonCode}\\`,\n 'Resolve feature selection or create/select the target feature before continuing.'\n );\n }\n printAdditionalContext('SessionStart', lines.join('\\\\n'));\n}\n`;\n case 'user_prompt_submit_lee_spec_kit.mjs':\n return `#!/usr/bin/env node\nimport { 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 'Prefer docs get plus feature-local docs as the primary context source.',\n 'Use workflow-stage --json to determine the next allowed stage before implementation.',\n ];\n if (stageResult.ok && stageResult.data?.status === 'ok') {\n lines.push(\n \\`Current workflow stage: \\${stageResult.data.stage}\\`,\n \\`Next allowed action: \\${stageResult.data.nextAction?.category || 'none'}\\`,\n \\`Approval required: \\${stageResult.data.approvalRequired ? 'yes' : 'no'}\\`,\n \\`Implementation allowed: \\${stageResult.data.implementationAllowed ? 'yes' : 'no'}\\`,\n 'Do not jump ahead of the reported nextAction.'\n );\n if (stageResult.data.primaryActionLabel && Array.isArray(stageResult.data.actionOptions)) {\n lines.push(\n 'If labeled action options are present, keep the option labels but ask the user to reply with the exact reply token shown for that option.',\n ...stageResult.data.actionOptions.map(\n (option) => \\`Option \\${option.label} -> reply \\${option.reply}: \\${option.summary}\\`\n )\n );\n }\n } else if (stageResult.ok && stageResult.data?.status === 'error') {\n lines.push(\n \\`Workflow stage is unresolved: \\${stageResult.data.reasonCode}\\`,\n 'Resolve feature selection before attempting implementation.'\n );\n }\n printAdditionalContext('UserPromptSubmit', lines.join('\\\\n'));\n}\n`;\n case 'pre_tool_use_policy.mjs':\n return `#!/usr/bin/env node\nimport { 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 isGhIssueCreate =\n isDangerousGhCommand && /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\s+issue\\\\s+create\\\\b/i.test(command);\nconst isGhPrCreate =\n isDangerousGhCommand && /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\s+pr\\\\s+create\\\\b/i.test(command);\nconst isGhPrMerge =\n isDangerousGhCommand && /\\\\bgh(?:\\\\.cmd|\\\\.exe)?\\\\s+pr\\\\s+merge\\\\b/i.test(command);\nlet stageBoundAction = null;\nif (isGitCreateBranch) {\n stageBoundAction = 'branch_create';\n} else if (isGitWorktreeAdd) {\n stageBoundAction = 'branch_create';\n} else if (isGhIssueCreate || isLeeSpecKitIssueCreate) {\n stageBoundAction = 'issue_create';\n} else if (isGhPrCreate || isLeeSpecKitPrCreate) {\n stageBoundAction = 'pr_create';\n} else if (isGhPrMerge || isLeeSpecKitPrMerge) {\n stageBoundAction = 'pr_merge';\n}\nconst isDangerousCommand =\n isAlwaysBlockedGhOperation ||\n hasUnsupportedShellWrappedDangerousCommand ||\n isGitCommit ||\n isGitPush ||\n isGitCheckout ||\n isGitSwitch ||\n isGitWorktree ||\n isGitRestore ||\n isGitClean ||\n isGitRebase ||\n isGitMerge ||\n isGitCherryPick ||\n isGitRevert ||\n isGitStash ||\n isGitBranchDelete ||\n isGitTagDelete ||\n isGitResetHard ||\n isDangerousGhCommand ||\n isLeeSpecKitIssueCreate ||\n isLeeSpecKitPrCreate ||\n isLeeSpecKitPrMerge;\n\nif (!command || !isDangerousCommand) {\n process.exit(0);\n}\n\nif (isAlwaysBlockedGhOperation) {\n printBlock('Destructive GitHub CLI commands such as repo or release deletion are not supported by lee-spec-kit hooks. Re-run them manually after explicit review.');\n process.exit(0);\n}\n\nif (hasUnsupportedGitTarget || hasGitTargetEnvOverride) {\n printBlock('Git commands using --git-dir, --work-tree, GIT_DIR, or GIT_WORK_TREE are not supported by lee-spec-kit hooks. Re-run the command from the target repo root instead.');\n process.exit(0);\n}\n\nconst detectedResult = runLeeSpecKitJson(['detect', '--json'], 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\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\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 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 '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"]}
|