@jaimevalasek/aioson 1.7.2 → 1.8.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 (362) hide show
  1. package/CHANGELOG.md +35 -0
  2. package/README.md +153 -10
  3. package/docs/en/cli-reference.md +56 -1
  4. package/docs/en/i18n.md +18 -18
  5. package/docs/en/schemas/index.json +10 -0
  6. package/docs/en/schemas/parallel-assign.schema.json +9 -0
  7. package/docs/en/schemas/parallel-doctor.schema.json +36 -0
  8. package/docs/en/schemas/parallel-guard.schema.json +63 -0
  9. package/docs/en/schemas/parallel-merge.schema.json +84 -0
  10. package/docs/en/schemas/parallel-status.schema.json +91 -1
  11. package/docs/integrations/apps-publish-marketplace.md +94 -0
  12. package/docs/pt/README.md +9 -0
  13. package/docs/pt/agentes.md +324 -3
  14. package/docs/pt/clientes-ai.md +7 -3
  15. package/docs/pt/comandos-cli.md +160 -13
  16. package/docs/pt/compress-agents.md +304 -0
  17. package/docs/pt/design-docs-governance.md +59 -0
  18. package/docs/pt/feature-archive.md +191 -0
  19. package/docs/pt/genome-3.0-spec.md +115 -4
  20. package/docs/pt/genome-distribution.md +232 -0
  21. package/docs/pt/inicio-rapido.md +1 -0
  22. package/docs/pt/motor-hardening.md +492 -0
  23. package/docs/pt/runner-system.md +113 -0
  24. package/package.json +2 -1
  25. package/src/agent-manifests.js +66 -0
  26. package/src/agents.js +27 -7
  27. package/src/autonomy-policy.js +139 -0
  28. package/src/brain-query.js +161 -0
  29. package/src/cli.js +1377 -1099
  30. package/src/commands/agents.js +102 -7
  31. package/src/commands/artifact-validate.js +33 -4
  32. package/src/commands/auth.js +272 -0
  33. package/src/commands/brain-query.js +44 -0
  34. package/src/commands/briefing.js +344 -0
  35. package/src/commands/commit-prepare.js +547 -0
  36. package/src/commands/compress-agents.js +416 -0
  37. package/src/commands/context-health.js +4 -2
  38. package/src/commands/context-trim.js +17 -11
  39. package/src/commands/design-hybrid-options.js +3 -3
  40. package/src/commands/devlog-process.js +6 -4
  41. package/src/commands/dossier.js +423 -0
  42. package/src/commands/feature-archive.js +513 -0
  43. package/src/commands/feature-close.js +123 -18
  44. package/src/commands/gate-approve.js +198 -0
  45. package/src/commands/gate-check.js +24 -5
  46. package/src/commands/genome-doctor.js +166 -9
  47. package/src/commands/git-guard.js +170 -0
  48. package/src/commands/harness.js +121 -0
  49. package/src/commands/implementation-plan.js +47 -20
  50. package/src/commands/init.js +6 -2
  51. package/src/commands/install.js +6 -2
  52. package/src/commands/live.js +497 -56
  53. package/src/commands/locale-apply.js +9 -6
  54. package/src/commands/locale-diff.js +11 -112
  55. package/src/commands/mcp-doctor.js +2 -1
  56. package/src/commands/mcp-init.js +4 -10
  57. package/src/commands/memory.js +234 -0
  58. package/src/commands/parallel-assign.js +107 -27
  59. package/src/commands/parallel-doctor.js +416 -3
  60. package/src/commands/parallel-guard.js +241 -0
  61. package/src/commands/parallel-init.js +66 -4
  62. package/src/commands/parallel-merge.js +299 -0
  63. package/src/commands/parallel-status.js +147 -3
  64. package/src/commands/preflight.js +63 -4
  65. package/src/commands/qa-init.js +10 -5
  66. package/src/commands/revision.js +235 -0
  67. package/src/commands/scaffold-complete.js +188 -0
  68. package/src/commands/security-audit.js +275 -0
  69. package/src/commands/security-scan.js +376 -0
  70. package/src/commands/self-implement-loop.js +46 -2
  71. package/src/commands/setup-context.js +11 -10
  72. package/src/commands/squad-agent-create.js +51 -9
  73. package/src/commands/squad-investigate.js +53 -0
  74. package/src/commands/squad-plan.js +33 -1
  75. package/src/commands/squad-scaffold.js +4 -3
  76. package/src/commands/squad-score.js +71 -14
  77. package/src/commands/squad-status.js +22 -1
  78. package/src/commands/squad-validate.js +93 -2
  79. package/src/commands/store-genome.js +304 -0
  80. package/src/commands/store-skill.js +247 -0
  81. package/src/commands/store-squad.js +431 -0
  82. package/src/commands/store-system.js +392 -0
  83. package/src/commands/tool-capabilities.js +63 -0
  84. package/src/commands/update.js +3 -3
  85. package/src/commands/verify-gate.js +40 -0
  86. package/src/commands/workflow-execute.js +644 -155
  87. package/src/commands/workflow-harden.js +231 -0
  88. package/src/commands/workflow-heal.js +136 -0
  89. package/src/commands/workflow-next.js +460 -22
  90. package/src/commands/workflow-status.js +328 -138
  91. package/src/commands/workspace.js +144 -0
  92. package/src/constants.js +42 -75
  93. package/src/context-memory.js +133 -4
  94. package/src/context-writer.js +2 -1
  95. package/src/context.js +32 -2
  96. package/src/doctor.js +46 -6
  97. package/src/dossier/codemap-store.js +267 -0
  98. package/src/dossier/dossier-bootstrap.js +222 -0
  99. package/src/dossier/dossier-compact.js +159 -0
  100. package/src/dossier/lock.js +128 -0
  101. package/src/dossier/revision-store.js +313 -0
  102. package/src/dossier/schema.js +155 -0
  103. package/src/dossier/store.js +400 -0
  104. package/src/execution-gateway.js +3 -0
  105. package/src/friction-scanner.js +202 -0
  106. package/src/genome-schema.js +24 -1
  107. package/src/genomes.js +33 -0
  108. package/src/handoff-contract.js +363 -0
  109. package/src/handoff-validator.js +45 -0
  110. package/src/harness/circuit-breaker.js +135 -0
  111. package/src/i18n/messages/en.js +317 -22
  112. package/src/i18n/messages/es.js +259 -18
  113. package/src/i18n/messages/fr.js +260 -18
  114. package/src/i18n/messages/pt-BR.js +313 -22
  115. package/src/install-profile.js +0 -16
  116. package/src/installer.js +70 -6
  117. package/src/lib/git-commit-guard.js +691 -0
  118. package/src/lib/security/artifact-reader.js +167 -0
  119. package/src/lib/security/exit-codes.js +51 -0
  120. package/src/lib/security/findings-writer.js +176 -0
  121. package/src/lib/security/runtime-events.js +77 -0
  122. package/src/lib/security/secrets-regex.js +115 -0
  123. package/src/lib/store/security-scan.js +173 -0
  124. package/src/lib/terminal-checkbox.js +130 -0
  125. package/src/lib/tmux-launcher.js +163 -0
  126. package/src/lib/tool-capabilities.js +102 -0
  127. package/src/locales.js +12 -8
  128. package/src/parallel-workspace.js +756 -0
  129. package/src/parser.js +8 -1
  130. package/src/path-guard.js +47 -0
  131. package/src/preflight-engine.js +237 -26
  132. package/src/self-healing.js +142 -0
  133. package/src/session-handoff.js +111 -1
  134. package/src/squad/squad-scaffold.js +183 -19
  135. package/src/test-briefing.js +226 -0
  136. package/src/updater.js +1 -1
  137. package/src/utils.js +3 -0
  138. package/src/workflow-gates.js +185 -0
  139. package/template/.aioson/agents/analyst.md +76 -130
  140. package/template/.aioson/agents/architect.md +53 -86
  141. package/template/.aioson/agents/committer.md +161 -0
  142. package/template/.aioson/agents/cypher.md +252 -0
  143. package/template/.aioson/agents/dev.md +112 -628
  144. package/template/.aioson/agents/deyvin.md +33 -236
  145. package/template/.aioson/agents/discover.md +235 -0
  146. package/template/.aioson/agents/discovery-design-doc.md +17 -252
  147. package/template/.aioson/agents/genome.md +76 -26
  148. package/template/.aioson/agents/manifests/analyst.manifest.json +26 -0
  149. package/template/.aioson/agents/manifests/architect.manifest.json +23 -0
  150. package/template/.aioson/agents/manifests/committer.manifest.json +23 -0
  151. package/template/.aioson/agents/manifests/dev.manifest.json +37 -0
  152. package/template/.aioson/agents/manifests/orchestrator.manifest.json +30 -0
  153. package/template/.aioson/agents/manifests/pentester.manifest.json +39 -0
  154. package/template/.aioson/agents/manifests/pm.manifest.json +26 -0
  155. package/template/.aioson/agents/manifests/product.manifest.json +23 -0
  156. package/template/.aioson/agents/manifests/qa.manifest.json +25 -0
  157. package/template/.aioson/agents/manifests/setup.manifest.json +20 -0
  158. package/template/.aioson/agents/manifests/ux-ui.manifest.json +24 -0
  159. package/template/.aioson/agents/neo.md +5 -7
  160. package/template/.aioson/agents/orache.md +2 -6
  161. package/template/.aioson/agents/orchestrator.md +81 -182
  162. package/template/.aioson/agents/pentester.md +235 -0
  163. package/template/.aioson/agents/pm.md +40 -104
  164. package/template/.aioson/agents/product.md +99 -344
  165. package/template/.aioson/agents/profiler-enricher.md +57 -6
  166. package/template/.aioson/agents/profiler-forge.md +17 -7
  167. package/template/.aioson/agents/profiler-researcher.md +29 -6
  168. package/template/.aioson/agents/qa.md +168 -514
  169. package/template/.aioson/agents/setup.md +52 -278
  170. package/template/.aioson/agents/sheldon.md +122 -754
  171. package/template/.aioson/agents/site-forge.md +111 -1583
  172. package/template/.aioson/agents/squad.md +139 -2010
  173. package/template/.aioson/agents/tester.md +10 -0
  174. package/template/.aioson/agents/ux-ui.md +104 -812
  175. package/template/.aioson/agents/validator.md +69 -0
  176. package/template/.aioson/brains/scripts/query.js +5 -1
  177. package/template/.aioson/config/autonomy-protocol.json +43 -0
  178. package/template/.aioson/config.md +43 -15
  179. package/template/.aioson/constitution.md +36 -33
  180. package/template/.aioson/context/design-doc.md +136 -0
  181. package/template/.aioson/context/project-map.md +57 -0
  182. package/template/.aioson/design-docs/code-reuse.md +48 -0
  183. package/template/.aioson/design-docs/componentization.md +47 -0
  184. package/template/.aioson/design-docs/file-size.md +52 -0
  185. package/template/.aioson/design-docs/folder-structure.md +51 -0
  186. package/template/.aioson/design-docs/naming.md +54 -0
  187. package/template/.aioson/docs/LAYERS.md +12 -2
  188. package/template/.aioson/docs/dev/execution-discipline.md +106 -0
  189. package/template/.aioson/docs/dev/stack-conventions.md +83 -0
  190. package/template/.aioson/docs/deyvin/continuity-recovery.md +57 -0
  191. package/template/.aioson/docs/deyvin/debugging-escalation.md +30 -0
  192. package/template/.aioson/docs/deyvin/pair-execution.md +44 -0
  193. package/template/.aioson/docs/deyvin/runtime-handoffs.md +36 -0
  194. package/template/.aioson/docs/product/conversation-playbook.md +116 -0
  195. package/template/.aioson/docs/product/prd-contract.md +107 -0
  196. package/template/.aioson/docs/product/quality-lens.md +57 -0
  197. package/template/.aioson/docs/product/research-loop.md +65 -0
  198. package/template/.aioson/docs/sheldon/enrichment-paths.md +134 -0
  199. package/template/.aioson/docs/sheldon/quality-lens.md +57 -0
  200. package/template/.aioson/docs/sheldon/research-loop.md +56 -0
  201. package/template/.aioson/docs/sheldon/web-intelligence.md +75 -0
  202. package/template/.aioson/docs/site-forge-build.md +195 -0
  203. package/template/.aioson/docs/site-forge-extraction.md +135 -0
  204. package/template/.aioson/docs/site-forge-qa.md +155 -0
  205. package/template/.aioson/docs/site-forge-recon.md +434 -0
  206. package/template/.aioson/docs/site-forge-transform.md +249 -0
  207. package/template/.aioson/docs/squad/content-output.md +91 -0
  208. package/template/.aioson/docs/squad/creation-flow.md +135 -0
  209. package/template/.aioson/docs/squad/domain-classification.md +117 -0
  210. package/template/.aioson/docs/squad/genome-bindings.md +47 -0
  211. package/template/.aioson/docs/squad/package-contract.md +234 -0
  212. package/template/.aioson/docs/squad/quality-lens.md +56 -0
  213. package/template/.aioson/docs/squad/research-loop.md +59 -0
  214. package/template/.aioson/docs/squad/session-operations.md +117 -0
  215. package/template/.aioson/docs/squad/workflow-quality.md +165 -0
  216. package/template/.aioson/docs/ux-ui/accessibility-audit.md +55 -0
  217. package/template/.aioson/docs/ux-ui/audit-mode.md +86 -0
  218. package/template/.aioson/docs/ux-ui/component-map.md +35 -0
  219. package/template/.aioson/docs/ux-ui/design-execution.md +111 -0
  220. package/template/.aioson/docs/ux-ui/design-gate.md +27 -0
  221. package/template/.aioson/docs/ux-ui/research-mode.md +39 -0
  222. package/template/.aioson/docs/ux-ui/site-delivery.md +156 -0
  223. package/template/.aioson/docs/ux-ui/token-contract.md +57 -0
  224. package/template/.aioson/genomes/copywriting.meta.json +48 -0
  225. package/template/.aioson/git-guard.json +11 -0
  226. package/template/.aioson/mcp/servers.md +0 -1
  227. package/template/.aioson/rules/agent-language-policy.md +93 -0
  228. package/template/.aioson/rules/aioson-context-boundary.md +63 -0
  229. package/template/.aioson/rules/canonical-path-contract.md +47 -0
  230. package/template/.aioson/rules/data-format-convention.md +24 -86
  231. package/template/.aioson/rules/disk-first-artifacts.md +44 -0
  232. package/template/.aioson/rules/output-brevity.md +44 -0
  233. package/template/.aioson/rules/prd-section-ownership.md +49 -0
  234. package/template/.aioson/rules/security-baseline.md +139 -0
  235. package/template/.aioson/rules/spec-level-ownership.md +61 -0
  236. package/template/.aioson/rules/squad-driver-pattern.md +81 -0
  237. package/template/.aioson/schemas/squad-blueprint.schema.json +24 -0
  238. package/template/.aioson/schemas/squad-manifest.schema.json +44 -0
  239. package/template/.aioson/skills/process/aioson-spec-driven/references/pm.md +30 -0
  240. package/template/.aioson/skills/process/secure-tdd/SKILL.md +97 -0
  241. package/template/.aioson/skills/process/secure-tdd/references/nextjs.md +81 -0
  242. package/template/.aioson/skills/process/secure-tdd/references/node-express.md +91 -0
  243. package/template/.aioson/skills/process/secure-tdd/references/planned-stacks.md +33 -0
  244. package/template/.aioson/skills/static/harness-validate/SKILL.md +46 -0
  245. package/template/.aioson/skills/static/web-research-cache.md +3 -0
  246. package/template/.aioson/tasks/squad-create.md +35 -8
  247. package/template/.aioson/tasks/squad-design.md +50 -2
  248. package/template/.aioson/tasks/squad-investigate.md +14 -1
  249. package/template/.claude/commands/aioson/agent/committer.md +5 -0
  250. package/template/.claude/commands/aioson/agent/copywriter.md +5 -0
  251. package/template/.claude/commands/aioson/agent/cypher.md +5 -0
  252. package/template/.claude/commands/aioson/agent/pair.md +5 -0
  253. package/template/.claude/commands/aioson/agent/validator.md +5 -0
  254. package/template/.gemini/commands/aios-analyst.toml +6 -3
  255. package/template/.gemini/commands/aios-architect.toml +7 -6
  256. package/template/.gemini/commands/aios-committer.toml +7 -0
  257. package/template/.gemini/commands/aios-copywriter.toml +7 -0
  258. package/template/.gemini/commands/aios-cypher.toml +7 -0
  259. package/template/.gemini/commands/aios-dev.toml +8 -7
  260. package/template/.gemini/commands/aios-deyvin.toml +6 -5
  261. package/template/.gemini/commands/aios-discovery-design-doc.toml +6 -3
  262. package/template/.gemini/commands/aios-genome.toml +7 -0
  263. package/template/.gemini/commands/aios-neo.toml +5 -3
  264. package/template/.gemini/commands/aios-orache.toml +7 -0
  265. package/template/.gemini/commands/aios-orchestrator.toml +8 -7
  266. package/template/.gemini/commands/aios-pair.toml +6 -5
  267. package/template/.gemini/commands/aios-pm.toml +8 -7
  268. package/template/.gemini/commands/aios-product.toml +5 -3
  269. package/template/.gemini/commands/aios-qa.toml +6 -5
  270. package/template/.gemini/commands/aios-setup.toml +5 -2
  271. package/template/.gemini/commands/aios-sheldon.toml +7 -0
  272. package/template/.gemini/commands/aios-site-forge.toml +7 -0
  273. package/template/.gemini/commands/aios-squad.toml +7 -0
  274. package/template/.gemini/commands/aios-tester.toml +6 -5
  275. package/template/.gemini/commands/aios-ux-ui.toml +8 -7
  276. package/template/.gemini/commands/aios-validator.toml +7 -0
  277. package/template/AGENTS.md +12 -1
  278. package/template/CLAUDE.md +5 -1
  279. package/template/.aioson/locales/en/agents/analyst.md +0 -244
  280. package/template/.aioson/locales/en/agents/architect.md +0 -245
  281. package/template/.aioson/locales/en/agents/dev.md +0 -397
  282. package/template/.aioson/locales/en/agents/deyvin.md +0 -137
  283. package/template/.aioson/locales/en/agents/discovery-design-doc.md +0 -27
  284. package/template/.aioson/locales/en/agents/genome.md +0 -212
  285. package/template/.aioson/locales/en/agents/neo.md +0 -8
  286. package/template/.aioson/locales/en/agents/orache.md +0 -6
  287. package/template/.aioson/locales/en/agents/orchestrator.md +0 -189
  288. package/template/.aioson/locales/en/agents/pair.md +0 -5
  289. package/template/.aioson/locales/en/agents/pm.md +0 -84
  290. package/template/.aioson/locales/en/agents/product.md +0 -378
  291. package/template/.aioson/locales/en/agents/profiler-enricher.md +0 -5
  292. package/template/.aioson/locales/en/agents/profiler-forge.md +0 -5
  293. package/template/.aioson/locales/en/agents/profiler-researcher.md +0 -5
  294. package/template/.aioson/locales/en/agents/qa.md +0 -270
  295. package/template/.aioson/locales/en/agents/setup.md +0 -421
  296. package/template/.aioson/locales/en/agents/sheldon.md +0 -455
  297. package/template/.aioson/locales/en/agents/squad.md +0 -449
  298. package/template/.aioson/locales/en/agents/tester.md +0 -6
  299. package/template/.aioson/locales/en/agents/ux-ui.md +0 -668
  300. package/template/.aioson/locales/es/agents/analyst.md +0 -225
  301. package/template/.aioson/locales/es/agents/architect.md +0 -245
  302. package/template/.aioson/locales/es/agents/dev.md +0 -370
  303. package/template/.aioson/locales/es/agents/deyvin.md +0 -99
  304. package/template/.aioson/locales/es/agents/discovery-design-doc.md +0 -21
  305. package/template/.aioson/locales/es/agents/genome.md +0 -104
  306. package/template/.aioson/locales/es/agents/neo.md +0 -50
  307. package/template/.aioson/locales/es/agents/orache.md +0 -105
  308. package/template/.aioson/locales/es/agents/orchestrator.md +0 -194
  309. package/template/.aioson/locales/es/agents/pair.md +0 -7
  310. package/template/.aioson/locales/es/agents/pm.md +0 -90
  311. package/template/.aioson/locales/es/agents/product.md +0 -372
  312. package/template/.aioson/locales/es/agents/profiler-enricher.md +0 -7
  313. package/template/.aioson/locales/es/agents/profiler-forge.md +0 -7
  314. package/template/.aioson/locales/es/agents/profiler-researcher.md +0 -7
  315. package/template/.aioson/locales/es/agents/qa.md +0 -198
  316. package/template/.aioson/locales/es/agents/setup.md +0 -405
  317. package/template/.aioson/locales/es/agents/sheldon.md +0 -309
  318. package/template/.aioson/locales/es/agents/squad.md +0 -532
  319. package/template/.aioson/locales/es/agents/tester.md +0 -9
  320. package/template/.aioson/locales/es/agents/ux-ui.md +0 -212
  321. package/template/.aioson/locales/fr/agents/analyst.md +0 -225
  322. package/template/.aioson/locales/fr/agents/architect.md +0 -245
  323. package/template/.aioson/locales/fr/agents/dev.md +0 -370
  324. package/template/.aioson/locales/fr/agents/deyvin.md +0 -99
  325. package/template/.aioson/locales/fr/agents/discovery-design-doc.md +0 -21
  326. package/template/.aioson/locales/fr/agents/genome.md +0 -104
  327. package/template/.aioson/locales/fr/agents/neo.md +0 -50
  328. package/template/.aioson/locales/fr/agents/orache.md +0 -106
  329. package/template/.aioson/locales/fr/agents/orchestrator.md +0 -194
  330. package/template/.aioson/locales/fr/agents/pair.md +0 -7
  331. package/template/.aioson/locales/fr/agents/pm.md +0 -90
  332. package/template/.aioson/locales/fr/agents/product.md +0 -372
  333. package/template/.aioson/locales/fr/agents/profiler-enricher.md +0 -7
  334. package/template/.aioson/locales/fr/agents/profiler-forge.md +0 -7
  335. package/template/.aioson/locales/fr/agents/profiler-researcher.md +0 -7
  336. package/template/.aioson/locales/fr/agents/qa.md +0 -198
  337. package/template/.aioson/locales/fr/agents/setup.md +0 -405
  338. package/template/.aioson/locales/fr/agents/sheldon.md +0 -309
  339. package/template/.aioson/locales/fr/agents/squad.md +0 -532
  340. package/template/.aioson/locales/fr/agents/tester.md +0 -9
  341. package/template/.aioson/locales/fr/agents/ux-ui.md +0 -212
  342. package/template/.aioson/locales/pt-BR/agents/analyst.md +0 -319
  343. package/template/.aioson/locales/pt-BR/agents/architect.md +0 -284
  344. package/template/.aioson/locales/pt-BR/agents/dev.md +0 -483
  345. package/template/.aioson/locales/pt-BR/agents/deyvin.md +0 -184
  346. package/template/.aioson/locales/pt-BR/agents/discovery-design-doc.md +0 -198
  347. package/template/.aioson/locales/pt-BR/agents/genome.md +0 -297
  348. package/template/.aioson/locales/pt-BR/agents/neo.md +0 -208
  349. package/template/.aioson/locales/pt-BR/agents/orache.md +0 -137
  350. package/template/.aioson/locales/pt-BR/agents/orchestrator.md +0 -324
  351. package/template/.aioson/locales/pt-BR/agents/pair.md +0 -5
  352. package/template/.aioson/locales/pt-BR/agents/pm.md +0 -182
  353. package/template/.aioson/locales/pt-BR/agents/product.md +0 -466
  354. package/template/.aioson/locales/pt-BR/agents/profiler-enricher.md +0 -5
  355. package/template/.aioson/locales/pt-BR/agents/profiler-forge.md +0 -5
  356. package/template/.aioson/locales/pt-BR/agents/profiler-researcher.md +0 -5
  357. package/template/.aioson/locales/pt-BR/agents/qa.md +0 -300
  358. package/template/.aioson/locales/pt-BR/agents/setup.md +0 -533
  359. package/template/.aioson/locales/pt-BR/agents/sheldon.md +0 -323
  360. package/template/.aioson/locales/pt-BR/agents/squad.md +0 -1330
  361. package/template/.aioson/locales/pt-BR/agents/tester.md +0 -449
  362. package/template/.aioson/locales/pt-BR/agents/ux-ui.md +0 -669
@@ -0,0 +1,691 @@
1
+ 'use strict';
2
+
3
+ const fs = require('node:fs/promises');
4
+ const path = require('node:path');
5
+ const { execFileSync } = require('node:child_process');
6
+
7
+ const DEFAULT_CONFIG_REL_PATH = '.aioson/git-guard.json';
8
+ const MANAGED_HOOK_MARKER = '# aioson-git-guard-hook';
9
+ const BACKUP_HOOK_GIT_PATH = 'hooks/pre-commit.aioson-backup';
10
+ const DEFAULT_POLICY = Object.freeze({
11
+ version: 1,
12
+ description: '',
13
+ allowPaths: [],
14
+ blockPaths: [],
15
+ allowExtensions: [],
16
+ blockExtensions: []
17
+ });
18
+
19
+ const BLOCKED_PATH_RULES = [
20
+ {
21
+ id: 'dependency_dir',
22
+ reason: 'dependency/vendor directory should not be committed',
23
+ test: (relPath) => /(^|\/)(node_modules|vendor)(\/|$)/i.test(relPath)
24
+ },
25
+ {
26
+ id: 'build_output',
27
+ reason: 'generated build output should not be committed',
28
+ test: (relPath) => /(^|\/)(dist|build|coverage|\.next|\.nuxt|\.svelte-kit|\.turbo|\.cache|\.parcel-cache|tmp|temp)(\/|$)/i.test(relPath)
29
+ },
30
+ {
31
+ id: 'session_artifact',
32
+ reason: 'runtime/session artifact should not be committed',
33
+ test: (relPath) => /(^|\/)(aioson-logs|output|media)(\/|$)/i.test(relPath)
34
+ },
35
+ {
36
+ id: 'aioson_backup',
37
+ reason: 'AIOSON backup artifact should not be committed',
38
+ test: (relPath) => /(^|\/)\.aioson\/backups(\/|$)/i.test(relPath)
39
+ },
40
+ {
41
+ id: 'env_file',
42
+ reason: 'environment file may contain secrets',
43
+ test: (relPath) => {
44
+ const base = path.posix.basename(relPath).toLowerCase();
45
+ if (!base.startsWith('.env')) return false;
46
+ return !['.env.example', '.env.sample', '.env.template', '.env.dist'].includes(base);
47
+ }
48
+ },
49
+ {
50
+ id: 'secret_file',
51
+ reason: 'secret or credential file should not be committed',
52
+ test: (relPath) => /\.(pem|key|p12|pfx|p8|keystore|mobileprovision|kdbx)$/i.test(relPath)
53
+ },
54
+ {
55
+ id: 'log_file',
56
+ reason: 'log/debug artifact should not be committed',
57
+ test: (relPath) => {
58
+ const base = path.posix.basename(relPath).toLowerCase();
59
+ return base === '.ds_store'
60
+ || base === 'npm-debug.log'
61
+ || base === 'yarn-error.log'
62
+ || base === 'pnpm-debug.log'
63
+ || /\.log$/i.test(base);
64
+ }
65
+ }
66
+ ];
67
+
68
+ const WARNING_PATH_RULES = [
69
+ {
70
+ id: 'backup_suffix',
71
+ reason: 'backup or temporary file staged',
72
+ test: (relPath) => /\.(bak|tmp|orig|rej|swp|swo|old|save)$/i.test(relPath)
73
+ },
74
+ {
75
+ id: 'scratch_name',
76
+ reason: 'draft/scratch-style file name staged',
77
+ test: (relPath) => {
78
+ const base = path.posix.basename(relPath);
79
+ return /(^|[._-])(draft|scratch|wip|junk|trash|temp|tmp)([._-]|$)/i.test(base);
80
+ }
81
+ },
82
+ {
83
+ id: 'local_database',
84
+ reason: 'local database or dump file staged',
85
+ test: (relPath) => /\.(sqlite|sqlite3|db|dump)$/i.test(relPath)
86
+ }
87
+ ];
88
+
89
+ const CONTENT_RULES = [
90
+ {
91
+ id: 'private_key_block',
92
+ severity: 'error',
93
+ reason: 'private key material detected',
94
+ pattern: /-----BEGIN(?: [A-Z0-9]+)? PRIVATE KEY-----/m
95
+ },
96
+ {
97
+ id: 'aws_access_key',
98
+ severity: 'error',
99
+ reason: 'AWS access key detected',
100
+ pattern: /\bAKIA[0-9A-Z]{16}\b/
101
+ },
102
+ {
103
+ id: 'github_token',
104
+ severity: 'error',
105
+ reason: 'GitHub token detected',
106
+ pattern: /\b(?:github_pat_[A-Za-z0-9_]{20,}|ghp_[A-Za-z0-9]{20,}|gho_[A-Za-z0-9]{20,}|ghu_[A-Za-z0-9]{20,}|ghs_[A-Za-z0-9]{20,}|ghr_[A-Za-z0-9]{20,})\b/
107
+ },
108
+ {
109
+ id: 'slack_token',
110
+ severity: 'error',
111
+ reason: 'Slack token detected',
112
+ pattern: /\bxox[baprs]-[A-Za-z0-9-]{10,}\b/
113
+ },
114
+ {
115
+ id: 'google_api_key',
116
+ severity: 'error',
117
+ reason: 'Google API key detected',
118
+ pattern: /\bAIza[0-9A-Za-z\-_]{35}\b/
119
+ },
120
+ {
121
+ id: 'stripe_secret',
122
+ severity: 'error',
123
+ reason: 'Stripe secret key detected',
124
+ pattern: /\bsk_(?:live|test)_[0-9A-Za-z]{16,}\b/
125
+ },
126
+ {
127
+ id: 'openai_secret',
128
+ severity: 'error',
129
+ reason: 'OpenAI-style secret detected',
130
+ pattern: /\bsk-[A-Za-z0-9]{20,}\b/
131
+ },
132
+ {
133
+ id: 'npm_token',
134
+ severity: 'error',
135
+ reason: 'npm token detected',
136
+ pattern: /\bnpm_[A-Za-z0-9]{20,}\b/
137
+ }
138
+ ];
139
+
140
+ // Detects literal secret assignments. Quotes are required so that function
141
+ // calls (e.g. `const token = requireToken(config)`) are not flagged: only the
142
+ // value inside the matched quote pair counts toward the 8-char minimum.
143
+ const GENERIC_SECRET_ASSIGNMENT = /\b([A-Z0-9_]*(?:SECRET|TOKEN|API_KEY|ACCESS_KEY|PRIVATE_KEY|PASSWORD|PASSWD|CLIENT_SECRET)[A-Z0-9_]*)\b\s*[:=]\s*(['"`])([^'"`\n\r]{8,})\2/gi;
144
+ const PLACEHOLDER_VALUE = /^(?:example|sample|placeholder|dummy|changeme|change-me|replace[-_]?me|your[_-]?value|your[_-]?token|test|local|localhost|xxx+)$/i;
145
+
146
+ function runGit(gitRoot, args, options = {}) {
147
+ return execFileSync('git', args, {
148
+ cwd: gitRoot,
149
+ encoding: options.encoding || 'utf8',
150
+ maxBuffer: options.maxBuffer || 8 * 1024 * 1024,
151
+ stdio: ['ignore', 'pipe', 'pipe']
152
+ });
153
+ }
154
+
155
+ function normalizeRelPath(relPath) {
156
+ return String(relPath || '').replace(/\\/g, '/').replace(/^\.\//, '');
157
+ }
158
+
159
+ function resolveGitRoot(projectDir) {
160
+ return String(runGit(projectDir, ['rev-parse', '--show-toplevel'])).trim();
161
+ }
162
+
163
+ function resolveGitPath(gitRoot, gitPath) {
164
+ const resolved = String(runGit(gitRoot, ['rev-parse', '--git-path', gitPath])).trim();
165
+ return path.isAbsolute(resolved) ? resolved : path.join(gitRoot, resolved);
166
+ }
167
+
168
+ function listStagedFiles(gitRoot) {
169
+ const output = runGit(gitRoot, ['diff', '--cached', '--name-only', '--diff-filter=ACMR', '-z'], {
170
+ encoding: 'buffer'
171
+ });
172
+ return String(output)
173
+ .split('\u0000')
174
+ .map((entry) => normalizeRelPath(entry.trim()))
175
+ .filter(Boolean);
176
+ }
177
+
178
+ function readStagedBlob(gitRoot, relPath) {
179
+ return runGit(gitRoot, ['show', `:${relPath}`], {
180
+ encoding: 'buffer',
181
+ maxBuffer: 16 * 1024 * 1024
182
+ });
183
+ }
184
+
185
+ function findLineFromIndex(text, index) {
186
+ if (index == null || index < 0) return null;
187
+ return text.slice(0, index).split('\n').length;
188
+ }
189
+
190
+ function isBinaryBuffer(buffer) {
191
+ for (let i = 0; i < buffer.length; i += 1) {
192
+ if (buffer[i] === 0) return true;
193
+ }
194
+ return false;
195
+ }
196
+
197
+ function escapeRegExp(value) {
198
+ return value.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
199
+ }
200
+
201
+ function globToRegExp(pattern) {
202
+ const normalized = normalizeRelPath(pattern).replace(/^\/+/, '');
203
+ let source = '';
204
+
205
+ for (let i = 0; i < normalized.length; i += 1) {
206
+ const char = normalized[i];
207
+ const next = normalized[i + 1];
208
+
209
+ if (char === '*') {
210
+ if (next === '*') {
211
+ source += '.*';
212
+ i += 1;
213
+ } else {
214
+ source += '[^/]*';
215
+ }
216
+ continue;
217
+ }
218
+
219
+ if (char === '?') {
220
+ source += '[^/]';
221
+ continue;
222
+ }
223
+
224
+ source += escapeRegExp(char);
225
+ }
226
+
227
+ return new RegExp(`^${source}$`, 'i');
228
+ }
229
+
230
+ function matchesAnyPattern(relPath, patterns) {
231
+ return patterns.some((pattern) => globToRegExp(pattern).test(relPath));
232
+ }
233
+
234
+ function normalizeExtension(value) {
235
+ const ext = String(value || '').trim().toLowerCase();
236
+ if (!ext) return '';
237
+ return ext.startsWith('.') ? ext : `.${ext}`;
238
+ }
239
+
240
+ function matchesAnyExtension(relPath, extensions) {
241
+ const lowered = relPath.toLowerCase();
242
+ return extensions.some((ext) => lowered.endsWith(ext));
243
+ }
244
+
245
+ function validateStringArray(data, key) {
246
+ if (data[key] == null) return [];
247
+ if (!Array.isArray(data[key])) {
248
+ throw new Error(`Invalid git guard config: "${key}" must be an array of strings`);
249
+ }
250
+ return data[key].map((item, index) => {
251
+ if (typeof item !== 'string' || item.trim().length === 0) {
252
+ throw new Error(`Invalid git guard config: "${key}[${index}]" must be a non-empty string`);
253
+ }
254
+ return key.endsWith('Extensions')
255
+ ? normalizeExtension(item)
256
+ : normalizeRelPath(item).replace(/^\/+/, '');
257
+ });
258
+ }
259
+
260
+ function resolveGuardConfigPath(projectDir, candidatePath = null) {
261
+ if (!candidatePath) return path.join(projectDir, DEFAULT_CONFIG_REL_PATH);
262
+ return path.isAbsolute(candidatePath)
263
+ ? candidatePath
264
+ : path.resolve(projectDir, String(candidatePath));
265
+ }
266
+
267
+ async function loadGuardConfig(projectDir, options = {}) {
268
+ const configPath = resolveGuardConfigPath(projectDir, options.configPath || options.config);
269
+
270
+ let raw;
271
+ try {
272
+ raw = await fs.readFile(configPath, 'utf8');
273
+ } catch (error) {
274
+ if (error && error.code === 'ENOENT') {
275
+ return {
276
+ path: configPath,
277
+ loaded: false,
278
+ config: { ...DEFAULT_POLICY }
279
+ };
280
+ }
281
+ throw error;
282
+ }
283
+
284
+ let parsed;
285
+ try {
286
+ parsed = JSON.parse(raw);
287
+ } catch (error) {
288
+ throw new Error(`Invalid git guard config at ${configPath}: ${error.message}`);
289
+ }
290
+
291
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
292
+ throw new Error(`Invalid git guard config at ${configPath}: root value must be an object`);
293
+ }
294
+
295
+ const version = parsed.version == null ? 1 : Number(parsed.version);
296
+ if (!Number.isInteger(version) || version !== 1) {
297
+ throw new Error(`Invalid git guard config at ${configPath}: unsupported version "${parsed.version}"`);
298
+ }
299
+
300
+ if (parsed.description != null && typeof parsed.description !== 'string') {
301
+ throw new Error(`Invalid git guard config at ${configPath}: "description" must be a string`);
302
+ }
303
+
304
+ return {
305
+ path: configPath,
306
+ loaded: true,
307
+ config: {
308
+ version,
309
+ description: parsed.description || '',
310
+ allowPaths: validateStringArray(parsed, 'allowPaths'),
311
+ contentAllowPaths: validateStringArray(parsed, 'contentAllowPaths'),
312
+ blockPaths: validateStringArray(parsed, 'blockPaths'),
313
+ allowExtensions: validateStringArray(parsed, 'allowExtensions'),
314
+ blockExtensions: validateStringArray(parsed, 'blockExtensions')
315
+ }
316
+ };
317
+ }
318
+
319
+ function isAllowlistedPath(relPath, policy) {
320
+ return matchesAnyPattern(relPath, policy.allowPaths) || matchesAnyExtension(relPath, policy.allowExtensions);
321
+ }
322
+
323
+ function isContentAllowlistedPath(relPath, policy) {
324
+ return matchesAnyPattern(relPath, policy.contentAllowPaths || []);
325
+ }
326
+
327
+ function collectPathFindings(relPath, rules, severity) {
328
+ const findings = [];
329
+ for (const rule of rules) {
330
+ if (!rule.test(relPath)) continue;
331
+ findings.push({
332
+ type: 'path',
333
+ severity,
334
+ id: rule.id,
335
+ path: relPath,
336
+ reason: rule.reason,
337
+ line: null
338
+ });
339
+ }
340
+ return findings;
341
+ }
342
+
343
+ function collectConfiguredPathFindings(relPath, policy) {
344
+ const findings = [];
345
+
346
+ if (matchesAnyPattern(relPath, policy.blockPaths)) {
347
+ findings.push({
348
+ type: 'path',
349
+ severity: 'error',
350
+ id: 'config_block_path',
351
+ path: relPath,
352
+ reason: 'project git guard policy blocks this path',
353
+ line: null
354
+ });
355
+ }
356
+
357
+ if (matchesAnyExtension(relPath, policy.blockExtensions)) {
358
+ findings.push({
359
+ type: 'path',
360
+ severity: 'error',
361
+ id: 'config_block_extension',
362
+ path: relPath,
363
+ reason: 'project git guard policy blocks this file extension',
364
+ line: null
365
+ });
366
+ }
367
+
368
+ return findings;
369
+ }
370
+
371
+ function collectContentFindings(relPath, text) {
372
+ const findings = [];
373
+
374
+ for (const rule of CONTENT_RULES) {
375
+ const match = text.match(rule.pattern);
376
+ if (!match || match.index == null) continue;
377
+ findings.push({
378
+ type: 'content',
379
+ severity: rule.severity,
380
+ id: rule.id,
381
+ path: relPath,
382
+ reason: rule.reason,
383
+ line: findLineFromIndex(text, match.index)
384
+ });
385
+ }
386
+
387
+ let genericMatch;
388
+ GENERIC_SECRET_ASSIGNMENT.lastIndex = 0;
389
+ while ((genericMatch = GENERIC_SECRET_ASSIGNMENT.exec(text)) !== null) {
390
+ const variableName = String(genericMatch[1] || '');
391
+ const value = String(genericMatch[3] || '');
392
+ const lowered = value.toLowerCase();
393
+ if (PLACEHOLDER_VALUE.test(value)) continue;
394
+ if (/(example|sample|dummy|placeholder|changeme|localhost|local[_-]?dev)/i.test(lowered)) continue;
395
+ if (/(public|publishable)/i.test(variableName)) continue;
396
+ findings.push({
397
+ type: 'content',
398
+ severity: 'warning',
399
+ id: 'generic_secret_assignment',
400
+ path: relPath,
401
+ reason: `possible secret assignment detected for ${variableName}`,
402
+ line: findLineFromIndex(text, genericMatch.index)
403
+ });
404
+ }
405
+
406
+ return findings;
407
+ }
408
+
409
+ function buildSuggestedCommands(findings) {
410
+ const paths = [...new Set(findings.map((item) => item.path).filter(Boolean))];
411
+ if (paths.length === 0) return [];
412
+ return paths.map((relPath) => `git restore --staged -- "${relPath}"`);
413
+ }
414
+
415
+ function summarizePolicy(policyState) {
416
+ return {
417
+ path: policyState.path,
418
+ loaded: policyState.loaded,
419
+ version: policyState.config.version,
420
+ allowPathsCount: policyState.config.allowPaths.length,
421
+ blockPathsCount: policyState.config.blockPaths.length,
422
+ allowExtensionsCount: policyState.config.allowExtensions.length,
423
+ blockExtensionsCount: policyState.config.blockExtensions.length
424
+ };
425
+ }
426
+
427
+ async function inspectStagedChanges(projectDir, options = {}) {
428
+ const gitRoot = resolveGitRoot(projectDir);
429
+ const policyState = await loadGuardConfig(gitRoot, options);
430
+ const stagedFiles = listStagedFiles(gitRoot);
431
+ const allowWarnings = Boolean(options.allowWarnings);
432
+ const findings = [];
433
+ const files = [];
434
+
435
+ for (const relPath of stagedFiles) {
436
+ const allowlisted = isAllowlistedPath(relPath, policyState.config);
437
+ const fileFindings = [
438
+ ...(allowlisted ? [] : collectPathFindings(relPath, BLOCKED_PATH_RULES, 'error')),
439
+ ...(allowlisted ? [] : collectPathFindings(relPath, WARNING_PATH_RULES, 'warning')),
440
+ ...collectConfiguredPathFindings(relPath, policyState.config)
441
+ ];
442
+
443
+ let size = 0;
444
+ let binary = false;
445
+ try {
446
+ const buffer = readStagedBlob(gitRoot, relPath);
447
+ size = buffer.length;
448
+ binary = isBinaryBuffer(buffer);
449
+ if (!binary && !isContentAllowlistedPath(relPath, policyState.config)) {
450
+ const text = buffer.toString('utf8');
451
+ fileFindings.push(...collectContentFindings(relPath, text));
452
+ }
453
+ } catch (error) {
454
+ fileFindings.push({
455
+ type: 'content',
456
+ severity: 'error',
457
+ id: 'staged_read_failed',
458
+ path: relPath,
459
+ reason: `failed to inspect staged content: ${error.message}`,
460
+ line: null
461
+ });
462
+ }
463
+
464
+ findings.push(...fileFindings);
465
+ files.push({
466
+ path: relPath,
467
+ size,
468
+ binary,
469
+ allowlisted,
470
+ findings: fileFindings
471
+ });
472
+ }
473
+
474
+ const errors = findings.filter((item) => item.severity === 'error');
475
+ const warnings = findings.filter((item) => item.severity === 'warning');
476
+ const ok = stagedFiles.length > 0 && errors.length === 0 && (allowWarnings || warnings.length === 0);
477
+
478
+ return {
479
+ ok,
480
+ gitRoot,
481
+ stagedFiles,
482
+ strict: !allowWarnings,
483
+ policy: summarizePolicy(policyState),
484
+ files,
485
+ errors,
486
+ warnings,
487
+ suggestedCommands: buildSuggestedCommands([...errors, ...warnings]),
488
+ summary: {
489
+ stagedCount: stagedFiles.length,
490
+ errorCount: errors.length,
491
+ warningCount: warnings.length
492
+ }
493
+ };
494
+ }
495
+
496
+ function isManagedHook(content) {
497
+ return String(content || '').includes(MANAGED_HOOK_MARKER);
498
+ }
499
+
500
+ function buildPreCommitHookScript({ backupPath = '' } = {}) {
501
+ const backupLiteral = backupPath ? JSON.stringify(backupPath) : '""';
502
+
503
+ return `#!/bin/sh
504
+ ${MANAGED_HOOK_MARKER}
505
+ # Managed by: aioson git:guard --install-hook
506
+
507
+ set -eu
508
+
509
+ GIT_ROOT="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
510
+ HOOK_BACKUP=${backupLiteral}
511
+
512
+ run_guard() {
513
+ if command -v aioson >/dev/null 2>&1; then
514
+ aioson git:guard "$GIT_ROOT"
515
+ return $?
516
+ fi
517
+
518
+ if [ -x "$GIT_ROOT/node_modules/.bin/aioson" ]; then
519
+ "$GIT_ROOT/node_modules/.bin/aioson" git:guard "$GIT_ROOT"
520
+ return $?
521
+ fi
522
+
523
+ if [ -f "$GIT_ROOT/bin/aioson.js" ]; then
524
+ node "$GIT_ROOT/bin/aioson.js" git:guard "$GIT_ROOT"
525
+ return $?
526
+ fi
527
+
528
+ echo "AIOSON pre-commit hook blocked this commit: aioson CLI was not found." >&2
529
+ echo "Install AIOSON CLI or remove the hook with 'aioson git:guard <project> --uninstall-hook'." >&2
530
+ return 1
531
+ }
532
+
533
+ run_guard
534
+
535
+ if [ -n "$HOOK_BACKUP" ] && [ -x "$HOOK_BACKUP" ]; then
536
+ "$HOOK_BACKUP" "$@"
537
+ fi
538
+ `;
539
+ }
540
+
541
+ async function exists(filePath) {
542
+ try {
543
+ await fs.access(filePath);
544
+ return true;
545
+ } catch {
546
+ return false;
547
+ }
548
+ }
549
+
550
+ async function installPreCommitHook(projectDir, options = {}) {
551
+ const gitRoot = resolveGitRoot(projectDir);
552
+ const hookPath = resolveGitPath(gitRoot, 'hooks/pre-commit');
553
+ const backupPath = resolveGitPath(gitRoot, BACKUP_HOOK_GIT_PATH);
554
+ const dryRun = Boolean(options.dryRun || options['dry-run']);
555
+ const force = Boolean(options.force);
556
+
557
+ let existingContent = null;
558
+ try {
559
+ existingContent = await fs.readFile(hookPath, 'utf8');
560
+ } catch (error) {
561
+ if (!error || error.code !== 'ENOENT') throw error;
562
+ }
563
+
564
+ const hookExists = typeof existingContent === 'string';
565
+ const managedExistingHook = isManagedHook(existingContent);
566
+ const backupExists = await exists(backupPath);
567
+
568
+ if (hookExists && !managedExistingHook && !force) {
569
+ return {
570
+ ok: false,
571
+ error: 'hook_exists',
572
+ message: 'A non-AIOSON pre-commit hook already exists. Re-run with --force to back it up and chain it.',
573
+ gitRoot,
574
+ hookPath,
575
+ backupPath
576
+ };
577
+ }
578
+
579
+ if (hookExists && !managedExistingHook && force && backupExists) {
580
+ return {
581
+ ok: false,
582
+ error: 'hook_backup_exists',
583
+ message: 'Cannot back up the existing pre-commit hook because an AIOSON backup hook already exists.',
584
+ gitRoot,
585
+ hookPath,
586
+ backupPath
587
+ };
588
+ }
589
+
590
+ const shouldChainBackup = backupExists || (hookExists && !managedExistingHook && force);
591
+ const script = buildPreCommitHookScript({
592
+ backupPath: shouldChainBackup ? backupPath : ''
593
+ });
594
+
595
+ if (!dryRun) {
596
+ await fs.mkdir(path.dirname(hookPath), { recursive: true });
597
+ if (hookExists && !managedExistingHook && force) {
598
+ await fs.rename(hookPath, backupPath);
599
+ }
600
+ await fs.writeFile(hookPath, script, 'utf8');
601
+ await fs.chmod(hookPath, 0o755);
602
+ }
603
+
604
+ return {
605
+ ok: true,
606
+ gitRoot,
607
+ hookPath,
608
+ backupPath,
609
+ installed: !dryRun,
610
+ dryRun,
611
+ replacedExistingHook: hookExists,
612
+ backedUpExistingHook: hookExists && !managedExistingHook && force,
613
+ chainedBackupHook: shouldChainBackup
614
+ };
615
+ }
616
+
617
+ async function uninstallPreCommitHook(projectDir, options = {}) {
618
+ const gitRoot = resolveGitRoot(projectDir);
619
+ const hookPath = resolveGitPath(gitRoot, 'hooks/pre-commit');
620
+ const backupPath = resolveGitPath(gitRoot, BACKUP_HOOK_GIT_PATH);
621
+ const dryRun = Boolean(options.dryRun || options['dry-run']);
622
+
623
+ let hookContent = null;
624
+ try {
625
+ hookContent = await fs.readFile(hookPath, 'utf8');
626
+ } catch (error) {
627
+ if (!error || error.code !== 'ENOENT') throw error;
628
+ }
629
+
630
+ if (hookContent == null) {
631
+ return {
632
+ ok: true,
633
+ gitRoot,
634
+ hookPath,
635
+ backupPath,
636
+ removed: false,
637
+ restoredBackup: false,
638
+ message: 'No pre-commit hook is installed.'
639
+ };
640
+ }
641
+
642
+ if (!isManagedHook(hookContent)) {
643
+ return {
644
+ ok: false,
645
+ error: 'hook_not_managed',
646
+ message: 'The current pre-commit hook is not managed by AIOSON.',
647
+ gitRoot,
648
+ hookPath,
649
+ backupPath
650
+ };
651
+ }
652
+
653
+ const backupExists = await exists(backupPath);
654
+ if (!dryRun) {
655
+ await fs.unlink(hookPath);
656
+ if (backupExists) {
657
+ await fs.rename(backupPath, hookPath);
658
+ await fs.chmod(hookPath, 0o755);
659
+ }
660
+ }
661
+
662
+ return {
663
+ ok: true,
664
+ gitRoot,
665
+ hookPath,
666
+ backupPath,
667
+ removed: !dryRun,
668
+ restoredBackup: backupExists,
669
+ dryRun
670
+ };
671
+ }
672
+
673
+ module.exports = {
674
+ inspectStagedChanges,
675
+ resolveGitRoot,
676
+ resolveGitPath,
677
+ resolveGuardConfigPath,
678
+ loadGuardConfig,
679
+ listStagedFiles,
680
+ readStagedBlob,
681
+ normalizeRelPath,
682
+ installPreCommitHook,
683
+ uninstallPreCommitHook,
684
+ buildPreCommitHookScript,
685
+ isManagedHook,
686
+ BLOCKED_PATH_RULES,
687
+ WARNING_PATH_RULES,
688
+ DEFAULT_CONFIG_REL_PATH,
689
+ BACKUP_HOOK_GIT_PATH,
690
+ MANAGED_HOOK_MARKER
691
+ };