@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,329 @@
1
+ import { outcomeOfExecution } from '../access/recorded-effect.js';
2
+ import { MAIN_AGENT_TYPE } from '../agent.js';
3
+ import { capabilityGaps } from '../capability.js';
4
+ import { completenessOf } from '../completeness.js';
5
+ import { projectRootOf } from '../project-root.js';
6
+ import { PLAIN_INVOCATION_SOURCE, WORD_BREAK } from '../../shared/plain-invocation.js';
7
+ /**
8
+ * Joins what an adapter read into the model, following spec §4.3 exactly: a result belongs to a call by the
9
+ * call's own id, a delegation to the agent it started by the id of the call that started it, and **nothing is
10
+ * ever joined by time** - there is no timestamp in these records to be tempted by.
11
+ *
12
+ * What cannot be joined confidently becomes a gap and gets no outcome (§4.3 rule 6), never a best guess.
13
+ */
14
+ export function correlate(records) {
15
+ const gaps = [...records.gaps];
16
+ const resultsByCall = groupResults(records.results);
17
+ const calls = new Set(records.calls.map((call) => call.id));
18
+ for (const result of records.results) {
19
+ if (!calls.has(result.callId))
20
+ gaps.push({ kind: 'relation-unresolved' });
21
+ }
22
+ const running = reportRunning(records, resultsByCall);
23
+ const events = records.calls.filter((call) => call !== running).map((call) => toEvent(call, resultsByCall.get(call.id) ?? [], gaps));
24
+ // §4.3 rule 2: a delegated agent joins to its delegation by the id of the call that started it.
25
+ const indexByCall = indexDelegations(records.delegationIndex, gaps);
26
+ const delegated = new Set(records.delegations.map((call) => call.callId));
27
+ for (const entry of records.delegationIndex) {
28
+ if (!delegated.has(entry.callId))
29
+ gaps.push({ kind: 'relation-unresolved', agentId: entry.agentId });
30
+ }
31
+ // R4 of `2026-09-15-where-the-value-went.md`: a delivered report joins the delegation its notification names, by that
32
+ // call's id and nothing else. One naming any other call is no report of a delegation, and joins nothing.
33
+ const joined = delegations(records, indexByCall);
34
+ const byAgents = joinAgentReports(records, joined, gaps);
35
+ const reportsByCall = groupReports([...records.deliveredReports, ...byAgents.reports]);
36
+ // X17: later words join the delegation that started the agent they reached, by the id of the call that carried them.
37
+ const { followUps, unjoined } = joinFollowUps(records.followUps, records.followUpIndex, records.delegations, joined, gaps);
38
+ const delegationsOut = records.delegations.map((call) => toDelegation(call, joined.get(call.callId), reportsByCall.get(call.callId) ?? [], followUps.get(call.callId) ?? [], gaps));
39
+ const turns = checkedTurns(records.turns, gaps);
40
+ const contexts = [...records.contexts, ...unjoined, ...byAgents.unjoined];
41
+ // A text that is not whole is a gap wherever it is (X10, X31): a result, a delivery, a message or a context.
42
+ for (const event of events) {
43
+ if (event.result !== undefined && event.result.completeness !== 'complete')
44
+ gaps.push({ kind: 'result-incomplete', agentId: event.agentId });
45
+ }
46
+ for (const text of [...records.deliveries.map((delivery) => ({ ...delivery, agentId: delivery.recipientAgentId })), ...records.messages, ...contexts]) {
47
+ if (text.completeness !== 'complete')
48
+ gaps.push({ kind: 'result-incomplete', ...(text.agentId === undefined ? {} : { agentId: text.agentId }) });
49
+ }
50
+ gaps.push(...capabilityGaps(records.capabilities));
51
+ return {
52
+ sessionId: records.sessionId,
53
+ provider: records.provider,
54
+ projectRoot: projectRootOf(records.workingDirectories),
55
+ agents: records.agents,
56
+ delegations: delegationsOut,
57
+ events,
58
+ messages: records.messages,
59
+ turns,
60
+ reviews: records.reviews.map((review) => checkedReview(review, turns, gaps)),
61
+ contexts,
62
+ deliveries: records.deliveries,
63
+ capabilities: records.capabilities,
64
+ gaps,
65
+ completeness: completenessOf(gaps),
66
+ };
67
+ }
68
+ /**
69
+ * The index entry each delegating call keeps, once its own record agrees (X16): an entry whose started agent names a
70
+ * different parent contradicts the call, so neither is trusted.
71
+ */
72
+ function delegations(records, indexByCall) {
73
+ const joined = new Map();
74
+ for (const call of records.delegations) {
75
+ const entry = indexByCall.get(call.callId);
76
+ // The delegation keeps no agent; `toDelegation` names that as the gap it is, once.
77
+ const contradicts = entry?.recordedParentAgentId !== undefined && entry.recordedParentAgentId !== call.parentAgentId;
78
+ joined.set(call.callId, contradicts ? undefined : entry);
79
+ }
80
+ return joined;
81
+ }
82
+ /**
83
+ * A report named by its agents joins the one delegation between them: the delegation whose agent sent it, made by the
84
+ * agent that received it (X18). None, or more than one, and it joins nothing: its words stay with the recipient as
85
+ * context another agent wrote, with a relation gap - never handed to a delegation by a guess.
86
+ */
87
+ function joinAgentReports(records, joined, gaps) {
88
+ const reports = [];
89
+ const unjoined = [];
90
+ for (const report of records.agentReports) {
91
+ const between = records.delegations.filter((delegation) => delegation.parentAgentId === report.recipientAgentId && joined.get(delegation.callId)?.agentId === report.fromAgentId);
92
+ const delegation = between.length === 1 ? between[0] : undefined;
93
+ if (delegation === undefined) {
94
+ gaps.push({ kind: 'relation-unresolved', agentId: report.recipientAgentId });
95
+ unjoined.push({
96
+ kind: 'conversation', author: 'another-agent', agentId: report.recipientAgentId, text: report.content, completeness: 'complete', evidence: report.evidence,
97
+ });
98
+ continue;
99
+ }
100
+ reports.push({ callId: delegation.callId, content: report.content, evidence: report.evidence });
101
+ }
102
+ return { reports, unjoined };
103
+ }
104
+ /**
105
+ * Each follow-up joined to the one delegation that started the agent it reached, by that agent and by who sent it
106
+ * (X17). A call whose agent is no child its sender started, or whose index entry is missing or doubled, is not a
107
+ * follow-up: its words stay as an unjoined instruction of the sender, scanned, with a relation gap - never dropped, and
108
+ * never handed to a helper by a guess.
109
+ */
110
+ function joinFollowUps(calls, index, delegationCalls, joined, gaps) {
111
+ // The agent each follow-up call reached; `undefined` where two entries name the call, since neither can be trusted.
112
+ const reached = new Map();
113
+ for (const entry of index)
114
+ reached.set(entry.callId, reached.has(entry.callId) ? undefined : entry.agentId);
115
+ const followUps = new Map();
116
+ const unjoined = [];
117
+ for (const call of calls) {
118
+ const agentId = reached.get(call.callId);
119
+ const starting = agentId === undefined
120
+ ? []
121
+ : delegationCalls.filter((delegation) => delegation.parentAgentId === call.senderAgentId && joined.get(delegation.callId)?.agentId === agentId);
122
+ const delegation = starting.length === 1 ? starting[0] : undefined;
123
+ if (delegation === undefined) {
124
+ gaps.push({ kind: 'relation-unresolved', agentId: call.senderAgentId });
125
+ unjoined.push({
126
+ kind: 'unjoined-instruction', author: 'agent', agentId: call.senderAgentId, text: call.text, completeness: 'complete', evidence: call.evidence,
127
+ });
128
+ continue;
129
+ }
130
+ const followUp = { id: call.callId, senderAgentId: call.senderAgentId, text: call.text, evidence: call.evidence };
131
+ followUps.set(delegation.callId, [...(followUps.get(delegation.callId) ?? []), followUp]);
132
+ }
133
+ return { followUps, unjoined };
134
+ }
135
+ /**
136
+ * One turn per `(agentId, id)`: a second record of the same turn contradicts the first, so neither is kept and the
137
+ * relation is a gap - a turn is never picked by time or position.
138
+ */
139
+ function checkedTurns(turns, gaps) {
140
+ const key = (turn) => JSON.stringify([turn.agentId, turn.id]);
141
+ const counts = new Map();
142
+ for (const turn of turns)
143
+ counts.set(key(turn), (counts.get(key(turn)) ?? 0) + 1);
144
+ const doubled = new Set();
145
+ for (const turn of turns) {
146
+ if ((counts.get(key(turn)) ?? 0) < 2 || doubled.has(key(turn)))
147
+ continue;
148
+ doubled.add(key(turn));
149
+ gaps.push({ kind: 'relation-unresolved', agentId: turn.agentId });
150
+ }
151
+ return turns.filter((turn) => !doubled.has(key(turn)));
152
+ }
153
+ /**
154
+ * A verdict keeps the turn it names only when that is a turn of the reviewed agent (X20). A missing one is a review of no
155
+ * known turn, which the report says; one naming a turn the agent does not have is unlinked, with a gap in the reviewer's
156
+ * own source.
157
+ */
158
+ function checkedReview(review, turns, gaps) {
159
+ const known = new Set(turns.filter((turn) => turn.agentId === review.reviewedAgentId).map((turn) => turn.id));
160
+ return {
161
+ ...review,
162
+ verdicts: review.verdicts.map((verdict) => {
163
+ if (verdict.turnId === undefined || known.has(verdict.turnId))
164
+ return verdict;
165
+ gaps.push({ kind: 'relation-unresolved', source: verdict.evidence.source });
166
+ const { turnId: _unlinked, ...unlinked } = verdict;
167
+ return unlinked;
168
+ }),
169
+ };
170
+ }
171
+ /**
172
+ * The call that is running this very report, where the session's own agent ran it: the session's last call, with no
173
+ * result yet, and nothing in it but `agentwhy report` and its flags. Its result cannot be in the record while the report
174
+ * is being drawn - the result is this report - so it is no gap, and a report opened from the conversation reads the
175
+ * same as one opened from Conversations a moment later. Anything more on the line (a pipe, a second command, a
176
+ * redirect) is a call like any other, and one with no result stays unknown.
177
+ */
178
+ function reportRunning(records, results) {
179
+ const main = records.agents.find((agent) => agent.type === MAIN_AGENT_TYPE);
180
+ if (main === undefined)
181
+ return undefined;
182
+ // A sequence counts one transcript's calls, so the last call is the main transcript's own.
183
+ const last = records.calls.filter((call) => call.agentId === main.id)
184
+ .reduce((latest, call) => (latest === undefined || call.sequence > latest.sequence ? call : latest), undefined);
185
+ if (last === undefined || results.has(last.id))
186
+ return undefined;
187
+ return last.commands.length === 1 && AGENTWHY_REPORT.test(last.commands[0] ?? '') ? last : undefined;
188
+ }
189
+ /**
190
+ * `agentwhy report` with flags and plain values only: no shell operator, quote, substitution or second line. agentwhy
191
+ * is named by the same pattern a hook's invocation is read with, because the agent is asked to run it the way the hook
192
+ * does (`the-agent-tells-you.md` R18): a copy of its own would drift, and a report opened from the chat would show a
193
+ * gap. Found by a review: any word holding agentwhy passed, so another script's missing result was dropped too. Words
194
+ * are split where a shell splits them, on a space or a tab: a line break passed for one, and so did a second command.
195
+ */
196
+ const AGENTWHY_REPORT = new RegExp(`^[ \\t]*(?:${PLAIN_INVOCATION_SOURCE})${WORD_BREAK}report(?:${WORD_BREAK}[\\w@%+=:,./~-]+)*[ \\t]*$`);
197
+ /**
198
+ * Retries are separate calls with separate ids (§4.3 rule 7), so two results for one id is not a retry - it is
199
+ * an ambiguity, and picking one of them would hand the event a confident outcome it has not earned. They are
200
+ * kept together and the event is left without one.
201
+ */
202
+ function groupResults(results) {
203
+ const byCall = new Map();
204
+ for (const result of results) {
205
+ const existing = byCall.get(result.callId);
206
+ if (existing === undefined)
207
+ byCall.set(result.callId, [result]);
208
+ else
209
+ existing.push(result);
210
+ }
211
+ return byCall;
212
+ }
213
+ function groupReports(reports) {
214
+ const byCall = new Map();
215
+ for (const report of reports) {
216
+ const existing = byCall.get(report.callId);
217
+ if (existing === undefined)
218
+ byCall.set(report.callId, [report]);
219
+ else
220
+ existing.push(report);
221
+ }
222
+ return byCall;
223
+ }
224
+ /** A second index entry for one call is the same kind of ambiguity: neither entry can be trusted to be the one. */
225
+ function indexDelegations(entries, gaps) {
226
+ const byCall = new Map();
227
+ for (const entry of entries) {
228
+ if (byCall.has(entry.callId)) {
229
+ gaps.push({ kind: 'relation-unresolved', agentId: entry.agentId });
230
+ byCall.set(entry.callId, undefined);
231
+ continue;
232
+ }
233
+ byCall.set(entry.callId, entry);
234
+ }
235
+ return byCall;
236
+ }
237
+ function toEvent(call, results, sessionGaps) {
238
+ const own = [];
239
+ const result = results.length === 1 ? results[0] : undefined;
240
+ const outcome = results.length > 1 ? unresolved(call.agentId, own) : outcomeOf(call, result, call.agentId, own);
241
+ sessionGaps.push(...own);
242
+ const event = {
243
+ id: call.id,
244
+ agentId: call.agentId,
245
+ sequence: call.sequence,
246
+ toolName: call.toolName,
247
+ input: call.input,
248
+ targets: call.targets,
249
+ commands: call.commands,
250
+ resultShape: call.resultShape,
251
+ toolKnown: call.toolKnown,
252
+ ...(call.printsMatches === true ? { printsMatches: true } : {}),
253
+ ...(call.written === undefined ? {} : { written: call.written }),
254
+ outcome,
255
+ ...(result?.execution === undefined ? {} : { execution: result.execution }),
256
+ ...(call.turnId === undefined ? {} : { turnId: call.turnId }),
257
+ evidence: call.evidence,
258
+ completeness: completenessOf(own),
259
+ };
260
+ return result === undefined ? event : { ...event, result: toResult(result) };
261
+ }
262
+ function unresolved(agentId, own) {
263
+ own.push({ kind: 'relation-unresolved', agentId });
264
+ return 'unknown';
265
+ }
266
+ function outcomeOf(call, result, agentId, own) {
267
+ if (result === undefined) {
268
+ own.push({ kind: 'result-missing', agentId });
269
+ return 'unknown';
270
+ }
271
+ if (result.attributionAmbiguous === true)
272
+ return unresolved(agentId, own);
273
+ if (result.spilledResultMissing === true) {
274
+ own.push({ kind: 'spilled-result-missing', agentId });
275
+ return 'unknown';
276
+ }
277
+ if (result.denial !== undefined) {
278
+ // An unrecognised marker is UNKNOWN, never BLOCKED: the field is typed, its vocabulary is not documented.
279
+ if (result.denial.recognised)
280
+ return 'blocked';
281
+ own.push({ kind: 'outcome-unrecognised', agentId });
282
+ return 'unknown';
283
+ }
284
+ // X11: where the runtime recorded running the call, the record establishes access only as far as a profile says.
285
+ if (result.execution !== undefined) {
286
+ if (result.execution.status === 'unrecognised') {
287
+ own.push({ kind: 'outcome-unrecognised', agentId });
288
+ return 'unknown';
289
+ }
290
+ const outcome = outcomeOfExecution(call, result.execution);
291
+ // An effect the record does not establish is a gap, so that no report built on it reads as whole (X11).
292
+ if (outcome === 'unknown')
293
+ own.push({ kind: 'capability-absent', question: 'access', agentId });
294
+ return outcome;
295
+ }
296
+ return 'succeeded';
297
+ }
298
+ function toResult(result) {
299
+ return {
300
+ ...(result.content === undefined ? {} : { content: result.content }),
301
+ ...(result.denial === undefined ? {} : { denialKind: result.denial.kind }),
302
+ ...(result.launchNotice === true ? { launchNotice: true } : {}),
303
+ stage: result.stage,
304
+ completeness: result.completeness,
305
+ evidence: result.evidence,
306
+ };
307
+ }
308
+ function toDelegation(call, entry, reports, followUps, sessionGaps) {
309
+ const own = [];
310
+ // No index entry means the agent it started cannot be named. The delegation still happened, and saying so is
311
+ // the point: a delegation that vanishes from the report because its index is missing is the worse failure.
312
+ if (entry === undefined)
313
+ own.push({ kind: 'relation-unresolved', agentId: call.parentAgentId });
314
+ sessionGaps.push(...own);
315
+ const completeness = completenessOf(own);
316
+ return {
317
+ id: call.callId,
318
+ parentAgentId: call.parentAgentId,
319
+ ...(entry === undefined ? {} : { childAgentId: entry.agentId }),
320
+ ...(entry?.requestedType === undefined ? {} : { requestedType: entry.requestedType }),
321
+ ...(entry?.depth === undefined ? {} : { depth: entry.depth }),
322
+ ...(call.prompt === undefined ? {} : { prompt: call.prompt }),
323
+ ...(call.description === undefined ? {} : { description: call.description }),
324
+ reports: reports.map(({ content, evidence }) => ({ content, evidence })),
325
+ followUps,
326
+ evidence: call.evidence,
327
+ completeness,
328
+ };
329
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,78 @@
1
+ /**
2
+ * The signals of specification §6.2, read out of the text a delegation carried.
3
+ *
4
+ * §6.2 gives each signal a meaning and one example; how one is recognised is
5
+ * `specs/2026-09-16-what-a-signal-looks-like.md`, written on counts taken from 15 real delegations rather than from
6
+ * intuition - which is how this project has three times written a rule over free text and paid for it (L009, L011).
7
+ *
8
+ * Two results of that measurement shape this module:
9
+ *
10
+ * - **Words are read outside paths** (R3). A prompt naming `apps/web/.env` was counted as being about configuration
11
+ * because the word `env` sits inside the path. The text a path covers is removed before a word rule runs.
12
+ * - **`S4` is an identifier, not a word** (R6). A word list for configuration fired on 12 of 15 delegations - four
13
+ * fifths of ordinary work - while `NEXT_PUBLIC_SITE_URL` and its like fired on 3. The word list is not here.
14
+ *
15
+ * Nothing in this module decides anything: a signal fires, and what a delegation led to is a label a person and the
16
+ * actions agree on, joined to these only by the scoring of the D1 plan (R8).
17
+ */
18
+ import { pathTokens } from '../access/path-tokens.js';
19
+ import { protects } from '../policy/policy.js';
20
+ /** R4: asks for a value or for contents. The list is the one that was counted; it was not tuned afterwards. */
21
+ const ASKS_FOR_A_VALUE = /\b(?:value|values|content|contents|what is set|what value|print|show)\b/i;
22
+ /** R5: asks for a comparison of two things. */
23
+ const ASKS_FOR_A_COMPARISON = /\b(?:match|matches|matching|compare|compared|comparison|same as|identical|differs?|different|equal)\b/i;
24
+ /** R6: a name that points at configuration while naming no file - §6.2's own example of `S4`. */
25
+ const CONFIGURATION_IDENTIFIER = /\b[A-Z][A-Z0-9]*(?:_[A-Z0-9]+)+\b/;
26
+ /** R7: `C1` covers disclosure - do not quote the value - and `C2` covers reading the file at all. */
27
+ const NOT_TO_QUOTE = /\b(?:do not quote|don't quote|without quoting|do not print|don't print|never quote|do not paste|don't paste)\b/i;
28
+ const NOT_TO_READ = /\b(?:do not read|don't read|without reading|do not open|don't open|only confirm|confirm only|presence of)\b/i;
29
+ /**
30
+ * A glob asks about a family of paths. `.env*` is a pattern for a protected path; `x*` is a pattern for nothing.
31
+ * Two expressions, because one carrying `g` would keep its place between calls and answer every second question
32
+ * wrongly.
33
+ */
34
+ const HOLDS_A_GLOB = /[*?]/;
35
+ const EVERY_GLOB = /[*?]/g;
36
+ function namesAProtectedPath(tokens, policy) {
37
+ return tokens.some((token) => protects(policy, token) || (HOLDS_A_GLOB.test(token) && protects(policy, token.replace(EVERY_GLOB, 'x'))));
38
+ }
39
+ /**
40
+ * The text with the paths blanked out, so that a word rule cannot read a word that is part of one (R3). Blanked
41
+ * rather than deleted, so two words either side of a path do not become one.
42
+ *
43
+ * Only the tokens that look like a path go: `pathTokens` splits text at every separator and returns **every** word,
44
+ * because the policy is what decides which of them is a path. Blanking all of them would leave no text for a word
45
+ * rule to read - which is what the first version did, and what its tests caught.
46
+ */
47
+ function looksLikeAPath(token) {
48
+ return token.includes('/') || token.startsWith('.') || token.startsWith('~');
49
+ }
50
+ function outsidePaths(text, tokens) {
51
+ let left = text;
52
+ for (const token of tokens.filter(looksLikeAPath))
53
+ left = left.split(token).join(' ');
54
+ return left;
55
+ }
56
+ /**
57
+ * What a delegation's prompt and description carry, read as one text (R1). The policy decides what a protected
58
+ * path is, so a project that protects more is read as naming more - the signal follows the policy, not a list.
59
+ */
60
+ export function readPrompt(text, policy) {
61
+ const tokens = pathTokens(text);
62
+ const outside = outsidePaths(text, tokens);
63
+ const signals = [];
64
+ if (namesAProtectedPath(tokens, policy))
65
+ signals.push('S1');
66
+ if (ASKS_FOR_A_VALUE.test(outside))
67
+ signals.push('S2');
68
+ if (ASKS_FOR_A_COMPARISON.test(outside))
69
+ signals.push('S3');
70
+ if (CONFIGURATION_IDENTIFIER.test(outside))
71
+ signals.push('S4');
72
+ const caveats = [];
73
+ if (NOT_TO_QUOTE.test(outside))
74
+ caveats.push('C1');
75
+ if (NOT_TO_READ.test(outside))
76
+ caveats.push('C2');
77
+ return { signals, caveats };
78
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,9 @@
1
+ export function mainSource() {
2
+ return { kind: 'main' };
3
+ }
4
+ export function agentSource(agentId) {
5
+ return { kind: 'agent', agentId };
6
+ }
7
+ export function reviewSource(reviewId) {
8
+ return { kind: 'review', reviewId };
9
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * An agent's last words, added to the model when the transcript on disk does not hold them yet
3
+ * (`specs/2026-09-16-when-an-agent-finishes.md` R9).
4
+ *
5
+ * **Measured 2026-09-16 (B4c):** on 4 of 4 finished agents - foreground, background and nested - the agent's only text
6
+ * block was written to its transcript after the `SubagentStop` hook had run. A reading of the file alone misses exactly
7
+ * the message the motivating case's value was in.
8
+ *
9
+ * The record it is given is the one after every record of that agent read from disk. That is not a line number: an
10
+ * attachment line may land between them. It is the one thing known about its place - it was written after all of
11
+ * them - and it is the order the rules that compare records need. When the words are already on disk, the model is
12
+ * returned unchanged, so a transcript that caught up is not read twice.
13
+ */
14
+ export function withLateMessage(model, agentId, text) {
15
+ if (text === '')
16
+ return model;
17
+ const fromAgent = (evidence) => evidence.source.kind === 'agent' && evidence.source.agentId === agentId;
18
+ const own = model.messages.filter((message) => message.agentId === agentId);
19
+ if (own.some((message) => message.text === text))
20
+ return model;
21
+ const records = [
22
+ ...model.events.flatMap((event) => [event.evidence, ...(event.result === undefined ? [] : [event.result.evidence])]),
23
+ ...own.map((message) => message.evidence),
24
+ ]
25
+ .filter(fromAgent)
26
+ .map((evidence) => evidence.record);
27
+ const after = records.reduce((last, record) => Math.max(last, record), 0) + 1;
28
+ return {
29
+ ...model,
30
+ messages: [...model.messages, { agentId, kind: 'said', text, completeness: 'complete', evidence: { source: { kind: 'agent', agentId }, record: after } }],
31
+ };
32
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,28 @@
1
+ import { isAbsolute, relative, sep } from 'node:path';
2
+ /**
3
+ * A folder that is gone cannot be offered, so only projects whose folder exists are considered. Where the folder lies
4
+ * inside more than one project, the nearest - the deepest - is the one; the projects inside it keep the listing's order.
5
+ * Paths are compared through `relative`, which compares a Windows path without regard to case, as its file system does.
6
+ *
7
+ * Nor is a folder `noProject` names (V6): Claude Code keeps the conversations of whatever folder it was started in, the
8
+ * home directory too, and a new terminal starts there. Found by a review: every folder under the home directory was
9
+ * offered the home directory as the project above it, which setup then refuses.
10
+ */
11
+ export function nearestProject(folder, listing, noProject) {
12
+ const present = listing.projects.filter((project) => project.exists && !noProject(project.path));
13
+ const itself = present.find((project) => relative(project.path, folder) === '');
14
+ if (itself !== undefined)
15
+ return { kind: 'itself', project: itself };
16
+ const above = present
17
+ .filter((project) => within(project.path, folder))
18
+ .sort((a, b) => b.path.length - a.path.length)[0];
19
+ if (above !== undefined)
20
+ return { kind: 'above', project: above };
21
+ const inside = present.filter((project) => within(folder, project.path));
22
+ return inside.length > 0 ? { kind: 'inside', projects: inside } : { kind: 'none' };
23
+ }
24
+ /** Whether `path` lies under `folder`, and is not `folder` itself. */
25
+ function within(folder, path) {
26
+ const between = relative(folder, path);
27
+ return between !== '' && between !== '..' && !between.startsWith(`..${sep}`) && !isAbsolute(between);
28
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * The fallback when no policy was configured anywhere.
3
+ *
4
+ * `allowed` is **empty on purpose**. The obvious entries would be `.env.example` and `.env.sample`, and adding
5
+ * them would have the tool decide, on its own, that a file is safe. Teams leave real values in those templates,
6
+ * and spec §8.2 makes the same point from the other side: if the policy covers a sample file, that file stays
7
+ * out of reach too. An exception is a decision someone takes and writes down, never a gift from the default.
8
+ */
9
+ export const DEFAULT_POLICY = {
10
+ level: 'no-read',
11
+ protected: [
12
+ // The name starts with `.env`, or ends with it. Chosen on measurement against a measured session
13
+ // (2026-09-14), not on taste - the wider shapes cost far more than they found:
14
+ //
15
+ // `.env*` 113 findings · 29 paths · 4 of 5 refused calls recognised
16
+ // `.env*` + `*.env` 124 findings · 34 paths · 4 of 5 <- this
17
+ // `*.env` + `*.env.*` 218 findings · 91 paths · 4 of 5
18
+ // `*.env*` 240 findings · 97 paths · 5 of 5
19
+ //
20
+ // The last catches one more refused call at the price of 116 further findings, most of them a mention of
21
+ // `.environment` in a document. That one call names a file shape none of the narrower patterns knows; it is
22
+ // recorded as an open question in the M3 plan rather than paid for with noise.
23
+ { pattern: '**/.env*' },
24
+ { pattern: '**/*.env' },
25
+ { pattern: '**/.npmrc' },
26
+ { pattern: '**/secrets/**' },
27
+ { pattern: '**/.ssh/**' },
28
+ { pattern: '**/id_rsa*' },
29
+ ],
30
+ allowed: [],
31
+ origin: { kind: 'default' },
32
+ };
@@ -0,0 +1,56 @@
1
+ /**
2
+ * Path-glob matching, written out rather than taken from a library or from an experimental Node API, because
3
+ * which paths count as protected is the security decision of this tool: it has to be small enough to read and
4
+ * pinned by its own tests.
5
+ *
6
+ * `**` crosses directory separators, `*` and `?` do not. A pattern is anchored at both ends.
7
+ */
8
+ const SPECIAL = /[.+^${}()|[\]\\]/g;
9
+ export function globToRegExp(pattern) {
10
+ let source = '';
11
+ for (let index = 0; index < pattern.length; index += 1) {
12
+ const character = pattern[index] ?? '';
13
+ const next = pattern[index + 1];
14
+ if (character === '*' && next === '*') {
15
+ // `**/` also matches nothing at all, so `**/.env` matches `.env` at the root.
16
+ if (pattern[index + 2] === '/') {
17
+ source += '(?:.*/)?';
18
+ index += 2;
19
+ }
20
+ else {
21
+ source += '.*';
22
+ index += 1;
23
+ }
24
+ continue;
25
+ }
26
+ // `*` stops at whitespace as well as at a separator. Without that, a sentence mentioning a path matches a
27
+ // path pattern: everything up to the last slash is eaten by `**/`, and `.env*` then happily covers
28
+ // `.env (only the names, no values) ---`. A directory with a space still works, because `**/` may hold one;
29
+ // the cost is a **file name** containing a space, which is the rarer of the two by far.
30
+ if (character === '*') {
31
+ source += '[^/\\s]*';
32
+ continue;
33
+ }
34
+ if (character === '?') {
35
+ source += '[^/\\s]';
36
+ continue;
37
+ }
38
+ source += character.replace(SPECIAL, '\\$&');
39
+ }
40
+ return new RegExp(`^${source}$`);
41
+ }
42
+ /**
43
+ * The compiled form of each pattern met so far. A policy holds a handful of patterns and they are asked about once
44
+ * per candidate path, so a busy session compiled the same few thousands of times: measured at 566 ms of one run on a
45
+ * record-heavy transcript, spent entirely on rebuilding regular expressions that never differ. The compiled
46
+ * expression carries no `g` flag and no state, so returning the same one twice cannot change an answer.
47
+ */
48
+ const COMPILED = new Map();
49
+ export function matchesGlob(path, pattern) {
50
+ let compiled = COMPILED.get(pattern);
51
+ if (compiled === undefined) {
52
+ compiled = globToRegExp(pattern);
53
+ COMPILED.set(pattern, compiled);
54
+ }
55
+ return compiled.test(path);
56
+ }
@@ -0,0 +1,70 @@
1
+ import { isJsonObject, parseJsonObject } from '../../shared/json.js';
2
+ export const POLICY_VERSION = 1;
3
+ const LEVELS = ['no-read', 'no-disclose'];
4
+ const KEYS = new Set(['version', 'level', 'protected', 'allowed']);
5
+ export function parsePolicy(text, path) {
6
+ const document = parseJsonObject(text);
7
+ if (document === undefined)
8
+ return { errors: [`${path} is not a JSON object`] };
9
+ const errors = [];
10
+ const unknown = Object.keys(document).filter((key) => !KEYS.has(key));
11
+ if (unknown.length > 0) {
12
+ errors.push(`unknown ${unknown.length === 1 ? 'key' : 'keys'} ${unknown.join(', ')} — remove them, or fix the spelling`);
13
+ }
14
+ if (document.version !== POLICY_VERSION) {
15
+ errors.push(`"version" must be ${POLICY_VERSION} — add it, so a later format change is detected rather than guessed`);
16
+ }
17
+ const level = document.level;
18
+ if (typeof level !== 'string' || !isLevel(level)) {
19
+ errors.push(`"level" must be one of ${LEVELS.join(', ')}`);
20
+ }
21
+ const protectedPaths = readProtected(document.protected, errors);
22
+ const allowed = readAllowed(document.allowed, errors);
23
+ if (errors.length > 0)
24
+ return { errors };
25
+ return {
26
+ policy: {
27
+ level: level,
28
+ protected: protectedPaths,
29
+ allowed,
30
+ origin: { kind: 'file', path },
31
+ },
32
+ };
33
+ }
34
+ function isLevel(value) {
35
+ return LEVELS.includes(value);
36
+ }
37
+ /** An entry is a pattern, or a pattern with its own level. Both shapes are read; nothing acts on the level yet. */
38
+ function readProtected(value, errors) {
39
+ if (!Array.isArray(value) || value.length === 0) {
40
+ errors.push('"protected" must be a non-empty array of path patterns');
41
+ return [];
42
+ }
43
+ return value.flatMap((entry, index) => {
44
+ if (typeof entry === 'string')
45
+ return entry === '' ? refuse(errors, index, 'is empty') : [{ pattern: entry }];
46
+ if (!isJsonObject(entry))
47
+ return refuse(errors, index, 'must be a pattern or an object with "path"');
48
+ const pattern = entry.path;
49
+ const level = entry.level;
50
+ if (typeof pattern !== 'string' || pattern === '')
51
+ return refuse(errors, index, 'has no "path"');
52
+ if (level !== undefined && (typeof level !== 'string' || !isLevel(level))) {
53
+ return refuse(errors, index, `has a "level" that is not one of ${LEVELS.join(', ')}`);
54
+ }
55
+ return [{ pattern, ...(level === undefined ? {} : { level: level }) }];
56
+ });
57
+ }
58
+ function readAllowed(value, errors) {
59
+ if (value === undefined)
60
+ return [];
61
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== 'string' || entry === '')) {
62
+ errors.push('"allowed" must be an array of path patterns');
63
+ return [];
64
+ }
65
+ return value;
66
+ }
67
+ function refuse(errors, index, problem) {
68
+ errors.push(`"protected" entry ${index + 1} ${problem}`);
69
+ return [];
70
+ }