heyspex 0.1.0 → 0.2.0
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/README.md +21 -0
- package/bin/hs.mjs +931 -98
- package/bin/spexcode.mjs +262 -0
- package/dev-factory/engine/worktree.mjs +740 -0
- package/dev-factory/lib/code-repo-root.mjs +67 -0
- package/dev-factory/lib/context-budget.mjs +84 -0
- package/dev-factory/lib/epic-detect.mjs +190 -0
- package/dev-factory/lib/fleet-queue-file.mjs +846 -0
- package/dev-factory/lib/fleet-queue.mjs +426 -0
- package/dev-factory/lib/fleet-state.mjs +14 -18
- package/dev-factory/lib/model-catalog.mjs +338 -0
- package/dev-factory/lib/seat-context.mjs +199 -0
- package/dev-factory/lib/spec-io.mjs +93 -0
- package/dev-factory/lib/sufficiency-score.mjs +8 -2
- package/dev-factory/lib/worktree-allocator.mjs +744 -0
- package/dev-factory/model-catalog.json +222 -0
- package/dist/cli/hs-check.mjs +1085 -103
- package/dist/cli/hs-claim.mjs +936 -63
- package/dist/cli/hs-connect.mjs +9786 -1910
- package/dist/cli/hs-defer.mjs +5267 -3053
- package/dist/cli/hs-dev.mjs +564 -0
- package/dist/cli/hs-env.mjs +385 -299
- package/dist/cli/hs-index-rebuild.mjs +7129 -0
- package/dist/cli/hs-index-reconcile.mjs +7852 -0
- package/dist/cli/hs-init.mjs +777 -138
- package/dist/cli/hs-install.mjs +6583 -485
- package/dist/cli/hs-join.mjs +10137 -2368
- package/dist/cli/hs-vault-pull.mjs +4659 -5882
- package/dist/cli/hs-vault.mjs +3793 -1537
- package/dist/cli/hs-workspace.mjs +2550 -524
- package/dist/cli-templates/customer-vault/specs/entity-model.md +594 -601
- package/dist/cli-templates/customer-vault/specs/protocols/canonical-frontmatter-templates.md +110 -177
- package/dist/cli-templates/customer-vault/specs/protocols/destructive-action-checklist.md +122 -0
- package/dist/cli-templates/customer-vault/specs/protocols/memory-protocol.md +13 -15
- package/dist/cli-templates/customer-vault/specs/protocols/session-close-protocol.md +91 -0
- package/dist/cli-templates/customer-vault/specs/protocols/spec-hierarchy.md +150 -168
- package/dist/cli-templates/customer-vault/specs/protocols/spec-summary-authoring.md +83 -0
- package/dist/cli-templates/harness-commands/claude/hs-auto.md +36 -0
- package/dist/cli-templates/harness-commands/claude/hs-backfill.md +28 -0
- package/dist/cli-templates/harness-commands/claude/hs-capture.md +62 -0
- package/dist/cli-templates/harness-commands/claude/hs-defer.md +29 -0
- package/dist/cli-templates/harness-commands/claude/hs-dev.md +20 -0
- package/dist/cli-templates/harness-commands/claude/hs-doctor.md +32 -0
- package/dist/cli-templates/harness-commands/claude/hs-ground.md +28 -0
- package/dist/cli-templates/harness-commands/claude/hs-handoff.md +33 -0
- package/dist/cli-templates/harness-commands/claude/hs-log.md +27 -0
- package/dist/cli-templates/harness-commands/claude/hs-next.md +29 -0
- package/dist/cli-templates/harness-commands/claude/hs-promote.md +70 -0
- package/dist/cli-templates/harness-commands/claude/hs-push.md +35 -0
- package/dist/cli-templates/harness-commands/claude/hs-qc.md +27 -0
- package/dist/cli-templates/harness-commands/claude/hs-res.md +63 -0
- package/dist/cli-templates/harness-commands/claude/hs-resolve.md +23 -0
- package/dist/cli-templates/harness-commands/claude/hs-review.md +23 -0
- package/dist/cli-templates/harness-commands/claude/hs-view.md +26 -0
- package/dist/cli-templates/harness-commands/claude/kill-ports.md +32 -0
- package/dist/cli-templates/harness-commands/claude/prototype.md +91 -0
- package/dist/cli-templates/harness-commands/cursor/hs-auto.md +40 -0
- package/dist/cli-templates/harness-commands/cursor/hs-backfill.md +31 -0
- package/dist/cli-templates/harness-commands/cursor/hs-capture.md +65 -0
- package/dist/cli-templates/harness-commands/cursor/hs-defer.md +32 -0
- package/dist/cli-templates/harness-commands/cursor/hs-dev.md +24 -0
- package/dist/cli-templates/harness-commands/cursor/hs-doctor.md +36 -0
- package/dist/cli-templates/harness-commands/cursor/hs-ground.md +31 -0
- package/dist/cli-templates/harness-commands/cursor/hs-handoff.md +37 -0
- package/dist/cli-templates/harness-commands/cursor/hs-log.md +31 -0
- package/dist/cli-templates/harness-commands/cursor/hs-next.md +32 -0
- package/dist/cli-templates/harness-commands/cursor/hs-promote.md +73 -0
- package/dist/cli-templates/harness-commands/cursor/hs-push.md +38 -0
- package/dist/cli-templates/harness-commands/cursor/hs-qc.md +30 -0
- package/dist/cli-templates/harness-commands/cursor/hs-res.md +67 -0
- package/dist/cli-templates/harness-commands/cursor/hs-resolve.md +26 -0
- package/dist/cli-templates/harness-commands/cursor/hs-review.md +26 -0
- package/dist/cli-templates/harness-commands/cursor/hs-view.md +30 -0
- package/dist/cli-templates/harness-commands/cursor/kill-ports.md +35 -0
- package/dist/cli-templates/harness-commands/cursor/prototype.md +95 -0
- package/docs/mcp-client-config.md +259 -6
- package/package.json +231 -124
- package/plugins/heyspex/.claude-plugin/plugin.json +12 -0
- package/plugins/heyspex/.cursor-plugin/plugin.json +15 -0
- package/plugins/heyspex/.mcp.json +12 -0
- package/plugins/heyspex/bin/launch-mcp.mjs +95 -0
- package/plugins/heyspex/bin/run-hook.mjs +62 -0
- package/plugins/heyspex/hooks/cursor-hooks.json +227 -0
- package/plugins/heyspex/hooks/hooks.json +361 -0
- package/plugins/heyspex/mcp.json +13 -0
- package/scripts/check-mcp-freshness.mjs +17 -9
- package/scripts/claude-hooks/_env-vault-events.mjs +68 -0
- package/scripts/claude-hooks/_guardrail-events.mjs +96 -0
- package/scripts/claude-hooks/_guardrail-loader.mjs +494 -0
- package/scripts/claude-hooks/_intent-classifier-router.mjs +410 -0
- package/scripts/claude-hooks/_intent-classifier-semantic.mjs +351 -0
- package/scripts/claude-hooks/_run.mjs +120 -0
- package/scripts/claude-hooks/_transcript-text.mjs +60 -0
- package/scripts/claude-hooks/_vault-retrieval.mjs +322 -0
- package/scripts/claude-hooks/agent-spawn-delegation-gate.mjs +133 -0
- package/scripts/claude-hooks/capture-workflow-gate.mjs +11 -0
- package/scripts/claude-hooks/check-auto-memory-budget.mjs +396 -0
- package/scripts/claude-hooks/check-claude-md-budget.mjs +327 -0
- package/scripts/claude-hooks/check-mcp-freshness-hook.mjs +14 -0
- package/scripts/claude-hooks/claim-before-code-gate.mjs +11 -0
- package/scripts/claude-hooks/claim-liveness-shared.mjs +575 -0
- package/scripts/claude-hooks/close-terminal-on-ship.mjs +64 -0
- package/scripts/claude-hooks/coo-denylist-gate.mjs +11 -0
- package/scripts/claude-hooks/coo-guard-check.mjs +11 -0
- package/scripts/claude-hooks/deep-research-cost-gate.mjs +11 -0
- package/scripts/claude-hooks/dev-server-task-reminder.mjs +15 -0
- package/scripts/claude-hooks/emit-claim-status.mjs +41 -0
- package/scripts/claude-hooks/enforce-session-sequence.mjs +11 -0
- package/scripts/claude-hooks/enforce-vault-naming.mjs +11 -0
- package/scripts/claude-hooks/env-vault-guard.mjs +11 -0
- package/scripts/claude-hooks/epic-milestone-phrasing-gate.mjs +11 -0
- package/scripts/claude-hooks/git-identity-guard.mjs +11 -0
- package/scripts/claude-hooks/git-reflog-audit.mjs +15 -0
- package/scripts/claude-hooks/git-stash-gate.mjs +11 -0
- package/scripts/claude-hooks/guard-auto-memory-writes.mjs +137 -0
- package/scripts/claude-hooks/id-display-format-gate.mjs +11 -0
- package/scripts/claude-hooks/inject-communication-style.mjs +66 -0
- package/scripts/claude-hooks/inject-coo-seat.mjs +78 -0
- package/scripts/claude-hooks/inject-dev-server-awareness.mjs +11 -0
- package/scripts/claude-hooks/inject-doctrine.mjs +13 -0
- package/scripts/claude-hooks/inject-vault-context.mjs +14 -0
- package/scripts/claude-hooks/intent-classifier-trigger.mjs +247 -0
- package/scripts/claude-hooks/log-session-activity.mjs +341 -0
- package/scripts/claude-hooks/naughty-list-gate.mjs +11 -0
- package/scripts/claude-hooks/notify-status-flip.mjs +72 -0
- package/scripts/claude-hooks/plan-exit-ingest-gate.mjs +170 -0
- package/scripts/claude-hooks/plan-ingest-close-reminder.mjs +66 -0
- package/scripts/claude-hooks/plan-review-gate.mjs +11 -0
- package/scripts/claude-hooks/preflight-reminder.mjs +262 -0
- package/scripts/claude-hooks/protected-action-intent.mjs +221 -0
- package/scripts/claude-hooks/research-method-guard.mjs +398 -0
- package/scripts/claude-hooks/research-protocol-gate.mjs +11 -0
- package/scripts/claude-hooks/session-end.mjs +449 -0
- package/scripts/claude-hooks/settings-write-gate.mjs +11 -0
- package/scripts/claude-hooks/settlement-check-gate.mjs +39 -0
- package/scripts/claude-hooks/status-flip-detect.mjs +17 -0
- package/scripts/claude-hooks/terminal-close-shared.mjs +426 -0
- package/scripts/claude-hooks/terminal-close-watcher.mjs +122 -0
- package/scripts/claude-hooks/wip-checkpoint.mjs +298 -0
- package/scripts/cursor-hooks/manifest.mjs +235 -0
- package/scripts/dev-server-status.mjs +34 -13
- package/scripts/dev-server-stop.mjs +313 -0
- package/scripts/heyspex-mode.mjs +268 -0
- package/scripts/hs-auto-resolve.mjs +240 -0
- package/scripts/hs-auto-source.mjs +398 -0
- package/scripts/hs-auto.mjs +134 -0
- package/scripts/hs-capture-cli.mjs +326 -0
- package/scripts/hs-classifier.mjs +136 -0
- package/scripts/hs-companion.mjs +88 -0
- package/scripts/hs-gateway.mjs +115 -0
- package/scripts/hs-mode.mjs +125 -0
- package/scripts/hs-next.mjs +441 -0
- package/scripts/hs-settings.mjs +338 -0
- package/scripts/hs-view.mjs +333 -0
- package/scripts/hs-worktree.mjs +201 -0
- package/scripts/install-claude-code-hooks.mjs +270 -0
- package/scripts/install-cursor-hooks.mjs +471 -0
- package/scripts/lib/agent-env-markers.mjs +38 -0
- package/scripts/lib/console-url-resolver.mjs +197 -60
- package/scripts/lib/customer-kit.mjs +886 -0
- package/scripts/lib/dev-mode.mjs +244 -28
- package/scripts/lib/heyspex-home.mjs +72 -0
- package/scripts/lib/hs-command.mjs +277 -0
- package/scripts/lib/hs-companion-settings.mjs +472 -0
- package/scripts/lib/hs-source-commit.mjs +59 -5
- package/scripts/lib/hs-source-identity.mjs +526 -0
- package/scripts/lib/hs-source-log.mjs +11 -21
- package/scripts/lib/hs-source-push.mjs +107 -58
- package/scripts/lib/hs-worktree-args.mjs +115 -0
- package/scripts/lib/shared-path-helpers.mjs +134 -0
- package/scripts/lib/standalone-doctor.mjs +540 -3
- package/scripts/lib/subagent-git-lock.mjs +109 -0
- package/scripts/lib/template-pin.mjs +34 -0
- package/scripts/provenance-lib.mjs +91 -0
- package/scripts/stamp-provenance-on-edit.mjs +222 -0
- package/scripts/wip-ledger-health.mjs +653 -0
- package/spexharness/doctrine/baseline/SPEXCODE-DOCTRINE.baseline.json +29 -0
- package/spexharness/src/adapters/audit.mjs +197 -0
- package/spexharness/src/adapters/claude-code.mjs +441 -0
- package/spexharness/src/chat/tui/ansi.mjs +448 -0
- package/spexharness/src/chat/tui/palette.mjs +22 -0
- package/spexharness/src/chat/tui/select.mjs +208 -0
- package/spexharness/src/config-root.mjs +54 -0
- package/spexharness/src/context-budget.mjs +141 -0
- package/spexharness/src/dev-server-control.mjs +389 -0
- package/spexharness/src/doctrine/behaviour.mjs +371 -0
- package/spexharness/src/doctrine/corpus.mjs +733 -0
- package/spexharness/src/doctrine/inject.mjs +338 -0
- package/spexharness/src/doctrine/merge.mjs +184 -0
- package/spexharness/src/doctrine/sources.mjs +252 -0
- package/spexharness/src/doctrine/synthesize.mjs +401 -0
- package/spexharness/src/doctrine/vault-source.mjs +574 -0
- package/spexharness/src/doctrine/writer.mjs +176 -0
- package/spexharness/src/events.mjs +84 -0
- package/spexharness/src/gates/capture-workflow-gate.mjs +202 -0
- package/spexharness/src/gates/claim-before-code-gate.decide.mjs +33 -0
- package/spexharness/src/gates/claim-before-code-gate.mjs +129 -0
- package/spexharness/src/gates/deep-research-cost-gate.mjs +274 -0
- package/spexharness/src/gates/enforce-session-sequence.mjs +895 -0
- package/spexharness/src/gates/enforce-vault-naming.mjs +215 -0
- package/spexharness/src/gates/epic-milestone-phrasing-gate.mjs +233 -0
- package/spexharness/src/gates/git-identity-guard.decide.mjs +436 -0
- package/spexharness/src/gates/git-identity-guard.mjs +61 -0
- package/spexharness/src/gates/git-reflog-audit.mjs +151 -0
- package/spexharness/src/gates/git-stash-gate.decide.mjs +361 -0
- package/spexharness/src/gates/git-stash-gate.mjs +159 -0
- package/spexharness/src/gates/id-display-format-gate.mjs +190 -0
- package/spexharness/src/gates/inject-doctrine-gate.mjs +89 -0
- package/spexharness/src/gates/inject-vault-context.mjs +239 -0
- package/spexharness/src/gates/naughty-list-gate.mjs +213 -0
- package/spexharness/src/gates/plan-review-gate.mjs +233 -0
- package/spexharness/src/gates/preflight-reminder-gate.mjs +265 -0
- package/spexharness/src/gates/research-protocol-gate.mjs +175 -0
- package/spexharness/src/gates/settings-write-gate.decide.mjs +96 -0
- package/spexharness/src/gates/settings-write-gate.mjs +104 -0
- package/spexharness/src/git-reflog-audit.mjs +294 -0
- package/spexharness/src/intent-classifier.mjs +194 -0
- package/spexharness/src/intent-directive.mjs +541 -0
- package/spexharness/src/profile/learn.mjs +551 -0
- package/spexharness/src/profile/proposals.mjs +357 -0
- package/spexharness/src/profile/vault-inclusion.mjs +386 -0
- package/spexharness/src/redact.mjs +151 -0
- package/spexharness/src/rule-table.mjs +819 -0
- package/spexharness/src/seat-context.mjs +122 -0
- package/spexharness/src/session-bootstrap.mjs +512 -0
- package/spexharness/src/session-record.mjs +815 -0
- package/spexharness/src/token-inventory.mjs +207 -0
- package/vendor/heyspex-mcp/account-switch.mjs +95 -0
- package/vendor/heyspex-mcp/alignment-gate.mjs +315 -1
- package/vendor/heyspex-mcp/blocker-resolver.mjs +37 -2
- package/vendor/heyspex-mcp/breakdown-ladder.mjs +119 -4
- package/vendor/heyspex-mcp/breakdown-source.mjs +271 -0
- package/vendor/heyspex-mcp/breakdown-writer.mjs +23 -3
- package/vendor/heyspex-mcp/capture-buffer-patches.mjs +46 -5
- package/vendor/heyspex-mcp/capture-channel.mjs +55 -5
- package/vendor/heyspex-mcp/capture-console-client.mjs +80 -1
- package/vendor/heyspex-mcp/capture-focus.mjs +1035 -29
- package/vendor/heyspex-mcp/capture-outbox.mjs +5 -1
- package/vendor/heyspex-mcp/capture-tools.mjs +1219 -0
- package/vendor/heyspex-mcp/claim-tools.mjs +1124 -0
- package/vendor/heyspex-mcp/cli-verbs.mjs +2076 -0
- package/vendor/heyspex-mcp/cli.mjs +201 -15524
- package/vendor/heyspex-mcp/cloud-tool-surface.mjs +128 -0
- package/vendor/heyspex-mcp/cloud-vault-writes.mjs +412 -0
- package/vendor/heyspex-mcp/config-seed.yaml +15 -0
- package/vendor/heyspex-mcp/config-settings.mjs +290 -7
- package/vendor/heyspex-mcp/console-fetch.mjs +657 -16
- package/vendor/heyspex-mcp/console-transport.mjs +166 -0
- package/vendor/heyspex-mcp/credentials.mjs +584 -0
- package/vendor/heyspex-mcp/defer-spec.mjs +44 -4
- package/vendor/heyspex-mcp/deferred-reclaim.mjs +29 -3
- package/vendor/heyspex-mcp/design-handlers.mjs +31 -0
- package/vendor/heyspex-mcp/design-tools.mjs +4 -1
- package/vendor/heyspex-mcp/dev-factory-bridge.mjs +4 -4
- package/vendor/heyspex-mcp/dev-mode-core.mjs +45 -0
- package/vendor/heyspex-mcp/dev-tools.mjs +124 -0
- package/vendor/heyspex-mcp/diagnose-handlers.mjs +54 -0
- package/vendor/heyspex-mcp/events-tools.mjs +207 -0
- package/vendor/heyspex-mcp/filing-tools.mjs +688 -0
- package/vendor/heyspex-mcp/fleet-handlers.mjs +37 -0
- package/vendor/heyspex-mcp/fleet-queue-tools.mjs +69 -14
- package/vendor/heyspex-mcp/focus-tools.mjs +190 -0
- package/vendor/heyspex-mcp/folder-trust-store.mjs +59 -0
- package/vendor/heyspex-mcp/http-transport.mjs +300 -0
- package/vendor/heyspex-mcp/list-tools.mjs +257 -0
- package/vendor/heyspex-mcp/login-handoff.mjs +15 -1
- package/vendor/heyspex-mcp/mcp-config.mjs +94 -0
- package/vendor/heyspex-mcp/memory-tools.mjs +523 -0
- package/vendor/heyspex-mcp/mvp-mode.mjs +53 -6
- package/vendor/heyspex-mcp/occupied-keys.mjs +141 -0
- package/vendor/heyspex-mcp/on-deck-brief.mjs +3360 -0
- package/vendor/heyspex-mcp/on-deck-scheduler.mjs +1708 -0
- package/vendor/heyspex-mcp/package-root.mjs +32 -0
- package/vendor/heyspex-mcp/peer-presence.mjs +8 -1
- package/vendor/heyspex-mcp/plan-ground.mjs +621 -652
- package/vendor/heyspex-mcp/plan-push.mjs +906 -21
- package/vendor/heyspex-mcp/plan-review.mjs +124 -30
- package/vendor/heyspex-mcp/preflight-gate.mjs +0 -0
- package/vendor/heyspex-mcp/procedure-handlers.mjs +325 -0
- package/vendor/heyspex-mcp/procedure-tools.mjs +540 -351
- package/vendor/heyspex-mcp/promote-capture.mjs +386 -69
- package/vendor/heyspex-mcp/promote-diagnose.mjs +74 -0
- package/vendor/heyspex-mcp/promote-research-to-diagnose.mjs +9 -2
- package/vendor/heyspex-mcp/promote-research.mjs +660 -53
- package/vendor/heyspex-mcp/promotion-helpers.mjs +251 -0
- package/vendor/heyspex-mcp/prompts.mjs +1460 -0
- package/vendor/heyspex-mcp/read-path.mjs +29 -9
- package/vendor/heyspex-mcp/receipt-shape.mjs +146 -0
- package/vendor/heyspex-mcp/recipe-handlers.mjs +117 -0
- package/vendor/heyspex-mcp/regen-tasks.mjs +210 -3
- package/vendor/heyspex-mcp/register-spec.mjs +266 -54
- package/vendor/heyspex-mcp/request-context.mjs +59 -0
- package/vendor/heyspex-mcp/research-focus.mjs +207 -4
- package/vendor/heyspex-mcp/research-handlers.mjs +623 -0
- package/vendor/heyspex-mcp/research-tools.mjs +4 -4
- package/vendor/heyspex-mcp/response-budget.mjs +348 -0
- package/vendor/heyspex-mcp/review-focus.mjs +25 -1
- package/vendor/heyspex-mcp/search-specs.mjs +280 -0
- package/vendor/heyspex-mcp/seat-block-ledger.mjs +113 -0
- package/vendor/heyspex-mcp/seat-identity.mjs +2 -6
- package/vendor/heyspex-mcp/session-owner.mjs +108 -0
- package/vendor/heyspex-mcp/session-tools.mjs +152 -0
- package/vendor/heyspex-mcp/settings-registry.mjs +232 -0
- package/vendor/heyspex-mcp/settings-tools.mjs +85 -12
- package/vendor/heyspex-mcp/spec-edit-tools.mjs +446 -0
- package/vendor/heyspex-mcp/spec-id-lib.mjs +93 -17
- package/vendor/heyspex-mcp/split-research.mjs +301 -56
- package/vendor/heyspex-mcp/stdio-rpc.mjs +22 -0
- package/vendor/heyspex-mcp/submit-for-review.mjs +132 -6
- package/vendor/heyspex-mcp/summary-contract.mjs +105 -0
- package/vendor/heyspex-mcp/summary-write-tools.mjs +224 -0
- package/vendor/heyspex-mcp/symptom-query.mjs +146 -0
- package/vendor/heyspex-mcp/test-helpers.mjs +108 -1
- package/vendor/heyspex-mcp/tool-catalog.mjs +2813 -0
- package/vendor/heyspex-mcp/tool-dispatch.mjs +639 -0
- package/vendor/heyspex-mcp/tool-helpers.mjs +325 -0
- package/vendor/heyspex-mcp/validate-linkage.mjs +414 -27
- package/vendor/heyspex-mcp/vault-io.mjs +724 -0
- package/vendor/heyspex-mcp/vault-less-endpoints.mjs +52 -0
- package/vendor/heyspex-mcp/vault-resolve.mjs +366 -68
- package/vendor/heyspex-mcp/verify-reproduction.mjs +270 -30
- package/vendor/heyspex-mcp/whoami.mjs +91 -0
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
// spexharness/src/doctrine/behaviour.mjs
|
|
2
|
+
//
|
|
3
|
+
// DR1 addendum (owner request, 2026-09-11, mid-build on
|
|
4
|
+
// [[HS-TWTC-8CMW-TASK-dr1-doctrine-corpus-reader-stream-every]]) — folds HeySpex's OWN
|
|
5
|
+
// historical session logs into the Doctrine corpus, as structured BEHAVIOUR evidence
|
|
6
|
+
// rather than transcript text: how sessions actually ran (tools called, gate
|
|
7
|
+
// refusals/blocks, claims made/released, specs advanced), not what the owner said.
|
|
8
|
+
//
|
|
9
|
+
// Two sources, both aggregated (never one observation per raw event — that would be
|
|
10
|
+
// tens of thousands of near-noise rows):
|
|
11
|
+
// - `heyspex-events` — the vault's own event stream, <vault>/specs/events/*.jsonl
|
|
12
|
+
// (`{timestamp, event_kind, actor, spec_id, metadata}`; session id lives at
|
|
13
|
+
// `metadata.sessionId` or `metadata.session_id` depending on the writer).
|
|
14
|
+
// - `heyspex-sesslog` — this repo's local hook-capture sidecars under `.heyspex/`:
|
|
15
|
+
// sesslog-history.jsonl (agent.tool_called, the SAME envelope shape), plus
|
|
16
|
+
// harness-audit.jsonl, guardrail-history.jsonl, claim-history.jsonl, and
|
|
17
|
+
// intent-classifier-history.jsonl — each its own shape; see
|
|
18
|
+
// {@link normalizeBehaviourRecord}.
|
|
19
|
+
// A third, OPTIONAL source — the console's Neon-indexed `/api/agent-sessions` — is
|
|
20
|
+
// folded in as `heyspex-agent-sessions` when the console answers; a down/erroring
|
|
21
|
+
// console is skipped with a note, never a hard failure (see {@link fetchAgentSessionsSummaries}).
|
|
22
|
+
//
|
|
23
|
+
// Grouping: every normalized record is bucketed by (sessionId ?? 'unknown', UTC day)
|
|
24
|
+
// and rendered into ONE `kind: 'behaviour'` observation per bucket — a compact,
|
|
25
|
+
// redacted, <=600-char summary (tool counts, guardrail rule names, claim/spec
|
|
26
|
+
// activity) — never the raw per-event stream.
|
|
27
|
+
//
|
|
28
|
+
// Unlike corpus.mjs's per-line incremental sources, these are FULL-RECOMPUTE every
|
|
29
|
+
// `hs doctrine extract` run: the source files are relatively small (event-stream +
|
|
30
|
+
// sidecars, not multi-hundred-MB session transcripts) and aggregation naturally spans
|
|
31
|
+
// many lines into one row, which does not compose with a line-offset resume. Their
|
|
32
|
+
// output lands in its own file ({@link defaultBehaviourObservationsPath}), REWRITTEN
|
|
33
|
+
// wholesale each run (never appended to), so a re-run never accumulates duplicate
|
|
34
|
+
// summaries the way an append-only log would.
|
|
35
|
+
//
|
|
36
|
+
// Streaming throughout: every file is read via `node:readline` over a
|
|
37
|
+
// `createReadStream`, never a whole-file `readFileSync` of the larger event-stream
|
|
38
|
+
// months (2026-07/08 run tens of MB).
|
|
39
|
+
|
|
40
|
+
import { createReadStream, mkdirSync, writeFileSync } from 'node:fs';
|
|
41
|
+
// The ONE shared console-URL resolver ([[HS-GD45-MCPC-TASK-mcp-cloud-auth-shared-console-resolver]])
|
|
42
|
+
// — replaces this module's own hard-wired `http://localhost:3000` default with
|
|
43
|
+
// env → ~/.heyspex/credentials.yaml console_url → the mode-derived default (no
|
|
44
|
+
// `repoRoot` here, so the admin-local discovery-file rung is never consulted).
|
|
45
|
+
import { resolveConsoleUrl as resolveConsoleUrlShared } from '../../../scripts/lib/console-url-resolver.mjs';
|
|
46
|
+
import { createInterface } from 'node:readline';
|
|
47
|
+
import { dirname, join } from 'node:path';
|
|
48
|
+
|
|
49
|
+
import { redactText } from '../redact.mjs';
|
|
50
|
+
|
|
51
|
+
export const HEYSPEX_EVENTS_SOURCE = 'heyspex-events';
|
|
52
|
+
export const HEYSPEX_SESSLOG_SOURCE = 'heyspex-sesslog';
|
|
53
|
+
export const HEYSPEX_AGENT_SESSIONS_SOURCE = 'heyspex-agent-sessions';
|
|
54
|
+
|
|
55
|
+
const MAX_SUMMARY_CHARS = 600;
|
|
56
|
+
|
|
57
|
+
export function defaultVaultEventsDir(vaultRoot) {
|
|
58
|
+
return join(vaultRoot, 'specs', 'events');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The local hook-capture sidecars under `.heyspex/` this module reads (read-only — never written). */
|
|
62
|
+
export function defaultSesslogFiles(repoRoot) {
|
|
63
|
+
return [
|
|
64
|
+
join(repoRoot, '.heyspex', 'sesslog-history.jsonl'),
|
|
65
|
+
join(repoRoot, '.heyspex', 'harness-audit.jsonl'),
|
|
66
|
+
join(repoRoot, '.heyspex', 'guardrail-history.jsonl'),
|
|
67
|
+
join(repoRoot, '.heyspex', 'claim-history.jsonl'),
|
|
68
|
+
join(repoRoot, '.heyspex', 'intent-classifier-history.jsonl'),
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
export function defaultBehaviourObservationsPath(repoRoot) {
|
|
73
|
+
return join(repoRoot, '.heyspex', 'doctrine', 'behaviour-observations.jsonl');
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Overwrite the behaviour-observations file wholesale (never appended — see module
|
|
78
|
+
* header: these sources are full-recompute every run, so the file always reflects
|
|
79
|
+
* exactly this run's aggregation, never a union with a stale prior run's rows).
|
|
80
|
+
* @param {string} path
|
|
81
|
+
* @param {import('./corpus.mjs').DoctrineObservation[]} observations
|
|
82
|
+
*/
|
|
83
|
+
export function writeBehaviourObservations(path, observations) {
|
|
84
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
85
|
+
const text = observations.map((o) => JSON.stringify(o)).join('\n');
|
|
86
|
+
writeFileSync(path, observations.length ? `${text}\n` : '');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Yield one parsed JSON record per non-blank line of `filePath`. Missing file → nothing (no throw). */
|
|
90
|
+
async function* streamJsonlRecords(filePath) {
|
|
91
|
+
let stream;
|
|
92
|
+
try {
|
|
93
|
+
stream = createReadStream(filePath, { encoding: 'utf8' });
|
|
94
|
+
} catch {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
const rl = createInterface({ input: stream, crlfDelay: Infinity });
|
|
98
|
+
try {
|
|
99
|
+
for await (const line of rl) {
|
|
100
|
+
const trimmed = line.trim();
|
|
101
|
+
if (!trimmed) continue;
|
|
102
|
+
try {
|
|
103
|
+
yield JSON.parse(trimmed);
|
|
104
|
+
} catch {
|
|
105
|
+
// corrupt/partial line — skip, don't throw
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
} catch (err) {
|
|
109
|
+
if (err && (err.code === 'ENOENT' || err.code === 'EISDIR')) return; // missing/odd path — silently empty
|
|
110
|
+
throw err;
|
|
111
|
+
} finally {
|
|
112
|
+
rl.close();
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Normalize one raw record — from the vault's `{timestamp, event_kind, actor,
|
|
118
|
+
* spec_id, metadata}` envelope (shared by `specs/events/*.jsonl` AND
|
|
119
|
+
* sesslog-history.jsonl/guardrail-history.jsonl, which use the SAME shape) or one of
|
|
120
|
+
* the other three sidecars' own shapes — into `{ ts, sessionId, kind, detail }`.
|
|
121
|
+
* Returns `null` for a record with no usable timestamp (nothing to bucket by day).
|
|
122
|
+
*
|
|
123
|
+
* @param {unknown} rec
|
|
124
|
+
* @returns {{ ts: string, sessionId: string|null, kind: string, detail: string }|null}
|
|
125
|
+
*/
|
|
126
|
+
export function normalizeBehaviourRecord(rec) {
|
|
127
|
+
if (!rec || typeof rec !== 'object') return null;
|
|
128
|
+
|
|
129
|
+
// claim-history.jsonl: { specId, agent, sessionId, sessionShort, claimedAt, releasedAt, status }
|
|
130
|
+
if (typeof rec.specId === 'string' && typeof rec.claimedAt === 'string' && rec.event_kind === undefined) {
|
|
131
|
+
return {
|
|
132
|
+
ts: rec.releasedAt ?? rec.claimedAt,
|
|
133
|
+
sessionId: typeof rec.sessionId === 'string' ? rec.sessionId : null,
|
|
134
|
+
kind: rec.releasedAt ? 'claim.released' : 'claim.acquired',
|
|
135
|
+
detail: rec.specId,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// harness-audit.jsonl: { timestamp, actor, action, target, workspace, mode }
|
|
140
|
+
if (typeof rec.action === 'string' && typeof rec.actor === 'string' && rec.event_kind === undefined) {
|
|
141
|
+
if (typeof rec.timestamp !== 'string') return null;
|
|
142
|
+
return { ts: rec.timestamp, sessionId: null, kind: `audit.${rec.action}`, detail: typeof rec.target === 'string' ? rec.target : '' };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// intent-classifier-history.jsonl: { timestamp, event_kind: 'intent_classifier.decision', decision, ... }
|
|
146
|
+
if (rec.event_kind === 'intent_classifier.decision') {
|
|
147
|
+
if (typeof rec.timestamp !== 'string') return null;
|
|
148
|
+
return { ts: rec.timestamp, sessionId: null, kind: 'intent-classifier.decision', detail: typeof rec.decision === 'string' ? rec.decision : '' };
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// The shared envelope: vault specs/events/*.jsonl, sesslog-history.jsonl, guardrail-history.jsonl.
|
|
152
|
+
if (typeof rec.event_kind === 'string') {
|
|
153
|
+
if (typeof rec.timestamp !== 'string') return null;
|
|
154
|
+
const meta = rec && typeof rec.metadata === 'object' && rec.metadata ? rec.metadata : {};
|
|
155
|
+
const sessionId = typeof meta.sessionId === 'string' ? meta.sessionId : typeof meta.session_id === 'string' ? meta.session_id : null;
|
|
156
|
+
let detail = '';
|
|
157
|
+
if (rec.event_kind === 'agent.tool_called') detail = typeof meta.tool_name === 'string' ? meta.tool_name : '';
|
|
158
|
+
else if (rec.event_kind === 'guardrail.fired') detail = typeof meta.rule === 'string' ? meta.rule : '';
|
|
159
|
+
else if (typeof rec.spec_id === 'string') detail = rec.spec_id;
|
|
160
|
+
return { ts: rec.timestamp, sessionId, kind: rec.event_kind, detail };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function dayOf(ts) {
|
|
167
|
+
return typeof ts === 'string' && ts.length >= 10 ? ts.slice(0, 10) : 'unknown-day';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Bucket normalized records by (sessionId ?? 'unknown', UTC day).
|
|
172
|
+
* @param {Array<{ ts: string, sessionId: string|null, kind: string, detail: string }>} records
|
|
173
|
+
* @returns {Array<{ sessionId: string, day: string, kindCounts: Map<string,number>, toolCounts: Map<string,number>, guardrailCounts: Map<string,number>, specs: Set<string>, firstTs: string, lastTs: string }>}
|
|
174
|
+
*/
|
|
175
|
+
export function groupBehaviourRecords(records) {
|
|
176
|
+
const groups = new Map();
|
|
177
|
+
for (const r of records) {
|
|
178
|
+
if (!r || !r.ts) continue;
|
|
179
|
+
const sessionId = r.sessionId ?? 'unknown';
|
|
180
|
+
const day = dayOf(r.ts);
|
|
181
|
+
const key = `${sessionId}::${day}`;
|
|
182
|
+
let g = groups.get(key);
|
|
183
|
+
if (!g) {
|
|
184
|
+
g = { sessionId, day, kindCounts: new Map(), toolCounts: new Map(), guardrailCounts: new Map(), specs: new Set(), firstTs: r.ts, lastTs: r.ts };
|
|
185
|
+
groups.set(key, g);
|
|
186
|
+
}
|
|
187
|
+
g.kindCounts.set(r.kind, (g.kindCounts.get(r.kind) ?? 0) + 1);
|
|
188
|
+
if (r.kind === 'agent.tool_called' && r.detail) g.toolCounts.set(r.detail, (g.toolCounts.get(r.detail) ?? 0) + 1);
|
|
189
|
+
if (r.kind === 'guardrail.fired' && r.detail) g.guardrailCounts.set(r.detail, (g.guardrailCounts.get(r.detail) ?? 0) + 1);
|
|
190
|
+
if (r.detail && (r.kind.startsWith('spec.') || r.kind.startsWith('claim.') || r.kind === 'focus.set')) g.specs.add(r.detail);
|
|
191
|
+
if (r.ts < g.firstTs) g.firstTs = r.ts;
|
|
192
|
+
if (r.ts > g.lastTs) g.lastTs = r.ts;
|
|
193
|
+
}
|
|
194
|
+
return [...groups.values()];
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function topEntries(map, limit) {
|
|
198
|
+
return [...map.entries()]
|
|
199
|
+
.sort((a, b) => b[1] - a[1])
|
|
200
|
+
.slice(0, limit)
|
|
201
|
+
.map(([k, v]) => `${k}×${v}`)
|
|
202
|
+
.join(', ');
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* Render one bucket into a compact (<=600 char) summary: tools called with counts,
|
|
207
|
+
* guardrail refusals/blocks with rule names, claims made/released and specs advanced,
|
|
208
|
+
* and the session's spec ids.
|
|
209
|
+
* @param {ReturnType<typeof groupBehaviourRecords>[number]} g
|
|
210
|
+
* @returns {string}
|
|
211
|
+
*/
|
|
212
|
+
export function summarizeBehaviourGroup(g) {
|
|
213
|
+
const parts = [];
|
|
214
|
+
if (g.toolCounts.size) parts.push(`tools: ${topEntries(g.toolCounts, 8)}`);
|
|
215
|
+
if (g.guardrailCounts.size) parts.push(`guardrail blocks: ${topEntries(g.guardrailCounts, 8)}`);
|
|
216
|
+
const acquired = g.kindCounts.get('claim.acquired') ?? 0;
|
|
217
|
+
const released = g.kindCounts.get('claim.released') ?? 0;
|
|
218
|
+
if (acquired || released) parts.push(`claims: ${acquired} acquired, ${released} released`);
|
|
219
|
+
const statusChanges = g.kindCounts.get('spec.status_change') ?? 0;
|
|
220
|
+
if (statusChanges) parts.push(`${statusChanges} status change(s)`);
|
|
221
|
+
const permissionEvents = [...g.kindCounts.keys()].filter((k) => k.startsWith('audit.'));
|
|
222
|
+
if (permissionEvents.length) parts.push(`audit: ${permissionEvents.map((k) => `${k}×${g.kindCounts.get(k)}`).join(', ')}`);
|
|
223
|
+
if (g.specs.size) parts.push(`specs: ${[...g.specs].slice(0, 12).join(', ')}`);
|
|
224
|
+
|
|
225
|
+
let text = `Session ${g.sessionId} on ${g.day} — ${parts.length ? parts.join('; ') : 'no notable activity'}.`;
|
|
226
|
+
if (text.length > MAX_SUMMARY_CHARS) text = `${text.slice(0, MAX_SUMMARY_CHARS - 1)}…`;
|
|
227
|
+
return text;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
/**
|
|
231
|
+
* Build `kind: 'behaviour'` observations from a flat list of normalized records —
|
|
232
|
+
* one per (session, day) bucket, sorted deterministically (day, then session).
|
|
233
|
+
* @param {Array<{ ts: string, sessionId: string|null, kind: string, detail: string }>} records
|
|
234
|
+
* @param {{ source: string, project: string, redact?: (text: string) => string }} opts
|
|
235
|
+
* @returns {import('./corpus.mjs').DoctrineObservation[]}
|
|
236
|
+
*/
|
|
237
|
+
export function buildBehaviourObservations(records, { source, project, redact = redactText }) {
|
|
238
|
+
const groups = groupBehaviourRecords(records).sort((a, b) => a.day.localeCompare(b.day) || a.sessionId.localeCompare(b.sessionId));
|
|
239
|
+
return groups.map((g, i) => ({
|
|
240
|
+
id: `${source}:${project}:${g.sessionId}:${g.day}`,
|
|
241
|
+
source,
|
|
242
|
+
project,
|
|
243
|
+
session: g.sessionId,
|
|
244
|
+
ts: g.lastTs,
|
|
245
|
+
seq: i + 1,
|
|
246
|
+
kind: 'behaviour',
|
|
247
|
+
text: redact(summarizeBehaviourGroup(g)),
|
|
248
|
+
}));
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/** Stream every `*.jsonl` file directly under `dir` (non-recursive — the vault's events dir is flat). */
|
|
252
|
+
async function collectFromDir(dir) {
|
|
253
|
+
const { readdirSync } = await import('node:fs');
|
|
254
|
+
let entries = [];
|
|
255
|
+
try {
|
|
256
|
+
entries = readdirSync(dir, { withFileTypes: true });
|
|
257
|
+
} catch {
|
|
258
|
+
return { records: [], filesRead: 0, linesRead: 0 };
|
|
259
|
+
}
|
|
260
|
+
const records = [];
|
|
261
|
+
let filesRead = 0;
|
|
262
|
+
let linesRead = 0;
|
|
263
|
+
for (const ent of entries) {
|
|
264
|
+
if (!ent.isFile() || !ent.name.endsWith('.jsonl')) continue;
|
|
265
|
+
filesRead++;
|
|
266
|
+
for await (const rec of streamJsonlRecords(join(dir, ent.name))) {
|
|
267
|
+
linesRead++;
|
|
268
|
+
const normalized = normalizeBehaviourRecord(rec);
|
|
269
|
+
if (normalized) records.push(normalized);
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return { records, filesRead, linesRead };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async function collectFromFiles(paths) {
|
|
276
|
+
const records = [];
|
|
277
|
+
let filesRead = 0;
|
|
278
|
+
let linesRead = 0;
|
|
279
|
+
for (const p of paths) {
|
|
280
|
+
filesRead++;
|
|
281
|
+
for await (const rec of streamJsonlRecords(p)) {
|
|
282
|
+
linesRead++;
|
|
283
|
+
const normalized = normalizeBehaviourRecord(rec);
|
|
284
|
+
if (normalized) records.push(normalized);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
return { records, filesRead, linesRead };
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Collect + aggregate BOTH behaviour sources (`heyspex-events`, `heyspex-sesslog`).
|
|
292
|
+
* Full-recompute every call — see module header. Never throws on a missing file/dir
|
|
293
|
+
* (silently empty).
|
|
294
|
+
*
|
|
295
|
+
* @param {{ vaultEventsDir: string, sesslogFiles: string[], project: string, redact?: (text: string) => string }} opts
|
|
296
|
+
* @returns {Promise<{ observations: import('./corpus.mjs').DoctrineObservation[], countsBySource: Record<string, number>, stats: Record<string, { filesRead: number, linesRead: number }> }>}
|
|
297
|
+
*/
|
|
298
|
+
export async function collectBehaviourObservations({ vaultEventsDir, sesslogFiles, project, redact = redactText }) {
|
|
299
|
+
const events = await collectFromDir(vaultEventsDir);
|
|
300
|
+
const sesslog = await collectFromFiles(sesslogFiles);
|
|
301
|
+
|
|
302
|
+
const eventsObs = buildBehaviourObservations(events.records, { source: HEYSPEX_EVENTS_SOURCE, project, redact });
|
|
303
|
+
const sesslogObs = buildBehaviourObservations(sesslog.records, { source: HEYSPEX_SESSLOG_SOURCE, project, redact });
|
|
304
|
+
|
|
305
|
+
return {
|
|
306
|
+
observations: [...eventsObs, ...sesslogObs],
|
|
307
|
+
countsBySource: { [HEYSPEX_EVENTS_SOURCE]: eventsObs.length, [HEYSPEX_SESSLOG_SOURCE]: sesslogObs.length },
|
|
308
|
+
stats: {
|
|
309
|
+
[HEYSPEX_EVENTS_SOURCE]: { filesRead: events.filesRead, linesRead: events.linesRead },
|
|
310
|
+
[HEYSPEX_SESSLOG_SOURCE]: { filesRead: sesslog.filesRead, linesRead: sesslog.linesRead },
|
|
311
|
+
},
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* OPTIONAL third source: the console's Neon-indexed `/api/agent-sessions`. A down
|
|
317
|
+
* console, a non-2xx response, or any network failure is reported as `{ skipped: true,
|
|
318
|
+
* reason }` — never thrown — per the owner's "if it 500s, skip with a note" ask.
|
|
319
|
+
*
|
|
320
|
+
* @param {{ baseUrl?: string, project?: string, redact?: (text: string) => string, fetchFn?: typeof fetch, timeoutMs?: number }} [opts]
|
|
321
|
+
* @returns {Promise<{ observations: import('./corpus.mjs').DoctrineObservation[], skipped: boolean, reason?: string }>}
|
|
322
|
+
*/
|
|
323
|
+
export async function fetchAgentSessionsSummaries({
|
|
324
|
+
baseUrl = resolveConsoleUrlShared(),
|
|
325
|
+
project = 'heyspex-app',
|
|
326
|
+
redact = redactText,
|
|
327
|
+
fetchFn = typeof fetch === 'function' ? fetch : null,
|
|
328
|
+
timeoutMs = 3000,
|
|
329
|
+
} = {}) {
|
|
330
|
+
if (!fetchFn) return { observations: [], skipped: true, reason: 'no fetch implementation available' };
|
|
331
|
+
const ac = new AbortController();
|
|
332
|
+
const timer = setTimeout(() => ac.abort(), timeoutMs);
|
|
333
|
+
let res;
|
|
334
|
+
try {
|
|
335
|
+
res = await fetchFn(`${baseUrl.replace(/\/$/, '')}/api/agent-sessions`, { signal: ac.signal });
|
|
336
|
+
} catch (err) {
|
|
337
|
+
return { observations: [], skipped: true, reason: `unreachable: ${err?.message ?? String(err)}` };
|
|
338
|
+
} finally {
|
|
339
|
+
clearTimeout(timer);
|
|
340
|
+
}
|
|
341
|
+
if (!res || !res.ok) {
|
|
342
|
+
return { observations: [], skipped: true, reason: `console responded ${res?.status ?? 'with no status'}` };
|
|
343
|
+
}
|
|
344
|
+
let body;
|
|
345
|
+
try {
|
|
346
|
+
body = await res.json();
|
|
347
|
+
} catch (err) {
|
|
348
|
+
return { observations: [], skipped: true, reason: `unparseable response: ${err?.message ?? String(err)}` };
|
|
349
|
+
}
|
|
350
|
+
const sessions = Array.isArray(body) ? body : Array.isArray(body?.sessions) ? body.sessions : [];
|
|
351
|
+
if (!sessions.length) return { observations: [], skipped: false };
|
|
352
|
+
|
|
353
|
+
const observations = sessions.map((s, i) => {
|
|
354
|
+
const sessionId = s.sessionId ?? s.id ?? `session-${i}`;
|
|
355
|
+
const ts = s.updatedAt ?? s.startedAt ?? s.createdAt ?? new Date().toISOString();
|
|
356
|
+
const specs = Array.isArray(s.specIds) ? s.specIds : Array.isArray(s.specs) ? s.specs : [];
|
|
357
|
+
let text = `Console-indexed session ${sessionId} — status ${s.status ?? 'unknown'}${specs.length ? `, specs: ${specs.slice(0, 12).join(', ')}` : ''}.`;
|
|
358
|
+
if (text.length > MAX_SUMMARY_CHARS) text = `${text.slice(0, MAX_SUMMARY_CHARS - 1)}…`;
|
|
359
|
+
return {
|
|
360
|
+
id: `${HEYSPEX_AGENT_SESSIONS_SOURCE}:${project}:${sessionId}:${dayOf(ts)}`,
|
|
361
|
+
source: HEYSPEX_AGENT_SESSIONS_SOURCE,
|
|
362
|
+
project,
|
|
363
|
+
session: sessionId,
|
|
364
|
+
ts,
|
|
365
|
+
seq: i + 1,
|
|
366
|
+
kind: 'behaviour',
|
|
367
|
+
text: redact(text),
|
|
368
|
+
};
|
|
369
|
+
});
|
|
370
|
+
return { observations, skipped: false };
|
|
371
|
+
}
|