@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,90 @@
|
|
|
1
|
+
import { filesRead } from '../flow-reads.js';
|
|
2
|
+
import { RULE_NAMES } from '../rule-names.js';
|
|
3
|
+
/**
|
|
4
|
+
* Whether a file read holds keys, as far as what was read says: a recognised key format, or a read shared with another
|
|
5
|
+
* protected file, whose keys may be this one's (`PrivateFile.mixed`). The to-do list and the marks decide by this alone.
|
|
6
|
+
*/
|
|
7
|
+
export function holdsKeys(file) {
|
|
8
|
+
return file.keys.length > 0 || file.mixed === true;
|
|
9
|
+
}
|
|
10
|
+
/** Names a file kept as an example rather than as configuration. A convention, matched on the name and nothing else. */
|
|
11
|
+
const TEMPLATE = /\.(example|sample|template|dist)$/;
|
|
12
|
+
/** The patterns of the built-in rules, every one of them for passwords and keys (F45). */
|
|
13
|
+
const KEY_RULES = new Set(RULE_NAMES.flatMap((group) => group.patterns));
|
|
14
|
+
/** Whether a path is named as a template (R12c): the one test the check and the report page share. */
|
|
15
|
+
export function isTemplate(path) {
|
|
16
|
+
return TEMPLATE.test(path);
|
|
17
|
+
}
|
|
18
|
+
export function actionsOf(report) {
|
|
19
|
+
const rotate = new Set();
|
|
20
|
+
for (const flow of report.flows) {
|
|
21
|
+
for (const step of flow.steps) {
|
|
22
|
+
// H7: a file whose text reached the agent - a traced value, or lines a search printed - is to see to.
|
|
23
|
+
if (step.kind === 'reached' && step.toolKnown && step.outcome === 'succeeded') {
|
|
24
|
+
for (const file of filesRead(step))
|
|
25
|
+
rotate.add(file);
|
|
26
|
+
}
|
|
27
|
+
// X10: a value its model was handed from code it wrote, which no call of its returned, is read all the same.
|
|
28
|
+
if (step.kind === 'received')
|
|
29
|
+
for (const file of step.files)
|
|
30
|
+
rotate.add(file);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
for (const statement of report.returns) {
|
|
34
|
+
if (statement.strength === 'value')
|
|
35
|
+
for (const file of statement.paths)
|
|
36
|
+
rotate.add(file);
|
|
37
|
+
for (const file of statement.writtenFrom)
|
|
38
|
+
rotate.add(file);
|
|
39
|
+
}
|
|
40
|
+
const patternOf = new Map(report.findings.map((finding) => [finding.path, finding.pattern]));
|
|
41
|
+
const toldPaths = new Set(report.findings.filter((finding) => finding.told === true).map((finding) => finding.path));
|
|
42
|
+
const routes = new Map();
|
|
43
|
+
const named = new Set();
|
|
44
|
+
// A call whose tool the adapter does not know is a mention, wherever the path sat: there is no profile saying that
|
|
45
|
+
// this tool addresses a file, so its text was searched for want of one. Found by running `check` on real sessions,
|
|
46
|
+
// where messages between agents were reported as routes to `.env`.
|
|
47
|
+
const reaching = report.stories.filter((story) => story.toolKnown);
|
|
48
|
+
const mentions = new Set(report.stories.filter((story) => !story.toolKnown).map((story) => story.path));
|
|
49
|
+
for (const story of reaching) {
|
|
50
|
+
if (story.outcome !== 'succeeded' || story.source !== 'input' || toldPaths.has(story.path))
|
|
51
|
+
continue;
|
|
52
|
+
named.add(story.path);
|
|
53
|
+
const key = `${story.path}\u0000${story.did}`;
|
|
54
|
+
const pattern = patternOf.get(story.path);
|
|
55
|
+
const known = routes.get(key);
|
|
56
|
+
routes.set(key, {
|
|
57
|
+
path: story.path,
|
|
58
|
+
did: story.did,
|
|
59
|
+
...(pattern === undefined ? {} : { pattern }),
|
|
60
|
+
occurrences: (known?.occurrences ?? 0) + story.occurrences,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const onlyInResults = new Set(reaching
|
|
64
|
+
.filter((story) => story.outcome === 'succeeded' && story.source === 'result' && !named.has(story.path) && !toldPaths.has(story.path))
|
|
65
|
+
.map((story) => story.path));
|
|
66
|
+
const keyed = new Set(report.privateFiles.filter(holdsKeys).map((file) => file.path));
|
|
67
|
+
const readOf = new Map(report.privateFiles.map((file) => [file.path, readOfFile(file)]));
|
|
68
|
+
return {
|
|
69
|
+
policy: report.scope.policy.origin,
|
|
70
|
+
// F57: a told file's contents were read because the person let them be; only keys in it still need changing - a key
|
|
71
|
+
// of a known format, or any file a built-in rule for passwords and keys covers (F45: that rule is itself the fact).
|
|
72
|
+
rotate: [...rotate].filter((path) => !toldPaths.has(path) || keyed.has(path) || KEY_RULES.has(patternOf.get(path))).sort().map((path) => {
|
|
73
|
+
const read = readOf.get(path);
|
|
74
|
+
return { path, template: TEMPLATE.test(path), ...(keyed.has(path) ? { keyed: true } : {}), ...(read === undefined ? {} : { read }) };
|
|
75
|
+
}),
|
|
76
|
+
openRoutes: [...routes.values()],
|
|
77
|
+
...(toldPaths.size === 0 ? {} : { told: [...new Set(reaching.filter((story) => story.outcome === 'succeeded' && toldPaths.has(story.path)).map((story) => story.path))].sort() }),
|
|
78
|
+
onlyInResults: [...onlyInResults].sort(),
|
|
79
|
+
unknown: [...new Set(reaching.filter((story) => story.outcome === 'unknown').map((story) => story.path))].sort(),
|
|
80
|
+
refusedAttempts: report.tally.refusedAttempts,
|
|
81
|
+
secretShapes: report.secretShapes.flatMap((finding) => finding.classes),
|
|
82
|
+
mentions: mentions.size,
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
/** A file's read, where it left anything to name. */
|
|
86
|
+
function readOfFile(file) {
|
|
87
|
+
if (file.keys.length === 0 && file.names.length === 0 && file.keyed.length === 0 && file.mixed !== true)
|
|
88
|
+
return undefined;
|
|
89
|
+
return { keys: file.keys, names: file.names, keyed: file.keyed, ...(file.mixed === true ? { mixed: true } : {}) };
|
|
90
|
+
}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { splitBySince } from '../../core/session-filter.js';
|
|
2
|
+
import { choosePolicy, policyRefusal } from '../choose-policy.js';
|
|
3
|
+
import { digestOf } from './actions-digest.js';
|
|
4
|
+
import { checkOf } from './check-lines.js';
|
|
5
|
+
import { noteProblem, recordMark, recordUnmark } from './mark-request.js';
|
|
6
|
+
import { actionsAfterMarks, marksInRange, standingMarks } from './marks.js';
|
|
7
|
+
/** Each report is run for its actions; the text it also renders is discarded, so this only has to be a valid width. */
|
|
8
|
+
const DISCARDED_WIDTH = 100;
|
|
9
|
+
/**
|
|
10
|
+
* `agentwhy check`: every session of this project active in the range, read the way `report` reads one, and what a
|
|
11
|
+
* person can do about them in the terminal (`specs/2026-09-16-worth-running-every-day.md` R11-R16). A check writes
|
|
12
|
+
* nothing; a mark appends one line to the person's record, outside the project (R31-R35).
|
|
13
|
+
*/
|
|
14
|
+
export class SessionCheck {
|
|
15
|
+
#dependencies;
|
|
16
|
+
constructor(dependencies) {
|
|
17
|
+
this.#dependencies = dependencies;
|
|
18
|
+
}
|
|
19
|
+
async run(options) {
|
|
20
|
+
const analysis = await this.#analyse(options, options.share);
|
|
21
|
+
if ('answer' in analysis)
|
|
22
|
+
return analysis.answer;
|
|
23
|
+
const reading = await this.#dependencies.marks.read();
|
|
24
|
+
const standing = standingMarks(reading.records);
|
|
25
|
+
const before = analysis.sessions.map((session) => session.actions);
|
|
26
|
+
const after = analysis.sessions.map((session) => actionsAfterMarks(session.actions, session.summary.modifiedAt, standing));
|
|
27
|
+
const { done, reopened } = marksInRange(before, after, standing);
|
|
28
|
+
const digest = digestOf(after, {
|
|
29
|
+
asked: options.since.asked,
|
|
30
|
+
sessionsUnreadable: analysis.unreadable,
|
|
31
|
+
policyKind: analysis.policyKind,
|
|
32
|
+
...(standing.size === 0 && !reading.failed
|
|
33
|
+
? {}
|
|
34
|
+
: { marks: { done, reopened: reopened.map(({ path, result, at }) => ({ path, result, at })), unreadable: reading.failed } }),
|
|
35
|
+
});
|
|
36
|
+
return { outcome: 'checked', output: this.#dependencies.createRenderer(options).render(digest) };
|
|
37
|
+
}
|
|
38
|
+
async mark(options) {
|
|
39
|
+
// A note that cannot be recorded is said before a single session is read.
|
|
40
|
+
const problem = noteProblem(options.note);
|
|
41
|
+
if (problem !== undefined)
|
|
42
|
+
return problem;
|
|
43
|
+
// Marked against the full answer, marks or not: a file marked yesterday can be marked again today.
|
|
44
|
+
const analysis = await this.#analyse(options, false);
|
|
45
|
+
if ('answer' in analysis)
|
|
46
|
+
return analysis.answer;
|
|
47
|
+
const lines = checkOf(analysis.sessions.map((session) => ({ name: session.summary.id, actions: session.actions }))).rows;
|
|
48
|
+
return recordMark(this.#dependencies.marks, lines, options, { asked: options.since.asked, now: this.#dependencies.now });
|
|
49
|
+
}
|
|
50
|
+
async unmark(options) {
|
|
51
|
+
return recordUnmark(this.#dependencies.marks, options.path, this.#dependencies.now);
|
|
52
|
+
}
|
|
53
|
+
async #analyse(options, share) {
|
|
54
|
+
const { catalogue, report, files, workingDirectory } = this.#dependencies;
|
|
55
|
+
const chosen = await choosePolicy(options, files, this.#dependencies.tell);
|
|
56
|
+
if ('errors' in chosen)
|
|
57
|
+
return { answer: { outcome: 'policy-refused', output: policyRefusal(chosen.errors) } };
|
|
58
|
+
const listing = await catalogue.list(workingDirectory);
|
|
59
|
+
if (!listing.found || listing.sessions.length === 0) {
|
|
60
|
+
return {
|
|
61
|
+
answer: {
|
|
62
|
+
outcome: 'no-sessions',
|
|
63
|
+
output: `No sessions are stored for this directory, so there is nothing to check.\nLooked in ${listing.directory}\n` +
|
|
64
|
+
`agentwhy reads sessions Claude Code already keeps, so either this directory has not been used with Claude Code yet, or it is not the one being worked in.\n` +
|
|
65
|
+
`Use Claude Code here, then run agentwhy check again - or run agentwhy init now, to be protected before there is history to check.\n`,
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const { inRange } = splitBySince(listing.sessions, options.since.since);
|
|
70
|
+
const sessions = [];
|
|
71
|
+
let unreadable = 0;
|
|
72
|
+
for (const summary of inRange) {
|
|
73
|
+
const result = await report.run({
|
|
74
|
+
input: summary.path,
|
|
75
|
+
policy: chosen.policy,
|
|
76
|
+
open: false,
|
|
77
|
+
ascii: false,
|
|
78
|
+
full: false,
|
|
79
|
+
colour: false,
|
|
80
|
+
share,
|
|
81
|
+
width: DISCARDED_WIDTH,
|
|
82
|
+
});
|
|
83
|
+
// Asked of the result, never of its text, as `start` asks it.
|
|
84
|
+
if (result.actions === undefined || result.outcome === 'session-unreadable')
|
|
85
|
+
unreadable += 1;
|
|
86
|
+
else
|
|
87
|
+
sessions.push({ summary, actions: result.actions });
|
|
88
|
+
}
|
|
89
|
+
return { policyKind: chosen.policy.origin.kind, sessions, unreadable };
|
|
90
|
+
}
|
|
91
|
+
}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { policyFromDenyRules, readDenyRules } from '../adapter/claude-code/policy/deny-rules.js';
|
|
2
|
+
import { parsePolicy } from '../core/policy/parse-policy.js';
|
|
3
|
+
import { readTellLists, tellPatterns } from './private-files/tell-lists.js';
|
|
4
|
+
import { resolvePolicy } from '../core/policy/resolve-policy.js';
|
|
5
|
+
import { FileAccessError } from '../ports/file-access-error.js';
|
|
6
|
+
/**
|
|
7
|
+
* Which policy a session is read under: the policy file, else the settings file's deny rules, else the built-in
|
|
8
|
+
* default. `report` and `watch` both ask this, so an alert and the report it points to cannot be read under different
|
|
9
|
+
* rules (`specs/2026-09-16-when-an-agent-finishes.md` R10). A file that was named and cannot be read refuses the run
|
|
10
|
+
* rather than falling back to rules nobody chose.
|
|
11
|
+
*/
|
|
12
|
+
export async function choosePolicy(choice, files, tell) {
|
|
13
|
+
const file = choice.policyPath;
|
|
14
|
+
if (file !== undefined) {
|
|
15
|
+
const text = await readOrUndefined(file, files);
|
|
16
|
+
if (text === undefined)
|
|
17
|
+
return { errors: [`${file} could not be read`] };
|
|
18
|
+
// A policy file is the whole of what was chosen: nothing is added to it.
|
|
19
|
+
return resolvePolicy({ file: parsePolicy(text, file) });
|
|
20
|
+
}
|
|
21
|
+
const settingsPath = choice.settingsPath;
|
|
22
|
+
const text = settingsPath === undefined ? undefined : await readOrUndefined(settingsPath, files);
|
|
23
|
+
if (settingsPath !== undefined && text === undefined)
|
|
24
|
+
return { errors: [`${settingsPath} could not be read`] };
|
|
25
|
+
const rules = text === undefined ? undefined : readDenyRules(text);
|
|
26
|
+
// A settings file with no deny list is not a policy; it falls through to the default, which says so.
|
|
27
|
+
const resolved = resolvePolicy(rules === undefined || settingsPath === undefined ? {} : { settings: policyFromDenyRules(rules, settingsPath) });
|
|
28
|
+
if ('errors' in resolved || tell === undefined)
|
|
29
|
+
return resolved;
|
|
30
|
+
return { policy: withTold(resolved.policy, tellPatterns(await readTellLists(files, tell.pathsFor(settingsPath)))) };
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* F57: the files a person asked only to be told about, added as private and `tell`. A pattern the rules block stays
|
|
34
|
+
* blocked - both lists naming it is a file kept from the agent, which is the safer of the two - and one the built-in
|
|
35
|
+
* list blocks is told instead, since the built-in list is nobody's choice and the told list is somebody's.
|
|
36
|
+
*/
|
|
37
|
+
function withTold(policy, told) {
|
|
38
|
+
if (told.length === 0)
|
|
39
|
+
return policy;
|
|
40
|
+
const builtIn = policy.origin.kind === 'default';
|
|
41
|
+
const kept = policy.protected.filter((entry) => !(builtIn && told.includes(entry.pattern)));
|
|
42
|
+
const blocked = new Set(kept.map((entry) => entry.pattern));
|
|
43
|
+
return {
|
|
44
|
+
...policy,
|
|
45
|
+
protected: [...kept, ...told.filter((pattern) => !blocked.has(pattern)).map((pattern) => ({ pattern, mode: 'tell' }))],
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** The words a refused policy is reported in, the same for every command that reads one. */
|
|
49
|
+
export function policyRefusal(errors) {
|
|
50
|
+
return `${['The policy file was refused, so nothing was analysed:', ...errors.map((error) => ` - ${error}`)].join('\n')}\n`;
|
|
51
|
+
}
|
|
52
|
+
async function readOrUndefined(path, files) {
|
|
53
|
+
try {
|
|
54
|
+
return await files.readText(path);
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
if (!(error instanceof FileAccessError))
|
|
58
|
+
throw error;
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The files a reached step showed the agent the text of (`search-hits-are-reads` H5): every file it reached where a
|
|
3
|
+
* traced value came back - the result does not say which one it came from - and every file whose lines a search
|
|
4
|
+
* printed, which it does say. The one answer every reader of "read" asks, so the page, `check`, To fix and the terminal
|
|
5
|
+
* cannot drift apart.
|
|
6
|
+
*/
|
|
7
|
+
export function filesRead(step) {
|
|
8
|
+
const printed = (step.lines ?? []).map((line) => line.path);
|
|
9
|
+
return step.carriedValue ? [...new Set([...step.files, ...printed])] : printed;
|
|
10
|
+
}
|
|
11
|
+
/** Whether a reached step showed the agent the text of this file. */
|
|
12
|
+
export function readIn(step, path) {
|
|
13
|
+
return filesRead(step).some((file) => file === path);
|
|
14
|
+
}
|
|
15
|
+
/** How many lines of this file a search in the step printed, or undefined where none. */
|
|
16
|
+
export function linesOf(step, path) {
|
|
17
|
+
return step.lines?.find((line) => line.path === path)?.count;
|
|
18
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { dirname, join } from 'node:path';
|
|
2
|
+
import { projectDirectoryName } from '../../adapter/claude-code/contract/projects.js';
|
|
3
|
+
import { SETTINGS_FILES } from '../../adapter/claude-code/contract/settings.js';
|
|
4
|
+
import { FileAccessError } from '../../ports/file-access-error.js';
|
|
5
|
+
import { parseJsonObject } from '../../shared/json.js';
|
|
6
|
+
export const TELL_LIST_VERSION = 1;
|
|
7
|
+
export function tellListPaths(home, workingDirectory) {
|
|
8
|
+
return {
|
|
9
|
+
pathsFor: (settingsPath) => {
|
|
10
|
+
// A hook is given `$CLAUDE_PROJECT_DIR/.claude/settings.json`: the project is two directories up.
|
|
11
|
+
const project = settingsPath === undefined ? workingDirectory : dirname(dirname(settingsPath));
|
|
12
|
+
return {
|
|
13
|
+
shared: join(project, SETTINGS_FILES.directory, 'agentwhy.json'),
|
|
14
|
+
local: join(home, '.agentwhy', 'projects', projectDirectoryName(project), 'private-files.json'),
|
|
15
|
+
};
|
|
16
|
+
},
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* One list, read. A file that is not there is an empty list; one that cannot be understood is `unreadable`, and the
|
|
21
|
+
* patterns it may hold are not guessed at - the page says so rather than showing a list nobody wrote.
|
|
22
|
+
*/
|
|
23
|
+
export function readTellList(text) {
|
|
24
|
+
if (text === undefined)
|
|
25
|
+
return [];
|
|
26
|
+
const document = parseJsonObject(text);
|
|
27
|
+
if (document === undefined || document.version !== TELL_LIST_VERSION || !Array.isArray(document.tell))
|
|
28
|
+
return 'unreadable';
|
|
29
|
+
const patterns = document.tell.filter((pattern) => typeof pattern === 'string' && pattern.trim() !== '');
|
|
30
|
+
return patterns.length === document.tell.length ? [...new Set(patterns.map((pattern) => pattern.trim()))] : 'unreadable';
|
|
31
|
+
}
|
|
32
|
+
export function writtenTellList(patterns) {
|
|
33
|
+
return `${JSON.stringify({ version: TELL_LIST_VERSION, tell: [...new Set(patterns)].sort() }, undefined, 2)}\n`;
|
|
34
|
+
}
|
|
35
|
+
export async function readTellLists(files, paths) {
|
|
36
|
+
return { local: readTellList(await textOf(files, paths.local)), shared: readTellList(await textOf(files, paths.shared)) };
|
|
37
|
+
}
|
|
38
|
+
/** Every pattern of both lists that could be read: what the policy adds as `tell`. */
|
|
39
|
+
export function tellPatterns(read) {
|
|
40
|
+
return [...new Set([read.local, read.shared].flatMap((list) => (list === 'unreadable' ? [] : list)))];
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Writes the lists (F57). The page's switch goes through here and through nothing else, so there is one set of rules
|
|
44
|
+
* for these files; a list that cannot be read is not written over, since that would lose what someone put in it.
|
|
45
|
+
*/
|
|
46
|
+
export class TellLists {
|
|
47
|
+
#dependencies;
|
|
48
|
+
constructor(dependencies) {
|
|
49
|
+
this.#dependencies = dependencies;
|
|
50
|
+
}
|
|
51
|
+
async read() {
|
|
52
|
+
return readTellLists(this.#dependencies.files, this.#dependencies.paths.pathsFor(undefined));
|
|
53
|
+
}
|
|
54
|
+
/** Adds to one list and takes out of it; `false` where the list could not be read or written. */
|
|
55
|
+
async change(file, add, remove) {
|
|
56
|
+
const path = this.#dependencies.paths.pathsFor(undefined)[file];
|
|
57
|
+
const now = readTellList(await textOf(this.#dependencies.files, path));
|
|
58
|
+
if (now === 'unreadable')
|
|
59
|
+
return false;
|
|
60
|
+
const next = [...now.filter((pattern) => !remove.includes(pattern)), ...add.filter((pattern) => !remove.includes(pattern))];
|
|
61
|
+
if (next.length === now.length && next.every((pattern) => now.includes(pattern)))
|
|
62
|
+
return true;
|
|
63
|
+
try {
|
|
64
|
+
await this.#dependencies.writer.ensureDirectory(dirname(path));
|
|
65
|
+
await this.#dependencies.writer.writeText(path, writtenTellList(next));
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
catch (error) {
|
|
69
|
+
if (!(error instanceof FileAccessError))
|
|
70
|
+
throw error;
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
async function textOf(files, path) {
|
|
76
|
+
try {
|
|
77
|
+
return await files.readText(path);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
if (!(error instanceof FileAccessError))
|
|
81
|
+
throw error;
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { join } from 'node:path';
|
|
2
|
+
import { SETTINGS_FILES } from '../adapter/claude-code/contract/settings.js';
|
|
3
|
+
import { readDenyRules } from '../adapter/claude-code/policy/deny-rules.js';
|
|
4
|
+
import { FileAccessError } from '../ports/file-access-error.js';
|
|
5
|
+
import { parseJsonObject } from '../shared/json.js';
|
|
6
|
+
/**
|
|
7
|
+
* The patterns the project's own settings files deny - its local file and its committed one - read at the moment the
|
|
8
|
+
* report is written (the report page spec M3, P36). A file one of these matches is *Protected*: Claude Code refuses the
|
|
9
|
+
* tool before anything is opened. One matched only by the built-in list or a policy file is not.
|
|
10
|
+
*
|
|
11
|
+
* `undefined` where a settings file exists and cannot be read or parsed: what it denies is then not known, and the page
|
|
12
|
+
* says so rather than calling a file unprotected. A file that is absent denies nothing, which is an answer.
|
|
13
|
+
*/
|
|
14
|
+
export async function projectDenyRules(files, projectDirectory) {
|
|
15
|
+
const patterns = [];
|
|
16
|
+
for (const name of [SETTINGS_FILES.local, SETTINGS_FILES.shared]) {
|
|
17
|
+
let text;
|
|
18
|
+
try {
|
|
19
|
+
text = await files.readText(join(projectDirectory, SETTINGS_FILES.directory, name));
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
if (!(error instanceof FileAccessError))
|
|
23
|
+
throw error;
|
|
24
|
+
if (error.failure === 'not-found')
|
|
25
|
+
continue;
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
if (parseJsonObject(text) === undefined)
|
|
29
|
+
return undefined;
|
|
30
|
+
patterns.push(...(readDenyRules(text)?.patterns ?? []));
|
|
31
|
+
}
|
|
32
|
+
return [...new Set(patterns)];
|
|
33
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { isAbsolute, relative, resolve, sep } from 'node:path';
|
|
2
|
+
/**
|
|
3
|
+
* Where a folder is, as a person reads it (`.ai/specs/2026-09-27-which-project.md` V1): `~/Projects/shop` under the home
|
|
4
|
+
* directory, and the path as it is anywhere else. The home directory itself is `~`. `relative` decides what lies under
|
|
5
|
+
* home, so a folder whose name merely begins like it (`/srv/home-else` beside `/srv/home`) is not shortened, and a Windows path is
|
|
6
|
+
* compared as its file system compares it. An empty home names no directory, so nothing is shortened.
|
|
7
|
+
*/
|
|
8
|
+
export function homeRelative(path, home) {
|
|
9
|
+
if (home === '')
|
|
10
|
+
return path;
|
|
11
|
+
const inside = relative(resolve(home), resolve(path));
|
|
12
|
+
if (inside === '')
|
|
13
|
+
return '~';
|
|
14
|
+
// `..` or `../…` climbs out of home; a folder of home whose own name begins with two dots does not.
|
|
15
|
+
if (inside === '..' || inside.startsWith(`..${sep}`) || isAbsolute(inside))
|
|
16
|
+
return path;
|
|
17
|
+
return `~${sep}${inside}`;
|
|
18
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The policy every page this tool writes is served under - by nothing, since a page is opened as a file. It says
|
|
3
|
+
* that the page may run its own inline script and style and may reach **nowhere**: no fetch, no image but a `data:` one, no form
|
|
4
|
+
* submission, no base to redirect relative links through (§7.5 hard constraint 1).
|
|
5
|
+
*
|
|
6
|
+
* One constant, because two pages carrying two copies of a security header is how one of them ends up weaker.
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* The index's policy: the same, except that it may reach the origin it was loaded from. Opened as a file that is
|
|
10
|
+
* nothing; served by `start` it is the loopback server a mark is sent to (`worth-running-every-day` R53).
|
|
11
|
+
*/
|
|
12
|
+
export const INDEX_CONTENT_SECURITY_POLICY_META = '<meta http-equiv="Content-Security-Policy" content="default-src \'none\'; script-src \'unsafe-inline\'; ' +
|
|
13
|
+
'style-src \'unsafe-inline\'; img-src data:; connect-src \'self\'; base-uri \'none\'; form-action \'none\'">';
|
|
14
|
+
export const CONTENT_SECURITY_POLICY_META = '<meta http-equiv="Content-Security-Policy" content="default-src \'none\'; script-src \'unsafe-inline\'; ' +
|
|
15
|
+
'style-src \'unsafe-inline\'; img-src data:; connect-src \'none\'; base-uri \'none\'; form-action \'none\'">';
|
|
16
|
+
/**
|
|
17
|
+
* The tab's icon: the mark both pages carry beside the wordmark, in the accent on the page's own background, drawn
|
|
18
|
+
* as a `data:` URI so the file stays one file. The policies above allow `img-src data:` for exactly this - a `data:`
|
|
19
|
+
* image is the page's own bytes and reaches nowhere - because a browser that applies `default-src 'none'` to the
|
|
20
|
+
* icon would otherwise leave the tab blank.
|
|
21
|
+
*/
|
|
22
|
+
const FAVICON_SVG = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="-3 -2 46 44"><rect x="-3" y="-2" width="46" height="44" rx="10" fill="#0d0e11"/>' +
|
|
23
|
+
'<g fill="none" stroke="#ff997f" stroke-width="6" stroke-linecap="round">' +
|
|
24
|
+
'<path d="m17 25-3 3a7.07 7.07 0 0 1-10-10l8-8a7.07 7.07 0 0 1 10 0"/>' +
|
|
25
|
+
'<path d="m23 15 3-3a7.07 7.07 0 0 1 10 10l-8 8a7.07 7.07 0 0 1-10 0"/></g></svg>';
|
|
26
|
+
export const FAVICON_LINK = '<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,' + encodeURIComponent(FAVICON_SVG) + '">';
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { inLanguages, labelAttributes } from './report-copy.js';
|
|
2
|
+
export function escapeHtml(value) {
|
|
3
|
+
return value.replace(/[&<>"']/g, (character) => ({
|
|
4
|
+
'&': '&', '<': '<', '>': '>', '"': '"', "'": ''',
|
|
5
|
+
})[character] ?? character);
|
|
6
|
+
}
|
|
7
|
+
const OUTCOME_SYMBOL = { succeeded: '!', blocked: '✓', unknown: '?' };
|
|
8
|
+
const EXPAND_GLYPH = '<svg viewBox="0 0 16 16" aria-hidden="true" width="14" height="14"><path d="M9.5 1.5h5v5M14.5 1.5 9 7' +
|
|
9
|
+
'M6.5 14.5h-5v-5M1.5 14.5 7 9" fill="none" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"' +
|
|
10
|
+
' stroke-linejoin="round"/></svg>';
|
|
11
|
+
/**
|
|
12
|
+
* A table, with a way to give it the width of the page. The button is an enhancement and says so: with no
|
|
13
|
+
* script the container still scrolls, which is the whole behaviour a reader needs. The dialog only helps where
|
|
14
|
+
* scrolling a column at a time is the thing that makes a table hard to read.
|
|
15
|
+
*/
|
|
16
|
+
export function scrollTable(table) {
|
|
17
|
+
return '<div class="table-wrap"><button type="button" class="table-open js-only" data-table-open' +
|
|
18
|
+
labelAttributes((t) => t('table.expand')) + '>' + EXPAND_GLYPH + '</button>' +
|
|
19
|
+
'<div class="table-scroll">' + table + '</div></div>';
|
|
20
|
+
}
|
|
21
|
+
export function badge(outcome) {
|
|
22
|
+
return '<span class="badge ' + outcome + '"><span aria-hidden="true">' + OUTCOME_SYMBOL[outcome] + '</span> ' +
|
|
23
|
+
inLanguages((t) => t('outcome.' + outcome)) + '</span>';
|
|
24
|
+
}
|
|
25
|
+
export function agentName(agent, report) {
|
|
26
|
+
return agent.ordinal === undefined ? 'Main agent' : 'Agent ' + agent.ordinal;
|
|
27
|
+
}
|
|
28
|
+
export function actionsFor(agent, report) {
|
|
29
|
+
if (agent.index === report.graph.main.index)
|
|
30
|
+
return report.mainAgent;
|
|
31
|
+
return report.delegations.find((entry) => entry.childAgentIndex === agent.index)?.actions ??
|
|
32
|
+
report.unattributedAgents.find((entry) => entry.agentIndex === agent.index)?.actions ?? [];
|
|
33
|
+
}
|
|
34
|
+
export function actionTable(actions, prefix) {
|
|
35
|
+
const heading = (key) => '<th scope="col">' + inLanguages((t) => t(key)) + '</th>';
|
|
36
|
+
if (actions.length === 0)
|
|
37
|
+
return '<p class="empty-note">' + inLanguages((t) => t('table.empty')) + '</p>';
|
|
38
|
+
return scrollTable('<table><caption>' + inLanguages((t) => t('table.caption')) + '</caption><thead><tr>' +
|
|
39
|
+
heading('table.step') + heading('table.tool') + heading('table.path') + heading('table.outcome') + heading('table.evidence') +
|
|
40
|
+
'</tr></thead><tbody>' + actions.map((action, index) => '<tr id="' + prefix + '-action-' + index + '" class="' + action.outcome + '"><td>' + action.sequence +
|
|
41
|
+
'</td><td>' + escapeHtml(action.tool) + '</td><td>' +
|
|
42
|
+
(action.target === undefined ? '<span class="muted">' + inLanguages((t) => t('table.notDetected')) + '</span>'
|
|
43
|
+
: '<code>' + escapeHtml(action.target) + '</code>') +
|
|
44
|
+
'</td><td>' + (action.outcome === 'succeeded' ? '<span class="neutral-outcome">' + inLanguages((t) => t('outcome.succeeded')) + '</span>' : badge(action.outcome)) +
|
|
45
|
+
'</td><td><code>' + escapeHtml(action.evidence) + '</code></td></tr>').join('') + '</tbody></table>');
|
|
46
|
+
}
|
|
47
|
+
/** The agent's own name is the tool's vocabulary, not the transcript's, so it is written per language. */
|
|
48
|
+
export const agentLabel = (t, name) => (name === 'Main agent' ? t('col.main') : name);
|