@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,156 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('node:path');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ReviewGuardError extends Error {
|
|
22
|
+
code ;
|
|
23
|
+
constructor(code , message ) {
|
|
24
|
+
super(message);
|
|
25
|
+
this.name = 'ReviewGuardError';
|
|
26
|
+
this.code = code;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
class NeverReviewOwnCodeError extends ReviewGuardError {
|
|
31
|
+
constructor(message ) {
|
|
32
|
+
super('ERR_REVIEWER_SELF_REVIEW', `ERR_REVIEWER_SELF_REVIEW: ${message}`);
|
|
33
|
+
this.name = 'NeverReviewOwnCodeError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class MechanicalPreChecksIncompleteError extends ReviewGuardError {
|
|
38
|
+
failures ;
|
|
39
|
+
constructor(failures ) {
|
|
40
|
+
super(
|
|
41
|
+
'ERR_MECHANICAL_CHECKS_FAILED',
|
|
42
|
+
`ERR_MECHANICAL_CHECKS_FAILED: Mechanical pre-checks failed prior to review dispatch: ${failures.join('; ')}`
|
|
43
|
+
);
|
|
44
|
+
this.name = 'MechanicalPreChecksIncompleteError';
|
|
45
|
+
this.failures = failures;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Asserts reviewer independence:
|
|
51
|
+
* 1. authorRole !== reviewerRole (e.g. core-implementer cannot review own code)
|
|
52
|
+
* 2. authorIdentity !== reviewerIdentity (model/session instance separation)
|
|
53
|
+
* Throws NeverReviewOwnCodeError (ERR_REVIEWER_SELF_REVIEW) if violated.
|
|
54
|
+
*/
|
|
55
|
+
function assertReviewerSeparation(context ) {
|
|
56
|
+
if (!context) {
|
|
57
|
+
throw new TypeError('assertReviewerSeparation: context must be provided');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// 1. Role separation invariant: authorRole !== reviewerRole
|
|
61
|
+
if (context.authorRole === context.reviewerRole) {
|
|
62
|
+
throw new NeverReviewOwnCodeError(
|
|
63
|
+
`Reviewer self-review violation: authorRole '${context.authorRole}' matches reviewerRole '${context.reviewerRole}'. Reviewer must be an independent role.`
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 2. Identity separation invariant: authorIdentity !== reviewerIdentity
|
|
68
|
+
if (
|
|
69
|
+
context.authorIdentity &&
|
|
70
|
+
context.reviewerIdentity &&
|
|
71
|
+
context.authorIdentity.trim() === context.reviewerIdentity.trim()
|
|
72
|
+
) {
|
|
73
|
+
throw new NeverReviewOwnCodeError(
|
|
74
|
+
`Reviewer self-review violation: authorIdentity '${context.authorIdentity}' matches reviewerIdentity '${context.reviewerIdentity}'. Reviewer must be an independent worker identity.`
|
|
75
|
+
);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Validates that mechanical pre-checks (syntax, build, unit test pass) are complete before review dispatch.
|
|
81
|
+
* Throws MechanicalPreChecksIncompleteError (ERR_MECHANICAL_CHECKS_FAILED) if failing.
|
|
82
|
+
*/
|
|
83
|
+
function assertMechanicalPreChecks(
|
|
84
|
+
taskId ,
|
|
85
|
+
projectRoot ,
|
|
86
|
+
options
|
|
87
|
+
) {
|
|
88
|
+
if (!taskId || typeof taskId !== 'string') {
|
|
89
|
+
throw new TypeError('assertMechanicalPreChecks: taskId must be a non-empty string');
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const fsImpl = options?.fsImpl || fs;
|
|
93
|
+
const root = projectRoot || process.cwd();
|
|
94
|
+
const failures = [];
|
|
95
|
+
|
|
96
|
+
// 1. Readiness evidence is a precondition, not an optional hint.
|
|
97
|
+
try {
|
|
98
|
+
const { readReadinessRecord } = require('../readiness-record.js');
|
|
99
|
+
const res = readReadinessRecord({ stateRoot: root, taskId, slug: options?.slug ?? null, fsImpl });
|
|
100
|
+
if (!res || res.status !== 'found' || !res.record || typeof res.record !== 'object') {
|
|
101
|
+
failures.push(`Task readiness evidence is ${res?.status || 'unavailable'}`);
|
|
102
|
+
} else {
|
|
103
|
+
const rec = res.record;
|
|
104
|
+
if (rec.state !== 'READY') {
|
|
105
|
+
const unmet = Array.isArray(rec.unmet) ? rec.unmet.join(', ') : '';
|
|
106
|
+
failures.push(`Task readiness state is '${rec.state}' (${unmet})`);
|
|
107
|
+
} else if (rec.unmet !== undefined && (!Array.isArray(rec.unmet) || rec.unmet.length > 0)) {
|
|
108
|
+
failures.push('Task readiness evidence is malformed or still has unmet requirements');
|
|
109
|
+
} else {
|
|
110
|
+
// The edit gate's own reading of a record: a version this runtime does not read, a missing
|
|
111
|
+
// field or an evaluation dated in the future does not establish READY.
|
|
112
|
+
const unusable = require('../implementation-gate.js').unusableDetail(rec, new Date());
|
|
113
|
+
if (unusable) failures.push(`Task readiness evidence is unusable (${unusable})`);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch (error ) {
|
|
117
|
+
failures.push(`Task readiness evidence could not be read (${error?.message || 'read error'})`);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// 2. A missing or malformed mechanical-check record cannot establish a successful pre-check.
|
|
121
|
+
const preCheckPath = path.join(root, '.doflow', 'state', 'team', 'pre-checks', `${taskId}.json`);
|
|
122
|
+
try {
|
|
123
|
+
if (!fsImpl.existsSync(preCheckPath)) {
|
|
124
|
+
failures.push(`Pre-check evidence is missing for ${taskId}`);
|
|
125
|
+
} else {
|
|
126
|
+
const data = JSON.parse(fsImpl.readFileSync(preCheckPath, 'utf8'));
|
|
127
|
+
if (!data || typeof data !== 'object' || Array.isArray(data) || typeof data.passed !== 'boolean'
|
|
128
|
+
|| !Array.isArray(data.failures) || data.failures.some((failure ) => typeof failure !== 'string')) {
|
|
129
|
+
failures.push(`Pre-check evidence is malformed for ${taskId}`);
|
|
130
|
+
} else if (data.passed === true && data.failures.length > 0) {
|
|
131
|
+
failures.push('Pre-check evidence reports failures while marked passed');
|
|
132
|
+
} else if (data.passed === false) {
|
|
133
|
+
failures.push(...(data.failures.length > 0 ? data.failures : [`Pre-check reported failure for ${taskId}`]));
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
} catch (error ) {
|
|
137
|
+
failures.push(`Pre-check evidence could not be read for ${taskId} (${error?.message || 'read error'})`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (failures.length > 0) {
|
|
141
|
+
throw new MechanicalPreChecksIncompleteError(failures);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
passed: true,
|
|
146
|
+
failures: [],
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
module.exports = {
|
|
151
|
+
ReviewGuardError,
|
|
152
|
+
NeverReviewOwnCodeError,
|
|
153
|
+
MechanicalPreChecksIncompleteError,
|
|
154
|
+
assertReviewerSeparation,
|
|
155
|
+
assertMechanicalPreChecks,
|
|
156
|
+
};
|
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* `trace analyze` (091 IC-001..IC-005, IC-011): three detectors over the spans view of spans.ts,
|
|
7
|
+
* run per scope (each trace, each ungrouped `session:`/`day:` group), each with a state, plus the
|
|
8
|
+
* verdict rule of `trace discover`. Findings print as two batches the existing verbs accept
|
|
9
|
+
* unchanged: evidence items (`evidence --action add --batch -`) and proposals
|
|
10
|
+
* (`followup --action add --batch <file>`).
|
|
11
|
+
*
|
|
12
|
+
* Read only and pure over the view: nothing is written, no wall-clock field, keys sorted, so the
|
|
13
|
+
* same stores give the same bytes. No field is a score. The rider R1 `context` section reports
|
|
14
|
+
* UserPromptSubmit emission metadata per session; it never changes the verdict or the exit code.
|
|
15
|
+
* Loads no network module; the evidence ledger loads only for `--emit followups --task-id`.
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const nodeFs = require('node:fs');
|
|
19
|
+
const path = require('node:path');
|
|
20
|
+
const crypto = require('node:crypto');
|
|
21
|
+
const { projectSpans } = require('./spans.js');
|
|
22
|
+
const { percentile } = require('./views.js');
|
|
23
|
+
const { finish } = require('./render.js');
|
|
24
|
+
const { maskLine } = require('../mask.js');
|
|
25
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
26
|
+
|
|
27
|
+
const FINDING = 'FINDING';
|
|
28
|
+
const CLEAR = 'CLEAR';
|
|
29
|
+
const NOT_DETERMINED = 'NOT_DETERMINED';
|
|
30
|
+
const THRESHOLD_KEYS = ['minSamples', 'tailRatio', 'minTailMs', 'retryRun'];
|
|
31
|
+
const SPAN_CAP = 20;
|
|
32
|
+
/** IC-011: a whitelist, so a reuse marker added later (089's `reused`) never counts as executed. */
|
|
33
|
+
const EXECUTED_SHARED = new Set(['none', 'leader']);
|
|
34
|
+
const EMIT = new Set(['evidence', 'followups']);
|
|
35
|
+
/** `--run`: a run key or a run id (the spans.ts rule). */
|
|
36
|
+
const RUN_ARG = /^[A-Za-z0-9][A-Za-z0-9._:-]*(?:\/[A-Za-z0-9._:-]+){0,2}$/;
|
|
37
|
+
/** followup.ts STATEMENT_MAX; not required from there, which would load the lifecycle store. */
|
|
38
|
+
const STATEMENT_MAX = 280;
|
|
39
|
+
/** The run shape mask.ts masks (LONG_RUN); a statement never carries one. */
|
|
40
|
+
const LONG_RUN = /[A-Za-z0-9+/=_-]{32,}/;
|
|
41
|
+
/** An evidence id as EvidenceLedger#generateId mints it; anything else is not cited. */
|
|
42
|
+
const EVIDENCE_ID = /^ev_[a-z0-9]+_[0-9a-f]{8}_[a-z0-9]+$/;
|
|
43
|
+
|
|
44
|
+
const sha16 = (text ) => crypto.createHash('sha256').update(text).digest('hex').slice(0, 16);
|
|
45
|
+
const cmp = (a , b ) => (a < b ? -1 : a > b ? 1 : 0);
|
|
46
|
+
const startMs = (s ) => (s.start === null ? Infinity : Date.parse(s.start));
|
|
47
|
+
const byStart = (a , b ) => startMs(a) - startMs(b) || cmp(a.spanId, b.spanId);
|
|
48
|
+
const sum = (list ) => list.reduce((n, v) => n + v, 0);
|
|
49
|
+
|
|
50
|
+
/** Object keys sorted at every level (the spans view rule). */
|
|
51
|
+
function sorted(value ) {
|
|
52
|
+
if (Array.isArray(value)) return value.map(sorted);
|
|
53
|
+
if (value && typeof value === 'object') return Object.fromEntries(Object.keys(value).sort().map((k) => [k, sorted(value[k])]));
|
|
54
|
+
return value;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// ── thresholds (IC-003) ─────────────────────────────────────────────────────────────────────────
|
|
58
|
+
|
|
59
|
+
/** The `analysis` block as thresholds, or the usage error naming the first bad key. */
|
|
60
|
+
function checkThresholds(block ) {
|
|
61
|
+
if (!block || typeof block !== 'object' || Array.isArray(block)) return { error: 'analysis block missing from core/registry/telemetry.json' };
|
|
62
|
+
for (const key of THRESHOLD_KEYS) {
|
|
63
|
+
if (!Number.isInteger(block[key]) || block[key] < 1) return { error: `analysis.${key} must be a positive integer (core/registry/telemetry.json)` };
|
|
64
|
+
}
|
|
65
|
+
return { thresholds: Object.fromEntries(THRESHOLD_KEYS.map((k) => [k, block[k]])) };
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** The registry's `analysis` block, read where spans.ts reads the kinds. */
|
|
69
|
+
function readThresholds() {
|
|
70
|
+
let block ;
|
|
71
|
+
try { block = JSON.parse(nodeFs.readFileSync(path.join(REPO_ROOT, 'core', 'registry', 'telemetry.json'), 'utf8')).analysis; } catch { block = undefined; }
|
|
72
|
+
return checkThresholds(block);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── detectors ───────────────────────────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
/** Each trace and each ungrouped group is one scope. */
|
|
78
|
+
function scopesOf(view ) {
|
|
79
|
+
return [
|
|
80
|
+
...view.traces.map((tr ) => ({ scope: tr.traceId, scopeKind: 'trace', spans: tr.spans })),
|
|
81
|
+
...view.ungrouped.map((g ) => ({ scope: g.group, scopeKind: g.group.startsWith('session:') ? 'session' : 'day', spans: g.spans })),
|
|
82
|
+
];
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* A finding over `spans`: its id hashes every span id; the listed spans are the first 20 of `keep`
|
|
87
|
+
* (the detector's order), shown by start then spanId, with `measures.spanCount` when capped.
|
|
88
|
+
*/
|
|
89
|
+
function finding(detector , sc , spans , measures , keep = [...spans].sort(byStart)) {
|
|
90
|
+
const ids = spans.map((s) => s.spanId).sort();
|
|
91
|
+
return {
|
|
92
|
+
id: sha16(`${detector}\n${sc.scope}\n${ids.join(',')}`), detector, scope: sc.scope, scopeKind: sc.scopeKind,
|
|
93
|
+
spans: keep.slice(0, SPAN_CAP).sort(byStart).map((s) => ({ spanId: s.spanId, locator: s.locator })),
|
|
94
|
+
measures: spans.length > SPAN_CAP ? { ...measures, spanCount: spans.length } : measures,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function result(id , findings , determined , detail ) {
|
|
99
|
+
const status = findings.length ? FINDING : determined ? CLEAR : NOT_DETERMINED;
|
|
100
|
+
return { id, status, detail: detail[status], findings };
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** 089: a reused or follower check carries lookup or wait time, not a run of the check. */
|
|
104
|
+
const ranItself = (s ) => s.attrs?.shared == null || EXECUTED_SHARED.has(s.attrs.shared);
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Per scope and (kind, name): p95 >= tailRatio x p50 and p95 - p50 >= minTailMs. A span whose
|
|
108
|
+
* `shared` is not none or leader (089 reused, follower) never enters a group.
|
|
109
|
+
*/
|
|
110
|
+
function latencyHotspots(scopes , th , latency ) {
|
|
111
|
+
const findings = [];
|
|
112
|
+
let determined = false;
|
|
113
|
+
for (const sc of scopes) {
|
|
114
|
+
const scopeFindings = [];
|
|
115
|
+
const groups = new Map ();
|
|
116
|
+
for (const s of sc.spans) {
|
|
117
|
+
if (!Number.isInteger(s.durationMs) || !ranItself(s)) continue;
|
|
118
|
+
const key = `${s.kind}\n${s.name ?? ''}`;
|
|
119
|
+
if (!groups.has(key)) groups.set(key, { kind: s.kind, name: s.name ?? null, spans: [] });
|
|
120
|
+
groups.get(key).spans.push(s);
|
|
121
|
+
}
|
|
122
|
+
for (const g of [...groups.values()].sort((a, b) => cmp(a.kind, b.kind) || cmp(a.name ?? '', b.name ?? ''))) {
|
|
123
|
+
if (g.spans.length < th.minSamples) continue;
|
|
124
|
+
determined = true;
|
|
125
|
+
const d = g.spans.map((s ) => s.durationMs);
|
|
126
|
+
const p50 = percentile(d, 0.5) ;
|
|
127
|
+
const p95 = percentile(d, 0.95) ;
|
|
128
|
+
const row = { kind: g.kind, name: g.name, count: d.length, p50Ms: p50, p95Ms: p95, totalMs: sum(d) };
|
|
129
|
+
latency.push({ scope: sc.scope, ...row });
|
|
130
|
+
if (p95 >= th.tailRatio * p50 && p95 - p50 >= th.minTailMs) {
|
|
131
|
+
const slow = g.spans.filter((s ) => s.durationMs > p50);
|
|
132
|
+
scopeFindings.push(finding('latency-hotspots', sc, slow, row, [...slow].sort((a, b) => b.durationMs - a.durationMs || cmp(a.spanId, b.spanId))));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
// A tier's tail is the same finding as its checks' only when a check of that tier is a hotspot too.
|
|
136
|
+
const checkNames = new Set(scopeFindings.filter((f) => f.measures.kind === 'verify.check').map((f) => f.measures.name));
|
|
137
|
+
for (const f of scopeFindings) {
|
|
138
|
+
if (f.measures.kind === 'verify.tier' && checkNames.has(f.measures.name)) f.derivedFrom = 'verify.check';
|
|
139
|
+
}
|
|
140
|
+
findings.push(...scopeFindings);
|
|
141
|
+
}
|
|
142
|
+
return result('latency-hotspots', findings, determined, {
|
|
143
|
+
[FINDING]: `${findings.length} span name(s) have p95 >= ${th.tailRatio}x p50 and a tail of at least ${th.minTailMs} ms.`,
|
|
144
|
+
[CLEAR]: `no span name with ${th.minSamples} or more durations has p95 >= ${th.tailRatio}x p50 and a tail of at least ${th.minTailMs} ms.`,
|
|
145
|
+
[NOT_DETERMINED]: `no span name has ${th.minSamples} durations.`,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** IC-011: a status other than NOT_RUN or null, and `shared` none, leader or null. */
|
|
150
|
+
const executed = (s ) => s.attrs.status != null && s.attrs.status !== 'NOT_RUN' && ranItself(s);
|
|
151
|
+
|
|
152
|
+
/** Per scope: one check key passed by two or more executed checks with no FAIL of that key between. */
|
|
153
|
+
function redundantVerification(scopes ) {
|
|
154
|
+
const findings = [];
|
|
155
|
+
let determined = false;
|
|
156
|
+
for (const sc of scopes) {
|
|
157
|
+
const checks = sc.spans.filter((s ) => s.kind === 'verify.check' && typeof s.attrs.check_key === 'string').sort(byStart);
|
|
158
|
+
const passes = new Map ();
|
|
159
|
+
for (const s of checks) {
|
|
160
|
+
if (!executed(s)) continue;
|
|
161
|
+
determined = true;
|
|
162
|
+
if (s.attrs.status !== 'PASS') continue;
|
|
163
|
+
if (!passes.has(s.attrs.check_key)) passes.set(s.attrs.check_key, []);
|
|
164
|
+
passes.get(s.attrs.check_key) .push(s);
|
|
165
|
+
}
|
|
166
|
+
for (const [key, list] of [...passes.entries()].sort(([a], [b]) => cmp(a, b))) {
|
|
167
|
+
if (list.length < 2) continue;
|
|
168
|
+
const first = list[0];
|
|
169
|
+
const last = list[list.length - 1];
|
|
170
|
+
if (checks.some((s ) => s.attrs.check_key === key && s.attrs.status === 'FAIL' && byStart(first, s) < 0 && byStart(s, last) < 0)) continue;
|
|
171
|
+
findings.push(finding('redundant-verification', sc, list, { checkKey: key, passes: list.length, tier: first.attrs.tier ?? null }));
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
return result('redundant-verification', findings, determined, {
|
|
175
|
+
[FINDING]: `${findings.length} check key(s) passed more than once with no failure between.`,
|
|
176
|
+
[CLEAR]: 'no executed check key passed twice without a failure between.',
|
|
177
|
+
[NOT_DETERMINED]: 'no executed verify.check carries a check_key.',
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Per scope and task (a null task groups by scope): a maximal run of at least `retryRun` FAIL runs
|
|
183
|
+
* with one failure class and no PASS between. The class is the sorted set of FAIL tiers under the
|
|
184
|
+
* run, or `exit:<code>` when it has no tier spans. A run whose status is neither PASS nor FAIL
|
|
185
|
+
* (null, UNRESOLVED, ...) neither extends nor breaks a sequence.
|
|
186
|
+
*/
|
|
187
|
+
function retryLoops(scopes , th ) {
|
|
188
|
+
const findings = [];
|
|
189
|
+
let determined = false;
|
|
190
|
+
for (const sc of scopes) {
|
|
191
|
+
const runs = sc.spans.filter((s ) => s.kind === 'verify.run').sort(byStart);
|
|
192
|
+
if (runs.some((s ) => s.attrs.status != null)) determined = true;
|
|
193
|
+
const tiers = new Map ();
|
|
194
|
+
for (const s of sc.spans) {
|
|
195
|
+
if (s.kind !== 'verify.tier' || typeof s.attrs.verify_id !== 'string') continue;
|
|
196
|
+
if (!tiers.has(s.attrs.verify_id)) tiers.set(s.attrs.verify_id, []);
|
|
197
|
+
tiers.get(s.attrs.verify_id) .push(s);
|
|
198
|
+
}
|
|
199
|
+
const classOf = (r ) => {
|
|
200
|
+
const under = tiers.get(r.attrs.verify_id) || [];
|
|
201
|
+
if (!under.length) return `exit:${r.attrs.exit_code ?? null}`;
|
|
202
|
+
return [...new Set(under.filter((s) => s.attrs.status === 'FAIL').map((s) => s.attrs.tier ?? s.name))].sort().join(',');
|
|
203
|
+
};
|
|
204
|
+
const groups = new Map ();
|
|
205
|
+
for (const r of runs) {
|
|
206
|
+
const task = r.attrs.task ?? null;
|
|
207
|
+
if (!groups.has(task)) groups.set(task, []);
|
|
208
|
+
groups.get(task) .push(r);
|
|
209
|
+
}
|
|
210
|
+
for (const [task, list] of groups) {
|
|
211
|
+
let seq = [];
|
|
212
|
+
let cls = null;
|
|
213
|
+
const close = () => {
|
|
214
|
+
if (seq.length >= th.retryRun) {
|
|
215
|
+
findings.push(finding('retry-loops', sc, seq, { task, runs: seq.length, failureClass: cls, grouping: task === null ? 'scope' : 'task' }));
|
|
216
|
+
}
|
|
217
|
+
seq = [];
|
|
218
|
+
cls = null;
|
|
219
|
+
};
|
|
220
|
+
for (const r of list) {
|
|
221
|
+
if (r.attrs.status === 'PASS') { close(); continue; }
|
|
222
|
+
if (r.attrs.status !== 'FAIL') continue;
|
|
223
|
+
const c = classOf(r);
|
|
224
|
+
if (seq.length && c !== cls) close();
|
|
225
|
+
seq.push(r);
|
|
226
|
+
cls = c;
|
|
227
|
+
}
|
|
228
|
+
close();
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
return result('retry-loops', findings, determined, {
|
|
232
|
+
[FINDING]: `${findings.length} task(s) failed verification ${th.retryRun} or more times in a row with the same failure class.`,
|
|
233
|
+
[CLEAR]: `no task failed verification ${th.retryRun} times in a row with the same failure class.`,
|
|
234
|
+
[NOT_DETERMINED]: 'no verify.run carries a status.',
|
|
235
|
+
});
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* Rider R1: per session, over hook spans that carry `session` (a `test` span is a folded post hook
|
|
240
|
+
* line, so it counts as a call): calls, emissions (`emit` or `refused:*`), their bytes, and the
|
|
241
|
+
* emissions whose digest an earlier emission of the session already had.
|
|
242
|
+
*/
|
|
243
|
+
function contextOf(scopes ) {
|
|
244
|
+
const hooks = scopes.flatMap((sc) => sc.spans).filter((s ) => s.kind === 'hook' || s.kind === 'test').sort(byStart);
|
|
245
|
+
if (!hooks.some((s ) => typeof s.attrs.emit_reason === 'string')) return { status: NOT_DETERMINED, sessions: [] };
|
|
246
|
+
const rows = new Map ();
|
|
247
|
+
for (const s of hooks) {
|
|
248
|
+
const session = s.attrs.session;
|
|
249
|
+
if (typeof session !== 'string') continue;
|
|
250
|
+
if (!rows.has(session)) rows.set(session, { session, hookCalls: 0, emissions: 0, emittedBytes: 0, duplicates: 0, duplicateBytes: 0, reasons: {}, seen: new Set() });
|
|
251
|
+
const row = rows.get(session);
|
|
252
|
+
row.hookCalls += 1;
|
|
253
|
+
const reason = s.attrs.emit_reason;
|
|
254
|
+
if (typeof reason !== 'string') continue;
|
|
255
|
+
row.reasons[reason] = (row.reasons[reason] || 0) + 1;
|
|
256
|
+
if (reason !== 'emit' && !reason.startsWith('refused:')) continue;
|
|
257
|
+
const bytes = Number.isInteger(s.attrs.emit_bytes) ? s.attrs.emit_bytes : 0;
|
|
258
|
+
row.emissions += 1;
|
|
259
|
+
row.emittedBytes += bytes;
|
|
260
|
+
const digest = s.attrs.emit_digest;
|
|
261
|
+
if (typeof digest !== 'string') continue;
|
|
262
|
+
if (row.seen.has(digest)) { row.duplicates += 1; row.duplicateBytes += bytes; } else row.seen.add(digest);
|
|
263
|
+
}
|
|
264
|
+
return { status: 'MEASURED', sessions: [...rows.values()].sort((a, b) => cmp(a.session, b.session)).map(({ seen, ...row }) => row) };
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* The IC-002 analysis view of a spans view. Pure.
|
|
269
|
+
* @param {Object} view `{v, traces, ungrouped, gaps}` from projectSpans
|
|
270
|
+
* @param {{minSamples: number, tailRatio: number, minTailMs: number, retryRun: number}} thresholds
|
|
271
|
+
*/
|
|
272
|
+
function buildAnalysis(view , thresholds ) {
|
|
273
|
+
const scopes = scopesOf(view);
|
|
274
|
+
const latency = [];
|
|
275
|
+
const detectors = [latencyHotspots(scopes, thresholds, latency), redundantVerification(scopes), retryLoops(scopes, thresholds)];
|
|
276
|
+
const undetermined = detectors.filter((d) => d.status === NOT_DETERMINED).length;
|
|
277
|
+
let verdict ;
|
|
278
|
+
if (detectors.some((d) => d.status === FINDING)) verdict = 'findings';
|
|
279
|
+
else if (undetermined === detectors.length) verdict = 'insufficient-data';
|
|
280
|
+
else if (undetermined) verdict = 'no-findings-in-the-analyses-that-could-run';
|
|
281
|
+
else verdict = 'no-findings';
|
|
282
|
+
return {
|
|
283
|
+
v: 1, view: 'analyze', thresholds: { ...thresholds }, scopes: scopes.length, verdict, detectors,
|
|
284
|
+
latency: latency.sort((a, b) => cmp(a.scope, b.scope) || cmp(a.kind, b.kind) || cmp(a.name ?? '', b.name ?? '')),
|
|
285
|
+
context: contextOf(scopes),
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
const findingsOf = (analysis ) => analysis.detectors.flatMap((d ) => d.findings);
|
|
290
|
+
|
|
291
|
+
// ── batches (IC-004, IC-005) ────────────────────────────────────────────────────────────────────
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* One `runtime-observation` item per finding. The command names only the flags that change the
|
|
295
|
+
* view, never the project argument; the content holds `finding <id>` once.
|
|
296
|
+
*/
|
|
297
|
+
function buildEvidenceBatch(analysis , { run = null, global = false } = {}) {
|
|
298
|
+
const command = ['doflow trace analyze --json', run ? `--run ${run}` : null, global ? '-g' : null].filter(Boolean).join(' ');
|
|
299
|
+
return findingsOf(analysis).map((f ) => ({
|
|
300
|
+
kind: 'runtime-observation',
|
|
301
|
+
provenance: 'extracted',
|
|
302
|
+
source: { provider: 'doflow', capability: 'trace.analyze' },
|
|
303
|
+
observation: { command, exitCode: 1 },
|
|
304
|
+
content: [`trace-analyze finding ${f.id}`, `detector=${f.detector}`,
|
|
305
|
+
// An ungrouped scope already reads `<kind>:<id>`; a trace scope gains its `trace:` prefix.
|
|
306
|
+
`scope=${f.scopeKind === 'trace' ? `trace:${f.scope}` : f.scope}`,
|
|
307
|
+
`spans=${f.spans.map((s ) => s.spanId).join(',')}`,
|
|
308
|
+
...Object.keys(f.measures).sort().map((k) => `${k}=${f.measures[k] ?? '-'}`)].join(' '),
|
|
309
|
+
}));
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
/** The statement's metric: a fixed prefix, the variable name part and the value. */
|
|
313
|
+
function metricOf(f ) {
|
|
314
|
+
const m = f.measures;
|
|
315
|
+
if (f.detector === 'latency-hotspots') return { prefix: `p95_ms:${m.kind}/`, name: m.name ?? '-', value: m.p95Ms };
|
|
316
|
+
if (f.detector === 'redundant-verification') return { prefix: 'passes:', name: m.tier ?? '-', value: m.passes };
|
|
317
|
+
return { prefix: 'failed_runs:', name: m.task ?? f.scopeKind, value: m.runs };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/** Ledger items whose content names the finding, newest first, at most two. */
|
|
321
|
+
function citedIds(f , items ) {
|
|
322
|
+
if (!Array.isArray(items)) return [];
|
|
323
|
+
const needle = `finding ${f.id}`;
|
|
324
|
+
return items.filter((it) => it && typeof it.id === 'string' && EVIDENCE_ID.test(it.id) && typeof it.content === 'string' && it.content.includes(needle))
|
|
325
|
+
.sort((a, b) => cmp(b.freshness?.observedAt ?? '', a.freshness?.observedAt ?? '') || cmp(b.id, a.id))
|
|
326
|
+
.slice(0, 2).map((it) => it.id);
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
/**
|
|
330
|
+
* The IC-005 statement. Over 280 characters, the second evidence id goes, then the name part is
|
|
331
|
+
* truncated until the line fits and carries no 32-character run; a name the masker would change
|
|
332
|
+
* (a key word such as `token`, an address) is replaced by `h<first 8 hex of its sha256>`.
|
|
333
|
+
*/
|
|
334
|
+
function statementFor(f , ids ) {
|
|
335
|
+
const { prefix, name, value } = metricOf(f);
|
|
336
|
+
const line = (part , cite ) => {
|
|
337
|
+
const metric = `${prefix}${part}`;
|
|
338
|
+
return `trace-analyze ${f.detector} ${f.id}: metric ${metric}=${value}; baseline span set finding ${f.id}; `
|
|
339
|
+
+ `evidence ${cite.length ? cite.join(',') : 'unrecorded'}; verify: re-run trace analyze on a later window and compare ${metric}`;
|
|
340
|
+
};
|
|
341
|
+
const masked = (s ) => maskLine(s).text !== s;
|
|
342
|
+
const digest = `h${sha16(name).slice(0, 8)}`;
|
|
343
|
+
let cite = ids;
|
|
344
|
+
if (cite.length > 1 && line(name, cite).length > STATEMENT_MAX) cite = cite.slice(0, 1);
|
|
345
|
+
const part = masked(line(name, cite)) ? digest : name;
|
|
346
|
+
for (let len = part.length; len > 0; len -= 1) {
|
|
347
|
+
const text = line(part.slice(0, len), cite);
|
|
348
|
+
if (text.length <= STATEMENT_MAX && !LONG_RUN.test(text) && !masked(text)) return text;
|
|
349
|
+
}
|
|
350
|
+
return line(digest, cite);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
/**
|
|
354
|
+
* One proposal per finding for `followup --action add --batch <file>`.
|
|
355
|
+
* @param {Object} analysis
|
|
356
|
+
* @param {Object[]|null} ledgerItems the `--task-id` ledger's items, or null without `--task-id`
|
|
357
|
+
*/
|
|
358
|
+
function buildProposalBatch(analysis , ledgerItems ) {
|
|
359
|
+
return findingsOf(analysis)
|
|
360
|
+
.filter((f ) => !f.derivedFrom)
|
|
361
|
+
.map((f ) => ({ statement: statementFor(f, citedIds(f, ledgerItems)), stage: 'maintain', source: { kind: 'manual' } }));
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// ── the verb (IC-001) ───────────────────────────────────────────────────────────────────────────
|
|
365
|
+
|
|
366
|
+
function printAnalysis(analysis ) {
|
|
367
|
+
console.log(`verdict ${analysis.verdict} scopes ${analysis.scopes}`);
|
|
368
|
+
for (const d of analysis.detectors) console.log(`${d.id} ${d.status} findings ${d.findings.length}`);
|
|
369
|
+
for (const f of findingsOf(analysis)) {
|
|
370
|
+
const { prefix, name, value } = metricOf(f);
|
|
371
|
+
const suffix = f.derivedFrom ? ` derivedFrom ${f.derivedFrom}` : '';
|
|
372
|
+
console.log(`${f.detector} ${f.id} ${f.scopeKind} ${prefix}${name}=${value}${suffix}`);
|
|
373
|
+
}
|
|
374
|
+
const c = analysis.context;
|
|
375
|
+
if (c.status !== 'MEASURED') {
|
|
376
|
+
console.log(`context ${c.status} (no hook line carries emit_reason)`);
|
|
377
|
+
return;
|
|
378
|
+
}
|
|
379
|
+
const total = (key ) => sum(c.sessions.map((s ) => s[key]));
|
|
380
|
+
console.log(`context MEASURED sessions ${c.sessions.length} hook-calls ${total('hookCalls')} emissions ${total('emissions')}`
|
|
381
|
+
+ ` emitted-bytes ${total('emittedBytes')} duplicates ${total('duplicates')} duplicate-bytes ${total('duplicateBytes')}`);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
/**
|
|
385
|
+
* `doflow trace analyze [--run <key|id>] [--json] [--emit evidence|followups] [--task-id <id>] [-g] [<project>]`.
|
|
386
|
+
* Exit 0 no finding, 1 findings, 2 usage. Writes nothing.
|
|
387
|
+
* @param {Object} options
|
|
388
|
+
* @param {string[]} [options.positional] the arguments after `analyze`: at most the project root
|
|
389
|
+
* @param {string} [options.stateRoot] where `--task-id`'s evidence ledger is read (the evidence verb's store root)
|
|
390
|
+
* @param {Object} [options.analysis] the `analysis` block, instead of the registry's
|
|
391
|
+
*/
|
|
392
|
+
function handleTraceAnalyze({ positional = [], run, json = false, global = false, emit, taskId, env = process.env, fsImpl = nodeFs, stateRoot, analysis: block } = {}) {
|
|
393
|
+
const asJson = json || (emit !== undefined && emit !== null);
|
|
394
|
+
const usage = (detail ) => {
|
|
395
|
+
if (asJson) console.log(JSON.stringify({ error: 'usage', detail }));
|
|
396
|
+
else console.error(`doflow trace analyze: ${detail}`);
|
|
397
|
+
return finish(2);
|
|
398
|
+
};
|
|
399
|
+
if (positional.length > 1) return usage('usage: trace analyze [--run <runKey|run>] [--json] [--emit evidence|followups] [--task-id <id>] [-g] [<project>]');
|
|
400
|
+
if (run !== undefined && run !== null && (typeof run !== 'string' || !RUN_ARG.test(run))) return usage('--run expects a run key (<hash>/<run>/<lineage>) or a run id');
|
|
401
|
+
if (emit !== undefined && emit !== null && !EMIT.has(emit)) return usage('--emit expects evidence or followups');
|
|
402
|
+
if (taskId !== undefined && taskId !== null && emit !== 'followups') return usage('--task-id needs --emit followups');
|
|
403
|
+
const th = block === undefined ? readThresholds() : checkThresholds(block);
|
|
404
|
+
if (th.error) return usage(th.error);
|
|
405
|
+
const projectRoot = global ? undefined : path.resolve(positional[0] || '.');
|
|
406
|
+
|
|
407
|
+
let ledgerItems = null;
|
|
408
|
+
if (emit === 'followups' && taskId !== undefined && taskId !== null) {
|
|
409
|
+
try {
|
|
410
|
+
const { EvidenceLedger } = require('../evidence-ledger.js');
|
|
411
|
+
const ledger = new EvidenceLedger({ repoRoot: stateRoot || projectRoot || process.cwd() });
|
|
412
|
+
ledger.load(taskId);
|
|
413
|
+
ledgerItems = ledger.queryEvidence({ taskId });
|
|
414
|
+
} catch {
|
|
415
|
+
return usage('cannot read the evidence ledger of --task-id (expected letters, digits, dot, dash or underscore)');
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
const analysis = buildAnalysis(projectSpans({ projectRoot, run: run ?? null, global, env, fsImpl }), th.thresholds);
|
|
420
|
+
if (emit === 'evidence') console.log(JSON.stringify(sorted(buildEvidenceBatch(analysis, { run, global })), null, 2));
|
|
421
|
+
else if (emit === 'followups') console.log(JSON.stringify(sorted(buildProposalBatch(analysis, ledgerItems)), null, 2));
|
|
422
|
+
else if (json) console.log(JSON.stringify(sorted(analysis), null, 2));
|
|
423
|
+
else printAnalysis(analysis);
|
|
424
|
+
return finish(findingsOf(analysis).length ? 1 : 0);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
module.exports = { buildAnalysis, buildEvidenceBatch, buildProposalBatch, printAnalysis, readThresholds, checkThresholds, handleTraceAnalyze };
|