@khoavu882/doflow 2.0.0-beta.4 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +12 -11
- package/bin/doflow.js +21 -11
- package/core/.antigravity-plugin/plugin.json +1 -3
- package/core/.claude-plugin/plugin.json +1 -1
- package/core/.codex-plugin/plugin.json +1 -1
- package/core/.plugin/plugin.json +1 -1
- package/core/harnesses/claude/hooks/lib.sh +43 -24
- package/core/harnesses/claude/hooks/mcp-tool-guard.sh +7 -17
- package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
- package/core/harnesses/claude/hooks/post-compact.sh +35 -32
- package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
- package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
- package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
- package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
- package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
- package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
- package/core/harnesses/claude/hooks/session-end.sh +23 -10
- package/core/harnesses/claude/hooks/session-start.sh +5 -3
- package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
- package/core/harnesses/claude/hooks/stop-check.sh +5 -3
- package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
- package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
- package/core/harnesses/claude/settings/settings.json +18 -0
- package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
- package/core/harnesses/codex/agents/core-implementer.toml +1 -0
- package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
- package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
- package/core/harnesses/codex/agents/research-writer.toml +1 -1
- package/core/harnesses/codex/agents/reviewer.toml +10 -0
- package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
- package/core/harnesses/codex/agents/system-architect.toml +1 -1
- package/core/harnesses/codex/hooks/lib.sh +44 -25
- package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
- package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
- package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
- package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
- package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
- package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
- package/core/harnesses/codex/hooks/session-start.sh +5 -3
- package/core/harnesses/codex/hooks/stop-check.sh +6 -5
- package/core/harnesses/codex/hooks/subagent-audit.sh +18 -6
- package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
- package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
- package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
- package/core/harnesses/copilot/hooks/doflow.json +57 -0
- package/core/harnesses/kiro/agents/doflow.json +1 -1
- package/core/harnesses/kiro/hooks/lib.sh +26 -6
- package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +21 -5
- package/core/harnesses/kiro/hooks/pre-bash-guard.sh +21 -5
- package/core/harnesses/kiro/hooks/pre-implement-gate.sh +21 -5
- package/core/harnesses/kiro/hooks/session-start.sh +12 -5
- package/core/harnesses/kiro/hooks/stop-check.sh +22 -30
- package/core/harnesses/pi/extensions/doflow-guard.js +167 -0
- package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -12
- package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
- package/core/harnesses/shared/hooks/bridge/bridge.sh +277 -0
- package/core/harnesses/shared/hooks/bridge/envelope.jq +129 -0
- package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
- package/core/harnesses/shared/hooks/bridge/respond.sh +70 -0
- package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
- package/core/harnesses/shared/hooks/policies/capture-failure.sh +51 -16
- package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
- package/core/harnesses/shared/hooks/policies/contract.sh +78 -0
- package/core/harnesses/shared/hooks/policies/deny-json.sh +35 -9
- package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
- package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
- package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
- package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
- package/core/harnesses/shared/hooks/policies/lib.sh +65 -40
- package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +48 -35
- package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +284 -33
- package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +180 -110
- package/core/harnesses/shared/hooks/policies/session-context.sh +66 -7
- package/core/harnesses/shared/hooks/policies/stop-check.sh +61 -23
- package/core/harnesses/shared/hooks/policies/subagent-audit.sh +39 -6
- package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
- package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +97 -159
- package/core/harnesses/shared/hooks/stream-hook-runner.js +114 -187
- package/core/harnesses/shared/locator/doflow-run +2 -3
- package/core/registry/advice.json +3 -3
- package/core/registry/assets.json +72 -80
- package/core/registry/capabilities.json +12 -0
- package/core/registry/contracts.json +60 -33
- package/core/registry/contribution-points.json +32 -0
- package/core/registry/gates.json +32 -0
- package/core/registry/guidance-caps.json +40 -0
- package/core/registry/harnesses.json +1542 -277
- package/core/registry/lifecycle.json +2837 -37
- package/core/registry/mcp.json +5 -1
- package/core/registry/models.json +7 -1
- package/core/registry/package-templates/README.md +19 -0
- package/core/registry/package-templates/agent/files/agent.md +9 -0
- package/core/registry/package-templates/agent/template.json +4 -0
- package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
- package/core/registry/package-templates/capability-provider/template.json +11 -0
- package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
- package/core/registry/package-templates/debate-preset/template.json +4 -0
- package/core/registry/package-templates/review-preset/files/preset.json +30 -0
- package/core/registry/package-templates/review-preset/template.json +4 -0
- package/core/registry/package-templates/skill/files/SKILL.md +8 -0
- package/core/registry/package-templates/skill/template.json +4 -0
- package/core/registry/package-templates/worker/files/worker.json +92 -0
- package/core/registry/package-templates/worker/template.json +11 -0
- package/core/registry/packages/doflow/doflow-package.json +15 -0
- package/core/registry/packages/doflow/presets/debate-3.json +26 -0
- package/core/registry/protocols.json +61 -0
- package/core/registry/routes.json +17 -0
- package/core/registry/rules.json +431 -0
- package/core/registry/settings.json +276 -0
- package/core/registry/storage.json +701 -30
- package/core/registry/telemetry.json +236 -0
- package/core/registry/transcripts.json +142 -0
- package/core/registry/verification.json +76 -1
- package/core/registry/workflows.json +55 -0
- package/core/shared/agent-specs/core-implementer.md +3 -0
- package/core/shared/agent-specs/delivery-lead.md +68 -0
- package/core/shared/agent-specs/quality-guardian.md +5 -1
- package/core/shared/agent-specs/research-writer.md +10 -3
- package/core/shared/agent-specs/reviewer.md +53 -0
- package/core/shared/agent-specs/spec-analyst.md +5 -6
- package/core/shared/agent-specs/system-architect.md +5 -1
- package/core/shared/guidance/DOFLOW_CORE.md +1 -5
- package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
- package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
- package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
- package/core/shared/guidance/pointers/codex.md +1 -4
- package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
- package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
- package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
- package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
- package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
- package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
- package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
- package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
- package/core/shared/guidance/rules/RULES.md +45 -0
- package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
- package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
- package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
- package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
- package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
- package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
- package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
- package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
- package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
- package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
- package/core/shared/scripts/doflow/bin/doflow-run +154 -35
- package/core/shared/skills/do/SKILL.md +12 -10
- package/core/shared/skills/do/references/advice_calls.md +26 -0
- package/core/shared/skills/do/references/pm_routing.md +1 -2
- package/core/shared/skills/do/references/program_mode.md +31 -0
- package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
- package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
- package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
- package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
- package/core/shared/skills/do-code-review/SKILL.md +41 -144
- package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
- package/core/shared/skills/do-code-review/references/tools.md +90 -0
- package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
- package/core/shared/skills/do-design/SKILL.md +24 -23
- package/core/shared/skills/do-diagnose/SKILL.md +14 -4
- package/core/shared/skills/do-document/SKILL.md +11 -1
- package/core/shared/skills/do-document/references/deep_research.md +1 -2
- package/core/shared/skills/do-document/references/embedded-research.md +18 -6
- package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
- package/core/shared/skills/do-execute-plan/SKILL.md +17 -59
- package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
- package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
- package/core/shared/skills/do-execute-plan/references/readiness_gate.md +28 -1
- package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
- package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
- package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
- package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
- package/core/shared/skills/do-flow/SKILL.md +8 -9
- package/core/shared/skills/do-flow/references/task_classes.md +2 -2
- package/core/shared/skills/do-git/SKILL.md +4 -0
- package/core/shared/skills/do-implement/SKILL.md +7 -9
- package/core/shared/skills/do-plan/SKILL.md +29 -19
- package/core/shared/skills/do-test/SKILL.md +7 -4
- package/core/shared/templates/doflow/constitution-template.md +9 -3
- package/core/shared/templates/doflow/data-model-template.md +1 -1
- package/core/shared/templates/doflow/design-template.md +113 -16
- package/core/shared/templates/doflow/plan-template.md +11 -10
- package/core/shared/templates/doflow/question-log-template.md +14 -2
- package/core/shared/templates/doflow/requirement-template.md +1 -1
- package/core/worker-evidence/antigravity/checks.json +14 -0
- package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
- package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
- package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
- package/core/worker-evidence/codex/checks.json +38 -0
- package/package.json +10 -6
- package/src/adapters/antigravity/index.js +226 -113
- package/src/adapters/claude/index.js +39 -5
- package/src/adapters/codex/agents.js +25 -15
- package/src/adapters/codex/hooks.js +40 -5
- package/src/adapters/codex/index.js +100 -22
- package/src/adapters/codex/native-projection.js +6 -2
- package/src/adapters/copilot/index.js +58 -23
- package/src/adapters/copy-tree.js +294 -48
- package/src/adapters/guidance-manifest.js +71 -0
- package/src/adapters/guidance-pointer.js +19 -0
- package/src/adapters/hook-commands.js +6 -10
- package/src/adapters/index.js +1 -0
- package/src/adapters/kiro/index.js +38 -13
- package/src/adapters/opencode/index.js +8 -8
- package/src/adapters/pi/hooks.js +120 -0
- package/src/adapters/pi/index.js +48 -16
- package/src/adapters/resolve-source.js +26 -2
- package/src/cli/commands/install.js +3 -2
- package/src/cli/commands/reconcile.js +53 -9
- package/src/cli/commands/remove.js +2 -2
- package/src/cli/commands/rollback.js +6 -3
- package/src/cli/commands/self-update.js +18 -9
- package/src/cli/commands/status.js +21 -2
- package/src/cli/commands/ui.js +99 -0
- package/src/cli/commands/update.js +3 -2
- package/src/cli/index.js +338 -33
- package/src/cli/runtime-commands.js +243 -52
- package/src/cli/shared.js +0 -2
- package/src/helper/git.js +1 -1
- package/src/helper/guardrails.js +1 -1
- package/src/helper/marker-merge.js +31 -13
- package/src/helper/package-ids.js +37 -0
- package/src/helper/semver.js +94 -0
- package/src/install/backup.js +105 -15
- package/src/install/context.js +1 -1
- package/src/install/targets.js +31 -16
- package/src/lifecycle/coverage.js +74 -0
- package/src/lifecycle/hook-step.js +61 -0
- package/src/lifecycle/hook-tier.js +160 -0
- package/src/lifecycle/index.js +434 -86
- package/src/lifecycle/retired-harnesses.js +318 -0
- package/src/lifecycle/view.js +35 -15
- package/src/registry/index.js +686 -32
- package/src/registry/worker-ids.js +45 -0
- package/src/release/build-package.js +0 -3
- package/src/release/docs-blocks.js +492 -0
- package/src/release/preflight.js +425 -0
- package/src/release/rehearse.js +401 -0
- package/src/release/release-workflow.js +733 -0
- package/src/release/sync-plugin-versions.js +43 -7
- package/src/runtime/advice/bench-gate.js +25 -0
- package/src/runtime/advice/cli.js +59 -16
- package/src/runtime/advice/client.js +32 -9
- package/src/runtime/advice/config.js +98 -133
- package/src/runtime/advice/doctor.js +1 -1
- package/src/runtime/advice/elevation.js +97 -0
- package/src/runtime/advice/endpoint-guard.js +9 -3
- package/src/runtime/advice/mode.js +37 -0
- package/src/runtime/advice/record.js +18 -5
- package/src/runtime/advice/registry.js +4 -1
- package/src/runtime/advice/shadow-config.js +229 -0
- package/src/runtime/advice/shadow-dispatcher.js +276 -0
- package/src/runtime/brief-rules.js +81 -0
- package/src/runtime/brief-tools.js +111 -34
- package/src/runtime/candidate.js +514 -0
- package/src/runtime/capabilities/executor.js +450 -0
- package/src/runtime/capabilities/loopback.js +41 -0
- package/src/runtime/capabilities/sanitizer.js +88 -0
- package/src/runtime/capability-router.js +135 -3
- package/src/runtime/checkouts.js +73 -11
- package/src/runtime/claims.js +27 -12
- package/src/runtime/cli-result.js +11 -4
- package/src/runtime/cli.js +221 -277
- package/src/runtime/config-home.js +42 -0
- package/src/runtime/context/arbitrate.js +767 -0
- package/src/runtime/context/budget.js +203 -0
- package/src/runtime/context/compose.js +1367 -0
- package/src/runtime/context/delivery-store.js +100 -0
- package/src/runtime/context/emission-rules.js +118 -0
- package/src/runtime/context/identity.js +227 -0
- package/src/runtime/context/mandatory.js +594 -0
- package/src/runtime/context/pointer.js +142 -0
- package/src/runtime/context/primary-command.js +117 -0
- package/src/runtime/context/primary.js +344 -0
- package/src/runtime/context/summary-store.js +655 -0
- package/src/runtime/context-pack.js +1106 -245
- package/src/runtime/decision-register.js +13 -4
- package/src/runtime/dispatch/admission.js +392 -0
- package/src/runtime/dispatch/attempt-end.js +274 -0
- package/src/runtime/dispatch/command.js +628 -0
- package/src/runtime/dispatch/controls.js +904 -0
- package/src/runtime/dispatch/envelope.js +276 -0
- package/src/runtime/dispatch/grants.js +310 -0
- package/src/runtime/dispatch/integrate.js +617 -0
- package/src/runtime/dispatch/loop.js +1835 -0
- package/src/runtime/dispatch/store.js +352 -0
- package/src/runtime/dispatch/verify-run.js +201 -0
- package/src/runtime/evidence-fields.js +39 -0
- package/src/runtime/evidence-ledger.js +41 -7
- package/src/runtime/failure/capture.js +3 -1
- package/src/runtime/failure/cli.js +25 -9
- package/src/runtime/failure/home.js +57 -30
- package/src/runtime/failure/store.js +7 -2
- package/src/runtime/feature-pointer.js +174 -0
- package/src/runtime/feature-resolve.js +13 -2
- package/src/runtime/fleet/command.js +77 -0
- package/src/runtime/fleet/projects.js +178 -0
- package/src/runtime/fleet/tree.js +383 -0
- package/src/runtime/freshness.js +77 -10
- package/src/runtime/git-memo.js +46 -0
- package/src/runtime/guidance/ablation.js +427 -0
- package/src/runtime/health.js +565 -17
- package/src/runtime/history-compactor.js +13 -3
- package/src/runtime/implementation-gate.js +277 -22
- package/src/runtime/inventory/coverage.js +1 -1
- package/src/runtime/inventory/index.js +1 -1
- package/src/runtime/inventory/precedence.js +2 -2
- package/src/runtime/inventory/siblings.js +6 -4
- package/src/runtime/knowledge/code-corpus.js +277 -0
- package/src/runtime/knowledge/code-lexical.js +130 -0
- package/src/runtime/knowledge/code-retrieval.js +307 -0
- package/src/runtime/knowledge/code-validation.js +106 -0
- package/src/runtime/knowledge/gp3-gate.js +74 -0
- package/src/runtime/knowledge/graph-adapter.js +288 -0
- package/src/runtime/knowledge/graph-fallback.js +394 -0
- package/src/runtime/knowledge/hybrid.js +136 -8
- package/src/runtime/knowledge/retrieval.js +34 -1
- package/src/runtime/knowledge/rtk-compress.js +77 -0
- package/src/runtime/knowledge/semble-bridge.py +106 -0
- package/src/runtime/knowledge/semble-lifecycle.js +114 -0
- package/src/runtime/knowledge/semble-provider.js +365 -0
- package/src/runtime/lifecycle/cli.js +16 -6
- package/src/runtime/lifecycle/event-store.js +8 -6
- package/src/runtime/lifecycle/followup.js +6 -1
- package/src/runtime/lifecycle/overview.js +24 -1
- package/src/runtime/lifecycle/report-store.js +8 -10
- package/src/runtime/lifecycle/retention.js +95 -31
- package/src/runtime/lifecycle/root.js +9 -1
- package/src/runtime/lifecycle/status.js +34 -17
- package/src/runtime/lifecycle/store-upkeep.js +56 -7
- package/src/runtime/mask.js +134 -7
- package/src/runtime/mcp-consultations.js +40 -6
- package/src/runtime/model-router.js +79 -7
- package/src/runtime/models/catalogue.js +191 -0
- package/src/runtime/models/command.js +153 -0
- package/src/runtime/off-words.js +12 -0
- package/src/runtime/outcome.js +3 -3
- package/src/runtime/packages/command.js +1009 -0
- package/src/runtime/packages/contributions.js +116 -0
- package/src/runtime/packages/digest.js +132 -0
- package/src/runtime/packages/fetch.js +182 -0
- package/src/runtime/packages/grants.js +192 -0
- package/src/runtime/packages/index-file.js +134 -0
- package/src/runtime/packages/instruction.js +90 -0
- package/src/runtime/packages/manifest.js +331 -0
- package/src/runtime/packages/points.js +477 -0
- package/src/runtime/packages/programs.js +216 -0
- package/src/runtime/packages/scaffold.js +277 -0
- package/src/runtime/packages/source.js +90 -0
- package/src/runtime/packages/state.js +164 -0
- package/src/runtime/packages/store.js +449 -0
- package/src/runtime/packages/tar.js +106 -0
- package/src/runtime/packages/view.js +406 -0
- package/src/runtime/plan-check.js +59 -0
- package/src/runtime/plan-content.js +324 -0
- package/src/runtime/policy-init.js +118 -0
- package/src/runtime/profiles/doctor.js +96 -0
- package/src/runtime/profiles/resolve.js +244 -0
- package/src/runtime/profiles/rollback.js +254 -0
- package/src/runtime/profiles/sampler.js +41 -0
- package/src/runtime/readiness-record.js +16 -11
- package/src/runtime/readiness.js +127 -16
- package/src/runtime/research-request.js +50 -25
- package/src/runtime/retrieval-plan.js +402 -73
- package/src/runtime/retrieval-record.js +293 -0
- package/src/runtime/review/command.js +246 -0
- package/src/runtime/review/dominance.js +68 -0
- package/src/runtime/review/findings.js +434 -0
- package/src/runtime/review/identity.js +255 -0
- package/src/runtime/review/protocol-command.js +109 -0
- package/src/runtime/review/protocols.js +405 -0
- package/src/runtime/review/resolve-protocol.js +302 -0
- package/src/runtime/review/start.js +530 -0
- package/src/runtime/roadmap/facts.js +479 -0
- package/src/runtime/roadmap/gates.js +270 -0
- package/src/runtime/roadmap/loop.js +1636 -0
- package/src/runtime/roadmap/merge.js +381 -0
- package/src/runtime/roadmap/rows.js +1113 -0
- package/src/runtime/roadmap/seed.js +951 -0
- package/src/runtime/rules-report.js +336 -0
- package/src/runtime/scaffold/artifacts.js +63 -9
- package/src/runtime/scaffold/generate.js +21 -9
- package/src/runtime/settings/autopilot.js +106 -0
- package/src/runtime/settings/cli.js +280 -0
- package/src/runtime/settings/config-write.js +314 -0
- package/src/runtime/settings/files.js +271 -0
- package/src/runtime/settings/legacy.js +154 -0
- package/src/runtime/settings/lexical.js +56 -0
- package/src/runtime/settings/prepared-run.js +269 -0
- package/src/runtime/settings/project-layers.js +118 -0
- package/src/runtime/settings/refs.js +288 -0
- package/src/runtime/settings/registry.js +553 -0
- package/src/runtime/settings/resolve.js +713 -0
- package/src/runtime/settings/shared.js +37 -0
- package/src/runtime/settings/worker-billing.js +107 -0
- package/src/runtime/settings/write.js +269 -0
- package/src/runtime/slot-command.js +78 -0
- package/src/runtime/slots.js +671 -0
- package/src/runtime/source-version.js +290 -0
- package/src/runtime/state-root.js +217 -0
- package/src/runtime/storage/cli.js +12 -5
- package/src/runtime/storage/engine.js +9 -2
- package/src/runtime/storage/registry.js +14 -1
- package/src/runtime/storage/roots.js +66 -16
- package/src/runtime/storage/rules.js +82 -10
- package/src/runtime/storage/upkeep.js +10 -2
- package/src/runtime/storage/work-status.js +245 -26
- package/src/runtime/task-attempts.js +589 -69
- package/src/runtime/task-classifier.js +47 -11
- package/src/runtime/task-graph.js +373 -0
- package/src/runtime/task-run.js +778 -0
- package/src/runtime/task-scope.js +202 -56
- package/src/runtime/task-sources.js +147 -0
- package/src/runtime/task-state.js +440 -65
- package/src/runtime/team/admission.js +301 -0
- package/src/runtime/team/envelope.js +324 -0
- package/src/runtime/team/review-guard.js +156 -0
- package/src/runtime/trace/analyze.js +427 -0
- package/src/runtime/trace/baseline.js +770 -0
- package/src/runtime/trace/ledger.js +63 -140
- package/src/runtime/trace/otlp-export.js +568 -0
- package/src/runtime/trace/otlp-map.js +481 -0
- package/src/runtime/trace/spans.js +745 -0
- package/src/runtime/trace/telemetry.js +225 -0
- package/src/runtime/trace/views.js +1 -0
- package/src/runtime/transcripts/command.js +98 -0
- package/src/runtime/transcripts/cursor.js +326 -0
- package/src/runtime/transcripts/read.js +126 -0
- package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
- package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
- package/src/runtime/transcripts/readers/copilot-events.js +90 -0
- package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
- package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
- package/src/runtime/transcripts/registry.js +161 -0
- package/src/runtime/transcripts/stats.js +161 -0
- package/src/runtime/verification/check-key.js +628 -0
- package/src/runtime/verification/contract-runner.js +546 -11
- package/src/runtime/verification/engine.js +499 -9
- package/src/runtime/verification/impact.js +617 -0
- package/src/runtime/verification/inflight.js +205 -0
- package/src/runtime/verification/registry.js +176 -0
- package/src/runtime/verification/reuse.js +184 -0
- package/src/runtime/verification/telemetry-emit.js +254 -0
- package/src/runtime/verification/test-selection.js +67 -7
- package/src/runtime/worker/active.js +90 -0
- package/src/runtime/worker/cancel.js +222 -0
- package/src/runtime/worker/catalogue.js +122 -0
- package/src/runtime/worker/command.js +677 -0
- package/src/runtime/worker/evidence.js +288 -0
- package/src/runtime/worker/launch.js +288 -0
- package/src/runtime/worker/models.js +137 -0
- package/src/runtime/worker/parsers.js +218 -0
- package/src/runtime/worker/probes/index.js +706 -0
- package/src/runtime/worker/process-tree.js +415 -0
- package/src/runtime/worker/quota.js +43 -0
- package/src/runtime/worker/readiness.js +713 -0
- package/src/runtime/worker/run-store.js +166 -0
- package/src/runtime/worker/supervisor.js +541 -0
- package/src/runtime/worker/worktree-check.js +252 -0
- package/src/runtime/workflow-engine.js +5 -1
- package/src/runtime/workflow-orchestrator.js +578 -63
- package/src/runtime/worktree.js +149 -22
- package/src/state/index.js +1 -1
- package/src/state/lockfile.js +64 -10
- package/src/state/run-lock.js +1 -1
- package/types/node-commonjs.d.ts +106 -7
- package/core/.antigravity-plugin/hooks.json +0 -39
- package/core/harnesses/gemini/hooks/hooks.json +0 -77
- package/core/harnesses/gemini/hooks/lib.sh +0 -302
- package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -21
- package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
- package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -21
- package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
- package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -22
- package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
- package/core/harnesses/gemini/hooks/session-end.sh +0 -5
- package/core/harnesses/gemini/hooks/session-start.sh +0 -5
- package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
- package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
- package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
- package/core/harnesses/shared/hooks/hooks.json +0 -47
- package/core/shared/guidance/FLAGS.md +0 -25
- package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
- package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
- package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
- package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
- package/src/adapters/gemini/hooks.js +0 -210
- package/src/adapters/gemini/index.js +0 -390
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `fleet --action projects` (093 IC-019): the projects this machine knows, from two read-only
|
|
5
|
+
* stores: the user-state `upkeep-roots` index and the `cwd` of each hook project directory
|
|
6
|
+
* (`<config home>/doflow/session-env/projects/<hash>/meta.json`). A candidate is kept when it is an
|
|
7
|
+
* existing directory (not a symlink) whose git work-tree top holds `.doflow/` and is not under a
|
|
8
|
+
* temp folder; candidates that share a git common dir are one project. `root` (the main checkout)
|
|
9
|
+
* is for the Console server only. Nothing is written: no lock, no mkdir, no upkeep.
|
|
10
|
+
* @returns {{ok: false, error: 'cannot-list-projects'} | {ok: true, body: Object}}
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
const fs = require('node:fs');
|
|
14
|
+
const os = require('node:os');
|
|
15
|
+
const path = require('node:path');
|
|
16
|
+
const { spawnSync } = require('node:child_process');
|
|
17
|
+
const { cwdHash, stateDir } = require('../context/identity.js');
|
|
18
|
+
const { listCheckouts } = require('../checkouts.js');
|
|
19
|
+
const { resolveRoots } = require('../storage/roots.js');
|
|
20
|
+
const { loadStorageRegistry } = require('../storage/registry.js');
|
|
21
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
22
|
+
|
|
23
|
+
const HOOK_DIR_LIMIT = 2000;
|
|
24
|
+
const PROBE_LIMIT = 500; // git probes per call: each is one process, and the Console polls this
|
|
25
|
+
const META_MAX_BYTES = 64 * 1024;
|
|
26
|
+
const LABEL = /^[A-Za-z0-9 ._:/@-]{1,64}$/;
|
|
27
|
+
const FIXED_TEMP_ROOTS = ['/private/tmp', '/private/var/folders'];
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
function realOrNull(p , fsImpl ) {
|
|
32
|
+
try { return fsImpl.realpathSync(p); } catch { return null; }
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function under(p , root ) {
|
|
36
|
+
return p === root || p.startsWith(root.endsWith(path.sep) ? root : root + path.sep);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** The label rule of IC-004: at most 64 characters of `[A-Za-z0-9 ._:/@-]`, else `(withheld)`. */
|
|
40
|
+
function label(name ) {
|
|
41
|
+
return LABEL.test(name) ? name : '(withheld)';
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function iso(ms ) {
|
|
45
|
+
return Number.isFinite(ms) ? new Date(ms).toISOString() : null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function lstatOrNull(p , fsImpl ) {
|
|
49
|
+
try { return fsImpl.lstatSync(p); } catch { return null; }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function listProjects({
|
|
53
|
+
env = process.env, cwd = process.cwd(), exec = spawnSync, fsImpl = fs, tempRoots,
|
|
54
|
+
} = {}) {
|
|
55
|
+
const excluded = { missing: 0, temp: 0, notDoflow: 0, unreadable: 0 };
|
|
56
|
+
const body = { v: 1, store: 'absent', projects: [], excluded, truncated: false };
|
|
57
|
+
|
|
58
|
+
const userState = resolveRoots({ registry: loadStorageRegistry({ repoRoot: REPO_ROOT }), projectRoot: null, global: true, env })['user-state'];
|
|
59
|
+
const hooks = stateDir(env);
|
|
60
|
+
if (userState === null && hooks === null) return { ok: true, body };
|
|
61
|
+
body.store = 'present';
|
|
62
|
+
|
|
63
|
+
// Candidates keyed by the path as written; each keeps its sources and the newest last_active seen.
|
|
64
|
+
const candidates = new Map ();
|
|
65
|
+
const add = (dir , source , activeMs = -Infinity) => {
|
|
66
|
+
const c = candidates.get(dir) ?? { sources: new Set (), activeMs: -Infinity };
|
|
67
|
+
c.sources.add(source);
|
|
68
|
+
c.activeMs = Math.max(c.activeMs, activeMs);
|
|
69
|
+
candidates.set(dir, c);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
if (userState !== null) {
|
|
73
|
+
const file = path.join(userState, 'upkeep-roots');
|
|
74
|
+
const info = lstatOrNull(file, fsImpl);
|
|
75
|
+
if (info && info.isFile()) {
|
|
76
|
+
let text = '';
|
|
77
|
+
try { text = fsImpl.readFileSync(file, 'utf8'); } catch { excluded.unreadable += 1; }
|
|
78
|
+
for (const line of text.split('\n')) {
|
|
79
|
+
if (line === '') continue;
|
|
80
|
+
if (path.isAbsolute(line)) add(line, 'upkeep-roots'); else excluded.unreadable += 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (hooks !== null) {
|
|
86
|
+
const base = path.join(hooks, 'projects');
|
|
87
|
+
let dirs = [];
|
|
88
|
+
try {
|
|
89
|
+
for (const dirent of fsImpl.readdirSync(base, { withFileTypes: true })) {
|
|
90
|
+
if (!dirent.isDirectory()) continue;
|
|
91
|
+
const st = lstatOrNull(path.join(base, dirent.name), fsImpl);
|
|
92
|
+
if (st) dirs.push({ name: dirent.name, mtimeMs: st.mtimeMs });
|
|
93
|
+
}
|
|
94
|
+
} catch { /* no hook project directories */ }
|
|
95
|
+
dirs.sort((a, b) => b.mtimeMs - a.mtimeMs || (a.name < b.name ? -1 : 1));
|
|
96
|
+
if (dirs.length > HOOK_DIR_LIMIT) { body.truncated = true; dirs = dirs.slice(0, HOOK_DIR_LIMIT); }
|
|
97
|
+
for (const { name } of dirs) {
|
|
98
|
+
const file = path.join(base, name, 'meta.json');
|
|
99
|
+
const info = lstatOrNull(file, fsImpl);
|
|
100
|
+
if (!info) continue; // a directory with no meta.json is not a record
|
|
101
|
+
let meta ;
|
|
102
|
+
try {
|
|
103
|
+
if (!info.isFile() || info.size > META_MAX_BYTES) throw new Error('unreadable');
|
|
104
|
+
meta = JSON.parse(fsImpl.readFileSync(file, 'utf8'));
|
|
105
|
+
} catch { excluded.unreadable += 1; continue; }
|
|
106
|
+
if (meta === null || typeof meta !== 'object' || typeof meta.cwd !== 'string' || !path.isAbsolute(meta.cwd)) { excluded.unreadable += 1; continue; }
|
|
107
|
+
add(meta.cwd, 'hook-dirs', typeof meta.last_active === 'string' ? Date.parse(meta.last_active) : -Infinity);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const temp = [...(tempRoots ?? [realOrNull(os.tmpdir(), fsImpl) ?? os.tmpdir(), ...FIXED_TEMP_ROOTS])];
|
|
112
|
+
const isTemp = (p ) => temp.some((root) => under(p, root));
|
|
113
|
+
|
|
114
|
+
/** `git rev-parse` for one directory: its work-tree top and common git dir, or itself when outside git. */
|
|
115
|
+
const probe = (dir ) => {
|
|
116
|
+
let res ;
|
|
117
|
+
try {
|
|
118
|
+
res = exec('git', ['-C', dir, 'rev-parse', '--path-format=absolute', '--show-toplevel', '--git-common-dir'], { encoding: 'utf8', timeout: 10000 });
|
|
119
|
+
} catch (error ) { res = { error }; }
|
|
120
|
+
if (res && res.error && res.error.code === 'ENOENT') return 'git-missing';
|
|
121
|
+
const lines = res && !res.error && res.status === 0 ? String(res.stdout || '').split('\n') : [];
|
|
122
|
+
const top = lines[0] ? realOrNull(lines[0], fsImpl) : null;
|
|
123
|
+
const common = lines[1] ? realOrNull(lines[1], fsImpl) : null;
|
|
124
|
+
if (top !== null && common !== null) return { top, common };
|
|
125
|
+
const self = realOrNull(dir, fsImpl) ?? dir;
|
|
126
|
+
return { top: self, common: self };
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
const groups = new Map ();
|
|
131
|
+
let probes = 0;
|
|
132
|
+
for (const [dir, candidate] of candidates) {
|
|
133
|
+
const info = lstatOrNull(dir, fsImpl);
|
|
134
|
+
if (!info || info.isSymbolicLink() || !info.isDirectory()) { excluded.missing += 1; continue; }
|
|
135
|
+
const real = realOrNull(dir, fsImpl);
|
|
136
|
+
if (real === null) { excluded.missing += 1; continue; }
|
|
137
|
+
if (isTemp(real)) { excluded.temp += 1; continue; }
|
|
138
|
+
if (probes >= PROBE_LIMIT) { body.truncated = true; continue; }
|
|
139
|
+
probes += 1;
|
|
140
|
+
const found = probe(dir);
|
|
141
|
+
if (found === 'git-missing') return { ok: false, error: 'cannot-list-projects' };
|
|
142
|
+
const doflow = lstatOrNull(path.join(found.top, '.doflow'), fsImpl);
|
|
143
|
+
if (!doflow || !doflow.isDirectory()) { excluded.notDoflow += 1; continue; }
|
|
144
|
+
if (isTemp(found.top)) { excluded.temp += 1; continue; }
|
|
145
|
+
const due = lstatOrNull(path.join(found.top, '.doflow', 'state', 'upkeep-due'), fsImpl);
|
|
146
|
+
const g = groups.get(found.common) ?? { common: found.common, tops: new Set (), sources: new Set (), lastMs: -Infinity };
|
|
147
|
+
g.tops.add(found.top);
|
|
148
|
+
for (const s of candidate.sources) g.sources.add(s);
|
|
149
|
+
g.lastMs = Math.max(g.lastMs, candidate.activeMs, due && due.isFile() ? due.mtimeMs : -Infinity);
|
|
150
|
+
groups.set(found.common, g);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const here = groups.size > 0 ? probe(cwd) : null;
|
|
154
|
+
if (here === 'git-missing') return { ok: false, error: 'cannot-list-projects' };
|
|
155
|
+
|
|
156
|
+
const projects = [...groups.values()].map((g) => {
|
|
157
|
+
const first = [...g.tops].sort()[0];
|
|
158
|
+
const listed = listCheckouts({ cwd: first, exec, fsImpl });
|
|
159
|
+
const real = (p ) => (typeof p === 'string' ? realOrNull(p, fsImpl) : null);
|
|
160
|
+
const main = (listed.ok && real(listed.main)) || first;
|
|
161
|
+
const count = listed.ok ? listed.entries.filter((e ) => !e.sandbox).length : g.tops.size;
|
|
162
|
+
return {
|
|
163
|
+
projectKey: cwdHash(main),
|
|
164
|
+
root: main,
|
|
165
|
+
label: label(path.basename(main)),
|
|
166
|
+
current: here !== null && g.common === here.common,
|
|
167
|
+
checkouts: Math.max(count, 1),
|
|
168
|
+
sources: ['upkeep-roots', 'hook-dirs'].filter((s) => g.sources.has(s)),
|
|
169
|
+
lastRecordAt: iso(g.lastMs),
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
const time = (p ) => (p.lastRecordAt === null ? -Infinity : Date.parse(p.lastRecordAt));
|
|
173
|
+
projects.sort((a, b) => Number(b.current) - Number(a.current) || time(b) - time(a) || (a.projectKey < b.projectKey ? -1 : 1));
|
|
174
|
+
body.projects = projects;
|
|
175
|
+
return { ok: true, body };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
module.exports = { listProjects };
|
|
@@ -0,0 +1,383 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `fleet --action tree` (093 IC-020): one project's agent hierarchy as nodes and edges, built from
|
|
5
|
+
* records that already exist and written to nowhere. Sources: IC-018 a sessions (hook observations
|
|
6
|
+
* joined to the session directory), the harnesses' own sub-agent records read through V.4's readers
|
|
7
|
+
* (`header`, and the metadata mode for the last record time), task attempts by run/lineage, worker runs
|
|
8
|
+
* (caller, `childOf`, binding, end, a bounded process check) and the dispatch `primary` snapshot.
|
|
9
|
+
* Every edge names the record it comes from; two nodes merge only on a shared key (a runKey, a run id,
|
|
10
|
+
* an agent key), never on a harness or a model. A label is the record's own role text, never invented.
|
|
11
|
+
* A transcript is joined by session id alone (gate-a Q4 A); nothing is written, no path, pid or raw
|
|
12
|
+
* session id is printed. `internal` (not part of the body) carries what V.6 needs per node: the file to open, and
|
|
13
|
+
* `records` (DoFlow's own records of the agent: type, time, task, result) and `holds` (the tasks it is working on).
|
|
14
|
+
* @returns {{ok: false, error: string} | {ok: true, body: Object, internal: Map<string, Object>}}
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const crypto = require('node:crypto');
|
|
18
|
+
const fs = require('node:fs');
|
|
19
|
+
const os = require('node:os');
|
|
20
|
+
const path = require('node:path');
|
|
21
|
+
const { spawnSync } = require('node:child_process');
|
|
22
|
+
const { cwdHash, stateDir } = require('../context/identity.js');
|
|
23
|
+
const { observedSessions } = require('../context/primary.js');
|
|
24
|
+
const { listCheckouts } = require('../checkouts.js');
|
|
25
|
+
const { readTaskState } = require('../task-state.js');
|
|
26
|
+
const { readRuns } = require('../worker/run-store.js');
|
|
27
|
+
const processTree = require('../worker/process-tree.js');
|
|
28
|
+
const { loadTranscriptRegistry } = require('../transcripts/registry.js');
|
|
29
|
+
const { readJsonFile, label } = require('../transcripts/cursor.js');
|
|
30
|
+
const { isSafeSlug } = require('../task-scope.js');
|
|
31
|
+
|
|
32
|
+
const MAX_SESSIONS = 64;
|
|
33
|
+
const MAX_NODES = 256;
|
|
34
|
+
const MAX_CHECKS = 16;
|
|
35
|
+
const SCAN_FILES = 200;
|
|
36
|
+
const LIST_CAP = 2000;
|
|
37
|
+
const SUBAGENT_FILES = 64;
|
|
38
|
+
const NEEDLE_BYTES = 2 * 1024 * 1024;
|
|
39
|
+
const SESSION_DIR = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
40
|
+
const RUN_KEY = /^[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+$/;
|
|
41
|
+
|
|
42
|
+
const sha16 = (text ) => crypto.createHash('sha256').update(text).digest('hex').slice(0, 16);
|
|
43
|
+
const READERS = {
|
|
44
|
+
'claude-jsonl': () => require('../transcripts/readers/claude-jsonl.js'),
|
|
45
|
+
'codex-rollout': () => require('../transcripts/readers/codex-rollout.js'),
|
|
46
|
+
'kiro-jsonl': () => require('../transcripts/readers/kiro-jsonl.js'),
|
|
47
|
+
'copilot-events': () => require('../transcripts/readers/copilot-events.js'),
|
|
48
|
+
'pi-jsonl': () => require('../transcripts/readers/pi-jsonl.js'),
|
|
49
|
+
};
|
|
50
|
+
/** How each harness encodes a cwd into its transcript directory name (research section 3). */
|
|
51
|
+
const ENCODE = {
|
|
52
|
+
claude: (cwd) => cwd.replace(/[^A-Za-z0-9]/g, '-'),
|
|
53
|
+
pi: (cwd) => cwd.replace(/^[\\/]/, '').replace(/[\\/:]/g, '-'),
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function regularFile(file , fsImpl ) {
|
|
57
|
+
try { return fsImpl.lstatSync(file).isFile(); } catch { return false; }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Files under `root` matching a glob whose only wildcard is `*` inside one path segment. */
|
|
61
|
+
function expandGlob(root , pattern , fsImpl ) {
|
|
62
|
+
let found = [root];
|
|
63
|
+
for (const seg of pattern.split('/')) {
|
|
64
|
+
const next = [];
|
|
65
|
+
if (!seg.includes('*')) for (const dir of found) next.push(path.join(dir, seg));
|
|
66
|
+
else {
|
|
67
|
+
const re = new RegExp(`^${seg.split('*').map((part) => part.replace(/[.+?^${}()|[\]\\]/g, '\\$&')).join('[^/]*')}$`);
|
|
68
|
+
for (const dir of found) {
|
|
69
|
+
let names = [];
|
|
70
|
+
try { names = fsImpl.readdirSync(dir); } catch { /* not a directory */ }
|
|
71
|
+
for (const name of names.slice(0, LIST_CAP)) if (re.test(name)) next.push(path.join(dir, name));
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
found = next.slice(0, LIST_CAP);
|
|
75
|
+
}
|
|
76
|
+
return found.filter((file) => regularFile(file, fsImpl)).sort();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The registry locations of `entry` for this platform, as `[root, glob]` pairs with the base resolved. */
|
|
80
|
+
function locations(entry , env , home , platform ) {
|
|
81
|
+
const out = [];
|
|
82
|
+
for (const loc of entry.locations) {
|
|
83
|
+
if (loc.os !== platform || loc.status === 'unknown') continue;
|
|
84
|
+
let base = null;
|
|
85
|
+
if (loc.base === 'home') base = home;
|
|
86
|
+
else if (loc.base === 'xdg-data') base = env.XDG_DATA_HOME || path.join(home, '.local', 'share');
|
|
87
|
+
else if (loc.base.startsWith('env:')) base = env[loc.base.slice(4)] || null;
|
|
88
|
+
if (base) out.push([path.join(base, ...loc.segments), loc.glob]);
|
|
89
|
+
}
|
|
90
|
+
return out;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Does `file` hold `needle` in its first 2 MiB? Only the yes or no leaves this function. */
|
|
94
|
+
function fileHas(file , needle , fsImpl ) {
|
|
95
|
+
try {
|
|
96
|
+
const fd = fsImpl.openSync(file, 'r');
|
|
97
|
+
try {
|
|
98
|
+
const buf = Buffer.alloc(Math.min(NEEDLE_BYTES, fsImpl.fstatSync(fd).size));
|
|
99
|
+
fsImpl.readSync(fd, buf, 0, buf.length, 0);
|
|
100
|
+
return buf.toString('latin1').includes(needle);
|
|
101
|
+
} finally { fsImpl.closeSync(fd); }
|
|
102
|
+
} catch { return false; }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function buildTree({
|
|
106
|
+
env = process.env, cwd = process.cwd(), exec = spawnSync, fsImpl = fs, platform = process.platform, home, alive = processTree.alive, now = Date.now,
|
|
107
|
+
} = {}) {
|
|
108
|
+
const observed = observedSessions({ env, cwd, exec, fsImpl, now });
|
|
109
|
+
if (!observed.ok) return { ok: false, error: observed.error };
|
|
110
|
+
const listed = listCheckouts({ cwd, exec, fsImpl });
|
|
111
|
+
const checkouts = listed.ok ? listed.entries.filter((e ) => !e.sandbox).map((e ) => e.path) : [];
|
|
112
|
+
const projectKey = cwdHash(listed.ok ? listed.main : cwd);
|
|
113
|
+
const homeDir = home ?? env.HOME ?? os.homedir();
|
|
114
|
+
const loaded = loadTranscriptRegistry();
|
|
115
|
+
const registry = {};
|
|
116
|
+
for (const entry of loaded.ok ? loaded.registry.entries : []) registry[entry.harness] = entry;
|
|
117
|
+
|
|
118
|
+
const nodes = new Map ();
|
|
119
|
+
const edges = new Map ();
|
|
120
|
+
const internal = new Map ();
|
|
121
|
+
const counts = { unreadable: 0, notChecked: 0 };
|
|
122
|
+
let checks = 0;
|
|
123
|
+
|
|
124
|
+
/** The node for `agentKey`, created once: a second source with the same key adds what the first lacked. */
|
|
125
|
+
const node = (agentKey , fields , intern ) => {
|
|
126
|
+
let n = nodes.get(agentKey);
|
|
127
|
+
if (!n) {
|
|
128
|
+
if (nodes.size >= MAX_NODES) { counts.notChecked += 1; return null; }
|
|
129
|
+
n = {
|
|
130
|
+
agentKey, kind: null, harness: null, model: null, modelBasis: null, label: null, depth: 0, worktreeKey: projectKey,
|
|
131
|
+
startedAt: null, lastRecordAt: null, liveness: 'not-checked', ended: null, transcript: { reader: null, linked: false, reason: 'no-reader' },
|
|
132
|
+
...fields,
|
|
133
|
+
};
|
|
134
|
+
nodes.set(agentKey, n);
|
|
135
|
+
internal.set(agentKey, { kind: fields.kind, records: [], holds: [] });
|
|
136
|
+
} else {
|
|
137
|
+
for (const [key, value] of Object.entries(fields)) if (n[key] === null) n[key] = value;
|
|
138
|
+
}
|
|
139
|
+
Object.assign(internal.get(agentKey), intern ?? {});
|
|
140
|
+
return n;
|
|
141
|
+
};
|
|
142
|
+
/** One of DoFlow's own records of an agent (`records`) or a task it is working on (`holds`), kept for V.6. */
|
|
143
|
+
const note = (n , list , item ) => { if (n) internal.get(n.agentKey)[list].push(item); };
|
|
144
|
+
const slugOf = (store , name ) => (isSafeSlug(store?.slug) ? store.slug : name.replace(/\.json$/, ''));
|
|
145
|
+
const edge = (from , to , basis ) => {
|
|
146
|
+
if (from !== to) edges.set(`${from}|${to}|${basis}`, { from, to, basis }); // kept only when both ends are nodes, below
|
|
147
|
+
};
|
|
148
|
+
const harnessOf = (id ) => (typeof id !== 'string' ? null : registry[id] ? id : '(other)');
|
|
149
|
+
const transcriptOf = (harness , file ) => {
|
|
150
|
+
const entry = harness ? registry[harness] : null;
|
|
151
|
+
if (!entry) return { reader: null, linked: false, reason: 'no-reader' };
|
|
152
|
+
if (entry.join === 'none') return { reader: null, linked: false, reason: 'not-linked' };
|
|
153
|
+
if (entry.reader === null) return { reader: null, linked: false, reason: 'no-reader' };
|
|
154
|
+
return file ? { reader: entry.reader, linked: true, reason: null } : { reader: entry.reader, linked: false, reason: 'not-found' };
|
|
155
|
+
};
|
|
156
|
+
const lastAt = (harness , file , companion ) => {
|
|
157
|
+
try {
|
|
158
|
+
const out = READERS[registry[harness].reader]().read({ file, companion, mode: 'metadata', maxBytes: 32768, limit: 64 });
|
|
159
|
+
return [...out.events].reverse().find((e ) => e.at)?.at ?? null;
|
|
160
|
+
} catch { return null; }
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
// ---- main sessions (IC-018 a) and the transcript each one's id names ----------------------------------
|
|
164
|
+
const sessions = observed.body.sessions.filter((s ) => s.kind === 'hook');
|
|
165
|
+
counts.notChecked += Math.max(0, sessions.length - MAX_SESSIONS);
|
|
166
|
+
const root = stateDir(env);
|
|
167
|
+
const rawIds = new Map ();
|
|
168
|
+
if (root !== null) {
|
|
169
|
+
try { for (const name of fsImpl.readdirSync(path.join(root, 'sessions')).slice(0, LIST_CAP)) if (SESSION_DIR.test(name)) rawIds.set(sha16(name), name); } catch { /* none */ }
|
|
170
|
+
}
|
|
171
|
+
const scanned = new Map (); // harness -> [{file, companion, header}] of the bounded scan
|
|
172
|
+
const scan = (harness ) => {
|
|
173
|
+
if (scanned.has(harness)) return scanned.get(harness);
|
|
174
|
+
const entry = registry[harness];
|
|
175
|
+
const files = [];
|
|
176
|
+
if (entry && entry.reader) {
|
|
177
|
+
for (const [dir, glob] of locations(entry, env, homeDir, platform)) {
|
|
178
|
+
if (harness === 'pi') for (const c of checkouts) files.push(...expandGlob(dir, glob.replace('{cwdEncoded}', ENCODE.pi(c)), fsImpl));
|
|
179
|
+
else files.push(...expandGlob(dir, glob.replace('{sessionId}', '*').replace('{cwdEncoded}', '*'), fsImpl));
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const newest = harness === 'kiro' || harness === 'pi'
|
|
183
|
+
? files.map((file) => { try { return { file, t: fsImpl.lstatSync(file).mtimeMs }; } catch { return { file, t: 0 }; } }).sort((a, b) => b.t - a.t).map((x) => x.file)
|
|
184
|
+
: [...files].sort().reverse();
|
|
185
|
+
const out = [];
|
|
186
|
+
for (const file of [...new Set(newest)].slice(0, SCAN_FILES)) {
|
|
187
|
+
try { out.push({ file, header: READERS[entry.reader]().header({ file }) }); } catch { counts.unreadable += 1; }
|
|
188
|
+
}
|
|
189
|
+
scanned.set(harness, out);
|
|
190
|
+
return out;
|
|
191
|
+
};
|
|
192
|
+
const locate = (harness , id ) => {
|
|
193
|
+
const entry = registry[harness];
|
|
194
|
+
if (!entry || !entry.reader || entry.join === 'none' || !SESSION_DIR.test(id)) return null;
|
|
195
|
+
if (harness === 'pi') return scan('pi').find((x) => x.header.sessionId === id)?.file ?? null;
|
|
196
|
+
for (const [dir, glob] of locations(entry, env, homeDir, platform)) {
|
|
197
|
+
const hit = expandGlob(dir, glob.replace('{sessionId}', id).replace('{cwdEncoded}', '*'), fsImpl)[0];
|
|
198
|
+
if (hit) return hit;
|
|
199
|
+
}
|
|
200
|
+
return null;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
const mains = [];
|
|
204
|
+
for (const s of sessions.slice(0, MAX_SESSIONS)) {
|
|
205
|
+
const harness = harnessOf(s.harness);
|
|
206
|
+
const rawId = rawIds.get(s.sessionKey) ?? null;
|
|
207
|
+
const file = harness && rawId ? locate(harness, rawId) : null;
|
|
208
|
+
const agentKey = sha16(`${harness ?? ''}:${rawId ?? s.sessionKey}`);
|
|
209
|
+
const companion = file && harness === 'kiro' ? file.replace(/\.jsonl$/, '.json') : undefined;
|
|
210
|
+
const n = node(agentKey, {
|
|
211
|
+
kind: 'main-session', harness, model: label(s.model), modelBasis: s.model ? 'hook-observation' : null, worktreeKey: s.worktreeKey,
|
|
212
|
+
startedAt: s.startedAt, lastRecordAt: (file && harness && lastAt(harness, file, companion)) || s.lastHookRecordAt,
|
|
213
|
+
transcript: transcriptOf(harness, file),
|
|
214
|
+
}, { harness, sessionId: rawId, file, companion });
|
|
215
|
+
if (n) {
|
|
216
|
+
mains.push({ n, harness, rawId, file });
|
|
217
|
+
if (s.startedAt) note(n, 'records', { type: 'hook-record', at: s.startedAt, task: null, result: null });
|
|
218
|
+
if (s.lastHookRecordAt && s.lastHookRecordAt !== s.startedAt) note(n, 'records', { type: 'hook-record', at: s.lastHookRecordAt, task: null, result: null });
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// ---- harness sub-agent records (IC-020 sources), through the readers' header ---------------------------
|
|
223
|
+
const child = (parent , harness , idPart , header , file , companion ) => {
|
|
224
|
+
const agentKey = sha16(`${harness}:${idPart}`);
|
|
225
|
+
const depth = Number.isInteger(header.depth) ? header.depth : parent.depth + 1;
|
|
226
|
+
const n = node(agentKey, {
|
|
227
|
+
kind: 'harness-subagent', harness, model: header.model, modelBasis: header.model ? 'harness-record' : null, label: header.label, depth,
|
|
228
|
+
worktreeKey: parent.worktreeKey, lastRecordAt: file ? lastAt(harness, file, companion) : null, transcript: transcriptOf(harness, file),
|
|
229
|
+
}, { harness, sessionId: header.sessionId, agentId: header.agentId, file, companion });
|
|
230
|
+
if (n) edge(parent.agentKey, agentKey, 'harness-subagent-record');
|
|
231
|
+
};
|
|
232
|
+
for (const { n, harness, rawId, file } of mains) {
|
|
233
|
+
if (!harness || !rawId || !file || !registry[harness]) continue;
|
|
234
|
+
const reader = READERS[registry[harness].reader]();
|
|
235
|
+
try {
|
|
236
|
+
if (harness === 'claude') {
|
|
237
|
+
const dir = path.join(path.dirname(file), path.basename(file, '.jsonl'), 'subagents');
|
|
238
|
+
let names = [];
|
|
239
|
+
try { names = fsImpl.readdirSync(dir).filter((x ) => /^agent-.+\.jsonl$/.test(x)).sort().slice(0, SUBAGENT_FILES); } catch { /* none */ }
|
|
240
|
+
const subs = names.map((name) => {
|
|
241
|
+
const sub = path.join(dir, name);
|
|
242
|
+
const meta = readJsonFile(sub.replace(/\.jsonl$/, '.meta.json')) || {};
|
|
243
|
+
return { sub, header: reader.header({ file: sub }), toolUseId: typeof meta.toolUseId === 'string' ? meta.toolUseId : null };
|
|
244
|
+
});
|
|
245
|
+
const made = new Map ();
|
|
246
|
+
for (const s of subs.sort((a, b) => (a.header.depth ?? 1) - (b.header.depth ?? 1))) {
|
|
247
|
+
const depth = s.header.depth ?? 1;
|
|
248
|
+
// A depth-1 agent was spawned by the main session; deeper ones by the file that holds their tool_use id.
|
|
249
|
+
const candidates = depth <= 1 ? [{ file, node: n }] : made.get(depth - 1) ?? [];
|
|
250
|
+
const parent = depth <= 1 ? candidates[0] : candidates.find((c) => s.toolUseId !== null && fileHas(c.file, s.toolUseId, fsImpl));
|
|
251
|
+
const agentId = s.header.agentId ?? path.basename(s.sub, '.jsonl').replace(/^agent-/, '');
|
|
252
|
+
const agentKey = sha16(`claude:${rawId}:${agentId}`);
|
|
253
|
+
const made1 = node(agentKey, {
|
|
254
|
+
kind: 'harness-subagent', harness, model: s.header.model, modelBasis: s.header.model ? 'harness-record' : null, label: s.header.label, depth,
|
|
255
|
+
worktreeKey: n.worktreeKey, lastRecordAt: lastAt(harness, s.sub), transcript: transcriptOf(harness, s.sub),
|
|
256
|
+
}, { harness, sessionId: rawId, agentId, file: s.sub });
|
|
257
|
+
if (!made1) continue;
|
|
258
|
+
if (parent) edge(parent.node.agentKey, agentKey, 'harness-subagent-record');
|
|
259
|
+
made.set(depth, [...(made.get(depth) ?? []), { file: s.sub, node: made1 }]);
|
|
260
|
+
}
|
|
261
|
+
} else if (harness === 'copilot') {
|
|
262
|
+
for (const c of reader.header({ file }).children) child(n, harness, `${rawId}:${c.agentId}`, { ...c, model: null, sessionId: rawId }, null);
|
|
263
|
+
} else if (harness === 'codex' || harness === 'pi' || harness === 'kiro') {
|
|
264
|
+
// Children name their parent session id in their own header: follow the chain through the bounded scan.
|
|
265
|
+
const parents = new Map ([[rawId, n]]);
|
|
266
|
+
const headers = scan(harness);
|
|
267
|
+
for (let grew = true; grew;) {
|
|
268
|
+
grew = false;
|
|
269
|
+
for (const h of headers) {
|
|
270
|
+
const p = h.header.parentSessionId;
|
|
271
|
+
if (!p || !parents.has(p) || !h.header.sessionId || parents.has(h.header.sessionId)) continue;
|
|
272
|
+
const companion = harness === 'kiro' ? h.file.replace(/\.jsonl$/, '.json') : undefined;
|
|
273
|
+
child(parents.get(p), harness, h.header.sessionId, h.header, h.file, companion);
|
|
274
|
+
const made = nodes.get(sha16(`${harness}:${h.header.sessionId}`));
|
|
275
|
+
if (made) { parents.set(h.header.sessionId, made); grew = true; }
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
} catch { counts.unreadable += 1; }
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// ---- DoFlow records: attempts, worker runs, dispatch --------------------------------------------------
|
|
283
|
+
const readStores = (checkout , dirName , read ) => {
|
|
284
|
+
const dir = path.join(checkout, '.doflow', 'state', dirName);
|
|
285
|
+
let names = [];
|
|
286
|
+
try { names = fsImpl.readdirSync(dir).filter((x ) => x.endsWith('.json')).sort(); } catch { return []; }
|
|
287
|
+
const out = [];
|
|
288
|
+
for (const name of names) {
|
|
289
|
+
try { const data = read(path.join(dir, name)); if (data) out.push([name, data]); } catch { counts.unreadable += 1; }
|
|
290
|
+
}
|
|
291
|
+
return out;
|
|
292
|
+
};
|
|
293
|
+
const runNode = (runKey , extra = {}) => {
|
|
294
|
+
if (!RUN_KEY.test(runKey)) return null;
|
|
295
|
+
return node(sha16(`doflow:${runKey}`), { kind: 'doflow-run', worktreeKey: runKey.split('/')[0], ...extra }, { runKey });
|
|
296
|
+
};
|
|
297
|
+
const attemptRuns = new Map (); // `task#token` -> runKey of the attempt
|
|
298
|
+
for (const checkout of checkouts) {
|
|
299
|
+
for (const [name, store] of readStores(checkout, 'task-attempts', (file) => readTaskState(fsImpl, file))) {
|
|
300
|
+
for (const [taskId, entry] of Object.entries(store.tasks ?? {}) ) {
|
|
301
|
+
for (const a of Array.isArray(entry?.attempts) ? entry.attempts : []) {
|
|
302
|
+
if (typeof a.runKey !== 'string') continue;
|
|
303
|
+
attemptRuns.set(`${taskId}#${a.token}`, a.runKey);
|
|
304
|
+
const at = [a.endedAt, a.finishedAt, a.startedAt].find((v) => typeof v === 'string') ?? null;
|
|
305
|
+
const n = runNode(a.runKey, { startedAt: typeof a.startedAt === 'string' ? a.startedAt : null, lastRecordAt: at });
|
|
306
|
+
if (n) {
|
|
307
|
+
if (typeof a.owner === 'string' && (n.label === null || (at ?? '') >= (n.lastRecordAt ?? ''))) n.label = label(a.owner);
|
|
308
|
+
if (at && (n.lastRecordAt === null || at > n.lastRecordAt)) n.lastRecordAt = at;
|
|
309
|
+
if (typeof a.startedAt === 'string' && (n.startedAt === null || a.startedAt < n.startedAt)) n.startedAt = a.startedAt;
|
|
310
|
+
const task = label(taskId);
|
|
311
|
+
if (typeof a.startedAt === 'string') note(n, 'records', { type: 'attempt-claim', at: a.startedAt, task, result: null });
|
|
312
|
+
const endedAt = [a.finishedAt, a.endedAt].find((v) => typeof v === 'string');
|
|
313
|
+
if (endedAt) note(n, 'records', { type: 'attempt-end', at: endedAt, task, result: typeof a.result === 'string' ? label(a.result) : null });
|
|
314
|
+
if (a.state === 'active') note(n, 'holds', { slug: slugOf(store, name), task: taskId });
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const runs = new Map ();
|
|
322
|
+
for (const checkout of checkouts) {
|
|
323
|
+
for (const [name, byId] of readStores(checkout, 'worker-runs', (file) => readRuns(file, fsImpl))) {
|
|
324
|
+
for (const [runId, run] of Object.entries(byId) ) if (!runs.has(runId)) runs.set(runId, { run, checkout, slug: slugOf(null, name) });
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
const ordered = [...runs.entries()].sort((a, b) => String(b[1].run.startedAt ?? b[1].run.createdAt ?? '').localeCompare(String(a[1].run.startedAt ?? a[1].run.createdAt ?? '')) || (a[0] < b[0] ? -1 : 1));
|
|
328
|
+
const workerKey = (runId ) => sha16(`worker:${runId}`);
|
|
329
|
+
for (const [runId, { run, checkout, slug }] of ordered) {
|
|
330
|
+
const caller = run.caller && typeof run.caller.runKey === 'string' ? runNode(run.caller.runKey) : null;
|
|
331
|
+
const harness = harnessOf(run.harness);
|
|
332
|
+
const isChild = typeof run.childOf === 'string' && run.childOf !== '';
|
|
333
|
+
let liveness = 'not-checked';
|
|
334
|
+
if (run.phase !== 'ended') {
|
|
335
|
+
if (checks < MAX_CHECKS) {
|
|
336
|
+
checks += 1;
|
|
337
|
+
const seen = [alive(run.supervisor, { platform }), alive(run.worker, { platform })];
|
|
338
|
+
liveness = seen.includes(true) ? 'process-seen' : seen.every((v) => v === false) ? 'process-gone' : 'not-checked';
|
|
339
|
+
} else counts.notChecked += 1;
|
|
340
|
+
}
|
|
341
|
+
const n = node(workerKey(runId), {
|
|
342
|
+
kind: isChild ? 'child-worker-run' : 'worker-run', harness, model: label(run.binding?.model), modelBasis: run.binding?.model ? 'worker-binding' : null,
|
|
343
|
+
depth: isChild ? 2 : caller ? 1 : 0, worktreeKey: (run.caller?.runKey ?? '').split('/')[0] || cwdHash(checkout),
|
|
344
|
+
startedAt: run.startedAt ?? run.createdAt ?? null, lastRecordAt: run.end?.at ?? run.startedAt ?? run.createdAt ?? null, liveness,
|
|
345
|
+
ended: run.end && typeof run.end.at === 'string' ? { at: run.end.at, result: typeof run.end.state === 'string' ? run.end.state : null } : null,
|
|
346
|
+
transcript: transcriptOf(harness, null),
|
|
347
|
+
}, { harness, runId });
|
|
348
|
+
if (!n) continue;
|
|
349
|
+
const task = label(run.task);
|
|
350
|
+
const startedAt = run.startedAt ?? run.createdAt;
|
|
351
|
+
if (typeof startedAt === 'string') note(n, 'records', { type: 'worker-run-start', at: startedAt, task, result: null });
|
|
352
|
+
if (run.end && typeof run.end.at === 'string') note(n, 'records', { type: 'worker-run-end', at: run.end.at, task, result: typeof run.end.state === 'string' ? label(run.end.state) : null });
|
|
353
|
+
if (run.phase !== 'ended' && typeof run.task === 'string') note(n, 'holds', { slug, task: run.task });
|
|
354
|
+
if (isChild) edge(workerKey(run.childOf), n.agentKey, 'worker-childOf');
|
|
355
|
+
else if (caller) edge(caller.agentKey, n.agentKey, 'worker-caller');
|
|
356
|
+
else {
|
|
357
|
+
const viaAttempt = attemptRuns.get(`${run.task}#${run.token}`);
|
|
358
|
+
const owner = viaAttempt ? runNode(viaAttempt) : null;
|
|
359
|
+
if (owner) edge(owner.agentKey, n.agentKey, 'attempt-runKey');
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
for (const checkout of checkouts) {
|
|
363
|
+
for (const [, store] of readStores(checkout, 'dispatch', (file) => readTaskState(fsImpl, file))) {
|
|
364
|
+
for (const d of Object.values(store.dispatches ?? {}) ) {
|
|
365
|
+
if (typeof d?.parentRunKey !== 'string') continue;
|
|
366
|
+
const snap = d.primary && typeof d.primary === 'object' ? d.primary : null;
|
|
367
|
+
const owner = runNode(d.parentRunKey, snap ? { harness: harnessOf(snap.harness), model: label(typeof snap.model === 'string' ? snap.model : null), modelBasis: snap.model ? 'dispatch-snapshot' : null } : {});
|
|
368
|
+
const launched = [...runs.entries()].find(([, r]) => r.run.task === d.task && r.run.token === d.token && !r.run.childOf);
|
|
369
|
+
if (owner && launched) edge(owner.agentKey, workerKey(launched[0]), 'dispatch-claim');
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
// ---- what leaves: no internal field, kept nodes' edges only ---------------------------------------------
|
|
375
|
+
const keep = [...nodes.values()];
|
|
376
|
+
const body = {
|
|
377
|
+
v: 1, projectKey, nodes: keep, edges: [...edges.values()].filter((e ) => nodes.has(e.from) && nodes.has(e.to)),
|
|
378
|
+
limits: { nodes: MAX_NODES, notChecked: counts.notChecked }, unreadable: counts.unreadable,
|
|
379
|
+
};
|
|
380
|
+
return { ok: true, body, internal };
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
module.exports = { buildTree, READERS, MAX_SESSIONS, MAX_NODES, MAX_CHECKS };
|