lee-spec-kit 0.8.6 → 0.8.8

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 CHANGED
@@ -75,8 +75,8 @@ The overall approach is influenced by [spec-kit](https://github.com/github/spec-
75
75
  - `docs`
76
76
  - `detect`
77
77
  - `github`
78
- - `integrations codex-hooks`
79
- - `integrations codex`
78
+ - `integrations codex-hooks`: install/remove hooks in the workspace and configured project roots
79
+ - `integrations codex`: install/remove the optional global `[features].hooks` setting
80
80
  - `commit-audit --json`
81
81
  - `workflow-audit --json`
82
82
 
package/README.md CHANGED
@@ -75,8 +75,8 @@ npx lee-spec-kit feature user-auth
75
75
  - `docs`: 내장 agent policy 문서 조회
76
76
  - `detect`: 현재 워크스페이스가 lee-spec-kit 프로젝트인지 감지
77
77
  - `github`: issue/pr 본문 생성 및 검증
78
- - `integrations codex-hooks`: 현재 workspace용 Codex hooks 스캐폴드 생성/제거
79
- - `integrations codex`: 전역 Codex hooks flag 설치/제거
78
+ - `integrations codex-hooks`: 현재 workspace와 configured project root용 Codex hooks 생성/제거
79
+ - `integrations codex`: 선택적 전역 `[features].hooks` 설정 설치/제거
80
80
  - `commit-audit --json`: hooks용 commit-time docs path validator
81
81
  - `workflow-audit --json`: hooks용 docs sync validator
82
82
 
@@ -1,5 +1,5 @@
1
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';
2
+ export { LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN, LEE_SPEC_KIT_CODEX_BOOTSTRAP_END, getCodexConfigPath, getCodexHome, hasLeeSpecKitCodexBootstrap, removeLeeSpecKitCodexBootstrap, upsertLeeSpecKitCodexBootstrap } from './chunk-3AFCPGGS.js';
3
3
  import './chunk-7V7RMGEU.js';
4
- //# sourceMappingURL=bootstrap-G37N6RGB.js.map
5
- //# sourceMappingURL=bootstrap-G37N6RGB.js.map
4
+ //# sourceMappingURL=bootstrap-Q77MTW3Q.js.map
5
+ //# sourceMappingURL=bootstrap-Q77MTW3Q.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":[],"names":[],"mappings":"","file":"bootstrap-G37N6RGB.js"}
1
+ {"version":3,"sources":[],"names":[],"mappings":"","file":"bootstrap-Q77MTW3Q.js"}
@@ -5,7 +5,7 @@ import path from 'path';
5
5
 
6
6
  var LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN = "# lee-spec-kit:codex-bootstrap:begin";
7
7
  var LEE_SPEC_KIT_CODEX_BOOTSTRAP_END = "# lee-spec-kit:codex-bootstrap:end";
8
- var REQUIRED_HOOKS_FLAG_LINE = "codex_hooks = true";
8
+ var REQUIRED_HOOKS_FLAG_LINE = "hooks = true";
9
9
  function renderManagedSegment() {
10
10
  return [
11
11
  LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN,
@@ -13,11 +13,6 @@ function renderManagedSegment() {
13
13
  LEE_SPEC_KIT_CODEX_BOOTSTRAP_END
14
14
  ].join("\n");
15
15
  }
16
- function renderManagedBlock() {
17
- return `${renderManagedSegment()}
18
-
19
- `;
20
- }
21
16
  function sanitizeTomlScanContent(content) {
22
17
  let result = "";
23
18
  let index = 0;
@@ -102,33 +97,22 @@ function stripManagedBlock(content) {
102
97
  const replaceEnd = endIndex + LEE_SPEC_KIT_CODEX_BOOTSTRAP_END.length;
103
98
  return `${content.slice(0, beginIndex)}${content.slice(replaceEnd)}`;
104
99
  }
105
- function findFirstTableHeaderIndex(content) {
100
+ function findFeaturesTableHeaderEnd(content) {
106
101
  const sanitized = sanitizeTomlScanContent(content);
107
- const match = sanitized.match(/^\s*\[[^\]]+\](?:\s*#.*)?$/m);
108
- return match?.index ?? -1;
102
+ const match = /^\s*\[features\](?:\s*#.*)?(?:\r?\n|$)/m.exec(sanitized);
103
+ return match?.index === void 0 ? -1 : match.index + match[0].length;
109
104
  }
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";
105
+ function insertManagedFeaturesBlock(content) {
106
+ const segment = renderManagedSegment();
107
+ const featuresHeaderEnd = findFeaturesTableHeaderEnd(content);
108
+ if (featuresHeaderEnd !== -1) {
109
+ return `${content.slice(0, featuresHeaderEnd)}${segment}
110
+ ${content.slice(featuresHeaderEnd)}`;
127
111
  }
128
- next += `${normalizedBlock}
129
-
130
- ${after}`;
131
- return next;
112
+ const prefix = content.trimEnd();
113
+ return `${prefix}${prefix ? "\n\n" : ""}[features]
114
+ ${segment}
115
+ `;
132
116
  }
133
117
  function getCodexHome() {
134
118
  const explicit = String(process.env.CODEX_HOME || "").trim();
@@ -139,7 +123,7 @@ function getCodexConfigPath() {
139
123
  return path.join(getCodexHome(), "config.toml");
140
124
  }
141
125
  function contentIncludesRequiredBootstrap(content) {
142
- return hasEnabledTopLevelCodexHooksKey(content) || hasEnabledFeaturesTableCodexHooksKey(content) || hasEnabledFeaturesInlineTableCodexHooksKey(content);
126
+ return hasEnabledTopLevelFeaturesHooksKey(content) || hasEnabledFeaturesTableHooksKey(content) || hasEnabledFeaturesInlineTableHooksKey(content) || hasEnabledTopLevelCodexHooksKey(content) || hasEnabledFeaturesTableCodexHooksKey(content) || hasEnabledFeaturesInlineTableCodexHooksKey(content);
143
127
  }
144
128
  function hasConflictingTopLevelKey(content, key) {
145
129
  const sanitized = sanitizeTomlScanContent(content);
@@ -148,7 +132,10 @@ function hasConflictingTopLevelKey(content, key) {
148
132
  return keyPattern.test(sanitized);
149
133
  }
150
134
  function hasEnabledTopLevelCodexHooksKey(content) {
151
- return /^\s*codex_hooks\s*=\s*true\b/m.test(
135
+ return /^\s*codex_hooks\s*=\s*true\b/m.test(sanitizeTomlScanContent(content));
136
+ }
137
+ function hasEnabledTopLevelFeaturesHooksKey(content) {
138
+ return /^\s*features\.hooks\s*=\s*true\b/m.test(
152
139
  sanitizeTomlScanContent(content)
153
140
  );
154
141
  }
@@ -164,7 +151,9 @@ function hasConflictingFeaturesTableKey(content, key) {
164
151
  continue;
165
152
  }
166
153
  if (!inFeaturesTable) continue;
167
- if (new RegExp(`^${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*=`).test(line)) {
154
+ if (new RegExp(`^${key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\s*=`).test(
155
+ line
156
+ )) {
168
157
  return true;
169
158
  }
170
159
  }
@@ -188,6 +177,23 @@ function hasEnabledFeaturesTableCodexHooksKey(content) {
188
177
  }
189
178
  return false;
190
179
  }
180
+ function hasEnabledFeaturesTableHooksKey(content) {
181
+ const lines = sanitizeTomlScanContent(content).split("\n");
182
+ let inFeaturesTable = false;
183
+ for (const rawLine of lines) {
184
+ const line = rawLine.trim();
185
+ if (!line || line.startsWith("#")) continue;
186
+ const tableMatch = line.match(/^\[([^\]]+)\](?:\s*#.*)?$/);
187
+ if (tableMatch) {
188
+ inFeaturesTable = tableMatch[1]?.trim() === "features";
189
+ continue;
190
+ }
191
+ if (inFeaturesTable && /^hooks\s*=\s*true\b/.test(line)) {
192
+ return true;
193
+ }
194
+ }
195
+ return false;
196
+ }
191
197
  function hasConflictingFeaturesInlineTableKey(content, key) {
192
198
  const escapedKey = key.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
193
199
  const lines = sanitizeTomlScanContent(content).split("\n");
@@ -213,44 +219,46 @@ function hasEnabledFeaturesInlineTableCodexHooksKey(content) {
213
219
  }
214
220
  return false;
215
221
  }
222
+ function hasEnabledFeaturesInlineTableHooksKey(content) {
223
+ const lines = sanitizeTomlScanContent(content).split("\n");
224
+ return lines.some((rawLine) => {
225
+ const line = rawLine.trim();
226
+ return !!line && !line.startsWith("#") && /^features\s*=\s*\{/.test(line) && /\bhooks\s*=\s*true\b/.test(line);
227
+ });
228
+ }
216
229
  async function hasLeeSpecKitCodexBootstrap(filePath = getCodexConfigPath()) {
217
230
  if (!await fs.pathExists(filePath)) return false;
218
231
  const content = await fs.readFile(filePath, "utf-8");
219
232
  return content.includes(LEE_SPEC_KIT_CODEX_BOOTSTRAP_BEGIN) && content.includes(LEE_SPEC_KIT_CODEX_BOOTSTRAP_END) || contentIncludesRequiredBootstrap(content);
220
233
  }
221
234
  async function upsertLeeSpecKitCodexBootstrap(filePath = getCodexConfigPath()) {
222
- const block = renderManagedBlock();
223
- const segment = renderManagedSegment();
224
235
  await fs.ensureDir(path.dirname(filePath));
225
236
  const exists = await fs.pathExists(filePath);
226
237
  if (!exists) {
227
- await fs.writeFile(filePath, block, "utf-8");
238
+ await fs.writeFile(filePath, insertManagedFeaturesBlock(""), "utf-8");
228
239
  return { changed: true, action: "created", filePath };
229
240
  }
230
241
  const current = await fs.readFile(filePath, "utf-8");
231
242
  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
+ if (contentIncludesRequiredBootstrap(externalContent)) {
244
+ if (externalContent === current) {
243
245
  return { changed: false, action: "noop", filePath };
244
246
  }
245
- await fs.writeFile(filePath, next2, "utf-8");
247
+ await fs.writeFile(filePath, externalContent.trimEnd() + "\n", "utf-8");
246
248
  return { changed: true, action: "updated", filePath };
247
249
  }
248
- if (contentIncludesRequiredBootstrap(current)) {
249
- return { changed: false, action: "noop", filePath };
250
+ if (hasConflictingTopLevelKey(externalContent, "features.hooks") || hasConflictingFeaturesTableKey(externalContent, "hooks") || hasConflictingFeaturesInlineTableKey(externalContent, "hooks") || hasConflictingTopLevelKey(externalContent, "codex_hooks") || hasConflictingTopLevelKey(externalContent, "features.codex_hooks") || hasConflictingFeaturesTableKey(externalContent, "codex_hooks") || hasConflictingFeaturesInlineTableKey(externalContent, "codex_hooks")) {
251
+ throw new Error(
252
+ `Codex config already defines hooks outside lee-spec-kit managed block: ${filePath}`
253
+ );
250
254
  }
251
- const next = insertManagedBlockAtTopLevel(current, block);
255
+ const next = insertManagedFeaturesBlock(externalContent);
252
256
  await fs.writeFile(filePath, next, "utf-8");
253
- return { changed: true, action: "appended", filePath };
257
+ return {
258
+ changed: true,
259
+ action: externalContent === current ? "appended" : "updated",
260
+ filePath
261
+ };
254
262
  }
255
263
  async function removeLeeSpecKitCodexBootstrap(filePath = getCodexConfigPath()) {
256
264
  if (!await fs.pathExists(filePath)) {
@@ -271,5 +279,5 @@ async function removeLeeSpecKitCodexBootstrap(filePath = getCodexConfigPath()) {
271
279
  }
272
280
 
273
281
  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
282
+ //# sourceMappingURL=chunk-3AFCPGGS.js.map
283
+ //# sourceMappingURL=chunk-3AFCPGGS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/integrations/codex/bootstrap.ts"],"names":[],"mappings":"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","file":"chunk-3AFCPGGS.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 = '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 sanitizeTomlScanContent(content: string): string {\n let result = '';\n let index = 0;\n let state: 'normal' | 'basic' | 'literal' | 'multibasic' | 'multiliteral' =\n '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 findFeaturesTableHeaderEnd(content: string): number {\n const sanitized = sanitizeTomlScanContent(content);\n const match = /^\\s*\\[features\\](?:\\s*#.*)?(?:\\r?\\n|$)/m.exec(sanitized);\n return match?.index === undefined ? -1 : match.index + match[0].length;\n}\n\nfunction insertManagedFeaturesBlock(content: string): string {\n const segment = renderManagedSegment();\n const featuresHeaderEnd = findFeaturesTableHeaderEnd(content);\n if (featuresHeaderEnd !== -1) {\n return `${content.slice(0, featuresHeaderEnd)}${segment}\\n${content.slice(featuresHeaderEnd)}`;\n }\n\n const prefix = content.trimEnd();\n return `${prefix}${prefix ? '\\n\\n' : ''}[features]\\n${segment}\\n`;\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 hasEnabledTopLevelFeaturesHooksKey(content) ||\n hasEnabledFeaturesTableHooksKey(content) ||\n hasEnabledFeaturesInlineTableHooksKey(content) ||\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(sanitizeTomlScanContent(content));\n}\n\nfunction hasEnabledTopLevelFeaturesHooksKey(content: string): boolean {\n return /^\\s*features\\.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 (\n new RegExp(`^${key.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\$&')}\\\\s*=`).test(\n line\n )\n ) {\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 hasEnabledFeaturesTableHooksKey(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 && /^hooks\\s*=\\s*true\\b/.test(line)) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction hasConflictingFeaturesInlineTableKey(\n content: string,\n key: string\n): 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\nfunction hasEnabledFeaturesInlineTableHooksKey(content: string): boolean {\n const lines = sanitizeTomlScanContent(content).split('\\n');\n return lines.some((rawLine) => {\n const line = rawLine.trim();\n return (\n !!line &&\n !line.startsWith('#') &&\n /^features\\s*=\\s*\\{/.test(line) &&\n /\\bhooks\\s*=\\s*true\\b/.test(line)\n );\n });\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 await fs.ensureDir(path.dirname(filePath));\n\n const exists = await fs.pathExists(filePath);\n if (!exists) {\n await fs.writeFile(filePath, insertManagedFeaturesBlock(''), '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\n if (contentIncludesRequiredBootstrap(externalContent)) {\n if (externalContent === current) {\n return { changed: false, action: 'noop', filePath };\n }\n await fs.writeFile(filePath, externalContent.trimEnd() + '\\n', 'utf-8');\n return { changed: true, action: 'updated', filePath };\n }\n\n if (\n hasConflictingTopLevelKey(externalContent, 'features.hooks') ||\n hasConflictingFeaturesTableKey(externalContent, 'hooks') ||\n hasConflictingFeaturesInlineTableKey(externalContent, 'hooks') ||\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 hooks outside lee-spec-kit managed block: ${filePath}`\n );\n }\n\n const next = insertManagedFeaturesBlock(externalContent);\n\n await fs.writeFile(filePath, next, 'utf-8');\n return {\n changed: true,\n action: externalContent === current ? 'appended' : 'updated',\n filePath,\n };\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"]}
File without changes
File without changes
@@ -12,12 +12,14 @@ var MANAGED_HOOK_FILENAMES = [
12
12
  "pre_tool_use_policy.mjs",
13
13
  "stop_workflow_audit.mjs"
14
14
  ];
15
- function getHookScriptContent(fileName) {
15
+ function getHookScriptContent(fileName, repoRoot, workflowRoot) {
16
16
  switch (fileName) {
17
17
  case "_lee_spec_kit_hook_utils.mjs":
18
18
  return `#!/usr/bin/env node
19
19
  import fs from 'node:fs';
20
+ import path from 'node:path';
20
21
  import { spawnSync } from 'node:child_process';
22
+ import { fileURLToPath } from 'node:url';
21
23
 
22
24
  export function readHookInput() {
23
25
  try {
@@ -38,6 +40,18 @@ export function readHookInput() {
38
40
  }
39
41
 
40
42
  const CLI_ENTRYPOINT = ${JSON.stringify(getInstalledCliEntrypoint())};
43
+ const HOOK_REPO_ROOT = path.resolve(
44
+ path.dirname(fileURLToPath(import.meta.url)),
45
+ '..',
46
+ '..'
47
+ );
48
+ const WORKFLOW_ROOT_RELATIVE = ${JSON.stringify(
49
+ path.relative(path.resolve(repoRoot), path.resolve(workflowRoot))
50
+ )};
51
+
52
+ export function getWorkflowCwd() {
53
+ return path.resolve(HOOK_REPO_ROOT, WORKFLOW_ROOT_RELATIVE);
54
+ }
41
55
 
42
56
  export function runLeeSpecKit(args, cwd = process.cwd()) {
43
57
  return spawnSync(process.execPath, [CLI_ENTRYPOINT, ...args], {
@@ -115,7 +129,7 @@ export function printBlock(reason) {
115
129
  `;
116
130
  case "session_start_lee_spec_kit.mjs":
117
131
  return `#!/usr/bin/env node
118
- import { printAdditionalContext, readHookInput, runLeeSpecKitJson } from './_lee_spec_kit_hook_utils.mjs';
132
+ import { getWorkflowCwd, printAdditionalContext, readHookInput, runLeeSpecKitJson } from './_lee_spec_kit_hook_utils.mjs';
119
133
 
120
134
  // Equivalent CLI probe: npx lee-spec-kit detect --json
121
135
  const inputResult = readHookInput();
@@ -123,7 +137,7 @@ if (!inputResult.ok) {
123
137
  process.exit(0);
124
138
  }
125
139
  const input = inputResult.value;
126
- const cwd = typeof input.cwd === 'string' && input.cwd ? input.cwd : process.cwd();
140
+ const cwd = getWorkflowCwd();
127
141
  const detectedResult = runLeeSpecKitJson(['detect', '--json'], cwd);
128
142
  const detected = detectedResult.ok ? detectedResult.data : null;
129
143
 
@@ -167,14 +181,14 @@ if (detected?.status === 'ok' && detected?.isLeeSpecKitProject === true) {
167
181
  `;
168
182
  case "user_prompt_submit_lee_spec_kit.mjs":
169
183
  return `#!/usr/bin/env node
170
- import { printAdditionalContext, readHookInput, runLeeSpecKitJson } from './_lee_spec_kit_hook_utils.mjs';
184
+ import { getWorkflowCwd, printAdditionalContext, readHookInput, runLeeSpecKitJson } from './_lee_spec_kit_hook_utils.mjs';
171
185
 
172
186
  const inputResult = readHookInput();
173
187
  if (!inputResult.ok) {
174
188
  process.exit(0);
175
189
  }
176
190
  const input = inputResult.value;
177
- const cwd = typeof input.cwd === 'string' && input.cwd ? input.cwd : process.cwd();
191
+ const cwd = getWorkflowCwd();
178
192
  const detectedResult = runLeeSpecKitJson(['detect', '--json'], cwd);
179
193
  const detected = detectedResult.ok ? detectedResult.data : null;
180
194
 
@@ -213,7 +227,7 @@ if (detected?.status === 'ok' && detected?.isLeeSpecKitProject === true) {
213
227
  `;
214
228
  case "pre_tool_use_policy.mjs":
215
229
  return `#!/usr/bin/env node
216
- import { printBlock, readHookInput, runLeeSpecKitJson } from './_lee_spec_kit_hook_utils.mjs';
230
+ import { getWorkflowCwd, printBlock, readHookInput, runLeeSpecKitJson } from './_lee_spec_kit_hook_utils.mjs';
217
231
  import fs from 'node:fs';
218
232
  import path from 'node:path';
219
233
 
@@ -232,6 +246,7 @@ if (!inputResult.ok) {
232
246
  }
233
247
  const input = inputResult.value;
234
248
  const cwd = typeof input.cwd === 'string' && input.cwd ? input.cwd : process.cwd();
249
+ const workflowCwd = getWorkflowCwd();
235
250
  const command = String(input?.tool_input?.command || '').trim();
236
251
 
237
252
  function tokenizeShellCommand(value) {
@@ -660,6 +675,88 @@ function normalizeCommandText(value) {
660
675
  return String(value || '').replace(/[ \\t\\r\\n]+/g, ' ').trim();
661
676
  }
662
677
 
678
+ function extractLeeSpecKitFeatureRef(value) {
679
+ const tokens = tokenizeShellCommand(value);
680
+ const cliIndex = tokens.findIndex((token) => /(?:^|[\\\\/])lee-spec-kit(?:\\.cmd|\\.exe)?$/i.test(token));
681
+ if (cliIndex === -1) return null;
682
+ for (let index = cliIndex + 1; index < tokens.length; index += 1) {
683
+ const token = tokens[index];
684
+ if (!token || token === 'npx' || token === '--yes' || token === '-y') continue;
685
+ if (token === 'github' && tokens[index + 1] === 'issue') return tokens[index + 2] || null;
686
+ if (token === 'github' && tokens[index + 1] === 'pr') return tokens[index + 2] || null;
687
+ if (token === 'workflow-stage') {
688
+ const candidate = tokens[index + 1] || null;
689
+ return candidate && !candidate.startsWith('-') ? candidate : null;
690
+ }
691
+ }
692
+ return null;
693
+ }
694
+
695
+ function extractBranchCreateTarget(value) {
696
+ const tokens = tokenizeShellCommand(unwrapShellCommand(value));
697
+ for (let index = 0; index < tokens.length; index += 1) {
698
+ const token = tokens[index] || '';
699
+ if (token === '-b' || token === '-c' || token === '--create') {
700
+ const candidate = tokens[index + 1] || '';
701
+ if (candidate && !candidate.startsWith('-')) return candidate;
702
+ }
703
+ if (token.startsWith('-b') && token.length > 2) return token.slice(2);
704
+ if (token.startsWith('-c') && token.length > 2) return token.slice(2);
705
+ }
706
+
707
+ const worktreeAddIndex = tokens.findIndex((token, index) => {
708
+ return token === 'add' && index > 0 && tokens.slice(0, index).some((item) => normalizeExecutableToken(item) === 'git' || item === 'worktree');
709
+ });
710
+ if (worktreeAddIndex !== -1) {
711
+ const nonOptionArgs = [];
712
+ for (let index = worktreeAddIndex + 1; index < tokens.length; index += 1) {
713
+ const token = tokens[index];
714
+ if (!token || token.startsWith('-')) continue;
715
+ nonOptionArgs.push(token);
716
+ }
717
+ return nonOptionArgs.length >= 2 ? nonOptionArgs[1] : null;
718
+ }
719
+
720
+ const branchNameMatch = String(value).match(/\\bfeat\\/[A-Za-z0-9][A-Za-z0-9._/-]*/);
721
+ return branchNameMatch?.[0] || null;
722
+ }
723
+
724
+ function escapeRegExp(value) {
725
+ return String(value || '').replace(/[\\^$.*+?()[\\]{}|]/g, '\\\\$&');
726
+ }
727
+
728
+ function readFeatureRefFromTasksBranch(docsDir, branchName) {
729
+ if (!docsDir || !branchName) return null;
730
+ const featuresRoot = path.join(docsDir, 'features');
731
+ const stack = [featuresRoot];
732
+ while (stack.length > 0) {
733
+ const current = stack.pop();
734
+ let entries = [];
735
+ try {
736
+ entries = fs.readdirSync(current, { withFileTypes: true });
737
+ } catch {
738
+ continue;
739
+ }
740
+ for (const entry of entries) {
741
+ const entryPath = path.join(current, entry.name);
742
+ if (entry.isDirectory()) {
743
+ stack.push(entryPath);
744
+ continue;
745
+ }
746
+ if (entry.name !== 'tasks.md') continue;
747
+ try {
748
+ const content = fs.readFileSync(entryPath, 'utf8');
749
+ if (new RegExp('^-\\\\s+\\\\*\\\\*(?:Branch|\uBE0C\uB79C\uCE58)\\\\*\\\\*:\\\\s+\`?' + escapeRegExp(branchName) + '\`?\\\\s*$', 'm').test(content)) {
750
+ return path.basename(path.dirname(entryPath));
751
+ }
752
+ } catch {
753
+ // Ignore unreadable feature docs and keep scanning.
754
+ }
755
+ }
756
+ }
757
+ return null;
758
+ }
759
+
663
760
  function hasUnsupportedGitTargetOptions(value) {
664
761
  const unwrappedValue = unwrapShellCommand(value);
665
762
  const tokens = tokenizeShellCommand(unwrappedValue);
@@ -786,17 +883,12 @@ if (isAlwaysBlockedGhOperation) {
786
883
  process.exit(0);
787
884
  }
788
885
 
789
- if (hasUnsupportedShellWrappedDangerousCommand) {
790
- printBlock('lee-spec-kit hooks do not support this shell wrapper for git or gh commands. Re-run the command from a supported shell or the target repo root instead.');
791
- process.exit(0);
792
- }
793
-
794
886
  if (hasUnsupportedGitTarget || hasGitTargetEnvOverride) {
795
887
  printBlock('Git commands using --git-dir, --work-tree, GIT_DIR, or GIT_WORK_TREE are not supported by lee-spec-kit hooks. Re-run the command from the target repo root instead.');
796
888
  process.exit(0);
797
889
  }
798
890
 
799
- const detectedResult = runLeeSpecKitJson(['detect', '--json'], cwd);
891
+ const detectedResult = runLeeSpecKitJson(['detect', '--json'], workflowCwd);
800
892
  if (!detectedResult.ok) {
801
893
  printBlock('lee-spec-kit detection failed inside the Codex hook. Fix the local CLI or hook setup before continuing.');
802
894
  process.exit(0);
@@ -833,8 +925,18 @@ const isPotentialMergeCleanupCommand =
833
925
  command.includes('branch -D') ||
834
926
  command.includes('push origin --delete')
835
927
  );
836
- if (stageBoundAction || isPotentialMergeCleanupCommand) {
837
- const stageResult = runLeeSpecKitJson(['workflow-stage', '--json'], cwd);
928
+ const commandFeatureRef =
929
+ extractLeeSpecKitFeatureRef(command) ||
930
+ readFeatureRefFromTasksBranch(docsDir, extractBranchCreateTarget(command));
931
+ if (hasUnsupportedShellWrappedDangerousCommand && !commandFeatureRef) {
932
+ 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.');
933
+ process.exit(0);
934
+ }
935
+ if (stageBoundAction || isPotentialMergeCleanupCommand || hasUnsupportedShellWrappedDangerousCommand) {
936
+ const stageArgs = commandFeatureRef
937
+ ? ['workflow-stage', commandFeatureRef, '--json']
938
+ : ['workflow-stage', '--json'];
939
+ const stageResult = runLeeSpecKitJson(stageArgs, workflowCwd);
838
940
  if (!stageResult.ok) {
839
941
  printBlock('lee-spec-kit workflow-stage failed inside the Codex hook. Resolve the workflow stage before running this stage-bound command.');
840
942
  process.exit(0);
@@ -844,7 +946,17 @@ if (stageBoundAction || isPotentialMergeCleanupCommand) {
844
946
  printBlock('Resolve feature selection and workflow stage before running this stage-bound command.');
845
947
  process.exit(0);
846
948
  }
847
- if (stageBoundAction && stage?.nextAction?.category !== stageBoundAction) {
949
+ const isExactNextActionCommand =
950
+ normalizeCommandText(stage?.nextAction?.command) === normalizeCommandText(command);
951
+ if (hasUnsupportedShellWrappedDangerousCommand && !isExactNextActionCommand) {
952
+ 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.');
953
+ process.exit(0);
954
+ }
955
+ if (
956
+ stageBoundAction &&
957
+ stage?.nextAction?.category !== stageBoundAction &&
958
+ !isExactNextActionCommand
959
+ ) {
848
960
  printBlock(
849
961
  \`Current workflow stage is \${stage?.stage || 'unknown'} and only \${stage?.nextAction?.category || 'the current nextAction'} is allowed next. Do not jump ahead to \${stageBoundAction}.\`
850
962
  );
@@ -855,10 +967,13 @@ if (stageBoundAction || isPotentialMergeCleanupCommand) {
855
967
  const isExactMergeCleanupCommand =
856
968
  stage?.nextAction?.category === 'merge_cleanup' &&
857
969
  normalizeCommandText(stage?.nextAction?.command) === normalizeCommandText(command);
970
+ const isExactNextActionCommand =
971
+ normalizeCommandText(stage?.nextAction?.command) === normalizeCommandText(command);
858
972
 
859
973
  if (
860
974
  path.resolve(gitCommandCwd) !== path.resolve(cwd) &&
861
975
  !isGitCommit &&
976
+ !isExactNextActionCommand &&
862
977
  !isExactMergeCleanupCommand &&
863
978
  !(stageBoundAction === 'branch_create' && (isGitCreateBranch || isGitWorktreeAdd))
864
979
  ) {
@@ -872,7 +987,7 @@ if (isGitCommit) {
872
987
  if (commitMessage) {
873
988
  commitAuditArgs.push('--message', commitMessage);
874
989
  }
875
- const commitAuditResult = runLeeSpecKitJson(commitAuditArgs, cwd);
990
+ const commitAuditResult = runLeeSpecKitJson(commitAuditArgs, workflowCwd);
876
991
  if (!commitAuditResult.ok) {
877
992
  printBlock('lee-spec-kit commit-audit failed inside the Codex hook. Resolve the docs guardrail failure before committing.');
878
993
  process.exit(0);
@@ -892,7 +1007,7 @@ if (isGitCommit) {
892
1007
  }
893
1008
  }
894
1009
 
895
- const auditResult = runLeeSpecKitJson(['workflow-audit', '--json'], cwd);
1010
+ const auditResult = runLeeSpecKitJson(['workflow-audit', '--json'], workflowCwd);
896
1011
  if (!auditResult.ok) {
897
1012
  printBlock('lee-spec-kit workflow-audit failed inside the Codex hook. Resolve the docs sync guardrail failure before continuing.');
898
1013
  process.exit(0);
@@ -908,7 +1023,7 @@ if (!(audit?.status === 'ok' || audit?.status === 'skipped')) {
908
1023
  `;
909
1024
  case "stop_workflow_audit.mjs":
910
1025
  return `#!/usr/bin/env node
911
- import { printBlock, readHookInput, runLeeSpecKitJson } from './_lee_spec_kit_hook_utils.mjs';
1026
+ import { getWorkflowCwd, printBlock, readHookInput, runLeeSpecKitJson } from './_lee_spec_kit_hook_utils.mjs';
912
1027
 
913
1028
  // Equivalent CLI probe: npx lee-spec-kit workflow-audit --json
914
1029
  const inputResult = readHookInput();
@@ -922,7 +1037,7 @@ if (input?.stop_hook_active === true) {
922
1037
  process.exit(0);
923
1038
  }
924
1039
 
925
- const cwd = typeof input.cwd === 'string' && input.cwd ? input.cwd : process.cwd();
1040
+ const cwd = getWorkflowCwd();
926
1041
  const detectedResult = runLeeSpecKitJson(['detect', '--json'], cwd);
927
1042
  if (!detectedResult.ok) {
928
1043
  printBlock('lee-spec-kit detection failed inside the stop hook. Resolve the local CLI or hook setup before stopping.');
@@ -965,7 +1080,9 @@ function escapeRegExp(value) {
965
1080
  return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
966
1081
  }
967
1082
  function getPortableHookCommandSuffix(fileName) {
968
- const relativeHookPath = normalizePathSlashes(getManagedHookRelativePath(fileName));
1083
+ const relativeHookPath = normalizePathSlashes(
1084
+ getManagedHookRelativePath(fileName)
1085
+ );
969
1086
  const loaderSource = [
970
1087
  "(async () => {",
971
1088
  " const fs = require('node:fs');",
@@ -995,7 +1112,9 @@ function getPortableHookCommandSuffix(fileName) {
995
1112
  function isManagedCommand(command) {
996
1113
  const normalized = normalizePathSlashes(command).trim();
997
1114
  return MANAGED_HOOK_FILENAMES.some((fileName) => {
998
- const currentCommand = normalizePathSlashes(toPortableHookCommand(fileName)).trim();
1115
+ const currentCommand = normalizePathSlashes(
1116
+ toPortableHookCommand(fileName)
1117
+ ).trim();
999
1118
  const portableSuffix = normalizePathSlashes(
1000
1119
  getPortableHookCommandSuffix(fileName)
1001
1120
  ).trim();
@@ -1018,7 +1137,7 @@ function getManagedHooksConfig() {
1018
1137
  return {
1019
1138
  SessionStart: [
1020
1139
  {
1021
- matcher: "startup|resume",
1140
+ matcher: "startup|resume|clear|compact",
1022
1141
  hooks: [
1023
1142
  {
1024
1143
  type: "command",
@@ -1103,7 +1222,12 @@ function removeManagedGroups(current) {
1103
1222
  const nextHooks = {
1104
1223
  ...current.hooks && typeof current.hooks === "object" ? current.hooks : {}
1105
1224
  };
1106
- for (const eventName of ["SessionStart", "UserPromptSubmit", "PreToolUse", "Stop"]) {
1225
+ for (const eventName of [
1226
+ "SessionStart",
1227
+ "UserPromptSubmit",
1228
+ "PreToolUse",
1229
+ "Stop"
1230
+ ]) {
1107
1231
  const pruned = pruneManagedGroups(
1108
1232
  Array.isArray(nextHooks[eventName]) ? nextHooks[eventName] : void 0
1109
1233
  );
@@ -1130,16 +1254,20 @@ function getRepoHooksDir(repoRoot = process.cwd()) {
1130
1254
  function getRepoHooksConfigPath(repoRoot = process.cwd()) {
1131
1255
  return path.join(getRepoCodexDir(repoRoot), "hooks.json");
1132
1256
  }
1133
- async function upsertLeeSpecKitCodexHooks(repoRoot = process.cwd()) {
1257
+ async function upsertLeeSpecKitCodexHooks(repoRoot = process.cwd(), workflowRoot = repoRoot) {
1134
1258
  const hooksDir = getRepoHooksDir(repoRoot);
1135
1259
  const hooksJsonPath = getRepoHooksConfigPath(repoRoot);
1136
1260
  await fs.ensureDir(hooksDir);
1137
1261
  for (const fileName of MANAGED_HOOK_FILENAMES) {
1138
1262
  const targetPath = path.join(hooksDir, fileName);
1139
- await fs.writeFile(targetPath, getHookScriptContent(fileName), {
1140
- encoding: "utf-8",
1141
- mode: 493
1142
- });
1263
+ await fs.writeFile(
1264
+ targetPath,
1265
+ getHookScriptContent(fileName, repoRoot, workflowRoot),
1266
+ {
1267
+ encoding: "utf-8",
1268
+ mode: 493
1269
+ }
1270
+ );
1143
1271
  }
1144
1272
  const managedHooks = getManagedHooksConfig();
1145
1273
  const exists = await fs.pathExists(hooksJsonPath);
@@ -1186,5 +1314,5 @@ async function removeLeeSpecKitCodexHooks(repoRoot = process.cwd()) {
1186
1314
  }
1187
1315
 
1188
1316
  export { getRepoCodexDir, getRepoHooksConfigPath, getRepoHooksDir, removeLeeSpecKitCodexHooks, resolveCodexHooksRepoRoot, upsertLeeSpecKitCodexHooks };
1189
- //# sourceMappingURL=hooks-43P4YKHY.js.map
1190
- //# sourceMappingURL=hooks-43P4YKHY.js.map
1317
+ //# sourceMappingURL=hooks-373Z6JG2.js.map
1318
+ //# sourceMappingURL=hooks-373Z6JG2.js.map