@agentwhy/cli 0.0.0-dev

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 (400) hide show
  1. package/LICENSE +202 -0
  2. package/NOTICE +2 -0
  3. package/README.md +181 -0
  4. package/THIRD_PARTY_NOTICES.md +31 -0
  5. package/dist/adapter/claude-code/contract/assumptions.js +208 -0
  6. package/dist/adapter/claude-code/contract/denials.js +15 -0
  7. package/dist/adapter/claude-code/contract/entry-points.js +15 -0
  8. package/dist/adapter/claude-code/contract/fields.js +73 -0
  9. package/dist/adapter/claude-code/contract/hooks.js +96 -0
  10. package/dist/adapter/claude-code/contract/identifiers.js +15 -0
  11. package/dist/adapter/claude-code/contract/layout.js +29 -0
  12. package/dist/adapter/claude-code/contract/line-types.js +26 -0
  13. package/dist/adapter/claude-code/contract/projects.js +23 -0
  14. package/dist/adapter/claude-code/contract/recognition.js +14 -0
  15. package/dist/adapter/claude-code/contract/session-title.js +20 -0
  16. package/dist/adapter/claude-code/contract/settings.js +27 -0
  17. package/dist/adapter/claude-code/contract/task-notifications.js +33 -0
  18. package/dist/adapter/claude-code/contract/tools.js +37 -0
  19. package/dist/adapter/claude-code/contract/version.js +51 -0
  20. package/dist/adapter/claude-code/discovery/claude-code-project-catalogue.js +129 -0
  21. package/dist/adapter/claude-code/discovery/claude-code-recognition.js +46 -0
  22. package/dist/adapter/claude-code/discovery/claude-code-session-catalogue.js +100 -0
  23. package/dist/adapter/claude-code/discovery/claude-code-session-discovery.js +61 -0
  24. package/dist/adapter/claude-code/discovery/claude-code-session-titles.js +28 -0
  25. package/dist/adapter/claude-code/discovery/discovered-session.js +1 -0
  26. package/dist/adapter/claude-code/discovery/session-entries.js +41 -0
  27. package/dist/adapter/claude-code/discovery/transcript-tail.js +81 -0
  28. package/dist/adapter/claude-code/events/claude-code-session-source.js +197 -0
  29. package/dist/adapter/claude-code/events/delegation-index.js +15 -0
  30. package/dist/adapter/claude-code/events/transcript-scan.js +179 -0
  31. package/dist/adapter/claude-code/hooks/hook-output.js +42 -0
  32. package/dist/adapter/claude-code/hooks/pre-tool-use-input.js +23 -0
  33. package/dist/adapter/claude-code/hooks/stop-input.js +25 -0
  34. package/dist/adapter/claude-code/hooks/subagent-stop-input.js +26 -0
  35. package/dist/adapter/claude-code/policy/deny-rules.js +46 -0
  36. package/dist/adapter/claude-code/probe/claude-code-probe.js +86 -0
  37. package/dist/adapter/claude-code/probe/collectors/agent-tool-input-key-collector.js +24 -0
  38. package/dist/adapter/claude-code/probe/collectors/denial-kind-collector.js +20 -0
  39. package/dist/adapter/claude-code/probe/collectors/denied-call-collector.js +11 -0
  40. package/dist/adapter/claude-code/probe/collectors/identifier-collector.js +19 -0
  41. package/dist/adapter/claude-code/probe/collectors/line-collector.js +1 -0
  42. package/dist/adapter/claude-code/probe/collectors/line-type-collector.js +29 -0
  43. package/dist/adapter/claude-code/probe/collectors/sidechain-collector.js +20 -0
  44. package/dist/adapter/claude-code/probe/collectors/task-notification-collector.js +51 -0
  45. package/dist/adapter/claude-code/probe/collectors/tool-result-reference-collector.js +31 -0
  46. package/dist/adapter/claude-code/probe/collectors/tool-use-collector.js +24 -0
  47. package/dist/adapter/claude-code/probe/collectors/tool-version-collector.js +14 -0
  48. package/dist/adapter/claude-code/probe/collectors/top-level-key-collector.js +14 -0
  49. package/dist/adapter/claude-code/probe/collectors/unknown-line-type-key-collector.js +24 -0
  50. package/dist/adapter/claude-code/probe/collectors/working-directory-collector.js +28 -0
  51. package/dist/adapter/claude-code/probe/doctor-report.js +1 -0
  52. package/dist/adapter/claude-code/probe/meta-tally.js +42 -0
  53. package/dist/adapter/claude-code/probe/session-probe.js +1 -0
  54. package/dist/adapter/claude-code/probe/session-tally.js +41 -0
  55. package/dist/adapter/claude-code/probe/transcript-line.js +30 -0
  56. package/dist/adapter/claude-code/probe/transcript-tally.js +74 -0
  57. package/dist/adapter/claude-code/settings/deny-entries.js +65 -0
  58. package/dist/adapter/claude-code/settings/hook-entries.js +303 -0
  59. package/dist/adapter/codex/contract/actions.js +69 -0
  60. package/dist/adapter/codex/contract/assumptions.js +55 -0
  61. package/dist/adapter/codex/contract/capabilities.js +24 -0
  62. package/dist/adapter/codex/contract/delegations.js +30 -0
  63. package/dist/adapter/codex/contract/deliveries.js +25 -0
  64. package/dist/adapter/codex/contract/envelope.js +33 -0
  65. package/dist/adapter/codex/contract/hook-refusals.js +14 -0
  66. package/dist/adapter/codex/contract/hooks.js +46 -0
  67. package/dist/adapter/codex/contract/messages.js +47 -0
  68. package/dist/adapter/codex/contract/permissions.js +27 -0
  69. package/dist/adapter/codex/contract/reviews.js +13 -0
  70. package/dist/adapter/codex/contract/session.js +31 -0
  71. package/dist/adapter/codex/contract/turns.js +23 -0
  72. package/dist/adapter/codex/contract/version.js +15 -0
  73. package/dist/adapter/codex/discovery/codex-conversations.js +44 -0
  74. package/dist/adapter/codex/discovery/codex-project-catalogue.js +49 -0
  75. package/dist/adapter/codex/discovery/codex-recognition.js +20 -0
  76. package/dist/adapter/codex/discovery/codex-session-catalogue.js +28 -0
  77. package/dist/adapter/codex/discovery/codex-session-discovery.js +115 -0
  78. package/dist/adapter/codex/discovery/codex-session-index.js +21 -0
  79. package/dist/adapter/codex/discovery/codex-session-titles.js +135 -0
  80. package/dist/adapter/codex/discovery/session-header.js +86 -0
  81. package/dist/adapter/codex/discovery/session-owners.js +11 -0
  82. package/dist/adapter/codex/discovery/session-roots.js +49 -0
  83. package/dist/adapter/codex/events/action-items.js +138 -0
  84. package/dist/adapter/codex/events/capability-records.js +26 -0
  85. package/dist/adapter/codex/events/codex-session-source.js +121 -0
  86. package/dist/adapter/codex/events/hook-refusals.js +27 -0
  87. package/dist/adapter/codex/events/rollout-scan.js +614 -0
  88. package/dist/adapter/codex/events/turn-permissions.js +60 -0
  89. package/dist/adapter/codex/hooks/pre-tool-use-input.js +24 -0
  90. package/dist/adapter/codex/hooks/stop-refusals.js +57 -0
  91. package/dist/adapter/codex/probe/codex-doctor-report.js +1 -0
  92. package/dist/adapter/codex/probe/codex-probe.js +268 -0
  93. package/dist/adapter/codex/settings/codex-hooks.js +83 -0
  94. package/dist/cli/cli-command.js +1 -0
  95. package/dist/cli/command-router.js +42 -0
  96. package/dist/cli/commands/check-cli-command.js +128 -0
  97. package/dist/cli/commands/codex-stop-cli-command.js +27 -0
  98. package/dist/cli/commands/doctor-arguments.js +32 -0
  99. package/dist/cli/commands/doctor-cli-command.js +31 -0
  100. package/dist/cli/commands/doctor-usage.js +14 -0
  101. package/dist/cli/commands/init-cli-command.js +123 -0
  102. package/dist/cli/commands/menu-cli-command.js +58 -0
  103. package/dist/cli/commands/notify-cli-command.js +158 -0
  104. package/dist/cli/commands/refuse-cli-command.js +79 -0
  105. package/dist/cli/commands/report-arguments.js +62 -0
  106. package/dist/cli/commands/report-cli-command.js +36 -0
  107. package/dist/cli/commands/report-usage.js +41 -0
  108. package/dist/cli/commands/sessions-cli-command.js +184 -0
  109. package/dist/cli/commands/start-cli-command.js +83 -0
  110. package/dist/cli/commands/watch-cli-command.js +142 -0
  111. package/dist/cli/exit-codes.js +13 -0
  112. package/dist/cli/project-switches.js +46 -0
  113. package/dist/cli.js +80 -0
  114. package/dist/composition-root.js +411 -0
  115. package/dist/core/access/command-line.js +454 -0
  116. package/dist/core/access/listing.js +58 -0
  117. package/dist/core/access/path-shape.js +59 -0
  118. package/dist/core/access/path-tokens.js +37 -0
  119. package/dist/core/access/protected-access.js +310 -0
  120. package/dist/core/access/protected-values.js +102 -0
  121. package/dist/core/access/recorded-effect.js +67 -0
  122. package/dist/core/access/returns.js +173 -0
  123. package/dist/core/access/search-output.js +34 -0
  124. package/dist/core/access/search-reach.js +268 -0
  125. package/dist/core/access/uses.js +143 -0
  126. package/dist/core/access/words-before.js +52 -0
  127. package/dist/core/agent.js +1 -0
  128. package/dist/core/capability.js +11 -0
  129. package/dist/core/combined-project-catalogue.js +36 -0
  130. package/dist/core/combined-session-catalogue.js +25 -0
  131. package/dist/core/completeness.js +10 -0
  132. package/dist/core/context.js +1 -0
  133. package/dist/core/correlation/correlate.js +329 -0
  134. package/dist/core/delegation.js +1 -0
  135. package/dist/core/detector/prompt-signals.js +78 -0
  136. package/dist/core/entry-point.js +1 -0
  137. package/dist/core/event.js +1 -0
  138. package/dist/core/evidence.js +9 -0
  139. package/dist/core/late-message.js +32 -0
  140. package/dist/core/message.js +1 -0
  141. package/dist/core/nearest-project.js +28 -0
  142. package/dist/core/policy/default-policy.js +32 -0
  143. package/dist/core/policy/glob.js +56 -0
  144. package/dist/core/policy/parse-policy.js +70 -0
  145. package/dist/core/policy/policy.js +20 -0
  146. package/dist/core/policy/resolve-policy.js +16 -0
  147. package/dist/core/project-catalogue.js +1 -0
  148. package/dist/core/project-root.js +133 -0
  149. package/dist/core/provider-titles.js +10 -0
  150. package/dist/core/redaction/entropy.js +46 -0
  151. package/dist/core/redaction/redacted.js +4 -0
  152. package/dist/core/redaction/redactor.js +339 -0
  153. package/dist/core/redaction/secret-patterns.js +22 -0
  154. package/dist/core/redaction/value-shapes.js +81 -0
  155. package/dist/core/redaction/value-trace.js +110 -0
  156. package/dist/core/review.js +1 -0
  157. package/dist/core/session-catalogue.js +19 -0
  158. package/dist/core/session-filter.js +42 -0
  159. package/dist/core/session-format.js +27 -0
  160. package/dist/core/session-model.js +1 -0
  161. package/dist/core/session-reader.js +22 -0
  162. package/dist/core/session-records.js +1 -0
  163. package/dist/core/session-source.js +1 -0
  164. package/dist/core/session-titles.js +21 -0
  165. package/dist/core/turn.js +1 -0
  166. package/dist/doctor/codex-doctor.js +14 -0
  167. package/dist/doctor/doctor-use-case.js +1 -0
  168. package/dist/doctor/format-selecting-doctor.js +28 -0
  169. package/dist/doctor/render/attention/attention-rule.js +1 -0
  170. package/dist/doctor/render/attention/doctor-attention-rules.js +28 -0
  171. package/dist/doctor/render/attention/rules/damaged-meta-files-rule.js +8 -0
  172. package/dist/doctor/render/attention/rules/damaged-transcripts-rule.js +9 -0
  173. package/dist/doctor/render/attention/rules/incomplete-subagent-pairs-rule.js +6 -0
  174. package/dist/doctor/render/attention/rules/missing-main-transcript-rule.js +5 -0
  175. package/dist/doctor/render/attention/rules/missing-spilled-results-rule.js +6 -0
  176. package/dist/doctor/render/attention/rules/multiple-working-directories-rule.js +15 -0
  177. package/dist/doctor/render/attention/rules/unknown-denial-kinds-rule.js +5 -0
  178. package/dist/doctor/render/attention/rules/unknown-line-types-rule.js +6 -0
  179. package/dist/doctor/render/attention/rules/unrecognised-entries-rule.js +6 -0
  180. package/dist/doctor/render/attention/rules/unusable-directories-rule.js +12 -0
  181. package/dist/doctor/render/attention/transcript-scopes.js +6 -0
  182. package/dist/doctor/render/codex-text-doctor-renderer.js +97 -0
  183. package/dist/doctor/render/doctor-renderers.js +1 -0
  184. package/dist/doctor/render/json-doctor-renderer.js +7 -0
  185. package/dist/doctor/render/output-format.js +1 -0
  186. package/dist/doctor/render/text-doctor-renderer.js +124 -0
  187. package/dist/doctor/session-doctor.js +16 -0
  188. package/dist/infrastructure/clack-asker.js +24 -0
  189. package/dist/infrastructure/clack-chooser.js +40 -0
  190. package/dist/infrastructure/clack-multi-chooser.js +31 -0
  191. package/dist/infrastructure/file-alert-store.js +120 -0
  192. package/dist/infrastructure/file-checked-store.js +55 -0
  193. package/dist/infrastructure/file-mark-store.js +67 -0
  194. package/dist/infrastructure/file-onboarding-store.js +93 -0
  195. package/dist/infrastructure/folder-window.js +26 -0
  196. package/dist/infrastructure/node-agentwhy-invocation.js +99 -0
  197. package/dist/infrastructure/node-browser.js +40 -0
  198. package/dist/infrastructure/node-file-system.js +111 -0
  199. package/dist/infrastructure/node-local-server.js +115 -0
  200. package/dist/infrastructure/node-text-input.js +26 -0
  201. package/dist/infrastructure/os-notifier.js +21 -0
  202. package/dist/infrastructure/osascript-folder-chooser.js +53 -0
  203. package/dist/infrastructure/powershell-folder-chooser.js +78 -0
  204. package/dist/infrastructure/real-directory.js +18 -0
  205. package/dist/infrastructure/stream-printer.js +11 -0
  206. package/dist/infrastructure/terminal-banner.js +19 -0
  207. package/dist/ports/agentwhy-invocation.js +1 -0
  208. package/dist/ports/alert-store.js +1 -0
  209. package/dist/ports/asker.js +1 -0
  210. package/dist/ports/browser.js +1 -0
  211. package/dist/ports/checked-store.js +1 -0
  212. package/dist/ports/chooser.js +1 -0
  213. package/dist/ports/directory-reader.js +1 -0
  214. package/dist/ports/file-access-error.js +11 -0
  215. package/dist/ports/file-reader.js +1 -0
  216. package/dist/ports/file-tail-reader.js +1 -0
  217. package/dist/ports/file-writer.js +1 -0
  218. package/dist/ports/folder-chooser.js +1 -0
  219. package/dist/ports/local-server.js +1 -0
  220. package/dist/ports/mark-store.js +10 -0
  221. package/dist/ports/multi-chooser.js +1 -0
  222. package/dist/ports/notifier.js +1 -0
  223. package/dist/ports/onboarding-store.js +1 -0
  224. package/dist/ports/printer.js +1 -0
  225. package/dist/ports/text-input.js +1 -0
  226. package/dist/refuse/command-reach.js +135 -0
  227. package/dist/refuse/command-refusal.js +88 -0
  228. package/dist/refuse/render/codex-stop-words.js +21 -0
  229. package/dist/refuse/render/refusal-words.js +34 -0
  230. package/dist/report/build-report.js +1037 -0
  231. package/dist/report/check/actions-digest.js +73 -0
  232. package/dist/report/check/check-lines.js +83 -0
  233. package/dist/report/check/mark-request.js +90 -0
  234. package/dist/report/check/marks.js +84 -0
  235. package/dist/report/check/render/text-digest-renderer.js +332 -0
  236. package/dist/report/check/session-actions.js +90 -0
  237. package/dist/report/check/session-check.js +91 -0
  238. package/dist/report/choose-policy.js +61 -0
  239. package/dist/report/flow-reads.js +18 -0
  240. package/dist/report/private-files/tell-lists.js +84 -0
  241. package/dist/report/project-rules.js +33 -0
  242. package/dist/report/render/home-relative.js +18 -0
  243. package/dist/report/render/html-head.js +26 -0
  244. package/dist/report/render/html-report-components.js +48 -0
  245. package/dist/report/render/html-report-styles.js +500 -0
  246. package/dist/report/render/html-sidebar.js +42 -0
  247. package/dist/report/render/path-tail.js +14 -0
  248. package/dist/report/render/report-copy.js +1177 -0
  249. package/dist/report/render/report-page/advanced-view.js +167 -0
  250. package/dist/report/render/report-page/file-story.js +114 -0
  251. package/dist/report/render/report-page/files-view.js +381 -0
  252. package/dist/report/render/report-page/files.js +77 -0
  253. package/dist/report/render/report-page/fix-wizard-script.js +269 -0
  254. package/dist/report/render/report-page/fix-wizard.js +310 -0
  255. package/dist/report/render/report-page/helpers-view.js +383 -0
  256. package/dist/report/render/report-page/helpers.js +51 -0
  257. package/dist/report/render/report-page/item-names.js +63 -0
  258. package/dist/report/render/report-page/protect-patterns.js +13 -0
  259. package/dist/report/render/report-page/providers.js +208 -0
  260. package/dist/report/render/report-page/record-view.js +367 -0
  261. package/dist/report/render/report-page/report-page-renderer.js +129 -0
  262. package/dist/report/render/report-page/report-views.js +31 -0
  263. package/dist/report/render/report-page/story-window.js +356 -0
  264. package/dist/report/render/report-page/times.js +25 -0
  265. package/dist/report/render/report-page/to-do-view.js +299 -0
  266. package/dist/report/render/report-page/to-do.js +152 -0
  267. package/dist/report/render/report-page.js +1 -0
  268. package/dist/report/render/session-view.js +250 -0
  269. package/dist/report/render/text-report-renderer.js +981 -0
  270. package/dist/report/render/ui/add-file-popup.js +122 -0
  271. package/dist/report/render/ui/app-sidebar.js +78 -0
  272. package/dist/report/render/ui/ask-panel.js +80 -0
  273. package/dist/report/render/ui/avatar.js +19 -0
  274. package/dist/report/render/ui/backdrop.js +29 -0
  275. package/dist/report/render/ui/brand-mark.js +4 -0
  276. package/dist/report/render/ui/button.js +67 -0
  277. package/dist/report/render/ui/callout.js +20 -0
  278. package/dist/report/render/ui/chat-example.js +17 -0
  279. package/dist/report/render/ui/checklist.js +50 -0
  280. package/dist/report/render/ui/confirm-dialog.js +34 -0
  281. package/dist/report/render/ui/data-table.js +49 -0
  282. package/dist/report/render/ui/drawer.js +25 -0
  283. package/dist/report/render/ui/file-chip.js +37 -0
  284. package/dist/report/render/ui/fold-line.js +22 -0
  285. package/dist/report/render/ui/guide-card.js +25 -0
  286. package/dist/report/render/ui/hero.js +37 -0
  287. package/dist/report/render/ui/labelled-select.js +10 -0
  288. package/dist/report/render/ui/live-script.js +222 -0
  289. package/dist/report/render/ui/local-date.js +58 -0
  290. package/dist/report/render/ui/mode-icon.js +6 -0
  291. package/dist/report/render/ui/page-shell.js +101 -0
  292. package/dist/report/render/ui/pill-tabs.js +47 -0
  293. package/dist/report/render/ui/popup.js +77 -0
  294. package/dist/report/render/ui/progress.js +35 -0
  295. package/dist/report/render/ui/project-list.js +284 -0
  296. package/dist/report/render/ui/scope-choice.js +19 -0
  297. package/dist/report/render/ui/stats.js +21 -0
  298. package/dist/report/render/ui/status-icon.js +30 -0
  299. package/dist/report/render/ui/status-look.js +21 -0
  300. package/dist/report/render/ui/switch.js +11 -0
  301. package/dist/report/render/ui/tag.js +19 -0
  302. package/dist/report/render/ui/task-list.js +56 -0
  303. package/dist/report/render/ui/tokens.js +46 -0
  304. package/dist/report/render/ui/update-notice.js +146 -0
  305. package/dist/report/render/ui/words/app-words.js +163 -0
  306. package/dist/report/render/ui/words/conversations-words.js +457 -0
  307. package/dist/report/render/ui/words/month-words.js +59 -0
  308. package/dist/report/render/ui/words/onboarding-words.js +446 -0
  309. package/dist/report/render/ui/words/projects-words.js +217 -0
  310. package/dist/report/render/ui/words/report-words.js +2099 -0
  311. package/dist/report/render/ui/words/settings-words.js +611 -0
  312. package/dist/report/render/ui/words/to-fix-words.js +287 -0
  313. package/dist/report/report-model.js +2 -0
  314. package/dist/report/report-use-case.js +1 -0
  315. package/dist/report/rule-names.js +20 -0
  316. package/dist/report/session-report.js +163 -0
  317. package/dist/report/start/app-nav.js +37 -0
  318. package/dist/report/start/conversations/calendar-view.js +104 -0
  319. package/dist/report/start/conversations/conversation-columns.js +187 -0
  320. package/dist/report/start/conversations/conversations-renderer.js +54 -0
  321. package/dist/report/start/conversations/empty-week.js +120 -0
  322. package/dist/report/start/conversations/files-window.js +166 -0
  323. package/dist/report/start/conversations/period-section.js +280 -0
  324. package/dist/report/start/conversations/periods-script.js +440 -0
  325. package/dist/report/start/conversations/periods.js +110 -0
  326. package/dist/report/start/conversations/week-view.js +30 -0
  327. package/dist/report/start/conversations/weeks.js +17 -0
  328. package/dist/report/start/month/month-renderer.js +50 -0
  329. package/dist/report/start/month/month-view.js +135 -0
  330. package/dist/report/start/month/months.js +17 -0
  331. package/dist/report/start/onboarding/done.js +171 -0
  332. package/dist/report/start/onboarding/finish-onboarding.js +93 -0
  333. package/dist/report/start/onboarding/intro.js +100 -0
  334. package/dist/report/start/onboarding/onboarding-changes.js +40 -0
  335. package/dist/report/start/onboarding/onboarding-renderer.js +195 -0
  336. package/dist/report/start/onboarding/onboarding-script.js +400 -0
  337. package/dist/report/start/onboarding/onboarding-view.js +98 -0
  338. package/dist/report/start/onboarding/project-step.js +210 -0
  339. package/dist/report/start/onboarding/steps.js +239 -0
  340. package/dist/report/start/onboarding/welcome.js +66 -0
  341. package/dist/report/start/page-notice.js +10 -0
  342. package/dist/report/start/projects/index-projects.js +43 -0
  343. package/dist/report/start/projects/moved-page.js +82 -0
  344. package/dist/report/start/projects/project-switch.js +138 -0
  345. package/dist/report/start/projects/projects-window.js +67 -0
  346. package/dist/report/start/projects/temporary-space.js +18 -0
  347. package/dist/report/start/render/air-datepicker-vendor.js +27 -0
  348. package/dist/report/start/render/session-status.js +41 -0
  349. package/dist/report/start/render/start-words.js +84 -0
  350. package/dist/report/start/repository.js +28 -0
  351. package/dist/report/start/serve/index-handler.js +372 -0
  352. package/dist/report/start/serve/onboarding-request.js +50 -0
  353. package/dist/report/start/serve/settings-request.js +1 -0
  354. package/dist/report/start/serve/settings-setup.js +193 -0
  355. package/dist/report/start/session-index.js +1 -0
  356. package/dist/report/start/session-start.js +974 -0
  357. package/dist/report/start/settings/alerts-tab.js +104 -0
  358. package/dist/report/start/settings/files-tab.js +169 -0
  359. package/dist/report/start/settings/general-tab.js +71 -0
  360. package/dist/report/start/settings/settings-renderer.js +254 -0
  361. package/dist/report/start/settings/settings-script.js +161 -0
  362. package/dist/report/start/settings/settings-view.js +240 -0
  363. package/dist/report/start/settings/settings-windows.js +334 -0
  364. package/dist/report/start/settings-files.js +43 -0
  365. package/dist/report/start/to-fix/done-list.js +45 -0
  366. package/dist/report/start/to-fix/file-window.js +110 -0
  367. package/dist/report/start/to-fix/to-fix-list.js +70 -0
  368. package/dist/report/start/to-fix/to-fix-renderer.js +237 -0
  369. package/dist/report/start/to-fix/to-fix-script.js +94 -0
  370. package/dist/report/start/to-fix/to-fix-view.js +124 -0
  371. package/dist/report/watch/agent-alert.js +72 -0
  372. package/dist/report/watch/notice-choices.js +35 -0
  373. package/dist/report/watch/notice-settings.js +65 -0
  374. package/dist/report/watch/preferences.js +100 -0
  375. package/dist/report/watch/render/instruction-words.js +54 -0
  376. package/dist/report/watch/render/notice-words.js +182 -0
  377. package/dist/report/watch/subagent-watch.js +345 -0
  378. package/dist/report/watch/watch-notice.js +1 -0
  379. package/dist/setup/behind.js +18 -0
  380. package/dist/setup/codex-mirror.js +171 -0
  381. package/dist/setup/not-a-project.js +19 -0
  382. package/dist/setup/project-setup.js +644 -0
  383. package/dist/setup/protected-patterns.js +30 -0
  384. package/dist/shared/colour.js +23 -0
  385. package/dist/shared/compare.js +4 -0
  386. package/dist/shared/content-version.js +13 -0
  387. package/dist/shared/counter.js +11 -0
  388. package/dist/shared/file-stamp.js +4 -0
  389. package/dist/shared/json.js +13 -0
  390. package/dist/shared/label.js +13 -0
  391. package/dist/shared/package-name.js +6 -0
  392. package/dist/shared/plain-invocation.js +44 -0
  393. package/dist/shared/plain-version.js +21 -0
  394. package/dist/shared/printable.js +25 -0
  395. package/dist/shared/renderer.js +1 -0
  396. package/dist/shared/sentence.js +20 -0
  397. package/dist/shared/terminal-logo.js +41 -0
  398. package/dist/shared/text-table.js +15 -0
  399. package/dist/shared/wrap.js +29 -0
  400. package/package.json +70 -0
@@ -0,0 +1,440 @@
1
+ /**
2
+ * What the pages read through periods do with a script - Conversations, a week at a time, and This month
3
+ * (`for-people-who-build-with-ai.md` F10-F14; `.ai/plans/2026-09-23-month-redesign.md`): one period shown at a time, a
4
+ * day tile narrowing every list to that day, and over the rest a search, a pill per group and five rows a group until
5
+ * asked for more. It changes visibility and the counts beside it, and nothing else - every row and every word is in
6
+ * the HTML already, and without it every period is on the page, one under another, every row of it shown. Left for a
7
+ * conversation's report, the page is found again where it was.
8
+ */
9
+ export const PERIODS_SCRIPT = String.raw `
10
+ (() => {
11
+ const periods = [...document.querySelectorAll('[data-period-at]')];
12
+ if (periods.length === 0) return;
13
+ const kind = periods[0].dataset.kind || 'week';
14
+ // F14: a group of the rest shows this many rows while the whole period is shown with no search and no group picked.
15
+ const CAP = 5;
16
+ // The groups of the rest, in period-section.ts REST's order.
17
+ const LOOKS = ['fixed', 'name', 'stopped', 'none'];
18
+ // F13: the rest, once opened, opens again - in this browser only, and folded where it cannot be remembered.
19
+ const REMEMBER = 'agentwhy.conversations.rest';
20
+ const remembered = () => { try { return localStorage.getItem(REMEMBER) === 'open'; } catch (error) { return false; } };
21
+ const remember = (open) => { try { localStorage.setItem(REMEMBER, open ? 'open' : 'shut'); } catch (error) {} };
22
+ // A count in every language the page carries, each with its plural forms beside it (period-section.ts countedLine).
23
+ const counted = (element, n) => {
24
+ const form = new Intl.PluralRules(element.lang).select(n);
25
+ const text = element.getAttribute('data-form-' + form) || element.getAttribute('data-form-other') || '';
26
+ element.textContent = text.replace('{n}', String(n));
27
+ };
28
+
29
+ const show = (at, scroll) => {
30
+ const target = periods[at];
31
+ if (!target) return;
32
+ periods.forEach((period) => period.classList.toggle('cw-current', period === target));
33
+ // Every period opens whole (F10, changed 2026-09-24): a day is chosen only by a tap.
34
+ target.dataset.day = '';
35
+ target.dataset.look = 'all';
36
+ const search = target.querySelector('[data-search-input]');
37
+ if (search) search.value = '';
38
+ const others = target.querySelector('[data-others]');
39
+ if (others && !others.closest('.cw-bare') && remembered()) others.open = true;
40
+ apply(target);
41
+ if (scroll) window.scrollTo({ top: 0, behavior: 'auto' });
42
+ };
43
+
44
+ const apply = (period) => {
45
+ const day = period.dataset.day || '';
46
+ const search = period.querySelector('[data-search-input]');
47
+ const query = search ? search.value.trim().toLowerCase() : '';
48
+ period.querySelectorAll('button.cw-day[data-day]').forEach((tile) => {
49
+ const on = tile.dataset.day === day;
50
+ tile.classList.toggle('cw-day-on', on);
51
+ tile.setAttribute('aria-pressed', String(on));
52
+ });
53
+ const fits = (row) => (!day || row.dataset.day === day) && (!query || (row.dataset.search || '').includes(query));
54
+
55
+ // "Needs your attention", "For your info" and "Not checked": each narrows to the day chosen, and hides where the
56
+ // day leaves none. The rest folds under the first two, and is laid bare where neither has a row in view.
57
+ let toFix = 0;
58
+ period.querySelectorAll('[data-need]').forEach((need) => {
59
+ const rows = [...need.querySelectorAll('.dt-row[data-day]')];
60
+ let shown = 0;
61
+ rows.forEach((row) => { const fit = fits(row); row.hidden = !fit; if (fit) shown += 1; });
62
+ need.hidden = shown === 0;
63
+ if (need.hasAttribute('data-need-fix') || need.hasAttribute('data-need-info')) toFix += shown;
64
+ const count = need.querySelector('[data-need-count]');
65
+ if (count) count.textContent = String(shown);
66
+ });
67
+
68
+ // The day chosen, over every list (F10): its name, how many conversations it holds, and × for the whole week.
69
+ const bar = period.querySelector('[data-day-bar]');
70
+ if (bar) {
71
+ const name = day ? period.querySelector('.cw-day[data-day="' + day + '"] .cw-day-name') : null;
72
+ bar.hidden = !name;
73
+ const label = bar.querySelector('[data-day-label]');
74
+ if (name && label) label.innerHTML = name.innerHTML;
75
+ const held = new Set([...period.querySelectorAll('.dt-row[data-day="' + day + '"]')].map((row) => row.dataset.liveKey || row));
76
+ bar.querySelectorAll('[data-day-count]').forEach((element) => counted(element, held.size));
77
+ }
78
+
79
+ const others = period.querySelector('[data-others]');
80
+ if (!others) return;
81
+ // With nothing to fix in view the rest is laid bare, open under its own heading (F13); with something, it folds
82
+ // back to what the person last left it as.
83
+ const section = others.closest('.cw-others');
84
+ const bare = toFix === 0;
85
+ if (section && bare !== section.classList.contains('cw-bare')) {
86
+ section.classList.toggle('cw-bare', bare);
87
+ settling = true;
88
+ others.open = bare || remembered();
89
+ settling = false;
90
+ }
91
+
92
+ const pick = period.dataset.look || 'all';
93
+ const rows = [...others.querySelectorAll('.dt-row[data-look]')];
94
+ const counts = { all: 0 };
95
+ rows.filter(fits).forEach((row) => { counts.all += 1; counts[row.dataset.look] = (counts[row.dataset.look] || 0) + 1; });
96
+ others.querySelectorAll('[data-look-n]').forEach((element) => { element.textContent = String(counts[element.dataset.lookN] || 0); });
97
+ others.querySelectorAll('[data-pick-look]').forEach((button) => {
98
+ button.setAttribute('aria-pressed', String(button.dataset.pickLook === pick));
99
+ button.hidden = button.dataset.pickLook !== 'all' && !counts[button.dataset.pickLook];
100
+ });
101
+ others.querySelectorAll('[data-mix-look]').forEach((fact) => { fact.hidden = !counts[fact.dataset.mixLook]; });
102
+
103
+ // F14: five rows a group while the whole period is shown; a day, a search or a group picked shows all they match.
104
+ const capped = !day && !query && pick === 'all';
105
+ let shown = 0;
106
+ LOOKS.forEach((look) => {
107
+ const mine = rows.filter((row) => row.dataset.look === look);
108
+ const fit = mine.filter((row) => fits(row) && (pick === 'all' || pick === look));
109
+ const more = others.querySelector('[data-look-more="' + look + '"]');
110
+ const whole = !capped || (more && more.getAttribute('aria-pressed') === 'true');
111
+ mine.forEach((row) => { row.hidden = true; });
112
+ fit.forEach((row, at) => { row.hidden = !whole && at >= CAP; if (!row.hidden) shown += 1; });
113
+ const head = others.querySelector('[data-look-head="' + look + '"]');
114
+ if (head) {
115
+ head.hidden = fit.length === 0;
116
+ const count = head.querySelector('.dt-group-count');
117
+ if (count) count.textContent = String(fit.length);
118
+ }
119
+ const note = others.querySelector('[data-look-note="' + look + '"]');
120
+ if (note) {
121
+ note.hidden = !capped || fit.length <= CAP;
122
+ note.querySelectorAll('[data-look-past="' + look + '"]').forEach((element) => { element.textContent = String(fit.length - CAP); });
123
+ }
124
+ });
125
+ const empty = others.querySelector('.dt-empty');
126
+ if (empty) empty.hidden = shown > 0;
127
+ others.querySelectorAll('[data-shown]').forEach((element) => { element.textContent = String(shown); });
128
+ // The line over the rest counts what it holds for the day and the search: its words carry every plural form.
129
+ others.querySelectorAll('.fold-line [data-counted]').forEach((element) => counted(element, counts.all));
130
+ };
131
+ let settling = false;
132
+
133
+ // ── the calendar a period is chosen in ──
134
+ // The earlier page's calendar (Air Datepicker, vendored), opened from the period's name. A week is drawn as the
135
+ // range it is: a click anywhere in a row takes the whole row, Monday to Sunday. A month is picked from a year of
136
+ // months. A period this page holds no conversation in is not offered at all. Days are calendar days, so they cross
137
+ // into the picker's own local dates through their numbers, never through a moment.
138
+ const DAY = 86400000;
139
+ const localOf = (number) => { const at = new Date(number * DAY); return new Date(at.getUTCFullYear(), at.getUTCMonth(), at.getUTCDate()); };
140
+ const numberOf = (date) => Math.floor(Date.UTC(date.getFullYear(), date.getMonth(), date.getDate()) / DAY);
141
+ const mondayOf = (number) => number - ((new Date(number * DAY).getUTCDay() + 6) % 7);
142
+ const firstOfMonth = (number) => { const at = new Date(number * DAY); return Math.floor(Date.UTC(at.getUTCFullYear(), at.getUTCMonth(), 1) / DAY); };
143
+ const firstOf = kind === 'month' ? firstOfMonth : mondayOf;
144
+ const offered = new Map(periods.map((period, at) => [Number(period.dataset.first), at]));
145
+ const busy = new Set([...document.querySelectorAll('.cw-day[data-day]:not(:disabled)')].map((tile) => Number(tile.dataset.day)));
146
+ let picker = null;
147
+
148
+ const pickerLocale = () => {
149
+ const lang = document.documentElement.dataset.lang || 'en';
150
+ // 2024-01-07 is a Sunday, and the picker reads its day names from Sunday whatever day it starts a week on.
151
+ const names = (options) => {
152
+ const format = new Intl.DateTimeFormat(lang, Object.assign({ timeZone: 'UTC' }, options));
153
+ return Array.from({ length: 7 }, (_, at) => format.format(new Date(Date.UTC(2024, 0, 7 + at))));
154
+ };
155
+ const months = (options) => {
156
+ const format = new Intl.DateTimeFormat(lang, Object.assign({ timeZone: 'UTC' }, options));
157
+ return Array.from({ length: 12 }, (_, at) => format.format(new Date(Date.UTC(2020, at, 1))));
158
+ };
159
+ return {
160
+ days: names({ weekday: 'long' }), daysShort: names({ weekday: 'short' }), daysMin: names({ weekday: 'narrow' }),
161
+ months: months({ month: 'long' }), monthsShort: months({ month: 'short' }),
162
+ today: '', clear: '', dateFormat: 'yyyy-MM-dd', timeFormat: 'HH:mm', firstDay: 1,
163
+ };
164
+ };
165
+
166
+ /** A day is not what is chosen here, so the row under the pointer lifts whole. Zero lifts none of them. */
167
+ const paintWeek = (monday) => {
168
+ if (!picker || !picker.$datepicker) return;
169
+ picker.$datepicker.querySelectorAll('.air-datepicker-cell.-day-').forEach((cell) => {
170
+ const number = cell.adpCell ? numberOf(cell.adpCell.date) : 0;
171
+ cell.classList.toggle('cw-week-hover', monday !== 0 && number >= monday && number < monday + 7);
172
+ });
173
+ };
174
+
175
+ const close = () => { if (picker) { picker.destroy(); picker = null; } };
176
+
177
+ /** The period a picked date falls in, shown once the picker has let go of it. */
178
+ const pickedFrom = (period) => ({ date }) => {
179
+ const dates = Array.isArray(date) ? date : [date];
180
+ const last = dates[dates.length - 1];
181
+ const at = last ? offered.get(firstOf(numberOf(last))) : undefined;
182
+ if (at === undefined) return;
183
+ const target = periods[at];
184
+ setTimeout(() => {
185
+ close();
186
+ if (target !== period) {
187
+ history.replaceState(null, '', '#' + target.id);
188
+ show(at, true);
189
+ }
190
+ }, 0);
191
+ };
192
+
193
+ const openPicker = (button) => {
194
+ if (!window.AirDatepicker) return;
195
+ const period = button.closest('[data-period-at]');
196
+ const first = Number(period.dataset.first);
197
+ const anchor = button.querySelector('.cw-pick-anchor');
198
+ close();
199
+ const firsts = [...offered.keys()];
200
+ const common = { position: 'bottom center', locale: pickerLocale(), startDate: localOf(first), onSelect: pickedFrom(period) };
201
+ picker = kind === 'month'
202
+ ? new window.AirDatepicker(anchor, Object.assign(common, {
203
+ view: 'months',
204
+ minView: 'months',
205
+ minDate: localOf(Math.min(...firsts)),
206
+ maxDate: localOf(Math.max(...firsts)),
207
+ selectedDates: [localOf(first)],
208
+ onRenderCell: ({ date, cellType }) => {
209
+ if (cellType !== 'month') return undefined;
210
+ return offered.has(numberOf(date)) ? { classes: 'cw-has-data' } : { disabled: true };
211
+ },
212
+ }))
213
+ : new window.AirDatepicker(anchor, Object.assign(common, {
214
+ range: true,
215
+ toggleSelected: false,
216
+ minDate: localOf(Math.min(...firsts)),
217
+ maxDate: localOf(Math.max(...firsts) + 6),
218
+ selectedDates: [localOf(first), localOf(first + 6)],
219
+ onRenderCell: ({ date, cellType }) => {
220
+ if (cellType !== 'day') return undefined;
221
+ const number = numberOf(date);
222
+ if (!offered.has(mondayOf(number))) return { disabled: true };
223
+ return busy.has(number) ? { classes: 'cw-has-data' } : undefined;
224
+ },
225
+ onHide: () => paintWeek(0),
226
+ }));
227
+ anchor.focus({ preventScroll: true });
228
+ picker.show();
229
+ };
230
+
231
+ document.addEventListener('mouseover', (event) => {
232
+ if (kind !== 'week' || !picker || !picker.$datepicker || !event.target.closest) return;
233
+ const cell = event.target.closest('.air-datepicker-cell.-day-');
234
+ paintWeek(cell && cell.adpCell && !cell.classList.contains('-disabled-') ? mondayOf(numberOf(cell.adpCell.date)) : 0);
235
+ });
236
+
237
+ document.addEventListener('mousedown', (event) => {
238
+ if (!picker || !event.target.closest) return;
239
+ if (event.target.closest('.air-datepicker') || event.target.closest('[data-period-pick]')) return;
240
+ close();
241
+ });
242
+
243
+ document.addEventListener('keydown', (event) => {
244
+ if (event.key === 'Escape') close();
245
+ });
246
+
247
+ document.addEventListener('click', (event) => {
248
+ const pick = event.target.closest('[data-period-pick]');
249
+ if (pick) {
250
+ event.preventDefault();
251
+ openPicker(pick);
252
+ return;
253
+ }
254
+ const go = event.target.closest('[data-period-go]');
255
+ if (go) {
256
+ event.preventDefault();
257
+ show(Number(go.dataset.periodGo), true);
258
+ history.replaceState(null, '', go.getAttribute('href'));
259
+ return;
260
+ }
261
+ // A tile that opens its day in a window (This month) is the window script's, not a filter.
262
+ const tile = event.target.closest('.cw-day[data-day]:not([data-popup-open])');
263
+ if (tile && !tile.disabled) {
264
+ const period = tile.closest('[data-period-at]');
265
+ period.dataset.day = period.dataset.day === tile.dataset.day ? '' : tile.dataset.day;
266
+ apply(period);
267
+ return;
268
+ }
269
+ const clear = event.target.closest('[data-day-clear]');
270
+ if (clear) {
271
+ const period = clear.closest('[data-period-at]');
272
+ period.dataset.day = '';
273
+ apply(period);
274
+ return;
275
+ }
276
+ const group = event.target.closest('[data-pick-look]');
277
+ if (group) {
278
+ const period = group.closest('[data-period-at]');
279
+ period.dataset.look = group.dataset.pickLook;
280
+ apply(period);
281
+ return;
282
+ }
283
+ const more = event.target.closest('[data-look-more]');
284
+ if (more) {
285
+ more.setAttribute('aria-pressed', String(more.getAttribute('aria-pressed') !== 'true'));
286
+ apply(more.closest('[data-period-at]'));
287
+ }
288
+ });
289
+
290
+ // What the person does to the fold is what it opens as next time, in this browser; the page's own changes are not.
291
+ document.addEventListener('toggle', (event) => {
292
+ const others = event.target;
293
+ if (settling || !others.matches || !others.matches('[data-others]') || others.closest('.cw-bare')) return;
294
+ remember(others.open);
295
+ }, true);
296
+
297
+ document.addEventListener('input', (event) => {
298
+ const search = event.target.closest('[data-search-input]');
299
+ if (search) apply(search.closest('[data-period-at]'));
300
+ });
301
+
302
+ // A period named in the address - a link from elsewhere, or the address changed by hand - is the period shown.
303
+ const fromAddress = () => {
304
+ const asked = new RegExp('^#' + kind + '-(\\d+)$').exec(location.hash);
305
+ const at = asked ? periods.findIndex((period) => period.id === kind + '-' + asked[1]) : 0;
306
+ return at < 0 ? 0 : at;
307
+ };
308
+ window.addEventListener('hashchange', () => show(fromAddress(), true));
309
+ show(fromAddress(), false);
310
+
311
+ // ── where the person was ──
312
+ // A long list is left through a row's Open report or Fix it. What they were looking at - the period, the day, the
313
+ // search, the group, the groups shown whole, the rest opened or folded - and how far down, is kept in this tab and put
314
+ // back the next time this page opens here, by Back or by the sidebar, and then forgotten. An address that names
315
+ // another period asked for that one, and is not overruled.
316
+ const PLACE = 'agentwhy.place.' + (location.pathname.split('/').pop() || '/');
317
+ const current = () => periods.find((period) => period.classList.contains('cw-current'));
318
+ const leave = () => {
319
+ const period = current();
320
+ if (!period) return;
321
+ const search = period.querySelector('[data-search-input]');
322
+ const others = period.querySelector('[data-others]');
323
+ const place = {
324
+ id: period.id,
325
+ day: period.dataset.day || '',
326
+ look: period.dataset.look || 'all',
327
+ search: search ? search.value : '',
328
+ more: [...period.querySelectorAll('[data-look-more][aria-pressed="true"]')].map((button) => button.dataset.lookMore),
329
+ open: others ? others.open : false,
330
+ scroll: window.scrollY,
331
+ };
332
+ try { sessionStorage.setItem(PLACE, JSON.stringify(place)); } catch (error) {}
333
+ };
334
+ const comeBack = () => {
335
+ let place = null;
336
+ try { place = JSON.parse(sessionStorage.getItem(PLACE) || 'null'); sessionStorage.removeItem(PLACE); } catch (error) { return; }
337
+ if (!place) return;
338
+ const at = periods.findIndex((period) => period.id === place.id);
339
+ if (at < 0 || (location.hash && location.hash !== '#' + place.id)) return;
340
+ const period = periods[at];
341
+ // The address is left as it came: a period named in it now would be scrolled to once the page has loaded.
342
+ show(at, false);
343
+ period.dataset.day = place.day || '';
344
+ period.dataset.look = place.look || 'all';
345
+ const search = period.querySelector('[data-search-input]');
346
+ if (search) search.value = place.search || '';
347
+ period.querySelectorAll('[data-look-more]').forEach((button) => {
348
+ button.setAttribute('aria-pressed', String((place.more || []).includes(button.dataset.lookMore)));
349
+ });
350
+ apply(period);
351
+ const others = period.querySelector('[data-others]');
352
+ if (others && !others.closest('.cw-bare')) { settling = true; others.open = !!place.open; settling = false; }
353
+ window.scrollTo(0, place.scroll || 0);
354
+ };
355
+ document.addEventListener('click', (event) => {
356
+ // A click that opens the report in another tab or window leaves this page where it is: nothing to put back.
357
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
358
+ if (event.target.closest && event.target.closest('.cw-action a[href]')) leave();
359
+ });
360
+ // Back that brings the page out of the browser's cache brings it as it was left: what was kept is not needed again.
361
+ window.addEventListener('pageshow', (event) => { if (event.persisted) { try { sessionStorage.removeItem(PLACE); } catch (error) {} } });
362
+ comeBack();
363
+
364
+ // ── a new version, put in place (live-pages L7a, the maintainer 2026-09-25) ──
365
+ // Conversations takes a new version of itself without a reload: each period keeps its element, and so what the script
366
+ // set on it - the one shown, the day, the group - while what is inside is the new one; the search, the groups shown
367
+ // whole and the fold are put back, and the row at the top of the view is held where it was, so nothing moves under
368
+ // the reader. This month is left to the reload: its day windows stand outside its periods. A page whose periods are
369
+ // not the ones it had (a week begun) answers nothing, and is reloaded as before.
370
+ if (kind !== 'week') return;
371
+ const signature = (row) => row.outerHTML.replace(/ hidden=""/g, '').replace(/ live-new/g, '');
372
+ const visibleRows = () => [...document.querySelectorAll('.dt-row[data-live-key]')].filter((row) => !row.hidden && row.offsetParent !== null);
373
+ window.agentwhyLiveSwap = (next) => {
374
+ const nextPeriods = [...next.querySelectorAll('[data-period-at]')];
375
+ if (nextPeriods.length !== periods.length || nextPeriods.some((period, at) => period.id !== periods[at].id)) return null;
376
+ const before = new Map();
377
+ document.querySelectorAll('.dt-row[data-live-key]').forEach((row) => { if (!before.has(row.dataset.liveKey)) before.set(row.dataset.liveKey, signature(row)); });
378
+ const anchor = visibleRows().find((row) => row.getBoundingClientRect().bottom > 0);
379
+ const anchorKey = anchor ? anchor.dataset.liveKey : '';
380
+ const anchorTop = anchor ? anchor.getBoundingClientRect().top : 0;
381
+
382
+ periods.forEach((period, at) => {
383
+ const search = period.querySelector('[data-search-input]');
384
+ const others = period.querySelector('[data-others]');
385
+ const kept = {
386
+ search: search ? search.value : '',
387
+ more: [...period.querySelectorAll('[data-look-more][aria-pressed="true"]')].map((button) => button.dataset.lookMore),
388
+ open: others ? others.open : false,
389
+ };
390
+ period.replaceChildren(...[...nextPeriods[at].childNodes].map((node) => document.importNode(node, true)));
391
+ const searchNow = period.querySelector('[data-search-input]');
392
+ if (searchNow) searchNow.value = kept.search;
393
+ period.querySelectorAll('[data-look-more]').forEach((button) => button.setAttribute('aria-pressed', String(kept.more.includes(button.dataset.lookMore))));
394
+ const othersNow = period.querySelector('[data-others]');
395
+ if (othersNow && !othersNow.closest('.cw-bare')) { settling = true; othersNow.open = kept.open; settling = false; }
396
+ apply(period);
397
+ });
398
+ // The counts beside the page names, and the days the calendar offers, follow the conversations.
399
+ document.querySelectorAll('.sb-item[href]').forEach((item) => {
400
+ const same = next.querySelector('.sb-item[href="' + item.getAttribute('href') + '"]');
401
+ if (same && same.innerHTML !== item.innerHTML) item.innerHTML = same.innerHTML;
402
+ });
403
+ busy.clear();
404
+ document.querySelectorAll('.cw-day[data-day]:not(:disabled)').forEach((tile) => busy.add(Number(tile.dataset.day)));
405
+
406
+ const held = anchorKey ? visibleRows().find((row) => row.dataset.liveKey === anchorKey) : undefined;
407
+ if (held) window.scrollBy(0, held.getBoundingClientRect().top - anchorTop);
408
+
409
+ const seen = new Set();
410
+ const fresh = [...document.querySelectorAll('.dt-row[data-live-key]')].filter((row) => {
411
+ const key = row.dataset.liveKey;
412
+ if (seen.has(key)) return false;
413
+ seen.add(key);
414
+ return before.get(key) !== signature(row);
415
+ });
416
+ return {
417
+ fresh,
418
+ added: fresh.filter((row) => !before.has(row.dataset.liveKey)),
419
+ // A row a filter hides - another day chosen, a search, a group folded or capped - is shown by setting them aside.
420
+ reveal: (row) => {
421
+ const period = row.closest('[data-period-at]');
422
+ if (!period) return;
423
+ if (!period.classList.contains('cw-current')) show(periods.indexOf(period), false);
424
+ if (row.hidden || row.offsetParent === null) {
425
+ period.dataset.day = '';
426
+ period.dataset.look = 'all';
427
+ const search = period.querySelector('[data-search-input]');
428
+ if (search) search.value = '';
429
+ const look = row.dataset.look;
430
+ const more = look ? period.querySelector('[data-look-more="' + look + '"]') : null;
431
+ if (more) more.setAttribute('aria-pressed', 'true');
432
+ const others = period.querySelector('[data-others]');
433
+ if (others && row.closest('[data-others]')) { settling = true; others.open = true; settling = false; }
434
+ apply(period);
435
+ }
436
+ },
437
+ };
438
+ };
439
+ })();
440
+ `;
@@ -0,0 +1,110 @@
1
+ import { TO_DO_LABELS } from '../../check/check-lines.js';
2
+ import { statusOf } from '../render/session-status.js';
3
+ import { LOOKS } from '../../render/ui/status-look.js';
4
+ import { dayOf, localClock } from '../../render/ui/local-date.js';
5
+ const LOOK_OF = {
6
+ seen: 'read',
7
+ result: 'name',
8
+ named: 'name',
9
+ blocked: 'stopped',
10
+ clean: 'none',
11
+ unknown: 'unchecked',
12
+ failed: 'failed',
13
+ outside: 'outside',
14
+ };
15
+ /**
16
+ * Every conversation on the page, newest first, read in the time zone of the machine that ran `start`
17
+ * (`for-people-who-build-with-ai.md` O2), and the day that is today there.
18
+ */
19
+ export function conversationsOf(index) {
20
+ const local = localClock(index.timeZone ?? 'UTC');
21
+ const today = local(index.now).day;
22
+ const done = fixedOf(index);
23
+ const conversations = index.entries
24
+ .map((entry) => conversation(entry, local, today, done))
25
+ .sort((a, b) => b.entry.modifiedAt - a.entry.modifiedAt);
26
+ return { today, conversations };
27
+ }
28
+ /** The days `first` to `last`, each counted, and the conversations among them that need a person first. */
29
+ export function period(ago, first, last, today, conversations) {
30
+ const days = Array.from({ length: last - first + 1 }, (_unused, offset) => {
31
+ const number = first + offset;
32
+ const those = conversations.filter((item) => item.day.number === number);
33
+ const looks = {};
34
+ for (const item of those)
35
+ looks[item.look] = (looks[item.look] ?? 0) + 1;
36
+ return {
37
+ day: dayOf(number),
38
+ conversations: those.length,
39
+ // A read of files the person let the AI read is to check too: it is put before them, as a read is (F57a).
40
+ toCheck: those.filter((item) => LOOKS[item.look].attention).length,
41
+ fixed: those.filter((item) => item.look === 'fixed').length,
42
+ unchecked: those.filter((item) => !LOOKS[item.look].known).length,
43
+ looks,
44
+ future: number > today,
45
+ today: number === today,
46
+ };
47
+ });
48
+ const start = conversations.find((item) => item.look === 'read');
49
+ return {
50
+ ago,
51
+ first: dayOf(first),
52
+ last: dayOf(last),
53
+ days,
54
+ conversations,
55
+ ...(start === undefined ? {} : { start }),
56
+ toFix: conversations.filter((item) => item.look === 'read').length,
57
+ };
58
+ }
59
+ /**
60
+ * Conversations in buckets numbered by how far back they are, the current one always among them: a period nothing
61
+ * happened in is a dead end, so it is not offered, but it is still counted when periods are named.
62
+ */
63
+ export function bucketed(conversations, agoOf) {
64
+ const buckets = new Map([[0, []]]);
65
+ for (const item of conversations) {
66
+ // A conversation dated after today by a clock that moved is still the current period's, not one the page cannot reach.
67
+ const ago = Math.max(0, agoOf(item));
68
+ buckets.set(ago, [...(buckets.get(ago) ?? []), item]);
69
+ }
70
+ return new Map([...buckets].sort(([a], [b]) => a - b));
71
+ }
72
+ /**
73
+ * Whether a conversation that read something private has nothing left to fix (F16, changed 2026-09-25): the To fix
74
+ * list names it under no file, so every file it read was marked done after it ended - the rule the sidebar's count and
75
+ * the To fix screen already read (O10). Only where that is known: a record of marks that could not be read hid nothing,
76
+ * and a conversation that read no file a mark could close - only files the person asked to be told about - was never
77
+ * fixed by anyone, so it keeps the look it has.
78
+ */
79
+ function fixedOf(index) {
80
+ const check = index.check;
81
+ if (check === undefined || check.marksUnreadable === true)
82
+ return () => false;
83
+ const open = new Set(check.rows.filter((row) => TO_DO_LABELS.includes(row.label)).flatMap((row) => row.sessions));
84
+ return (entry, files) => !open.has(entry.name) && files.some((file) => file.kind === 'seen');
85
+ }
86
+ /**
87
+ * F57a: a conversation whose AI read only files the person chose Tell me for, none holding a key - its report lists
88
+ * them and asks for nothing, so its row does not ask either. It read them, and still says so. Only where its files are
89
+ * known: a report that listed none is not taken to have read nothing that is to fix.
90
+ */
91
+ function allowedOnly(files) {
92
+ return files.some((file) => file.kind === 'told') && !files.some((file) => file.kind === 'seen');
93
+ }
94
+ function conversation(entry, local, today, done) {
95
+ const { day, time } = local(entry.modifiedAt);
96
+ const files = entry.report.kind === 'generated' ? entry.report.files ?? [] : [];
97
+ const rung = LOOK_OF[statusOf(entry)];
98
+ const look = rung !== 'read' ? rung : allowedOnly(files) ? 'allowed' : done(entry, files) ? 'fixed' : rung;
99
+ const relative = day.number === today.number ? 'today' : day.number === today.number - 1 ? 'yesterday' : undefined;
100
+ return {
101
+ entry,
102
+ look,
103
+ day,
104
+ time,
105
+ ...(relative === undefined ? {} : { relative }),
106
+ files: files.map((file) => file.path),
107
+ read: files.filter((file) => file.kind === 'seen').length,
108
+ ...(entry.report.kind === 'generated' && entry.report.reached !== undefined ? { reached: entry.report.reached } : {}),
109
+ };
110
+ }
@@ -0,0 +1,30 @@
1
+ import { rangeName, tileName } from '../../render/ui/local-date.js';
2
+ import { inLanguages } from '../../render/report-copy.js';
3
+ import { dayTile, periodSwitch } from './calendar-view.js';
4
+ const WEEK_WORDS = {
5
+ heading: 'conv.week.heading',
6
+ earlier: 'conv.week.earlier',
7
+ later: 'conv.week.later',
8
+ back: 'conv.week.back',
9
+ pick: 'conv.week.pick',
10
+ hint: 'conv.week.hint',
11
+ };
12
+ /** "This week", "Last week", "3 weeks ago" - counted by the calendar, so a skipped empty week is still counted. */
13
+ export function weekName(week, t) {
14
+ return week.ago === 0 ? t('conv.week.this') : week.ago === 1 ? t('conv.week.last') : t('conv.week.ago', { n: week.ago });
15
+ }
16
+ /** "This week · Sep 21 – 27", in every language. */
17
+ export function weekTitle(week) {
18
+ return inLanguages((t, lang) => weekName(week, t) + ' · ' + rangeName(week.first, week.last, lang));
19
+ }
20
+ /**
21
+ * "Your week" (`for-people-who-build-with-ai.md` F10, F11): the switcher between weeks and one tile per day - today's
22
+ * called "Today", and the days still ahead saying so (the maintainer's design, 2026-09-29).
23
+ */
24
+ export function weekView(weeks, at) {
25
+ const week = weeks[at];
26
+ return '<section class="cw-week">' +
27
+ periodSwitch('week', weeks, at, WEEK_WORDS, (each, t, lang) => weekName(each, t) + ' <span class="cw-switch-range">· ' + rangeName(each.first, each.last, lang) + '</span>') +
28
+ '<div class="cw-days">' + week.days.map((day) => dayTile(day, inLanguages((t, lang) => (day.today ? t('conv.day.today') : tileName(day.day, lang))), { ahead: true })).join('') + '</div>' +
29
+ '</section>';
30
+ }
@@ -0,0 +1,17 @@
1
+ import { TO_DO_LABELS } from '../../check/check-lines.js';
2
+ import { bucketed, conversationsOf, period } from './periods.js';
3
+ /**
4
+ * The weeks the Conversations page is read through (`for-people-who-build-with-ai.md` F10, F11; O2, O3): calendar
5
+ * weeks, Monday to Sunday, in the time zone of the machine that ran `start`. Every number and date the page shows is
6
+ * decided here, and the page only writes it.
7
+ */
8
+ export function conversationWeeks(index) {
9
+ const { today, conversations } = conversationsOf(index);
10
+ const thisMonday = today.number - today.weekday;
11
+ const byWeek = bucketed(conversations, (item) => Math.floor((thisMonday - (item.day.number - item.day.weekday)) / 7));
12
+ return {
13
+ weeks: [...byWeek].map(([ago, those]) => period(ago, thisMonday - ago * 7, thisMonday - ago * 7 + 6, today.number, those)),
14
+ total: index.entries.length,
15
+ toFix: (index.check?.rows ?? []).filter((row) => TO_DO_LABELS.includes(row.label)).length,
16
+ };
17
+ }
@@ -0,0 +1,50 @@
1
+ import { INDEX_CONTENT_SECURITY_POLICY_META } from '../../render/html-head.js';
2
+ import { appSidebar } from '../../render/ui/app-sidebar.js';
3
+ import { pageShell } from '../../render/ui/page-shell.js';
4
+ import { appNav, sidebarProject } from '../app-nav.js';
5
+ import { projectsWindows } from '../projects/projects-window.js';
6
+ import { filesWindow, FILES_WINDOW_SCRIPTS, FILES_WINDOW_STYLES } from '../conversations/files-window.js';
7
+ import { periodSection, PERIOD_PAGE_LIBRARIES, PERIOD_PAGE_SCRIPTS, PERIOD_PAGE_STYLES } from '../conversations/period-section.js';
8
+ import { monthTitle, monthView, MONTH_VIEW_STYLE } from './month-view.js';
9
+ import { conversationMonths } from './months.js';
10
+ import { pageNotice } from '../page-notice.js';
11
+ /**
12
+ * This month, written as `month.html` beside the index (`.ai/plans/2026-09-23-month-redesign.md`): the Conversations
13
+ * page read a month at a time: the same answer and the same day tiles, in the same words, laid out as a calendar,
14
+ * each day with work opening in a window that lists its conversations, and each of those opening every file it
15
+ * reached in the window Conversations opens them in.
16
+ */
17
+ export class MonthRenderer {
18
+ #links;
19
+ constructor(links) {
20
+ this.#links = links;
21
+ }
22
+ render(index) {
23
+ const months = conversationMonths(index);
24
+ return pageShell({
25
+ // which-project V2, V11: the person's projects, opened from the sidebar's card.
26
+ windows: projectsWindows(index),
27
+ title: 'month.title',
28
+ // nothing-updates-by-itself U4: the update notice, where the project's hooks run an older release.
29
+ ...pageNotice(index),
30
+ policy: INDEX_CONTENT_SECURITY_POLICY_META,
31
+ styles: [...PERIOD_PAGE_STYLES, ...FILES_WINDOW_STYLES, MONTH_VIEW_STYLE],
32
+ libraries: PERIOD_PAGE_LIBRARIES,
33
+ scripts: [...PERIOD_PAGE_SCRIPTS, ...FILES_WINDOW_SCRIPTS],
34
+ sidebar: appSidebar({
35
+ ...sidebarProject(index),
36
+ home: this.#links.conversations,
37
+ items: appNav(index, 'month', this.#links),
38
+ }),
39
+ main: months.map((month, at) => periodSection(months, at, index, {
40
+ kind: 'month',
41
+ eyebrow: monthTitle(month),
42
+ view: monthView(months, at, index.widen, !index.shared),
43
+ zero: 'month.hero.zero',
44
+ // A month is read for its shape: it opens whole (T4), and a day opens in a window (T5). Nothing is drawn under
45
+ // the calendar - no guide card (T7), no lists (T8): what a day holds is in its window.
46
+ lists: false,
47
+ })).join('') + filesWindow(),
48
+ });
49
+ }
50
+ }