@khoavu882/doflow 2.0.0-beta.5 → 2.0.1
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 +16 -16
- package/bin/doflow.js +22 -12
- package/core/.antigravity-plugin/plugin.json +1 -2
- 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 +4 -1
- 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-compact.sh +23 -27
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +4 -1
- package/core/harnesses/claude/hooks/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- package/core/harnesses/claude/settings/settings.json +26 -9
- 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/hooks.json +0 -10
- package/core/harnesses/codex/hooks/lib.sh +44 -25
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +4 -1
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +4 -1
- package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
- package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
- package/core/harnesses/copilot/hooks/doflow-lib.sh +94 -0
- package/core/harnesses/copilot/hooks/doflow-mcp-tool-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 +51 -0
- package/core/harnesses/kiro/agents/doflow.json +1 -11
- package/core/harnesses/kiro/hooks/doflow.json +0 -10
- package/core/harnesses/kiro/hooks/lib.sh +26 -6
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +8 -3
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +8 -3
- package/core/harnesses/kiro/hooks/session-start.sh +1 -0
- package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
- package/core/harnesses/pi/extensions/doflow-guard.js +11 -2
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +1 -2
- package/core/harnesses/shared/hooks/adapters/antigravity.js +1 -1
- package/core/harnesses/shared/hooks/bridge/bridge.sh +126 -23
- package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
- package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +16 -2
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +49 -16
- package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
- package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +20 -7
- 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 +63 -40
- package/core/harnesses/shared/hooks/policies/mcp-policy.conf +1 -1
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +7 -8
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +132 -57
- package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
- package/core/harnesses/shared/hooks/policies/stop-check.sh +27 -15
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +6 -3
- package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
- package/core/harnesses/shared/hooks/stream-hook-runner.js +58 -20
- package/core/harnesses/shared/locator/doflow-run +2 -3
- package/core/registry/advice.json +3 -3
- package/core/registry/assets.json +55 -82
- package/core/registry/capabilities.json +12 -0
- package/core/registry/contracts.json +58 -31
- package/core/registry/contribution-points.json +35 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1445 -366
- package/core/registry/intake.json +53 -0
- package/core/registry/lifecycle.json +599 -996
- 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/intake-category/files/category.json +8 -0
- package/core/registry/package-templates/intake-category/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 +272 -0
- package/core/registry/storage.json +687 -31
- package/core/registry/telemetry.json +236 -0
- package/core/registry/transcripts.json +142 -0
- package/core/registry/verification.json +76 -6
- 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 +3 -2
- package/core/shared/guidance/references/WORKFLOW_HANDOFF.md +19 -1
- 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 +49 -5
- package/core/shared/scripts/doflow/bin/doflow-run +152 -34
- package/core/shared/skills/do/SKILL.md +14 -12
- 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 +45 -97
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +36 -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 +48 -148
- 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 +23 -20
- package/core/shared/skills/do-diagnose/SKILL.md +18 -6
- package/core/shared/skills/do-document/SKILL.md +12 -1
- package/core/shared/skills/do-document/references/deep_research.md +1 -2
- package/core/shared/skills/do-document/references/embedded-research.md +19 -7
- package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +19 -60
- 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 +26 -1
- package/core/shared/skills/do-execute-plan/references/run_position.md +16 -2
- 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 +10 -11
- package/core/shared/skills/do-flow/references/task_classes.md +5 -5
- package/core/shared/skills/do-git/SKILL.md +4 -0
- package/core/shared/skills/do-implement/SKILL.md +13 -14
- package/core/shared/skills/do-plan/SKILL.md +34 -20
- 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/shared/workflows/do-flow-chain.md +2 -0
- 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 +143 -66
- package/src/adapters/claude/index.js +46 -5
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/hooks.js +39 -3
- package/src/adapters/codex/index.js +61 -13
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +291 -46
- 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 +23 -12
- package/src/adapters/opencode/index.js +8 -8
- package/src/adapters/pi/hooks.js +2 -1
- package/src/adapters/pi/index.js +14 -7
- package/src/adapters/resolve-source.js +26 -2
- package/src/cli/commands/install.js +2 -1
- 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 +22 -3
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +2 -1
- package/src/cli/index.js +337 -32
- package/src/cli/runtime-commands.js +220 -60
- 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 +4 -16
- package/src/lifecycle/hook-tier.js +160 -0
- package/src/lifecycle/index.js +412 -78
- package/src/lifecycle/retired-harnesses.js +318 -0
- package/src/lifecycle/view.js +35 -15
- package/src/registry/index.js +551 -26
- package/src/registry/worker-ids.js +45 -0
- package/src/release/build-package.js +0 -3
- package/src/release/docs-blocks.js +472 -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 +20 -11
- 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 +13 -7
- package/src/runtime/cli.js +196 -19
- package/src/runtime/config-home.js +42 -0
- package/src/runtime/context/arbitrate.js +767 -0
- package/src/runtime/context/delivery-store.js +4 -6
- package/src/runtime/context/emission-rules.js +118 -0
- package/src/runtime/context/identity.js +227 -0
- package/src/runtime/context/pointer.js +67 -12
- 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 +158 -79
- 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-ledger.js +52 -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 +33 -3
- package/src/runtime/git-memo.js +46 -0
- package/src/runtime/guidance/ablation.js +427 -0
- package/src/runtime/health.js +624 -18
- package/src/runtime/history-compactor.js +13 -3
- package/src/runtime/implementation-gate.js +281 -26
- package/src/runtime/intake/engine.js +677 -0
- package/src/runtime/intake/index.js +136 -0
- package/src/runtime/intake/manifest.js +140 -0
- package/src/runtime/intake/taxonomy.js +206 -0
- 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 +88 -10
- package/src/runtime/lifecycle/event-store.js +8 -6
- package/src/runtime/lifecycle/followup.js +38 -7
- package/src/runtime/lifecycle/overview.js +26 -2
- 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 +2 -2
- 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 +487 -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 +46 -5
- package/src/runtime/research-request.js +50 -25
- package/src/runtime/retrieval-plan.js +10 -30
- package/src/runtime/retrieval-record.js +75 -5
- 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/attendance.js +137 -0
- package/src/runtime/roadmap/facts.js +479 -0
- package/src/runtime/roadmap/gates.js +273 -0
- package/src/runtime/roadmap/loop.js +1719 -0
- package/src/runtime/roadmap/merge.js +390 -0
- package/src/runtime/roadmap/rows.js +1122 -0
- package/src/runtime/roadmap/seed.js +1063 -0
- package/src/runtime/roadmap/worktree-cleanup.js +147 -0
- package/src/runtime/rules-report.js +336 -0
- package/src/runtime/scaffold/artifacts.js +60 -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 +1 -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 +85 -14
- package/src/runtime/task-graph.js +373 -0
- package/src/runtime/task-run.js +778 -0
- package/src/runtime/task-scope.js +176 -55
- package/src/runtime/task-sources.js +147 -0
- package/src/runtime/task-state.js +373 -64
- 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/verb-view.js +267 -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 -54
- 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 +256 -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 +610 -67
- 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/harnesses/claude/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +0 -10
- 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 -10
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
- package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
- 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 -7
- package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +0 -24
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +0 -91
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +0 -378
- 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 -251
- package/src/adapters/gemini/index.js +0 -429
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// 093 V.1, IC-021: loader, validator and doctor view for core/registry/transcripts.json, the data that
|
|
4
|
+
// says where each harness keeps its transcripts. Like advice.json it stays outside REGISTRY_FILES: a bad
|
|
5
|
+
// file turns the transcript feature off (`config-invalid`), it must not fail every install command.
|
|
6
|
+
|
|
7
|
+
const fs = require('node:fs');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
10
|
+
|
|
11
|
+
const TRANSCRIPTS_FILE = 'core/registry/transcripts.json';
|
|
12
|
+
const HARNESSES_FILE = 'core/registry/harnesses.json';
|
|
13
|
+
const STALE_DAYS = 90;
|
|
14
|
+
const DAY_MS = 86400000;
|
|
15
|
+
const STATUSES = ['supported', 'events-only', 'unknown'];
|
|
16
|
+
const FORMATS = ['jsonl', 'sqlite', 'jsonl+json'];
|
|
17
|
+
const APPEND_ONLY = ['yes', 'no', 'unknown'];
|
|
18
|
+
const JOINS = ['session-id', 'cwd', 'none'];
|
|
19
|
+
const OSES = ['darwin', 'linux', 'win32'];
|
|
20
|
+
const PLACEHOLDERS = new Set(['sessionId', 'cwdEncoded']);
|
|
21
|
+
const ENTRY_KEYS = ['harness', 'reader', 'status', 'locations', 'format', 'appendOnly', 'join', 'requiredKeys', 'evidence', 'verifiedOn', 'versions'];
|
|
22
|
+
|
|
23
|
+
function isObject(value ) {
|
|
24
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function isStringArray(value ) {
|
|
28
|
+
return Array.isArray(value) && value.every((item) => typeof item === 'string' && item.length > 0);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** True for a real calendar date written YYYY-MM-DD. */
|
|
32
|
+
function isDate(value ) {
|
|
33
|
+
if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
|
|
34
|
+
const at = new Date(`${value}T00:00:00Z`);
|
|
35
|
+
return !Number.isNaN(at.getTime()) && at.toISOString().slice(0, 10) === value;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function validateLocation(location , where , errors ) {
|
|
39
|
+
if (!isObject(location)) { errors.push(`${where} must be an object`); return; }
|
|
40
|
+
if (!OSES.includes(location.os)) errors.push(`${where}.os must be one of ${OSES.join(', ')}`);
|
|
41
|
+
if (!(typeof location.base === 'string' && /^(home|xdg-data|env:[A-Z_][A-Z0-9_]*)$/.test(location.base))) {
|
|
42
|
+
errors.push(`${where}.base must be home, xdg-data or env:<NAME>`);
|
|
43
|
+
}
|
|
44
|
+
if (!isStringArray(location.segments)) errors.push(`${where}.segments must be an array of non-empty strings`);
|
|
45
|
+
if (typeof location.glob !== 'string' || !location.glob) {
|
|
46
|
+
errors.push(`${where}.glob must be a non-empty string`);
|
|
47
|
+
} else {
|
|
48
|
+
for (const [, name] of location.glob.matchAll(/\{([^}]*)\}/g)) {
|
|
49
|
+
if (!PLACEHOLDERS.has(name)) errors.push(`${where}.glob has an unknown placeholder {${name}}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// R28: a Windows location stays unverified until someone measures it, and only Windows may say so.
|
|
53
|
+
if (location.os === 'win32') {
|
|
54
|
+
if (location.status !== 'unknown') errors.push(`${where} is win32, so its status must be "unknown"`);
|
|
55
|
+
} else if (location.status !== undefined) {
|
|
56
|
+
errors.push(`${where}.status is only allowed on a win32 location`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function validateEntry(entry , index , errors ) {
|
|
61
|
+
const label = isObject(entry) && typeof entry.harness === 'string' ? entry.harness : `entries[${index}]`;
|
|
62
|
+
if (!isObject(entry)) { errors.push(`${label} must be an object`); return; }
|
|
63
|
+
for (const key of ENTRY_KEYS) {
|
|
64
|
+
if (!Object.hasOwn(entry, key)) errors.push(`${label}.${key} is missing`);
|
|
65
|
+
}
|
|
66
|
+
if (typeof entry.harness !== 'string' || !entry.harness) errors.push(`${label}.harness must be a non-empty string`);
|
|
67
|
+
if (Object.hasOwn(entry, 'reader') && entry.reader !== null && (typeof entry.reader !== 'string' || !entry.reader)) {
|
|
68
|
+
errors.push(`${label}.reader must be a module id or null`);
|
|
69
|
+
}
|
|
70
|
+
if (Object.hasOwn(entry, 'status') && !STATUSES.includes(entry.status)) errors.push(`${label}.status must be one of ${STATUSES.join(', ')}`);
|
|
71
|
+
// A reader exists exactly when the harness is supported (IC-021: OpenCode moves only with its reader).
|
|
72
|
+
if (STATUSES.includes(entry.status) && entry.reader !== undefined) {
|
|
73
|
+
if (entry.status === 'supported' && entry.reader === null) errors.push(`${label}.reader must be set when status is supported`);
|
|
74
|
+
if (entry.status !== 'supported' && entry.reader !== null) errors.push(`${label}.reader must be null unless status is supported`);
|
|
75
|
+
}
|
|
76
|
+
if (Object.hasOwn(entry, 'locations')) {
|
|
77
|
+
if (!Array.isArray(entry.locations) || entry.locations.length === 0) errors.push(`${label}.locations must be a non-empty array`);
|
|
78
|
+
else entry.locations.forEach((location , i ) => validateLocation(location, `${label}.locations[${i}]`, errors));
|
|
79
|
+
}
|
|
80
|
+
if (Object.hasOwn(entry, 'format') && !FORMATS.includes(entry.format)) errors.push(`${label}.format must be one of ${FORMATS.join(', ')}`);
|
|
81
|
+
if (Object.hasOwn(entry, 'appendOnly') && !APPEND_ONLY.includes(entry.appendOnly)) errors.push(`${label}.appendOnly must be one of ${APPEND_ONLY.join(', ')}`);
|
|
82
|
+
if (Object.hasOwn(entry, 'join') && !JOINS.includes(entry.join)) errors.push(`${label}.join must be one of ${JOINS.join(', ')}`);
|
|
83
|
+
if (Object.hasOwn(entry, 'requiredKeys') && !isStringArray(entry.requiredKeys)) errors.push(`${label}.requiredKeys must be an array of non-empty strings`);
|
|
84
|
+
if (Object.hasOwn(entry, 'evidence') && (!isStringArray(entry.evidence) || entry.evidence.length === 0)) errors.push(`${label}.evidence must be a non-empty array of strings`);
|
|
85
|
+
if (Object.hasOwn(entry, 'verifiedOn') && !isDate(entry.verifiedOn)) errors.push(`${label}.verifiedOn must be a YYYY-MM-DD date`);
|
|
86
|
+
if (Object.hasOwn(entry, 'versions') && entry.versions !== null && (typeof entry.versions !== 'string' || !entry.versions)) {
|
|
87
|
+
errors.push(`${label}.versions must be a version range or null`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** The registry ids of core/registry/harnesses.json, read as plain data. */
|
|
92
|
+
function readHarnessIds(repoRoot , fsImpl = fs) {
|
|
93
|
+
const parsed = JSON.parse(fsImpl.readFileSync(path.join(path.resolve(repoRoot), HARNESSES_FILE), 'utf8'));
|
|
94
|
+
return parsed.harnesses.map((harness ) => harness.id);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Checks the registry's shape and the id guard: the entry ids and `harnessIds` are the same set, with no
|
|
99
|
+
* exception (row 127 retired Gemini CLI, so there is no Gemini entry and no allowance for one).
|
|
100
|
+
*/
|
|
101
|
+
function validateTranscriptRegistry(registry , { harnessIds } ) {
|
|
102
|
+
const errors = [];
|
|
103
|
+
if (!isObject(registry)) return { ok: false, errors: ['registry must be an object'] };
|
|
104
|
+
if (registry.version !== 1) errors.push('version must be 1');
|
|
105
|
+
if (!Array.isArray(registry.entries)) {
|
|
106
|
+
errors.push('entries must be an array');
|
|
107
|
+
return { ok: false, errors };
|
|
108
|
+
}
|
|
109
|
+
registry.entries.forEach((entry , i ) => validateEntry(entry, i, errors));
|
|
110
|
+
const seen = new Set ();
|
|
111
|
+
for (const entry of registry.entries) {
|
|
112
|
+
if (!isObject(entry) || typeof entry.harness !== 'string') continue;
|
|
113
|
+
if (seen.has(entry.harness)) errors.push(`${entry.harness} is a duplicate entry`);
|
|
114
|
+
seen.add(entry.harness);
|
|
115
|
+
if (!harnessIds.includes(entry.harness)) errors.push(`${entry.harness} is not a harnesses.json id`);
|
|
116
|
+
}
|
|
117
|
+
for (const id of harnessIds) {
|
|
118
|
+
if (!seen.has(id)) errors.push(`${id} has no entry (every harnesses.json id needs one)`);
|
|
119
|
+
}
|
|
120
|
+
return { ok: errors.length === 0, errors };
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function loadTranscriptRegistry({ repoRoot = REPO_ROOT, file, fsImpl = fs } = {}) {
|
|
124
|
+
const root = path.resolve(repoRoot);
|
|
125
|
+
const target = file || path.join(root, TRANSCRIPTS_FILE);
|
|
126
|
+
let registry ;
|
|
127
|
+
let harnessIds ;
|
|
128
|
+
try {
|
|
129
|
+
registry = JSON.parse(fsImpl.readFileSync(target, 'utf8'));
|
|
130
|
+
harnessIds = readHarnessIds(root, fsImpl);
|
|
131
|
+
} catch (error ) {
|
|
132
|
+
return { ok: false, reason: 'config-invalid', detail: `${target}: ${error.message}` };
|
|
133
|
+
}
|
|
134
|
+
const validation = validateTranscriptRegistry(registry, { harnessIds });
|
|
135
|
+
if (!validation.ok) return { ok: false, reason: 'config-invalid', detail: validation.errors.join('; ') };
|
|
136
|
+
return { ok: true, registry };
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/** Entries whose `verifiedOn` is more than STALE_DAYS before `now`. */
|
|
140
|
+
function staleEntries(registry , now = new Date()) {
|
|
141
|
+
const out = [];
|
|
142
|
+
for (const entry of registry.entries) {
|
|
143
|
+
const ageDays = Math.floor((now.getTime() - new Date(`${entry.verifiedOn}T00:00:00Z`).getTime()) / DAY_MS);
|
|
144
|
+
if (ageDays > STALE_DAYS) out.push({ harness: entry.harness, verifiedOn: entry.verifiedOn, ageDays });
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* The doctor's `transcripts` row: `ok`, `stale` (a `verifiedOn` older than 90 days) or `drift` (the file or
|
|
151
|
+
* the id guard is invalid, so the transcript feature is off). Read-only; never a finding, so the exit code
|
|
152
|
+
* does not change. A `format-unrecognized` seen at run time joins this row when V.4's readers exist.
|
|
153
|
+
*/
|
|
154
|
+
function transcriptsDoctorView({ repoRoot = REPO_ROOT, file, fsImpl = fs, now = new Date() } = {}) {
|
|
155
|
+
const loaded = loadTranscriptRegistry({ repoRoot, file, fsImpl });
|
|
156
|
+
if (!loaded.ok) return { state: 'drift', entries: 0, stale: [], errors: (loaded.detail ).split('; ') };
|
|
157
|
+
const stale = staleEntries(loaded.registry, now);
|
|
158
|
+
return { state: stale.length ? 'stale' : 'ok', entries: loaded.registry.entries.length, stale, errors: [] };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { loadTranscriptRegistry, validateTranscriptRegistry, staleEntries, transcriptsDoctorView, STALE_DAYS, TRANSCRIPTS_FILE };
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `transcript --action stats` (093 IC-023): traffic over the last window and who works with whom, for one
|
|
5
|
+
* project. The agents are `buildTree`'s nodes (newest first, at most 32; the rest counted in `notChecked`).
|
|
6
|
+
* A linked transcript is read in metadata mode (no text, no arguments), at most its last 256 KiB, in pages.
|
|
7
|
+
* An event without a time (Kiro keeps none per record) takes the transcript's newest known time, which for
|
|
8
|
+
* Kiro is the newest turn `end_timestamp`; if there is none, the window is unknown and the counts are null.
|
|
9
|
+
* Unknown is never zero: a harness that does not record a value gives null, and an agent with no readable
|
|
10
|
+
* transcript gives null everywhere. Zero is reported only for a transcript that was read and held nothing.
|
|
11
|
+
* `pairs` says who works with whom: equal model id (`same-model`), active attempts or running worker runs on
|
|
12
|
+
* one task id (`same-task`, `task`), and a plan `overlaps` pair held by two agents (`overlap`, `tasks`).
|
|
13
|
+
* Nothing is written; no path, pid or raw session id is printed.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const fs = require('node:fs');
|
|
17
|
+
const { buildTree, READERS } = require('../fleet/tree.js');
|
|
18
|
+
const { loadPlan } = require('../task-attempts.js');
|
|
19
|
+
const { overlaps } = require('../task-graph.js');
|
|
20
|
+
const { parseCursor, toMs } = require('./cursor.js');
|
|
21
|
+
const { inCheckouts, checkoutRoots, registryById } = require('./read.js');
|
|
22
|
+
|
|
23
|
+
const DEFAULT_WINDOW = 10;
|
|
24
|
+
const MAX_AGENTS = 32;
|
|
25
|
+
const MAX_PAGES = 8;
|
|
26
|
+
const TAIL_RECORDS = 4096;
|
|
27
|
+
const TOKEN_FIELDS = ['input', 'output', 'cacheRead', 'cacheWrite', 'reasoning'];
|
|
28
|
+
|
|
29
|
+
/** Sums of the fields any event recorded; a field no event recorded stays null, and none at all is null. */
|
|
30
|
+
function sumTokens(list ) {
|
|
31
|
+
const out = {};
|
|
32
|
+
for (const field of TOKEN_FIELDS) {
|
|
33
|
+
const values = list.map((t) => t[field]).filter((v) => Number.isFinite(v));
|
|
34
|
+
out[field] = values.length ? values.reduce((a, b) => a + b, 0) : null;
|
|
35
|
+
}
|
|
36
|
+
return Object.values(out).some((v) => v !== null) ? out : null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const round2 = (n ) => Math.round(n * 100) / 100;
|
|
40
|
+
|
|
41
|
+
/** Every event of the transcript's window, in pages, with the newest time the reader knew. */
|
|
42
|
+
function readWindow(mod , node , internal ) {
|
|
43
|
+
const events = [];
|
|
44
|
+
let cursor ;
|
|
45
|
+
let clock = NaN;
|
|
46
|
+
for (let page = 0; page < MAX_PAGES; page += 1) {
|
|
47
|
+
const r = mod.read({ file: internal.file, companion: internal.companion, mode: 'metadata', cursor, limit: 500, tailRecords: TAIL_RECORDS, agentKey: node.agentKey });
|
|
48
|
+
events.push(...r.events);
|
|
49
|
+
const c = parseCursor(r.cursor);
|
|
50
|
+
clock = c && c.atMs > 0 ? c.atMs : clock;
|
|
51
|
+
cursor = r.cursor;
|
|
52
|
+
if (r.eof) break;
|
|
53
|
+
}
|
|
54
|
+
return { events, clock };
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The per-agent numbers of the events at or after `startMs`. */
|
|
58
|
+
function summarize(events , clock , startMs , windowMin ) {
|
|
59
|
+
if (events.length === 0) {
|
|
60
|
+
return { eventsPerMin: 0, toolCalls: 0, toolErrors: 0, toolDurationMs: null, tokens: null };
|
|
61
|
+
}
|
|
62
|
+
const timed = events.map((e) => ({ e, t: Number.isFinite(toMs(e.at)) ? toMs(e.at) : clock })).filter((x) => Number.isFinite(x.t));
|
|
63
|
+
if (timed.length === 0) return { eventsPerMin: null, toolCalls: null, toolErrors: null, toolDurationMs: null, tokens: null };
|
|
64
|
+
const inWindow = timed.filter((x) => x.t >= startMs).map((x) => x.e);
|
|
65
|
+
const results = inWindow.filter((e) => e.kind === 'tool-result');
|
|
66
|
+
const known = results.filter((e) => e.tool && e.tool.status !== null && e.tool.status !== undefined);
|
|
67
|
+
const durations = results.map((e) => e.tool && e.tool.durationMs).filter((d) => Number.isFinite(d)).sort((a, b) => a - b);
|
|
68
|
+
const usage = inWindow.filter((e) => e.kind === 'usage' && e.tokens).map((e) => e.tokens);
|
|
69
|
+
return {
|
|
70
|
+
eventsPerMin: round2(inWindow.length / windowMin),
|
|
71
|
+
toolCalls: inWindow.filter((e) => e.kind === 'tool-call').length,
|
|
72
|
+
toolErrors: results.length > 0 && known.length === 0 ? null : known.filter((e) => e.tool.status === 'error').length,
|
|
73
|
+
toolDurationMs: durations.length ? { p50: durations[Math.ceil(durations.length / 2) - 1], max: durations[durations.length - 1], n: durations.length } : null,
|
|
74
|
+
tokens: sumTokens(usage),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function groupModels(agents ) {
|
|
79
|
+
const byModel = new Map ();
|
|
80
|
+
for (const a of agents) if (a.model !== null) byModel.set(a.model, [...(byModel.get(a.model) ?? []), a]);
|
|
81
|
+
const sum = (list , key ) => {
|
|
82
|
+
const values = list.map((a) => a[key]).filter((v) => v !== null);
|
|
83
|
+
return values.length ? round2(values.reduce((x, y) => x + y, 0)) : null;
|
|
84
|
+
};
|
|
85
|
+
return [...byModel.entries()].sort((x, y) => (x[0] < y[0] ? -1 : 1)).map(([model, list]) => ({
|
|
86
|
+
model, agents: list.length, eventsPerMin: sum(list, 'eventsPerMin'), toolCalls: sum(list, 'toolCalls'), tokens: sumTokens(list.map((a) => a.tokens).filter(Boolean)),
|
|
87
|
+
}));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function pairsOf(agents , holds , planOverlaps ) {
|
|
91
|
+
const out = new Map ();
|
|
92
|
+
const add = (a , b , basis , task , tasks ) => {
|
|
93
|
+
out.set(`${a}|${b}|${basis}|${task ?? ''}|${(tasks ?? []).join(',')}`, { a, b, basis, task, tasks });
|
|
94
|
+
};
|
|
95
|
+
const sorted = [...agents].sort((x, y) => (x.agentKey < y.agentKey ? -1 : 1));
|
|
96
|
+
for (let i = 0; i < sorted.length; i += 1) {
|
|
97
|
+
for (let j = i + 1; j < sorted.length; j += 1) {
|
|
98
|
+
const [x, y] = [sorted[i], sorted[j]];
|
|
99
|
+
if (x.model !== null && x.model === y.model) add(x.agentKey, y.agentKey, 'same-model', null, null);
|
|
100
|
+
const [hx, hy] = [holds.get(x.agentKey) ?? [], holds.get(y.agentKey) ?? []];
|
|
101
|
+
for (const h of hx) {
|
|
102
|
+
if (hy.some((g) => g.slug === h.slug && g.task === h.task)) add(x.agentKey, y.agentKey, 'same-task', h.task, null);
|
|
103
|
+
for (const pair of planOverlaps(h.slug)) {
|
|
104
|
+
const other = pair.a === h.task ? pair.b : pair.b === h.task ? pair.a : null;
|
|
105
|
+
if (other !== null && hy.some((g) => g.slug === h.slug && g.task === other)) add(x.agentKey, y.agentKey, 'overlap', null, [h.task, other]);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return [...out.values()].sort((p, q) => (p.a < q.a ? -1 : p.a > q.a ? 1 : p.b < q.b ? -1 : p.b > q.b ? 1 : p.basis < q.basis ? -1 : p.basis > q.basis ? 1 : String(p.task ?? p.tasks).localeCompare(String(q.task ?? q.tasks))));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function computeStats({
|
|
114
|
+
env = process.env, cwd = process.cwd(), windowMin = DEFAULT_WINDOW, exec, alive, fsImpl = fs, platform, home, now = Date.now,
|
|
115
|
+
} = {}) {
|
|
116
|
+
const built = buildTree({ env, cwd, ...(exec ? { exec } : {}), ...(alive ? { alive } : {}), ...(fsImpl !== fs ? { fsImpl } : {}), ...(platform ? { platform } : {}), ...(home ? { home } : {}) });
|
|
117
|
+
if (!built.ok) return { ok: false, error: built.error };
|
|
118
|
+
const registry = registryById();
|
|
119
|
+
const roots = checkoutRoots({ cwd, exec, fsImpl });
|
|
120
|
+
const startMs = now() - windowMin * 60000;
|
|
121
|
+
const ranked = [...built.body.nodes].sort((a , b ) => (a.lastRecordAt === b.lastRecordAt ? 0 : a.lastRecordAt === null ? 1 : b.lastRecordAt === null ? -1 : a.lastRecordAt < b.lastRecordAt ? 1 : -1) || (a.agentKey < b.agentKey ? -1 : 1));
|
|
122
|
+
const chosen = ranked.slice(0, MAX_AGENTS);
|
|
123
|
+
|
|
124
|
+
const holds = new Map ();
|
|
125
|
+
const agents = chosen.map((node ) => {
|
|
126
|
+
const internal = built.internal.get(node.agentKey) ?? {};
|
|
127
|
+
holds.set(node.agentKey, internal.holds ?? []);
|
|
128
|
+
let numbers = { eventsPerMin: null, toolCalls: null, toolErrors: null, toolDurationMs: null, tokens: null };
|
|
129
|
+
if (node.transcript.linked && internal.file) {
|
|
130
|
+
try {
|
|
131
|
+
const mod = READERS[registry[node.harness].reader]();
|
|
132
|
+
const header = mod.header({ file: internal.file, companion: internal.companion });
|
|
133
|
+
if (inCheckouts(header.cwd, roots, fsImpl)) {
|
|
134
|
+
const { events, clock } = readWindow(mod, node, internal);
|
|
135
|
+
numbers = summarize(events, clock, startMs, windowMin);
|
|
136
|
+
}
|
|
137
|
+
} catch { /* an unreadable transcript is unknown, not zero */ }
|
|
138
|
+
}
|
|
139
|
+
return { agentKey: node.agentKey, harness: node.harness, model: node.model, ...numbers, lastRecordAt: node.lastRecordAt };
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const planCache = new Map ();
|
|
143
|
+
const planOverlaps = (slug ) => {
|
|
144
|
+
if (!planCache.has(slug)) {
|
|
145
|
+
let list = [];
|
|
146
|
+
try { list = overlaps(loadPlan({ roots, slug, fsImpl }).graph); } catch { /* no readable plan: no overlap pairs */ }
|
|
147
|
+
planCache.set(slug, list);
|
|
148
|
+
}
|
|
149
|
+
return planCache.get(slug) ;
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
ok: true,
|
|
154
|
+
body: {
|
|
155
|
+
v: 1, projectKey: built.body.projectKey, windowMin, agents, models: groupModels(agents),
|
|
156
|
+
pairs: pairsOf(agents, holds, planOverlaps), notChecked: Math.max(0, ranked.length - MAX_AGENTS),
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
module.exports = { computeStats, DEFAULT_WINDOW, MAX_AGENTS };
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The shared view helper for verbs that answer in a brief or a full view (feature 137, IC-002, IC-003, IC-007,
|
|
7
|
+
* IC-008). A verb declares its own brief spec beside its handler; this module resolves the view a call asked for,
|
|
8
|
+
* projects a full answer onto a keep-path list, pages an ordered item list by cursor, builds next lines from
|
|
9
|
+
* validated parsed options, and serializes JSON compact off a terminal and indented on one.
|
|
10
|
+
*
|
|
11
|
+
* Like `cli-result.ts`, this module requires nothing from src/runtime/ at load time, so any handler can require
|
|
12
|
+
* it without a cycle. It requires nothing at all.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
/** One verb's brief view, declared beside its handler. */
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
/** The ordering key of a pageable item: the creating event's time in milliseconds, then its event id. */
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
/** A caller mistake in a view option: the handler reports it as a usage error (exit 2). */
|
|
32
|
+
class ViewUsageError extends Error {}
|
|
33
|
+
|
|
34
|
+
const VIEWS = ['brief', 'full'];
|
|
35
|
+
|
|
36
|
+
function resolveView(raw , defaultView ) {
|
|
37
|
+
if (raw === undefined || raw === null) return defaultView;
|
|
38
|
+
if (typeof raw === 'string' && VIEWS.includes(raw)) return raw ;
|
|
39
|
+
throw new ViewUsageError(`--view must be brief or full (got '${String(raw)}')`);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ---------------------------------------------------------------------------------------------- keep paths
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
const KEEP_PATH = /^\$(?:\.[A-Za-z_][A-Za-z0-9_]*(?:\[\])?)+$/;
|
|
50
|
+
const KEEP_SEGMENT = /\.([A-Za-z_][A-Za-z0-9_]*)(\[\])?/g;
|
|
51
|
+
const keepTrees = new Map ();
|
|
52
|
+
|
|
53
|
+
/** Builds the tree of a keep list; a malformed path, or one name used both as an array and not, throws. */
|
|
54
|
+
function keepTree(keep ) {
|
|
55
|
+
const cacheKey = keep.join('\n');
|
|
56
|
+
const cached = keepTrees.get(cacheKey);
|
|
57
|
+
if (cached) return cached;
|
|
58
|
+
const root = { all: false, children: new Map() };
|
|
59
|
+
for (const path of keep) {
|
|
60
|
+
if (!KEEP_PATH.test(path)) throw new Error(`malformed keep path '${path}'`);
|
|
61
|
+
let node = root;
|
|
62
|
+
for (const match of path.matchAll(KEEP_SEGMENT)) {
|
|
63
|
+
const [, name, brackets] = match;
|
|
64
|
+
const array = brackets !== undefined;
|
|
65
|
+
let child = node.children.get(name);
|
|
66
|
+
if (!child) {
|
|
67
|
+
child = { array, node: { all: false, children: new Map() } };
|
|
68
|
+
node.children.set(name, child);
|
|
69
|
+
} else if (child.array !== array) {
|
|
70
|
+
throw new Error(`keep path '${path}' uses '${name}' both as an array and as a single value`);
|
|
71
|
+
}
|
|
72
|
+
node = child.node;
|
|
73
|
+
}
|
|
74
|
+
node.all = true;
|
|
75
|
+
}
|
|
76
|
+
keepTrees.set(cacheKey, root);
|
|
77
|
+
return root;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function projectValue(value , node ) {
|
|
81
|
+
if (node.all) return value;
|
|
82
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) return undefined;
|
|
83
|
+
const out = {};
|
|
84
|
+
for (const key of Object.keys(value)) {
|
|
85
|
+
const child = node.children.get(key);
|
|
86
|
+
if (!child || value[key] === undefined) continue;
|
|
87
|
+
let part;
|
|
88
|
+
if (child.array) {
|
|
89
|
+
part = Array.isArray(value[key])
|
|
90
|
+
? value[key].map((element ) => (child.node.all ? element : (projectValue(element, child.node) ?? {})))
|
|
91
|
+
: undefined;
|
|
92
|
+
} else {
|
|
93
|
+
part = projectValue(value[key], child.node);
|
|
94
|
+
// An object none of whose kept paths exist is absent, not an empty shell.
|
|
95
|
+
if (part !== undefined && !child.node.all && Object.keys(part).length === 0) part = undefined;
|
|
96
|
+
}
|
|
97
|
+
if (part !== undefined) out[key] = part;
|
|
98
|
+
}
|
|
99
|
+
return out;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Projects a full answer onto keep paths: `$` then segments `.name` or `.name[]`; a path keeps the whole value at
|
|
104
|
+
* its last segment, `[]` maps over an array (order and length kept), an absent path is skipped, and output keys
|
|
105
|
+
* follow the full answer's key order.
|
|
106
|
+
*/
|
|
107
|
+
function projectBrief(full , keep ) {
|
|
108
|
+
return projectValue(full, keepTree(keep)) ?? {};
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// -------------------------------------------------------------------------------------------------- cursor
|
|
112
|
+
|
|
113
|
+
const CURSOR = /^([0-9]{1,15}):([0-9]{8}T[0-9]{9}Z-[0-9a-hjkmnp-tv-z]{6})$/;
|
|
114
|
+
const DIGITS = /^[0-9]+$/;
|
|
115
|
+
|
|
116
|
+
function encodeCursor(key ) {
|
|
117
|
+
if (!Number.isSafeInteger(key.atMs) || key.atMs < 0 || String(key.atMs).length > 15) {
|
|
118
|
+
throw new TypeError(`cursor time must be 0 to 15 digits of milliseconds (got ${key.atMs})`);
|
|
119
|
+
}
|
|
120
|
+
return `${key.atMs}:${key.event}`;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** The key a cursor names, or null when the text is not a cursor of this format. */
|
|
124
|
+
function decodeCursor(text ) {
|
|
125
|
+
const match = typeof text === 'string' ? CURSOR.exec(text) : null;
|
|
126
|
+
return match ? { atMs: Number(match[1]), event: match[2] } : null;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** True when key `a` orders after key `b`: later time, or the same time and a greater event id. */
|
|
130
|
+
function keyAfter(a , b ) {
|
|
131
|
+
return a.atMs > b.atMs || (a.atMs === b.atMs && a.event > b.event);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Pages an item list already in ascending key order. Items at or before the cursor are dropped, then the first
|
|
136
|
+
* `limit` are kept. `nextCursor` is the last shown item's key when items remain after it, else null.
|
|
137
|
+
*/
|
|
138
|
+
function pageItems (items , opts
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
) {
|
|
142
|
+
let limit = opts.defaultLimit;
|
|
143
|
+
if (opts.limit !== undefined) {
|
|
144
|
+
const text = String(opts.limit);
|
|
145
|
+
if (!DIGITS.test(text) || Number(text) < 1) {
|
|
146
|
+
throw new ViewUsageError(`--limit must be a whole number of 1 or more (at most ${opts.maxLimit} are returned)`);
|
|
147
|
+
}
|
|
148
|
+
limit = Math.min(opts.maxLimit, Number(text));
|
|
149
|
+
}
|
|
150
|
+
let rest = items;
|
|
151
|
+
if (opts.cursor !== undefined) {
|
|
152
|
+
const after = decodeCursor(opts.cursor);
|
|
153
|
+
if (after === null) throw new ViewUsageError('--cursor must be the nextCursor of an earlier list');
|
|
154
|
+
rest = items.filter((item) => keyAfter(opts.key(item), after));
|
|
155
|
+
}
|
|
156
|
+
const page = limit === null ? rest.slice() : rest.slice(0, limit);
|
|
157
|
+
const more = page.length < rest.length;
|
|
158
|
+
return {
|
|
159
|
+
items: page,
|
|
160
|
+
shown: page.length,
|
|
161
|
+
nextCursor: more && page.length > 0 ? encodeCursor(opts.key(page[page.length - 1])) : null,
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ----------------------------------------------------------------------------------------------- next lines
|
|
166
|
+
|
|
167
|
+
const SAFE_VALUE = /^[A-Za-z0-9._:@,/-]{1,200}$/;
|
|
168
|
+
const LABELS = { page: 'Next page', 'page-full': 'Full view of this page', full: 'Full view' } ;
|
|
169
|
+
const VIEW_FLAGS = ['cursor', 'limit', 'view'];
|
|
170
|
+
|
|
171
|
+
function kebab(name ) {
|
|
172
|
+
return name.replace(/[A-Z]/g, (letter) => `-${letter.toLowerCase()}`);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* `doflow-run <verb>[ --action <action>]( --<flag>[ <value>])*`, flags in the order of `carried`. A value renders
|
|
177
|
+
* only when it is `true` (bare flag) or a short safe token (or finite number); any other value renders as the
|
|
178
|
+
* placeholder `<flag>`, so free text is never carried. `false`, null and undefined are omitted.
|
|
179
|
+
*/
|
|
180
|
+
function commandLine(verb , action , carried ) {
|
|
181
|
+
const parts = ['doflow-run', verb];
|
|
182
|
+
if (action !== undefined) parts.push('--action', action);
|
|
183
|
+
for (const [name, value] of Object.entries(carried)) {
|
|
184
|
+
if (value === undefined || value === null || value === false) continue;
|
|
185
|
+
const flag = kebab(name);
|
|
186
|
+
parts.push(`--${flag}`);
|
|
187
|
+
if (value === true) continue;
|
|
188
|
+
const text = typeof value === 'number' && Number.isFinite(value) ? String(value) : value;
|
|
189
|
+
parts.push(typeof text === 'string' && SAFE_VALUE.test(text) ? text : `<${flag}>`);
|
|
190
|
+
}
|
|
191
|
+
return parts.join(' ');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* The next line of one kind: `page` (the page after this one), `page-full` (this page in the full view, carrying
|
|
196
|
+
* the input cursor and `--limit <shown>`) or `full` (the verb's whole, bounded, full answer). The options echoed
|
|
197
|
+
* are those the spec's `carry` names, in that order, then `--cursor`, `--limit`, `--view` (a `page` line names
|
|
198
|
+
* `--view full` only when the caller's `view` is `full`). Returns no line when
|
|
199
|
+
* the kind has nothing to point at (`page` without a nextCursor, `page-full` with nothing shown). In a JSON answer
|
|
200
|
+
* (`json: true`) the `page-full` and `full` lines end with `--json`, so the command as written prints the full
|
|
201
|
+
* payload; a text answer's lines carry no `--json`.
|
|
202
|
+
*/
|
|
203
|
+
function viewLines(kind , ctx
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
) {
|
|
207
|
+
const args = {};
|
|
208
|
+
for (const name of ctx.spec.carry) {
|
|
209
|
+
if (!VIEW_FLAGS.includes(name)) args[name] = ctx.carried[name];
|
|
210
|
+
}
|
|
211
|
+
if (kind === 'page') {
|
|
212
|
+
if (!ctx.nextCursor) return [];
|
|
213
|
+
args.cursor = ctx.nextCursor;
|
|
214
|
+
args.limit = ctx.spec.carry.includes('limit') ? ctx.carried.limit : undefined;
|
|
215
|
+
// A walk stays in the view it started in: brief is the default, so only the full view is named.
|
|
216
|
+
if (ctx.view === 'full') args.view = 'full';
|
|
217
|
+
} else if (kind === 'page-full') {
|
|
218
|
+
if (!ctx.shown || ctx.shown < 1) return [];
|
|
219
|
+
args.cursor = ctx.cursor ?? undefined;
|
|
220
|
+
args.limit = ctx.shown;
|
|
221
|
+
args.view = 'full';
|
|
222
|
+
} else {
|
|
223
|
+
args.view = 'full';
|
|
224
|
+
}
|
|
225
|
+
const asJson = ctx.json === true && kind !== 'page' ? ' --json' : '';
|
|
226
|
+
return [`${LABELS[kind]}: ${commandLine(ctx.spec.verb, ctx.spec.action, args)}${asJson}`];
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
/**
|
|
230
|
+
* Adds the envelope to an answer body: `view` first; `shown` and `nextCursor` directly after `count` when the body
|
|
231
|
+
* has one, else after `view`; `next` last (replacing a `next` the body already carries).
|
|
232
|
+
*/
|
|
233
|
+
function withEnvelope(body , env
|
|
234
|
+
|
|
235
|
+
) {
|
|
236
|
+
const paging = {};
|
|
237
|
+
if (env.shown !== undefined) paging.shown = env.shown;
|
|
238
|
+
if (env.nextCursor !== undefined) paging.nextCursor = env.nextCursor;
|
|
239
|
+
const out = { view: env.view };
|
|
240
|
+
const hasCount = 'count' in body;
|
|
241
|
+
if (!hasCount) Object.assign(out, paging);
|
|
242
|
+
for (const [key, value] of Object.entries(body)) {
|
|
243
|
+
if (key === 'view' || key === 'shown' || key === 'nextCursor' || key === 'next') continue;
|
|
244
|
+
out[key] = value;
|
|
245
|
+
if (key === 'count') Object.assign(out, paging);
|
|
246
|
+
}
|
|
247
|
+
const next = env.next ?? (body.next );
|
|
248
|
+
if (next !== undefined) out.next = next;
|
|
249
|
+
return out;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// --------------------------------------------------------------------------------------------- serialization
|
|
253
|
+
|
|
254
|
+
/** Compact JSON off a terminal, two-space indented on one; `isTTY` defaults to `process.stdout.isTTY === true`. */
|
|
255
|
+
function serializeJson(value , opts = {}) {
|
|
256
|
+
const isTTY = opts.isTTY ?? process.stdout.isTTY === true;
|
|
257
|
+
return isTTY === true ? JSON.stringify(value, null, 2) : JSON.stringify(value);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function emitJson(value , opts = {}) {
|
|
261
|
+
console.log(serializeJson(value, opts));
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
module.exports = {
|
|
265
|
+
ViewUsageError, resolveView, projectBrief, pageItems, encodeCursor, decodeCursor,
|
|
266
|
+
commandLine, viewLines, withEnvelope, serializeJson, emitJson,
|
|
267
|
+
};
|