@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.
- package/LICENSE +202 -0
- package/NOTICE +2 -0
- package/README.md +181 -0
- package/THIRD_PARTY_NOTICES.md +31 -0
- package/dist/adapter/claude-code/contract/assumptions.js +208 -0
- package/dist/adapter/claude-code/contract/denials.js +15 -0
- package/dist/adapter/claude-code/contract/entry-points.js +15 -0
- package/dist/adapter/claude-code/contract/fields.js +73 -0
- package/dist/adapter/claude-code/contract/hooks.js +96 -0
- package/dist/adapter/claude-code/contract/identifiers.js +15 -0
- package/dist/adapter/claude-code/contract/layout.js +29 -0
- package/dist/adapter/claude-code/contract/line-types.js +26 -0
- package/dist/adapter/claude-code/contract/projects.js +23 -0
- package/dist/adapter/claude-code/contract/recognition.js +14 -0
- package/dist/adapter/claude-code/contract/session-title.js +20 -0
- package/dist/adapter/claude-code/contract/settings.js +27 -0
- package/dist/adapter/claude-code/contract/task-notifications.js +33 -0
- package/dist/adapter/claude-code/contract/tools.js +37 -0
- package/dist/adapter/claude-code/contract/version.js +51 -0
- package/dist/adapter/claude-code/discovery/claude-code-project-catalogue.js +129 -0
- package/dist/adapter/claude-code/discovery/claude-code-recognition.js +46 -0
- package/dist/adapter/claude-code/discovery/claude-code-session-catalogue.js +100 -0
- package/dist/adapter/claude-code/discovery/claude-code-session-discovery.js +61 -0
- package/dist/adapter/claude-code/discovery/claude-code-session-titles.js +28 -0
- package/dist/adapter/claude-code/discovery/discovered-session.js +1 -0
- package/dist/adapter/claude-code/discovery/session-entries.js +41 -0
- package/dist/adapter/claude-code/discovery/transcript-tail.js +81 -0
- package/dist/adapter/claude-code/events/claude-code-session-source.js +197 -0
- package/dist/adapter/claude-code/events/delegation-index.js +15 -0
- package/dist/adapter/claude-code/events/transcript-scan.js +179 -0
- package/dist/adapter/claude-code/hooks/hook-output.js +42 -0
- package/dist/adapter/claude-code/hooks/pre-tool-use-input.js +23 -0
- package/dist/adapter/claude-code/hooks/stop-input.js +25 -0
- package/dist/adapter/claude-code/hooks/subagent-stop-input.js +26 -0
- package/dist/adapter/claude-code/policy/deny-rules.js +46 -0
- package/dist/adapter/claude-code/probe/claude-code-probe.js +86 -0
- package/dist/adapter/claude-code/probe/collectors/agent-tool-input-key-collector.js +24 -0
- package/dist/adapter/claude-code/probe/collectors/denial-kind-collector.js +20 -0
- package/dist/adapter/claude-code/probe/collectors/denied-call-collector.js +11 -0
- package/dist/adapter/claude-code/probe/collectors/identifier-collector.js +19 -0
- package/dist/adapter/claude-code/probe/collectors/line-collector.js +1 -0
- package/dist/adapter/claude-code/probe/collectors/line-type-collector.js +29 -0
- package/dist/adapter/claude-code/probe/collectors/sidechain-collector.js +20 -0
- package/dist/adapter/claude-code/probe/collectors/task-notification-collector.js +51 -0
- package/dist/adapter/claude-code/probe/collectors/tool-result-reference-collector.js +31 -0
- package/dist/adapter/claude-code/probe/collectors/tool-use-collector.js +24 -0
- package/dist/adapter/claude-code/probe/collectors/tool-version-collector.js +14 -0
- package/dist/adapter/claude-code/probe/collectors/top-level-key-collector.js +14 -0
- package/dist/adapter/claude-code/probe/collectors/unknown-line-type-key-collector.js +24 -0
- package/dist/adapter/claude-code/probe/collectors/working-directory-collector.js +28 -0
- package/dist/adapter/claude-code/probe/doctor-report.js +1 -0
- package/dist/adapter/claude-code/probe/meta-tally.js +42 -0
- package/dist/adapter/claude-code/probe/session-probe.js +1 -0
- package/dist/adapter/claude-code/probe/session-tally.js +41 -0
- package/dist/adapter/claude-code/probe/transcript-line.js +30 -0
- package/dist/adapter/claude-code/probe/transcript-tally.js +74 -0
- package/dist/adapter/claude-code/settings/deny-entries.js +65 -0
- package/dist/adapter/claude-code/settings/hook-entries.js +303 -0
- package/dist/adapter/codex/contract/actions.js +69 -0
- package/dist/adapter/codex/contract/assumptions.js +55 -0
- package/dist/adapter/codex/contract/capabilities.js +24 -0
- package/dist/adapter/codex/contract/delegations.js +30 -0
- package/dist/adapter/codex/contract/deliveries.js +25 -0
- package/dist/adapter/codex/contract/envelope.js +33 -0
- package/dist/adapter/codex/contract/hook-refusals.js +14 -0
- package/dist/adapter/codex/contract/hooks.js +46 -0
- package/dist/adapter/codex/contract/messages.js +47 -0
- package/dist/adapter/codex/contract/permissions.js +27 -0
- package/dist/adapter/codex/contract/reviews.js +13 -0
- package/dist/adapter/codex/contract/session.js +31 -0
- package/dist/adapter/codex/contract/turns.js +23 -0
- package/dist/adapter/codex/contract/version.js +15 -0
- package/dist/adapter/codex/discovery/codex-conversations.js +44 -0
- package/dist/adapter/codex/discovery/codex-project-catalogue.js +49 -0
- package/dist/adapter/codex/discovery/codex-recognition.js +20 -0
- package/dist/adapter/codex/discovery/codex-session-catalogue.js +28 -0
- package/dist/adapter/codex/discovery/codex-session-discovery.js +115 -0
- package/dist/adapter/codex/discovery/codex-session-index.js +21 -0
- package/dist/adapter/codex/discovery/codex-session-titles.js +135 -0
- package/dist/adapter/codex/discovery/session-header.js +86 -0
- package/dist/adapter/codex/discovery/session-owners.js +11 -0
- package/dist/adapter/codex/discovery/session-roots.js +49 -0
- package/dist/adapter/codex/events/action-items.js +138 -0
- package/dist/adapter/codex/events/capability-records.js +26 -0
- package/dist/adapter/codex/events/codex-session-source.js +121 -0
- package/dist/adapter/codex/events/hook-refusals.js +27 -0
- package/dist/adapter/codex/events/rollout-scan.js +614 -0
- package/dist/adapter/codex/events/turn-permissions.js +60 -0
- package/dist/adapter/codex/hooks/pre-tool-use-input.js +24 -0
- package/dist/adapter/codex/hooks/stop-refusals.js +57 -0
- package/dist/adapter/codex/probe/codex-doctor-report.js +1 -0
- package/dist/adapter/codex/probe/codex-probe.js +268 -0
- package/dist/adapter/codex/settings/codex-hooks.js +83 -0
- package/dist/cli/cli-command.js +1 -0
- package/dist/cli/command-router.js +42 -0
- package/dist/cli/commands/check-cli-command.js +128 -0
- package/dist/cli/commands/codex-stop-cli-command.js +27 -0
- package/dist/cli/commands/doctor-arguments.js +32 -0
- package/dist/cli/commands/doctor-cli-command.js +31 -0
- package/dist/cli/commands/doctor-usage.js +14 -0
- package/dist/cli/commands/init-cli-command.js +123 -0
- package/dist/cli/commands/menu-cli-command.js +58 -0
- package/dist/cli/commands/notify-cli-command.js +158 -0
- package/dist/cli/commands/refuse-cli-command.js +79 -0
- package/dist/cli/commands/report-arguments.js +62 -0
- package/dist/cli/commands/report-cli-command.js +36 -0
- package/dist/cli/commands/report-usage.js +41 -0
- package/dist/cli/commands/sessions-cli-command.js +184 -0
- package/dist/cli/commands/start-cli-command.js +83 -0
- package/dist/cli/commands/watch-cli-command.js +142 -0
- package/dist/cli/exit-codes.js +13 -0
- package/dist/cli/project-switches.js +46 -0
- package/dist/cli.js +80 -0
- package/dist/composition-root.js +411 -0
- package/dist/core/access/command-line.js +454 -0
- package/dist/core/access/listing.js +58 -0
- package/dist/core/access/path-shape.js +59 -0
- package/dist/core/access/path-tokens.js +37 -0
- package/dist/core/access/protected-access.js +310 -0
- package/dist/core/access/protected-values.js +102 -0
- package/dist/core/access/recorded-effect.js +67 -0
- package/dist/core/access/returns.js +173 -0
- package/dist/core/access/search-output.js +34 -0
- package/dist/core/access/search-reach.js +268 -0
- package/dist/core/access/uses.js +143 -0
- package/dist/core/access/words-before.js +52 -0
- package/dist/core/agent.js +1 -0
- package/dist/core/capability.js +11 -0
- package/dist/core/combined-project-catalogue.js +36 -0
- package/dist/core/combined-session-catalogue.js +25 -0
- package/dist/core/completeness.js +10 -0
- package/dist/core/context.js +1 -0
- package/dist/core/correlation/correlate.js +329 -0
- package/dist/core/delegation.js +1 -0
- package/dist/core/detector/prompt-signals.js +78 -0
- package/dist/core/entry-point.js +1 -0
- package/dist/core/event.js +1 -0
- package/dist/core/evidence.js +9 -0
- package/dist/core/late-message.js +32 -0
- package/dist/core/message.js +1 -0
- package/dist/core/nearest-project.js +28 -0
- package/dist/core/policy/default-policy.js +32 -0
- package/dist/core/policy/glob.js +56 -0
- package/dist/core/policy/parse-policy.js +70 -0
- package/dist/core/policy/policy.js +20 -0
- package/dist/core/policy/resolve-policy.js +16 -0
- package/dist/core/project-catalogue.js +1 -0
- package/dist/core/project-root.js +133 -0
- package/dist/core/provider-titles.js +10 -0
- package/dist/core/redaction/entropy.js +46 -0
- package/dist/core/redaction/redacted.js +4 -0
- package/dist/core/redaction/redactor.js +339 -0
- package/dist/core/redaction/secret-patterns.js +22 -0
- package/dist/core/redaction/value-shapes.js +81 -0
- package/dist/core/redaction/value-trace.js +110 -0
- package/dist/core/review.js +1 -0
- package/dist/core/session-catalogue.js +19 -0
- package/dist/core/session-filter.js +42 -0
- package/dist/core/session-format.js +27 -0
- package/dist/core/session-model.js +1 -0
- package/dist/core/session-reader.js +22 -0
- package/dist/core/session-records.js +1 -0
- package/dist/core/session-source.js +1 -0
- package/dist/core/session-titles.js +21 -0
- package/dist/core/turn.js +1 -0
- package/dist/doctor/codex-doctor.js +14 -0
- package/dist/doctor/doctor-use-case.js +1 -0
- package/dist/doctor/format-selecting-doctor.js +28 -0
- package/dist/doctor/render/attention/attention-rule.js +1 -0
- package/dist/doctor/render/attention/doctor-attention-rules.js +28 -0
- package/dist/doctor/render/attention/rules/damaged-meta-files-rule.js +8 -0
- package/dist/doctor/render/attention/rules/damaged-transcripts-rule.js +9 -0
- package/dist/doctor/render/attention/rules/incomplete-subagent-pairs-rule.js +6 -0
- package/dist/doctor/render/attention/rules/missing-main-transcript-rule.js +5 -0
- package/dist/doctor/render/attention/rules/missing-spilled-results-rule.js +6 -0
- package/dist/doctor/render/attention/rules/multiple-working-directories-rule.js +15 -0
- package/dist/doctor/render/attention/rules/unknown-denial-kinds-rule.js +5 -0
- package/dist/doctor/render/attention/rules/unknown-line-types-rule.js +6 -0
- package/dist/doctor/render/attention/rules/unrecognised-entries-rule.js +6 -0
- package/dist/doctor/render/attention/rules/unusable-directories-rule.js +12 -0
- package/dist/doctor/render/attention/transcript-scopes.js +6 -0
- package/dist/doctor/render/codex-text-doctor-renderer.js +97 -0
- package/dist/doctor/render/doctor-renderers.js +1 -0
- package/dist/doctor/render/json-doctor-renderer.js +7 -0
- package/dist/doctor/render/output-format.js +1 -0
- package/dist/doctor/render/text-doctor-renderer.js +124 -0
- package/dist/doctor/session-doctor.js +16 -0
- package/dist/infrastructure/clack-asker.js +24 -0
- package/dist/infrastructure/clack-chooser.js +40 -0
- package/dist/infrastructure/clack-multi-chooser.js +31 -0
- package/dist/infrastructure/file-alert-store.js +120 -0
- package/dist/infrastructure/file-checked-store.js +55 -0
- package/dist/infrastructure/file-mark-store.js +67 -0
- package/dist/infrastructure/file-onboarding-store.js +93 -0
- package/dist/infrastructure/folder-window.js +26 -0
- package/dist/infrastructure/node-agentwhy-invocation.js +99 -0
- package/dist/infrastructure/node-browser.js +40 -0
- package/dist/infrastructure/node-file-system.js +111 -0
- package/dist/infrastructure/node-local-server.js +115 -0
- package/dist/infrastructure/node-text-input.js +26 -0
- package/dist/infrastructure/os-notifier.js +21 -0
- package/dist/infrastructure/osascript-folder-chooser.js +53 -0
- package/dist/infrastructure/powershell-folder-chooser.js +78 -0
- package/dist/infrastructure/real-directory.js +18 -0
- package/dist/infrastructure/stream-printer.js +11 -0
- package/dist/infrastructure/terminal-banner.js +19 -0
- package/dist/ports/agentwhy-invocation.js +1 -0
- package/dist/ports/alert-store.js +1 -0
- package/dist/ports/asker.js +1 -0
- package/dist/ports/browser.js +1 -0
- package/dist/ports/checked-store.js +1 -0
- package/dist/ports/chooser.js +1 -0
- package/dist/ports/directory-reader.js +1 -0
- package/dist/ports/file-access-error.js +11 -0
- package/dist/ports/file-reader.js +1 -0
- package/dist/ports/file-tail-reader.js +1 -0
- package/dist/ports/file-writer.js +1 -0
- package/dist/ports/folder-chooser.js +1 -0
- package/dist/ports/local-server.js +1 -0
- package/dist/ports/mark-store.js +10 -0
- package/dist/ports/multi-chooser.js +1 -0
- package/dist/ports/notifier.js +1 -0
- package/dist/ports/onboarding-store.js +1 -0
- package/dist/ports/printer.js +1 -0
- package/dist/ports/text-input.js +1 -0
- package/dist/refuse/command-reach.js +135 -0
- package/dist/refuse/command-refusal.js +88 -0
- package/dist/refuse/render/codex-stop-words.js +21 -0
- package/dist/refuse/render/refusal-words.js +34 -0
- package/dist/report/build-report.js +1037 -0
- package/dist/report/check/actions-digest.js +73 -0
- package/dist/report/check/check-lines.js +83 -0
- package/dist/report/check/mark-request.js +90 -0
- package/dist/report/check/marks.js +84 -0
- package/dist/report/check/render/text-digest-renderer.js +332 -0
- package/dist/report/check/session-actions.js +90 -0
- package/dist/report/check/session-check.js +91 -0
- package/dist/report/choose-policy.js +61 -0
- package/dist/report/flow-reads.js +18 -0
- package/dist/report/private-files/tell-lists.js +84 -0
- package/dist/report/project-rules.js +33 -0
- package/dist/report/render/home-relative.js +18 -0
- package/dist/report/render/html-head.js +26 -0
- package/dist/report/render/html-report-components.js +48 -0
- package/dist/report/render/html-report-styles.js +500 -0
- package/dist/report/render/html-sidebar.js +42 -0
- package/dist/report/render/path-tail.js +14 -0
- package/dist/report/render/report-copy.js +1177 -0
- package/dist/report/render/report-page/advanced-view.js +167 -0
- package/dist/report/render/report-page/file-story.js +114 -0
- package/dist/report/render/report-page/files-view.js +381 -0
- package/dist/report/render/report-page/files.js +77 -0
- package/dist/report/render/report-page/fix-wizard-script.js +269 -0
- package/dist/report/render/report-page/fix-wizard.js +310 -0
- package/dist/report/render/report-page/helpers-view.js +383 -0
- package/dist/report/render/report-page/helpers.js +51 -0
- package/dist/report/render/report-page/item-names.js +63 -0
- package/dist/report/render/report-page/protect-patterns.js +13 -0
- package/dist/report/render/report-page/providers.js +208 -0
- package/dist/report/render/report-page/record-view.js +367 -0
- package/dist/report/render/report-page/report-page-renderer.js +129 -0
- package/dist/report/render/report-page/report-views.js +31 -0
- package/dist/report/render/report-page/story-window.js +356 -0
- package/dist/report/render/report-page/times.js +25 -0
- package/dist/report/render/report-page/to-do-view.js +299 -0
- package/dist/report/render/report-page/to-do.js +152 -0
- package/dist/report/render/report-page.js +1 -0
- package/dist/report/render/session-view.js +250 -0
- package/dist/report/render/text-report-renderer.js +981 -0
- package/dist/report/render/ui/add-file-popup.js +122 -0
- package/dist/report/render/ui/app-sidebar.js +78 -0
- package/dist/report/render/ui/ask-panel.js +80 -0
- package/dist/report/render/ui/avatar.js +19 -0
- package/dist/report/render/ui/backdrop.js +29 -0
- package/dist/report/render/ui/brand-mark.js +4 -0
- package/dist/report/render/ui/button.js +67 -0
- package/dist/report/render/ui/callout.js +20 -0
- package/dist/report/render/ui/chat-example.js +17 -0
- package/dist/report/render/ui/checklist.js +50 -0
- package/dist/report/render/ui/confirm-dialog.js +34 -0
- package/dist/report/render/ui/data-table.js +49 -0
- package/dist/report/render/ui/drawer.js +25 -0
- package/dist/report/render/ui/file-chip.js +37 -0
- package/dist/report/render/ui/fold-line.js +22 -0
- package/dist/report/render/ui/guide-card.js +25 -0
- package/dist/report/render/ui/hero.js +37 -0
- package/dist/report/render/ui/labelled-select.js +10 -0
- package/dist/report/render/ui/live-script.js +222 -0
- package/dist/report/render/ui/local-date.js +58 -0
- package/dist/report/render/ui/mode-icon.js +6 -0
- package/dist/report/render/ui/page-shell.js +101 -0
- package/dist/report/render/ui/pill-tabs.js +47 -0
- package/dist/report/render/ui/popup.js +77 -0
- package/dist/report/render/ui/progress.js +35 -0
- package/dist/report/render/ui/project-list.js +284 -0
- package/dist/report/render/ui/scope-choice.js +19 -0
- package/dist/report/render/ui/stats.js +21 -0
- package/dist/report/render/ui/status-icon.js +30 -0
- package/dist/report/render/ui/status-look.js +21 -0
- package/dist/report/render/ui/switch.js +11 -0
- package/dist/report/render/ui/tag.js +19 -0
- package/dist/report/render/ui/task-list.js +56 -0
- package/dist/report/render/ui/tokens.js +46 -0
- package/dist/report/render/ui/update-notice.js +146 -0
- package/dist/report/render/ui/words/app-words.js +163 -0
- package/dist/report/render/ui/words/conversations-words.js +457 -0
- package/dist/report/render/ui/words/month-words.js +59 -0
- package/dist/report/render/ui/words/onboarding-words.js +446 -0
- package/dist/report/render/ui/words/projects-words.js +217 -0
- package/dist/report/render/ui/words/report-words.js +2099 -0
- package/dist/report/render/ui/words/settings-words.js +611 -0
- package/dist/report/render/ui/words/to-fix-words.js +287 -0
- package/dist/report/report-model.js +2 -0
- package/dist/report/report-use-case.js +1 -0
- package/dist/report/rule-names.js +20 -0
- package/dist/report/session-report.js +163 -0
- package/dist/report/start/app-nav.js +37 -0
- package/dist/report/start/conversations/calendar-view.js +104 -0
- package/dist/report/start/conversations/conversation-columns.js +187 -0
- package/dist/report/start/conversations/conversations-renderer.js +54 -0
- package/dist/report/start/conversations/empty-week.js +120 -0
- package/dist/report/start/conversations/files-window.js +166 -0
- package/dist/report/start/conversations/period-section.js +280 -0
- package/dist/report/start/conversations/periods-script.js +440 -0
- package/dist/report/start/conversations/periods.js +110 -0
- package/dist/report/start/conversations/week-view.js +30 -0
- package/dist/report/start/conversations/weeks.js +17 -0
- package/dist/report/start/month/month-renderer.js +50 -0
- package/dist/report/start/month/month-view.js +135 -0
- package/dist/report/start/month/months.js +17 -0
- package/dist/report/start/onboarding/done.js +171 -0
- package/dist/report/start/onboarding/finish-onboarding.js +93 -0
- package/dist/report/start/onboarding/intro.js +100 -0
- package/dist/report/start/onboarding/onboarding-changes.js +40 -0
- package/dist/report/start/onboarding/onboarding-renderer.js +195 -0
- package/dist/report/start/onboarding/onboarding-script.js +400 -0
- package/dist/report/start/onboarding/onboarding-view.js +98 -0
- package/dist/report/start/onboarding/project-step.js +210 -0
- package/dist/report/start/onboarding/steps.js +239 -0
- package/dist/report/start/onboarding/welcome.js +66 -0
- package/dist/report/start/page-notice.js +10 -0
- package/dist/report/start/projects/index-projects.js +43 -0
- package/dist/report/start/projects/moved-page.js +82 -0
- package/dist/report/start/projects/project-switch.js +138 -0
- package/dist/report/start/projects/projects-window.js +67 -0
- package/dist/report/start/projects/temporary-space.js +18 -0
- package/dist/report/start/render/air-datepicker-vendor.js +27 -0
- package/dist/report/start/render/session-status.js +41 -0
- package/dist/report/start/render/start-words.js +84 -0
- package/dist/report/start/repository.js +28 -0
- package/dist/report/start/serve/index-handler.js +372 -0
- package/dist/report/start/serve/onboarding-request.js +50 -0
- package/dist/report/start/serve/settings-request.js +1 -0
- package/dist/report/start/serve/settings-setup.js +193 -0
- package/dist/report/start/session-index.js +1 -0
- package/dist/report/start/session-start.js +974 -0
- package/dist/report/start/settings/alerts-tab.js +104 -0
- package/dist/report/start/settings/files-tab.js +169 -0
- package/dist/report/start/settings/general-tab.js +71 -0
- package/dist/report/start/settings/settings-renderer.js +254 -0
- package/dist/report/start/settings/settings-script.js +161 -0
- package/dist/report/start/settings/settings-view.js +240 -0
- package/dist/report/start/settings/settings-windows.js +334 -0
- package/dist/report/start/settings-files.js +43 -0
- package/dist/report/start/to-fix/done-list.js +45 -0
- package/dist/report/start/to-fix/file-window.js +110 -0
- package/dist/report/start/to-fix/to-fix-list.js +70 -0
- package/dist/report/start/to-fix/to-fix-renderer.js +237 -0
- package/dist/report/start/to-fix/to-fix-script.js +94 -0
- package/dist/report/start/to-fix/to-fix-view.js +124 -0
- package/dist/report/watch/agent-alert.js +72 -0
- package/dist/report/watch/notice-choices.js +35 -0
- package/dist/report/watch/notice-settings.js +65 -0
- package/dist/report/watch/preferences.js +100 -0
- package/dist/report/watch/render/instruction-words.js +54 -0
- package/dist/report/watch/render/notice-words.js +182 -0
- package/dist/report/watch/subagent-watch.js +345 -0
- package/dist/report/watch/watch-notice.js +1 -0
- package/dist/setup/behind.js +18 -0
- package/dist/setup/codex-mirror.js +171 -0
- package/dist/setup/not-a-project.js +19 -0
- package/dist/setup/project-setup.js +644 -0
- package/dist/setup/protected-patterns.js +30 -0
- package/dist/shared/colour.js +23 -0
- package/dist/shared/compare.js +4 -0
- package/dist/shared/content-version.js +13 -0
- package/dist/shared/counter.js +11 -0
- package/dist/shared/file-stamp.js +4 -0
- package/dist/shared/json.js +13 -0
- package/dist/shared/label.js +13 -0
- package/dist/shared/package-name.js +6 -0
- package/dist/shared/plain-invocation.js +44 -0
- package/dist/shared/plain-version.js +21 -0
- package/dist/shared/printable.js +25 -0
- package/dist/shared/renderer.js +1 -0
- package/dist/shared/sentence.js +20 -0
- package/dist/shared/terminal-logo.js +41 -0
- package/dist/shared/text-table.js +15 -0
- package/dist/shared/wrap.js +29 -0
- package/package.json +70 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The exclusions of spec §5.4 — "the real work here". Without them class B is a noise generator, because a
|
|
3
|
+
* sensitive name sits beside a harmless value far more often than beside a secret: `SECRET_ENABLED=true`,
|
|
4
|
+
* `TOKEN_EXPIRY=3600`, `API_KEY_HEADER=X-Api-Key`.
|
|
5
|
+
*
|
|
6
|
+
* They run **before** name matching (resolution order, step 3 before step 4). The reverse order redacts
|
|
7
|
+
* `SECRET_ENABLED=true`, which is the mistake the order exists to prevent.
|
|
8
|
+
*/
|
|
9
|
+
/** A name that describes a secret, or names something public, rather than holding one. */
|
|
10
|
+
const NAME_EXCLUSIONS = [
|
|
11
|
+
// `NEXT_PUBLIC_SITE_URL` was the prompt of test B2; these are published in client-side code by design.
|
|
12
|
+
/PUBLIC/i,
|
|
13
|
+
/\b(PRIMARY|FOREIGN|SORT|PARTITION|CACHE|IDEMPOTENCY)_KEY\b/i,
|
|
14
|
+
/KEYWORD|KEYCODE|KEYBOARD/i,
|
|
15
|
+
/_(ENABLED|EXPIRY|TTL|LENGTH|ROUNDS|ALGO|HEADER|NAME|PATH|FILE|PROVIDER|STRATEGY)$/i,
|
|
16
|
+
];
|
|
17
|
+
const SENSITIVE_NAME = /(SECRET|TOKEN|KEY|PASSWORD|PASSWD|PWD|CREDENTIAL|PRIVATE|AUTH|APIKEY|SALT|SIGNING|BEARER|SESSION|COOKIE|DSN|CONNECTION_STRING)/i;
|
|
18
|
+
/** A value of one of these shapes is not a secret, whatever the name beside it says. */
|
|
19
|
+
const VALUE_EXCLUSIONS = [
|
|
20
|
+
/^(true|false|yes|no|on|off|enabled|disabled|null|none|undefined)$/i,
|
|
21
|
+
/^-?\d+(\.\d+)?([a-z%]{1,3})?$/i,
|
|
22
|
+
/^\d+(ms|s|m|h|d|w)(\d+(ms|s|m|h|d|w))*$/i,
|
|
23
|
+
/^(development|production|staging|test|local|debug|info|warn|error|postgres|postgresql|mysql|redis|sqlite|utf8|utf-8)$/i,
|
|
24
|
+
/^(v?\d+(\.\d+)*(-[\w.]+)?|latest|next|\^[\w.]+|~[\w.]+)$/i,
|
|
25
|
+
/^[\w.-]+:[\w.-]+$/,
|
|
26
|
+
/^([~.]{0,2}\/|[A-Za-z]:\\)/,
|
|
27
|
+
/^[\w.-]+\.(ts|js|json|ya?ml|md|txt|log|sql|sh)$/i,
|
|
28
|
+
/^(https?|postgres(ql)?|mysql|redis|mongodb|amqp):\/\//i,
|
|
29
|
+
/^[\w.+-]+@[\w.-]+\.\w+$/,
|
|
30
|
+
/^(#[0-9a-f]{3,8}|rgba?\([\d,.\s]+\))$/i,
|
|
31
|
+
/^[a-z]{2}([_-][A-Za-z]{2,4})?$/,
|
|
32
|
+
/^[\w.-]+\/[\w.+-]+$/,
|
|
33
|
+
/^(changeme|your-[\w-]+|replace_?me|todo|example|dummy|test|xxx+|<.*>|\.{3}|)$/i,
|
|
34
|
+
/^(.)\1*$/,
|
|
35
|
+
];
|
|
36
|
+
/**
|
|
37
|
+
* A name as configuration writes them: `WEBHOOK_SECRET`, `aws_access_key_id`, `api-key`. Code writes `sessionId`,
|
|
38
|
+
* `key` and `tokens`, and on one real session those three shapes alone accounted for 62 of 139 class B matches -
|
|
39
|
+
* names from a repository's own vocabulary, standing beside values that are not secrets.
|
|
40
|
+
*
|
|
41
|
+
* This narrows **reporting only** (spec §5.4, two thresholds): redaction keeps the wider rule, because replacing
|
|
42
|
+
* something that was not a secret costs nothing and missing one costs everything.
|
|
43
|
+
*/
|
|
44
|
+
export function isConfigName(name) {
|
|
45
|
+
return /^[A-Z][A-Z0-9_]*$/.test(name) || /[_-]/.test(name);
|
|
46
|
+
}
|
|
47
|
+
export function isSensitiveName(name) {
|
|
48
|
+
return SENSITIVE_NAME.test(name) && !NAME_EXCLUSIONS.some((pattern) => pattern.test(name));
|
|
49
|
+
}
|
|
50
|
+
/** Prose is many words with spaces: a sentence beside a sensitive name is a description, not a credential. */
|
|
51
|
+
export function isExcludedValue(value) {
|
|
52
|
+
const trimmed = unquote(value.trim());
|
|
53
|
+
if (trimmed.length < 8)
|
|
54
|
+
return true;
|
|
55
|
+
if (trimmed.split(/\s+/).length > 2)
|
|
56
|
+
return true;
|
|
57
|
+
if (VALUE_EXCLUSIONS.some((pattern) => pattern.test(trimmed)))
|
|
58
|
+
return true;
|
|
59
|
+
// A list is excluded when every item of it is.
|
|
60
|
+
const items = trimmed.split(',');
|
|
61
|
+
return items.length > 1 && items.every((item) => isExcludedValue(item.trim()) || item.trim().length < 8);
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The shape exclusions without the length floor. `specs/2026-09-15-what-came-back.md` §5.2 proposes these to decide which values
|
|
65
|
+
* read from a protected file are too ordinary to trace, and leaves the floor out on purpose: §2 of that specification
|
|
66
|
+
* shows a real secret may be shorter than 8 characters. Redaction does not use this - rule 1 redacts every
|
|
67
|
+
* value of a protected resource, whatever its shape.
|
|
68
|
+
*/
|
|
69
|
+
export function isOrdinaryShape(value) {
|
|
70
|
+
const trimmed = unquote(value.trim());
|
|
71
|
+
if (trimmed.split(/\s+/).length > 2)
|
|
72
|
+
return true;
|
|
73
|
+
if (VALUE_EXCLUSIONS.some((pattern) => pattern.test(trimmed)))
|
|
74
|
+
return true;
|
|
75
|
+
const items = trimmed.split(',');
|
|
76
|
+
return items.length > 1 && items.every((item) => isOrdinaryShape(item.trim()));
|
|
77
|
+
}
|
|
78
|
+
export function unquote(value) {
|
|
79
|
+
const quoted = /^(["'])(.*)\1$/.exec(value);
|
|
80
|
+
return quoted?.[2] ?? value;
|
|
81
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Values read from protected resources, held as salted digests of their runs and asked about other text
|
|
3
|
+
* (`specs/2026-09-15-what-came-back.md` R3, R4). No value is kept and no run of one (§5.4 rule 6): what is kept is the digest
|
|
4
|
+
* of each run, and the position of the value it came from in the list the trace was built from.
|
|
5
|
+
*
|
|
6
|
+
* **Thresholds** - spec §5.1's proposal, until the measurement closes it: a run of `RUN_ANYWHERE` characters
|
|
7
|
+
* anywhere in a value, or of `RUN_AT_EDGE` characters at its start or its end, because a prefix and a suffix were
|
|
8
|
+
* what leaked. A value shorter than `RUN_AT_EDGE` can therefore never be found.
|
|
9
|
+
*/
|
|
10
|
+
export const RUN_ANYWHERE = 12;
|
|
11
|
+
export const RUN_AT_EDGE = 6;
|
|
12
|
+
/**
|
|
13
|
+
* The multiplier of the cheap fingerprint below. Any odd number works; 33 is the one Bernstein's string hash uses,
|
|
14
|
+
* and it is picked for nothing more than being widely read.
|
|
15
|
+
*/
|
|
16
|
+
const FINGERPRINT_BASE = 33;
|
|
17
|
+
/**
|
|
18
|
+
* A cheap, unsalted fingerprint of a run, used only to decide whether the salted digest is worth computing.
|
|
19
|
+
* `Math.imul` keeps the arithmetic to 32 bits, which is what makes it rollable in constant time per position.
|
|
20
|
+
*/
|
|
21
|
+
function fingerprintOf(run) {
|
|
22
|
+
let hash = 0;
|
|
23
|
+
for (let index = 0; index < run.length; index += 1)
|
|
24
|
+
hash = (Math.imul(hash, FINGERPRINT_BASE) + run.charCodeAt(index)) | 0;
|
|
25
|
+
return hash;
|
|
26
|
+
}
|
|
27
|
+
export class ValueTrace {
|
|
28
|
+
/**
|
|
29
|
+
* Digest of a run -> positions of the values it came from. Public on purpose: everything a trace holds can be
|
|
30
|
+
* inspected, and a test holds it to rule 6 by inspecting it. A private field would hide from that test too.
|
|
31
|
+
*/
|
|
32
|
+
anywhere;
|
|
33
|
+
/** The same, for the run at a value's start and the run at its end. */
|
|
34
|
+
edges;
|
|
35
|
+
/**
|
|
36
|
+
* Fingerprints of the very same runs, for the prefilter in `#scan`. Public for the same reason the maps are: a
|
|
37
|
+
* number is not a run of a value, and rule 6 is checked by inspecting everything a trace holds.
|
|
38
|
+
*/
|
|
39
|
+
anywhereFingerprints;
|
|
40
|
+
edgeFingerprints;
|
|
41
|
+
#digest;
|
|
42
|
+
/** `digest` must be salted per run; the redactor is the one place that has the salt, and builds traces. */
|
|
43
|
+
constructor(values, digest) {
|
|
44
|
+
this.#digest = digest;
|
|
45
|
+
const anywhere = new Map();
|
|
46
|
+
const edges = new Map();
|
|
47
|
+
const anywhereFingerprints = new Set();
|
|
48
|
+
const edgeFingerprints = new Set();
|
|
49
|
+
values.forEach((value, index) => {
|
|
50
|
+
for (let start = 0; start + RUN_ANYWHERE <= value.length; start += 1) {
|
|
51
|
+
const run = value.slice(start, start + RUN_ANYWHERE);
|
|
52
|
+
remember(anywhere, digest(run), index);
|
|
53
|
+
anywhereFingerprints.add(fingerprintOf(run));
|
|
54
|
+
}
|
|
55
|
+
if (value.length >= RUN_AT_EDGE) {
|
|
56
|
+
for (const run of [value.slice(0, RUN_AT_EDGE), value.slice(-RUN_AT_EDGE)]) {
|
|
57
|
+
remember(edges, digest(run), index);
|
|
58
|
+
edgeFingerprints.add(fingerprintOf(run));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
this.anywhere = anywhere;
|
|
63
|
+
this.edges = edges;
|
|
64
|
+
this.anywhereFingerprints = anywhereFingerprints;
|
|
65
|
+
this.edgeFingerprints = edgeFingerprints;
|
|
66
|
+
}
|
|
67
|
+
/** Which traced values have a run in `text`, as positions in the list the trace was built from. Never where. */
|
|
68
|
+
foundIn(text) {
|
|
69
|
+
const found = new Set();
|
|
70
|
+
this.#scan(text, RUN_ANYWHERE, this.anywhere, this.anywhereFingerprints, found);
|
|
71
|
+
this.#scan(text, RUN_AT_EDGE, this.edges, this.edgeFingerprints, found);
|
|
72
|
+
return found;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Every window of `text`, asked about. The salted digest is the answer, but computing one per position meant a
|
|
76
|
+
* cryptographic hash per character of every message and result a session holds: measured at 3.6 seconds of one
|
|
77
|
+
* run on a record-heavy transcript, and it grows with the session. So each window is first fingerprinted by a
|
|
78
|
+
* hash that rolls in constant time, and only a window whose fingerprint belongs to a traced run is digested.
|
|
79
|
+
*
|
|
80
|
+
* The prefilter can only add work, never remove an answer: equal strings always fingerprint alike, so every run
|
|
81
|
+
* that was there is still digested and still found. A collision costs one digest and is then refused by the map.
|
|
82
|
+
*/
|
|
83
|
+
#scan(text, length, runs, fingerprints, found) {
|
|
84
|
+
if (runs.size === 0 || text.length < length)
|
|
85
|
+
return;
|
|
86
|
+
// What the leaving character contributes, so it can be taken back out: the base to the power of the window.
|
|
87
|
+
let leaving = 1;
|
|
88
|
+
for (let step = 1; step < length; step += 1)
|
|
89
|
+
leaving = Math.imul(leaving, FINGERPRINT_BASE) | 0;
|
|
90
|
+
let fingerprint = fingerprintOf(text.slice(0, length));
|
|
91
|
+
for (let start = 0; start + length <= text.length; start += 1) {
|
|
92
|
+
if (fingerprints.has(fingerprint)) {
|
|
93
|
+
for (const index of runs.get(this.#digest(text.slice(start, start + length))) ?? [])
|
|
94
|
+
found.add(index);
|
|
95
|
+
}
|
|
96
|
+
const next = start + length;
|
|
97
|
+
if (next >= text.length)
|
|
98
|
+
return;
|
|
99
|
+
const without = (fingerprint - Math.imul(text.charCodeAt(start), leaving)) | 0;
|
|
100
|
+
fingerprint = (Math.imul(without, FINGERPRINT_BASE) + text.charCodeAt(next)) | 0;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
function remember(runs, digest, index) {
|
|
105
|
+
const known = runs.get(digest);
|
|
106
|
+
if (known === undefined)
|
|
107
|
+
runs.set(digest, [index]);
|
|
108
|
+
else if (!known.includes(index))
|
|
109
|
+
known.push(index);
|
|
110
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What names a session wherever two AIs' sessions meet - a row, a file, a cached report, `report --input`: its id where
|
|
3
|
+
* Claude Code wrote it, as it always was, and the id after its AI's name otherwise, so equal ids never overwrite each
|
|
4
|
+
* other (`.ai/plans/2026-09-29-what-codex-wrote.md` step 5). A Claude Code id is a UUID and never starts with an AI's name.
|
|
5
|
+
*/
|
|
6
|
+
export function sessionKey(session) {
|
|
7
|
+
return session.provider === 'claude-code' ? session.id : `${session.provider}-${session.id}`;
|
|
8
|
+
}
|
|
9
|
+
/** Every AI whose name `sessionKey` puts before an id: a record, so that an AI added to `Provider` must be named here. */
|
|
10
|
+
const NAMED_IN_KEY = { codex: true };
|
|
11
|
+
/**
|
|
12
|
+
* The id a key was made from. A short form is cut from it, not from the key: the key of every Codex session starts with
|
|
13
|
+
* the same `codex-`, and so, for minutes at a time, does a UUIDv7 - its first characters are a timestamp. Anything that
|
|
14
|
+
* is no key, like a `--share` position, is returned as it is.
|
|
15
|
+
*/
|
|
16
|
+
export function idInKey(key) {
|
|
17
|
+
const named = Object.keys(NAMED_IN_KEY).find((provider) => key.startsWith(`${provider}-`));
|
|
18
|
+
return named === undefined ? key : key.slice(named.length + 1);
|
|
19
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const HOUR = 3_600_000;
|
|
2
|
+
const DAY = 24 * HOUR;
|
|
3
|
+
const UNIT = { h: HOUR, d: DAY, w: 7 * DAY };
|
|
4
|
+
/**
|
|
5
|
+
* `7d`, `12h`, `2w`, or a calendar date. **Now is given, never read**: a filter that looks at the clock for itself
|
|
6
|
+
* gives a different answer to the same command a minute later, and cannot be tested at all.
|
|
7
|
+
*
|
|
8
|
+
* A calendar date is midnight UTC. A local midnight would move with the machine the command runs on, and a
|
|
9
|
+
* range is only worth stating if it means the same thing to whoever reads the page.
|
|
10
|
+
*/
|
|
11
|
+
export function parseSince(text, now) {
|
|
12
|
+
const relative = /^(\d+)([hdw])$/.exec(text);
|
|
13
|
+
if (relative !== null) {
|
|
14
|
+
const amount = Number(relative[1]);
|
|
15
|
+
const unit = UNIT[relative[2] ?? ''] ?? DAY;
|
|
16
|
+
if (amount <= 0)
|
|
17
|
+
return { error: `--since must be more than nothing: ${text}` };
|
|
18
|
+
return { since: now - amount * unit, asked: text };
|
|
19
|
+
}
|
|
20
|
+
if (/^\d{4}-\d{2}-\d{2}$/.test(text)) {
|
|
21
|
+
const since = Date.parse(`${text}T00:00:00Z`);
|
|
22
|
+
// The parser rolls an overflowing day into the next month - 2026-02-30 is read as 2 March - so a date is
|
|
23
|
+
// accepted only if it names itself back. Otherwise the sessions the person meant to include fall out of the
|
|
24
|
+
// range without a word. Found by a review; the earlier test tried month 13, which the parser already refused.
|
|
25
|
+
if (Number.isNaN(since) || new Date(since).toISOString().slice(0, 10) !== text) {
|
|
26
|
+
return { error: `--since is not a calendar date: ${text}` };
|
|
27
|
+
}
|
|
28
|
+
return { since, asked: text };
|
|
29
|
+
}
|
|
30
|
+
return { error: `--since takes a span such as 7d, 12h or 2w, or a date such as 2026-09-01: ${text}` };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Sessions on either side of a start. What is compared is when a session was **last written**, so this means
|
|
34
|
+
* "active since" and not "started since": a session resumed a week later belongs to this week (L008). The page
|
|
35
|
+
* says so in those words, until a start time is in the format contract.
|
|
36
|
+
*/
|
|
37
|
+
export function splitBySince(items, since) {
|
|
38
|
+
return {
|
|
39
|
+
inRange: items.filter((item) => item.modifiedAt >= since),
|
|
40
|
+
outOfRange: items.filter((item) => item.modifiedAt < since),
|
|
41
|
+
};
|
|
42
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Each AI by its product's name, which is the same in every language a page is written in. */
|
|
2
|
+
export const PROVIDER_NAMES = { 'claude-code': 'Claude Code', codex: 'Codex' };
|
|
3
|
+
/**
|
|
4
|
+
* The one registry that tells formats apart (XD7), shared by every command that reads a session. Formats are tested in
|
|
5
|
+
* the order given - the stricter rule first - and the first that recognises the input has it. What each command does
|
|
6
|
+
* with the provider is its own keyed choice, never a fallback: no format's reader runs on an input it did not recognise.
|
|
7
|
+
*/
|
|
8
|
+
export class SessionFormats {
|
|
9
|
+
#formats;
|
|
10
|
+
constructor(formats) {
|
|
11
|
+
const providers = formats.map((format) => format.provider);
|
|
12
|
+
if (new Set(providers).size !== providers.length)
|
|
13
|
+
throw new Error('A provider is registered more than once.');
|
|
14
|
+
this.#formats = formats;
|
|
15
|
+
}
|
|
16
|
+
async select(input) {
|
|
17
|
+
let unavailable = false;
|
|
18
|
+
for (const format of this.#formats) {
|
|
19
|
+
const recognition = await format.recognise(input);
|
|
20
|
+
if (recognition === 'recognised')
|
|
21
|
+
return { kind: 'recognised', provider: format.provider };
|
|
22
|
+
if (recognition === 'unavailable')
|
|
23
|
+
unavailable = true;
|
|
24
|
+
}
|
|
25
|
+
return unavailable ? { kind: 'unavailable' } : { kind: 'unknown' };
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reads an input with the format its content names (`2026-09-27-what-codex-wrote.md` X2, XD7), through the registry
|
|
3
|
+
* `doctor` uses. An input every format read and none recognised is read by none: it is never taken for the nearer one.
|
|
4
|
+
*/
|
|
5
|
+
export class FormatSelectingReader {
|
|
6
|
+
#dependencies;
|
|
7
|
+
constructor(dependencies) {
|
|
8
|
+
this.#dependencies = dependencies;
|
|
9
|
+
}
|
|
10
|
+
async read(input) {
|
|
11
|
+
const { formats, sources, whenUnavailable } = this.#dependencies;
|
|
12
|
+
const selection = await formats.select(input);
|
|
13
|
+
if (selection.kind === 'unknown')
|
|
14
|
+
return { kind: 'unknown-format' };
|
|
15
|
+
const provider = selection.kind === 'recognised' ? selection.provider : whenUnavailable;
|
|
16
|
+
return { kind: 'read', model: await sources[provider].read(input) };
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
/** A reader of one format alone, for a caller only that format concerns. */
|
|
20
|
+
export function readerOf(source) {
|
|
21
|
+
return { read: async (input) => ({ kind: 'read', model: await source.read(input) }) };
|
|
22
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* How many transcripts are read for a title at once. Each read holds up to a megabyte of a transcript, and a
|
|
3
|
+
* descriptor for it, until it answers; a project with hundreds of sessions asked for all of them at once would
|
|
4
|
+
* hold all of that at the same moment, for a list that is one line per session.
|
|
5
|
+
*/
|
|
6
|
+
const TITLES_AT_ONCE = 8;
|
|
7
|
+
/**
|
|
8
|
+
* What every session is recognised by, in the order given, read a few at a time. A row whose title could not be read
|
|
9
|
+
* is a row a person reads one line slower, never a list that failed: `recognise` answers nothing only for the failures
|
|
10
|
+
* the file system port translates, and a descriptor limit reached while reading a long list, or a read that fails part
|
|
11
|
+
* way, would otherwise unwind the whole list. Found by a review: `start` read titles this way and `sessions` asked for
|
|
12
|
+
* every one at once, with nothing caught.
|
|
13
|
+
*/
|
|
14
|
+
export async function recogniseAll(titles, sessions) {
|
|
15
|
+
const recognised = [];
|
|
16
|
+
for (let from = 0; from < sessions.length; from += TITLES_AT_ONCE) {
|
|
17
|
+
const batch = sessions.slice(from, from + TITLES_AT_ONCE);
|
|
18
|
+
recognised.push(...(await Promise.all(batch.map((session) => titles.recognise(session).catch(() => ({}))))));
|
|
19
|
+
}
|
|
20
|
+
return recognised;
|
|
21
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/** `doctor` on Codex rollouts (X26): measures, renders, and says when nothing it was given was a Codex session. */
|
|
2
|
+
export class CodexDoctor {
|
|
3
|
+
#dependencies;
|
|
4
|
+
constructor(dependencies) {
|
|
5
|
+
this.#dependencies = dependencies;
|
|
6
|
+
}
|
|
7
|
+
async run(options) {
|
|
8
|
+
const report = await this.#dependencies.probe.probe(options.input);
|
|
9
|
+
return {
|
|
10
|
+
outcome: report.files.recognised > 0 ? 'complete' : 'unknown-format',
|
|
11
|
+
output: this.#dependencies.renderers[options.format].render(report),
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What `doctor` says of an input no format recognised, in each output format. The JSON is a closed schema of its own: it
|
|
3
|
+
* names no provider it guessed and nothing the input held.
|
|
4
|
+
*/
|
|
5
|
+
const UNKNOWN_FORMAT = {
|
|
6
|
+
text: 'agentwhy doctor: the input is neither a Claude Code session nor a Codex session, so it was not read.\n',
|
|
7
|
+
json: `${JSON.stringify({ schemaVersion: 1, provider: 'unknown' }, null, 2)}\n`,
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* `doctor` on whichever format the input is, told by content through the shared registry (`2026-09-27-what-codex-wrote.md`
|
|
11
|
+
* X2, XD7). This lifts the coupling of `doctor` to the Claude Code adapter (main spec §13.6). An input every format read
|
|
12
|
+
* and none recognised is read by none.
|
|
13
|
+
*/
|
|
14
|
+
export class FormatSelectingDoctor {
|
|
15
|
+
#dependencies;
|
|
16
|
+
constructor(dependencies) {
|
|
17
|
+
this.#dependencies = dependencies;
|
|
18
|
+
}
|
|
19
|
+
async run(options) {
|
|
20
|
+
const { formats, doctors, whenUnavailable } = this.#dependencies;
|
|
21
|
+
const selection = await formats.select(options.input);
|
|
22
|
+
if (selection.kind === 'recognised')
|
|
23
|
+
return doctors[selection.provider].run(options);
|
|
24
|
+
if (selection.kind === 'unavailable')
|
|
25
|
+
return doctors[whenUnavailable].run(options);
|
|
26
|
+
return { outcome: 'unknown-format', output: UNKNOWN_FORMAT[options.format] };
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { DamagedMetaFilesRule } from './rules/damaged-meta-files-rule.js';
|
|
2
|
+
import { DamagedTranscriptsRule } from './rules/damaged-transcripts-rule.js';
|
|
3
|
+
import { IncompleteSubagentPairsRule } from './rules/incomplete-subagent-pairs-rule.js';
|
|
4
|
+
import { MissingMainTranscriptRule } from './rules/missing-main-transcript-rule.js';
|
|
5
|
+
import { MissingSpilledResultsRule } from './rules/missing-spilled-results-rule.js';
|
|
6
|
+
import { MultipleWorkingDirectoriesRule } from './rules/multiple-working-directories-rule.js';
|
|
7
|
+
import { UnknownDenialKindsRule } from './rules/unknown-denial-kinds-rule.js';
|
|
8
|
+
import { UnknownLineTypesRule } from './rules/unknown-line-types-rule.js';
|
|
9
|
+
import { UnrecognisedEntriesRule } from './rules/unrecognised-entries-rule.js';
|
|
10
|
+
import { UnusableDirectoriesRule } from './rules/unusable-directories-rule.js';
|
|
11
|
+
/**
|
|
12
|
+
* The rules behind "Needs attention", in reading order. Unknown variants lead: they are what `doctor` exists to
|
|
13
|
+
* surface after a Claude Code update. A new finding is a new rule added here; no existing rule changes.
|
|
14
|
+
*/
|
|
15
|
+
export function createDoctorAttentionRules() {
|
|
16
|
+
return [
|
|
17
|
+
new UnknownLineTypesRule(),
|
|
18
|
+
new UnknownDenialKindsRule(),
|
|
19
|
+
new MultipleWorkingDirectoriesRule(),
|
|
20
|
+
new MissingMainTranscriptRule(),
|
|
21
|
+
new UnusableDirectoriesRule(),
|
|
22
|
+
new DamagedTranscriptsRule(),
|
|
23
|
+
new IncompleteSubagentPairsRule(),
|
|
24
|
+
new DamagedMetaFilesRule(),
|
|
25
|
+
new MissingSpilledResultsRule(),
|
|
26
|
+
new UnrecognisedEntriesRule(),
|
|
27
|
+
];
|
|
28
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export class DamagedMetaFilesRule {
|
|
2
|
+
findings({ meta }) {
|
|
3
|
+
return [
|
|
4
|
+
...(meta.unparsableFiles > 0 ? [`unparsable meta.json files: ${meta.unparsableFiles}`] : []),
|
|
5
|
+
...(meta.unreadableFiles > 0 ? [`unreadable meta.json files: ${meta.unreadableFiles}`] : []),
|
|
6
|
+
];
|
|
7
|
+
}
|
|
8
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { transcriptScopes } from '../transcript-scopes.js';
|
|
2
|
+
export class DamagedTranscriptsRule {
|
|
3
|
+
findings(report) {
|
|
4
|
+
return transcriptScopes(report).flatMap(([scope, stats]) => [
|
|
5
|
+
...(stats.unparsableLines > 0 ? [`unparsable lines in ${scope}: ${stats.unparsableLines}`] : []),
|
|
6
|
+
...(stats.unreadableFiles > 0 ? [`unreadable transcript files in ${scope}: ${stats.unreadableFiles}`] : []),
|
|
7
|
+
]);
|
|
8
|
+
}
|
|
9
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A session is expected to carry one working directory. Two would mean the project root cannot be read from the
|
|
3
|
+
* transcript, and every path a report makes relative to it would be relative to a guess.
|
|
4
|
+
*/
|
|
5
|
+
export class MultipleWorkingDirectoriesRule {
|
|
6
|
+
findings(report) {
|
|
7
|
+
const { distinct, invalid } = report.workingDirectories;
|
|
8
|
+
const findings = [];
|
|
9
|
+
if (distinct > 1)
|
|
10
|
+
findings.push(`UNKNOWN variant: ${distinct} working directories in one session`);
|
|
11
|
+
if (invalid > 0)
|
|
12
|
+
findings.push(`INVALID cwd on ${invalid} lines: not a string`);
|
|
13
|
+
return findings;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { transcriptScopes } from '../transcript-scopes.js';
|
|
2
|
+
export class UnknownLineTypesRule {
|
|
3
|
+
findings(report) {
|
|
4
|
+
return transcriptScopes(report).flatMap(([scope, stats]) => stats.unknownLineTypes.map((type) => `UNKNOWN line type in ${scope}: ${type} (${stats.lineTypes[type] ?? 0})`));
|
|
5
|
+
}
|
|
6
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export class UnusableDirectoriesRule {
|
|
2
|
+
findings({ sources }) {
|
|
3
|
+
const directories = [
|
|
4
|
+
['subagents directory', sources.subagentsDirectory],
|
|
5
|
+
['tool-results directory', sources.toolResultsDirectory],
|
|
6
|
+
];
|
|
7
|
+
// `not-found` is normal: a session without subagents or spilled results has no such directory.
|
|
8
|
+
return directories
|
|
9
|
+
.filter(([, state]) => state === 'unreadable' || state === 'wrong-kind')
|
|
10
|
+
.map(([name, state]) => `${name}: ${state}`);
|
|
11
|
+
}
|
|
12
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { countsTable, table } from '../../shared/text-table.js';
|
|
2
|
+
const SECTION_INDENT = 2;
|
|
3
|
+
/**
|
|
4
|
+
* The Codex section of `doctor` as text (`2026-09-27-what-codex-wrote.md` X26): what the files are, who wrote them, how
|
|
5
|
+
* they join and what they can answer. Every label comes from the closed report, so nothing a record said is printed.
|
|
6
|
+
*/
|
|
7
|
+
export class CodexTextDoctorRenderer {
|
|
8
|
+
render(report) {
|
|
9
|
+
const { files, identity, tree, workingDirectories, order } = report;
|
|
10
|
+
return [
|
|
11
|
+
'agentwhy doctor · Codex',
|
|
12
|
+
`format contract v${report.contractVersion}, verified against Codex ${report.verifiedAgainst.join(', ')}`,
|
|
13
|
+
'',
|
|
14
|
+
'Needs attention',
|
|
15
|
+
...attention(report).map((item) => `${' '.repeat(SECTION_INDENT)}${item}`),
|
|
16
|
+
'',
|
|
17
|
+
'Files',
|
|
18
|
+
...table([
|
|
19
|
+
['found', String(files.found)],
|
|
20
|
+
['recognised as Codex sessions', String(files.recognised)],
|
|
21
|
+
['not a Codex session', String(files.unknownFormat)],
|
|
22
|
+
['could not be read', String(files.unreadable)],
|
|
23
|
+
['lines', `${report.lines.total} (${report.lines.unparsable} unparsable)`],
|
|
24
|
+
], SECTION_INDENT),
|
|
25
|
+
'',
|
|
26
|
+
'Codex versions (first session_meta)',
|
|
27
|
+
...countsTable(report.versions, SECTION_INDENT),
|
|
28
|
+
'',
|
|
29
|
+
'History modes',
|
|
30
|
+
...countsTable(report.historyModes, SECTION_INDENT),
|
|
31
|
+
'',
|
|
32
|
+
'Who started each thread',
|
|
33
|
+
...countsTable(report.origins, SECTION_INDENT),
|
|
34
|
+
'',
|
|
35
|
+
'Identity and tree',
|
|
36
|
+
...table([
|
|
37
|
+
['ids one file holds', String(identity.uniqueIds)],
|
|
38
|
+
['ids several files hold', `${identity.sharedIds} (${identity.filesSharingIds} files, joined to nothing)`],
|
|
39
|
+
['file name names another id', String(identity.fileNameDisagrees)],
|
|
40
|
+
['later session_meta lines', String(identity.laterMetadata)],
|
|
41
|
+
['conversations', String(tree.roots)],
|
|
42
|
+
['threads within them', String(tree.descendants)],
|
|
43
|
+
], SECTION_INDENT),
|
|
44
|
+
' unresolved relations',
|
|
45
|
+
...countsTable(tree.unresolved, SECTION_INDENT * 2),
|
|
46
|
+
'',
|
|
47
|
+
'Working directories and order',
|
|
48
|
+
...table([
|
|
49
|
+
['files with several cwd', String(workingDirectories.filesWithSeveral)],
|
|
50
|
+
['files with no cwd', String(workingDirectories.filesWithNone)],
|
|
51
|
+
['ordinal lower than the line before', String(order.ordinalBackwards)],
|
|
52
|
+
['ordinal equal to the line before', String(order.ordinalRepeats)],
|
|
53
|
+
], SECTION_INDENT),
|
|
54
|
+
'',
|
|
55
|
+
'Line types',
|
|
56
|
+
...countsTable(report.lineTypes, SECTION_INDENT),
|
|
57
|
+
'',
|
|
58
|
+
'response_item types',
|
|
59
|
+
...countsTable(report.responseItems, SECTION_INDENT),
|
|
60
|
+
'',
|
|
61
|
+
'event_msg types',
|
|
62
|
+
...countsTable(report.events, SECTION_INDENT),
|
|
63
|
+
'',
|
|
64
|
+
'item_completed items',
|
|
65
|
+
...countsTable(report.items, SECTION_INDENT),
|
|
66
|
+
'',
|
|
67
|
+
'Action items by status',
|
|
68
|
+
...countsTable(report.itemStatuses, SECTION_INDENT),
|
|
69
|
+
'',
|
|
70
|
+
'Command shapes',
|
|
71
|
+
...countsTable(report.commandShapes, SECTION_INDENT),
|
|
72
|
+
'',
|
|
73
|
+
'What the files can answer (per file)',
|
|
74
|
+
...countsTable(report.capabilities, SECTION_INDENT),
|
|
75
|
+
'',
|
|
76
|
+
].join('\n');
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** What a reader should look at first: each an unknown or a join that did not hold, said by its count. */
|
|
80
|
+
function attention(report) {
|
|
81
|
+
const unknown = (counts) => counts['<unknown>'] ?? 0;
|
|
82
|
+
const findings = [
|
|
83
|
+
report.files.unknownFormat > 0 ? `${report.files.unknownFormat} file(s) are not Codex sessions and were not read` : '',
|
|
84
|
+
report.files.unreadable > 0 ? `${report.files.unreadable} file(s) or folder(s) could not be read` : '',
|
|
85
|
+
report.identity.sharedIds > 0 ? `${report.identity.sharedIds} session id(s) are held by more than one file; nothing joins them` : '',
|
|
86
|
+
report.identity.fileNameDisagrees > 0 ? `${report.identity.fileNameDisagrees} file name(s) name another id than the file's own` : '',
|
|
87
|
+
report.workingDirectories.filesWithSeveral > 0 ? `${report.workingDirectories.filesWithSeveral} file(s) carry more than one cwd` : '',
|
|
88
|
+
report.order.ordinalBackwards > 0 ? `${report.order.ordinalBackwards} line(s) have a lower ordinal than the line before` : '',
|
|
89
|
+
Object.values(report.tree.unresolved).some((n) => n > 0) ? 'some threads could not be joined to their conversation' : '',
|
|
90
|
+
unknown(report.versions) > 0 ? `${unknown(report.versions)} file(s) were written by a Codex version this contract was not measured on` : '',
|
|
91
|
+
unknown(report.historyModes) > 0 ? `${unknown(report.historyModes)} file(s) have an unknown history mode` : '',
|
|
92
|
+
unknown(report.lineTypes) + unknown(report.responseItems) + unknown(report.events) + unknown(report.items) > 0
|
|
93
|
+
? 'records of a kind this contract does not know were counted as <unknown>'
|
|
94
|
+
: '',
|
|
95
|
+
].filter((finding) => finding !== '');
|
|
96
|
+
return findings.length > 0 ? findings : ['none'];
|
|
97
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|