kortext 2.2.3 → 3.1.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 (275) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +175 -65
  3. package/agents/architect.md +85 -0
  4. package/agents/compliance-expert.md +36 -75
  5. package/agents/copywriter.md +36 -92
  6. package/agents/db-admin.md +43 -91
  7. package/agents/designer.md +40 -77
  8. package/agents/devops-engineer.md +37 -113
  9. package/agents/growth-expert.md +45 -88
  10. package/agents/product-manager.md +38 -83
  11. package/agents/qa-engineer.md +51 -99
  12. package/agents/security-engineer.md +47 -92
  13. package/bin/kortext.js +2 -0
  14. package/dist/app.js +829 -0
  15. package/dist/cli-spawn.js +137 -0
  16. package/dist/daemon.js +40 -0
  17. package/dist/db.js +78 -0
  18. package/dist/design-preview.js +533 -0
  19. package/dist/docs.js +701 -0
  20. package/dist/engines.js +225 -0
  21. package/dist/index.js +109 -0
  22. package/dist/pick-directory.js +38 -0
  23. package/dist/projects.js +189 -0
  24. package/dist/readiness.js +288 -0
  25. package/dist/runner.js +771 -0
  26. package/dist/update.js +55 -0
  27. package/docs/CHANGELOG.md +11 -0
  28. package/package.json +53 -13
  29. package/templates/AGENTS.md +62 -0
  30. package/templates/docs/API.md +67 -0
  31. package/templates/docs/ARCHITECTURE.md +31 -0
  32. package/templates/docs/BRIEF.md +27 -0
  33. package/templates/docs/CONTENT.md +66 -0
  34. package/templates/docs/DATABASE.md +63 -0
  35. package/templates/docs/DESIGN.md +124 -0
  36. package/templates/docs/ENGINEERING.md +66 -0
  37. package/templates/docs/ENVIRONMENT.md +102 -0
  38. package/templates/docs/GROWTH.md +44 -0
  39. package/templates/docs/LEGAL.md +48 -0
  40. package/templates/docs/PRODUCT.md +43 -0
  41. package/templates/docs/SECURITY.md +65 -0
  42. package/templates/docs/STACK.md +57 -0
  43. package/templates/docs/STRUCTURE.md +73 -0
  44. package/templates/docs/TEST.md +62 -0
  45. package/ui/dist/assets/abnfDiagram-VCTEODGH-Bm7Q2BML.js +1 -0
  46. package/ui/dist/assets/arc-BV9hs0Hu.js +1 -0
  47. package/ui/dist/assets/architecture-7GRP2DOG-RckR3gBI.js +1 -0
  48. package/ui/dist/assets/architectureDiagram-5GKGNRK7-CHiJ6JFT.js +36 -0
  49. package/ui/dist/assets/array-BifhSqXX.js +1 -0
  50. package/ui/dist/assets/blockDiagram-I7D4REHJ-qCbaKVhd.js +129 -0
  51. package/ui/dist/assets/c4Diagram-7LVT6UL2-DidCDJrv.js +38 -0
  52. package/ui/dist/assets/channel-CNUQ11ne.js +1 -0
  53. package/ui/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
  54. package/ui/dist/assets/chunk-4HAMMTFA-CDGFjfVp.js +62 -0
  55. package/ui/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
  56. package/ui/dist/assets/chunk-75Z2AOVW-CbNT4sDh.js +2 -0
  57. package/ui/dist/assets/chunk-DU6HZSFF-Cq2BVv26.js +127 -0
  58. package/ui/dist/assets/chunk-F27PBJKO-CE365m0v.js +1 -0
  59. package/ui/dist/assets/chunk-FOHPRMQF-DHwB1DNv.js +161 -0
  60. package/ui/dist/assets/chunk-GMAD6QVW-DxWL1VC-.js +72 -0
  61. package/ui/dist/assets/chunk-GVQU2GXP-aARV8YbK.js +1 -0
  62. package/ui/dist/assets/chunk-IMKFNOWR-CC790Kt2.js +231 -0
  63. package/ui/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
  64. package/ui/dist/assets/chunk-L3NEJ4N5-BfE4VE8J.js +1 -0
  65. package/ui/dist/assets/chunk-OSK3NFVY-BImQbSWH.js +10 -0
  66. package/ui/dist/assets/chunk-P2QGCYS3-CN3XD313.js +1 -0
  67. package/ui/dist/assets/chunk-POPQ4Y6H-Dqb6jOrX.js +1 -0
  68. package/ui/dist/assets/chunk-PWAF6VOD-DfXX6rBk.js +1 -0
  69. package/ui/dist/assets/chunk-SHT3W25Y-DATyyqPK.js +168 -0
  70. package/ui/dist/assets/chunk-SVP7TREG-r1chwRmg.js +88 -0
  71. package/ui/dist/assets/chunk-TICWLB2K-DL4atHUC.js +206 -0
  72. package/ui/dist/assets/chunk-XXDRQBXY-DXrM6jh7.js +1 -0
  73. package/ui/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
  74. package/ui/dist/assets/classDiagram-ZZMXUADV-BpQxfIQ7.js +1 -0
  75. package/ui/dist/assets/classDiagram-v2-VYDZK3BY-BpQxfIQ7.js +1 -0
  76. package/ui/dist/assets/cose-bilkent-JH36ORCC-Bhe-ZBlS.js +1 -0
  77. package/ui/dist/assets/cynefin-OW5HDTMX-D1koLd-G.js +1 -0
  78. package/ui/dist/assets/cynefinDiagram-5FMLGOSQ-BMcw40hV.js +62 -0
  79. package/ui/dist/assets/cytoscape.esm-CGd-uY3x.js +321 -0
  80. package/ui/dist/assets/dagre-GXQ25YYZ-DBun-Rs2.js +4 -0
  81. package/ui/dist/assets/dagre-PrKaheQc.js +1 -0
  82. package/ui/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
  83. package/ui/dist/assets/diagram-S7CK7UJ4-C1fUIGvo.js +30 -0
  84. package/ui/dist/assets/diagram-UQ7AKVKN-B5FDfkvB.js +41 -0
  85. package/ui/dist/assets/diagram-VSXAHHWV-CCIpOCNC.js +3 -0
  86. package/ui/dist/assets/diagram-VX7I27RA-DV0q2xo_.js +24 -0
  87. package/ui/dist/assets/diagram-Z3DM3KII-B3LCdNgI.js +24 -0
  88. package/ui/dist/assets/dist-EcW76Zvr.js +1 -0
  89. package/ui/dist/assets/ebnfDiagram-PWID7BFC-De_oRbYe.js +1 -0
  90. package/ui/dist/assets/erDiagram-RLTQ6QDP-B9NikMr1.js +99 -0
  91. package/ui/dist/assets/eventmodeling-NTZA5JFV-DZ4t581k.js +1 -0
  92. package/ui/dist/assets/flowDiagram-HODETNUW-DuTOJp1C.js +1 -0
  93. package/ui/dist/assets/ganttDiagram-EL5Y4UJY-BVTqqlPw.js +292 -0
  94. package/ui/dist/assets/gitGraph-4MIJSDKK-xRFaEJdZ.js +1 -0
  95. package/ui/dist/assets/gitGraphDiagram-WWUBYQGX-CK5_GCmX.js +106 -0
  96. package/ui/dist/assets/graphlib-DS17s2tU.js +1 -0
  97. package/ui/dist/assets/index-CjkpNftW.js +46 -0
  98. package/ui/dist/assets/index-DVIDTWin.css +1 -0
  99. package/ui/dist/assets/info-A6RAGUB7-Dg7z-CTI.js +1 -0
  100. package/ui/dist/assets/infoDiagram-27XIBGKW-YaqsOXh0.js +2 -0
  101. package/ui/dist/assets/init-C-OQMol4.js +1 -0
  102. package/ui/dist/assets/ishikawaDiagram-5VMMS53U-C-wtxZPa.js +70 -0
  103. package/ui/dist/assets/journeyDiagram-3NMN7TZE-DsXSXf16.js +139 -0
  104. package/ui/dist/assets/kanban-definition-UXKFOSKX-Ct59fuWk.js +89 -0
  105. package/ui/dist/assets/katex-CXMH3UgJ.js +257 -0
  106. package/ui/dist/assets/line-BUVw9Mhs.js +1 -0
  107. package/ui/dist/assets/linear-D8ncLO0X.js +1 -0
  108. package/ui/dist/assets/mermaid-parser.core-BDC4bG-O.js +7 -0
  109. package/ui/dist/assets/mermaid.core-DZY3WwzX.js +44 -0
  110. package/ui/dist/assets/mindmap-definition-YA3MSWOX-DtiuF6Ce.js +96 -0
  111. package/ui/dist/assets/ordinal-BDEzSJ7C.js +1 -0
  112. package/ui/dist/assets/packet-AYTQ26CC-BX2-w9rD.js +1 -0
  113. package/ui/dist/assets/path-fybaL0A-.js +1 -0
  114. package/ui/dist/assets/pegDiagram-XKGWAZYB-CotwuIHB.js +1 -0
  115. package/ui/dist/assets/pie-WAS4IAKB-Hth8ZVpm.js +1 -0
  116. package/ui/dist/assets/pieDiagram-E7YTZNPT-D9-INs8B.js +39 -0
  117. package/ui/dist/assets/quadrantDiagram-AXDQQJYC-CMtBXZGV.js +7 -0
  118. package/ui/dist/assets/radar-RG4KPBEZ-6UuwQPsw.js +1 -0
  119. package/ui/dist/assets/railroad-74A4TZTK-BF5TZgj0.js +1 -0
  120. package/ui/dist/assets/railroad-abnf-HS5TGJTU-3KYovtYT.js +1 -0
  121. package/ui/dist/assets/railroad-ebnf-LZEXJU2U-DeP4WedE.js +1 -0
  122. package/ui/dist/assets/railroad-peg-WCYAUIDC-i42N1-yk.js +1 -0
  123. package/ui/dist/assets/railroadDiagram-O6MQD6OU-BS6zkU3Z.js +1 -0
  124. package/ui/dist/assets/requirementDiagram-BXWQKSXE-J4IOB-kP.js +84 -0
  125. package/ui/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
  126. package/ui/dist/assets/sankeyDiagram-P5KCCOFB-jYTCuYzQ.js +40 -0
  127. package/ui/dist/assets/sequenceDiagram-WJ2MYXX4-Cd2r6PW-.js +162 -0
  128. package/ui/dist/assets/sizeCapture-INFHLROL-B0uUizjq.js +1 -0
  129. package/ui/dist/assets/src-7TsfZXRO.js +1 -0
  130. package/ui/dist/assets/stateDiagram-D77RDMKH-DXOdtOKe.js +1 -0
  131. package/ui/dist/assets/stateDiagram-v2-MP3YSRHH-9h7sIf3R.js +1 -0
  132. package/ui/dist/assets/swimlanes-42K2YHIH-BvJGmGs1.js +1 -0
  133. package/ui/dist/assets/swimlanesDiagram-VR7AAH4N-QA4gPi6a.js +8 -0
  134. package/ui/dist/assets/timeline-definition-24CTP7MA-CAaD6-yd.js +120 -0
  135. package/ui/dist/assets/treeView-Q6P3EWNA-4gDEXIyJ.js +1 -0
  136. package/ui/dist/assets/treemap-WGGIJYW6-CGel5TwW.js +1 -0
  137. package/ui/dist/assets/vennDiagram-4TSXK5OY-DpxsKboL.js +34 -0
  138. package/ui/dist/assets/wardley-WFR3VGLG-Ck38v5ns.js +1 -0
  139. package/ui/dist/assets/wardleyDiagram-VM6X3IG4-Ba0grrqs.js +78 -0
  140. package/ui/dist/assets/xychartDiagram-S5SC5T6Z-DH_gC96K.js +7 -0
  141. package/ui/dist/index.html +13 -0
  142. package/ui/dist/kortext-logo-dark.svg +41 -0
  143. package/ui/dist/kortext-logo.svg +41 -0
  144. package/workflows/existing-project-analysis.md +87 -0
  145. package/workflows/new-project-analysis.md +98 -0
  146. package/workflows/planning-pipeline.md +61 -0
  147. package/AGENTS.md +0 -28
  148. package/agents/backend-developer.md +0 -132
  149. package/agents/delivery-manager.md +0 -121
  150. package/agents/engineering-manager.md +0 -208
  151. package/agents/frontend-developer.md +0 -174
  152. package/agents/operation-manager.md +0 -122
  153. package/hooks/audit-logger.sh +0 -25
  154. package/hooks/auto-locker.sh +0 -74
  155. package/hooks/auto-unlocker.sh +0 -17
  156. package/hooks/backlog-sync-guard.sh +0 -12
  157. package/hooks/branch-guard.sh +0 -32
  158. package/hooks/commit-msg-guard.sh +0 -43
  159. package/hooks/git-pre-commit.sh +0 -76
  160. package/hooks/git-pre-push.sh +0 -17
  161. package/hooks/handover-guard.sh +0 -33
  162. package/hooks/kortext-init.sh +0 -194
  163. package/hooks/kortext-lib.sh +0 -151
  164. package/hooks/lint-guard.sh +0 -50
  165. package/hooks/secret-scanner.sh +0 -89
  166. package/hooks/size-guard.sh +0 -48
  167. package/hooks/snapshot-guard.sh +0 -39
  168. package/hooks/write-guard.sh +0 -77
  169. package/rules/behavior.md +0 -252
  170. package/rules/branching.md +0 -82
  171. package/rules/commands.md +0 -112
  172. package/rules/emergency.md +0 -160
  173. package/rules/mcp.md +0 -47
  174. package/rules/models.md +0 -93
  175. package/scripts/kortext-backlog-add.py +0 -123
  176. package/scripts/kortext-backlog-health.py +0 -112
  177. package/scripts/kortext-backlog-sync.py +0 -117
  178. package/scripts/kortext-bulk-plan.py +0 -63
  179. package/scripts/kortext-cli.py +0 -376
  180. package/scripts/kortext-consistency-check.py +0 -74
  181. package/scripts/kortext-context-check.py +0 -146
  182. package/scripts/kortext-handover.py +0 -107
  183. package/scripts/kortext-item-check.py +0 -61
  184. package/scripts/kortext-item-start.py +0 -70
  185. package/scripts/kortext-item-transition.py +0 -78
  186. package/scripts/kortext-lock.py +0 -174
  187. package/scripts/kortext-session-start.py +0 -127
  188. package/scripts/lock_kortext.sh +0 -34
  189. package/settings/.claude-settings.template.json +0 -48
  190. package/settings/CHANGELOG.md +0 -176
  191. package/settings/INTEGRATION-MAP.md +0 -276
  192. package/settings/README.md +0 -255
  193. package/settings/USER-GUIDE.md +0 -502
  194. package/settings/VERSION +0 -1
  195. package/settings/config.md +0 -8
  196. package/settings/runtime-adapters.md +0 -104
  197. package/skills/backend-developer/.gitkeep +0 -0
  198. package/skills/compliance-expert/.gitkeep +0 -0
  199. package/skills/copywriter/.gitkeep +0 -0
  200. package/skills/db-admin/.gitkeep +0 -0
  201. package/skills/delivery-manager/.gitkeep +0 -0
  202. package/skills/designer/.gitkeep +0 -0
  203. package/skills/devops-engineer/.gitkeep +0 -0
  204. package/skills/engineering-manager/.gitkeep +0 -0
  205. package/skills/frontend-developer/.gitkeep +0 -0
  206. package/skills/growth-expert/.gitkeep +0 -0
  207. package/skills/operation-manager/.gitkeep +0 -0
  208. package/skills/product-manager/.gitkeep +0 -0
  209. package/skills/qa-engineer/.gitkeep +0 -0
  210. package/skills/security-engineer/.gitkeep +0 -0
  211. package/workflows/00-kortext-setup.md +0 -53
  212. package/workflows/01a-analysis-pipeline.md +0 -117
  213. package/workflows/01b-onboarding-pipeline.md +0 -110
  214. package/workflows/02-planning-pipeline.md +0 -146
  215. package/workflows/02b-spike-workflow.md +0 -120
  216. package/workflows/03-environment-setup.md +0 -99
  217. package/workflows/04-development-cycle.md +0 -203
  218. package/workflows/05-test-cycle.md +0 -123
  219. package/workflows/06-deployment-cycle.md +0 -224
  220. package/workflows/07-rollback-pipeline.md +0 -203
  221. package/workflows/08-hotfix-pipeline.md +0 -196
  222. package/workflows/09-maintenance-cycle.md +0 -133
  223. package/workspace/archive/.gitkeep +0 -0
  224. package/workspace/backups/.gitkeep +0 -0
  225. package/workspace/memory/backlog/.gitkeep +0 -0
  226. package/workspace/memory/backlog/BXX-[bug-name].md +0 -2
  227. package/workspace/memory/backlog/DXX-[debt-name].md +0 -2
  228. package/workspace/memory/backlog/README.md +0 -31
  229. package/workspace/memory/backlog/TXX-[task-name].md +0 -2
  230. package/workspace/memory/backlog/debt-dashboard.md +0 -3
  231. package/workspace/memory/backlog/epic-dashboard.md +0 -10
  232. package/workspace/memory/backlog/version-dashboard.md +0 -7
  233. package/workspace/memory/context/.gitkeep +0 -0
  234. package/workspace/memory/context/README.md +0 -35
  235. package/workspace/memory/context/[agent-name]-active.md +0 -2
  236. package/workspace/memory/decisions.md +0 -17
  237. package/workspace/memory/handover.md +0 -40
  238. package/workspace/memory/learned.md +0 -21
  239. package/workspace/references/access.md +0 -47
  240. package/workspace/references/api-reference.md +0 -59
  241. package/workspace/references/blueprint.md +0 -29
  242. package/workspace/references/content-strategy.md +0 -39
  243. package/workspace/references/db-schema.md +0 -51
  244. package/workspace/references/design-system.md +0 -107
  245. package/workspace/references/dictionary.md +0 -42
  246. package/workspace/references/file-system.md +0 -42
  247. package/workspace/references/growth-strategy.md +0 -46
  248. package/workspace/references/legal-strategy.md +0 -35
  249. package/workspace/references/required-skills.md +0 -11
  250. package/workspace/references/security-rules.md +0 -34
  251. package/workspace/references/tech-stack.md +0 -49
  252. package/workspace/references/test-strategy.md +0 -48
  253. package/workspace/reports/analysis-reports.md +0 -40
  254. package/workspace/reports/content-reports.md +0 -29
  255. package/workspace/reports/delivery-reports.md +0 -26
  256. package/workspace/reports/growth-reports.md +0 -9
  257. package/workspace/reports/legal-reports.md +0 -29
  258. package/workspace/reports/product-requirements.md +0 -28
  259. package/workspace/reports/release-notes.md +0 -20
  260. package/workspace/reports/security-reports.md +0 -27
  261. package/workspace/reports/status-reports.md +0 -48
  262. package/workspace/reports/tech-requirements.md +0 -30
  263. package/workspace/reports/test-reports.md +0 -61
  264. package/workspace/templates/BXX-[bug-name].md +0 -34
  265. package/workspace/templates/DXX-[debt-name].md +0 -35
  266. package/workspace/templates/EXX-[epic-name].md +0 -48
  267. package/workspace/templates/HXX-[hotfix-name].md +0 -73
  268. package/workspace/templates/SXX-[spike-name].md +0 -51
  269. package/workspace/templates/TXX-[task-name].md +0 -34
  270. package/workspace/templates/[agent-name]-active.md +0 -4
  271. package/workspace/templates/[original-name]_[YYYY-MM-DD_HHMMSS].md +0 -5
  272. package/workspace/templates/adr-XXX.md +0 -66
  273. package/workspace/templates/debt-dashboard.md +0 -10
  274. package/workspace/templates/epic-dashboard.md +0 -10
  275. package/workspace/templates/version-dashboard.md +0 -10
@@ -0,0 +1,288 @@
1
+ import { createHash } from 'node:crypto';
2
+ import { existsSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
3
+ import { join } from 'node:path';
4
+ import { spawnCli } from './cli-spawn.js';
5
+ import { logPathFor } from './db.js';
6
+ import { readFrontmatter, setFrontmatterStatus } from './docs.js';
7
+ import { engineArgs, engineEnv } from './engines.js';
8
+ const CACHE_REL = join('.kortext', '.readiness.json');
9
+ const JUDGMENT_TIMEOUT_MS = 5 * 60 * 1000;
10
+ // Use the template questions in their original order when the local content check fails.
11
+ const BRIEF_SECTIONS = [
12
+ {
13
+ heading: /vision|goal/i,
14
+ ask: 'Product Vision & Goals — what is this product, and why does it exist?',
15
+ },
16
+ { heading: /audience|persona/i, ask: 'Target Audience & Personas — who is it for?' },
17
+ {
18
+ heading: /language|locali[sz]|locale/i,
19
+ ask: 'Interface Language — which language does the product speak to its users, and is more than one in scope?',
20
+ },
21
+ {
22
+ heading: /kpi|performance indicator/i,
23
+ ask: 'Key Performance Indicators — what does success look like?',
24
+ },
25
+ { heading: /scope/i, ask: 'Future Scope & Out of Scope — what is deliberately not in it?' },
26
+ ];
27
+ // Minimum non-placeholder prose length for the brief.
28
+ const MIN_BODY_CHARS = 240;
29
+ function stripFrontmatter(content) {
30
+ if (!content.startsWith('---'))
31
+ return content;
32
+ const end = content.indexOf('\n---', 3);
33
+ return end === -1 ? content : content.slice(end + 4);
34
+ }
35
+ // Skeleton lines are not content: headings announce a section, and a bracket
36
+ // line ("- [What is out of scope?]") is the template asking the question the
37
+ // brief was supposed to answer.
38
+ function isSkeletonLine(line) {
39
+ const t = line.trim();
40
+ return t === '' || t.startsWith('#') || t.startsWith('>') || /^[-*]?\s*\[[^\]]*\]$/.test(t);
41
+ }
42
+ /**
43
+ * Check prose length after excluding headings and template placeholders.
44
+ * Require populated template sections only when most English template headings are present;
45
+ * briefs using other headings are assessed by prose length and the later CLI judgment.
46
+ */
47
+ export function assessBrief(content) {
48
+ const body = stripFrontmatter(content);
49
+ const lines = body.split('\n');
50
+ const prose = lines
51
+ .filter((l) => !isSkeletonLine(l))
52
+ .join(' ')
53
+ .trim();
54
+ const allAsks = BRIEF_SECTIONS.map((s) => s.ask);
55
+ if (prose.length < MIN_BODY_CHARS)
56
+ return { ok: false, questions: allAsks };
57
+ // Which of the template's sections are present, and which carry content.
58
+ const present = new Set();
59
+ const answered = new Set();
60
+ let current = null;
61
+ for (const line of lines) {
62
+ const heading = line.match(/^#{1,6}\s+(.*)$/);
63
+ if (heading) {
64
+ current = BRIEF_SECTIONS.find((s) => s.heading.test(heading[1]))?.ask ?? null;
65
+ if (current)
66
+ present.add(current);
67
+ continue;
68
+ }
69
+ if (current && !isSkeletonLine(line))
70
+ answered.add(current);
71
+ }
72
+ // Require most template headings to match before reporting missing sections.
73
+ // This avoids treating incidental heading matches in other languages as an incomplete template.
74
+ const templateShaped = present.size >= BRIEF_SECTIONS.length - 1;
75
+ if (!templateShaped)
76
+ return { ok: true, questions: [] };
77
+ const unanswered = BRIEF_SECTIONS.filter((s) => !answered.has(s.ask)).map((s) => s.ask);
78
+ return unanswered.length === 0
79
+ ? { ok: true, questions: [] }
80
+ : { ok: false, questions: unanswered };
81
+ }
82
+ // Directories that are never the project's own work.
83
+ const IGNORED_DIRS = new Set([
84
+ '.git',
85
+ '.kortext',
86
+ '.kopeng',
87
+ 'node_modules',
88
+ 'dist',
89
+ 'build',
90
+ 'out',
91
+ '.next',
92
+ '.nuxt',
93
+ 'coverage',
94
+ 'vendor',
95
+ '.venv',
96
+ '__pycache__',
97
+ 'Pods',
98
+ ]);
99
+ // Stop counting source files once the minimum evidence threshold is reached.
100
+ export function countSourceFiles(root, limit) {
101
+ let seen = 0;
102
+ const walk = (dir) => {
103
+ if (seen >= limit)
104
+ return;
105
+ let entries;
106
+ try {
107
+ entries = readdirSync(dir, { withFileTypes: true });
108
+ }
109
+ catch {
110
+ return; // unreadable directory is not evidence
111
+ }
112
+ for (const entry of entries) {
113
+ if (seen >= limit)
114
+ return;
115
+ if (entry.name.startsWith('.') || IGNORED_DIRS.has(entry.name))
116
+ continue;
117
+ if (entry.isDirectory())
118
+ walk(join(dir, entry.name));
119
+ else if (entry.isFile() && entry.name !== 'AGENTS.md')
120
+ seen++;
121
+ }
122
+ };
123
+ walk(root);
124
+ return seen;
125
+ }
126
+ const MIN_SOURCE_FILES = 3;
127
+ function briefPath(project) {
128
+ return join(project.repo_path, '.kortext', 'BRIEF.md');
129
+ }
130
+ function cachePath(project) {
131
+ return join(project.repo_path, CACHE_REL);
132
+ }
133
+ export function readReadiness(project) {
134
+ const p = cachePath(project);
135
+ if (!existsSync(p))
136
+ return null;
137
+ try {
138
+ return JSON.parse(readFileSync(p, 'utf8'));
139
+ }
140
+ catch {
141
+ return null;
142
+ }
143
+ }
144
+ function write(project, verdict) {
145
+ writeFileSync(cachePath(project), `${JSON.stringify(verdict, null, 2)}\n`, 'utf8');
146
+ return verdict;
147
+ }
148
+ function buildJudgmentPrompt(project) {
149
+ return [
150
+ 'You are the readiness gate of a Kortext analysis flow, running headless inside the project folder.',
151
+ `Project: ${project.name}.`,
152
+ '',
153
+ 'Read ONLY this file: .kortext/BRIEF.md',
154
+ '',
155
+ 'Decide ONE thing: does this brief say enough for an analysis team to write a product',
156
+ 'requirements document, a tech stack and a security model WITHOUT inventing the product?',
157
+ 'A brief that names the project but not what it does, who it is for, what problem it',
158
+ 'solves, or which language it speaks to its users is NOT enough. Neither is one whose',
159
+ 'sections restate the template questions.',
160
+ '',
161
+ 'Err towards not-ready. Producing documents from an empty brief is the failure this gate exists to prevent;',
162
+ 'asking the human one more question is not a failure.',
163
+ '',
164
+ 'Write EXACTLY this file and nothing else: .kortext/.readiness.json',
165
+ 'It must be valid JSON with this shape:',
166
+ ' { "ready": true }',
167
+ 'or, when the brief is not enough:',
168
+ ' { "ready": false, "questions": ["...", "..."] }',
169
+ '',
170
+ 'Each question is one plain sentence naming what the brief must answer. Ask at most six.',
171
+ 'Ask only about what is genuinely missing — never restate something the brief already answers.',
172
+ 'Write the questions in the language of the brief.',
173
+ 'Do not write, edit or create any other file. Do not start the analysis.',
174
+ ].join('\n');
175
+ }
176
+ // One judgment per brief version at a time; approvals fan into advance() from
177
+ // several routes and must not each spend an engine run.
178
+ const inFlight = new Map();
179
+ /** True while the judgment run for this project is still out. */
180
+ export function isChecking(projectId) {
181
+ return inFlight.has(projectId);
182
+ }
183
+ /**
184
+ * The gate. Returns the cached verdict when the brief has not changed since it
185
+ * was made, otherwise re-runs: floor first, engine judgment only if the floor
186
+ * holds and the project is a new one.
187
+ */
188
+ export async function ensureReadiness(db, project, engine, signal) {
189
+ const running = inFlight.get(project.id);
190
+ if (running)
191
+ return running;
192
+ const p = check(db, project, engine, signal).finally(() => inFlight.delete(project.id));
193
+ inFlight.set(project.id, p);
194
+ return p;
195
+ }
196
+ async function check(db, project, engine, signal) {
197
+ const path = briefPath(project);
198
+ const content = existsSync(path) ? readFileSync(path, 'utf8') : '';
199
+ const briefHash = createHash('sha256').update(content).digest('hex').slice(0, 16);
200
+ const checkedAt = new Date().toISOString();
201
+ const verdict = (v) => write(project, { ...v, briefHash, checkedAt });
202
+ // An existing project has no brief: the code is the evidence. Cheap enough to
203
+ // recompute every time, so adding files opens the gate with no cache to clear.
204
+ if ((project.kind ?? 'new') === 'existing') {
205
+ if (countSourceFiles(project.repo_path, MIN_SOURCE_FILES) >= MIN_SOURCE_FILES) {
206
+ return verdict({ ready: true, stage: 'floor', questions: [] });
207
+ }
208
+ return verdict({
209
+ ready: false,
210
+ stage: 'floor',
211
+ questions: [
212
+ 'This folder holds almost no code, so there is nothing to analyse yet.',
213
+ 'Point the project at the repository you want documented — or add it as a new project and write a brief instead.',
214
+ ],
215
+ });
216
+ }
217
+ // Not approved yet — the human is still writing it. No engine call, no cache
218
+ // entry to invalidate later.
219
+ if (readFrontmatter(content).status !== 'approved') {
220
+ return { ready: false, stage: 'floor', questions: [], briefHash, checkedAt };
221
+ }
222
+ // Demote a refused brief to draft so it requires human review before another judgment.
223
+ const refuse = (v) => {
224
+ setFrontmatterStatus(path, 'draft');
225
+ return verdict(v);
226
+ };
227
+ const floor = assessBrief(content);
228
+ if (!floor.ok)
229
+ return refuse({ ready: false, stage: 'floor', questions: floor.questions });
230
+ const cached = readReadiness(project);
231
+ if (cached && cached.briefHash === briefHash && cached.stage === 'judgment')
232
+ return cached;
233
+ const logPath = logPathFor(db, `p${project.id}-readiness.log`);
234
+ // Delete the cached verdict before the CLI writes its replacement. Otherwise a failed run
235
+ // could reuse an old ready=true result under the new brief hash.
236
+ rmSync(cachePath(project), { force: true });
237
+ try {
238
+ const res = await spawnCli({
239
+ binary: engine.binary,
240
+ args: engineArgs(engine, project),
241
+ promptFlag: engine.promptFlag,
242
+ env: engineEnv(engine, project),
243
+ cwd: project.repo_path,
244
+ stdin: buildJudgmentPrompt(project),
245
+ logPath,
246
+ signal,
247
+ timeoutMs: JUDGMENT_TIMEOUT_MS,
248
+ });
249
+ if (res.aborted) {
250
+ return { ready: false, stage: 'error', questions: [], briefHash, checkedAt };
251
+ }
252
+ // A CLI that exits non-zero has not judged anything, whatever is on disk.
253
+ if (res.exitCode !== 0) {
254
+ return verdict({
255
+ ready: false,
256
+ stage: 'error',
257
+ questions: [
258
+ `The readiness check could not run (${engine.id} exited ${res.exitCode}).`,
259
+ 'Press Start to run it again.',
260
+ ],
261
+ });
262
+ }
263
+ // Re-read what this run wrote — the file was removed above, so anything
264
+ // here came from the run that just finished.
265
+ const written = readReadiness(project);
266
+ if (!written || typeof written.ready !== 'boolean') {
267
+ return verdict({
268
+ ready: false,
269
+ stage: 'error',
270
+ questions: ['The readiness check did not complete — press Start to run it again.'],
271
+ });
272
+ }
273
+ if (written.ready)
274
+ return verdict({ ready: true, stage: 'judgment', questions: [] });
275
+ return refuse({
276
+ ready: false,
277
+ stage: 'judgment',
278
+ questions: (written.questions ?? []).slice(0, 6),
279
+ });
280
+ }
281
+ catch {
282
+ return verdict({
283
+ ready: false,
284
+ stage: 'error',
285
+ questions: ['The readiness check did not complete — press Start to run it again.'],
286
+ });
287
+ }
288
+ }