@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,179 @@
|
|
|
1
|
+
import { isJsonObject } from '../../../shared/json.js';
|
|
2
|
+
import { isKnownDenialKind, RULE_REFUSED_READ, RULE_REFUSED_READ_KIND } from '../contract/denials.js';
|
|
3
|
+
import { AGENT_TOOL, HANDBACK_TOOL, FIELDS, TEXT_BLOCK_TYPE, THINKING_BLOCK_TYPE, TOOL_RESULT_BLOCK_TYPE } from '../contract/fields.js';
|
|
4
|
+
import { ASSISTANT_LINE_TYPE, USER_LINE_TYPE } from '../contract/line-types.js';
|
|
5
|
+
import { toolResultReferences } from '../contract/layout.js';
|
|
6
|
+
import { LAUNCH_NOTICE, notificationsIn } from '../contract/task-notifications.js';
|
|
7
|
+
import { GREP_TOOL, profileOf } from '../contract/tools.js';
|
|
8
|
+
import { contentBlocks, toolUseBlocks, wordsWithoutResult } from '../probe/transcript-line.js';
|
|
9
|
+
export function callsIn(record) {
|
|
10
|
+
return toolUseBlocks(record).flatMap((block) => {
|
|
11
|
+
const id = block[FIELDS.blockId];
|
|
12
|
+
const toolName = block[FIELDS.toolName];
|
|
13
|
+
if (typeof id !== 'string' || typeof toolName !== 'string')
|
|
14
|
+
return [];
|
|
15
|
+
const raw = block[FIELDS.toolInput];
|
|
16
|
+
const input = isJsonObject(raw) ? raw : {};
|
|
17
|
+
const { profile, known } = profileOf(toolName);
|
|
18
|
+
return [
|
|
19
|
+
{
|
|
20
|
+
id,
|
|
21
|
+
toolName,
|
|
22
|
+
input,
|
|
23
|
+
...addressedIn(input, profile),
|
|
24
|
+
...(profile.writes === undefined ? {} : { written: writtenIn(input, profile) }),
|
|
25
|
+
resultShape: profile.result,
|
|
26
|
+
toolKnown: known,
|
|
27
|
+
...(printsMatchesIn(toolName, input) ? { printsMatches: true } : {}),
|
|
28
|
+
},
|
|
29
|
+
];
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
/** Whether a call's own input asks for the lines it matched, as the contract names the one tool that can. */
|
|
33
|
+
function printsMatchesIn(toolName, input) {
|
|
34
|
+
return toolName === GREP_TOOL.name && input[GREP_TOOL.outputModeKey] === GREP_TOOL.contentMode;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Where this tool states its target, split by how it must be read. For a tool the contract does not know,
|
|
38
|
+
* everything it was given is treated as a path: the noisiest reading, and a visible one.
|
|
39
|
+
*/
|
|
40
|
+
function addressedIn(input, profile) {
|
|
41
|
+
if (profile.searchWholeInput)
|
|
42
|
+
return { targets: stringsOf(input), commands: [] };
|
|
43
|
+
return {
|
|
44
|
+
targets: profile.pathKeys.flatMap((key) => stringsOf(input[key])),
|
|
45
|
+
commands: profile.commandKeys.flatMap((key) => stringsOf(input[key])),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** What a call that writes a file puts into it: every string it was given, except its path and the text it takes out. */
|
|
49
|
+
function writtenIn(input, profile) {
|
|
50
|
+
const skipped = new Set([...profile.pathKeys, ...(profile.writes?.removedTextKeys ?? [])]);
|
|
51
|
+
return Object.entries(input).flatMap(([key, value]) => (skipped.has(key) ? [] : stringsOf(value)));
|
|
52
|
+
}
|
|
53
|
+
function stringsOf(value) {
|
|
54
|
+
if (typeof value === 'string')
|
|
55
|
+
return [value];
|
|
56
|
+
if (Array.isArray(value))
|
|
57
|
+
return value.flatMap(stringsOf);
|
|
58
|
+
if (value !== null && typeof value === 'object')
|
|
59
|
+
return Object.values(value).flatMap(stringsOf);
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
/** Where each kind of the agent's own words is written: the block that holds it, and the field inside it. */
|
|
63
|
+
const MESSAGE_BLOCKS = [
|
|
64
|
+
{ type: TEXT_BLOCK_TYPE, field: FIELDS.blockText, kind: 'said' },
|
|
65
|
+
{ type: THINKING_BLOCK_TYPE, field: FIELDS.blockThinking, kind: 'reasoning' },
|
|
66
|
+
];
|
|
67
|
+
/**
|
|
68
|
+
* What the agent wrote on this line, block by block and in the order written (`specs/2026-09-15-what-came-back.md` R1,
|
|
69
|
+
* `specs/2026-09-15-why-this-call.md` R1, R4). Only an assistant line: a user line carries text blocks as well, and they are
|
|
70
|
+
* someone else's words.
|
|
71
|
+
*/
|
|
72
|
+
export function messagesIn(record) {
|
|
73
|
+
if (record[FIELDS.lineType] !== ASSISTANT_LINE_TYPE)
|
|
74
|
+
return [];
|
|
75
|
+
return contentBlocks(record).flatMap((block) => {
|
|
76
|
+
const written = MESSAGE_BLOCKS.find((kind) => block[FIELDS.blockType] === kind.type);
|
|
77
|
+
if (written === undefined)
|
|
78
|
+
return [];
|
|
79
|
+
const text = block[written.field];
|
|
80
|
+
// An empty block is kept, not dropped: in the measured session every reasoning block is empty, and a
|
|
81
|
+
// report that said "nothing" of them would confuse what an agent did not write with what the record did not
|
|
82
|
+
// keep. What counts as words is decided above the adapter.
|
|
83
|
+
return typeof text === 'string' ? [{ kind: written.kind, text }] : [];
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The reports delivered on this line (`specs/2026-09-15-where-the-value-went.md` R1): notifications that name a call,
|
|
88
|
+
* on a user line holding no tool result. Whether the call is a delegation is the core's join. A notification naming
|
|
89
|
+
* no call delivers nothing to anything, so it is left out here; the doctor counts it.
|
|
90
|
+
*/
|
|
91
|
+
export function deliveriesIn(record) {
|
|
92
|
+
if (record[FIELDS.lineType] !== USER_LINE_TYPE)
|
|
93
|
+
return [];
|
|
94
|
+
const words = wordsWithoutResult(record);
|
|
95
|
+
if (words === undefined)
|
|
96
|
+
return [];
|
|
97
|
+
return notificationsIn(words).flatMap(({ callId, text }) => (callId === undefined ? [] : [{ callId, text }]));
|
|
98
|
+
}
|
|
99
|
+
export function isDelegation(call) {
|
|
100
|
+
return call.toolName === AGENT_TOOL.name;
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* The report a subagent hands back through a call rather than a message (D9). The words only - which delegation
|
|
104
|
+
* they answer is not in the call, and is the join the caller makes from the transcript the call sits on.
|
|
105
|
+
*/
|
|
106
|
+
export function handbackIn(call) {
|
|
107
|
+
if (call.toolName !== HANDBACK_TOOL.name)
|
|
108
|
+
return undefined;
|
|
109
|
+
const words = call.input[HANDBACK_TOOL.reportInputKey];
|
|
110
|
+
return typeof words === 'string' ? words : undefined;
|
|
111
|
+
}
|
|
112
|
+
/** The instruction a delegation carried. Raw content: what the report exists to quote, once M3 can redact it. */
|
|
113
|
+
export function delegationTextIn(call) {
|
|
114
|
+
const prompt = call.input[AGENT_TOOL.promptInputKey];
|
|
115
|
+
const description = call.input[AGENT_TOOL.descriptionInputKey];
|
|
116
|
+
return {
|
|
117
|
+
...(typeof prompt === 'string' ? { prompt } : {}),
|
|
118
|
+
...(typeof description === 'string' ? { description } : {}),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
export function resultsIn(record) {
|
|
122
|
+
const payload = record[FIELDS.toolUseResult];
|
|
123
|
+
const denialValue = record[FIELDS.denialKind];
|
|
124
|
+
const denial = typeof denialValue === 'string'
|
|
125
|
+
? { kind: denialValue, recognised: isKnownDenialKind(denialValue) }
|
|
126
|
+
: undefined;
|
|
127
|
+
const blocks = contentBlocks(record).filter((block) => block[FIELDS.blockType] === TOOL_RESULT_BLOCK_TYPE);
|
|
128
|
+
// The marker sits on the record, not on a block, so it names a call only while the record holds exactly one.
|
|
129
|
+
const attributable = blocks.length === 1;
|
|
130
|
+
// The payload sits on the record as well, so a launch notice is attributed on the same terms as a denial.
|
|
131
|
+
const launched = isJsonObject(payload) && payload[LAUNCH_NOTICE.statusField] === LAUNCH_NOTICE.launchedStatus;
|
|
132
|
+
const results = blocks.flatMap((block) => {
|
|
133
|
+
const callId = block[FIELDS.resultCallId];
|
|
134
|
+
if (typeof callId !== 'string')
|
|
135
|
+
return [];
|
|
136
|
+
const content = textOf(block[FIELDS.blockContent]) ?? textOf(payload);
|
|
137
|
+
// A refusal the tool says in words sits on its own block, so it names its call however many the record holds.
|
|
138
|
+
const refusedByRule = block[FIELDS.resultIsError] === true && content?.includes(RULE_REFUSED_READ) === true;
|
|
139
|
+
return [
|
|
140
|
+
{
|
|
141
|
+
callId,
|
|
142
|
+
...(content === undefined ? {} : { content }),
|
|
143
|
+
...(denial !== undefined && attributable
|
|
144
|
+
? { denial }
|
|
145
|
+
: refusedByRule
|
|
146
|
+
? { denial: { kind: RULE_REFUSED_READ_KIND, recognised: true } }
|
|
147
|
+
: {}),
|
|
148
|
+
...(launched && attributable ? { launchNotice: true } : {}),
|
|
149
|
+
spilledFiles: spilledFilesIn(block[FIELDS.blockContent], payload),
|
|
150
|
+
},
|
|
151
|
+
];
|
|
152
|
+
});
|
|
153
|
+
return { results, denialUnattributed: denial !== undefined && !attributable };
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* A result may be a string or a structure; either way the model holds what was written, never a summary. A list of
|
|
157
|
+
* text blocks is read as its words (`specs/2026-09-15-what-came-back.md` R1b): all 15 delegating results of the measured session
|
|
158
|
+
* take that form, and held encoded, a line break inside one was an escape - a report was then never found in its own
|
|
159
|
+
* result, and its length was the length of the encoding. Any other structure is kept encoded, as before.
|
|
160
|
+
*/
|
|
161
|
+
function textOf(value) {
|
|
162
|
+
if (value === undefined)
|
|
163
|
+
return undefined;
|
|
164
|
+
if (typeof value === 'string')
|
|
165
|
+
return value;
|
|
166
|
+
if (Array.isArray(value) && value.length > 0 && value.every(isTextBlock)) {
|
|
167
|
+
return value.map((block) => block[FIELDS.blockText]).join('\n');
|
|
168
|
+
}
|
|
169
|
+
return JSON.stringify(value);
|
|
170
|
+
}
|
|
171
|
+
function isTextBlock(value) {
|
|
172
|
+
return isJsonObject(value) && value[FIELDS.blockType] === TEXT_BLOCK_TYPE && typeof value[FIELDS.blockText] === 'string';
|
|
173
|
+
}
|
|
174
|
+
function spilledFilesIn(...positions) {
|
|
175
|
+
return positions.flatMap((position) => {
|
|
176
|
+
const text = textOf(position);
|
|
177
|
+
return text === undefined ? [] : toolResultReferences(text);
|
|
178
|
+
});
|
|
179
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { HOOK_OUTPUT, TERMINAL } from '../contract/hooks.js';
|
|
2
|
+
/** C0 and C1 control characters, and DEL: any of them in the words would take the sequence outside the allowlist. */
|
|
3
|
+
const CONTROL = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
4
|
+
/**
|
|
5
|
+
* A hook's stdout carrying a terminal notification with these words, or nothing for no words
|
|
6
|
+
* (`specs/2026-09-16-when-an-agent-finishes.md` R15). Control characters are replaced before the words go in, since
|
|
7
|
+
* Claude Code ignores the field whole when the sequence leaves the allowlist; `;` is kept out of the title, where
|
|
8
|
+
* OSC 777 reads it as the end of the title.
|
|
9
|
+
*/
|
|
10
|
+
export function terminalNotice(title, text) {
|
|
11
|
+
if (text === '')
|
|
12
|
+
return '';
|
|
13
|
+
const body = text.replace(CONTROL, ' ');
|
|
14
|
+
const heading = title.replace(CONTROL, ' ').replaceAll(';', ' ');
|
|
15
|
+
const { osc, bel, notify, notifyWithTitle } = TERMINAL;
|
|
16
|
+
const sequence = `${osc}${notify};${heading}: ${body}${bel}${osc}${notifyWithTitle};${heading};${body}${bel}`;
|
|
17
|
+
return JSON.stringify({ [HOOK_OUTPUT.terminalSequence]: sequence });
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* A hook's stdout carrying a `systemMessage` with these words, or nothing for no words
|
|
21
|
+
* (`specs/2026-09-16-a-notice-in-the-conversation.md` R4). Only `Stop` shows it — B6a measured it rendered under the
|
|
22
|
+
* assistant's reply as `Stop says: …`, and B4d and B4f measured `SubagentStop` discarding it — so the event that
|
|
23
|
+
* returns this is decided by the caller. Control characters go, as they do in a terminal sequence.
|
|
24
|
+
*/
|
|
25
|
+
export function chatNotice(text) {
|
|
26
|
+
return text === '' ? '' : JSON.stringify({ [HOOK_OUTPUT.systemMessage]: text.replace(CONTROL, ' ') });
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* A hook's stdout keeping the turn from ending, so that the session's own agent says the finding itself
|
|
30
|
+
* (`specs/2026-09-21-the-agent-tells-you.md` R9, R11). The exit code stays 0: this is the JSON form of the same
|
|
31
|
+
* control, and exit 2 is what R2 of `a-notice-in-the-conversation` forbids for good.
|
|
32
|
+
*
|
|
33
|
+
* The line goes out beside it, because B9b measured both shown and R11 keeps it: the line is agentwhy's own
|
|
34
|
+
* record of what it found, in words the agent's message cannot soften.
|
|
35
|
+
*/
|
|
36
|
+
export function blockNotice(reason, text) {
|
|
37
|
+
return JSON.stringify({
|
|
38
|
+
[HOOK_OUTPUT.decision]: HOOK_OUTPUT.block,
|
|
39
|
+
[HOOK_OUTPUT.reason]: reason.replace(CONTROL, ' '),
|
|
40
|
+
...(text === '' ? {} : { [HOOK_OUTPUT.systemMessage]: text.replace(CONTROL, ' ') }),
|
|
41
|
+
});
|
|
42
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { PRE_TOOL_USE } from '../contract/hooks.js';
|
|
2
|
+
import { isJsonObject, parseJsonObject } from '../../../shared/json.js';
|
|
3
|
+
/**
|
|
4
|
+
* Reads the text a `PreToolUse` hook was given. A call to any tool but the shell is `another-tool`: a hook installed
|
|
5
|
+
* without a matcher sees every call, and those are not this command's to judge, so they pass without a word. Never
|
|
6
|
+
* throws. The working directory is carried when the input has one.
|
|
7
|
+
*/
|
|
8
|
+
export function parsePreToolUseInput(text) {
|
|
9
|
+
const input = parseJsonObject(text);
|
|
10
|
+
if (input === undefined)
|
|
11
|
+
return { unusable: 'not-json' };
|
|
12
|
+
const { fields } = PRE_TOOL_USE;
|
|
13
|
+
if (input[fields.event] !== PRE_TOOL_USE.event)
|
|
14
|
+
return { unusable: 'another-event' };
|
|
15
|
+
if (input[fields.toolName] !== PRE_TOOL_USE.shellTool)
|
|
16
|
+
return { anotherTool: true };
|
|
17
|
+
const toolInput = input[fields.toolInput];
|
|
18
|
+
const command = isJsonObject(toolInput) ? toolInput[fields.command] : undefined;
|
|
19
|
+
if (typeof command !== 'string')
|
|
20
|
+
return { unusable: 'field-missing' };
|
|
21
|
+
const cwd = input[fields.cwd];
|
|
22
|
+
return typeof cwd === 'string' && cwd !== '' ? { command, cwd } : { command };
|
|
23
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { STOP, SUBAGENT_STOP } from '../contract/hooks.js';
|
|
2
|
+
import { parseJsonObject } from '../../../shared/json.js';
|
|
3
|
+
export function parseStopInput(text) {
|
|
4
|
+
const input = parseJsonObject(text);
|
|
5
|
+
if (input === undefined)
|
|
6
|
+
return { unusable: 'not-json' };
|
|
7
|
+
const { fields } = STOP;
|
|
8
|
+
if (input[fields.event] !== STOP.event)
|
|
9
|
+
return { unusable: 'another-event' };
|
|
10
|
+
const sessionId = input[fields.sessionId];
|
|
11
|
+
if (typeof sessionId !== 'string' || sessionId === '')
|
|
12
|
+
return { unusable: 'field-missing' };
|
|
13
|
+
const transcriptPath = input[SUBAGENT_STOP.fields.transcriptPath];
|
|
14
|
+
const lastMessage = input[SUBAGENT_STOP.fields.lastMessage];
|
|
15
|
+
return {
|
|
16
|
+
turn: {
|
|
17
|
+
sessionId,
|
|
18
|
+
// Anything but `true` is read as "not inside one": a flag this version cannot read must never be the reason
|
|
19
|
+
// a person is told nothing, and the block it guards is refused by the store's own record as well.
|
|
20
|
+
active: input[fields.active] === true,
|
|
21
|
+
...(typeof transcriptPath === 'string' && transcriptPath !== '' ? { transcriptPath } : {}),
|
|
22
|
+
...(typeof lastMessage === 'string' ? { lastMessage } : {}),
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { SUBAGENT_STOP } from '../contract/hooks.js';
|
|
2
|
+
import { parseJsonObject } from '../../../shared/json.js';
|
|
3
|
+
/** Reads the text a hook was given on standard input. Never throws: an input it cannot use is a named answer. */
|
|
4
|
+
export function parseSubagentStopInput(text) {
|
|
5
|
+
const input = parseJsonObject(text);
|
|
6
|
+
if (input === undefined)
|
|
7
|
+
return { unusable: 'not-json' };
|
|
8
|
+
const { fields } = SUBAGENT_STOP;
|
|
9
|
+
if (input[fields.event] !== SUBAGENT_STOP.event)
|
|
10
|
+
return { unusable: 'another-event' };
|
|
11
|
+
const transcriptPath = input[fields.transcriptPath];
|
|
12
|
+
const agentId = input[fields.agentId];
|
|
13
|
+
if (typeof transcriptPath !== 'string' || transcriptPath === '' || typeof agentId !== 'string' || agentId === '') {
|
|
14
|
+
return { unusable: 'field-missing' };
|
|
15
|
+
}
|
|
16
|
+
const lastMessage = input[fields.lastMessage];
|
|
17
|
+
const sessionId = input[fields.sessionId];
|
|
18
|
+
return {
|
|
19
|
+
agent: {
|
|
20
|
+
transcriptPath,
|
|
21
|
+
agentId,
|
|
22
|
+
...(typeof lastMessage === 'string' ? { lastMessage } : {}),
|
|
23
|
+
...(typeof sessionId === 'string' && sessionId !== '' ? { sessionId } : {}),
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { isJsonObject, parseJsonObject } from '../../../shared/json.js';
|
|
2
|
+
/**
|
|
3
|
+
* Reads protected paths out of a Claude Code settings file's deny list.
|
|
4
|
+
*
|
|
5
|
+
* The limit is the point of test A7: a deny rule names **a tool and a route**, not a resource. `Read(./.env*)`
|
|
6
|
+
* carries a path and can be read as one; `Bash(cat:*.env*)` is a command pattern and is not a path at all.
|
|
7
|
+
* Pretending to understand the second would hand the reader a policy the tool never applied, so those entries
|
|
8
|
+
* are counted as ignored and the report says how many.
|
|
9
|
+
*/
|
|
10
|
+
const FILE_RULE = /^(Read|Edit|Write|NotebookEdit)\(([^)]+)\)$/;
|
|
11
|
+
/** The path a deny rule names, when the rule is one about files at all. `Bash(cat:*)` names none (test A7). */
|
|
12
|
+
export function fileRulePathOf(entry) {
|
|
13
|
+
return FILE_RULE.exec(entry.trim())?.[2];
|
|
14
|
+
}
|
|
15
|
+
const DENY_KEYS = { permissions: 'permissions', deny: 'deny' };
|
|
16
|
+
export function readDenyRules(text) {
|
|
17
|
+
const settings = parseJsonObject(text);
|
|
18
|
+
const permissions = settings?.[DENY_KEYS.permissions];
|
|
19
|
+
const deny = isJsonObject(permissions) ? permissions[DENY_KEYS.deny] : undefined;
|
|
20
|
+
if (!Array.isArray(deny))
|
|
21
|
+
return undefined;
|
|
22
|
+
const entries = deny.filter((entry) => typeof entry === 'string');
|
|
23
|
+
const patterns = entries.flatMap((entry) => {
|
|
24
|
+
const match = FILE_RULE.exec(entry);
|
|
25
|
+
const path = match?.[2];
|
|
26
|
+
return path === undefined ? [] : [toPattern(path)];
|
|
27
|
+
});
|
|
28
|
+
return { patterns: [...new Set(patterns)], used: patterns.length, ignored: deny.length - patterns.length };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A deny rule is written relative to a project or a home directory, while a transcript records the path the
|
|
32
|
+
* tool was given, often absolute. Anchoring at any depth keeps the two comparable. It **broadens** the match,
|
|
33
|
+
* which errs toward reporting a call rather than passing over it - the safe direction here.
|
|
34
|
+
*/
|
|
35
|
+
export function toPattern(path) {
|
|
36
|
+
const relative = path.replace(/^\.\//, '').replace(/^~\//, '');
|
|
37
|
+
return relative.startsWith('**/') || relative.startsWith('/') ? relative : `**/${relative}`;
|
|
38
|
+
}
|
|
39
|
+
export function policyFromDenyRules(rules, path) {
|
|
40
|
+
return {
|
|
41
|
+
level: 'no-read',
|
|
42
|
+
protected: rules.patterns.map((pattern) => ({ pattern })),
|
|
43
|
+
allowed: [],
|
|
44
|
+
origin: { kind: 'settings', path, used: rules.used, ignored: rules.ignored },
|
|
45
|
+
};
|
|
46
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { FileAccessError } from '../../../ports/file-access-error.js';
|
|
2
|
+
import { toLabel } from '../../../shared/label.js';
|
|
3
|
+
import { CONTRACT_VERSION, VERIFIED_AGAINST } from '../contract/version.js';
|
|
4
|
+
import { MetaTally } from './meta-tally.js';
|
|
5
|
+
import { SessionTally } from './session-tally.js';
|
|
6
|
+
import { TranscriptTally } from './transcript-tally.js';
|
|
7
|
+
export class ClaudeCodeProbe {
|
|
8
|
+
#files;
|
|
9
|
+
constructor(files) {
|
|
10
|
+
this.#files = files;
|
|
11
|
+
}
|
|
12
|
+
async probe(session) {
|
|
13
|
+
const tally = new SessionTally();
|
|
14
|
+
const main = new TranscriptTally(tally.collectors);
|
|
15
|
+
const subagents = new TranscriptTally(tally.collectors);
|
|
16
|
+
if (session.mainTranscript.present)
|
|
17
|
+
await this.#readTranscript(session.mainTranscript.path, main);
|
|
18
|
+
for (const source of session.subagents) {
|
|
19
|
+
if (source.transcript.present)
|
|
20
|
+
await this.#readTranscript(source.transcript.path, subagents);
|
|
21
|
+
}
|
|
22
|
+
const meta = await this.#readMeta(session.subagents);
|
|
23
|
+
return {
|
|
24
|
+
schemaVersion: 4,
|
|
25
|
+
contractVersion: CONTRACT_VERSION,
|
|
26
|
+
verifiedAgainst: VERIFIED_AGAINST.claudeCode,
|
|
27
|
+
sessionId: toLabel(session.sessionId),
|
|
28
|
+
sources: sourcesOf(session),
|
|
29
|
+
toolVersions: tally.toolVersions(),
|
|
30
|
+
workingDirectories: tally.workingDirectories(),
|
|
31
|
+
main: main.toStats(),
|
|
32
|
+
subagents: subagents.toStats(),
|
|
33
|
+
meta,
|
|
34
|
+
agentToolInputKeys: tally.agentToolInputKeys(),
|
|
35
|
+
denials: tally.denials(),
|
|
36
|
+
toolResultReferences: tally.toolResultReferences(session.toolResultFiles),
|
|
37
|
+
taskNotifications: tally.taskNotifications(),
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
async #readTranscript(path, tally) {
|
|
41
|
+
tally.recordFile();
|
|
42
|
+
try {
|
|
43
|
+
for await (const raw of this.#files.readLines(path))
|
|
44
|
+
tally.recordLine(raw);
|
|
45
|
+
}
|
|
46
|
+
catch (error) {
|
|
47
|
+
if (!(error instanceof FileAccessError))
|
|
48
|
+
throw error;
|
|
49
|
+
tally.recordUnreadableFile();
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
async #readMeta(sources) {
|
|
53
|
+
const tally = new MetaTally();
|
|
54
|
+
for (const source of sources) {
|
|
55
|
+
if (!source.meta.present)
|
|
56
|
+
continue;
|
|
57
|
+
try {
|
|
58
|
+
tally.recordFile(await this.#files.readText(source.meta.path));
|
|
59
|
+
}
|
|
60
|
+
catch (error) {
|
|
61
|
+
if (!(error instanceof FileAccessError))
|
|
62
|
+
throw error;
|
|
63
|
+
tally.recordUnreadableFile();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return tally.toStats();
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function sourcesOf(session) {
|
|
70
|
+
return {
|
|
71
|
+
mainTranscript: state(session.mainTranscript),
|
|
72
|
+
subagentsDirectory: state(session.subagentsDir),
|
|
73
|
+
toolResultsDirectory: state(session.toolResultsDir),
|
|
74
|
+
subagentFiles: {
|
|
75
|
+
total: session.subagents.length,
|
|
76
|
+
withTranscript: session.subagents.filter((source) => source.transcript.present).length,
|
|
77
|
+
withMeta: session.subagents.filter((source) => source.meta.present).length,
|
|
78
|
+
incompletePairs: session.subagents.filter((source) => source.transcript.present !== source.meta.present).length,
|
|
79
|
+
},
|
|
80
|
+
toolResultFiles: session.toolResultFiles.length,
|
|
81
|
+
unrecognisedEntries: session.unrecognised.length,
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function state(presence) {
|
|
85
|
+
return presence.present ? 'present' : presence.reason;
|
|
86
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Counter } from '../../../../shared/counter.js';
|
|
2
|
+
import { isJsonObject } from '../../../../shared/json.js';
|
|
3
|
+
import { toLabel } from '../../../../shared/label.js';
|
|
4
|
+
import { AGENT_TOOL, FIELDS } from '../../contract/fields.js';
|
|
5
|
+
import { toolUseBlocks } from '../transcript-line.js';
|
|
6
|
+
export class AgentToolInputKeyCollector {
|
|
7
|
+
#keys = new Counter();
|
|
8
|
+
collect({ json }) {
|
|
9
|
+
if (json === undefined)
|
|
10
|
+
return;
|
|
11
|
+
for (const block of toolUseBlocks(json)) {
|
|
12
|
+
if (block[FIELDS.toolName] !== AGENT_TOOL.name)
|
|
13
|
+
continue;
|
|
14
|
+
const input = block[FIELDS.toolInput];
|
|
15
|
+
if (isJsonObject(input)) {
|
|
16
|
+
for (const key of Object.keys(input))
|
|
17
|
+
this.#keys.add(toLabel(key));
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
counts() {
|
|
22
|
+
return this.#keys.toCounts();
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { byString } from '../../../../shared/compare.js';
|
|
2
|
+
import { Counter } from '../../../../shared/counter.js';
|
|
3
|
+
import { toLabel } from '../../../../shared/label.js';
|
|
4
|
+
import { isKnownDenialKind } from '../../contract/denials.js';
|
|
5
|
+
import { FIELDS } from '../../contract/fields.js';
|
|
6
|
+
export class DenialKindCollector {
|
|
7
|
+
#kinds = new Counter();
|
|
8
|
+
#unknownKinds = new Set();
|
|
9
|
+
collect({ json }) {
|
|
10
|
+
if (json === undefined || !(FIELDS.denialKind in json))
|
|
11
|
+
return;
|
|
12
|
+
const kind = json[FIELDS.denialKind];
|
|
13
|
+
this.#kinds.add(toLabel(kind));
|
|
14
|
+
if (!isKnownDenialKind(kind))
|
|
15
|
+
this.#unknownKinds.add(toLabel(kind));
|
|
16
|
+
}
|
|
17
|
+
stats() {
|
|
18
|
+
return { byKind: this.#kinds.toCounts(), unknownKinds: [...this.#unknownKinds].sort(byString) };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { FIELDS } from '../../contract/fields.js';
|
|
2
|
+
export class DeniedCallCollector {
|
|
3
|
+
#deniedCalls = 0;
|
|
4
|
+
collect({ json }) {
|
|
5
|
+
if (json !== undefined && FIELDS.denialKind in json)
|
|
6
|
+
this.#deniedCalls += 1;
|
|
7
|
+
}
|
|
8
|
+
deniedCalls() {
|
|
9
|
+
return this.#deniedCalls;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { identifierMatches } from '../../contract/identifiers.js';
|
|
2
|
+
export class IdentifierCollector {
|
|
3
|
+
#toolUseIds = new Set();
|
|
4
|
+
#uuids = new Set();
|
|
5
|
+
// Reads the raw text, so the identifiers on an unparsable line still count.
|
|
6
|
+
collect({ raw }) {
|
|
7
|
+
const identifiers = identifierMatches(raw);
|
|
8
|
+
for (const id of identifiers.toolUseIds)
|
|
9
|
+
this.#toolUseIds.add(id);
|
|
10
|
+
for (const id of identifiers.uuids)
|
|
11
|
+
this.#uuids.add(id);
|
|
12
|
+
}
|
|
13
|
+
uniqueToolUseIds() {
|
|
14
|
+
return this.#toolUseIds.size;
|
|
15
|
+
}
|
|
16
|
+
uniqueUuids() {
|
|
17
|
+
return this.#uuids.size;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { byString } from '../../../../shared/compare.js';
|
|
2
|
+
import { Counter } from '../../../../shared/counter.js';
|
|
3
|
+
import { toLabel } from '../../../../shared/label.js';
|
|
4
|
+
import { FIELDS } from '../../contract/fields.js';
|
|
5
|
+
import { SYSTEM_LINE_TYPE, classifyLineType } from '../../contract/line-types.js';
|
|
6
|
+
export class LineTypeCollector {
|
|
7
|
+
#lineTypes = new Counter();
|
|
8
|
+
#unknownLineTypes = new Set();
|
|
9
|
+
#systemSubtypes = new Counter();
|
|
10
|
+
collect({ json }) {
|
|
11
|
+
if (json === undefined)
|
|
12
|
+
return;
|
|
13
|
+
const type = json[FIELDS.lineType];
|
|
14
|
+
this.#lineTypes.add(toLabel(type));
|
|
15
|
+
if (classifyLineType(type) === 'unknown')
|
|
16
|
+
this.#unknownLineTypes.add(toLabel(type));
|
|
17
|
+
if (type === SYSTEM_LINE_TYPE)
|
|
18
|
+
this.#systemSubtypes.add(toLabel(json[FIELDS.systemSubtype]));
|
|
19
|
+
}
|
|
20
|
+
lineTypes() {
|
|
21
|
+
return this.#lineTypes.toCounts();
|
|
22
|
+
}
|
|
23
|
+
unknownLineTypes() {
|
|
24
|
+
return [...this.#unknownLineTypes].sort(byString);
|
|
25
|
+
}
|
|
26
|
+
systemSubtypes() {
|
|
27
|
+
return this.#systemSubtypes.toCounts();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { FIELDS } from '../../contract/fields.js';
|
|
2
|
+
export class SidechainCollector {
|
|
3
|
+
#sidechain = { true: 0, false: 0, absent: 0, invalid: 0 };
|
|
4
|
+
collect({ json }) {
|
|
5
|
+
if (json === undefined)
|
|
6
|
+
return;
|
|
7
|
+
const value = json[FIELDS.sidechain];
|
|
8
|
+
if (value === true)
|
|
9
|
+
this.#sidechain.true += 1;
|
|
10
|
+
else if (value === false)
|
|
11
|
+
this.#sidechain.false += 1;
|
|
12
|
+
else if (value === undefined)
|
|
13
|
+
this.#sidechain.absent += 1;
|
|
14
|
+
else
|
|
15
|
+
this.#sidechain.invalid += 1;
|
|
16
|
+
}
|
|
17
|
+
stats() {
|
|
18
|
+
return { ...this.#sidechain };
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { AGENT_TOOL, FIELDS } from '../../contract/fields.js';
|
|
2
|
+
import { USER_LINE_TYPE } from '../../contract/line-types.js';
|
|
3
|
+
import { notificationsIn } from '../../contract/task-notifications.js';
|
|
4
|
+
import { toolUseBlocks, wordsWithoutResult } from '../transcript-line.js';
|
|
5
|
+
/**
|
|
6
|
+
* Notifications delivered on a user line, by what they name (`specs/2026-09-15-where-the-value-went.md` R4): a
|
|
7
|
+
* delegating call, another call, a call no transcript of the session holds, or nothing. Only the first is read as a
|
|
8
|
+
* report, so the rest are counted here rather than lost. Fed every transcript, because a notification and the call it
|
|
9
|
+
* names need not sit in the same one.
|
|
10
|
+
*/
|
|
11
|
+
export class TaskNotificationCollector {
|
|
12
|
+
#calls = new Set();
|
|
13
|
+
#delegatingCalls = new Set();
|
|
14
|
+
#named = [];
|
|
15
|
+
collect({ json }) {
|
|
16
|
+
if (json === undefined)
|
|
17
|
+
return;
|
|
18
|
+
for (const block of toolUseBlocks(json)) {
|
|
19
|
+
const id = block[FIELDS.blockId];
|
|
20
|
+
if (typeof id !== 'string')
|
|
21
|
+
continue;
|
|
22
|
+
this.#calls.add(id);
|
|
23
|
+
if (block[FIELDS.toolName] === AGENT_TOOL.name)
|
|
24
|
+
this.#delegatingCalls.add(id);
|
|
25
|
+
}
|
|
26
|
+
if (json[FIELDS.lineType] !== USER_LINE_TYPE)
|
|
27
|
+
return;
|
|
28
|
+
const words = wordsWithoutResult(json);
|
|
29
|
+
if (words === undefined)
|
|
30
|
+
return;
|
|
31
|
+
for (const { callId } of notificationsIn(words))
|
|
32
|
+
this.#named.push(callId);
|
|
33
|
+
}
|
|
34
|
+
stats() {
|
|
35
|
+
let namingADelegation = 0;
|
|
36
|
+
let namingAnotherCall = 0;
|
|
37
|
+
let namingNoCall = 0;
|
|
38
|
+
let withoutCallId = 0;
|
|
39
|
+
for (const callId of this.#named) {
|
|
40
|
+
if (callId === undefined)
|
|
41
|
+
withoutCallId += 1;
|
|
42
|
+
else if (this.#delegatingCalls.has(callId))
|
|
43
|
+
namingADelegation += 1;
|
|
44
|
+
else if (this.#calls.has(callId))
|
|
45
|
+
namingAnotherCall += 1;
|
|
46
|
+
else
|
|
47
|
+
namingNoCall += 1;
|
|
48
|
+
}
|
|
49
|
+
return { delivered: this.#named.length, namingADelegation, namingAnotherCall, namingNoCall, withoutCallId };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { FIELDS, TOOL_RESULT_BLOCK_TYPE } from '../../contract/fields.js';
|
|
2
|
+
import { toolResultReferences } from '../../contract/layout.js';
|
|
3
|
+
import { contentBlocks } from '../transcript-line.js';
|
|
4
|
+
export class ToolResultReferenceCollector {
|
|
5
|
+
#referenced = new Set();
|
|
6
|
+
// Only result positions count. A tool-results path in a prompt, in assistant text or in a tool input is a
|
|
7
|
+
// mention, not a spill reference, and counting it would report a missing spilled result that never existed
|
|
8
|
+
// (lesson L009).
|
|
9
|
+
collect({ json }) {
|
|
10
|
+
if (json === undefined)
|
|
11
|
+
return;
|
|
12
|
+
const results = [json[FIELDS.toolUseResult]];
|
|
13
|
+
for (const block of contentBlocks(json)) {
|
|
14
|
+
if (block[FIELDS.blockType] === TOOL_RESULT_BLOCK_TYPE)
|
|
15
|
+
results.push(block[FIELDS.blockContent]);
|
|
16
|
+
}
|
|
17
|
+
for (const result of results) {
|
|
18
|
+
if (result === undefined)
|
|
19
|
+
continue;
|
|
20
|
+
const text = typeof result === 'string' ? result : JSON.stringify(result);
|
|
21
|
+
for (const name of toolResultReferences(text))
|
|
22
|
+
this.#referenced.add(name);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
// Counts only: a file name captured from a transcript is free text and must not reach the output (lesson L009).
|
|
26
|
+
stats(availableFiles) {
|
|
27
|
+
const available = new Set(availableFiles);
|
|
28
|
+
const referenced = [...this.#referenced];
|
|
29
|
+
return { referenced: referenced.length, missing: referenced.filter((name) => !available.has(name)).length };
|
|
30
|
+
}
|
|
31
|
+
}
|