@khoavu882/doflow 2.0.0-beta.4 → 2.0.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 +12 -11
- package/bin/doflow.js +21 -11
- package/core/.antigravity-plugin/plugin.json +1 -3
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/lib.sh +43 -24
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
- package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
- package/core/harnesses/claude/hooks/post-compact.sh +35 -32
- package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
- package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
- package/core/harnesses/claude/hooks/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/session-start.sh +5 -3
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- package/core/harnesses/claude/hooks/stop-check.sh +5 -3
- package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
- package/core/harnesses/claude/settings/settings.json +18 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
- package/core/harnesses/codex/agents/core-implementer.toml +1 -0
- package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
- package/core/harnesses/codex/agents/research-writer.toml +1 -1
- package/core/harnesses/codex/agents/reviewer.toml +10 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
- package/core/harnesses/codex/agents/system-architect.toml +1 -1
- package/core/harnesses/codex/hooks/lib.sh +44 -25
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
- package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
- package/core/harnesses/codex/hooks/session-start.sh +5 -3
- package/core/harnesses/codex/hooks/stop-check.sh +6 -5
- package/core/harnesses/codex/hooks/subagent-audit.sh +18 -6
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
- package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
- package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow.json +57 -0
- package/core/harnesses/kiro/agents/doflow.json +1 -1
- package/core/harnesses/kiro/hooks/lib.sh +26 -6
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +21 -5
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +21 -5
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +21 -5
- package/core/harnesses/kiro/hooks/session-start.sh +12 -5
- package/core/harnesses/kiro/hooks/stop-check.sh +22 -30
- package/core/harnesses/pi/extensions/doflow-guard.js +167 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -12
- package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
- package/core/harnesses/shared/hooks/bridge/bridge.sh +277 -0
- package/core/harnesses/shared/hooks/bridge/envelope.jq +129 -0
- package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +70 -0
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +51 -16
- package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
- package/core/harnesses/shared/hooks/policies/contract.sh +78 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +35 -9
- package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
- package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
- package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
- package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +65 -40
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +48 -35
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +284 -33
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +180 -110
- package/core/harnesses/shared/hooks/policies/session-context.sh +66 -7
- package/core/harnesses/shared/hooks/policies/stop-check.sh +61 -23
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +39 -6
- package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +97 -159
- package/core/harnesses/shared/hooks/stream-hook-runner.js +114 -187
- package/core/harnesses/shared/locator/doflow-run +2 -3
- package/core/registry/advice.json +3 -3
- package/core/registry/assets.json +72 -80
- package/core/registry/capabilities.json +12 -0
- package/core/registry/contracts.json +60 -33
- package/core/registry/contribution-points.json +32 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1542 -277
- package/core/registry/lifecycle.json +2837 -37
- package/core/registry/mcp.json +5 -1
- package/core/registry/models.json +7 -1
- package/core/registry/package-templates/README.md +19 -0
- package/core/registry/package-templates/agent/files/agent.md +9 -0
- package/core/registry/package-templates/agent/template.json +4 -0
- package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
- package/core/registry/package-templates/capability-provider/template.json +11 -0
- package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
- package/core/registry/package-templates/debate-preset/template.json +4 -0
- package/core/registry/package-templates/review-preset/files/preset.json +30 -0
- package/core/registry/package-templates/review-preset/template.json +4 -0
- package/core/registry/package-templates/skill/files/SKILL.md +8 -0
- package/core/registry/package-templates/skill/template.json +4 -0
- package/core/registry/package-templates/worker/files/worker.json +92 -0
- package/core/registry/package-templates/worker/template.json +11 -0
- package/core/registry/packages/doflow/doflow-package.json +15 -0
- package/core/registry/packages/doflow/presets/debate-3.json +26 -0
- package/core/registry/protocols.json +61 -0
- package/core/registry/routes.json +17 -0
- package/core/registry/rules.json +431 -0
- package/core/registry/settings.json +276 -0
- package/core/registry/storage.json +701 -30
- package/core/registry/telemetry.json +236 -0
- package/core/registry/transcripts.json +142 -0
- package/core/registry/verification.json +76 -1
- package/core/registry/workflows.json +55 -0
- package/core/shared/agent-specs/core-implementer.md +3 -0
- package/core/shared/agent-specs/delivery-lead.md +68 -0
- package/core/shared/agent-specs/quality-guardian.md +5 -1
- package/core/shared/agent-specs/research-writer.md +10 -3
- package/core/shared/agent-specs/reviewer.md +53 -0
- package/core/shared/agent-specs/spec-analyst.md +5 -6
- package/core/shared/agent-specs/system-architect.md +5 -1
- package/core/shared/guidance/DOFLOW_CORE.md +1 -5
- package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
- package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
- package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
- package/core/shared/guidance/pointers/codex.md +1 -4
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
- package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
- package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
- package/core/shared/guidance/rules/RULES.md +45 -0
- package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
- package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
- package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
- package/core/shared/scripts/doflow/bin/doflow-run +154 -35
- package/core/shared/skills/do/SKILL.md +12 -10
- package/core/shared/skills/do/references/advice_calls.md +26 -0
- package/core/shared/skills/do/references/pm_routing.md +1 -2
- package/core/shared/skills/do/references/program_mode.md +31 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
- package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
- package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
- package/core/shared/skills/do-code-review/SKILL.md +41 -144
- package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
- package/core/shared/skills/do-code-review/references/tools.md +90 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
- package/core/shared/skills/do-design/SKILL.md +24 -23
- package/core/shared/skills/do-diagnose/SKILL.md +14 -4
- package/core/shared/skills/do-document/SKILL.md +11 -1
- package/core/shared/skills/do-document/references/deep_research.md +1 -2
- package/core/shared/skills/do-document/references/embedded-research.md +18 -6
- package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +17 -59
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +28 -1
- package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
- package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
- package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
- package/core/shared/skills/do-flow/SKILL.md +8 -9
- package/core/shared/skills/do-flow/references/task_classes.md +2 -2
- package/core/shared/skills/do-git/SKILL.md +4 -0
- package/core/shared/skills/do-implement/SKILL.md +7 -9
- package/core/shared/skills/do-plan/SKILL.md +29 -19
- package/core/shared/skills/do-test/SKILL.md +7 -4
- package/core/shared/templates/doflow/constitution-template.md +9 -3
- package/core/shared/templates/doflow/data-model-template.md +1 -1
- package/core/shared/templates/doflow/design-template.md +113 -16
- package/core/shared/templates/doflow/plan-template.md +11 -10
- package/core/shared/templates/doflow/question-log-template.md +14 -2
- package/core/shared/templates/doflow/requirement-template.md +1 -1
- package/core/worker-evidence/antigravity/checks.json +14 -0
- package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
- package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
- package/core/worker-evidence/codex/checks.json +38 -0
- package/package.json +10 -6
- package/src/adapters/antigravity/index.js +226 -113
- package/src/adapters/claude/index.js +39 -5
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/hooks.js +40 -5
- package/src/adapters/codex/index.js +100 -22
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +294 -48
- package/src/adapters/guidance-manifest.js +71 -0
- package/src/adapters/guidance-pointer.js +19 -0
- package/src/adapters/hook-commands.js +6 -10
- package/src/adapters/index.js +1 -0
- package/src/adapters/kiro/index.js +38 -13
- package/src/adapters/opencode/index.js +8 -8
- package/src/adapters/pi/hooks.js +120 -0
- package/src/adapters/pi/index.js +48 -16
- package/src/adapters/resolve-source.js +26 -2
- package/src/cli/commands/install.js +3 -2
- package/src/cli/commands/reconcile.js +53 -9
- package/src/cli/commands/remove.js +2 -2
- package/src/cli/commands/rollback.js +6 -3
- package/src/cli/commands/self-update.js +18 -9
- package/src/cli/commands/status.js +21 -2
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +3 -2
- package/src/cli/index.js +338 -33
- package/src/cli/runtime-commands.js +243 -52
- package/src/cli/shared.js +0 -2
- package/src/helper/git.js +1 -1
- package/src/helper/guardrails.js +1 -1
- package/src/helper/marker-merge.js +31 -13
- package/src/helper/package-ids.js +37 -0
- package/src/helper/semver.js +94 -0
- package/src/install/backup.js +105 -15
- package/src/install/context.js +1 -1
- package/src/install/targets.js +31 -16
- package/src/lifecycle/coverage.js +74 -0
- package/src/lifecycle/hook-step.js +61 -0
- package/src/lifecycle/hook-tier.js +160 -0
- package/src/lifecycle/index.js +434 -86
- package/src/lifecycle/retired-harnesses.js +318 -0
- package/src/lifecycle/view.js +35 -15
- package/src/registry/index.js +686 -32
- package/src/registry/worker-ids.js +45 -0
- package/src/release/build-package.js +0 -3
- package/src/release/docs-blocks.js +492 -0
- package/src/release/preflight.js +425 -0
- package/src/release/rehearse.js +401 -0
- package/src/release/release-workflow.js +733 -0
- package/src/release/sync-plugin-versions.js +43 -7
- package/src/runtime/advice/bench-gate.js +25 -0
- package/src/runtime/advice/cli.js +59 -16
- package/src/runtime/advice/client.js +32 -9
- package/src/runtime/advice/config.js +98 -133
- package/src/runtime/advice/doctor.js +1 -1
- package/src/runtime/advice/elevation.js +97 -0
- package/src/runtime/advice/endpoint-guard.js +9 -3
- package/src/runtime/advice/mode.js +37 -0
- package/src/runtime/advice/record.js +18 -5
- package/src/runtime/advice/registry.js +4 -1
- package/src/runtime/advice/shadow-config.js +229 -0
- package/src/runtime/advice/shadow-dispatcher.js +276 -0
- package/src/runtime/brief-rules.js +81 -0
- package/src/runtime/brief-tools.js +111 -34
- package/src/runtime/candidate.js +514 -0
- package/src/runtime/capabilities/executor.js +450 -0
- package/src/runtime/capabilities/loopback.js +41 -0
- package/src/runtime/capabilities/sanitizer.js +88 -0
- package/src/runtime/capability-router.js +135 -3
- package/src/runtime/checkouts.js +73 -11
- package/src/runtime/claims.js +27 -12
- package/src/runtime/cli-result.js +11 -4
- package/src/runtime/cli.js +221 -277
- package/src/runtime/config-home.js +42 -0
- package/src/runtime/context/arbitrate.js +767 -0
- package/src/runtime/context/budget.js +203 -0
- package/src/runtime/context/compose.js +1367 -0
- package/src/runtime/context/delivery-store.js +100 -0
- package/src/runtime/context/emission-rules.js +118 -0
- package/src/runtime/context/identity.js +227 -0
- package/src/runtime/context/mandatory.js +594 -0
- package/src/runtime/context/pointer.js +142 -0
- package/src/runtime/context/primary-command.js +117 -0
- package/src/runtime/context/primary.js +344 -0
- package/src/runtime/context/summary-store.js +655 -0
- package/src/runtime/context-pack.js +1106 -245
- package/src/runtime/decision-register.js +13 -4
- package/src/runtime/dispatch/admission.js +392 -0
- package/src/runtime/dispatch/attempt-end.js +274 -0
- package/src/runtime/dispatch/command.js +628 -0
- package/src/runtime/dispatch/controls.js +904 -0
- package/src/runtime/dispatch/envelope.js +276 -0
- package/src/runtime/dispatch/grants.js +310 -0
- package/src/runtime/dispatch/integrate.js +617 -0
- package/src/runtime/dispatch/loop.js +1835 -0
- package/src/runtime/dispatch/store.js +352 -0
- package/src/runtime/dispatch/verify-run.js +201 -0
- package/src/runtime/evidence-fields.js +39 -0
- package/src/runtime/evidence-ledger.js +41 -7
- package/src/runtime/failure/capture.js +3 -1
- package/src/runtime/failure/cli.js +25 -9
- package/src/runtime/failure/home.js +57 -30
- package/src/runtime/failure/store.js +7 -2
- package/src/runtime/feature-pointer.js +174 -0
- package/src/runtime/feature-resolve.js +13 -2
- package/src/runtime/fleet/command.js +77 -0
- package/src/runtime/fleet/projects.js +178 -0
- package/src/runtime/fleet/tree.js +383 -0
- package/src/runtime/freshness.js +77 -10
- package/src/runtime/git-memo.js +46 -0
- package/src/runtime/guidance/ablation.js +427 -0
- package/src/runtime/health.js +565 -17
- package/src/runtime/history-compactor.js +13 -3
- package/src/runtime/implementation-gate.js +277 -22
- package/src/runtime/inventory/coverage.js +1 -1
- package/src/runtime/inventory/index.js +1 -1
- package/src/runtime/inventory/precedence.js +2 -2
- package/src/runtime/inventory/siblings.js +6 -4
- package/src/runtime/knowledge/code-corpus.js +277 -0
- package/src/runtime/knowledge/code-lexical.js +130 -0
- package/src/runtime/knowledge/code-retrieval.js +307 -0
- package/src/runtime/knowledge/code-validation.js +106 -0
- package/src/runtime/knowledge/gp3-gate.js +74 -0
- package/src/runtime/knowledge/graph-adapter.js +288 -0
- package/src/runtime/knowledge/graph-fallback.js +394 -0
- package/src/runtime/knowledge/hybrid.js +136 -8
- package/src/runtime/knowledge/retrieval.js +34 -1
- package/src/runtime/knowledge/rtk-compress.js +77 -0
- package/src/runtime/knowledge/semble-bridge.py +106 -0
- package/src/runtime/knowledge/semble-lifecycle.js +114 -0
- package/src/runtime/knowledge/semble-provider.js +365 -0
- package/src/runtime/lifecycle/cli.js +16 -6
- package/src/runtime/lifecycle/event-store.js +8 -6
- package/src/runtime/lifecycle/followup.js +6 -1
- package/src/runtime/lifecycle/overview.js +24 -1
- package/src/runtime/lifecycle/report-store.js +8 -10
- package/src/runtime/lifecycle/retention.js +95 -31
- package/src/runtime/lifecycle/root.js +9 -1
- package/src/runtime/lifecycle/status.js +34 -17
- package/src/runtime/lifecycle/store-upkeep.js +56 -7
- package/src/runtime/mask.js +134 -7
- package/src/runtime/mcp-consultations.js +40 -6
- package/src/runtime/model-router.js +79 -7
- package/src/runtime/models/catalogue.js +191 -0
- package/src/runtime/models/command.js +153 -0
- package/src/runtime/off-words.js +12 -0
- package/src/runtime/outcome.js +3 -3
- package/src/runtime/packages/command.js +1009 -0
- package/src/runtime/packages/contributions.js +116 -0
- package/src/runtime/packages/digest.js +132 -0
- package/src/runtime/packages/fetch.js +182 -0
- package/src/runtime/packages/grants.js +192 -0
- package/src/runtime/packages/index-file.js +134 -0
- package/src/runtime/packages/instruction.js +90 -0
- package/src/runtime/packages/manifest.js +331 -0
- package/src/runtime/packages/points.js +477 -0
- package/src/runtime/packages/programs.js +216 -0
- package/src/runtime/packages/scaffold.js +277 -0
- package/src/runtime/packages/source.js +90 -0
- package/src/runtime/packages/state.js +164 -0
- package/src/runtime/packages/store.js +449 -0
- package/src/runtime/packages/tar.js +106 -0
- package/src/runtime/packages/view.js +406 -0
- package/src/runtime/plan-check.js +59 -0
- package/src/runtime/plan-content.js +324 -0
- package/src/runtime/policy-init.js +118 -0
- package/src/runtime/profiles/doctor.js +96 -0
- package/src/runtime/profiles/resolve.js +244 -0
- package/src/runtime/profiles/rollback.js +254 -0
- package/src/runtime/profiles/sampler.js +41 -0
- package/src/runtime/readiness-record.js +16 -11
- package/src/runtime/readiness.js +127 -16
- package/src/runtime/research-request.js +50 -25
- package/src/runtime/retrieval-plan.js +402 -73
- package/src/runtime/retrieval-record.js +293 -0
- package/src/runtime/review/command.js +246 -0
- package/src/runtime/review/dominance.js +68 -0
- package/src/runtime/review/findings.js +434 -0
- package/src/runtime/review/identity.js +255 -0
- package/src/runtime/review/protocol-command.js +109 -0
- package/src/runtime/review/protocols.js +405 -0
- package/src/runtime/review/resolve-protocol.js +302 -0
- package/src/runtime/review/start.js +530 -0
- package/src/runtime/roadmap/facts.js +479 -0
- package/src/runtime/roadmap/gates.js +270 -0
- package/src/runtime/roadmap/loop.js +1636 -0
- package/src/runtime/roadmap/merge.js +381 -0
- package/src/runtime/roadmap/rows.js +1113 -0
- package/src/runtime/roadmap/seed.js +951 -0
- package/src/runtime/rules-report.js +336 -0
- package/src/runtime/scaffold/artifacts.js +63 -9
- package/src/runtime/scaffold/generate.js +21 -9
- package/src/runtime/settings/autopilot.js +106 -0
- package/src/runtime/settings/cli.js +280 -0
- package/src/runtime/settings/config-write.js +314 -0
- package/src/runtime/settings/files.js +271 -0
- package/src/runtime/settings/legacy.js +154 -0
- package/src/runtime/settings/lexical.js +56 -0
- package/src/runtime/settings/prepared-run.js +269 -0
- package/src/runtime/settings/project-layers.js +118 -0
- package/src/runtime/settings/refs.js +288 -0
- package/src/runtime/settings/registry.js +553 -0
- package/src/runtime/settings/resolve.js +713 -0
- package/src/runtime/settings/shared.js +37 -0
- package/src/runtime/settings/worker-billing.js +107 -0
- package/src/runtime/settings/write.js +269 -0
- package/src/runtime/slot-command.js +78 -0
- package/src/runtime/slots.js +671 -0
- package/src/runtime/source-version.js +290 -0
- package/src/runtime/state-root.js +217 -0
- package/src/runtime/storage/cli.js +12 -5
- package/src/runtime/storage/engine.js +9 -2
- package/src/runtime/storage/registry.js +14 -1
- package/src/runtime/storage/roots.js +66 -16
- package/src/runtime/storage/rules.js +82 -10
- package/src/runtime/storage/upkeep.js +10 -2
- package/src/runtime/storage/work-status.js +245 -26
- package/src/runtime/task-attempts.js +589 -69
- package/src/runtime/task-classifier.js +47 -11
- package/src/runtime/task-graph.js +373 -0
- package/src/runtime/task-run.js +778 -0
- package/src/runtime/task-scope.js +202 -56
- package/src/runtime/task-sources.js +147 -0
- package/src/runtime/task-state.js +440 -65
- package/src/runtime/team/admission.js +301 -0
- package/src/runtime/team/envelope.js +324 -0
- package/src/runtime/team/review-guard.js +156 -0
- package/src/runtime/trace/analyze.js +427 -0
- package/src/runtime/trace/baseline.js +770 -0
- package/src/runtime/trace/ledger.js +63 -140
- package/src/runtime/trace/otlp-export.js +568 -0
- package/src/runtime/trace/otlp-map.js +481 -0
- package/src/runtime/trace/spans.js +745 -0
- package/src/runtime/trace/telemetry.js +225 -0
- package/src/runtime/trace/views.js +1 -0
- package/src/runtime/transcripts/command.js +98 -0
- package/src/runtime/transcripts/cursor.js +326 -0
- package/src/runtime/transcripts/read.js +126 -0
- package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
- package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
- package/src/runtime/transcripts/readers/copilot-events.js +90 -0
- package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
- package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
- package/src/runtime/transcripts/registry.js +161 -0
- package/src/runtime/transcripts/stats.js +161 -0
- package/src/runtime/verification/check-key.js +628 -0
- package/src/runtime/verification/contract-runner.js +546 -11
- package/src/runtime/verification/engine.js +499 -9
- package/src/runtime/verification/impact.js +617 -0
- package/src/runtime/verification/inflight.js +205 -0
- package/src/runtime/verification/registry.js +176 -0
- package/src/runtime/verification/reuse.js +184 -0
- package/src/runtime/verification/telemetry-emit.js +254 -0
- package/src/runtime/verification/test-selection.js +67 -7
- package/src/runtime/worker/active.js +90 -0
- package/src/runtime/worker/cancel.js +222 -0
- package/src/runtime/worker/catalogue.js +122 -0
- package/src/runtime/worker/command.js +677 -0
- package/src/runtime/worker/evidence.js +288 -0
- package/src/runtime/worker/launch.js +288 -0
- package/src/runtime/worker/models.js +137 -0
- package/src/runtime/worker/parsers.js +218 -0
- package/src/runtime/worker/probes/index.js +706 -0
- package/src/runtime/worker/process-tree.js +415 -0
- package/src/runtime/worker/quota.js +43 -0
- package/src/runtime/worker/readiness.js +713 -0
- package/src/runtime/worker/run-store.js +166 -0
- package/src/runtime/worker/supervisor.js +541 -0
- package/src/runtime/worker/worktree-check.js +252 -0
- package/src/runtime/workflow-engine.js +5 -1
- package/src/runtime/workflow-orchestrator.js +578 -63
- package/src/runtime/worktree.js +149 -22
- package/src/state/index.js +1 -1
- package/src/state/lockfile.js +64 -10
- package/src/state/run-lock.js +1 -1
- package/types/node-commonjs.d.ts +106 -7
- package/core/.antigravity-plugin/hooks.json +0 -39
- package/core/harnesses/gemini/hooks/hooks.json +0 -77
- package/core/harnesses/gemini/hooks/lib.sh +0 -302
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -21
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -21
- package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -22
- package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
- package/core/harnesses/gemini/hooks/session-end.sh +0 -5
- package/core/harnesses/gemini/hooks/session-start.sh +0 -5
- package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
- package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
- package/core/harnesses/shared/hooks/hooks.json +0 -47
- package/core/shared/guidance/FLAGS.md +0 -25
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
- package/src/adapters/gemini/hooks.js +0 -210
- package/src/adapters/gemini/index.js +0 -390
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
const nodeFs = require('node:fs');
|
|
6
|
+
const path = require('node:path');
|
|
7
|
+
const { resolveRunsLocation } = require('./ledger.js');
|
|
8
|
+
const { identityFields } = require('../context/identity.js');
|
|
9
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
10
|
+
const { loadProtocols, presetIds } = require('../review/protocols.js');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Telemetry contract v1 (088 A.1, IC-001, IC-002): the switch and the writer for the lines that
|
|
14
|
+
* live in `telemetry/<UTC date>.jsonl`, a subdirectory of the run ledger's own directory.
|
|
15
|
+
*
|
|
16
|
+
* Why not `RunLedger`: it drops every line that has no `verb` (ledger.ts `sanitizeRunEvent`) and its
|
|
17
|
+
* readers list only the `YYYY-MM-DD.jsonl` files directly under `runs/`. These lines are keyed on
|
|
18
|
+
* `kind`, carry a field set per kind taken from `core/registry/telemetry.json`, and sit in a
|
|
19
|
+
* subdirectory those readers never see.
|
|
20
|
+
*
|
|
21
|
+
* Rules that constrain everything below.
|
|
22
|
+
* NFR-004 — records are metadata. A field is written only when the registry names it for the kind
|
|
23
|
+
* and its value passes that field's type; anything else is dropped by name or becomes null, and
|
|
24
|
+
* the dropped value is never echoed.
|
|
25
|
+
* NFR-005 / FR-002 — telemetry never changes an outcome. `write` never throws, writes one
|
|
26
|
+
* open-and-write per call, never blocks on a non-regular target, and writes nothing at all when
|
|
27
|
+
* `DOFLOW_TELEMETRY=off`.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** Same pattern as ledger.ts `SAFE_TOKEN` (not exported there); a test holds the two equal. */
|
|
31
|
+
const SAFE_TOKEN = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,63}$/;
|
|
32
|
+
/** The run and lineage rule (context/identity.ts `ID_PATTERN`). */
|
|
33
|
+
const ID = /^[A-Za-z0-9][A-Za-z0-9_-]{0,55}$/;
|
|
34
|
+
const HEX16 = /^[0-9a-f]{16}$/;
|
|
35
|
+
const HEX32 = /^[0-9a-f]{32}$/;
|
|
36
|
+
const ISO_MS = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
|
|
37
|
+
const DATE = /^\d{4}-\d{2}-\d{2}$/;
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Vocabularies another registry owns, which telemetry.json names in `enum:<name>` types but does not
|
|
44
|
+
* copy (119 C-9): a preset added to protocols.json is a valid `protocol` with no edit here. Each is
|
|
45
|
+
* read from its owner, through the owner's validator, the first time a writer needs it. An owner that
|
|
46
|
+
* cannot be read or fails its validator gives no list, so the field is written null (as any value
|
|
47
|
+
* outside its vocabulary is) and the rest of the line is still written.
|
|
48
|
+
*/
|
|
49
|
+
const DERIVED_VOCABULARIES = Object.freeze({
|
|
50
|
+
depth: 'core/registry/protocols.json review preset depth labels, loosest first',
|
|
51
|
+
protocol: 'core/registry/protocols.json order.review, then the literal package',
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
/** The derived vocabularies read from the registries under `repoRoot`; `{}` when protocols.json is unusable. */
|
|
55
|
+
function derivedVocabularies(repoRoot = REPO_ROOT) {
|
|
56
|
+
const loaded = loadProtocols({ repoRoot });
|
|
57
|
+
if (!loaded.ok) return {};
|
|
58
|
+
const review = presetIds(loaded.registry, 'review');
|
|
59
|
+
return {
|
|
60
|
+
depth: [...review].reverse().map((id) => loaded.registry.presets[id].depth),
|
|
61
|
+
// 122 IC-024: `package` stands for any preset an active package contributes, so no third-party name is written.
|
|
62
|
+
protocol: [...review, 'package'],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* The one reader of the switch in Node. The bridge and the observer test the same string in shell.
|
|
68
|
+
* @param {Object} [env=process.env]
|
|
69
|
+
* @returns {boolean}
|
|
70
|
+
*/
|
|
71
|
+
function telemetryOff(env = process.env) {
|
|
72
|
+
return env.DOFLOW_TELEMETRY === 'off';
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** One type alternative of a field (`token`, `uint`, `enum:<vocabulary>`, ...) against one value. */
|
|
76
|
+
function matches(alternative , value , vocabulary ) {
|
|
77
|
+
if (alternative === 'null') return value === null;
|
|
78
|
+
if (alternative === 'uint') return typeof value === 'number' && Number.isFinite(value) && Math.trunc(value) >= 0;
|
|
79
|
+
if (alternative === 'int') return typeof value === 'number' && Number.isFinite(value);
|
|
80
|
+
if (typeof value !== 'string') return false;
|
|
81
|
+
if (alternative === 'token') return SAFE_TOKEN.test(value);
|
|
82
|
+
if (alternative === 'id') return ID.test(value);
|
|
83
|
+
if (alternative === 'hex16') return HEX16.test(value);
|
|
84
|
+
if (alternative === 'hex32') return HEX32.test(value);
|
|
85
|
+
if (alternative === 'iso8601ms') return ISO_MS.test(value);
|
|
86
|
+
if (alternative === 'date') return DATE.test(value);
|
|
87
|
+
if (alternative.startsWith('enum:')) {
|
|
88
|
+
const list = vocabulary(alternative.slice(5));
|
|
89
|
+
return Array.isArray(list) && list.includes(value);
|
|
90
|
+
}
|
|
91
|
+
return false; // an unknown type never passes, so a typo in the registry writes null, not content
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** The value as it is written: integers truncated, anything failing the field's type null. */
|
|
95
|
+
function coerce(type , value , vocabulary ) {
|
|
96
|
+
if (typeof type !== 'string' || !type.split('|').some((alternative) => matches(alternative, value, vocabulary))) return null;
|
|
97
|
+
return typeof value === 'number' ? Math.trunc(value) : value;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let shipped = null;
|
|
101
|
+
function shippedRegistry() {
|
|
102
|
+
if (!shipped) shipped = JSON.parse(nodeFs.readFileSync(path.join(REPO_ROOT, 'core', 'registry', 'telemetry.json'), 'utf8'));
|
|
103
|
+
return shipped;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
class TelemetryWriter {
|
|
107
|
+
registry ;
|
|
108
|
+
env ;
|
|
109
|
+
fsImpl ;
|
|
110
|
+
now ;
|
|
111
|
+
start ;
|
|
112
|
+
repoRoot ;
|
|
113
|
+
derived = null;
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {Object} [options]
|
|
117
|
+
* @param {Object} [options.registry] a parsed `telemetry.json` (default: the shipped one)
|
|
118
|
+
* @param {Object} [options.env=process.env] the switch, the identity trio and `DOFLOW_CONFIG_DIR`
|
|
119
|
+
* @param {Object} [options.fsImpl=fs] test seam
|
|
120
|
+
* @param {Function} [options.now] returns the current Date; test seam
|
|
121
|
+
* @param {string} [options.start] directory to walk up from for `.doflow` (default: cwd)
|
|
122
|
+
* @param {string} [options.repoRoot=REPO_ROOT] the install root the derived vocabularies are read from
|
|
123
|
+
*/
|
|
124
|
+
constructor({ registry, env = process.env, fsImpl = nodeFs, now = () => new Date(), start, repoRoot = REPO_ROOT } = {}) {
|
|
125
|
+
this.registry = registry;
|
|
126
|
+
this.repoRoot = repoRoot;
|
|
127
|
+
this.env = env;
|
|
128
|
+
this.fsImpl = fsImpl;
|
|
129
|
+
this.now = now;
|
|
130
|
+
this.start = start;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Appends the records, one line each, in one open-and-write to the partition named for the UTC
|
|
135
|
+
* date of the first line. Never throws; a fault writes nothing.
|
|
136
|
+
* @param {Array<{kind: string}>} records
|
|
137
|
+
* @returns {void}
|
|
138
|
+
*/
|
|
139
|
+
write(records ) {
|
|
140
|
+
try {
|
|
141
|
+
if (telemetryOff(this.env) || !Array.isArray(records)) return;
|
|
142
|
+
const registry = this.registry || shippedRegistry();
|
|
143
|
+
const lines = [];
|
|
144
|
+
let first = '';
|
|
145
|
+
for (const record of records) {
|
|
146
|
+
let line = null;
|
|
147
|
+
try { line = this.line(registry, record); } catch { line = null; } // a hostile record costs only itself
|
|
148
|
+
if (!line) continue;
|
|
149
|
+
if (!first) first = line.timestamp;
|
|
150
|
+
lines.push(JSON.stringify(line));
|
|
151
|
+
}
|
|
152
|
+
if (!lines.length) return;
|
|
153
|
+
const { dir: ledgerDir } = resolveRunsLocation({ start: this.start, env: this.env, fsImpl: this.fsImpl });
|
|
154
|
+
const dir = path.join(ledgerDir, 'telemetry');
|
|
155
|
+
// Non-recursive: when the ledger directory is absent this throws ENOENT and nothing is written or invented.
|
|
156
|
+
try {
|
|
157
|
+
this.fsImpl.mkdirSync(dir, { recursive: false });
|
|
158
|
+
} catch (error ) {
|
|
159
|
+
if (error?.code !== 'EEXIST') throw error;
|
|
160
|
+
}
|
|
161
|
+
this.append(path.join(dir, `${first.slice(0, 10)}.jsonl`), Buffer.from(`${lines.join('\n')}\n`, 'utf8'));
|
|
162
|
+
} catch {
|
|
163
|
+
// A telemetry fault changes no outcome (FR-002, NFR-005): there is nothing to report to.
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Appends to a regular file and nothing else. A FIFO with no reader would block a plain open
|
|
169
|
+
* forever, so: lstat first and skip anything that exists and is not a regular file (FIFO,
|
|
170
|
+
* directory, socket, device, symlink); then open O_NONBLOCK|O_NOFOLLOW so a swap between the lstat
|
|
171
|
+
* and the open fails (ENXIO, ELOOP) instead of blocking or following; then fstat the descriptor.
|
|
172
|
+
*/
|
|
173
|
+
append(file , data ) {
|
|
174
|
+
let known = null;
|
|
175
|
+
try {
|
|
176
|
+
known = this.fsImpl.lstatSync(file);
|
|
177
|
+
} catch (error ) {
|
|
178
|
+
if (error?.code !== 'ENOENT') return;
|
|
179
|
+
}
|
|
180
|
+
if (known && !known.isFile()) return;
|
|
181
|
+
const { O_WRONLY, O_APPEND, O_CREAT, O_NOFOLLOW = 0, O_NONBLOCK = 0 } = nodeFs.constants;
|
|
182
|
+
const fd = this.fsImpl.openSync(file, O_WRONLY | O_APPEND | O_CREAT | O_NOFOLLOW | O_NONBLOCK, 0o666);
|
|
183
|
+
try {
|
|
184
|
+
if (!this.fsImpl.fstatSync(fd).isFile()) return;
|
|
185
|
+
for (let done = 0; done < data.length;) done += this.fsImpl.writeSync(fd, data, done);
|
|
186
|
+
} finally {
|
|
187
|
+
this.fsImpl.closeSync(fd);
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/** The sanitised line for one record, or null when it has no registered `kind`. */
|
|
192
|
+
line(registry , record ) {
|
|
193
|
+
if (!record || typeof record !== 'object' || Array.isArray(record)) return null;
|
|
194
|
+
const kind = record.kind;
|
|
195
|
+
if (typeof kind !== 'string' || !Object.hasOwn(registry.kinds, kind)) return null;
|
|
196
|
+
const vocabularies = registry.vocabularies;
|
|
197
|
+
const vocabulary = (name) => {
|
|
198
|
+
if (Object.hasOwn(DERIVED_VOCABULARIES, name)) {
|
|
199
|
+
this.derived ??= derivedVocabularies(this.repoRoot);
|
|
200
|
+
return this.derived[name] ?? null;
|
|
201
|
+
}
|
|
202
|
+
return vocabularies && Object.hasOwn(vocabularies, name) ? vocabularies[name] : null;
|
|
203
|
+
};
|
|
204
|
+
const timestamp = typeof record.timestamp === 'string' && ISO_MS.test(record.timestamp)
|
|
205
|
+
? record.timestamp
|
|
206
|
+
: this.now().toISOString();
|
|
207
|
+
// Identity comes from the environment only (a caller-supplied value is ignored), as for the ledger.
|
|
208
|
+
const identity = identityFields(this.env);
|
|
209
|
+
const out = {
|
|
210
|
+
timestamp,
|
|
211
|
+
kind,
|
|
212
|
+
run: identity.run ?? null,
|
|
213
|
+
lineage: identity.lineage ?? null,
|
|
214
|
+
attempt: identity.attempt === undefined ? null : Number(identity.attempt),
|
|
215
|
+
};
|
|
216
|
+
const fields = registry.kinds[kind].fields || {};
|
|
217
|
+
for (const key of Object.keys(fields)) {
|
|
218
|
+
if (!Object.hasOwn(record, key)) continue;
|
|
219
|
+
out[key] = coerce(fields[key].type, record[key], vocabulary);
|
|
220
|
+
}
|
|
221
|
+
return out;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
module.exports = { SAFE_TOKEN, DERIVED_VOCABULARIES, derivedVocabularies, telemetryOff, TelemetryWriter };
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `doflow transcript` (093 V8): the read-only per-agent transcript verb. `--action read --agent <key>` prints
|
|
5
|
+
* one agent's recent events with DoFlow's own record events (IC-022, `readAgent`); `--action stats` prints the
|
|
6
|
+
* project's traffic and who works with whom (IC-023, `computeStats`). Run with `cwd` = a project. It writes
|
|
7
|
+
* nothing: no state, no config home, no harness home, no upkeep.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { finishRuntime, captureCaught } = require('../cli-result.js');
|
|
11
|
+
|
|
12
|
+
const ACTION_FLAGS = {
|
|
13
|
+
read: new Set(['--action', '--agent', '--cursor', '--limit', '--json']),
|
|
14
|
+
stats: new Set(['--action', '--window', '--json']),
|
|
15
|
+
};
|
|
16
|
+
const AGENT_KEY = /^[0-9a-f]{16}$/;
|
|
17
|
+
const CURSOR = /^[0-9]+:[0-9]+:[0-9]+:[0-9]+$/;
|
|
18
|
+
const DIGITS = /^[0-9]+$/;
|
|
19
|
+
|
|
20
|
+
const dash = (value ) => (value === null || value === undefined ? '-' : String(value));
|
|
21
|
+
|
|
22
|
+
/** The text form of `read`: a header, one line per event and a footer. Text and arguments are JSON-quoted (one line each). */
|
|
23
|
+
function readLines(body ) {
|
|
24
|
+
const lines = [
|
|
25
|
+
`agent ${body.agentKey} harness ${dash(body.harness)} reader ${dash(body.reader.id)} ${body.reader.status} transcript ${body.transcript.linked ? 'linked' : body.transcript.reason}`,
|
|
26
|
+
];
|
|
27
|
+
for (const e of body.events) {
|
|
28
|
+
const parts = [`event ${e.seq} ${dash(e.at)} ${e.kind}`];
|
|
29
|
+
if (e.model !== null) parts.push(`model ${e.model}`);
|
|
30
|
+
if (e.tool) parts.push(`tool ${dash(e.tool.name)} status ${dash(e.tool.status)} bytes ${dash(e.tool.bytes)} ms ${dash(e.tool.durationMs)}`);
|
|
31
|
+
if (e.text !== null) parts.push(`text ${JSON.stringify(e.text)}`);
|
|
32
|
+
if (e.args !== null) parts.push(`args ${JSON.stringify(e.args)}`);
|
|
33
|
+
if (e.cut) parts.push('cut');
|
|
34
|
+
if (e.tokens) parts.push(`tokens in ${dash(e.tokens.input)} out ${dash(e.tokens.output)} cache-read ${dash(e.tokens.cacheRead)} cache-write ${dash(e.tokens.cacheWrite)} reasoning ${dash(e.tokens.reasoning)}`);
|
|
35
|
+
if (e.record) parts.push(`record ${e.record.type} task ${dash(e.record.task)} result ${dash(e.record.result)}`);
|
|
36
|
+
lines.push(parts.join(' '));
|
|
37
|
+
}
|
|
38
|
+
lines.push(`cursor ${body.cursor} reset ${body.reset} eof ${body.eof} other ${body.otherRecords} unparsable ${body.unparsable} masked ${body.masked} warnings ${body.warnings.join(',') || '-'}`);
|
|
39
|
+
return lines;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** The text form of `stats`: keys, models and counts only. */
|
|
43
|
+
function statsLines(body ) {
|
|
44
|
+
return [
|
|
45
|
+
`project ${body.projectKey} window ${body.windowMin} min`,
|
|
46
|
+
...body.agents.map((a ) => `agent ${a.agentKey} harness ${dash(a.harness)} model ${dash(a.model)} events/min ${dash(a.eventsPerMin)} tool-calls ${dash(a.toolCalls)} tool-errors ${dash(a.toolErrors)} tool-ms ${a.toolDurationMs ? `p50 ${a.toolDurationMs.p50} max ${a.toolDurationMs.max} n ${a.toolDurationMs.n}` : '-'} tokens ${a.tokens ? `in ${dash(a.tokens.input)} out ${dash(a.tokens.output)}` : '-'} last-record ${dash(a.lastRecordAt)}`),
|
|
47
|
+
...body.models.map((m ) => `model ${m.model} agents ${m.agents} events/min ${dash(m.eventsPerMin)} tool-calls ${dash(m.toolCalls)}`),
|
|
48
|
+
...body.pairs.map((p ) => `pair ${p.a} ${p.b} ${p.basis}${p.task ? ` task ${p.task}` : ''}${p.tasks ? ` tasks ${p.tasks.join(',')}` : ''}`),
|
|
49
|
+
`not-checked ${body.notChecked}`,
|
|
50
|
+
];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function handleTranscriptCommand({
|
|
54
|
+
action, agent, cursor, limit, window, json = false, flags = [], env = process.env, cwd = process.cwd(), exec, alive, now, out = (line ) => console.log(line),
|
|
55
|
+
} = {}) {
|
|
56
|
+
const usage = (message ) => {
|
|
57
|
+
out(json ? JSON.stringify({ ok: false, error: 'usage', message }) : `doflow transcript: ${message}`);
|
|
58
|
+
return finishRuntime(2);
|
|
59
|
+
};
|
|
60
|
+
if (action !== 'read' && action !== 'stats') return usage('action must be read or stats');
|
|
61
|
+
const extra = (flags ).map((flag) => flag.split('=')[0]).find((flag) => !ACTION_FLAGS[action].has(flag));
|
|
62
|
+
if (extra !== undefined) return usage(`unsupported flag ${extra} for ${action}; only ${[...ACTION_FLAGS[action]].join(', ')} apply`);
|
|
63
|
+
|
|
64
|
+
let count ;
|
|
65
|
+
let minutes ;
|
|
66
|
+
if (action === 'read') {
|
|
67
|
+
if (typeof agent !== 'string' || !AGENT_KEY.test(agent)) return usage('--agent must be an agent key of 16 hex characters');
|
|
68
|
+
if (cursor !== undefined && (typeof cursor !== 'string' || !CURSOR.test(cursor))) return usage('--cursor must be the cursor of an earlier read');
|
|
69
|
+
if (limit !== undefined) {
|
|
70
|
+
if (typeof limit !== 'string' || !DIGITS.test(limit) || Number(limit) < 1) return usage('--limit must be a whole number of 1 or more (at most 500 are returned)');
|
|
71
|
+
count = Math.min(500, Number(limit));
|
|
72
|
+
}
|
|
73
|
+
} else if (window !== undefined) {
|
|
74
|
+
if (typeof window !== 'string' || !DIGITS.test(window) || Number(window) < 1 || Number(window) > 60) return usage('--window must be a whole number of minutes, 1 to 60');
|
|
75
|
+
minutes = Number(window);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const common = { env, cwd, ...(exec ? { exec } : {}), ...(alive ? { alive } : {}), ...(now ? { now } : {}) };
|
|
80
|
+
const result = action === 'read'
|
|
81
|
+
? require('./read.js').readAgent({ ...common, agent, cursor, limit: count })
|
|
82
|
+
: require('./stats.js').computeStats({ ...common, ...(minutes !== undefined ? { windowMin: minutes } : {}) });
|
|
83
|
+
if (!result.ok) {
|
|
84
|
+
if (json) out(JSON.stringify({ v: 1, error: result.error }));
|
|
85
|
+
else console.error(`[ERROR] transcript: ${result.error}`);
|
|
86
|
+
return finishRuntime(1);
|
|
87
|
+
}
|
|
88
|
+
if (json) out(JSON.stringify(result.body));
|
|
89
|
+
else for (const line of action === 'read' ? readLines(result.body) : statsLines(result.body)) out(line);
|
|
90
|
+
return finishRuntime(0);
|
|
91
|
+
} catch (error ) {
|
|
92
|
+
captureCaught(error, 'transcript', 1);
|
|
93
|
+
console.error(`[ERROR] transcript: ${error?.message || String(error)}`);
|
|
94
|
+
return finishRuntime(1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
module.exports = { handleTranscriptCommand };
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 093 V.4 (IC-022): the byte cursor, the bounded window read and the driver every transcript reader shares.
|
|
5
|
+
*
|
|
6
|
+
* A reader (`readers/<id>.ts`) is a `SPEC` of three small parts: its required keys, an optional `init`, and
|
|
7
|
+
* `parse(record, ctx)`, which returns the normalised events of one record. The driver owns the rest:
|
|
8
|
+
* - the cursor `<inode>:<byteOffset>:<recordSeq>:<recordAtMs>`; a changed inode or a shorter file resets it
|
|
9
|
+
* to the last 64 records (`reset: true`); a read with no cursor starts the same way with `reset: false`;
|
|
10
|
+
* - at most 256 KiB per call, whole lines only: an unfinished last line is left for the next read;
|
|
11
|
+
* - `otherRecords` (records and blocks never read into an event: thinking, prompts, attachments, unknown types),
|
|
12
|
+
* `unparsable` (lines that are not a JSON object), and the `format-unrecognized` warning when more than 20 %
|
|
13
|
+
* of the processed records are unparsable or lack the reader's required keys;
|
|
14
|
+
* - text policy: every text-bearing string is masked with `maskBody` AFTER JSON decoding, then cut after
|
|
15
|
+
* masking (reply 4 KiB, tool arguments 512 B) with `cut: true`. Never on the raw line (R§4.8): escaped JSON
|
|
16
|
+
* such as `\"token\":\"...\"` inside a string slips past the key rules until it is decoded.
|
|
17
|
+
* Read-only: files are opened `'r'` and nothing is created or written. Ceilings: `recordSeq` counts events
|
|
18
|
+
* from the reset point, not from the start of the file; Claude usage dedup and the model of an event only see
|
|
19
|
+
* the current window, so a message split across two reads can count its usage twice.
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('node:fs');
|
|
23
|
+
const { createHash } = require('node:crypto');
|
|
24
|
+
const { maskBody } = require('../mask.js');
|
|
25
|
+
|
|
26
|
+
const MAX_READ_BYTES = 256 * 1024;
|
|
27
|
+
const TAIL_RECORDS = 64;
|
|
28
|
+
const DEFAULT_LIMIT = 200;
|
|
29
|
+
const MAX_LIMIT = 500;
|
|
30
|
+
const TEXT_CAP = 4096;
|
|
31
|
+
const ARGS_CAP = 512;
|
|
32
|
+
const MASK_MARGIN = 4096; // bytes kept past the cap before masking, so a secret cut by the cap is still whole when masked
|
|
33
|
+
const LEAF_MAX = 8192;
|
|
34
|
+
const UNRECOGNIZED_RATIO = 0.2;
|
|
35
|
+
const LABEL = /^[A-Za-z0-9 ._:/@-]{1,64}$/;
|
|
36
|
+
const CURSOR = /^(\d+):(\d+):(\d+):(\d+)$/;
|
|
37
|
+
|
|
38
|
+
/** IC-004's label rule: a value that is not a short plain label is withheld (null for an absent value). */
|
|
39
|
+
function label(value ) {
|
|
40
|
+
if (value === null || value === undefined) return null;
|
|
41
|
+
return typeof value === 'string' && LABEL.test(value) ? value : '(withheld)';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function parseCursor(text ) {
|
|
45
|
+
const m = typeof text === 'string' ? CURSOR.exec(text) : null;
|
|
46
|
+
if (!m) return null;
|
|
47
|
+
const [offset, seq, atMs] = [Number(m[2]), Number(m[3]), Number(m[4])];
|
|
48
|
+
return [offset, seq, atMs].every(Number.isSafeInteger) ? { ino: m[1], offset, seq, atMs } : null;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Milliseconds from an ISO string or an epoch-ms number; NaN when neither. */
|
|
52
|
+
function toMs(at ) {
|
|
53
|
+
if (typeof at === 'number') return Number.isFinite(at) ? at : NaN;
|
|
54
|
+
return typeof at === 'string' ? Date.parse(at) : NaN;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function toIso(at ) {
|
|
58
|
+
const ms = toMs(at);
|
|
59
|
+
return Number.isFinite(ms) ? new Date(ms).toISOString() : null;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function callKey(id ) {
|
|
63
|
+
return typeof id === 'string' || typeof id === 'number' ? createHash('sha256').update(String(id)).digest('hex').slice(0, 16) : null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Bytes of a tool result's content, never the content itself. */
|
|
67
|
+
function sizeOf(value ) {
|
|
68
|
+
if (value === null || value === undefined) return null;
|
|
69
|
+
return Buffer.byteLength(typeof value === 'string' ? value : JSON.stringify(value) ?? '', 'utf8');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** A token record with a number or null per field; null when nothing is known (unknown is never zero). */
|
|
73
|
+
function tokens(fields ) {
|
|
74
|
+
const out = {};
|
|
75
|
+
for (const key of ['input', 'output', 'cacheRead', 'cacheWrite', 'reasoning']) {
|
|
76
|
+
out[key] = Number.isFinite(fields[key]) ? fields[key] : null;
|
|
77
|
+
}
|
|
78
|
+
return Object.values(out).some((v) => v !== null) ? out : null;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function cutBytes(text , cap ) {
|
|
82
|
+
const buf = Buffer.from(text, 'utf8');
|
|
83
|
+
if (buf.length <= cap) return { text, cut: false };
|
|
84
|
+
let end = cap;
|
|
85
|
+
while (end > 0 && (buf[end] & 0xc0) === 0x80) end -= 1; // never split a character
|
|
86
|
+
return { text: buf.toString('utf8', 0, end), cut: true };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/** Reply text: mask (after decoding), then cut. */
|
|
90
|
+
function capText(value , cap ) {
|
|
91
|
+
const s = String(value);
|
|
92
|
+
const long = s.length > cap + MASK_MARGIN;
|
|
93
|
+
const m = maskBody(long ? s.slice(0, cap + MASK_MARGIN) : s);
|
|
94
|
+
const r = cutBytes(m.text, cap);
|
|
95
|
+
return { text: r.text, cut: r.cut || long, masked: m.masked };
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Tool arguments: a JSON string is decoded, every string leaf is masked on its own decoded text (so a
|
|
100
|
+
* quoted key inside an embedded JSON string is plain again), the structure is rendered, the rendering is
|
|
101
|
+
* masked once more (a `{"password":"x"}` pair only shows its key next to its value there), then cut.
|
|
102
|
+
*/
|
|
103
|
+
function capArgs(value , cap ) {
|
|
104
|
+
let masked = 0;
|
|
105
|
+
let leaves = 0;
|
|
106
|
+
let v = value;
|
|
107
|
+
if (typeof v === 'string') {
|
|
108
|
+
try { const parsed = JSON.parse(v); if (parsed !== null && typeof parsed === 'object') v = parsed; } catch { /* plain text */ }
|
|
109
|
+
}
|
|
110
|
+
const walk = (x , depth ) => {
|
|
111
|
+
if (typeof x === 'string') {
|
|
112
|
+
if (++leaves > 64) return '...';
|
|
113
|
+
const r = maskBody(x.length > LEAF_MAX ? x.slice(0, LEAF_MAX) : x);
|
|
114
|
+
masked += r.masked;
|
|
115
|
+
return r.text;
|
|
116
|
+
}
|
|
117
|
+
if (x === null || typeof x !== 'object') return x;
|
|
118
|
+
if (depth > 6) return '...';
|
|
119
|
+
if (Array.isArray(x)) return x.slice(0, 64).map((item) => walk(item, depth + 1));
|
|
120
|
+
const out = {};
|
|
121
|
+
for (const key of Object.keys(x).slice(0, 64)) out[key] = walk(x[key], depth + 1);
|
|
122
|
+
return out;
|
|
123
|
+
};
|
|
124
|
+
const walked = walk(v, 0);
|
|
125
|
+
const r = capText(typeof walked === 'string' ? walked : JSON.stringify(walked) ?? '', cap);
|
|
126
|
+
return { text: r.text, cut: r.cut, masked: masked + r.masked };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function readRange(fd , from , to ) {
|
|
130
|
+
const buf = Buffer.alloc(Math.max(0, to - from));
|
|
131
|
+
let got = 0;
|
|
132
|
+
while (got < buf.length) {
|
|
133
|
+
const n = fs.readSync(fd, buf, got, buf.length - got, from + got);
|
|
134
|
+
if (n === 0) break;
|
|
135
|
+
got += n;
|
|
136
|
+
}
|
|
137
|
+
return got < buf.length ? buf.subarray(0, got) : buf;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Whole lines of `buf` (absolute offset `base` for its first byte). `skipFirst` drops the leading partial
|
|
144
|
+
* line (the buffer then starts one byte before the first wanted byte). Blank lines are skipped but consumed.
|
|
145
|
+
*/
|
|
146
|
+
function splitLines(buf , base , skipFirst ) {
|
|
147
|
+
const lines = [];
|
|
148
|
+
let p = 0;
|
|
149
|
+
if (skipFirst) {
|
|
150
|
+
const i = buf.indexOf(10);
|
|
151
|
+
p = i === -1 ? buf.length : i + 1;
|
|
152
|
+
}
|
|
153
|
+
for (let nl = buf.indexOf(10, p); nl !== -1 && p < buf.length; nl = buf.indexOf(10, p)) {
|
|
154
|
+
const text = buf.toString('utf8', p, nl);
|
|
155
|
+
if (text.trim() !== '') lines.push({ text, start: base + p, end: base + nl + 1 });
|
|
156
|
+
p = nl + 1;
|
|
157
|
+
}
|
|
158
|
+
return { lines, end: base + p, rest: buf.length - p };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* The window one call reads. `lines` are the records to process; `prior()` returns the raw lines before the
|
|
163
|
+
* window (Copilot differences its cumulative usage against them).
|
|
164
|
+
*/
|
|
165
|
+
function openWindow(file , cursorText , maxBytes , tailRecords = TAIL_RECORDS) {
|
|
166
|
+
const fd = fs.openSync(file, 'r');
|
|
167
|
+
try {
|
|
168
|
+
const st = fs.fstatSync(fd, { bigint: true });
|
|
169
|
+
const ino = String(st.ino);
|
|
170
|
+
const size = Number(st.size);
|
|
171
|
+
const cursor = parseCursor(cursorText);
|
|
172
|
+
const reset = cursor !== null && (cursor.ino !== ino || size < cursor.offset);
|
|
173
|
+
const tail = cursor === null || reset;
|
|
174
|
+
const from = tail ? Math.max(0, size - maxBytes) : (cursor ).offset;
|
|
175
|
+
const skipFirst = tail && from > 0;
|
|
176
|
+
const base = skipFirst ? from - 1 : from;
|
|
177
|
+
const buf = readRange(fd, base, Math.min(size, from + maxBytes));
|
|
178
|
+
const split = splitLines(buf, base, skipFirst);
|
|
179
|
+
let { lines } = split;
|
|
180
|
+
if (tail && lines.length > tailRecords) lines = lines.slice(-tailRecords);
|
|
181
|
+
const windowEnd = base + buf.length;
|
|
182
|
+
// One line longer than the whole window: consume it as a single unparsable record.
|
|
183
|
+
if (!skipFirst && lines.length === 0 && split.rest > 0 && windowEnd < size) {
|
|
184
|
+
lines = [{ text: '', start: from, end: windowEnd, oversize: true }];
|
|
185
|
+
}
|
|
186
|
+
const startOffset = lines.length ? lines[0].start : split.end;
|
|
187
|
+
return {
|
|
188
|
+
ino, size, cursor, reset, tail, lines, startOffset, completeEnd: lines.length ? lines[lines.length - 1].end : split.end,
|
|
189
|
+
reachedEnd: windowEnd >= size,
|
|
190
|
+
prior() {
|
|
191
|
+
if (startOffset === 0) return [];
|
|
192
|
+
const pFrom = Math.max(0, startOffset - maxBytes);
|
|
193
|
+
const pBase = pFrom > 0 ? pFrom - 1 : 0;
|
|
194
|
+
const pfd = fs.openSync(file, 'r');
|
|
195
|
+
try { return splitLines(readRange(pfd, pBase, startOffset), pBase, pFrom > 0).lines.map((l) => l.text); } finally { fs.closeSync(pfd); }
|
|
196
|
+
},
|
|
197
|
+
};
|
|
198
|
+
} finally {
|
|
199
|
+
fs.closeSync(fd);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/** The first `maxLines` JSON-object records of a file (at most 64 KiB), for a reader's `header`. */
|
|
204
|
+
function headRecords(file , maxLines = 8, maxBytes = 64 * 1024) {
|
|
205
|
+
const fd = fs.openSync(file, 'r');
|
|
206
|
+
try {
|
|
207
|
+
const out = [];
|
|
208
|
+
const { lines } = splitLines(readRange(fd, 0, maxBytes), 0, false);
|
|
209
|
+
for (const line of lines) {
|
|
210
|
+
if (out.length >= maxLines) break;
|
|
211
|
+
try { const rec = JSON.parse(line.text); if (rec && typeof rec === 'object') out.push(rec); } catch { /* torn */ }
|
|
212
|
+
}
|
|
213
|
+
return out;
|
|
214
|
+
} finally {
|
|
215
|
+
fs.closeSync(fd);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/** A small JSON companion file (at most 256 KiB), or null when absent or unreadable. */
|
|
220
|
+
function readJsonFile(file ) {
|
|
221
|
+
try {
|
|
222
|
+
const fd = fs.openSync(file, 'r');
|
|
223
|
+
try {
|
|
224
|
+
const size = fs.fstatSync(fd).size;
|
|
225
|
+
if (size > MAX_READ_BYTES) return null;
|
|
226
|
+
const parsed = JSON.parse(readRange(fd, 0, size).toString('utf8'));
|
|
227
|
+
return parsed !== null && typeof parsed === 'object' ? parsed : null;
|
|
228
|
+
} finally { fs.closeSync(fd); }
|
|
229
|
+
} catch { return null; }
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function makeContext(mode , agentKey ) {
|
|
233
|
+
const ctx = { mode, agentKey, pending: 0, state: {}, calls: new Map () };
|
|
234
|
+
ctx.tool = (name , id , extra = {}) => ({
|
|
235
|
+
name: label(name), callKey: callKey(id), status: extra.status ?? null, bytes: extra.bytes ?? null, durationMs: extra.durationMs ?? null,
|
|
236
|
+
});
|
|
237
|
+
ctx.noteCall = (id , at , name ) => { if (id !== undefined && id !== null) ctx.calls.set(String(id), { ms: toMs(at), name: typeof name === 'string' ? name : null }); };
|
|
238
|
+
ctx.callOf = (id ) => (id === undefined || id === null ? undefined : ctx.calls.get(String(id)));
|
|
239
|
+
/** Milliseconds from the call's record to `at`, when both are in the window. */
|
|
240
|
+
ctx.durationOf = (id , at ) => {
|
|
241
|
+
const call = ctx.callOf(id);
|
|
242
|
+
const d = call ? toMs(at) - call.ms : NaN;
|
|
243
|
+
return Number.isFinite(d) && d >= 0 ? d : null;
|
|
244
|
+
};
|
|
245
|
+
ctx.event = (kind , at , f = {}) => {
|
|
246
|
+
let text = null;
|
|
247
|
+
let args = null;
|
|
248
|
+
let cut = false;
|
|
249
|
+
if (mode === 'text') {
|
|
250
|
+
if (f.text !== undefined && f.text !== null) { const r = capText(f.text, TEXT_CAP); text = r.text; cut = r.cut; ctx.pending += r.masked; }
|
|
251
|
+
if (f.args !== undefined && f.args !== null) { const r = capArgs(f.args, ARGS_CAP); args = r.text; cut = cut || r.cut; ctx.pending += r.masked; }
|
|
252
|
+
}
|
|
253
|
+
return {
|
|
254
|
+
seq: 0, at: toIso(at), kind, agentKey: ctx.agentKey, model: label(f.model ?? ctx.state.model ?? null),
|
|
255
|
+
tool: f.tool ?? null, text, args, cut, tokens: f.tokens ?? null, record: null,
|
|
256
|
+
};
|
|
257
|
+
};
|
|
258
|
+
return ctx;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Runs one reader over one transcript file. `opts`: `file`, `mode` ('text' | 'metadata'), `cursor`, `limit`
|
|
263
|
+
* (default 200, at most 500), `agentKey`, `companion` (Kiro's `.json`), `maxBytes` (default 256 KiB), `tailRecords`
|
|
264
|
+
* (records kept by a read with no cursor, default 64; the traffic stats read the whole 256 KiB tail).
|
|
265
|
+
* Throws the fs error when the file cannot be opened; the caller maps it to `not-found`.
|
|
266
|
+
*/
|
|
267
|
+
function readTranscript(spec , opts ) {
|
|
268
|
+
const mode = opts.mode === 'metadata' ? 'metadata' : 'text';
|
|
269
|
+
const limit = Math.min(MAX_LIMIT, Math.max(1, Number.isInteger(opts.limit) ? opts.limit : DEFAULT_LIMIT));
|
|
270
|
+
const win = openWindow(opts.file, opts.cursor, opts.maxBytes ?? MAX_READ_BYTES, opts.tailRecords ?? TAIL_RECORDS);
|
|
271
|
+
const ctx = makeContext(mode, opts.agentKey ?? null);
|
|
272
|
+
if (spec.init) ctx.state = spec.init(win, opts, ctx) ?? {};
|
|
273
|
+
|
|
274
|
+
const events = [];
|
|
275
|
+
let seq = win.tail ? 0 : win.cursor.seq;
|
|
276
|
+
let atMs = win.tail ? 0 : win.cursor.atMs;
|
|
277
|
+
let committedEnd = null;
|
|
278
|
+
let [other, unparsable, bad, processed, masked] = [0, 0, 0, 0, 0];
|
|
279
|
+
let stopped = false;
|
|
280
|
+
|
|
281
|
+
for (const line of win.lines) {
|
|
282
|
+
let rec = null;
|
|
283
|
+
if (!line.oversize) { try { rec = JSON.parse(line.text); } catch { /* counted below */ } }
|
|
284
|
+
ctx.pending = 0;
|
|
285
|
+
let out ;
|
|
286
|
+
if (rec === null || typeof rec !== 'object' || Array.isArray(rec)) out = { events: [], other: 0, unparsable: 1 };
|
|
287
|
+
else if (!spec.requiredKeys.every((key ) => rec[key] !== undefined)) out = { events: [], other: 1, bad: 1 };
|
|
288
|
+
else {
|
|
289
|
+
out = spec.parse(rec, ctx);
|
|
290
|
+
if (out.other === undefined) out.other = out.events.length ? 0 : 1;
|
|
291
|
+
}
|
|
292
|
+
if (events.length > 0 && events.length + out.events.length > limit) { stopped = true; break; }
|
|
293
|
+
for (const event of out.events) { event.seq = seq; seq += 1; events.push(event); }
|
|
294
|
+
other += out.other;
|
|
295
|
+
unparsable += out.unparsable ?? 0;
|
|
296
|
+
bad += out.bad ?? 0;
|
|
297
|
+
masked += ctx.pending;
|
|
298
|
+
processed += 1;
|
|
299
|
+
committedEnd = line.end;
|
|
300
|
+
const ms = rec ? toMs(spec.at ? spec.at(rec) : rec.timestamp) : NaN;
|
|
301
|
+
if (Number.isFinite(ms)) atMs = ms;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const offset = committedEnd ?? (win.tail ? win.startOffset : win.cursor.offset);
|
|
305
|
+
let eof = !stopped && win.reachedEnd;
|
|
306
|
+
if (spec.extra) {
|
|
307
|
+
ctx.pending = 0;
|
|
308
|
+
const more = spec.extra(ctx, win, opts, limit - events.length);
|
|
309
|
+
for (const event of more.events) { event.seq = seq; seq += 1; events.push(event); }
|
|
310
|
+
masked += ctx.pending;
|
|
311
|
+
if (Number.isFinite(more.atMs)) atMs = more.atMs;
|
|
312
|
+
if (more.more) eof = false;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const warnings = [];
|
|
316
|
+
if (processed > 0 && (unparsable + bad) / processed > UNRECOGNIZED_RATIO) warnings.push('format-unrecognized');
|
|
317
|
+
return {
|
|
318
|
+
events, cursor: `${win.ino}:${offset}:${seq}:${Math.trunc(atMs)}`, reset: win.reset, eof,
|
|
319
|
+
otherRecords: other, unparsable, masked, warnings,
|
|
320
|
+
};
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
module.exports = {
|
|
324
|
+
MAX_READ_BYTES, TAIL_RECORDS, DEFAULT_LIMIT, MAX_LIMIT, TEXT_CAP, ARGS_CAP, UNRECOGNIZED_RATIO,
|
|
325
|
+
parseCursor, readTranscript, headRecords, readJsonFile, label, toMs, toIso, sizeOf, tokens,
|
|
326
|
+
};
|