@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
|
@@ -3,9 +3,8 @@
|
|
|
3
3
|
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
4
|
|
|
5
5
|
const fs = require('node:fs');
|
|
6
|
-
const path = require('node:path');
|
|
7
6
|
const { boundedAcquire, updateTaskState } = require('../task-state.js');
|
|
8
|
-
const {
|
|
7
|
+
const { taskRecord } = require('../task-scope.js');
|
|
9
8
|
|
|
10
9
|
const MAX_DELIVERIES = 5;
|
|
11
10
|
const ACTUAL_UNMEASURED = 'no harness reports token usage to an external CLI; profile tokenTelemetry unverified';
|
|
@@ -64,8 +63,7 @@ function createDeliveryEntry({ pack, form, harness = null, sources = {}, expansi
|
|
|
64
63
|
}
|
|
65
64
|
|
|
66
65
|
function readDeliveryEntry({ projectRoot, taskId, slug = null, packDigest, fsImpl = fs } ) {
|
|
67
|
-
const
|
|
68
|
-
const file = path.join(dir, `${taskId}.json`);
|
|
66
|
+
const file = taskRecord({ projectRoot, store: 'delivery', taskId, slug, fsImpl }).read;
|
|
69
67
|
try {
|
|
70
68
|
const disk = JSON.parse(fsImpl.readFileSync(file, 'utf8'));
|
|
71
69
|
return (Array.isArray(disk.deliveries) ? disk.deliveries : []).find((entry ) => entry.packDigest === packDigest) || null;
|
|
@@ -80,11 +78,11 @@ function recordDelivery({ projectRoot, taskId, slug = null, entry, fsImpl = fs,
|
|
|
80
78
|
if (typeof taskId !== 'string' || !/^[A-Za-z0-9][A-Za-z0-9._-]*$/.test(taskId) || taskId.includes('..')) {
|
|
81
79
|
throw new Error(`Invalid task id '${taskId}'`);
|
|
82
80
|
}
|
|
83
|
-
const
|
|
84
|
-
const file = path.join(dir, `${taskId}.json`);
|
|
81
|
+
const { file, seed } = taskRecord({ projectRoot, store: 'delivery', taskId, slug, fsImpl });
|
|
85
82
|
const written = updateTaskState({
|
|
86
83
|
fsImpl,
|
|
87
84
|
file,
|
|
85
|
+
prepare: seed,
|
|
88
86
|
acquire: (impl , target ) => acquire(impl, target, { retries: 3, waitMs: 5 }),
|
|
89
87
|
build(disk ) {
|
|
90
88
|
const deliveries = Array.isArray(disk?.deliveries) ? disk.deliveries : [];
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Rule compiler for the hook emission table (078 IC-003, DEC-005). Run directly by Node native
|
|
4
|
+
// type-stripping:
|
|
5
|
+
//
|
|
6
|
+
// node src/runtime/context/emission-rules.js --write rewrite the committed table
|
|
7
|
+
// node src/runtime/context/emission-rules.js --check exit 0 equal, 1 differs, 2 usage
|
|
8
|
+
//
|
|
9
|
+
// The table is compiled from EMISSION_RULES, never written by hand: each rule's finite domain is
|
|
10
|
+
// enumerated through INPUT_DOMAINS and ruleAction(), so every arm the hook reads is the action
|
|
11
|
+
// arbitrate() gives that vector. Every combination is written as its own literal arm; a pattern
|
|
12
|
+
// holds no glob, so the hook's lookup matches exactly what IC-002 lists and nothing else.
|
|
13
|
+
|
|
14
|
+
const crypto = require('node:crypto');
|
|
15
|
+
const fs = require('node:fs');
|
|
16
|
+
const path = require('node:path');
|
|
17
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
18
|
+
const { EMISSION_RULES, INPUT_DOMAINS, ruleAction } = require('./arbitrate.js');
|
|
19
|
+
|
|
20
|
+
const TABLE_REL = 'core/harnesses/shared/hooks/policies/emission-rules.sh';
|
|
21
|
+
const RULES_VERSION = 1;
|
|
22
|
+
const MAX_BYTES_DEFAULT = 16384;
|
|
23
|
+
const SUMMARY_CAP = 4000;
|
|
24
|
+
const SUMMARY_READ = 65536;
|
|
25
|
+
|
|
26
|
+
/** Every value combination of the named inputs, in vector order. */
|
|
27
|
+
function combinations(inputs ) {
|
|
28
|
+
let out = [[]];
|
|
29
|
+
for (const name of inputs) {
|
|
30
|
+
const domain = INPUT_DOMAINS[name];
|
|
31
|
+
out = out.flatMap((prefix) => domain.map((value) => [...prefix, value]));
|
|
32
|
+
}
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** JSON with object keys sorted at every depth, so the digest does not depend on key order. */
|
|
37
|
+
function canonicalJson(value ) {
|
|
38
|
+
if (Array.isArray(value)) return `[${value.map(canonicalJson).join(',')}]`;
|
|
39
|
+
if (value && typeof value === 'object') {
|
|
40
|
+
const entries = Object.keys(value ).sort()
|
|
41
|
+
.map((key) => `${JSON.stringify(key)}:${canonicalJson((value )[key])}`);
|
|
42
|
+
return `{${entries.join(',')}}`;
|
|
43
|
+
}
|
|
44
|
+
return JSON.stringify(value);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function rulesDigest(rules ) {
|
|
48
|
+
return crypto.createHash('sha256').update(canonicalJson(rules), 'utf8').digest('hex');
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function compare(a , b ) {
|
|
52
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
/** Every non-skip combination of every rule, sorted by event, segment, vector. */
|
|
58
|
+
function compileArms(rules = EMISSION_RULES) {
|
|
59
|
+
const arms = [];
|
|
60
|
+
for (const r of rules) {
|
|
61
|
+
for (const values of combinations(r.inputs)) {
|
|
62
|
+
const action = ruleAction(r, values);
|
|
63
|
+
if (action !== 'skip') arms.push({ event: r.event, segment: r.segment, vector: values.join(':'), action });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
return arms.sort((a, b) => compare(a.event, b.event) || compare(a.segment, b.segment) || compare(a.vector, b.vector));
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** A value safe inside a single-quoted bash pattern, with no glob character. */
|
|
70
|
+
function literal(text ) {
|
|
71
|
+
if (!/^[A-Za-z0-9._:|-]+$/.test(text)) throw new TypeError(`emission-rules: ${JSON.stringify(text)} is not a plain token`);
|
|
72
|
+
return text;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** IC-003: the full text of `emission-rules.sh`. */
|
|
76
|
+
function compileEmissionTable(rules = EMISSION_RULES) {
|
|
77
|
+
const lines = [
|
|
78
|
+
'# Generated by node src/runtime/context/emission-rules.js --write. Do not edit.',
|
|
79
|
+
`# rules-digest: sha256:${rulesDigest(rules)}`,
|
|
80
|
+
`DF_RULES_VERSION=${RULES_VERSION}`,
|
|
81
|
+
`DF_RULES_MAX_BYTES_DEFAULT=${MAX_BYTES_DEFAULT}`,
|
|
82
|
+
`DF_RULES_SUMMARY_CAP=${SUMMARY_CAP}`,
|
|
83
|
+
`DF_RULES_SUMMARY_READ=${SUMMARY_READ}`,
|
|
84
|
+
'doflow_emission_rule() {',
|
|
85
|
+
" # $1 = event.segment, $2 = vector: input values joined with ':' in IC-002 order",
|
|
86
|
+
' case "$1|$2" in',
|
|
87
|
+
];
|
|
88
|
+
for (const arm of compileArms(rules)) {
|
|
89
|
+
lines.push(` '${literal(`${arm.event}.${arm.segment}|${arm.vector}`)}') DF_ACTION=${literal(arm.action)} ;;`);
|
|
90
|
+
}
|
|
91
|
+
lines.push(' *) DF_ACTION=skip ;;', ' esac', '}');
|
|
92
|
+
return `${lines.join('\n')}\n`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const USAGE = 'usage: node src/runtime/context/emission-rules.js --write|--check\n';
|
|
96
|
+
|
|
97
|
+
/** The CLI: 0 written or equal, 1 the committed table differs from the rules, 2 usage. */
|
|
98
|
+
function main(argv ) {
|
|
99
|
+
if (argv.length !== 1 || (argv[0] !== '--write' && argv[0] !== '--check')) {
|
|
100
|
+
process.stderr.write(USAGE);
|
|
101
|
+
return 2;
|
|
102
|
+
}
|
|
103
|
+
const target = path.join(REPO_ROOT, TABLE_REL);
|
|
104
|
+
const expected = compileEmissionTable();
|
|
105
|
+
if (argv[0] === '--write') {
|
|
106
|
+
fs.writeFileSync(target, expected);
|
|
107
|
+
return 0;
|
|
108
|
+
}
|
|
109
|
+
let actual ;
|
|
110
|
+
try { actual = fs.readFileSync(target, 'utf8'); } catch { actual = null; }
|
|
111
|
+
if (actual === expected) return 0;
|
|
112
|
+
process.stderr.write(`emission-rules: ${TABLE_REL} is ${actual === null ? 'missing' : 'stale'}; run node src/runtime/context/emission-rules.js --write\n`);
|
|
113
|
+
return 1;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
module.exports = { TABLE_REL, canonicalJson, compileArms, compileEmissionTable, rulesDigest };
|
|
117
|
+
|
|
118
|
+
if (require.main === module) process.exitCode = main(process.argv.slice(2));
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// Session identity reader (078 IC-007). Run directly by Node native type-stripping.
|
|
4
|
+
//
|
|
5
|
+
// The TypeScript twin of `policies/identity.sh`: both read the launcher-set environment the same
|
|
6
|
+
// way, and test/fixtures/identity/cases.json is the corpus both must agree on (G38.6). Identity
|
|
7
|
+
// comes only from DOFLOW_RUN_ID, DOFLOW_ATTEMPT, DOFLOW_LINEAGE_ID, DOFLOW_SESSION_ROLE and
|
|
8
|
+
// DOFLOW_WORKTREE plus the payload's session_id and cwd; it is never inferred from git or from the
|
|
9
|
+
// harness's own idea of a main session.
|
|
10
|
+
|
|
11
|
+
const crypto = require('node:crypto');
|
|
12
|
+
const fs = require('node:fs');
|
|
13
|
+
const path = require('node:path');
|
|
14
|
+
const { configHome } = require('../config-home.js');
|
|
15
|
+
|
|
16
|
+
/** DEC-024: a launched unit id `L.<run>.<lineage>.<attempt>` stays inside the 128-character session-dirs match. */
|
|
17
|
+
const ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,55}$/;
|
|
18
|
+
const ATTEMPT_PATTERN = /^[1-9][0-9]{0,3}$/;
|
|
19
|
+
const DEFAULT_RUN = 'local';
|
|
20
|
+
const UNKNOWN_ROLE_LINE = '[doflow] unknown DOFLOW_SESSION_ROLE; treating session as worker\n';
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
let roleWarned = false;
|
|
47
|
+
|
|
48
|
+
/** `<config home>/doflow/session-env`, the root `policies/lib.sh` names STATE_DIR; null with no config home (IC-010), and the callers then skip session state. */
|
|
49
|
+
function stateDir(env = process.env) {
|
|
50
|
+
const { dir } = configHome(env);
|
|
51
|
+
return dir === null ? null : path.join(dir, 'session-env');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** `(cd "$p" && pwd -P)`: the physical path of an existing directory, or null. */
|
|
55
|
+
function physicalDir(p ) {
|
|
56
|
+
try {
|
|
57
|
+
if (!fs.statSync(p).isDirectory()) return null;
|
|
58
|
+
return fs.realpathSync.native(p);
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** `canonicalize_path` from policies/lib.sh: resolve the parent chain of an existing path, never its leaf. */
|
|
65
|
+
function canonicalizePath(p ) {
|
|
66
|
+
try {
|
|
67
|
+
fs.statSync(p); // `[[ -e ]]` follows symlinks, so a dangling link is "missing"
|
|
68
|
+
const dir = fs.realpathSync.native(path.dirname(p));
|
|
69
|
+
const base = path.basename(p);
|
|
70
|
+
return dir === '/' ? `/${base}` : `${dir}/${base}`;
|
|
71
|
+
} catch {
|
|
72
|
+
return p;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** `cwd_hash` from policies/lib.sh: first 16 hex chars of sha256 over the canonical path plus `echo`'s newline. */
|
|
77
|
+
function cwdHash(p ) {
|
|
78
|
+
let canonical = physicalDir(p) ?? canonicalizePath(p);
|
|
79
|
+
// bash hashes `pwd -P`, which Git Bash prints as /c/Users/x; hash the same spelling for a native C:\\Users\\x.
|
|
80
|
+
if (process.platform === 'win32') {
|
|
81
|
+
const drive = /^([A-Za-z]):[\\/]?(.*)$/.exec(canonical);
|
|
82
|
+
if (drive) canonical = `/${drive[1].toLowerCase()}${drive[2] ? `/${drive[2].replace(/\\/g, '/')}` : ''}`;
|
|
83
|
+
}
|
|
84
|
+
return crypto.createHash('sha256').update(`${canonical}\n`).digest('hex').slice(0, 16);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** `tr '[:upper:]' '[:lower:]'` on the role, ASCII only, then today's mapping: unset is main, unknown is worker. */
|
|
88
|
+
function readRole(raw ) {
|
|
89
|
+
const role = (raw || '').replace(/[A-Z]/g, (c) => c.toLowerCase());
|
|
90
|
+
if (role === '' || role === 'main') return { role: 'main', unknown: false };
|
|
91
|
+
if (role === 'worker') return { role: 'worker', unknown: false };
|
|
92
|
+
return { role: 'worker', unknown: true };
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** DOFLOW_WORKTREE canonicalised when it names an absolute existing directory; else the payload cwd as given (cwdHash canonicalises for the store key). */
|
|
96
|
+
function readWorktree(raw , cwd ) {
|
|
97
|
+
if (raw && raw.startsWith('/')) {
|
|
98
|
+
const dir = physicalDir(raw);
|
|
99
|
+
if (dir !== null) return dir;
|
|
100
|
+
}
|
|
101
|
+
return cwd;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Create `sessions/<unitId>/bound` exclusively, holding the session id. True when this session owns
|
|
106
|
+
* the unit (just bound, or bound earlier by the same session); false when another session bound it,
|
|
107
|
+
* and false when it cannot be decided (the store is unwritable, `bound` is a symlink, which is never
|
|
108
|
+
* followed, or `bound` is still empty after one retry). Never throws: the caller treats false as an
|
|
109
|
+
* inherited session (L1-04).
|
|
110
|
+
*/
|
|
111
|
+
function bindUnit(env , unitId , sessionId ) {
|
|
112
|
+
const root = stateDir(env);
|
|
113
|
+
if (root === null) return false;
|
|
114
|
+
const dir = path.join(root, 'sessions', unitId);
|
|
115
|
+
const file = path.join(dir, 'bound');
|
|
116
|
+
try {
|
|
117
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
118
|
+
const fd = fs.openSync(file, 'wx');
|
|
119
|
+
try {
|
|
120
|
+
fs.writeSync(fd, `${sessionId}\n`);
|
|
121
|
+
} finally {
|
|
122
|
+
fs.closeSync(fd);
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
} catch (error ) {
|
|
126
|
+
if (error?.code !== 'EEXIST') return false;
|
|
127
|
+
}
|
|
128
|
+
// `IFS= read -r bound` of a regular file, not a symlink: its first line. An empty first line may
|
|
129
|
+
// be a concurrent binder between its create and its write, so it is read once more.
|
|
130
|
+
for (let tries = 0; tries < 2; tries++) {
|
|
131
|
+
let bound ;
|
|
132
|
+
try {
|
|
133
|
+
if (!fs.lstatSync(file).isFile()) return false;
|
|
134
|
+
bound = fs.readFileSync(file, 'utf8').split('\n')[0];
|
|
135
|
+
} catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
if (bound !== '') return bound === sessionId;
|
|
139
|
+
}
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/** IC-007: the session identity from the environment and the payload's session id and cwd. */
|
|
144
|
+
function resolveIdentity(env , options ) {
|
|
145
|
+
const { cwd, sessionId, bind = false } = options;
|
|
146
|
+
const diagnostics = new Set ();
|
|
147
|
+
|
|
148
|
+
const { role, unknown } = readRole(env.DOFLOW_SESSION_ROLE);
|
|
149
|
+
if (unknown) {
|
|
150
|
+
diagnostics.add('role-unknown');
|
|
151
|
+
if (!roleWarned) {
|
|
152
|
+
roleWarned = true;
|
|
153
|
+
(options.warn ?? ((line ) => process.stderr.write(line)))(UNKNOWN_ROLE_LINE);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const rawRun = env.DOFLOW_RUN_ID || '';
|
|
158
|
+
const runValid = rawRun !== '' && rawRun !== DEFAULT_RUN && ID_PATTERN.test(rawRun);
|
|
159
|
+
if (rawRun !== '' && !runValid) diagnostics.add('identity-invalid');
|
|
160
|
+
const run = runValid ? rawRun : DEFAULT_RUN;
|
|
161
|
+
|
|
162
|
+
const rawAttempt = env.DOFLOW_ATTEMPT || '';
|
|
163
|
+
const attempt = ATTEMPT_PATTERN.test(rawAttempt) ? rawAttempt : '';
|
|
164
|
+
|
|
165
|
+
const rawLineage = env.DOFLOW_LINEAGE_ID || '';
|
|
166
|
+
const lineageValid = rawLineage !== '' && ID_PATTERN.test(rawLineage);
|
|
167
|
+
if (rawLineage !== '' && !lineageValid) diagnostics.add('identity-invalid');
|
|
168
|
+
let lineage ;
|
|
169
|
+
if (rawRun !== '' && !runValid) lineage = 'solo';
|
|
170
|
+
else if (rawLineage === '') lineage = role === 'main' ? 'main' : 'solo';
|
|
171
|
+
else lineage = lineageValid ? rawLineage : 'solo';
|
|
172
|
+
|
|
173
|
+
let lineageKind = 'solo';
|
|
174
|
+
if (runValid && lineageValid && attempt !== '') lineageKind = 'launched';
|
|
175
|
+
else if (run === DEFAULT_RUN && lineage === 'main') lineageKind = 'default-main';
|
|
176
|
+
|
|
177
|
+
let unitId = lineageKind === 'launched' ? `L.${run}.${lineage}.${attempt}` : sessionId;
|
|
178
|
+
if (lineageKind === 'launched' && bind) {
|
|
179
|
+
// An empty session id cannot bind: solo with an empty unit and no capture, as identity.sh.
|
|
180
|
+
// Any other failure to bind is an inherited session (DEC-044).
|
|
181
|
+
const mine = sessionId !== '' && bindUnit(env, unitId, sessionId);
|
|
182
|
+
if (!mine) {
|
|
183
|
+
if (sessionId !== '') diagnostics.add('identity-inherited');
|
|
184
|
+
lineageKind = 'solo';
|
|
185
|
+
lineage = 'solo';
|
|
186
|
+
unitId = sessionId;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
return {
|
|
191
|
+
run,
|
|
192
|
+
attempt,
|
|
193
|
+
lineage,
|
|
194
|
+
role,
|
|
195
|
+
worktree: readWorktree(env.DOFLOW_WORKTREE, cwd),
|
|
196
|
+
lineageKind,
|
|
197
|
+
unitId,
|
|
198
|
+
session: sessionId,
|
|
199
|
+
diagnostics: [...diagnostics].sort(),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* The run ledger's identity fields (082 IC-034, DEC-042). Each value is tested on its own and
|
|
205
|
+
* omitted, never written, when absent or invalid: `run` by `resolveIdentity`'s `runValid` rule
|
|
206
|
+
* (non-empty, not `local`, `ID_PATTERN`), `lineage` by `ID_PATTERN`, `attempt` by `ATTEMPT_PATTERN`.
|
|
207
|
+
*/
|
|
208
|
+
function identityFields(env ) {
|
|
209
|
+
const out = {};
|
|
210
|
+
const run = env.DOFLOW_RUN_ID || '';
|
|
211
|
+
if (run !== '' && run !== DEFAULT_RUN && ID_PATTERN.test(run)) out.run = run;
|
|
212
|
+
const lineage = env.DOFLOW_LINEAGE_ID || '';
|
|
213
|
+
if (ID_PATTERN.test(lineage)) out.lineage = lineage;
|
|
214
|
+
const attempt = env.DOFLOW_ATTEMPT || '';
|
|
215
|
+
if (ATTEMPT_PATTERN.test(attempt)) out.attempt = attempt;
|
|
216
|
+
return out;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
module.exports = {
|
|
220
|
+
ATTEMPT_PATTERN,
|
|
221
|
+
ID_PATTERN,
|
|
222
|
+
UNKNOWN_ROLE_LINE,
|
|
223
|
+
cwdHash,
|
|
224
|
+
identityFields,
|
|
225
|
+
resolveIdentity,
|
|
226
|
+
stateDir,
|
|
227
|
+
};
|
|
@@ -4,6 +4,22 @@
|
|
|
4
4
|
const fs = require('node:fs');
|
|
5
5
|
const path = require('node:path');
|
|
6
6
|
const crypto = require('node:crypto');
|
|
7
|
+
const { loadTaxonomy, isPlainText, isUserContextPath } = require('../intake/taxonomy.js');
|
|
8
|
+
|
|
9
|
+
/** The user's own files (feature 136's intake): a feature folder's `additional-context/`. Markdown there up to
|
|
10
|
+
* `pointerMaxBytes` of core/registry/intake.json becomes pointers; nothing else does. */
|
|
11
|
+
const USER_CONTEXT_DIR = 'additional-context';
|
|
12
|
+
|
|
13
|
+
/** The registry's byte cap, or null when the taxonomy cannot be read (intake is then off: no user pointers). */
|
|
14
|
+
function userPointerMaxBytes(file ) {
|
|
15
|
+
const loaded = loadTaxonomy({ file, contributions: false });
|
|
16
|
+
return loaded.ok ? loaded.taxonomy.pointerMaxBytes : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** `'user-provided'` for a project-relative path inside `agent-docs/doflow/<slug>/additional-context/`, else null. */
|
|
20
|
+
function originOf(rel ) {
|
|
21
|
+
return isUserContextPath(rel) ? 'user-provided' : null;
|
|
22
|
+
}
|
|
7
23
|
|
|
8
24
|
function sha256(text ) {
|
|
9
25
|
return `sha256:${crypto.createHash('sha256').update(text, 'utf8').digest('hex')}`;
|
|
@@ -67,7 +83,7 @@ function rangesFor(file , text , requirement )
|
|
|
67
83
|
return result;
|
|
68
84
|
}
|
|
69
85
|
|
|
70
|
-
function createPointer({ path: rel, heading, range, slice } ) {
|
|
86
|
+
function createPointer({ path: rel, heading, range, slice, user = false } ) {
|
|
71
87
|
const bytes = Buffer.byteLength(slice, 'utf8');
|
|
72
88
|
return {
|
|
73
89
|
id: `ptr:${rel}:${range.startLine}-${range.endLine}`,
|
|
@@ -79,20 +95,42 @@ function createPointer({ path: rel, heading, range, slice } ) {
|
|
|
79
95
|
tokensEst: { value: Math.ceil(bytes / 4), basis: 'estimated:bytes/4' },
|
|
80
96
|
inlineBytes: Buffer.byteLength(`- ptr:${rel}:${range.startLine}-${range.endLine} ${rel}:${range.startLine}-${range.endLine} ${bytes} B\n`, 'utf8'),
|
|
81
97
|
load: `read ${rel} lines ${range.startLine}-${range.endLine}`,
|
|
98
|
+
origin: user ? 'user-provided' : originOf(rel),
|
|
82
99
|
};
|
|
83
100
|
}
|
|
84
101
|
|
|
85
|
-
|
|
102
|
+
/** Visible, regular `.md` files under a context folder, `.intake/` and every dotfile or symlink left out,
|
|
103
|
+
* in byte order of path. A folder that is absent, or itself a symlink, gives none. */
|
|
104
|
+
function userContextFiles(dir , fsImpl = fs) {
|
|
105
|
+
const found = [];
|
|
106
|
+
const walk = (current ) => {
|
|
107
|
+
let entries ;
|
|
108
|
+
try { entries = fsImpl.readdirSync(current, { withFileTypes: true }); } catch { return; }
|
|
109
|
+
for (const entry of entries) {
|
|
110
|
+
if (entry.name.startsWith('.') || entry.isSymbolicLink()) continue;
|
|
111
|
+
const full = path.join(current, entry.name);
|
|
112
|
+
if (entry.isDirectory()) walk(full);
|
|
113
|
+
else if (entry.isFile() && entry.name.toLowerCase().endsWith('.md')) found.push(full);
|
|
114
|
+
}
|
|
115
|
+
};
|
|
116
|
+
try { if (fsImpl.lstatSync(dir).isSymbolicLink()) return []; } catch { return []; }
|
|
117
|
+
walk(dir);
|
|
118
|
+
return found.sort((a, b) => (a < b ? -1 : a > b ? 1 : 0));
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function sourceFiles({ projectRoot, feature, fsImpl = fs, intakeFile } ) {
|
|
86
122
|
const root = path.resolve(projectRoot);
|
|
87
123
|
const featureDir = feature?.featureDir ? path.resolve(feature.featureDir) : null;
|
|
88
|
-
const candidates
|
|
89
|
-
const add = (file , requirement = false) => {
|
|
124
|
+
const candidates = [];
|
|
125
|
+
const add = (file , requirement = false, maxBytes = Infinity, user = false) => {
|
|
90
126
|
if (!file) return;
|
|
91
127
|
const absolute = path.isAbsolute(file) ? file : path.resolve(feature?.repoRoot || root, file);
|
|
92
128
|
let stat ;
|
|
93
129
|
try { stat = fsImpl.statSync(absolute); } catch { return; }
|
|
94
|
-
if (!inside(root, absolute, fsImpl) || !stat.isFile()) return;
|
|
95
|
-
|
|
130
|
+
if (!inside(root, absolute, fsImpl) || !stat.isFile() || stat.size > maxBytes) return;
|
|
131
|
+
const known = candidates.find((item) => item.absolute === absolute);
|
|
132
|
+
if (known) known.user ||= user;
|
|
133
|
+
else candidates.push({ absolute, requirement, user });
|
|
96
134
|
};
|
|
97
135
|
if (featureDir) {
|
|
98
136
|
add(feature.paths?.requirement || path.join(featureDir, 'intention', 'requirement.md'), true);
|
|
@@ -106,23 +144,40 @@ function sourceFiles({ projectRoot, feature, fsImpl = fs } )
|
|
|
106
144
|
path.join(featureDir, 'design.md'),
|
|
107
145
|
path.join(featureDir, 'plan.md'),
|
|
108
146
|
]) add(file);
|
|
147
|
+
const maxBytes = userPointerMaxBytes(intakeFile);
|
|
148
|
+
if (maxBytes !== null) {
|
|
149
|
+
for (const file of userContextFiles(path.join(featureDir, USER_CONTEXT_DIR), fsImpl)) add(file, false, maxBytes, true);
|
|
150
|
+
}
|
|
109
151
|
}
|
|
110
152
|
return candidates;
|
|
111
153
|
}
|
|
112
154
|
|
|
113
|
-
|
|
155
|
+
/** `intakeFile` overrides the intake registry path (tests); the default is core/registry/intake.json. */
|
|
156
|
+
function collectPointers({ projectRoot, feature, fsImpl = fs, intakeFile } ) {
|
|
114
157
|
const root = path.resolve(projectRoot);
|
|
115
158
|
const pointers = [];
|
|
116
|
-
for (const source of sourceFiles({ projectRoot: root, feature, fsImpl })) {
|
|
159
|
+
for (const source of sourceFiles({ projectRoot: root, feature, fsImpl, intakeFile })) {
|
|
117
160
|
const rel = inside(root, source.absolute, fsImpl);
|
|
118
161
|
if (!rel) continue;
|
|
162
|
+
const user = source.user || Boolean(originOf(rel));
|
|
119
163
|
let text ;
|
|
120
|
-
try {
|
|
164
|
+
try {
|
|
165
|
+
const data = fsImpl.readFileSync(source.absolute);
|
|
166
|
+
// A user's file may be anything saved as .md; only plain UTF-8 text becomes a pointer. `source.user` is where
|
|
167
|
+
// the file was found, so a realpath outside the `agent-docs/doflow/<slug>/` shape (a linked agent-docs) is held to it too.
|
|
168
|
+
if (user && !isPlainText(data)) continue;
|
|
169
|
+
text = data.toString('utf8');
|
|
170
|
+
} catch { continue; }
|
|
121
171
|
const lines = text.split(/\r?\n/);
|
|
122
|
-
|
|
172
|
+
const ranges = rangesFor(rel, text, source.requirement);
|
|
173
|
+
// A user's markdown often opens with a title or runs without `##` headings; the sections alone would drop that text.
|
|
174
|
+
if (user && (!ranges.length || ranges[0].startLine > 1)) {
|
|
175
|
+
ranges.unshift({ startLine: 1, endLine: ranges.length ? ranges[0].startLine - 1 : lines.length, heading: null });
|
|
176
|
+
}
|
|
177
|
+
for (const range of ranges) {
|
|
123
178
|
const slice = lineSlice(lines, range.startLine, range.endLine);
|
|
124
179
|
if (!slice.trim()) continue;
|
|
125
|
-
pointers.push(createPointer({ path: rel, heading: range.heading, range: { startLine: range.startLine, endLine: range.endLine }, slice }));
|
|
180
|
+
pointers.push(createPointer({ path: rel, heading: range.heading, range: { startLine: range.startLine, endLine: range.endLine }, slice, user }));
|
|
126
181
|
}
|
|
127
182
|
}
|
|
128
183
|
return pointers;
|
|
@@ -139,4 +194,4 @@ function readPointerSlice({ projectRoot, pointer, fsImpl = fs } )
|
|
|
139
194
|
} catch { return null; }
|
|
140
195
|
}
|
|
141
196
|
|
|
142
|
-
module.exports = { collectPointers, createPointer, readPointerSlice, sha256 };
|
|
197
|
+
module.exports = { collectPointers, createPointer, originOf, readPointerSlice, sha256 };
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* `doflow primary` (120 IC-008): the read-only view of which harness and model the main session runs.
|
|
5
|
+
* `--action resolve` prints `resolvePrimary` (IC-007); `--action observed` prints `observedSessions`
|
|
6
|
+
* (093 IC-018 a). Neither writes anything: no state, no config home, no upkeep.
|
|
7
|
+
* Loaded only when the verb, `status` or `doctor` prints the primary, never on an install path.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
11
|
+
const { loadRegistry } = require('../../registry/index.js');
|
|
12
|
+
const { finishRuntime, captureCaught } = require('../cli-result.js');
|
|
13
|
+
const { resolvePrimary, observedSessions } = require('./primary.js');
|
|
14
|
+
|
|
15
|
+
const ALLOWED_FLAGS = new Set(['--action', '--json']);
|
|
16
|
+
|
|
17
|
+
function dash(value ) {
|
|
18
|
+
return value === null || value === undefined ? '-' : String(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** The text form: one line per field, then each warning. */
|
|
22
|
+
function primaryLines(primary ) {
|
|
23
|
+
const { declared, observed } = primary;
|
|
24
|
+
const declaredText = declared
|
|
25
|
+
? `${declared.harness}/${dash(declared.model)} (${declared.origin.harness}${declared.origin.model ? `, model ${declared.origin.model}` : ''})`
|
|
26
|
+
: '-';
|
|
27
|
+
const observedText = observed ? `${observed.model} (${observed.event}, ${observed.observedAt}, sessions ${observed.sessions})` : '-';
|
|
28
|
+
return [
|
|
29
|
+
`state: ${primary.state}`,
|
|
30
|
+
`harness: ${dash(primary.harness)}`,
|
|
31
|
+
`model: ${dash(primary.model)}`,
|
|
32
|
+
`family: ${primary.family}`,
|
|
33
|
+
`declared: ${declaredText}`,
|
|
34
|
+
`observed: ${observedText}`,
|
|
35
|
+
`probe: ${primary.probe}`,
|
|
36
|
+
`reason: ${dash(primary.reason)}`,
|
|
37
|
+
...primary.warnings.map((warning ) => `warning: ${warning}`),
|
|
38
|
+
];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** The origin layer of the declared harness; `default` when nothing declared one. */
|
|
42
|
+
function primaryOrigin(primary ) {
|
|
43
|
+
return primary.declared?.origin?.harness ?? 'default';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** `<state> <harness>/<model> (<origin>)`, the line `status` and `doctor` share. */
|
|
47
|
+
function primarySummary(primary ) {
|
|
48
|
+
return `${primary.state} ${dash(primary.harness)}/${dash(primary.model)} (${primaryOrigin(primary)})`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** The `doctor` object: the probe label is `declared-only` without a probe, else the harness's recorded probe status. */
|
|
52
|
+
function primaryDoctorView({ primary, registry } ) {
|
|
53
|
+
const entry = (registry.harnesses || []).find((harness ) => harness?.id === primary.harness);
|
|
54
|
+
const probe = primary.probe === 'none' ? 'declared-only' : (entry?.primaryProbe?.status ?? 'unverified');
|
|
55
|
+
return { state: primary.state, harness: primary.harness, model: primary.model, origin: primaryOrigin(primary), probe };
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** The text form of `observed`: the store, one line per checkout, one per session; no path or raw id. */
|
|
59
|
+
function observedLines(body ) {
|
|
60
|
+
return [
|
|
61
|
+
`store: ${body.store}`,
|
|
62
|
+
...body.checkouts.map((c ) => `checkout ${c.worktreeKey} ${c.role}${c.current ? ' current' : ''} ${c.branch === null ? 'detached' : c.branch} ${dash(c.head)} ${dash(c.headCommittedAt)}`),
|
|
63
|
+
...body.sessions.map((s ) => {
|
|
64
|
+
const unit = s.launched ? ` L.${s.launched.run}.${s.launched.lineage}.${s.launched.attempt}` : '';
|
|
65
|
+
return `session ${s.sessionKey} ${s.worktreeKey} ${s.kind}${unit} harness ${dash(s.harness)} model ${dash(s.model)} started ${dash(s.startedAt)} last-hook-record ${dash(s.lastHookRecordAt)}`;
|
|
66
|
+
}),
|
|
67
|
+
...(body.truncated ? ['truncated: true'] : []),
|
|
68
|
+
...(body.unreadable > 0 ? [`unreadable: ${body.unreadable}`] : []),
|
|
69
|
+
];
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function handlePrimaryCommand({ action, json = false, flags = [], env = process.env, cwd = process.cwd(), registry, out = (line ) => console.log(line) } = {}) {
|
|
73
|
+
const usage = (message ) => {
|
|
74
|
+
out(json ? JSON.stringify({ ok: false, error: 'usage', message }) : `doflow primary: ${message}`);
|
|
75
|
+
return finishRuntime(2);
|
|
76
|
+
};
|
|
77
|
+
if (action !== 'resolve' && action !== 'observed') return usage('action must be resolve');
|
|
78
|
+
const extra = (flags ).map((flag) => flag.split('=')[0]).find((flag) => !ALLOWED_FLAGS.has(flag));
|
|
79
|
+
if (extra !== undefined) return usage(`unsupported flag ${extra}; only --action and --json apply`);
|
|
80
|
+
|
|
81
|
+
if (action === 'observed') {
|
|
82
|
+
try {
|
|
83
|
+
const observed = observedSessions({ env, cwd });
|
|
84
|
+
if (!observed.ok) {
|
|
85
|
+
if (json) out(JSON.stringify({ v: 1, error: observed.error }));
|
|
86
|
+
else console.error('[ERROR] primary: cannot list checkouts');
|
|
87
|
+
return finishRuntime(1);
|
|
88
|
+
}
|
|
89
|
+
if (json) out(JSON.stringify(observed.body));
|
|
90
|
+
else for (const line of observedLines(observed.body)) out(line);
|
|
91
|
+
return finishRuntime(0);
|
|
92
|
+
} catch (error ) {
|
|
93
|
+
captureCaught(error, 'primary', 1);
|
|
94
|
+
console.error(`[ERROR] primary: ${error?.message || String(error)}`);
|
|
95
|
+
return finishRuntime(1);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
try {
|
|
100
|
+
const primary = resolvePrimary({ env, projectRoot: cwd, registry });
|
|
101
|
+
if (json) out(JSON.stringify(primary));
|
|
102
|
+
else for (const line of primaryLines(primary)) out(line);
|
|
103
|
+
return finishRuntime(primary.state === 'refused' ? 1 : 0);
|
|
104
|
+
} catch (error ) {
|
|
105
|
+
captureCaught(error, 'primary', 1);
|
|
106
|
+
console.error(`[ERROR] primary: ${error?.message || String(error)}`);
|
|
107
|
+
return finishRuntime(1);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** The `status` and `doctor` reader: one resolution with the shipped registry; the caller handles a fault. */
|
|
112
|
+
function resolveForReport({ env, projectRoot, repoRoot = REPO_ROOT } ) {
|
|
113
|
+
const registry = loadRegistry({ repoRoot });
|
|
114
|
+
return { primary: resolvePrimary({ env, projectRoot, registry }), registry };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
module.exports = { handlePrimaryCommand, primaryLines, primarySummary, primaryDoctorView, resolveForReport };
|