cc-codeconductor 1.4.2 → 1.5.0

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.
Files changed (186) hide show
  1. package/README.md +37 -12
  2. package/dist/index.js +291 -109
  3. package/docs/generated/cli.md +8 -0
  4. package/package.json +1 -1
  5. package/presets/agy/README.md +1 -1
  6. package/presets/agy/hooks.json +1 -1
  7. package/presets/agy/scripts/invoke-hook.cjs +20 -5
  8. package/presets/agy/settings.json +1 -1
  9. package/presets/agy/skills/api-versioning/SKILL.md +394 -0
  10. package/presets/agy/skills/astro/SKILL.md +318 -0
  11. package/presets/agy/skills/auth-token-inspector/SKILL.md +30 -0
  12. package/presets/agy/skills/cc-pagespeed/SKILL.md +2 -3
  13. package/presets/agy/skills/code-review/SKILL.md +207 -0
  14. package/presets/agy/skills/django-orm/SKILL.md +460 -0
  15. package/presets/agy/skills/django-uv/SKILL.md +405 -0
  16. package/presets/agy/skills/drizzle-schema-architect/SKILL.md +50 -0
  17. package/presets/agy/skills/fastapi-pydantic-strict/SKILL.md +43 -0
  18. package/presets/agy/skills/jpa-nplusone-detector/SKILL.md +45 -0
  19. package/presets/agy/skills/jpa-postgres/SKILL.md +623 -0
  20. package/presets/agy/skills/livewire-alpine-bridge/SKILL.md +35 -0
  21. package/presets/agy/skills/nextjs-typescript/SKILL.md +390 -0
  22. package/presets/agy/skills/python/SKILL.md +611 -0
  23. package/presets/agy/skills/seo-analytics-injector/SKILL.md +43 -0
  24. package/presets/agy/skills/spring-auth-auditor/SKILL.md +29 -0
  25. package/presets/agy/skills/spring-boot-feature/SKILL.md +563 -0
  26. package/presets/agy/skills/spring-boot-testing-strategy/SKILL.md +475 -0
  27. package/presets/agy/skills/tailwind-responsive-auditor/SKILL.md +29 -0
  28. package/presets/agy/skills/tdd-mutation-tester/SKILL.md +27 -0
  29. package/presets/agy/workflows/cc-pagespeed.md +2 -3
  30. package/presets/agy/workflows/cc-review.md +31 -0
  31. package/presets/agy/workflows/cc-security.md +1 -1
  32. package/presets/claude/commands/cc/review.md +33 -0
  33. package/presets/claude/settings.json +2 -2
  34. package/presets/claude/skills/android/SKILL.md +1 -1
  35. package/presets/claude/skills/api-versioning/SKILL.md +1 -1
  36. package/presets/claude/skills/astro/SKILL.md +318 -0
  37. package/presets/claude/skills/auth-token-inspector/SKILL.md +30 -0
  38. package/presets/claude/skills/code-review/SKILL.md +207 -0
  39. package/presets/claude/skills/django-orm/SKILL.md +1 -1
  40. package/presets/claude/skills/django-testing/SKILL.md +1 -1
  41. package/presets/claude/skills/django-uv/SKILL.md +405 -0
  42. package/presets/claude/skills/drizzle-schema-architect/SKILL.md +50 -0
  43. package/presets/claude/skills/fastapi-pydantic-strict/SKILL.md +43 -0
  44. package/presets/claude/skills/jpa-nplusone-detector/SKILL.md +45 -0
  45. package/presets/claude/skills/jpa-postgres/SKILL.md +1 -1
  46. package/presets/claude/skills/livewire-alpine-bridge/SKILL.md +35 -0
  47. package/presets/claude/skills/nextjs-typescript/SKILL.md +390 -0
  48. package/presets/claude/skills/pagespeed-perf/SKILL.md +1 -1
  49. package/presets/claude/skills/python/SKILL.md +1 -1
  50. package/presets/claude/skills/python-django-stack/SKILL.md +1 -1
  51. package/presets/claude/skills/python-fastapi-stack/SKILL.md +1 -1
  52. package/presets/claude/skills/security/SKILL.md +1 -1
  53. package/presets/claude/skills/seo-analytics-injector/SKILL.md +43 -0
  54. package/presets/claude/skills/spring-auth-auditor/SKILL.md +29 -0
  55. package/presets/claude/skills/spring-boot-feature/SKILL.md +1 -1
  56. package/presets/claude/skills/spring-boot-kotlin/SKILL.md +1 -1
  57. package/presets/claude/skills/spring-boot-testing-strategy/SKILL.md +475 -0
  58. package/presets/claude/skills/sqlalchemy/SKILL.md +1 -1
  59. package/presets/claude/skills/tailwind-responsive-auditor/SKILL.md +29 -0
  60. package/presets/claude/skills/tdd-mutation-tester/SKILL.md +27 -0
  61. package/presets/claude/skills/testing-strategy/SKILL.md +1 -1
  62. package/presets/codex/skills/android/SKILL.md +1 -1
  63. package/presets/codex/skills/api-versioning/SKILL.md +1 -1
  64. package/presets/codex/skills/astro/SKILL.md +318 -0
  65. package/presets/codex/skills/auth-token-inspector/SKILL.md +30 -0
  66. package/presets/codex/skills/cc-openspec/SKILL.md +1 -1
  67. package/presets/codex/skills/cc-pagespeed/SKILL.md +2 -3
  68. package/presets/codex/skills/cc-review/SKILL.md +31 -0
  69. package/presets/codex/skills/cc-security/SKILL.md +1 -1
  70. package/presets/codex/skills/code-review/SKILL.md +207 -0
  71. package/presets/codex/skills/django-orm/SKILL.md +1 -1
  72. package/presets/codex/skills/django-testing/SKILL.md +1 -1
  73. package/presets/codex/skills/django-uv/SKILL.md +405 -0
  74. package/presets/codex/skills/drizzle-schema-architect/SKILL.md +50 -0
  75. package/presets/codex/skills/fastapi-pydantic-strict/SKILL.md +43 -0
  76. package/presets/codex/skills/jpa-nplusone-detector/SKILL.md +45 -0
  77. package/presets/codex/skills/jpa-postgres/SKILL.md +1 -1
  78. package/presets/codex/skills/livewire-alpine-bridge/SKILL.md +35 -0
  79. package/presets/codex/skills/nextjs-typescript/SKILL.md +390 -0
  80. package/presets/codex/skills/pagespeed-perf/SKILL.md +1 -1
  81. package/presets/codex/skills/python/SKILL.md +1 -1
  82. package/presets/codex/skills/python-django-stack/SKILL.md +1 -1
  83. package/presets/codex/skills/python-fastapi-stack/SKILL.md +1 -1
  84. package/presets/codex/skills/security-ai-llm/SKILL.md +43 -0
  85. package/presets/codex/skills/security-blue-team/SKILL.md +43 -0
  86. package/presets/codex/skills/security-cloud/SKILL.md +43 -0
  87. package/presets/codex/skills/security-crypto/SKILL.md +43 -0
  88. package/presets/codex/skills/security-exploit-dev/SKILL.md +45 -0
  89. package/presets/codex/skills/security-grc/SKILL.md +43 -0
  90. package/presets/codex/skills/security-incident-response/SKILL.md +45 -0
  91. package/presets/codex/skills/security-log-analysis/SKILL.md +43 -0
  92. package/presets/codex/skills/security-malware-analysis/SKILL.md +44 -0
  93. package/presets/codex/skills/security-mobile/SKILL.md +43 -0
  94. package/presets/codex/skills/security-network/SKILL.md +43 -0
  95. package/presets/codex/skills/security-ot-ics/SKILL.md +43 -0
  96. package/presets/codex/skills/security-recon/SKILL.md +45 -0
  97. package/presets/codex/skills/security-red-team/SKILL.md +44 -0
  98. package/presets/codex/skills/security-reverse-engineering/SKILL.md +44 -0
  99. package/presets/codex/skills/security-soc-automation/SKILL.md +43 -0
  100. package/presets/codex/skills/security-threat-hunting/SKILL.md +43 -0
  101. package/presets/codex/skills/security-vuln-assessment/SKILL.md +45 -0
  102. package/presets/codex/skills/security-web/SKILL.md +44 -0
  103. package/presets/codex/skills/seo-analytics-injector/SKILL.md +43 -0
  104. package/presets/codex/skills/spring-auth-auditor/SKILL.md +29 -0
  105. package/presets/codex/skills/spring-boot-feature/SKILL.md +2 -2
  106. package/presets/codex/skills/spring-boot-kotlin/SKILL.md +1 -1
  107. package/presets/codex/skills/spring-boot-testing-strategy/SKILL.md +475 -0
  108. package/presets/codex/skills/sqlalchemy/SKILL.md +1 -1
  109. package/presets/codex/skills/tailwind-responsive-auditor/SKILL.md +29 -0
  110. package/presets/codex/skills/tdd-mutation-tester/SKILL.md +27 -0
  111. package/presets/codex/skills/testing-strategy/SKILL.md +1 -1
  112. package/presets/cursor/commands/cc/openspec.md +1 -1
  113. package/presets/cursor/commands/cc/pagespeed.md +2 -3
  114. package/presets/cursor/commands/cc/review.md +31 -0
  115. package/presets/cursor/commands/cc/security.md +1 -1
  116. package/presets/cursor/skills/android/SKILL.md +1 -1
  117. package/presets/cursor/skills/api-versioning/SKILL.md +2 -1
  118. package/presets/cursor/skills/astro/SKILL.md +1 -1
  119. package/presets/cursor/skills/auth-token-inspector/SKILL.md +1 -1
  120. package/presets/cursor/skills/code-review/SKILL.md +1 -1
  121. package/presets/cursor/skills/django-orm/SKILL.md +3 -5
  122. package/presets/cursor/skills/django-testing/SKILL.md +1 -1
  123. package/presets/cursor/skills/django-uv/SKILL.md +1 -1
  124. package/presets/cursor/skills/drizzle-schema-architect/SKILL.md +1 -1
  125. package/presets/cursor/skills/fastapi-pydantic-strict/SKILL.md +1 -1
  126. package/presets/cursor/skills/jpa-nplusone-detector/SKILL.md +1 -1
  127. package/presets/cursor/skills/jpa-postgres/SKILL.md +2 -4
  128. package/presets/cursor/skills/livewire-alpine-bridge/SKILL.md +1 -1
  129. package/presets/cursor/skills/nextjs-typescript/SKILL.md +1 -1
  130. package/presets/cursor/skills/pagespeed-perf/SKILL.md +1 -1
  131. package/presets/cursor/skills/python/SKILL.md +6 -7
  132. package/presets/cursor/skills/python-django-stack/SKILL.md +1 -1
  133. package/presets/cursor/skills/python-fastapi-stack/SKILL.md +1 -1
  134. package/presets/cursor/skills/security/SKILL.md +1 -1
  135. package/presets/cursor/skills/seo-analytics-injector/SKILL.md +1 -1
  136. package/presets/cursor/skills/spring-auth-auditor/SKILL.md +1 -1
  137. package/presets/cursor/skills/spring-boot-feature/SKILL.md +2 -4
  138. package/presets/cursor/skills/spring-boot-kotlin/SKILL.md +1 -1
  139. package/presets/cursor/skills/spring-boot-testing-strategy/SKILL.md +1 -1
  140. package/presets/cursor/skills/sqlalchemy/SKILL.md +1 -1
  141. package/presets/cursor/skills/tailwind-responsive-auditor/SKILL.md +1 -1
  142. package/presets/cursor/skills/tdd-mutation-tester/SKILL.md +1 -1
  143. package/presets/gemini/commands/cc/openspec.toml +1 -1
  144. package/presets/gemini/commands/cc/pagespeed.toml +2 -3
  145. package/presets/gemini/commands/cc/review.toml +31 -0
  146. package/presets/gemini/commands/cc/security.toml +1 -1
  147. package/presets/opencode/README.md +45 -52
  148. package/presets/opencode/commands/cc-pagespeed.md +2 -3
  149. package/presets/opencode/commands/cc-review.md +31 -0
  150. package/presets/opencode/commands/cc-security.md +1 -1
  151. package/presets/opencode/opencode.jsonc +1 -1
  152. package/presets/opencode/skills/android/SKILL.md +1 -1
  153. package/presets/opencode/skills/api-versioning/SKILL.md +2 -1
  154. package/presets/opencode/skills/astro/SKILL.md +1 -1
  155. package/presets/opencode/skills/auth-token-inspector/SKILL.md +1 -1
  156. package/presets/opencode/skills/code-review/SKILL.md +1 -1
  157. package/presets/opencode/skills/django-orm/SKILL.md +3 -3
  158. package/presets/opencode/skills/django-testing/SKILL.md +1 -1
  159. package/presets/opencode/skills/django-uv/SKILL.md +1 -1
  160. package/presets/opencode/skills/drizzle-schema-architect/SKILL.md +1 -1
  161. package/presets/opencode/skills/fastapi-pydantic-strict/SKILL.md +1 -1
  162. package/presets/opencode/skills/jpa-nplusone-detector/SKILL.md +1 -1
  163. package/presets/opencode/skills/jpa-postgres/SKILL.md +2 -1
  164. package/presets/opencode/skills/livewire-alpine-bridge/SKILL.md +1 -1
  165. package/presets/opencode/skills/nextjs-typescript/SKILL.md +1 -1
  166. package/presets/opencode/skills/pagespeed-perf/SKILL.md +1 -1
  167. package/presets/opencode/skills/python/SKILL.md +6 -5
  168. package/presets/opencode/skills/python-django-stack/SKILL.md +1 -1
  169. package/presets/opencode/skills/python-fastapi-stack/SKILL.md +1 -1
  170. package/presets/opencode/skills/security/SKILL.md +1 -1
  171. package/presets/opencode/skills/seo-analytics-injector/SKILL.md +1 -1
  172. package/presets/opencode/skills/spring-auth-auditor/SKILL.md +1 -1
  173. package/presets/opencode/skills/spring-boot-feature/SKILL.md +2 -1
  174. package/presets/opencode/skills/spring-boot-kotlin/SKILL.md +1 -1
  175. package/presets/opencode/skills/spring-boot-testing-strategy/SKILL.md +1 -1
  176. package/presets/opencode/skills/sqlalchemy/SKILL.md +1 -1
  177. package/presets/opencode/skills/tailwind-responsive-auditor/SKILL.md +1 -1
  178. package/presets/opencode/skills/tdd-mutation-tester/SKILL.md +1 -1
  179. package/presets/seo-hotel/skills/astro-seo/SKILL.md +1 -1
  180. package/presets/seo-hotel/skills/geo-readiness/SKILL.md +1 -1
  181. package/presets/seo-hotel/skills/off-page/SKILL.md +1 -1
  182. package/presets/seo-hotel/skills/schema-validator/SKILL.md +1 -1
  183. package/presets/seo-hotel/skills/seo-audit/SKILL.md +1 -1
  184. package/presets/shared/invoke-hook.cjs +20 -5
  185. package/src/presets/models/roles.yml +28 -28
  186. package/src/presets/shared-skills.yml +58 -22
package/dist/index.js CHANGED
@@ -72,7 +72,7 @@ var package_default;
72
72
  var init_package = __esm(() => {
73
73
  package_default = {
74
74
  name: "cc-codeconductor",
75
- version: "1.4.2",
75
+ version: "1.5.0",
76
76
  description: "A multi-agent orchestration framework for AI-assisted software engineering workflows.",
77
77
  keywords: [
78
78
  "ai",
@@ -12989,18 +12989,19 @@ var init_path_containment = __esm(() => {
12989
12989
  });
12990
12990
 
12991
12991
  // src/core/presets/complementary-detector.ts
12992
- import { execFileSync } from "node:child_process";
12992
+ import { execFile } from "node:child_process";
12993
12993
  import { existsSync, readFileSync } from "node:fs";
12994
12994
  import { join as join3 } from "node:path";
12995
12995
  import { homedir } from "node:os";
12996
- function detectComplementaryTools() {
12996
+ import { promisify } from "node:util";
12997
+ async function detectComplementaryTools() {
12997
12998
  if (cachedStatus) {
12998
12999
  return cachedStatus;
12999
13000
  }
13000
- const isCmdAvailable = (cmd) => {
13001
+ const isCmdAvailable = async (cmd) => {
13001
13002
  try {
13002
13003
  const binary = process.platform === "win32" ? "where" : "which";
13003
- execFileSync(binary, [cmd], { stdio: "ignore", timeout: 1000 });
13004
+ await execFileAsync(binary, [cmd], { timeout: 1000 });
13004
13005
  return true;
13005
13006
  } catch {
13006
13007
  return false;
@@ -13029,18 +13030,28 @@ function detectComplementaryTools() {
13029
13030
  } catch {}
13030
13031
  return false;
13031
13032
  };
13033
+ const [rtk, codeReviewGraph, tokenSavior, tokenSaviorRecall, engram, gentleAi] = await Promise.all([
13034
+ isCmdAvailable("rtk"),
13035
+ isCmdAvailable("code-review-graph"),
13036
+ isCmdAvailable("token-savior"),
13037
+ isCmdAvailable("token-savior-recall"),
13038
+ isCmdAvailable("engram"),
13039
+ isCmdAvailable("gentle-ai")
13040
+ ]);
13032
13041
  cachedStatus = {
13033
- rtk: isCmdAvailable("rtk"),
13034
- codeReviewGraph: isCmdAvailable("code-review-graph"),
13035
- tokenSavior: isCmdAvailable("token-savior") || isCmdAvailable("token-savior-recall"),
13042
+ rtk,
13043
+ codeReviewGraph,
13044
+ tokenSavior: tokenSavior || tokenSaviorRecall,
13036
13045
  caveman: hasCaveman(),
13037
- engram: isCmdAvailable("engram"),
13038
- gentleAi: isCmdAvailable("gentle-ai")
13046
+ engram,
13047
+ gentleAi
13039
13048
  };
13040
13049
  return cachedStatus;
13041
13050
  }
13042
- var cachedStatus = null;
13043
- var init_complementary_detector = () => {};
13051
+ var execFileAsync, cachedStatus = null;
13052
+ var init_complementary_detector = __esm(() => {
13053
+ execFileAsync = promisify(execFile);
13054
+ });
13044
13055
 
13045
13056
  // src/core/presets/file-copier.ts
13046
13057
  import { lstat as lstat2, readdir, readFile as readFile4, stat } from "node:fs/promises";
@@ -13133,9 +13144,9 @@ function extractAgentRole(filePath) {
13133
13144
  ];
13134
13145
  return knownRoles.includes(name) ? name : null;
13135
13146
  }
13136
- function renderTemplate(content, modelConfig, filePath, locale = "en") {
13147
+ async function renderTemplate(content, modelConfig, filePath, locale = "en") {
13137
13148
  const cleanLocale = locale === "es" || locale === "en" ? locale : "en";
13138
- const tools = detectComplementaryTools();
13149
+ const tools = await detectComplementaryTools();
13139
13150
  let complementaryRules = "";
13140
13151
  if (cleanLocale === "es") {
13141
13152
  if (tools.rtk) {
@@ -13258,7 +13269,7 @@ function substituteToolNames(content, modelConfig) {
13258
13269
  const eol = fmMatch[1];
13259
13270
  const frontmatter = fmMatch[2];
13260
13271
  if (target === "opencode" && modelConfig.permissions) {
13261
- const updatedFrontmatter2 = frontmatter.replace(/^tools:\s*(.+)\r?\n?/m, "");
13272
+ const updatedFrontmatter2 = frontmatter.replace(/^tools:\s*(.+)\r?\n?/m, "").replace(/^(?:temperature|effort):[^\r\n]*\r?\n?/gm, "");
13262
13273
  return content.replace(fmMatch[0], `---${eol}${updatedFrontmatter2}${eol}---`);
13263
13274
  }
13264
13275
  if (!modelConfig.tools)
@@ -13276,7 +13287,7 @@ function substituteToolNames(content, modelConfig) {
13276
13287
  });
13277
13288
  return content.replace(fmMatch[0], `---${eol}${updatedFrontmatter}${eol}---`);
13278
13289
  }
13279
- function injectMcpServers(jsonString, filePath) {
13290
+ async function injectMcpServers(jsonString, filePath) {
13280
13291
  const isAgyMcpConfig = filePath.endsWith("mcp_config.json");
13281
13292
  const isClaudeSettings = filePath.endsWith("settings.json");
13282
13293
  const isOpencodeJsonc = filePath.endsWith("opencode.jsonc") || filePath.endsWith("opencode.json");
@@ -13289,7 +13300,7 @@ function injectMcpServers(jsonString, filePath) {
13289
13300
  cleanJson = jsonString.replace(/\/\*[\s\S]*?\*\/|([^\\:]|^)\/\/.*$/gm, "$1");
13290
13301
  }
13291
13302
  const obj = JSON.parse(cleanJson);
13292
- const tools = detectComplementaryTools();
13303
+ const tools = await detectComplementaryTools();
13293
13304
  const mcpServers = {};
13294
13305
  if (tools.tokenSavior) {
13295
13306
  mcpServers["token-savior-recall"] = {
@@ -13361,8 +13372,8 @@ async function applySingleFile(srcPath, destPath, strategy, force, dryRun, isTem
13361
13372
  } catch (e) {
13362
13373
  return { src: srcPath, dest: destPath, action: "error", error: `Cannot read source: ${e}` };
13363
13374
  }
13364
- let incomingContent = isTemplate && modelConfig ? renderTemplate(content, modelConfig, srcPath, locale) : content;
13365
- incomingContent = injectMcpServers(incomingContent, destPath);
13375
+ let incomingContent = isTemplate && modelConfig ? await renderTemplate(content, modelConfig, srcPath, locale) : content;
13376
+ incomingContent = await injectMcpServers(incomingContent, destPath);
13366
13377
  let finalContent = incomingContent;
13367
13378
  let action = "written";
13368
13379
  if (strategy === "append") {
@@ -14629,7 +14640,7 @@ async function checkUpdates(basePath, isGlobal, includeConflicts = false) {
14629
14640
  let expectedContent;
14630
14641
  try {
14631
14642
  const srcContent = await readFile7(src, "utf-8");
14632
- expectedContent = isTemplate && modelConfig ? renderTemplate(srcContent, modelConfig, src, locale) : srcContent;
14643
+ expectedContent = isTemplate && modelConfig ? await renderTemplate(srcContent, modelConfig, src, locale) : srcContent;
14633
14644
  } catch {
14634
14645
  continue;
14635
14646
  }
@@ -15091,7 +15102,7 @@ async function doctorCommand(options) {
15091
15102
  message: "No product graph yet. Run `codeconductor ingest` to build product memory."
15092
15103
  });
15093
15104
  }
15094
- const compTools = detectComplementaryTools();
15105
+ const compTools = await detectComplementaryTools();
15095
15106
  const toolDetails = [
15096
15107
  { name: "rtk", key: "rtk", label: "RTK", desc: "Run `brew install rtk` to compress command outputs" },
15097
15108
  { name: "code-review-graph", key: "codeReviewGraph", label: "code-review-graph", desc: "Run `pipx install code-review-graph` to navigate symbol graph" },
@@ -18373,13 +18384,13 @@ var init_binary_downloader = __esm(() => {
18373
18384
  });
18374
18385
 
18375
18386
  // src/core/lsp/lsp-installer.ts
18376
- import { execFile } from "node:child_process";
18387
+ import { execFile as execFile2 } from "node:child_process";
18377
18388
  import { access as access6, mkdir as mkdir6, mkdtemp, rename as rename2, rm, writeFile as writeFile5 } from "node:fs/promises";
18378
18389
  import { homedir as homedir8, tmpdir } from "node:os";
18379
18390
  import { join as join9 } from "node:path";
18380
- import { promisify } from "node:util";
18391
+ import { promisify as promisify2 } from "node:util";
18381
18392
  async function assertTarArchiveSafe(archivePath, destRoot) {
18382
- const { stdout } = await execFileAsync("tar", ["-tzf", archivePath], {
18393
+ const { stdout } = await execFileAsync2("tar", ["-tzf", archivePath], {
18383
18394
  timeout: 60000,
18384
18395
  maxBuffer: 10 * 1024 * 1024
18385
18396
  });
@@ -18391,7 +18402,7 @@ async function assertTarArchiveSafe(archivePath, destRoot) {
18391
18402
  }
18392
18403
  async function extractHardenedTar(archivePath, destRoot) {
18393
18404
  await assertTarArchiveSafe(archivePath, destRoot);
18394
- await execFileAsync("tar", ["-xzf", archivePath, "-C", destRoot, ...TAR_EXTRACT_HARDENING_FLAGS], { timeout: 120000 });
18405
+ await execFileAsync2("tar", ["-xzf", archivePath, "-C", destRoot, ...TAR_EXTRACT_HARDENING_FLAGS], { timeout: 120000 });
18395
18406
  }
18396
18407
 
18397
18408
  class LspInstaller {
@@ -18401,7 +18412,7 @@ class LspInstaller {
18401
18412
  }
18402
18413
  async checkInstalled(def) {
18403
18414
  try {
18404
- const { stdout } = await execFileAsync("which", [def.binaryName], { timeout: 5000 });
18415
+ const { stdout } = await execFileAsync2("which", [def.binaryName], { timeout: 5000 });
18405
18416
  const path = stdout.trim();
18406
18417
  if (path) {
18407
18418
  const version = await this.getVersion(def);
@@ -18410,7 +18421,7 @@ class LspInstaller {
18410
18421
  } catch {}
18411
18422
  if (def.packageManager === "npm" && def.npmDetect) {
18412
18423
  try {
18413
- const { stdout } = await execFileAsync("npm", ["list", "-g", def.npmDetect, "--depth=0"], { timeout: 1e4 });
18424
+ const { stdout } = await execFileAsync2("npm", ["list", "-g", def.npmDetect, "--depth=0"], { timeout: 1e4 });
18414
18425
  if (stdout.includes(def.npmDetect)) {
18415
18426
  const version = this.parseVersionFromNpmList(stdout, def.npmDetect);
18416
18427
  return { installed: true, version };
@@ -18419,7 +18430,7 @@ class LspInstaller {
18419
18430
  }
18420
18431
  if (def.packageManager === "pip" && def.pipDetect) {
18421
18432
  try {
18422
- const { stdout } = await execFileAsync("pip", ["show", def.pipDetect], { timeout: 1e4 });
18433
+ const { stdout } = await execFileAsync2("pip", ["show", def.pipDetect], { timeout: 1e4 });
18423
18434
  if (stdout.includes("Version:")) {
18424
18435
  const version = this.parseVersionFromPipShow(stdout);
18425
18436
  return { installed: true, version };
@@ -18483,7 +18494,7 @@ class LspInstaller {
18483
18494
  }
18484
18495
  async getVersion(def) {
18485
18496
  try {
18486
- const { stdout } = await execFileAsync(def.binaryName, [def.versionFlag], { timeout: 5000 });
18497
+ const { stdout } = await execFileAsync2(def.binaryName, [def.versionFlag], { timeout: 5000 });
18487
18498
  return stdout.trim().split(`
18488
18499
  `)[0];
18489
18500
  } catch {
@@ -18501,7 +18512,7 @@ class LspInstaller {
18501
18512
  async installNpm(def) {
18502
18513
  assertPinnedPackage(def);
18503
18514
  try {
18504
- await execFileAsync("npm", ["install", "-g", def.package], { timeout: 120000 });
18515
+ await execFileAsync2("npm", ["install", "-g", def.package], { timeout: 120000 });
18505
18516
  } catch (error) {
18506
18517
  throw new Error(`Failed to install ${def.serverName}: ${error instanceof Error ? error.message : String(error)}`);
18507
18518
  }
@@ -18509,7 +18520,7 @@ class LspInstaller {
18509
18520
  async installPip(def) {
18510
18521
  assertPinnedPackage(def);
18511
18522
  try {
18512
- await execFileAsync("pip", ["install", "--user", def.package], { timeout: 120000 });
18523
+ await execFileAsync2("pip", ["install", "--user", def.package], { timeout: 120000 });
18513
18524
  } catch (error) {
18514
18525
  throw new Error(`Failed to install ${def.serverName}: ${error instanceof Error ? error.message : String(error)}`);
18515
18526
  }
@@ -18551,7 +18562,7 @@ class LspInstaller {
18551
18562
  await access6(candidate);
18552
18563
  await rename2(candidate, destPath);
18553
18564
  } catch {
18554
- const { stdout: found } = await execFileAsync("find", [workDir, "-type", "f", "-name", def.binaryName], { timeout: 30000 });
18565
+ const { stdout: found } = await execFileAsync2("find", [workDir, "-type", "f", "-name", def.binaryName], { timeout: 30000 });
18555
18566
  const first = found.split(`
18556
18567
  `).map((l) => l.trim()).find((l) => l.length > 0);
18557
18568
  if (!first) {
@@ -18561,7 +18572,7 @@ class LspInstaller {
18561
18572
  await rename2(first, destPath);
18562
18573
  }
18563
18574
  if (process.platform !== "win32") {
18564
- await execFileAsync("chmod", ["+x", destPath], { timeout: 5000 });
18575
+ await execFileAsync2("chmod", ["+x", destPath], { timeout: 5000 });
18565
18576
  }
18566
18577
  } finally {
18567
18578
  await rm(workDir, { recursive: true, force: true });
@@ -18571,11 +18582,11 @@ class LspInstaller {
18571
18582
  function createLspInstaller() {
18572
18583
  return new LspInstaller;
18573
18584
  }
18574
- var execFileAsync, TAR_EXTRACT_HARDENING_FLAGS;
18585
+ var execFileAsync2, TAR_EXTRACT_HARDENING_FLAGS;
18575
18586
  var init_lsp_installer = __esm(() => {
18576
18587
  init_binary_integrity();
18577
18588
  init_binary_downloader();
18578
- execFileAsync = promisify(execFile);
18589
+ execFileAsync2 = promisify2(execFile2);
18579
18590
  TAR_EXTRACT_HARDENING_FLAGS = [
18580
18591
  "--no-same-owner",
18581
18592
  "--no-same-permissions"
@@ -20840,13 +20851,13 @@ function slugify(text) {
20840
20851
 
20841
20852
  // src/core/knowledge/source-parsers.ts
20842
20853
  import { readFile as readFile19 } from "node:fs/promises";
20843
- import { join as join15 } from "node:path";
20854
+ import { basename as basename6, join as join15, relative as relative7, sep as sep5 } from "node:path";
20844
20855
  async function parseAdrFile(filePath) {
20845
20856
  try {
20846
20857
  const content = await readFile19(filePath, "utf-8");
20847
- const basename6 = filePath.split("/").pop() ?? "";
20848
- const idMatch = basename6.match(/adr-(\d+)/i);
20849
- const id = idMatch ? `adr-${idMatch[1]}` : slugify(basename6);
20858
+ const base = basename6(filePath);
20859
+ const idMatch = base.match(/adr-(\d+)/i);
20860
+ const id = idMatch ? `adr-${idMatch[1]}` : slugify(base);
20850
20861
  const titleMatch = content.match(/^#\s+ADR-\d+:\s*(.+)$/m);
20851
20862
  const statusMatch = content.match(/\*\*Status:\*\*\s*(\w+)/i);
20852
20863
  const contextSection = extractSection(content, "Context");
@@ -20856,7 +20867,7 @@ async function parseAdrFile(filePath) {
20856
20867
  `).filter((l) => l.trim().startsWith("-") || l.trim().startsWith("*")).map((l) => l.replace(/^[\s*-]+/, "").trim()).filter(Boolean);
20857
20868
  return {
20858
20869
  id,
20859
- title: titleMatch?.[1]?.trim() ?? basename6,
20870
+ title: titleMatch?.[1]?.trim() ?? base,
20860
20871
  status: statusMatch?.[1]?.toLowerCase() ?? "unknown",
20861
20872
  context: contextSection.trim(),
20862
20873
  decision: decisionSection.trim(),
@@ -20956,7 +20967,7 @@ async function scanSrcComponents(projectRoot) {
20956
20967
  source: "src/",
20957
20968
  confidence: "medium",
20958
20969
  relations: [],
20959
- data: { path: full.replace(projectRoot + "/", "") }
20970
+ data: { path: relative7(projectRoot, full).split(sep5).join("/") }
20960
20971
  });
20961
20972
  await walk(full, prefix ? `${prefix}-${domainSlug}` : domainSlug);
20962
20973
  }
@@ -21017,7 +21028,7 @@ var init_episodic_store = __esm(() => {
21017
21028
  // src/core/knowledge/ingest-pipeline.ts
21018
21029
  import { createHash as createHash3 } from "node:crypto";
21019
21030
  import { readFile as readFile21 } from "node:fs/promises";
21020
- import { join as join16 } from "node:path";
21031
+ import { join as join16, relative as relative8, sep as sep6 } from "node:path";
21021
21032
  import { mkdir as mkdir13, writeFile as writeFile12 } from "node:fs/promises";
21022
21033
  async function mapLimit(items, limit, fn) {
21023
21034
  if (items.length === 0)
@@ -21066,7 +21077,7 @@ async function collectDeferRisks(projectRoot) {
21066
21077
  const perFile = await mapLimit(files, WALK_READ_CONCURRENCY, async (full) => {
21067
21078
  const entities = [];
21068
21079
  const content = await readFile21(full, "utf-8");
21069
- const rel = full.replace(projectRoot + "/", "");
21080
+ const rel = relative8(projectRoot, full).split(sep6).join("/");
21070
21081
  const deferRe = new RegExp(DEFER_REGEX_SOURCE, "gm");
21071
21082
  let match;
21072
21083
  while ((match = deferRe.exec(content)) !== null) {
@@ -22265,21 +22276,21 @@ __export(exports_rdd_receipt, {
22265
22276
  });
22266
22277
  import { createHash as createHash4 } from "node:crypto";
22267
22278
  import { readdir as readdir7, readFile as readFile27 } from "node:fs/promises";
22268
- import { join as join18, relative as relative7, resolve as resolve26, sep as sep5 } from "node:path";
22279
+ import { join as join18, relative as relative9, resolve as resolve26, sep as sep7 } from "node:path";
22269
22280
  function sha2562(content) {
22270
22281
  return createHash4("sha256").update(content).digest("hex");
22271
22282
  }
22272
22283
  function canonicalPath(projectRoot, input) {
22273
22284
  const root = resolve26(projectRoot);
22274
22285
  const absolute = resolve26(root, input);
22275
- if (absolute !== root && !absolute.startsWith(`${root}${sep5}`)) {
22286
+ if (absolute !== root && !absolute.startsWith(`${root}${sep7}`)) {
22276
22287
  throw new Error(`RDD receipt path "${input}" is outside the project root`);
22277
22288
  }
22278
- const normalized = relative7(root, absolute);
22289
+ const normalized = relative9(root, absolute);
22279
22290
  if (!normalized || normalized === ".") {
22280
22291
  throw new Error("RDD receipts require file paths, not the project root");
22281
22292
  }
22282
- return { absolute, relative: normalized.split(sep5).join("/") };
22293
+ return { absolute, relative: normalized.split(sep7).join("/") };
22283
22294
  }
22284
22295
  async function fingerprint(projectRoot, paths) {
22285
22296
  const deduplicated = new Map;
@@ -22334,7 +22345,7 @@ async function collectReceiptPaths(projectRoot) {
22334
22345
  continue;
22335
22346
  }
22336
22347
  if (entry.isFile())
22337
- paths.push(relative7(root, join18(current, entry.name)).split(sep5).join("/"));
22348
+ paths.push(relative9(root, join18(current, entry.name)).split(sep7).join("/"));
22338
22349
  }
22339
22350
  }
22340
22351
  await visit2(root);
@@ -23037,8 +23048,8 @@ var init_loop_state = __esm(() => {
23037
23048
  });
23038
23049
 
23039
23050
  // src/core/loop/git-stats.ts
23040
- import { execFile as execFile2 } from "node:child_process";
23041
- import { promisify as promisify2 } from "node:util";
23051
+ import { execFile as execFile3 } from "node:child_process";
23052
+ import { promisify as promisify3 } from "node:util";
23042
23053
  function countPorcelain(output) {
23043
23054
  return output.split(`
23044
23055
  `).filter((line) => line.trim().length > 0).length;
@@ -23062,12 +23073,12 @@ function countNumstat(output) {
23062
23073
  async function fetchGitChangeStats(cwd) {
23063
23074
  try {
23064
23075
  const [status, diff] = await Promise.all([
23065
- execFileAsync2("git", ["status", "--porcelain"], {
23076
+ execFileAsync3("git", ["status", "--porcelain"], {
23066
23077
  cwd,
23067
23078
  encoding: "utf-8",
23068
23079
  timeout: 15000
23069
23080
  }),
23070
- execFileAsync2("git", ["diff", "--numstat"], {
23081
+ execFileAsync3("git", ["diff", "--numstat"], {
23071
23082
  cwd,
23072
23083
  encoding: "utf-8",
23073
23084
  timeout: 15000
@@ -23091,9 +23102,9 @@ async function readGitChangeStats(cwd, options) {
23091
23102
  cache = { cwd, at: now, stats };
23092
23103
  return stats;
23093
23104
  }
23094
- var execFileAsync2, GIT_STATS_CACHE_TTL_MS = 3000, cache;
23105
+ var execFileAsync3, GIT_STATS_CACHE_TTL_MS = 3000, cache;
23095
23106
  var init_git_stats = __esm(() => {
23096
- execFileAsync2 = promisify2(execFile2);
23107
+ execFileAsync3 = promisify3(execFile3);
23097
23108
  });
23098
23109
 
23099
23110
  // src/core/loop/loop-engine.ts
@@ -23826,11 +23837,11 @@ __export(exports_rdd_command, {
23826
23837
  rddCommand: () => rddCommand,
23827
23838
  rddStatus: () => rddStatus
23828
23839
  });
23829
- import { execFile as execFile3 } from "node:child_process";
23840
+ import { execFile as execFile4 } from "node:child_process";
23830
23841
  import { existsSync as existsSync4 } from "node:fs";
23831
23842
  import { mkdir as mkdir19, readFile as readFile29, readdir as readdir9, rename as rename3, writeFile as writeFile19, chmod } from "node:fs/promises";
23832
23843
  import { dirname as dirname11, resolve as resolve28 } from "node:path";
23833
- import { promisify as promisify3 } from "node:util";
23844
+ import { promisify as promisify4 } from "node:util";
23834
23845
  function receiptPath(projectRoot, id) {
23835
23846
  const directory = resolve28(evidenceDir(projectRoot));
23836
23847
  const path = resolve28(directory, `${id.replace(/[^A-Za-z0-9_-]/g, "_")}.json`);
@@ -23877,7 +23888,7 @@ async function rddStatus(projectRoot, taskId) {
23877
23888
  return receipts.sort((left, right) => right.timestamp.localeCompare(left.timestamp));
23878
23889
  }
23879
23890
  async function installHooks(projectRoot) {
23880
- const { stdout } = await execFileAsync3("git", ["rev-parse", "--git-path", "hooks"], {
23891
+ const { stdout } = await execFileAsync4("git", ["rev-parse", "--git-path", "hooks"], {
23881
23892
  cwd: projectRoot,
23882
23893
  encoding: "utf8"
23883
23894
  });
@@ -23969,12 +23980,12 @@ async function rddCommand(options) {
23969
23980
  return { code: 1, data: { success: false, errors: [error instanceof Error ? error.message : String(error)] } };
23970
23981
  }
23971
23982
  }
23972
- var execFileAsync3;
23983
+ var execFileAsync4;
23973
23984
  var init_rdd_command = __esm(() => {
23974
23985
  init_schemas();
23975
23986
  init_paths();
23976
23987
  init_rdd_receipt();
23977
- execFileAsync3 = promisify3(execFile3);
23988
+ execFileAsync4 = promisify4(execFile4);
23978
23989
  });
23979
23990
 
23980
23991
  // src/core/ccep/confirmation-gate.ts
@@ -24283,7 +24294,7 @@ var init_output_validator = __esm(() => {
24283
24294
 
24284
24295
  // src/core/ccep/command-parser.ts
24285
24296
  import { existsSync as existsSync5, readFileSync as readFileSync3 } from "node:fs";
24286
- import { join as join20 } from "node:path";
24297
+ import { basename as basename7, join as join20 } from "node:path";
24287
24298
  function readProjectId(projectRoot) {
24288
24299
  const packageJsonPath = join20(projectRoot, "package.json");
24289
24300
  if (existsSync5(packageJsonPath)) {
@@ -24294,7 +24305,7 @@ function readProjectId(projectRoot) {
24294
24305
  }
24295
24306
  } catch {}
24296
24307
  }
24297
- return projectRoot.split("/").pop() ?? "unknown";
24308
+ return basename7(projectRoot) || "unknown";
24298
24309
  }
24299
24310
  function parseCommand(command, userRequest, projectRoot) {
24300
24311
  const validatedCommand = WorkflowCommandSchema.parse(command);
@@ -25969,7 +25980,7 @@ var init_backlog_validator = __esm(() => {
25969
25980
  });
25970
25981
 
25971
25982
  // src/core/openspec/backlog-scanner.ts
25972
- import { execFileSync as execFileSync2 } from "node:child_process";
25983
+ import { execFileSync } from "node:child_process";
25973
25984
  import { readFile as readFile33 } from "node:fs/promises";
25974
25985
  import { resolve as resolve31 } from "node:path";
25975
25986
  function itemSnapshot(items) {
@@ -25986,7 +25997,7 @@ async function scanBacklog(projectRoot, previousSnapshot = {}) {
25986
25997
  const hash = hashContent(content);
25987
25998
  let fileChanged = true;
25988
25999
  try {
25989
- const diff = execFileSync2("git", ["diff", "--name-only", "--", BACKLOG_FILENAME], {
26000
+ const diff = execFileSync("git", ["diff", "--name-only", "--", BACKLOG_FILENAME], {
25990
26001
  cwd: projectRoot,
25991
26002
  encoding: "utf-8",
25992
26003
  stdio: ["ignore", "pipe", "ignore"]
@@ -26220,7 +26231,7 @@ var init_backlog_planner = __esm(() => {
26220
26231
 
26221
26232
  // src/core/openspec/openspec-generator.ts
26222
26233
  import { mkdir as mkdir22, rename as rename5, writeFile as writeFile22 } from "node:fs/promises";
26223
- import { basename as basename6, resolve as resolve32 } from "node:path";
26234
+ import { basename as basename8, resolve as resolve32 } from "node:path";
26224
26235
  function pad3(n) {
26225
26236
  return String(n).padStart(3, "0");
26226
26237
  }
@@ -26367,7 +26378,7 @@ capabilities:
26367
26378
  }
26368
26379
  async function archiveChangeFolder(projectRoot, changePath) {
26369
26380
  const src = resolve32(projectRoot, changePath);
26370
- const slug = basename6(changePath);
26381
+ const slug = basename8(changePath);
26371
26382
  const destDir = resolve32(projectRoot, "openspec", "changes", "archive");
26372
26383
  await mkdir22(destDir, { recursive: true });
26373
26384
  const dest = resolve32(destDir, slug);
@@ -26393,7 +26404,7 @@ var init_openspec_generator = __esm(() => {
26393
26404
 
26394
26405
  // src/core/openspec/spec-quality.ts
26395
26406
  import { readdir as readdir11, readFile as readFile34 } from "node:fs/promises";
26396
- import { join as join23, relative as relative8, resolve as resolve33 } from "node:path";
26407
+ import { join as join23, relative as relative10, resolve as resolve33 } from "node:path";
26397
26408
  function uniqueIds(matches) {
26398
26409
  return [...new Set(matches ?? [])];
26399
26410
  }
@@ -26550,7 +26561,7 @@ async function assessChangeFolder(projectRoot, changePath) {
26550
26561
  const reports = [];
26551
26562
  for (const file of specFiles) {
26552
26563
  const content = await readFile34(file, "utf-8");
26553
- reports.push(assessSpecMarkdown(content, relative8(root, file).replace(/\\/g, "/")));
26564
+ reports.push(assessSpecMarkdown(content, relative10(root, file).replace(/\\/g, "/")));
26554
26565
  }
26555
26566
  for (const file of extra) {
26556
26567
  let content;
@@ -26559,7 +26570,7 @@ async function assessChangeFolder(projectRoot, changePath) {
26559
26570
  } catch {
26560
26571
  continue;
26561
26572
  }
26562
- const rel = relative8(root, file).replace(/\\/g, "/");
26573
+ const rel = relative10(root, file).replace(/\\/g, "/");
26563
26574
  if (PLACEHOLDER.test(content)) {
26564
26575
  reports.push({
26565
26576
  valid: false,
@@ -26756,7 +26767,7 @@ var init_spec_analyzer = __esm(() => {
26756
26767
 
26757
26768
  // src/core/openspec/spec-sync.ts
26758
26769
  import { mkdir as mkdir23, readFile as readFile36, readdir as readdir13 } from "node:fs/promises";
26759
- import { join as join25, relative as relative9, resolve as resolve35 } from "node:path";
26770
+ import { join as join25, relative as relative11, resolve as resolve35 } from "node:path";
26760
26771
  async function markdownFiles(dir) {
26761
26772
  let entries;
26762
26773
  try {
@@ -26860,17 +26871,17 @@ async function syncChangeSpecs(projectRoot, changePath) {
26860
26871
  return {
26861
26872
  success: false,
26862
26873
  syncedPaths: [],
26863
- errors: [`${relative9(root, file)}: no delta requirements found`]
26874
+ errors: [`${relative11(root, file)}: no delta requirements found`]
26864
26875
  };
26865
26876
  }
26866
- const relativeSpec = relative9(changeSpecs, file);
26877
+ const relativeSpec = relative11(changeSpecs, file);
26867
26878
  const target = join25(root, "openspec", "specs", relativeSpec);
26868
26879
  let existing = `# ${relativeSpec.replace(/\\/g, "/")}
26869
26880
  `;
26870
26881
  try {
26871
26882
  existing = await readFile36(target, "utf-8");
26872
26883
  } catch {}
26873
- const merged = upsertRequirements(existing, requirements, relative9(root, file));
26884
+ const merged = upsertRequirements(existing, requirements, relative11(root, file));
26874
26885
  if (!merged.ok)
26875
26886
  return { success: false, syncedPaths: [], errors: [merged.error] };
26876
26887
  writes.push({ path: target, content: merged.content });
@@ -26881,7 +26892,7 @@ async function syncChangeSpecs(projectRoot, changePath) {
26881
26892
  }
26882
26893
  return {
26883
26894
  success: true,
26884
- syncedPaths: writes.map((write) => relative9(root, write.path).replace(/\\/g, "/")),
26895
+ syncedPaths: writes.map((write) => relative11(root, write.path).replace(/\\/g, "/")),
26885
26896
  errors: []
26886
26897
  };
26887
26898
  }
@@ -27587,7 +27598,7 @@ var init_context_assembly = () => {};
27587
27598
  // src/core/delivery/delivery-ledger.ts
27588
27599
  import { createHash as createHash7 } from "node:crypto";
27589
27600
  import { mkdir as mkdir24, readFile as readFile38, writeFile as writeFile23 } from "node:fs/promises";
27590
- import { resolve as resolve37, sep as sep6 } from "node:path";
27601
+ import { resolve as resolve37, sep as sep8 } from "node:path";
27591
27602
  function ledgerPath(projectRoot, id) {
27592
27603
  if (!/^[a-z0-9][a-z0-9-]{0,63}$/.test(id))
27593
27604
  throw new Error("Invalid delivery ledger id");
@@ -27598,7 +27609,7 @@ async function fingerprintWorkspace(projectRoot, paths) {
27598
27609
  const fingerprints = {};
27599
27610
  for (const path of paths) {
27600
27611
  const candidate = resolve37(root, path);
27601
- if (candidate !== root && !candidate.startsWith(`${root}${sep6}`)) {
27612
+ if (candidate !== root && !candidate.startsWith(`${root}${sep8}`)) {
27602
27613
  fingerprints[path] = "outside-root";
27603
27614
  continue;
27604
27615
  }
@@ -27755,7 +27766,11 @@ function normalizeCommand(raw) {
27755
27766
  return raw.replace(/\s+/g, " ").trim();
27756
27767
  }
27757
27768
  function stripGitBinary(command) {
27758
- return command.replace(/(?:^|[\\/])git(?:\.exe)?\b/i, "git");
27769
+ const match = command.match(/^(?:&\s*)?("[^"]+"|'[^']+'|[^\s]+)([\s\S]*)$/);
27770
+ if (!match)
27771
+ return command;
27772
+ const binary = match[1].replace(/^["']|["']$/g, "").replace(/\\/g, "/").split("/").pop();
27773
+ return /^git(?:\.exe)?$/i.test(binary ?? "") ? `git${match[2]}` : command;
27759
27774
  }
27760
27775
  function evaluatePath(filePath) {
27761
27776
  if (SENSITIVE_PATH.test(filePath.replace(/\//g, "\\")) || SENSITIVE_PATH.test(filePath)) {
@@ -27764,9 +27779,44 @@ function evaluatePath(filePath) {
27764
27779
  return allow();
27765
27780
  }
27766
27781
  function evaluateCommand(rawCommand) {
27767
- const command = stripGitBinary(normalizeCommand(rawCommand));
27782
+ if (/(?:curl|wget)\b.+\|\s*(sh|bash|zsh|cmd)\b/i.test(normalizeCommand(rawCommand)))
27783
+ return deny(MSG_POLICY);
27784
+ const tokens = rawCommand.match(/"[^"]*"|'[^']*'|[^"';&|\r\n]+|[;&|\r\n]+/g) ?? [];
27785
+ const commands = [];
27786
+ let current = "";
27787
+ for (const token of tokens) {
27788
+ if (/^[;&|\r\n]+$/.test(token) && !(token === "&" && !current.trim())) {
27789
+ commands.push(current);
27790
+ current = "";
27791
+ } else {
27792
+ current += token;
27793
+ }
27794
+ }
27795
+ commands.push(current);
27796
+ let verdict = allow();
27797
+ for (const part of commands) {
27798
+ const result = evaluateSingleCommand(part);
27799
+ if (result.action === "deny")
27800
+ return result;
27801
+ if (result.action === "ask")
27802
+ verdict = result;
27803
+ }
27804
+ return verdict;
27805
+ }
27806
+ function evaluateSingleCommand(rawCommand) {
27807
+ let command = stripGitBinary(normalizeCommand(rawCommand));
27768
27808
  if (!command)
27769
27809
  return allow();
27810
+ if (/^git\s/i.test(command)) {
27811
+ const words = command.match(/"[^"]*"|'[^']*'|\S+/g) ?? [];
27812
+ let index = 1;
27813
+ while (words[index]?.startsWith("-")) {
27814
+ const option = words[index++];
27815
+ if (/^(?:-C|-c|--git-dir|--work-tree|--namespace)$/.test(option))
27816
+ index++;
27817
+ }
27818
+ command = ["git", ...words.slice(index)].join(" ");
27819
+ }
27770
27820
  if (SENSITIVE_PATH.test(command) && READ_LIKE.test(command)) {
27771
27821
  return deny(MSG_SECRET);
27772
27822
  }
@@ -27789,9 +27839,12 @@ function evaluateCommand(rawCommand) {
27789
27839
  }
27790
27840
  if (/^rm\s+-rf\s+(\*|[\\/])/i.test(command))
27791
27841
  return deny(MSG_POLICY);
27792
- if (/^sudo\s+/i.test(command))
27842
+ const windowsTarget = /(?:^|\s)["']?(?:[a-z]:[\\/]|\\|\*)/i.test(command);
27843
+ if (windowsTarget && /^Remove-Item\b/i.test(command) && /\s-(?:Recurse|r)\b/i.test(command) && /\s-(?:Force|f)\b/i.test(command))
27844
+ return deny(MSG_POLICY);
27845
+ if (windowsTarget && /^(?:rd|rmdir|del|erase)\b/i.test(command) && /\s\/s\b/i.test(command))
27793
27846
  return deny(MSG_POLICY);
27794
- if (/(?:curl|wget)\b.+\|\s*(sh|bash|zsh|cmd)\b/i.test(command))
27847
+ if (/^sudo\s+/i.test(command))
27795
27848
  return deny(MSG_POLICY);
27796
27849
  if (/^chmod\s+777\b/i.test(command))
27797
27850
  return deny(MSG_POLICY);
@@ -27818,13 +27871,16 @@ function evaluatePreTool(input) {
27818
27871
  function formatHookOutput(verdict, format) {
27819
27872
  if (format === "agy") {
27820
27873
  if (verdict.action === "deny") {
27821
- return JSON.stringify({ decision: "deny", error: verdict.message });
27874
+ return JSON.stringify({ decision: "deny", reason: verdict.message });
27822
27875
  }
27823
27876
  if (verdict.action === "ask") {
27824
27877
  return JSON.stringify({ decision: "ask" });
27825
27878
  }
27826
27879
  return JSON.stringify({ decision: "allow" });
27827
27880
  }
27881
+ if (verdict.action === "ask") {
27882
+ return JSON.stringify({ hookSpecificOutput: { hookEventName: "PreToolUse", permissionDecision: "ask" } });
27883
+ }
27828
27884
  return "";
27829
27885
  }
27830
27886
  function claudeExitCode(verdict) {
@@ -27835,11 +27891,24 @@ function parseAgyPayload(raw) {
27835
27891
  return {};
27836
27892
  try {
27837
27893
  const parsed = JSON.parse(raw);
27838
- const args = parsed.arguments ?? {};
27894
+ const args = parsed.toolCall?.args ?? parsed.arguments ?? {};
27839
27895
  return {
27840
- toolName: parsed.toolName,
27841
- command: args.CommandLine,
27842
- filePath: args.TargetFile ?? args.AbsolutePath
27896
+ toolName: typeof (parsed.toolCall?.name ?? parsed.toolName) === "string" ? parsed.toolCall?.name ?? parsed.toolName : undefined,
27897
+ command: typeof args.CommandLine === "string" ? args.CommandLine : undefined,
27898
+ filePath: typeof args.TargetFile === "string" ? args.TargetFile : typeof args.AbsolutePath === "string" ? args.AbsolutePath : undefined
27899
+ };
27900
+ } catch {
27901
+ return {};
27902
+ }
27903
+ }
27904
+ function parseClaudePayload(raw) {
27905
+ try {
27906
+ const parsed = JSON.parse(raw);
27907
+ const input = parsed.tool_input ?? {};
27908
+ return {
27909
+ toolName: typeof parsed.tool_name === "string" ? parsed.tool_name : undefined,
27910
+ command: typeof input.command === "string" ? input.command : undefined,
27911
+ filePath: typeof input.file_path === "string" ? input.file_path : undefined
27843
27912
  };
27844
27913
  } catch {
27845
27914
  return {};
@@ -27901,14 +27970,17 @@ __export(exports_hook_command, {
27901
27970
  readStdinText: () => readStdinText
27902
27971
  });
27903
27972
  import { spawnSync } from "node:child_process";
27904
- import { existsSync as existsSync8 } from "node:fs";
27905
- import { resolve as resolve38 } from "node:path";
27973
+ import { existsSync as existsSync8, readFileSync as readFileSync5 } from "node:fs";
27974
+ import { createRequire as createRequire2 } from "node:module";
27975
+ import { dirname as dirname13, resolve as resolve38 } from "node:path";
27906
27976
  function detectFormat(explicit, stdinText) {
27907
27977
  if (explicit)
27908
27978
  return explicit;
27909
- const trimmed = stdinText.trim();
27910
- if (trimmed.startsWith("{"))
27911
- return "agy";
27979
+ try {
27980
+ const payload = JSON.parse(stdinText);
27981
+ if (payload && (("toolCall" in payload) || ("toolName" in payload) || ("workspacePaths" in payload)))
27982
+ return "agy";
27983
+ } catch {}
27912
27984
  return "claude";
27913
27985
  }
27914
27986
  async function readStdinText() {
@@ -27920,11 +27992,11 @@ async function readStdinText() {
27920
27992
  }
27921
27993
  return Buffer.concat(chunks).toString("utf8");
27922
27994
  }
27923
- function resolvePreToolInput(options, stdinText) {
27924
- const fromStdin = parseAgyPayload(stdinText);
27995
+ function resolvePreToolInput(options, stdinText, format) {
27996
+ const fromStdin = format === "agy" ? parseAgyPayload(stdinText) : parseClaudePayload(stdinText);
27925
27997
  return {
27926
- command: options.command ?? process.env.CLAUDE_TOOL_INPUT_COMMAND ?? fromStdin.command,
27927
- filePath: options.filePath ?? process.env.CLAUDE_TOOL_INPUT_FILE_PATH ?? fromStdin.filePath,
27998
+ command: options.command ?? (process.env.CLAUDE_TOOL_INPUT_COMMAND || undefined) ?? fromStdin.command,
27999
+ filePath: options.filePath ?? (process.env.CLAUDE_TOOL_INPUT_FILE_PATH || undefined) ?? fromStdin.filePath,
27928
28000
  toolName: fromStdin.toolName
27929
28001
  };
27930
28002
  }
@@ -27951,15 +28023,15 @@ async function hookCommand(options) {
27951
28023
  return { code: 0, data: { success: true, command: "hook session-start" } };
27952
28024
  }
27953
28025
  if (event === "post-tool") {
27954
- const filePath = options.filePath ?? process.env.CLAUDE_TOOL_INPUT_FILE_PATH ?? parseAgyPayload(stdinText).filePath;
27955
- formatWrittenFile(filePath);
28026
+ const filePath = resolvePreToolInput(options, stdinText, format).filePath;
28027
+ formatWrittenFile(filePath, options.projectRoot);
27956
28028
  if (format === "agy") {
27957
28029
  process.stdout.write(`{}
27958
28030
  `);
27959
28031
  }
27960
28032
  return { code: 0, data: { success: true, command: "hook post-tool" } };
27961
28033
  }
27962
- const input = resolvePreToolInput(options, stdinText);
28034
+ const input = resolvePreToolInput(options, stdinText, format);
27963
28035
  const verdict = evaluatePreTool(input);
27964
28036
  if (format === "agy") {
27965
28037
  process.stdout.write(`${formatHookOutput(verdict, "agy")}
@@ -27973,23 +28045,40 @@ async function hookCommand(options) {
27973
28045
  process.stderr.write(`${verdict.message}
27974
28046
  `);
27975
28047
  }
28048
+ const output = formatHookOutput(verdict, "claude");
28049
+ if (output)
28050
+ process.stdout.write(`${output}
28051
+ `);
27976
28052
  return {
27977
28053
  code: claudeExitCode(verdict),
27978
28054
  data: { success: verdict.action !== "deny", command: "hook pre-tool", verdict }
27979
28055
  };
27980
28056
  }
27981
- function formatWrittenFile(filePath) {
27982
- if (!filePath || !existsSync8(filePath))
28057
+ function formatWrittenFile(filePath, projectRoot) {
28058
+ if (!filePath)
28059
+ return;
28060
+ filePath = resolve38(projectRoot, filePath);
28061
+ if (!existsSync8(filePath))
27983
28062
  return;
27984
28063
  const prettier = /\.(ts|tsx|js|jsx|mjs|cjs|json|jsonc|md|mdx|css|scss|html|astro|ya?ml)$/i;
27985
28064
  const eslint = /\.(ts|tsx|js|jsx|mjs|cjs)$/i;
27986
28065
  const python = /\.py$/i;
27987
- const opts = { stdio: "ignore", windowsHide: true };
28066
+ const opts = { cwd: projectRoot, stdio: "ignore", windowsHide: true, timeout: 5000 };
28067
+ const require2 = createRequire2(resolve38(projectRoot, "package.json"));
28068
+ function runNodeTool(name, args) {
28069
+ try {
28070
+ const manifestPath = require2.resolve(`${name}/package.json`);
28071
+ const manifest = JSON.parse(readFileSync5(manifestPath, "utf8"));
28072
+ const bin = typeof manifest.bin === "string" ? manifest.bin : manifest.bin?.[name];
28073
+ if (typeof bin === "string")
28074
+ spawnSync(process.execPath, [resolve38(dirname13(manifestPath), bin), ...args], opts);
28075
+ } catch {}
28076
+ }
27988
28077
  if (prettier.test(filePath)) {
27989
- spawnSync("npx", ["--no-install", "prettier", "--write", filePath], opts);
28078
+ runNodeTool("prettier", ["--write", filePath]);
27990
28079
  }
27991
28080
  if (eslint.test(filePath)) {
27992
- spawnSync("npx", ["--no-install", "eslint", "--fix", filePath], opts);
28081
+ runNodeTool("eslint", ["--fix", filePath]);
27993
28082
  }
27994
28083
  if (python.test(filePath)) {
27995
28084
  spawnSync("ruff", ["format", filePath], opts);
@@ -28396,7 +28485,7 @@ function ccGainToScorecardImpact(result) {
28396
28485
  }
28397
28486
 
28398
28487
  // src/core/evaluation/scorecard-signals.ts
28399
- import { execFileSync as execFileSync3 } from "node:child_process";
28488
+ import { execFileSync as execFileSync2 } from "node:child_process";
28400
28489
  function parseScopeFiles(scope) {
28401
28490
  return scope.split(/[,;]/).map((entry) => entry.trim().replace(/[.;:]$/, "").replace(/^`|`$/g, "")).filter(Boolean);
28402
28491
  }
@@ -28426,7 +28515,7 @@ function collectScorecardSignals(projectRoot, scopeFiles, extra) {
28426
28515
  }
28427
28516
  let diff = "";
28428
28517
  try {
28429
- diff = execFileSync3("git", ["diff", "HEAD"], {
28518
+ diff = execFileSync2("git", ["diff", "HEAD"], {
28430
28519
  cwd: projectRoot,
28431
28520
  encoding: "utf-8",
28432
28521
  stdio: ["ignore", "pipe", "ignore"]
@@ -28522,7 +28611,7 @@ var init_scorecard_signals = __esm(() => {
28522
28611
  // src/core/evaluation/harness-experiment.ts
28523
28612
  import { existsSync as existsSync9 } from "node:fs";
28524
28613
  import { cp, mkdir as mkdir25, readFile as readFile39, readdir as readdir14, writeFile as writeFile24 } from "node:fs/promises";
28525
- import { dirname as dirname13, join as join26, resolve as resolve39 } from "node:path";
28614
+ import { dirname as dirname14, join as join26, resolve as resolve39 } from "node:path";
28526
28615
  function experimentDir(projectRoot, experimentId) {
28527
28616
  return resolve39(projectRoot, EVAL_DIR2, EXPERIMENTS_DIR, experimentId);
28528
28617
  }
@@ -28637,7 +28726,7 @@ function generateExperimentId(suiteId, now = new Date) {
28637
28726
  return `abl-${date}-${suiteId}-${rand}`;
28638
28727
  }
28639
28728
  async function copyFixture(suitePath, fixtureDir, dest) {
28640
- const source = fixtureDir ? resolve39(dirname13(suitePath), fixtureDir) : join26(dirname13(suitePath), "fixture");
28729
+ const source = fixtureDir ? resolve39(dirname14(suitePath), fixtureDir) : join26(dirname14(suitePath), "fixture");
28641
28730
  await mkdir25(dest, { recursive: true });
28642
28731
  if (existsSync9(source)) {
28643
28732
  await cp(source, dest, { recursive: true });
@@ -29042,9 +29131,9 @@ var init_prompt_diff = __esm(() => {
29042
29131
  });
29043
29132
 
29044
29133
  // src/core/evaluation/regression-checklist.ts
29045
- import { execFileSync as execFileSync4 } from "node:child_process";
29134
+ import { execFileSync as execFileSync3 } from "node:child_process";
29046
29135
  import { mkdir as mkdir26, readFile as readFile42, writeFile as writeFile25 } from "node:fs/promises";
29047
- import { dirname as dirname14, resolve as resolve43 } from "node:path";
29136
+ import { dirname as dirname15, resolve as resolve43 } from "node:path";
29048
29137
  async function loadRegressionChecklist() {
29049
29138
  try {
29050
29139
  const path = resolve43(ROOT_PRESETS_DIR, "templates", "regression-checklist.yml");
@@ -29062,7 +29151,7 @@ function runCheckCommand(cmd, cwd) {
29062
29151
  if (!file)
29063
29152
  return { passed: false, message: "Empty command" };
29064
29153
  try {
29065
- execFileSync4(file, args, {
29154
+ execFileSync3(file, args, {
29066
29155
  cwd,
29067
29156
  encoding: "utf-8",
29068
29157
  stdio: ["ignore", "pipe", "pipe"],
@@ -29104,7 +29193,7 @@ async function runRegressionChecklist(projectRoot, options = {}) {
29104
29193
  timestamp: new Date().toISOString()
29105
29194
  };
29106
29195
  const outPath = resolve43(projectRoot, ".codeconductor/evaluation/regression-last.json");
29107
- await mkdir26(dirname14(outPath), { recursive: true });
29196
+ await mkdir26(dirname15(outPath), { recursive: true });
29108
29197
  await writeFile25(outPath, JSON.stringify(report, null, 2), "utf-8");
29109
29198
  return ok(report);
29110
29199
  } catch (e) {
@@ -31516,6 +31605,7 @@ var COMMANDS = [
31516
31605
  { name: "setup", group: "getting-started", summary: "Configure and install CodeConductor.", usage: ["cc-codeconductor setup [--target <target>] [--locale en|es] [--yes] [--dry-run]"] },
31517
31606
  { name: "detect", group: "getting-started", summary: "Inspect the project stack and recommended presets.", usage: ["cc-codeconductor detect"] },
31518
31607
  { name: "init", group: "getting-started", summary: "Initialize low-level CodeConductor configuration.", usage: ["cc-codeconductor init [--locale en|es]"] },
31608
+ { name: "usage", group: "getting-started", summary: "Show installation examples for presets, council, and LSP.", usage: ["cc-codeconductor usage"] },
31519
31609
  { name: "install", group: "getting-started", summary: "Install harness components.", usage: ["cc-codeconductor install preset --target <target>"], options: ["--target opencode|claude|codex|gemini|cursor|agy|pi|all"], subcommands: [
31520
31610
  { name: "preset", group: "getting-started", summary: "Install agents, prompts, skills, and commands.", usage: ["cc-codeconductor install preset --target <target> [--locale en|es]"] },
31521
31611
  { name: "council", group: "getting-started", summary: "Install generated council files.", usage: ["cc-codeconductor install council --target <target>"] },
@@ -31629,6 +31719,96 @@ _arguments '1:command:(${names})' '--target[target]:target:(${targets})'`;
31629
31719
  return null;
31630
31720
  }
31631
31721
 
31722
+ // src/cli/usage-guide.ts
31723
+ init_cli_bin();
31724
+ var RECOMMENDED_FLOW = [
31725
+ "cc-codeconductor setup --target claude",
31726
+ "cc-codeconductor setup --target claude --locale es --yes",
31727
+ "cc-codeconductor setup --target claude --dry-run"
31728
+ ];
31729
+ var PRIMITIVES = [
31730
+ "cc-codeconductor detect",
31731
+ "cc-codeconductor init --locale es",
31732
+ "cc-codeconductor install preset --target claude",
31733
+ "cc-codeconductor install council --target claude",
31734
+ "cc-codeconductor install lsp --target claude"
31735
+ ];
31736
+ var PRESET_EXAMPLES = [
31737
+ "cc-codeconductor install preset --target claude",
31738
+ "cc-codeconductor install preset --target claude --global",
31739
+ "cc-codeconductor install preset --target claude --force",
31740
+ "cc-codeconductor install preset --target claude --dry-run",
31741
+ "cc-codeconductor install preset --target all",
31742
+ "cc-codeconductor install preset --target claude --locale es",
31743
+ "cc-codeconductor install preset --target claude --global --force"
31744
+ ];
31745
+ var COUNCIL_EXAMPLES = [
31746
+ "cc-codeconductor install council --target claude",
31747
+ "cc-codeconductor install council --target claude --global",
31748
+ "cc-codeconductor install council --target claude --force",
31749
+ "cc-codeconductor install council --target claude --dry-run",
31750
+ "cc-codeconductor install council --target all",
31751
+ "cc-codeconductor install council --target claude --global --force"
31752
+ ];
31753
+ var LSP_EXAMPLES = [
31754
+ "cc-codeconductor install lsp --target claude --lang typescript,python",
31755
+ "cc-codeconductor install lsp --target all --lang typescript,python",
31756
+ "cc-codeconductor install lsp --target claude --global",
31757
+ "cc-codeconductor install lsp --target claude --dry-run"
31758
+ ];
31759
+ var AFTER_INSTALL_EXAMPLES = [
31760
+ ["cc-codeconductor doctor", "validates the installation"],
31761
+ ["cc-codeconductor status", "shows what is installed"],
31762
+ ["cc-codeconductor update --dry-run", "previews reconciliation of managed files after upgrading"],
31763
+ ["cc-codeconductor update --force", "applies reconciliation of managed files after upgrading"],
31764
+ ["cc-codeconductor migrate --dry-run", "previews repair of orphaned artifacts a reinstall cannot fix"]
31765
+ ];
31766
+ function renderExamples(lines, bin) {
31767
+ return lines.map((line) => ` ${formatUsage(line, bin)}`);
31768
+ }
31769
+ function renderAnnotatedExamples(entries, bin) {
31770
+ return entries.map(([line, description]) => ` ${formatUsage(line, bin)} — ${description}`);
31771
+ }
31772
+ function renderUsageGuide(bin = resolveCliBin()) {
31773
+ return [
31774
+ "CodeConductor — installation usage guide",
31775
+ "",
31776
+ "Recommended flow (one command, installs and configures everything):",
31777
+ ...renderExamples(RECOMMENDED_FLOW, bin),
31778
+ "",
31779
+ "Fine-grained primitives (for scripting or partial installs):",
31780
+ ...renderExamples(PRIMITIVES, bin),
31781
+ "",
31782
+ "Shared flags:",
31783
+ " --target opencode|claude|codex|gemini|cursor|agy|pi|all (default: opencode)",
31784
+ " --global Install to the home directory (~/.claude, ~/.opencode, etc.) instead of the project",
31785
+ " --force Allow overwriting existing files",
31786
+ " --dry-run Show what would happen without writing files",
31787
+ " --locale Instruction language for agent files: en (default) | es — install preset only",
31788
+ " --lang Comma-separated languages to install LSP servers for — install lsp only",
31789
+ " --output Output mode: human (default) | json",
31790
+ "",
31791
+ "install preset — agents, prompts, skills, and commands:",
31792
+ ...renderExamples(PRESET_EXAMPLES, bin),
31793
+ "",
31794
+ "install council — generated council spec files:",
31795
+ ...renderExamples(COUNCIL_EXAMPLES, bin),
31796
+ "",
31797
+ "install lsp — language servers:",
31798
+ ...renderExamples(LSP_EXAMPLES, bin),
31799
+ " Dedicated integration config is generated for: opencode, claude, codex, gemini, cursor, agy.",
31800
+ " pi installs the language servers but does not get a dedicated integration config file.",
31801
+ "",
31802
+ "After installing, verify and keep in sync:",
31803
+ ...renderAnnotatedExamples(AFTER_INSTALL_EXAMPLES, bin),
31804
+ "",
31805
+ "More detail (GitHub only, not bundled in the npm package):",
31806
+ " docs/usage-cli.md Detailed CLI reference, incl. per-target generated files",
31807
+ " docs/getting-started/ Installation, first project, and update walkthroughs"
31808
+ ].join(`
31809
+ `);
31810
+ }
31811
+
31632
31812
  // src/cli/router.ts
31633
31813
  var OutputModeSchema = exports_external.enum(["json", "human"]);
31634
31814
  var SeoFormatSchema = exports_external.enum(["cli", "json", "markdown"]);
@@ -31909,6 +32089,8 @@ async function routeCommand(args, projectRoot) {
31909
32089
  case "docs":
31910
32090
  case "man":
31911
32091
  return { code: 0, data: { success: true, command, output: renderDocs(subcommand) } };
32092
+ case "usage":
32093
+ return { code: 0, data: { success: true, command: "usage", output: renderUsageGuide() } };
31912
32094
  case "completion": {
31913
32095
  const output = renderCompletion(subcommand ?? "");
31914
32096
  return output ? { code: 0, data: { success: true, command: "completion", output } } : cliContractError("completion", ["Expected shell: bash, zsh, fish, or powershell."]);