avorelo 0.3.8 → 0.4.0-rc.2
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 +16 -23
- package/README.md +62 -72
- package/bin/avorelo +9 -0
- package/package.json +147 -139
- package/scripts/README.md +40 -0
- package/scripts/avorelo-session-collision-guard.js +540 -0
- package/scripts/avorelo-workstream-preflight.js +918 -0
- package/scripts/cco-dashboard.js +346 -0
- package/scripts/cco-posttooluse.js +504 -0
- package/scripts/cco-pretooluse.js +381 -0
- package/scripts/cco-status.js +540 -0
- package/scripts/cco-stop.js +77 -0
- package/scripts/cco-userprompt-submit.js +83 -0
- package/scripts/lib/activation/account-state.js +37 -0
- package/scripts/lib/activation/activate-command.js +114 -0
- package/scripts/lib/activation/activation-runner.js +607 -0
- package/scripts/lib/activation/activation-self-healing.js +481 -0
- package/scripts/lib/activation/activation-state.js +96 -0
- package/scripts/lib/activation/activation-summary.js +241 -0
- package/scripts/lib/activation/adapters/claude-code.js +77 -0
- package/scripts/lib/activation/adapters/codex-cli.js +52 -0
- package/scripts/lib/activation/adapters/cursor.js +37 -0
- package/scripts/lib/activation/adapters/github-agent.js +39 -0
- package/scripts/lib/activation/adapters/terminal.js +42 -0
- package/scripts/lib/activation/adapters/vscode.js +39 -0
- package/scripts/lib/activation/adapters/windsurf.js +37 -0
- package/scripts/lib/activation/ai-surface-detector.js +151 -0
- package/scripts/lib/activation/connect-account.js +145 -0
- package/scripts/lib/activation/detect-environment.js +75 -0
- package/scripts/lib/activation/detect-hosts.js +62 -0
- package/scripts/lib/activation/format-activation-output.js +109 -0
- package/scripts/lib/activation/next-action.js +43 -0
- package/scripts/lib/activation/repair-engine.js +219 -0
- package/scripts/lib/activation/run-entry.js +346 -0
- package/scripts/lib/activation-distribution-readiness.js +507 -0
- package/scripts/lib/adapter-conformance.js +176 -0
- package/scripts/lib/adapter-readiness.js +428 -0
- package/scripts/lib/adapter-safety-boundaries.js +335 -0
- package/scripts/lib/adapter-technical-readiness-gate.js +205 -0
- package/scripts/lib/agent-access-governance.js +455 -0
- package/scripts/lib/agent-activation-readiness.js +878 -0
- package/scripts/lib/agent-enforcement.js +765 -0
- package/scripts/lib/agent-evolution-loop.js +479 -0
- package/scripts/lib/agent-policy-profile.js +210 -0
- package/scripts/lib/agent-security/action-evaluator.js +507 -0
- package/scripts/lib/agent-security/adapter-registry.js +98 -0
- package/scripts/lib/agent-security/auto-policy.js +139 -0
- package/scripts/lib/agent-security/bounded-scan.js +93 -0
- package/scripts/lib/agent-security/enforcement-adapter.js +174 -0
- package/scripts/lib/agent-security/enforcement-engine.js +1129 -0
- package/scripts/lib/agent-security/file-write-adapter.js +183 -0
- package/scripts/lib/agent-security/file-write-rules.js +178 -0
- package/scripts/lib/agent-security/index.js +3342 -0
- package/scripts/lib/agent-security/instruction-risk.js +181 -0
- package/scripts/lib/agent-security/mcp-action-adapter.js +185 -0
- package/scripts/lib/agent-security/mcp-action-rules.js +184 -0
- package/scripts/lib/agent-security/package-action-adapter.js +175 -0
- package/scripts/lib/agent-security/package-action-rules.js +233 -0
- package/scripts/lib/agent-security/performance.js +148 -0
- package/scripts/lib/agent-security/permission-minimizer.js +403 -0
- package/scripts/lib/agent-security/scan-cache.js +74 -0
- package/scripts/lib/agent-security/source-trust.js +146 -0
- package/scripts/lib/ai-efficiency-dogfood-pack.js +437 -0
- package/scripts/lib/ai-efficiency-eval.js +207 -0
- package/scripts/lib/ai-efficiency-guard-contract.js +209 -0
- package/scripts/lib/ai-efficiency-measured-value.js +106 -0
- package/scripts/lib/ai-install-prompt.js +293 -0
- package/scripts/lib/ai-task-inference.js +155 -0
- package/scripts/lib/ai-value-ledger.js +121 -0
- package/scripts/lib/ai-value-proof-table.js +151 -0
- package/scripts/lib/ai-work-efficiency-routing.js +623 -0
- package/scripts/lib/ai-workspace-hygiene.js +1499 -0
- package/scripts/lib/alpha-activation.js +520 -0
- package/scripts/lib/alpha-feedback.js +263 -0
- package/scripts/lib/alpha-readiness-gate.js +332 -0
- package/scripts/lib/anti-gaming.js +169 -0
- package/scripts/lib/artifact-health.js +431 -0
- package/scripts/lib/artifact-receipt-hygiene.js +619 -0
- package/scripts/lib/attribution.js +180 -0
- package/scripts/lib/audit.js +289 -0
- package/scripts/lib/avorelo-skill-registry.js +810 -0
- package/scripts/lib/batch-jobs.js +71 -0
- package/scripts/lib/brain-pack.js +578 -0
- package/scripts/lib/brand-boundary.js +424 -0
- package/scripts/lib/brand.js +74 -0
- package/scripts/lib/browser-capability.js +1048 -0
- package/scripts/lib/browser-proof-governed-workflow.js +1020 -0
- package/scripts/lib/browser-proof-preflight.js +321 -0
- package/scripts/lib/cache-readiness.js +296 -0
- package/scripts/lib/canonical-reentry.js +162 -0
- package/scripts/lib/capability-contract-validator.js +177 -0
- package/scripts/lib/capability-hygiene.js +504 -0
- package/scripts/lib/capability-intake-quarantine.js +789 -0
- package/scripts/lib/capability-kernel-registry.js +566 -0
- package/scripts/lib/capability-kernel.js +202 -0
- package/scripts/lib/capability-manifest.js +255 -0
- package/scripts/lib/capability-outcome-normalizer.js +104 -0
- package/scripts/lib/capability-packs.js +314 -0
- package/scripts/lib/capability-recommender.js +513 -0
- package/scripts/lib/capability-registry.js +1062 -0
- package/scripts/lib/carry-forward-surfacing.js +194 -0
- package/scripts/lib/ccusage-adapter.js +188 -0
- package/scripts/lib/claude-session-evidence/index.js +1027 -0
- package/scripts/lib/cli-output-sanitizer.js +118 -0
- package/scripts/lib/company-loop.js +1149 -0
- package/scripts/lib/config.js +641 -0
- package/scripts/lib/context-acquisition-plan.js +359 -0
- package/scripts/lib/context-authority-rules.js +138 -0
- package/scripts/lib/context-budget-guard.js +170 -0
- package/scripts/lib/context-budget-scanner.js +257 -0
- package/scripts/lib/context-budget.js +167 -0
- package/scripts/lib/context-claim-safety.js +116 -0
- package/scripts/lib/context-compiler.js +236 -0
- package/scripts/lib/context-optimizer.js +715 -0
- package/scripts/lib/context-packet.js +121 -0
- package/scripts/lib/context-receipt.js +57 -0
- package/scripts/lib/context-reduction-plan.js +179 -0
- package/scripts/lib/context-safety.js +88 -0
- package/scripts/lib/context-savings-engine.js +158 -0
- package/scripts/lib/context-source-registry.js +393 -0
- package/scripts/lib/context-task-profiles.js +147 -0
- package/scripts/lib/cost-abuse-guard.js +975 -0
- package/scripts/lib/cost-evidence.js +287 -0
- package/scripts/lib/cost-to-outcome-signals.js +639 -0
- package/scripts/lib/cross-host-install-plan.js +308 -0
- package/scripts/lib/cross-host-install-readiness.js +242 -0
- package/scripts/lib/cross-host-value-flow.js +268 -0
- package/scripts/lib/dashboard.js +1056 -0
- package/scripts/lib/data-permissions-guard.js +1187 -0
- package/scripts/lib/deployment-environment-guard.js +1236 -0
- package/scripts/lib/design-partner-feedback.js +346 -0
- package/scripts/lib/dogfood-evidence-freshness.js +567 -0
- package/scripts/lib/efficiency-receipt.js +354 -0
- package/scripts/lib/entitlements.js +100 -0
- package/scripts/lib/execution-packet.js +559 -0
- package/scripts/lib/execution-route-governor.js +179 -0
- package/scripts/lib/experimentation-events.js +547 -0
- package/scripts/lib/external-capability-compliance.js +107 -0
- package/scripts/lib/external-readiness/ai-visibility/ai-visibility-runner.js +322 -0
- package/scripts/lib/external-readiness/calibration/benchmark-site-catalog.js +121 -0
- package/scripts/lib/external-readiness/calibration/completion-gate.js +112 -0
- package/scripts/lib/external-readiness/calibration/real-site-benchmark-runner.js +284 -0
- package/scripts/lib/external-readiness/drift-detector.js +100 -0
- package/scripts/lib/external-readiness/endpoint-prober.js +332 -0
- package/scripts/lib/external-readiness/external-readiness-runner.js +112 -0
- package/scripts/lib/external-readiness/full-agent-readiness-scorer.js +220 -0
- package/scripts/lib/external-readiness/golden-path-runner.js +333 -0
- package/scripts/lib/external-readiness/inside-out-runner.js +118 -0
- package/scripts/lib/external-readiness/proof-receipt-writer.js +112 -0
- package/scripts/lib/external-readiness/safe-fix-executor.js +117 -0
- package/scripts/lib/external-readiness/skills/category-boundary-guard.js +30 -0
- package/scripts/lib/external-readiness/skills/pmm-positioning-guard.js +69 -0
- package/scripts/lib/external-readiness/skills/safe-fix-policy.js +35 -0
- package/scripts/lib/external-user-simulation.js +166 -0
- package/scripts/lib/failure-recovery-readiness.js +81 -0
- package/scripts/lib/failure-recovery.js +419 -0
- package/scripts/lib/feedback-intelligence.js +537 -0
- package/scripts/lib/feedback-signals.js +205 -0
- package/scripts/lib/file-integrity.js +68 -0
- package/scripts/lib/fsx.js +129 -0
- package/scripts/lib/full-readiness-gate.js +451 -0
- package/scripts/lib/governed-action-exposure-primitives.js +1514 -0
- package/scripts/lib/governed-exposure-planner.js +186 -0
- package/scripts/lib/governed-workspace-hygiene.js +946 -0
- package/scripts/lib/guard-suite-common.js +412 -0
- package/scripts/lib/guidance-builder.js +174 -0
- package/scripts/lib/hook-apply.js +1075 -0
- package/scripts/lib/hook-baseline.js +310 -0
- package/scripts/lib/hook-config-preview.js +275 -0
- package/scripts/lib/hook-contracts.js +290 -0
- package/scripts/lib/hook-safety-boundary-readiness.js +80 -0
- package/scripts/lib/host-capability-matrix.js +351 -0
- package/scripts/lib/host-support-context.js +254 -0
- package/scripts/lib/http-hook-action.js +538 -0
- package/scripts/lib/install-ai-readiness.js +84 -0
- package/scripts/lib/install-intake-risk.js +1051 -0
- package/scripts/lib/install-journey-intelligence.js +329 -0
- package/scripts/lib/intervention-guidance.js +57 -0
- package/scripts/lib/known-limitations.js +115 -0
- package/scripts/lib/l8-path-truth.js +146 -0
- package/scripts/lib/launch-hardening-gate.js +436 -0
- package/scripts/lib/launch-readiness.js +635 -0
- package/scripts/lib/learning-experimentation-foundation.js +1484 -0
- package/scripts/lib/learning-memory.js +686 -0
- package/scripts/lib/lifecycle-hooks.js +982 -0
- package/scripts/lib/local-package-smoke.js +436 -0
- package/scripts/lib/local-pricing.js +299 -0
- package/scripts/lib/mcp-enforcement.js +311 -0
- package/scripts/lib/mcp-least-privilege-policy.js +303 -0
- package/scripts/lib/mcp-tool-inventory.js +388 -0
- package/scripts/lib/mcp-tool-risk.js +0 -0
- package/scripts/lib/memory.js +335 -0
- package/scripts/lib/metrics.js +699 -0
- package/scripts/lib/micro-proof.js +133 -0
- package/scripts/lib/model-work-class-routing.js +79 -0
- package/scripts/lib/next-run-context.js +436 -0
- package/scripts/lib/next-run-continuity.js +1207 -0
- package/scripts/lib/observability-recovery-guard.js +1030 -0
- package/scripts/lib/operating-value.js +1662 -0
- package/scripts/lib/optimization-v3.js +122 -0
- package/scripts/lib/orchestration/adapters/_shared.js +49 -0
- package/scripts/lib/orchestration/adapters/aider.js +18 -0
- package/scripts/lib/orchestration/adapters/claude-code.js +35 -0
- package/scripts/lib/orchestration/adapters/codex.js +35 -0
- package/scripts/lib/orchestration/adapters/gemini-cli.js +18 -0
- package/scripts/lib/orchestration/adapters/git.js +25 -0
- package/scripts/lib/orchestration/adapters/index.js +31 -0
- package/scripts/lib/orchestration/adapters/lm-studio.js +18 -0
- package/scripts/lib/orchestration/adapters/ollama.js +18 -0
- package/scripts/lib/orchestration/adapters/opencode.js +18 -0
- package/scripts/lib/orchestration/adapters/openrouter.js +18 -0
- package/scripts/lib/orchestration/adapters/test-runner.js +25 -0
- package/scripts/lib/orchestration/cli.js +438 -0
- package/scripts/lib/orchestration/execution-manager.js +279 -0
- package/scripts/lib/orchestration/handoff.js +314 -0
- package/scripts/lib/orchestration/index.js +456 -0
- package/scripts/lib/orchestration/inventory.js +47 -0
- package/scripts/lib/orchestration/model-discovery.js +498 -0
- package/scripts/lib/orchestration/model-profiler.js +170 -0
- package/scripts/lib/orchestration/model-profiles.js +252 -0
- package/scripts/lib/orchestration/model-refresh-policy.js +72 -0
- package/scripts/lib/orchestration/proof-writer.js +349 -0
- package/scripts/lib/orchestration/provider-discovery/aider.js +49 -0
- package/scripts/lib/orchestration/provider-discovery/claude-code.js +56 -0
- package/scripts/lib/orchestration/provider-discovery/codex.js +49 -0
- package/scripts/lib/orchestration/provider-discovery/common.js +186 -0
- package/scripts/lib/orchestration/provider-discovery/gemini.js +106 -0
- package/scripts/lib/orchestration/provider-discovery/lm-studio.js +118 -0
- package/scripts/lib/orchestration/provider-discovery/models-dev.js +12 -0
- package/scripts/lib/orchestration/provider-discovery/ollama.js +100 -0
- package/scripts/lib/orchestration/provider-discovery/opencode.js +47 -0
- package/scripts/lib/orchestration/provider-discovery/openrouter.js +44 -0
- package/scripts/lib/orchestration/risk-classifier.js +130 -0
- package/scripts/lib/orchestration/routing-policy.js +486 -0
- package/scripts/lib/orchestration/settings.js +95 -0
- package/scripts/lib/orchestration/state.js +165 -0
- package/scripts/lib/orchestration/verification-manager.js +138 -0
- package/scripts/lib/outcome-evidence-stack.js +1083 -0
- package/scripts/lib/output-profiles.js +146 -0
- package/scripts/lib/package-content-audit.js +368 -0
- package/scripts/lib/package-runtime.js +278 -0
- package/scripts/lib/plan-surface.js +53 -0
- package/scripts/lib/plans.js +2327 -0
- package/scripts/lib/policy-provider.js +27 -0
- package/scripts/lib/prelaunch-activation-readiness.js +409 -0
- package/scripts/lib/prelaunch-evidence-store.js +816 -0
- package/scripts/lib/prelaunch-intelligence.js +869 -0
- package/scripts/lib/pricing-experiment.js +118 -0
- package/scripts/lib/pro-moment-events.js +77 -0
- package/scripts/lib/pro-moment-state.js +227 -0
- package/scripts/lib/pro-moments.js +1216 -0
- package/scripts/lib/product-home-receipt.js +1790 -0
- package/scripts/lib/product-learning-events.js +696 -0
- package/scripts/lib/production-confidence-control-tower.js +1209 -0
- package/scripts/lib/production-confidence.js +1858 -0
- package/scripts/lib/project-profile.js +555 -0
- package/scripts/lib/prompt-compiler-plan.js +94 -0
- package/scripts/lib/prompt-compiler.js +280 -0
- package/scripts/lib/prompt-lint.js +32 -0
- package/scripts/lib/prompt-suggestions.js +52 -0
- package/scripts/lib/proof-canonical.js +398 -0
- package/scripts/lib/proof-drilldown.js +383 -0
- package/scripts/lib/proof-e2e/promise-completion.js +141 -0
- package/scripts/lib/proof-e2e/promise-dashboard-claims.js +135 -0
- package/scripts/lib/proof-e2e/promise-local-proof.js +156 -0
- package/scripts/lib/proof-e2e/promise-provider-neutral.js +136 -0
- package/scripts/lib/proof-e2e/promise-registry.js +219 -0
- package/scripts/lib/proof-e2e/promise-security-trust.js +141 -0
- package/scripts/lib/proof-e2e/promise-skills-risk.js +163 -0
- package/scripts/lib/proof-e2e/promise-token-savings.js +112 -0
- package/scripts/lib/proof-e2e/promise-work-management.js +126 -0
- package/scripts/lib/proof-events.js +342 -0
- package/scripts/lib/proof-history.js +243 -0
- package/scripts/lib/proof-metrics.js +296 -0
- package/scripts/lib/proof-outcome-evidence.js +134 -0
- package/scripts/lib/proof-receipt.js +335 -0
- package/scripts/lib/proof-record.js +461 -0
- package/scripts/lib/proof-savings-report.js +1549 -0
- package/scripts/lib/public-activation-distribution-gate.js +266 -0
- package/scripts/lib/public-cli.js +5631 -0
- package/scripts/lib/public-distribution-truth.js +211 -0
- package/scripts/lib/public-distribution.js +73 -0
- package/scripts/lib/public-install-claim-checker.js +300 -0
- package/scripts/lib/publish-provenance-readiness.js +287 -0
- package/scripts/lib/readiness-delta.js +218 -0
- package/scripts/lib/readiness-evidence-closure.js +196 -0
- package/scripts/lib/reentry-memory-capture.js +241 -0
- package/scripts/lib/reentry-memory-retrieval.js +302 -0
- package/scripts/lib/reentry-memory-status.js +147 -0
- package/scripts/lib/reentry-memory-store.js +183 -0
- package/scripts/lib/reentry-state.js +66 -0
- package/scripts/lib/release-candidate-bundle.js +166 -0
- package/scripts/lib/remediation.js +81 -0
- package/scripts/lib/repo-map.js +391 -0
- package/scripts/lib/run-action-policy.js +104 -0
- package/scripts/lib/run-contract-receipt.js +48 -0
- package/scripts/lib/run-contract.js +288 -0
- package/scripts/lib/run-cost-to-verified-outcome.js +87 -0
- package/scripts/lib/run-improvements-lifecycle.js +330 -0
- package/scripts/lib/run-improvements.js +789 -0
- package/scripts/lib/run-proof-contract.js +116 -0
- package/scripts/lib/run-risk-classifier.js +131 -0
- package/scripts/lib/run-task-classifier.js +216 -0
- package/scripts/lib/runtime-decision-policy.js +387 -0
- package/scripts/lib/safe-path-engine.js +705 -0
- package/scripts/lib/safe-repair.js +877 -0
- package/scripts/lib/safe-run-controller.js +887 -0
- package/scripts/lib/scope-repair-before-approval.js +865 -0
- package/scripts/lib/score.js +262 -0
- package/scripts/lib/seamless-enforcement.js +329 -0
- package/scripts/lib/seamless-outcome.js +689 -0
- package/scripts/lib/seamless-reality-gate.js +5053 -0
- package/scripts/lib/security-confidence/activation-safety.js +78 -0
- package/scripts/lib/security-confidence/adapters/adapter-contract.js +125 -0
- package/scripts/lib/security-confidence/adapters/mock-claude-bughunter-adapter.js +36 -0
- package/scripts/lib/security-confidence/adapters/mock-decepticon-adapter.js +34 -0
- package/scripts/lib/security-confidence/adapters/mock-mcp-scan-adapter.js +35 -0
- package/scripts/lib/security-confidence/adapters/mock-snyk-agent-scan-adapter.js +35 -0
- package/scripts/lib/security-confidence/adapters/mock-tank-adapter.js +34 -0
- package/scripts/lib/security-confidence/adapters/reference-registry.js +63 -0
- package/scripts/lib/security-confidence/auth-rbac-plan.js +39 -0
- package/scripts/lib/security-confidence/component-inventory.js +151 -0
- package/scripts/lib/security-confidence/constants.js +191 -0
- package/scripts/lib/security-confidence/dependency-script-risk.js +95 -0
- package/scripts/lib/security-confidence/evidence-hygiene.js +127 -0
- package/scripts/lib/security-confidence/feature-impact-classifier.js +80 -0
- package/scripts/lib/security-confidence/file-utils.js +161 -0
- package/scripts/lib/security-confidence/finding-triage.js +90 -0
- package/scripts/lib/security-confidence/findings.js +115 -0
- package/scripts/lib/security-confidence/index.js +29 -0
- package/scripts/lib/security-confidence/permission-budget.js +93 -0
- package/scripts/lib/security-confidence/redaction.js +82 -0
- package/scripts/lib/security-confidence/redteam-fixtures.js +53 -0
- package/scripts/lib/security-confidence/reference-adoption.js +229 -0
- package/scripts/lib/security-confidence/reference-taxonomy.js +155 -0
- package/scripts/lib/security-confidence/report-quality.js +99 -0
- package/scripts/lib/security-confidence/report-writer.js +79 -0
- package/scripts/lib/security-confidence/risk-classifier.js +187 -0
- package/scripts/lib/security-confidence/runner.js +179 -0
- package/scripts/lib/security-confidence/schema.js +188 -0
- package/scripts/lib/security-confidence/secret-safety.js +74 -0
- package/scripts/lib/security-confidence/security-capability-router.js +98 -0
- package/scripts/lib/security-confidence/web-security-smoke-plan.js +46 -0
- package/scripts/lib/security-risk-classifier.js +511 -0
- package/scripts/lib/security-scan.js +396 -0
- package/scripts/lib/session-context-optimizer.js +1211 -0
- package/scripts/lib/session-timing.js +322 -0
- package/scripts/lib/skill-hygiene.js +805 -0
- package/scripts/lib/skill-packs.js +161 -0
- package/scripts/lib/skills-operating-layer-v1.js +1023 -0
- package/scripts/lib/skills-operating-layer.js +629 -0
- package/scripts/lib/smallest-safe-plan.js +214 -0
- package/scripts/lib/smart-routing-plan.js +115 -0
- package/scripts/lib/smart-work-routing.js +768 -0
- package/scripts/lib/source-catalog.js +1022 -0
- package/scripts/lib/status-value-summary.js +32 -0
- package/scripts/lib/supply-chain-dependency-guard.js +1333 -0
- package/scripts/lib/supply-chain-guard.js +219 -0
- package/scripts/lib/support-bundle.js +561 -0
- package/scripts/lib/support-redaction.js +66 -0
- package/scripts/lib/task-continuation.js +440 -0
- package/scripts/lib/test-helpers.js +15 -0
- package/scripts/lib/tier.js +38 -0
- package/scripts/lib/token-context-quality-gate.js +370 -0
- package/scripts/lib/token-cost-capture.js +187 -0
- package/scripts/lib/token-cost-evidence.js +862 -0
- package/scripts/lib/token-cost-intelligence.js +358 -0
- package/scripts/lib/token-efficiency-evidence.js +213 -0
- package/scripts/lib/token-efficiency-map.js +110 -0
- package/scripts/lib/token-evidence.js +703 -0
- package/scripts/lib/tokenish.js +17 -0
- package/scripts/lib/tool-output-sandbox.js +559 -0
- package/scripts/lib/trust-audit.js +136 -0
- package/scripts/lib/unified-events.js +396 -0
- package/scripts/lib/upgrade-interruption-recovery.js +407 -0
- package/scripts/lib/usage-ledger.js +201 -0
- package/scripts/lib/value-ledger.js +130 -0
- package/scripts/lib/value-proof-calibration.js +531 -0
- package/scripts/lib/visual-qa.js +231 -0
- package/scripts/lib/voice-alpha.js +29 -0
- package/scripts/lib/webhook-idempotency-guard.js +990 -0
- package/scripts/lib/work-aware-orchestration.js +976 -0
- package/scripts/lib/work-control-receipts.js +577 -0
- package/scripts/lib/work-ledger.js +1123 -0
- package/scripts/lib/work-panel-preview.js +352 -0
- package/scripts/lib/workflow-discipline.js +280 -0
- package/scripts/lib/workflow-intelligence-radar.js +804 -0
- package/scripts/lib/workflow-signals.js +419 -0
- package/scripts/lib/workspace-map.js +281 -0
- package/scripts/lib/workspace-registry.js +1367 -0
- package/scripts/lib/workspace-resolver.js +542 -0
- package/scripts/wuz-doctor.js +315 -0
- package/bin/avorelo.mjs +0 -7
- package/dist/avorelo.mjs +0 -24678
|
@@ -0,0 +1,1027 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const os = require("os");
|
|
6
|
+
const { redactString } = require("../support-redaction");
|
|
7
|
+
|
|
8
|
+
// --- Constants ---
|
|
9
|
+
|
|
10
|
+
const CLAUDE_HOME = path.join(os.homedir(), ".claude");
|
|
11
|
+
const SESSIONS_DIR = path.join(CLAUDE_HOME, "sessions");
|
|
12
|
+
const PROJECTS_DIR = path.join(CLAUDE_HOME, "projects");
|
|
13
|
+
|
|
14
|
+
const SECRET_PATTERNS = [
|
|
15
|
+
{ re: /ghp_[A-Za-z0-9]{20,}/, code: "SEC_GH_TOKEN" },
|
|
16
|
+
{ re: /gho_[A-Za-z0-9]{20,}/, code: "SEC_GH_OAUTH" },
|
|
17
|
+
{ re: /github_pat_[A-Za-z0-9_]{20,}/, code: "SEC_GH_PAT" },
|
|
18
|
+
{ re: /xoxb-[A-Za-z0-9\-]{20,}/, code: "SEC_SLACK_TOKEN" },
|
|
19
|
+
{ re: /xoxp-[A-Za-z0-9\-]{20,}/, code: "SEC_SLACK_USER_TOKEN" },
|
|
20
|
+
{ re: /AKIA[0-9A-Z]{16}/, code: "SEC_AWS_KEY" },
|
|
21
|
+
{ re: /sk-[A-Za-z0-9]{20,}/, code: "SEC_OPENAI_KEY" },
|
|
22
|
+
{ re: /sk-ant-[A-Za-z0-9\-]{20,}/, code: "SEC_ANTHROPIC_KEY" },
|
|
23
|
+
{ re: /BEGIN (?:RSA |EC |DSA |OPENSSH )?PRIVATE KEY/, code: "SEC_PRIVATE_KEY" },
|
|
24
|
+
{ re: /eyJ[A-Za-z0-9_-]{30,}\.[A-Za-z0-9_-]{30,}/, code: "SEC_JWT" },
|
|
25
|
+
{ re: /(?:password|passwd|pwd)\s*[:=]\s*["'][^"']{4,}/i, code: "SEC_PASSWORD_LITERAL" },
|
|
26
|
+
{ re: /(?:api[_-]?key|apikey)\s*[:=]\s*["'][^"']{8,}/i, code: "SEC_API_KEY_LITERAL" },
|
|
27
|
+
];
|
|
28
|
+
|
|
29
|
+
const FILE_TOUCHING_TOOLS = new Set(["Read", "Write", "Edit", "NotebookEdit"]);
|
|
30
|
+
const COMMAND_TOOLS = new Set(["Bash", "PowerShell"]);
|
|
31
|
+
const VALIDATION_COMMANDS = [
|
|
32
|
+
/\btest\b/i, /\bvitest\b/i, /\bjest\b/i, /\bpytest\b/i,
|
|
33
|
+
/\bnpm\s+test\b/i, /\bnpm\s+run\s+test/i, /\bnode\s+--test\b/i,
|
|
34
|
+
/\btsc\b/, /\btypecheck\b/i, /\blint\b/i, /\beslint\b/i,
|
|
35
|
+
/\bcheck\b/i, /\bverify\b/i, /\bvalidate\b/i,
|
|
36
|
+
];
|
|
37
|
+
const ERROR_PATTERNS = [
|
|
38
|
+
/error/i, /failed/i, /exception/i, /exit code [1-9]/i,
|
|
39
|
+
/ENOENT/i, /EPERM/i, /EACCES/i, /TypeError/i, /SyntaxError/i,
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
// --- Path redaction ---
|
|
43
|
+
|
|
44
|
+
function redactHomePath(filePath) {
|
|
45
|
+
if (!filePath || typeof filePath !== "string") return filePath;
|
|
46
|
+
const home = os.homedir().replace(/\\/g, "/");
|
|
47
|
+
const normalized = filePath.replace(/\\/g, "/");
|
|
48
|
+
if (normalized.startsWith(home + "/") || normalized.startsWith(home + "\\")) {
|
|
49
|
+
return "~" + normalized.slice(home.length);
|
|
50
|
+
}
|
|
51
|
+
const homeWin = os.homedir();
|
|
52
|
+
if (filePath.startsWith(homeWin + "\\") || filePath.startsWith(homeWin + "/")) {
|
|
53
|
+
return "~" + filePath.slice(homeWin.length).replace(/\\/g, "/");
|
|
54
|
+
}
|
|
55
|
+
return filePath;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// --- JSONL Parser (defensive) ---
|
|
59
|
+
|
|
60
|
+
function parseJsonlFile(filePath) {
|
|
61
|
+
const events = [];
|
|
62
|
+
let raw;
|
|
63
|
+
try {
|
|
64
|
+
raw = fs.readFileSync(filePath, "utf8");
|
|
65
|
+
} catch {
|
|
66
|
+
return events;
|
|
67
|
+
}
|
|
68
|
+
for (const line of raw.split("\n")) {
|
|
69
|
+
const trimmed = line.trim();
|
|
70
|
+
if (!trimmed) continue;
|
|
71
|
+
try {
|
|
72
|
+
events.push(JSON.parse(trimmed));
|
|
73
|
+
} catch {
|
|
74
|
+
// malformed line — skip silently
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return events;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
// --- Discovery ---
|
|
81
|
+
|
|
82
|
+
function discoverClaudeHome() {
|
|
83
|
+
const exists = fs.existsSync(CLAUDE_HOME);
|
|
84
|
+
const sessionsExist = fs.existsSync(SESSIONS_DIR);
|
|
85
|
+
const projectsExist = fs.existsSync(PROJECTS_DIR);
|
|
86
|
+
return { claudeHome: CLAUDE_HOME, exists, sessionsExist, projectsExist };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function discoverProjectKeys() {
|
|
90
|
+
if (!fs.existsSync(PROJECTS_DIR)) return [];
|
|
91
|
+
try {
|
|
92
|
+
return fs.readdirSync(PROJECTS_DIR).filter((name) => {
|
|
93
|
+
const full = path.join(PROJECTS_DIR, name);
|
|
94
|
+
return fs.statSync(full).isDirectory() || name.endsWith(".jsonl");
|
|
95
|
+
});
|
|
96
|
+
} catch {
|
|
97
|
+
return [];
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function discoverSessionFiles(projectKey) {
|
|
102
|
+
const projectDir = path.join(PROJECTS_DIR, projectKey);
|
|
103
|
+
if (!fs.existsSync(projectDir)) return [];
|
|
104
|
+
try {
|
|
105
|
+
const entries = fs.readdirSync(projectDir);
|
|
106
|
+
return entries
|
|
107
|
+
.filter((e) => e.endsWith(".jsonl"))
|
|
108
|
+
.map((e) => ({
|
|
109
|
+
sessionId: e.replace(".jsonl", ""),
|
|
110
|
+
filePath: path.join(projectDir, e),
|
|
111
|
+
sizeBytes: fs.statSync(path.join(projectDir, e)).size,
|
|
112
|
+
mtimeMs: fs.statSync(path.join(projectDir, e)).mtimeMs,
|
|
113
|
+
}))
|
|
114
|
+
.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
115
|
+
} catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function discoverActiveSessions() {
|
|
121
|
+
if (!fs.existsSync(SESSIONS_DIR)) return [];
|
|
122
|
+
try {
|
|
123
|
+
return fs.readdirSync(SESSIONS_DIR)
|
|
124
|
+
.filter((f) => f.endsWith(".json"))
|
|
125
|
+
.map((f) => {
|
|
126
|
+
try {
|
|
127
|
+
const data = JSON.parse(fs.readFileSync(path.join(SESSIONS_DIR, f), "utf8"));
|
|
128
|
+
return { pid: data.pid, sessionId: data.sessionId, cwd: data.cwd, startedAt: data.startedAt, version: data.version, entrypoint: data.entrypoint };
|
|
129
|
+
} catch {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
})
|
|
133
|
+
.filter(Boolean);
|
|
134
|
+
} catch {
|
|
135
|
+
return [];
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// --- Secret scanning ---
|
|
140
|
+
|
|
141
|
+
function scanForSecrets(text) {
|
|
142
|
+
if (!text || typeof text !== "string") return [];
|
|
143
|
+
const hits = [];
|
|
144
|
+
for (const { re, code } of SECRET_PATTERNS) {
|
|
145
|
+
if (re.test(text)) hits.push(code);
|
|
146
|
+
}
|
|
147
|
+
return hits;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function redactSecrets(text) {
|
|
151
|
+
if (!text || typeof text !== "string") return text;
|
|
152
|
+
let result = text;
|
|
153
|
+
for (const { re } of SECRET_PATTERNS) {
|
|
154
|
+
result = result.replace(new RegExp(re.source, re.flags + "g"), "[REDACTED]");
|
|
155
|
+
}
|
|
156
|
+
return result;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// --- Event normalization ---
|
|
160
|
+
|
|
161
|
+
function normalizeEvents(rawEvents) {
|
|
162
|
+
const normalized = {
|
|
163
|
+
sessionId: null,
|
|
164
|
+
title: null,
|
|
165
|
+
model: null,
|
|
166
|
+
mode: null,
|
|
167
|
+
startTime: null,
|
|
168
|
+
endTime: null,
|
|
169
|
+
userMessages: 0,
|
|
170
|
+
assistantMessages: 0,
|
|
171
|
+
toolUses: [],
|
|
172
|
+
filesTouched: new Set(),
|
|
173
|
+
commandsRun: [],
|
|
174
|
+
validationAttempts: [],
|
|
175
|
+
errorSignals: [],
|
|
176
|
+
secretHits: [],
|
|
177
|
+
totalInputTokens: 0,
|
|
178
|
+
totalOutputTokens: 0,
|
|
179
|
+
totalCacheCreationTokens: 0,
|
|
180
|
+
totalCacheReadTokens: 0,
|
|
181
|
+
prLinks: [],
|
|
182
|
+
entrypoint: null,
|
|
183
|
+
version: null,
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
for (const evt of rawEvents) {
|
|
187
|
+
try {
|
|
188
|
+
// session id
|
|
189
|
+
if (evt.sessionId && !normalized.sessionId) {
|
|
190
|
+
normalized.sessionId = evt.sessionId;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// title
|
|
194
|
+
if (evt.type === "ai-title" && evt.aiTitle) {
|
|
195
|
+
normalized.title = evt.aiTitle;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// mode
|
|
199
|
+
if (evt.type === "mode" && evt.mode) {
|
|
200
|
+
normalized.mode = evt.mode;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// PR links
|
|
204
|
+
if (evt.type === "pr-link") {
|
|
205
|
+
normalized.prLinks.push({
|
|
206
|
+
prNumber: evt.prNumber,
|
|
207
|
+
prUrl: evt.prUrl,
|
|
208
|
+
repository: evt.prRepository,
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// timestamps
|
|
213
|
+
if (evt.timestamp) {
|
|
214
|
+
const ts = typeof evt.timestamp === "string" ? new Date(evt.timestamp).getTime() : evt.timestamp;
|
|
215
|
+
if (!isNaN(ts)) {
|
|
216
|
+
if (!normalized.startTime || ts < normalized.startTime) normalized.startTime = ts;
|
|
217
|
+
if (!normalized.endTime || ts > normalized.endTime) normalized.endTime = ts;
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// user messages
|
|
222
|
+
if (evt.type === "user" || evt.message?.role === "user") {
|
|
223
|
+
normalized.userMessages++;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// assistant messages with tool use and usage
|
|
227
|
+
if (evt.type === "assistant" || evt.message?.role === "assistant") {
|
|
228
|
+
normalized.assistantMessages++;
|
|
229
|
+
const msg = evt.message || {};
|
|
230
|
+
if (!normalized.model && msg.model) normalized.model = msg.model;
|
|
231
|
+
|
|
232
|
+
// usage
|
|
233
|
+
const usage = msg.usage;
|
|
234
|
+
if (usage) {
|
|
235
|
+
normalized.totalInputTokens += usage.input_tokens || 0;
|
|
236
|
+
normalized.totalOutputTokens += usage.output_tokens || 0;
|
|
237
|
+
normalized.totalCacheCreationTokens += usage.cache_creation_input_tokens || 0;
|
|
238
|
+
normalized.totalCacheReadTokens += usage.cache_read_input_tokens || 0;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// tool use blocks
|
|
242
|
+
const content = msg.content || [];
|
|
243
|
+
for (const block of content) {
|
|
244
|
+
if (block.type === "tool_use") {
|
|
245
|
+
const toolName = block.name || "unknown";
|
|
246
|
+
normalized.toolUses.push(toolName);
|
|
247
|
+
|
|
248
|
+
// file touches
|
|
249
|
+
if (FILE_TOUCHING_TOOLS.has(toolName)) {
|
|
250
|
+
const fp = block.input?.file_path || block.input?.path;
|
|
251
|
+
if (fp) normalized.filesTouched.add(fp);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// commands
|
|
255
|
+
if (COMMAND_TOOLS.has(toolName)) {
|
|
256
|
+
const cmd = block.input?.command || "";
|
|
257
|
+
const shortCmd = cmd.length > 120 ? cmd.slice(0, 120) + "..." : cmd;
|
|
258
|
+
normalized.commandsRun.push({ tool: toolName, command: redactSecrets(shortCmd) });
|
|
259
|
+
|
|
260
|
+
// check for validation commands
|
|
261
|
+
for (const pat of VALIDATION_COMMANDS) {
|
|
262
|
+
if (pat.test(cmd)) {
|
|
263
|
+
normalized.validationAttempts.push({ tool: toolName, command: redactSecrets(shortCmd) });
|
|
264
|
+
break;
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// scan command text for secrets
|
|
269
|
+
const hits = scanForSecrets(cmd);
|
|
270
|
+
if (hits.length) normalized.secretHits.push(...hits);
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// scan tool input stringified for secrets (shallow)
|
|
274
|
+
if (block.input) {
|
|
275
|
+
const inputStr = typeof block.input === "string" ? block.input : JSON.stringify(block.input);
|
|
276
|
+
const hits = scanForSecrets(inputStr);
|
|
277
|
+
if (hits.length) normalized.secretHits.push(...hits);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
// scan text blocks for secrets
|
|
282
|
+
if (block.type === "text" && block.text) {
|
|
283
|
+
const hits = scanForSecrets(block.text);
|
|
284
|
+
if (hits.length) normalized.secretHits.push(...hits);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// system events — check for errors
|
|
290
|
+
if (evt.type === "system") {
|
|
291
|
+
if (evt.hookErrors?.length) {
|
|
292
|
+
for (const err of evt.hookErrors) {
|
|
293
|
+
normalized.errorSignals.push({ source: "hook", detail: redactSecrets(String(err).slice(0, 200)) });
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
} catch {
|
|
298
|
+
// tolerate any parsing error on individual events
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// deduplicate secret hits
|
|
303
|
+
normalized.secretHits = [...new Set(normalized.secretHits)];
|
|
304
|
+
// convert filesTouched to array
|
|
305
|
+
normalized.filesTouched = [...normalized.filesTouched];
|
|
306
|
+
|
|
307
|
+
return normalized;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// --- Error/loop detection from tool sequences ---
|
|
311
|
+
|
|
312
|
+
function detectErrorLoops(toolUses, windowSize = 4) {
|
|
313
|
+
const loops = [];
|
|
314
|
+
if (toolUses.length < windowSize * 2) return loops;
|
|
315
|
+
for (let i = windowSize * 2; i <= toolUses.length; i++) {
|
|
316
|
+
const a = toolUses.slice(i - windowSize, i).join("|");
|
|
317
|
+
const b = toolUses.slice(i - windowSize * 2, i - windowSize).join("|");
|
|
318
|
+
if (a === b) {
|
|
319
|
+
loops.push({ index: i, sequence: toolUses.slice(i - windowSize, i) });
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
// deduplicate consecutive identical loops
|
|
323
|
+
const deduped = [];
|
|
324
|
+
for (const loop of loops) {
|
|
325
|
+
const lastSeq = deduped.length ? deduped[deduped.length - 1].sequence.join("|") : "";
|
|
326
|
+
if (loop.sequence.join("|") !== lastSeq) deduped.push(loop);
|
|
327
|
+
}
|
|
328
|
+
return deduped;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
function detectErrorBursts(commandsRun) {
|
|
332
|
+
let burst = 0;
|
|
333
|
+
let maxBurst = 0;
|
|
334
|
+
for (const cmd of commandsRun) {
|
|
335
|
+
const hasError = ERROR_PATTERNS.some((p) => p.test(cmd.command));
|
|
336
|
+
if (hasError) {
|
|
337
|
+
burst++;
|
|
338
|
+
if (burst > maxBurst) maxBurst = burst;
|
|
339
|
+
} else {
|
|
340
|
+
burst = 0;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return maxBurst;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// --- Session summary (privacy-safe) ---
|
|
347
|
+
|
|
348
|
+
function buildSessionSummary(normalized) {
|
|
349
|
+
const durationMs = normalized.endTime && normalized.startTime
|
|
350
|
+
? normalized.endTime - normalized.startTime
|
|
351
|
+
: null;
|
|
352
|
+
|
|
353
|
+
const toolLoops = detectErrorLoops(normalized.toolUses);
|
|
354
|
+
const errorBurst = detectErrorBursts(normalized.commandsRun);
|
|
355
|
+
const hasValidation = normalized.validationAttempts.length > 0;
|
|
356
|
+
const hasErrors = normalized.errorSignals.length > 0 || errorBurst >= 3;
|
|
357
|
+
|
|
358
|
+
return {
|
|
359
|
+
sessionId: normalized.sessionId,
|
|
360
|
+
title: normalized.title,
|
|
361
|
+
model: normalized.model,
|
|
362
|
+
durationMs,
|
|
363
|
+
durationMinutes: durationMs ? Math.round(durationMs / 60000) : null,
|
|
364
|
+
userMessages: normalized.userMessages,
|
|
365
|
+
assistantMessages: normalized.assistantMessages,
|
|
366
|
+
totalToolUses: normalized.toolUses.length,
|
|
367
|
+
uniqueToolsUsed: [...new Set(normalized.toolUses)],
|
|
368
|
+
filesTouchedCount: normalized.filesTouched.length,
|
|
369
|
+
filesTouched: normalized.filesTouched.map((f) => redactHomePath(redactSecrets(f))),
|
|
370
|
+
commandsRunCount: normalized.commandsRun.length,
|
|
371
|
+
validationAttempts: normalized.validationAttempts.length,
|
|
372
|
+
hasValidation,
|
|
373
|
+
errorSignals: normalized.errorSignals.length,
|
|
374
|
+
hasErrors,
|
|
375
|
+
toolLoopsDetected: toolLoops.length,
|
|
376
|
+
toolLoops: toolLoops.map((l) => ({ sequence: l.sequence })),
|
|
377
|
+
errorBurstMax: errorBurst,
|
|
378
|
+
secretHits: normalized.secretHits,
|
|
379
|
+
hasSecretRisk: normalized.secretHits.length > 0,
|
|
380
|
+
tokenSummary: {
|
|
381
|
+
inputTokens: normalized.totalInputTokens,
|
|
382
|
+
outputTokens: normalized.totalOutputTokens,
|
|
383
|
+
cacheCreationTokens: normalized.totalCacheCreationTokens,
|
|
384
|
+
cacheReadTokens: normalized.totalCacheReadTokens,
|
|
385
|
+
totalTokens: normalized.totalInputTokens + normalized.totalOutputTokens,
|
|
386
|
+
},
|
|
387
|
+
prLinks: normalized.prLinks,
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
// --- Resume packet ---
|
|
392
|
+
|
|
393
|
+
function buildResumePacket(summary, normalized) {
|
|
394
|
+
const touchedAreas = normalized.filesTouched.map((f) => {
|
|
395
|
+
const parts = f.replace(/\\/g, "/").split("/");
|
|
396
|
+
return parts.length > 2 ? parts.slice(-3).join("/") : parts.join("/");
|
|
397
|
+
});
|
|
398
|
+
|
|
399
|
+
const uniqueAreas = [...new Set(touchedAreas.map((a) => {
|
|
400
|
+
const parts = a.split("/");
|
|
401
|
+
return parts.length > 1 ? parts.slice(0, -1).join("/") : a;
|
|
402
|
+
}))].slice(0, 10);
|
|
403
|
+
|
|
404
|
+
const risks = [];
|
|
405
|
+
if (!summary.hasValidation) risks.push("No validation commands detected — consider running tests");
|
|
406
|
+
if (summary.toolLoopsDetected > 0) risks.push(`${summary.toolLoopsDetected} repeated tool loop(s) detected — may indicate stuck work`);
|
|
407
|
+
if (summary.hasErrors) risks.push("Error signals detected — check for unresolved failures");
|
|
408
|
+
if (summary.hasSecretRisk) risks.push(`Possible secret exposure: ${summary.secretHits.join(", ")}`);
|
|
409
|
+
if (summary.errorBurstMax >= 3) risks.push(`Error burst of ${summary.errorBurstMax} consecutive commands — possible debugging loop`);
|
|
410
|
+
|
|
411
|
+
const nextSteps = [];
|
|
412
|
+
if (!summary.hasValidation) nextSteps.push("Run the project test suite to validate changes");
|
|
413
|
+
if (summary.toolLoopsDetected > 0) nextSteps.push("Review the repeated tool sequences for stuck patterns");
|
|
414
|
+
if (summary.hasSecretRisk) nextSteps.push("Audit touched files for accidental secret commits");
|
|
415
|
+
if (summary.prLinks.length > 0) nextSteps.push(`Review open PR(s): ${summary.prLinks.map((p) => p.prUrl).join(", ")}`);
|
|
416
|
+
if (nextSteps.length === 0) nextSteps.push("Continue from where the session left off");
|
|
417
|
+
|
|
418
|
+
return {
|
|
419
|
+
generatedAt: new Date().toISOString(),
|
|
420
|
+
sessionId: summary.sessionId,
|
|
421
|
+
title: summary.title || "(untitled session)",
|
|
422
|
+
model: summary.model,
|
|
423
|
+
durationMinutes: summary.durationMinutes,
|
|
424
|
+
touchedAreas: uniqueAreas,
|
|
425
|
+
filesTouchedCount: summary.filesTouchedCount,
|
|
426
|
+
toolsUsed: summary.uniqueToolsUsed,
|
|
427
|
+
validationStatus: summary.hasValidation ? "partial" : "none",
|
|
428
|
+
proofStatus: summary.hasValidation && !summary.hasErrors ? "partial" : "unverified",
|
|
429
|
+
risks,
|
|
430
|
+
nextSteps,
|
|
431
|
+
tokenSummary: summary.tokenSummary,
|
|
432
|
+
};
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// --- Improvement suggestions ---
|
|
436
|
+
|
|
437
|
+
function buildImprovementSuggestions(summaries) {
|
|
438
|
+
const suggestions = [];
|
|
439
|
+
|
|
440
|
+
const noValidationCount = summaries.filter((s) => !s.hasValidation).length;
|
|
441
|
+
if (noValidationCount >= 2) {
|
|
442
|
+
suggestions.push({
|
|
443
|
+
type: "claude_md",
|
|
444
|
+
suggestion: "Add a project validation command to CLAUDE.md (e.g., 'After changes, run: npm test')",
|
|
445
|
+
evidence: `${noValidationCount}/${summaries.length} recent sessions had no detected validation step`,
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
const loopSessions = summaries.filter((s) => s.toolLoopsDetected > 0).length;
|
|
450
|
+
if (loopSessions >= 2) {
|
|
451
|
+
suggestions.push({
|
|
452
|
+
type: "workflow",
|
|
453
|
+
suggestion: "Consider creating a debug/fix skill to break out of repeated tool loops faster",
|
|
454
|
+
evidence: `${loopSessions} sessions had repeated tool sequence loops`,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const secretSessions = summaries.filter((s) => s.hasSecretRisk).length;
|
|
459
|
+
if (secretSessions >= 1) {
|
|
460
|
+
suggestions.push({
|
|
461
|
+
type: "security",
|
|
462
|
+
suggestion: "Add pre-commit secret scanning or .gitignore rules for sensitive files",
|
|
463
|
+
evidence: `${secretSessions} session(s) had possible secret patterns in tool activity`,
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
// repeated file patterns
|
|
468
|
+
const fileCounts = {};
|
|
469
|
+
for (const s of summaries) {
|
|
470
|
+
for (const f of s.filesTouched) {
|
|
471
|
+
fileCounts[f] = (fileCounts[f] || 0) + 1;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
const hotFiles = Object.entries(fileCounts)
|
|
475
|
+
.filter(([, c]) => c >= 3)
|
|
476
|
+
.sort((a, b) => b[1] - a[1])
|
|
477
|
+
.slice(0, 5);
|
|
478
|
+
if (hotFiles.length > 0) {
|
|
479
|
+
suggestions.push({
|
|
480
|
+
type: "attention",
|
|
481
|
+
suggestion: `Frequently touched files across sessions: ${hotFiles.map(([f, c]) => `${path.basename(f)} (${c}x)`).join(", ")}`,
|
|
482
|
+
evidence: "Files edited in 3+ sessions may benefit from dedicated tests or review",
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
|
|
486
|
+
return suggestions;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
// --- Proof gap classification ---
|
|
490
|
+
|
|
491
|
+
function classifyProofGap(summary) {
|
|
492
|
+
const hasFiles = summary.filesTouchedCount > 0;
|
|
493
|
+
const hasValidation = summary.hasValidation;
|
|
494
|
+
const hasErrors = summary.hasErrors;
|
|
495
|
+
const hasLoops = summary.toolLoopsDetected > 0;
|
|
496
|
+
const hasSecrets = summary.hasSecretRisk;
|
|
497
|
+
|
|
498
|
+
if (!hasFiles) return { status: "no_changes", label: "No file activity observed", basis: "observed" };
|
|
499
|
+
if (hasValidation && !hasErrors) return { status: "proved", label: "Validation observed, no errors detected", basis: "observed" };
|
|
500
|
+
if (hasValidation && hasErrors) return { status: "partial", label: "Validation observed, errors remain", basis: "observed" };
|
|
501
|
+
if (hasLoops) return { status: "stuck", label: "Repeated tool loops detected — may need manual review", basis: "inferred" };
|
|
502
|
+
if (hasSecrets) return { status: "risk", label: "Possible secret exposure — audit required", basis: "inferred" };
|
|
503
|
+
return { status: "unproved", label: "File activity observed without validation evidence", basis: "observed" };
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// --- Receipt persistence ---
|
|
507
|
+
|
|
508
|
+
const SESSION_EVIDENCE_CONTRACT = "avorelo.sessionEvidence.v1";
|
|
509
|
+
const SESSION_EVIDENCE_RECEIPT_REL = ".claude/cco/orchestration/session-evidence/latest-receipt.json";
|
|
510
|
+
const SESSION_EVIDENCE_EVENT_REL = ".claude/cco/events/session-evidence.jsonl";
|
|
511
|
+
const RESUME_PACKET_REL = ".claude/cco/state/session-resume-packet.json";
|
|
512
|
+
|
|
513
|
+
function persistScanReceipt(cwd, result) {
|
|
514
|
+
const { ensureCcoDirs, safeWriteJson, nowIso } = require("../fsx");
|
|
515
|
+
ensureCcoDirs(cwd);
|
|
516
|
+
|
|
517
|
+
const proofGaps = (result.summaries || []).map((s) => ({
|
|
518
|
+
sessionId: s.sessionId,
|
|
519
|
+
title: s.title,
|
|
520
|
+
...classifyProofGap(s),
|
|
521
|
+
}));
|
|
522
|
+
|
|
523
|
+
const proved = proofGaps.filter((g) => g.status === "proved").length;
|
|
524
|
+
const unproved = proofGaps.filter((g) => g.status === "unproved" || g.status === "partial" || g.status === "stuck").length;
|
|
525
|
+
|
|
526
|
+
const receipt = {
|
|
527
|
+
contract: SESSION_EVIDENCE_CONTRACT,
|
|
528
|
+
schemaVersion: 1,
|
|
529
|
+
generatedAt: nowIso(),
|
|
530
|
+
source: "claude-code",
|
|
531
|
+
privacy: {
|
|
532
|
+
localOnly: true,
|
|
533
|
+
cloudUploaded: false,
|
|
534
|
+
rawTranscriptStored: false,
|
|
535
|
+
secretsRedacted: (result.summaries || []).some((s) => s.hasSecretRisk),
|
|
536
|
+
pathMode: "relative-or-redacted",
|
|
537
|
+
},
|
|
538
|
+
sessionsScanned: result.sessionsScanned,
|
|
539
|
+
proofSummary: { proved, unproved, total: proofGaps.length },
|
|
540
|
+
proofGaps,
|
|
541
|
+
resumePacket: result.resumePacket,
|
|
542
|
+
suggestions: result.suggestions,
|
|
543
|
+
attentionItems: result.attentionItems || [],
|
|
544
|
+
};
|
|
545
|
+
|
|
546
|
+
safeWriteJson(cwd, SESSION_EVIDENCE_RECEIPT_REL, receipt);
|
|
547
|
+
|
|
548
|
+
if (result.resumePacket) {
|
|
549
|
+
safeWriteJson(cwd, RESUME_PACKET_REL, result.resumePacket);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
const eventLine = JSON.stringify({ ts: nowIso(), event: "scan_completed", sessionsScanned: result.sessionsScanned, proved, unproved }) + "\n";
|
|
553
|
+
const eventPath = path.join(cwd, SESSION_EVIDENCE_EVENT_REL);
|
|
554
|
+
fs.mkdirSync(path.dirname(eventPath), { recursive: true });
|
|
555
|
+
fs.appendFileSync(eventPath, eventLine, "utf8");
|
|
556
|
+
|
|
557
|
+
return { receiptPath: SESSION_EVIDENCE_RECEIPT_REL, resumePacketPath: result.resumePacket ? RESUME_PACKET_REL : null };
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// --- Value ledger integration ---
|
|
561
|
+
|
|
562
|
+
function emitValueEvents(cwd, result) {
|
|
563
|
+
let ValueLedger;
|
|
564
|
+
try {
|
|
565
|
+
ValueLedger = require("../value-ledger").ValueLedger;
|
|
566
|
+
} catch {
|
|
567
|
+
return [];
|
|
568
|
+
}
|
|
569
|
+
const ledger = new ValueLedger(cwd);
|
|
570
|
+
const events = [];
|
|
571
|
+
|
|
572
|
+
const proofGaps = (result.summaries || []).map((s) => classifyProofGap(s));
|
|
573
|
+
const proved = proofGaps.filter((g) => g.status === "proved").length;
|
|
574
|
+
const unproved = proofGaps.filter((g) => g.status === "unproved").length;
|
|
575
|
+
|
|
576
|
+
if (proved > 0) {
|
|
577
|
+
events.push(ledger.append({
|
|
578
|
+
bucket: "proof_completed",
|
|
579
|
+
label: `Validation observed in ${proved} session(s)`,
|
|
580
|
+
notes: "Observed: validation commands detected in Claude Code session transcripts",
|
|
581
|
+
artifactRef: SESSION_EVIDENCE_RECEIPT_REL,
|
|
582
|
+
meta: { valueClaimLevel: "observed", source: "claude-code" },
|
|
583
|
+
}));
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
if (unproved > 0) {
|
|
587
|
+
events.push(ledger.append({
|
|
588
|
+
bucket: "proof_completed",
|
|
589
|
+
label: `Proof gap detected: ${unproved} session(s) with file activity but no validation observed`,
|
|
590
|
+
notes: "Detected: file activity observed without validation evidence — surfaced for attention, not claimed as avoided",
|
|
591
|
+
artifactRef: SESSION_EVIDENCE_RECEIPT_REL,
|
|
592
|
+
meta: { valueClaimLevel: "detected", source: "claude-code" },
|
|
593
|
+
}));
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
const secretSessions = (result.summaries || []).filter((s) => s.hasSecretRisk).length;
|
|
597
|
+
if (secretSessions > 0) {
|
|
598
|
+
events.push(ledger.append({
|
|
599
|
+
bucket: "proof_completed",
|
|
600
|
+
label: `Secret-like patterns detected and redacted in ${secretSessions} session(s)`,
|
|
601
|
+
notes: "Detected: possible secret patterns found in tool activity metadata — redacted in output, surfaced for audit",
|
|
602
|
+
artifactRef: SESSION_EVIDENCE_RECEIPT_REL,
|
|
603
|
+
meta: { valueClaimLevel: "detected", source: "claude-code" },
|
|
604
|
+
}));
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
return events.filter(Boolean);
|
|
608
|
+
}
|
|
609
|
+
|
|
610
|
+
// --- CLI rendering (Avorelo style) ---
|
|
611
|
+
|
|
612
|
+
function formatScanText(result) {
|
|
613
|
+
const lines = [];
|
|
614
|
+
lines.push("Avorelo — Claude Code Session Evidence");
|
|
615
|
+
lines.push("");
|
|
616
|
+
|
|
617
|
+
if (!result.discovery.exists) {
|
|
618
|
+
lines.push("Found: No Claude Code local data directory detected.");
|
|
619
|
+
lines.push("Next: Install or run Claude Code in this project first.");
|
|
620
|
+
return lines.join("\n");
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
if (result.summaries.length === 0) {
|
|
624
|
+
lines.push("Found: Claude Code installed, but no session transcripts for this project.");
|
|
625
|
+
lines.push("Next: Run a Claude Code session in this workspace, then re-scan.");
|
|
626
|
+
return lines.join("\n");
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// --- What Avorelo found ---
|
|
630
|
+
const totalFiles = new Set(result.summaries.flatMap((s) => s.filesTouched)).size;
|
|
631
|
+
const totalTokens = result.summaries.reduce((n, s) => n + s.tokenSummary.totalTokens, 0);
|
|
632
|
+
lines.push(`Found: ${result.sessionsScanned} session(s), ${totalFiles} files touched, ${totalTokens.toLocaleString()} tokens (metadata only — raw transcripts not stored)`);
|
|
633
|
+
|
|
634
|
+
const recent = result.summaries.slice(0, 5);
|
|
635
|
+
for (const s of recent) {
|
|
636
|
+
const dur = s.durationMinutes != null ? `${s.durationMinutes}m` : "?m";
|
|
637
|
+
const title = s.title || "(untitled)";
|
|
638
|
+
const gap = classifyProofGap(s);
|
|
639
|
+
lines.push(` ${s.sessionId.slice(0, 8)} ${dur} ${title.slice(0, 50)} [${gap.status}]`);
|
|
640
|
+
}
|
|
641
|
+
lines.push("");
|
|
642
|
+
|
|
643
|
+
// --- What was proved ---
|
|
644
|
+
const proofGaps = result.summaries.map((s) => ({ sessionId: s.sessionId, title: s.title, ...classifyProofGap(s) }));
|
|
645
|
+
const proved = proofGaps.filter((g) => g.status === "proved");
|
|
646
|
+
const partial = proofGaps.filter((g) => g.status === "partial");
|
|
647
|
+
const unproved = proofGaps.filter((g) => g.status === "unproved");
|
|
648
|
+
const stuck = proofGaps.filter((g) => g.status === "stuck");
|
|
649
|
+
const risks = proofGaps.filter((g) => g.status === "risk");
|
|
650
|
+
|
|
651
|
+
if (proved.length > 0) {
|
|
652
|
+
lines.push(`Proved: ${proved.length} session(s) — validation observed, no errors detected`);
|
|
653
|
+
}
|
|
654
|
+
if (partial.length > 0) {
|
|
655
|
+
lines.push(`Partial: ${partial.length} session(s) — validation observed, errors remain`);
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// --- What was NOT proved ---
|
|
659
|
+
if (unproved.length > 0) {
|
|
660
|
+
lines.push(`Not proved: ${unproved.length} session(s) — file activity observed, validation not observed`);
|
|
661
|
+
}
|
|
662
|
+
if (stuck.length > 0) {
|
|
663
|
+
lines.push(`Stuck: ${stuck.length} session(s) — repeated tool loops detected (inferred)`);
|
|
664
|
+
}
|
|
665
|
+
if (risks.length > 0) {
|
|
666
|
+
lines.push(`Risk: ${risks.length} session(s) — possible secret exposure detected (inferred)`);
|
|
667
|
+
}
|
|
668
|
+
lines.push("");
|
|
669
|
+
|
|
670
|
+
// --- What needs attention ---
|
|
671
|
+
const attentionItems = [];
|
|
672
|
+
if (unproved.length > 0) attentionItems.push(`${unproved.length} session(s) changed files without detected validation`);
|
|
673
|
+
const loopSessions = result.summaries.filter((s) => s.toolLoopsDetected > 0).length;
|
|
674
|
+
if (loopSessions > 0) attentionItems.push(`${loopSessions} repeated error/tool loop(s) detected`);
|
|
675
|
+
const secretSessions = result.summaries.filter((s) => s.hasSecretRisk).length;
|
|
676
|
+
if (secretSessions > 0) attentionItems.push(`${secretSessions} possible secret-like value(s) redacted`);
|
|
677
|
+
const errorSessions = result.summaries.filter((s) => s.hasErrors).length;
|
|
678
|
+
if (errorSessions > 0) attentionItems.push(`${errorSessions} session(s) had error signals`);
|
|
679
|
+
|
|
680
|
+
if (attentionItems.length) {
|
|
681
|
+
lines.push("Attention:");
|
|
682
|
+
for (const item of attentionItems) lines.push(` - ${item}`);
|
|
683
|
+
lines.push("");
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
// --- What was safely saved ---
|
|
687
|
+
if (result.persisted) {
|
|
688
|
+
lines.push("Saved:");
|
|
689
|
+
lines.push(` Receipt: ${result.persisted.receiptPath}`);
|
|
690
|
+
if (result.persisted.resumePacketPath) {
|
|
691
|
+
lines.push(` Resume packet: ${result.persisted.resumePacketPath}`);
|
|
692
|
+
}
|
|
693
|
+
if (result.persisted.valueEventsCount > 0) {
|
|
694
|
+
lines.push(` Value events: ${result.persisted.valueEventsCount} logged`);
|
|
695
|
+
}
|
|
696
|
+
lines.push("");
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// --- What to do next ---
|
|
700
|
+
if (result.resumePacket) {
|
|
701
|
+
lines.push("Next:");
|
|
702
|
+
for (const s of result.resumePacket.nextSteps) lines.push(` - ${s}`);
|
|
703
|
+
lines.push("");
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
// --- Suggested improvements ---
|
|
707
|
+
if (result.suggestions.length) {
|
|
708
|
+
lines.push("Improve:");
|
|
709
|
+
for (const s of result.suggestions) {
|
|
710
|
+
lines.push(` - ${s.suggestion}`);
|
|
711
|
+
lines.push(` (${s.evidence})`);
|
|
712
|
+
}
|
|
713
|
+
lines.push("");
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// --- Privacy ---
|
|
717
|
+
lines.push("Privacy:");
|
|
718
|
+
lines.push(" Local only — no data uploaded");
|
|
719
|
+
lines.push(" Metadata only — raw transcripts not stored");
|
|
720
|
+
const secretCount = result.summaries.filter((s) => s.hasSecretRisk).length;
|
|
721
|
+
if (secretCount > 0) {
|
|
722
|
+
lines.push(` ${secretCount} secret-like value(s) redacted`);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
return lines.join("\n");
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
function formatScanJson(result) {
|
|
729
|
+
const safe = { ...result };
|
|
730
|
+
if (safe.discovery) {
|
|
731
|
+
safe.discovery = { ...safe.discovery, claudeHome: redactHomePath(safe.discovery.claudeHome) };
|
|
732
|
+
}
|
|
733
|
+
return JSON.stringify(safe, null, 2);
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
// --- Resume packet retrieval ---
|
|
737
|
+
|
|
738
|
+
function readLatestResumePacket(cwd) {
|
|
739
|
+
const abs = path.join(cwd, RESUME_PACKET_REL);
|
|
740
|
+
try {
|
|
741
|
+
return JSON.parse(fs.readFileSync(abs, "utf8"));
|
|
742
|
+
} catch {
|
|
743
|
+
return null;
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
function formatResumeText(packet) {
|
|
748
|
+
if (!packet) {
|
|
749
|
+
return "No resume packet found for this project.\nNext: Run `avorelo scan claude` after a Claude Code session.";
|
|
750
|
+
}
|
|
751
|
+
const lines = [];
|
|
752
|
+
lines.push("Avorelo — Resume Packet");
|
|
753
|
+
lines.push("");
|
|
754
|
+
lines.push(`Session: ${packet.title || "(untitled)"}`);
|
|
755
|
+
lines.push(`Model: ${packet.model || "unknown"}`);
|
|
756
|
+
lines.push(`Duration: ${packet.durationMinutes || "?"}m`);
|
|
757
|
+
lines.push(`Files touched: ${packet.filesTouchedCount || 0}`);
|
|
758
|
+
lines.push(`Validation: ${packet.validationStatus || "unknown"}`);
|
|
759
|
+
lines.push(`Proof: ${packet.proofStatus || "unknown"}`);
|
|
760
|
+
if (packet.touchedAreas?.length) {
|
|
761
|
+
lines.push(`Areas: ${packet.touchedAreas.join(", ")}`);
|
|
762
|
+
}
|
|
763
|
+
if (packet.risks?.length) {
|
|
764
|
+
lines.push("");
|
|
765
|
+
lines.push("Risks:");
|
|
766
|
+
for (const r of packet.risks) lines.push(` - ${r}`);
|
|
767
|
+
}
|
|
768
|
+
if (packet.nextSteps?.length) {
|
|
769
|
+
lines.push("");
|
|
770
|
+
lines.push("Next:");
|
|
771
|
+
for (const s of packet.nextSteps) lines.push(` - ${s}`);
|
|
772
|
+
}
|
|
773
|
+
lines.push("");
|
|
774
|
+
lines.push("Privacy: Local only — metadata only — raw transcripts not stored");
|
|
775
|
+
return lines.join("\n");
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
function formatResumeJson(packet) {
|
|
779
|
+
return JSON.stringify(packet, null, 2);
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// --- Doctor helper ---
|
|
783
|
+
|
|
784
|
+
function buildDoctorCheck(cwd) {
|
|
785
|
+
const receiptPath = path.join(cwd, SESSION_EVIDENCE_RECEIPT_REL);
|
|
786
|
+
const resumePath = path.join(cwd, RESUME_PACKET_REL);
|
|
787
|
+
const hasReceipt = fs.existsSync(receiptPath);
|
|
788
|
+
const hasResume = fs.existsSync(resumePath);
|
|
789
|
+
|
|
790
|
+
if (!hasReceipt && !hasResume) {
|
|
791
|
+
return {
|
|
792
|
+
id: "claude_evidence",
|
|
793
|
+
label: "Claude Code evidence",
|
|
794
|
+
severity: "recommended",
|
|
795
|
+
passed: false,
|
|
796
|
+
details: "Not found for this project — run `avorelo scan claude` after using Claude Code",
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
let proofGapCount = 0;
|
|
801
|
+
try {
|
|
802
|
+
const receipt = JSON.parse(fs.readFileSync(receiptPath, "utf8"));
|
|
803
|
+
proofGapCount = receipt.proofSummary?.unproved || 0;
|
|
804
|
+
} catch {}
|
|
805
|
+
|
|
806
|
+
const parts = [];
|
|
807
|
+
if (hasReceipt) parts.push("receipt ready");
|
|
808
|
+
if (hasResume) parts.push("resume packet ready");
|
|
809
|
+
if (proofGapCount > 0) parts.push(`${proofGapCount} proof gap(s)`);
|
|
810
|
+
parts.push("local only, metadata only");
|
|
811
|
+
|
|
812
|
+
return {
|
|
813
|
+
id: "claude_evidence",
|
|
814
|
+
label: "Claude Code evidence",
|
|
815
|
+
severity: "recommended",
|
|
816
|
+
passed: true,
|
|
817
|
+
details: parts.join(", "),
|
|
818
|
+
};
|
|
819
|
+
}
|
|
820
|
+
|
|
821
|
+
// --- Local manifest for open/control center ---
|
|
822
|
+
|
|
823
|
+
function buildLocalManifest(cwd) {
|
|
824
|
+
const receiptPath = path.join(cwd, SESSION_EVIDENCE_RECEIPT_REL);
|
|
825
|
+
const resumePath = path.join(cwd, RESUME_PACKET_REL);
|
|
826
|
+
const hasReceipt = fs.existsSync(receiptPath);
|
|
827
|
+
const hasResume = fs.existsSync(resumePath);
|
|
828
|
+
|
|
829
|
+
let proofGapCount = 0;
|
|
830
|
+
let nextAction = null;
|
|
831
|
+
try {
|
|
832
|
+
const receipt = JSON.parse(fs.readFileSync(receiptPath, "utf8"));
|
|
833
|
+
proofGapCount = receipt.proofSummary?.unproved || 0;
|
|
834
|
+
} catch {}
|
|
835
|
+
try {
|
|
836
|
+
const packet = JSON.parse(fs.readFileSync(resumePath, "utf8"));
|
|
837
|
+
nextAction = packet.nextSteps?.[0] || null;
|
|
838
|
+
} catch {}
|
|
839
|
+
|
|
840
|
+
return {
|
|
841
|
+
schemaVersion: 1,
|
|
842
|
+
source: "claude-code",
|
|
843
|
+
latestReceiptPath: hasReceipt ? SESSION_EVIDENCE_RECEIPT_REL : null,
|
|
844
|
+
latestResumePacketPath: hasResume ? RESUME_PACKET_REL : null,
|
|
845
|
+
proofGapCount,
|
|
846
|
+
privacyStatus: "metadata-only-redacted",
|
|
847
|
+
nextAction,
|
|
848
|
+
rawTranscriptStored: false,
|
|
849
|
+
cloudUploaded: false,
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// --- Main scan function ---
|
|
854
|
+
|
|
855
|
+
function cwdToProjectKey(p) {
|
|
856
|
+
return p.replace(/[:\\/. ]/g, "-").replace(/^-+/, "").replace(/-+/g, "-");
|
|
857
|
+
}
|
|
858
|
+
|
|
859
|
+
function matchProjectKey(projectKeys, cwd) {
|
|
860
|
+
const key1 = cwdToProjectKey(cwd);
|
|
861
|
+
const exact = projectKeys.find((k) => k === key1);
|
|
862
|
+
if (exact) return exact;
|
|
863
|
+
|
|
864
|
+
const key2 = cwd.replace(/[:\\/]/g, "-").replace(/^-+/, "");
|
|
865
|
+
const exact2 = projectKeys.find((k) => k === key2);
|
|
866
|
+
if (exact2) return exact2;
|
|
867
|
+
|
|
868
|
+
const segments = cwd.replace(/\\/g, "/").split("/").filter(Boolean);
|
|
869
|
+
if (segments.length >= 2) {
|
|
870
|
+
const tail2 = segments.slice(-2).join("-").replace(/\./g, "-");
|
|
871
|
+
const match = projectKeys.find((k) => k.endsWith(tail2) && !k.endsWith("-" + tail2 + "-"));
|
|
872
|
+
if (match) return match;
|
|
873
|
+
}
|
|
874
|
+
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
|
|
878
|
+
function scanClaudeSessions(options = {}) {
|
|
879
|
+
const cwd = options.cwd || process.cwd();
|
|
880
|
+
const maxSessions = options.maxSessions || 20;
|
|
881
|
+
const persist = options.persist !== false;
|
|
882
|
+
|
|
883
|
+
const discovery = discoverClaudeHome();
|
|
884
|
+
if (!discovery.exists) {
|
|
885
|
+
return {
|
|
886
|
+
discovery,
|
|
887
|
+
projectKeys: [],
|
|
888
|
+
sessionsScanned: 0,
|
|
889
|
+
summaries: [],
|
|
890
|
+
resumePacket: null,
|
|
891
|
+
suggestions: [],
|
|
892
|
+
proofGaps: [],
|
|
893
|
+
persisted: null,
|
|
894
|
+
};
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
const projectKeys = discoverProjectKeys();
|
|
898
|
+
const bestKey = matchProjectKey(projectKeys, cwd);
|
|
899
|
+
|
|
900
|
+
let sessionFiles = [];
|
|
901
|
+
if (bestKey) {
|
|
902
|
+
sessionFiles = discoverSessionFiles(bestKey);
|
|
903
|
+
}
|
|
904
|
+
|
|
905
|
+
const limited = sessionFiles.slice(0, maxSessions);
|
|
906
|
+
|
|
907
|
+
const summaries = [];
|
|
908
|
+
for (const sf of limited) {
|
|
909
|
+
const rawEvents = parseJsonlFile(sf.filePath);
|
|
910
|
+
if (rawEvents.length === 0) continue;
|
|
911
|
+
const normalized = normalizeEvents(rawEvents);
|
|
912
|
+
const summary = buildSessionSummary(normalized);
|
|
913
|
+
summary._normalized = normalized;
|
|
914
|
+
summaries.push(summary);
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
let resumePacket = null;
|
|
918
|
+
if (summaries.length > 0) {
|
|
919
|
+
const mostRecent = summaries[0];
|
|
920
|
+
resumePacket = buildResumePacket(mostRecent, mostRecent._normalized);
|
|
921
|
+
}
|
|
922
|
+
|
|
923
|
+
for (const s of summaries) delete s._normalized;
|
|
924
|
+
|
|
925
|
+
const suggestions = buildImprovementSuggestions(summaries);
|
|
926
|
+
const proofGaps = summaries.map((s) => ({
|
|
927
|
+
sessionId: s.sessionId,
|
|
928
|
+
title: s.title,
|
|
929
|
+
...classifyProofGap(s),
|
|
930
|
+
}));
|
|
931
|
+
|
|
932
|
+
const result = {
|
|
933
|
+
discovery,
|
|
934
|
+
projectKeys: projectKeys.slice(0, 20),
|
|
935
|
+
sessionsScanned: summaries.length,
|
|
936
|
+
summaries,
|
|
937
|
+
resumePacket,
|
|
938
|
+
suggestions,
|
|
939
|
+
proofGaps,
|
|
940
|
+
persisted: null,
|
|
941
|
+
};
|
|
942
|
+
|
|
943
|
+
if (persist && summaries.length > 0) {
|
|
944
|
+
try {
|
|
945
|
+
const paths = persistScanReceipt(cwd, result);
|
|
946
|
+
const valueEvents = emitValueEvents(cwd, result);
|
|
947
|
+
result.persisted = { ...paths, valueEventsCount: valueEvents.length };
|
|
948
|
+
} catch {
|
|
949
|
+
result.persisted = null;
|
|
950
|
+
}
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
return result;
|
|
954
|
+
}
|
|
955
|
+
|
|
956
|
+
// --- Exports ---
|
|
957
|
+
|
|
958
|
+
module.exports = {
|
|
959
|
+
// discovery
|
|
960
|
+
discoverClaudeHome,
|
|
961
|
+
discoverProjectKeys,
|
|
962
|
+
discoverSessionFiles,
|
|
963
|
+
discoverActiveSessions,
|
|
964
|
+
CLAUDE_HOME,
|
|
965
|
+
SESSIONS_DIR,
|
|
966
|
+
PROJECTS_DIR,
|
|
967
|
+
|
|
968
|
+
// parsing
|
|
969
|
+
parseJsonlFile,
|
|
970
|
+
|
|
971
|
+
// normalization
|
|
972
|
+
normalizeEvents,
|
|
973
|
+
buildSessionSummary,
|
|
974
|
+
|
|
975
|
+
// security
|
|
976
|
+
scanForSecrets,
|
|
977
|
+
redactSecrets,
|
|
978
|
+
SECRET_PATTERNS,
|
|
979
|
+
|
|
980
|
+
// detection
|
|
981
|
+
detectErrorLoops,
|
|
982
|
+
detectErrorBursts,
|
|
983
|
+
FILE_TOUCHING_TOOLS,
|
|
984
|
+
COMMAND_TOOLS,
|
|
985
|
+
|
|
986
|
+
// proof gaps
|
|
987
|
+
classifyProofGap,
|
|
988
|
+
|
|
989
|
+
// resume packet
|
|
990
|
+
buildResumePacket,
|
|
991
|
+
|
|
992
|
+
// persistence
|
|
993
|
+
persistScanReceipt,
|
|
994
|
+
emitValueEvents,
|
|
995
|
+
SESSION_EVIDENCE_CONTRACT,
|
|
996
|
+
SESSION_EVIDENCE_RECEIPT_REL,
|
|
997
|
+
SESSION_EVIDENCE_EVENT_REL,
|
|
998
|
+
RESUME_PACKET_REL,
|
|
999
|
+
|
|
1000
|
+
// suggestions
|
|
1001
|
+
buildImprovementSuggestions,
|
|
1002
|
+
|
|
1003
|
+
// project key matching
|
|
1004
|
+
cwdToProjectKey,
|
|
1005
|
+
matchProjectKey,
|
|
1006
|
+
|
|
1007
|
+
// resume
|
|
1008
|
+
readLatestResumePacket,
|
|
1009
|
+
formatResumeText,
|
|
1010
|
+
formatResumeJson,
|
|
1011
|
+
|
|
1012
|
+
// doctor
|
|
1013
|
+
buildDoctorCheck,
|
|
1014
|
+
|
|
1015
|
+
// local manifest
|
|
1016
|
+
buildLocalManifest,
|
|
1017
|
+
|
|
1018
|
+
// path redaction
|
|
1019
|
+
redactHomePath,
|
|
1020
|
+
|
|
1021
|
+
// main scan
|
|
1022
|
+
scanClaudeSessions,
|
|
1023
|
+
|
|
1024
|
+
// formatting
|
|
1025
|
+
formatScanText,
|
|
1026
|
+
formatScanJson,
|
|
1027
|
+
};
|