@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,37 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
/** Shared redaction rules for settings and their compatibility adapters. */
|
|
6
|
+
const { OFF_WORDS, isOffWord } = require('../off-words.js');
|
|
7
|
+
|
|
8
|
+
function maskUrlParts(text ) {
|
|
9
|
+
return text.split('&').map((part) => {
|
|
10
|
+
if (part === '') return part;
|
|
11
|
+
const equalsAt = part.indexOf('=');
|
|
12
|
+
return equalsAt < 0 ? '***' : `${part.slice(0, equalsAt)}=***`;
|
|
13
|
+
}).join('&');
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/** A URL with query values and user-info redacted, including non-hierarchical endpoint forms. */
|
|
17
|
+
function maskUrl(text ) {
|
|
18
|
+
let value = text;
|
|
19
|
+
try {
|
|
20
|
+
const url = new URL(text);
|
|
21
|
+
if (url.username || url.password) value = `${url.protocol}//***@${url.host}${url.pathname}${url.search}${url.hash}`;
|
|
22
|
+
} catch {
|
|
23
|
+
// The historical fallback also covers malformed URL-style authorities.
|
|
24
|
+
}
|
|
25
|
+
value = value.replace(/\/\/.*@/g, '//***@');
|
|
26
|
+
value = value.replace(/^([^/:]+:)[^@/]*@/, '$1***@');
|
|
27
|
+
const hashAt = value.indexOf('#');
|
|
28
|
+
const beforeHash = hashAt < 0 ? value : value.slice(0, hashAt);
|
|
29
|
+
const fragment = hashAt < 0 ? '' : `#${maskUrlParts(value.slice(hashAt + 1))}`;
|
|
30
|
+
const queryAt = beforeHash.indexOf('?');
|
|
31
|
+
if (queryAt < 0) return `${beforeHash}${fragment}`;
|
|
32
|
+
const head = beforeHash.slice(0, queryAt + 1);
|
|
33
|
+
const query = maskUrlParts(beforeHash.slice(queryAt + 1));
|
|
34
|
+
return `${head}${query}${fragment}`;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
module.exports = { OFF_WORDS, isOffWord, maskUrl };
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The person-written billing record (`workers.billing`, global-only): which headless workers the
|
|
7
|
+
* owner states are `free` or `metered`. It lets a capped dispatch (`--billing-cap free`) treat a
|
|
8
|
+
* worker the registry lists as `unknown` as free, and it can only narrow a worker the registry
|
|
9
|
+
* lists as `free`. It lives with the settings it reads so dispatch needs no settings internals.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
const none = (state , reason = null) => ({ state, reason, workers: {} });
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Read fresh on every call (never memoised: a copied env defeats the resolver cache), from the user
|
|
20
|
+
* layer only: a project file never decides it. Absent is no config home or no decided value. Invalid
|
|
21
|
+
* is a resolution in the invalid state, any invalid candidate, an id that is not in `workerIds`
|
|
22
|
+
* (reason `unknown-worker:<id>`) or any thrown error: the caller then counts no worker as free.
|
|
23
|
+
* A retired harness id (127 IC-005) is dropped by the resolver before this sees the value, with an
|
|
24
|
+
* advisory in the settings view's notes; it is never an unknown worker. `registry` is an optional
|
|
25
|
+
* loaded settings registry (tests); it defaults to the shipped one.
|
|
26
|
+
*/
|
|
27
|
+
function readWorkerBilling(env = process.env, { workerIds = [], registry } = {}) {
|
|
28
|
+
try {
|
|
29
|
+
const { loadSettings } = require('./resolve.js');
|
|
30
|
+
const resolution = loadSettings({ global: true, env: { ...env }, registry }).get('workers.billing', 'global');
|
|
31
|
+
const bad = resolution.candidates.find((c ) => c.status === 'invalid');
|
|
32
|
+
if (bad || resolution.state === 'invalid') return none('invalid', (bad && bad.reason) || 'invalid');
|
|
33
|
+
if (resolution.origin?.via === 'no-config-home') return none('absent');
|
|
34
|
+
const value = resolution.value;
|
|
35
|
+
if (value === null || value === undefined) return none('absent');
|
|
36
|
+
for (const id of Object.keys(value)) {
|
|
37
|
+
if (!workerIds.includes(id)) return none('invalid', `unknown-worker:${id}`);
|
|
38
|
+
}
|
|
39
|
+
return { state: 'set', reason: null, workers: structuredClone(value) };
|
|
40
|
+
} catch {
|
|
41
|
+
return none('invalid', 'unreadable');
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Derives billing category from harness detected authentication mode (IC-004).
|
|
47
|
+
* subscription -> free, api-key -> metered, unverified -> unknown.
|
|
48
|
+
*/
|
|
49
|
+
function deriveBilling(mode ) {
|
|
50
|
+
if (mode === 'subscription') return 'free';
|
|
51
|
+
if (mode === 'api-key') return 'metered';
|
|
52
|
+
return 'unknown';
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* The billing a capped dispatch judges for one worker.
|
|
57
|
+
* Precedence: user record (`workers.billing`) > derived (`catalogue`) > registry descriptor.
|
|
58
|
+
* Cap enforcement: neither record nor derived ever lifts a registry `metered`.
|
|
59
|
+
*/
|
|
60
|
+
function effectiveBilling(
|
|
61
|
+
descriptorBilling ,
|
|
62
|
+
harnessId ,
|
|
63
|
+
record ,
|
|
64
|
+
catalogue
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
{
|
|
71
|
+
const base = descriptorBilling === 'free' || descriptorBilling === 'metered' ? descriptorBilling : 'unknown';
|
|
72
|
+
const entry = record && record.state === 'set' && Object.hasOwn(record.workers, harnessId) ? record.workers[harnessId] : null;
|
|
73
|
+
|
|
74
|
+
// 1. User record
|
|
75
|
+
if (entry) {
|
|
76
|
+
if (base === 'metered') {
|
|
77
|
+
return { value: 'metered', source: 'registry' };
|
|
78
|
+
}
|
|
79
|
+
if (entry.billing !== base && (entry.billing === 'metered' || base === 'unknown')) {
|
|
80
|
+
return { value: entry.billing, source: 'user-record', asserted: entry.asserted };
|
|
81
|
+
}
|
|
82
|
+
if (entry.billing === base) {
|
|
83
|
+
return { value: base, source: 'registry' };
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 2. Derived from catalogue
|
|
88
|
+
if (catalogue && catalogue.harnesses && Object.hasOwn(catalogue.harnesses, harnessId)) {
|
|
89
|
+
const catHarness = catalogue.harnesses[harnessId];
|
|
90
|
+
const derived = catHarness.derivedBilling || deriveBilling(catHarness.detectedMode);
|
|
91
|
+
if (derived === 'free' || derived === 'metered') {
|
|
92
|
+
if (base === 'metered') {
|
|
93
|
+
return { value: 'metered', source: 'registry' };
|
|
94
|
+
}
|
|
95
|
+
return {
|
|
96
|
+
value: derived,
|
|
97
|
+
source: 'derived',
|
|
98
|
+
detectedMode: catHarness.detectedMode,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// 3. Fallback to registry descriptor
|
|
104
|
+
return { value: base, source: 'registry' };
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
module.exports = { readWorkerBilling, deriveBilling, effectiveBilling };
|
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
/** Atomic writer for one user or project settings layer (086 IC-011). */
|
|
6
|
+
|
|
7
|
+
const fs = require('node:fs');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
const { randomBytes } = require('node:crypto');
|
|
10
|
+
const { isDeepStrictEqual } = require('node:util');
|
|
11
|
+
const { REPO_ROOT } = require('../../helper/repo-root.js');
|
|
12
|
+
const { loadSettingsRegistry } = require('./registry.js');
|
|
13
|
+
const { MAX_BYTES, readLayerFile, validateValue } = require('./files.js');
|
|
14
|
+
const { clearSettingsCache } = require('./resolve.js');
|
|
15
|
+
const { containedDoflow } = require('./project-layers.js');
|
|
16
|
+
|
|
17
|
+
function isObject(value ) {
|
|
18
|
+
return value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function invalid(file , detail , code = null) {
|
|
22
|
+
return { ok: false , reason: 'config-invalid' , code, detail: `${file}: ${detail}` };
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function scrub(value , keys ) {
|
|
26
|
+
if (Array.isArray(value)) return value.map((entry) => scrub(entry, keys));
|
|
27
|
+
if (!isObject(value)) return value;
|
|
28
|
+
const out = {};
|
|
29
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
30
|
+
if (!keys.has(key)) Object.defineProperty(out, key, { value: scrub(entry, keys), enumerable: true, configurable: true, writable: true });
|
|
31
|
+
}
|
|
32
|
+
return out;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/** A layer document as written: `schema` first, then the owned keys in registry order, then the rest. */
|
|
36
|
+
function layerDocument(registry , values ) {
|
|
37
|
+
const after = { schema: 1 };
|
|
38
|
+
for (const key of registry.keys.keys()) {
|
|
39
|
+
if (Object.hasOwn(values, key)) after[key] = values[key];
|
|
40
|
+
}
|
|
41
|
+
for (const [key, value] of Object.entries(values)) {
|
|
42
|
+
if (key !== 'schema' && !registry.keys.has(key)) {
|
|
43
|
+
Object.defineProperty(after, key, { value, enumerable: true, configurable: true, writable: true });
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return after;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** The bytes of a layer file. The one serializer: a check that measures a write measures these. */
|
|
50
|
+
function serializeLayer(document ) {
|
|
51
|
+
return Buffer.from(`${JSON.stringify(document, null, 2)}\n`, 'utf8');
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The refusal for bytes the layer reader (`readLayerFile`, MAX_BYTES) would reject as too-large. */
|
|
55
|
+
function oversize(file , bytes ) {
|
|
56
|
+
if (bytes.length <= MAX_BYTES) return null;
|
|
57
|
+
return invalid(file, `would be ${bytes.length} bytes as written, over the ${MAX_BYTES}-byte limit of a settings file`, 'too-large');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function backupLayerFile(file , fsImpl = fs) {
|
|
61
|
+
let contents;
|
|
62
|
+
try {
|
|
63
|
+
contents = fsImpl.readFileSync(file);
|
|
64
|
+
} catch (error ) {
|
|
65
|
+
return invalid(file, `could not be preserved: ${error?.message || 'could not be read'}`);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
for (let index = 0; ; index++) {
|
|
69
|
+
const backup = `${file}.bak${index === 0 ? '' : `.${index}`}`;
|
|
70
|
+
try {
|
|
71
|
+
fsImpl.writeFileSync(backup, contents, { flag: 'wx', mode: 0o600 });
|
|
72
|
+
fsImpl.chmodSync(backup, 0o600);
|
|
73
|
+
return { ok: true, file: backup };
|
|
74
|
+
} catch (error ) {
|
|
75
|
+
if (error?.code === 'EEXIST') continue;
|
|
76
|
+
try { fsImpl.rmSync(backup, { force: true }); } catch { /* preserve the primary refusal */ }
|
|
77
|
+
return invalid(file, `could not be preserved: ${error?.message || 'could not be written'}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Keep the bytes a write replaces in `<file>.config.bak`, replaced atomically on every write, so the
|
|
84
|
+
* backup stays one file however often a layer is written. It is not backupLayerFile's `.bak[.N]`
|
|
85
|
+
* series, which preserves a malformed file once and must never be overwritten by a later write.
|
|
86
|
+
*/
|
|
87
|
+
function replaceBackup(file , fsImpl ) {
|
|
88
|
+
const backup = `${file}.config.bak`;
|
|
89
|
+
const temporary = path.join(path.dirname(file), `.${path.basename(backup)}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`);
|
|
90
|
+
try {
|
|
91
|
+
fsImpl.writeFileSync(temporary, fsImpl.readFileSync(file), { flag: 'wx', mode: 0o600 });
|
|
92
|
+
fsImpl.chmodSync(temporary, 0o600);
|
|
93
|
+
fsImpl.renameSync(temporary, backup);
|
|
94
|
+
return { ok: true, file: backup };
|
|
95
|
+
} catch (error ) {
|
|
96
|
+
try { fsImpl.rmSync(temporary, { force: true }); } catch { /* preserve the primary refusal */ }
|
|
97
|
+
return invalid(file, `could not be backed up: ${error?.message || 'could not be written'}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** How long a writer waits for another writer's lock. */
|
|
102
|
+
const LOCK_WAIT_MS = 2000;
|
|
103
|
+
const PAUSE = new Int32Array(new SharedArrayBuffer(4));
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Take `<file>.lock` (created exclusively, holding a token) so one read-merge-rename of a layer runs at a
|
|
107
|
+
* time across processes. A held lock is waited on for LOCK_WAIT_MS, then refused with code `locked`, naming
|
|
108
|
+
* the lock and its age. A lock is never removed automatically, whatever its age: a writer stalled in its
|
|
109
|
+
* critical section would otherwise lose its write silently, and a lock left by a crashed writer is one
|
|
110
|
+
* file the person removes.
|
|
111
|
+
*/
|
|
112
|
+
function acquireLock(file , fsImpl ) {
|
|
113
|
+
const lock = `${file}.lock`;
|
|
114
|
+
const token = `${process.pid} ${randomBytes(8).toString('hex')}\n`;
|
|
115
|
+
const deadline = Date.now() + LOCK_WAIT_MS;
|
|
116
|
+
for (;;) {
|
|
117
|
+
try {
|
|
118
|
+
fsImpl.writeFileSync(lock, token, { encoding: 'utf8', flag: 'wx', mode: 0o600 });
|
|
119
|
+
return { ok: true, lock, token };
|
|
120
|
+
} catch (error ) {
|
|
121
|
+
if (error?.code !== 'EEXIST') return invalid(file, `could not be locked: ${error?.message || 'lock not created'}`, 'locked');
|
|
122
|
+
}
|
|
123
|
+
let age ;
|
|
124
|
+
try { age = Math.round((Date.now() - fsImpl.statSync(lock).mtimeMs) / 1000); } catch (error ) {
|
|
125
|
+
if (error?.code === 'ENOENT') continue;
|
|
126
|
+
age = NaN;
|
|
127
|
+
}
|
|
128
|
+
if (Date.now() >= deadline) {
|
|
129
|
+
const aged = Number.isFinite(age) ? `${Math.max(age, 0)} s old` : 'age unknown';
|
|
130
|
+
return invalid(file, `is held by the lock ${lock} (${aged}); nothing was written; if no writer is running, remove that lock file and run it again`, 'locked');
|
|
131
|
+
}
|
|
132
|
+
Atomics.wait(PAUSE, 0, 0, 20);
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function releaseLock({ lock, token } , fsImpl ) {
|
|
137
|
+
try {
|
|
138
|
+
if (fsImpl.readFileSync(lock, 'utf8') === token) fsImpl.rmSync(lock, { force: true });
|
|
139
|
+
} catch { /* already gone; keep the primary result */ }
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Write one layer. The read, merge and rename run under the layer's lock (`acquireLock`), so two
|
|
144
|
+
* writers of different keys never drop each other's key. `expect` (optional): the document the caller
|
|
145
|
+
* validated against, or null when it found no file; when the file under the lock differs, nothing is
|
|
146
|
+
* written and the result has code `changed`, so a caller's verdict always describes what it replaces.
|
|
147
|
+
* `backup` (optional): before an existing file is replaced, keep its bytes (`replaceBackup`).
|
|
148
|
+
*/
|
|
149
|
+
function writeLayer({ file, set = {}, unset = [], scrubKeys = [], force = false, expect, backup = false, fsImpl = fs } = {}) {
|
|
150
|
+
if (typeof file !== 'string' || file.length === 0) {
|
|
151
|
+
return { ok: false, reason: 'config-invalid', detail: 'file is required' };
|
|
152
|
+
}
|
|
153
|
+
if (!isObject(set) || !Array.isArray(unset) || unset.some((key) => typeof key !== 'string')) {
|
|
154
|
+
return invalid(file, 'set must be an object and unset must be an array of keys');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!Array.isArray(scrubKeys) || scrubKeys.some((key ) => typeof key !== 'string')) {
|
|
158
|
+
return invalid(file, 'scrubKeys must be an array of keys');
|
|
159
|
+
}
|
|
160
|
+
const loaded = loadSettingsRegistry({ repoRoot: REPO_ROOT, fsImpl });
|
|
161
|
+
if (!loaded.ok) return invalid(file, `settings registry is invalid: ${loaded.detail}`);
|
|
162
|
+
const { registry } = loaded;
|
|
163
|
+
const unsetKeys = new Set(unset);
|
|
164
|
+
for (const [key, value] of Object.entries(set)) {
|
|
165
|
+
const spec = registry.keys.get(key);
|
|
166
|
+
if (!spec) return invalid(file, `unknown owned key '${key}'`);
|
|
167
|
+
if (unsetKeys.has(key)) return invalid(file, `key '${key}' cannot be both set and unset`);
|
|
168
|
+
const fault = validateValue(spec, value, registry.enumValues(key));
|
|
169
|
+
if (fault) return invalid(file, `'${key}' ${fault.reason}`);
|
|
170
|
+
}
|
|
171
|
+
for (const key of unsetKeys) {
|
|
172
|
+
if (!registry.keys.has(key)) return invalid(file, `unknown owned key '${key}'`);
|
|
173
|
+
}
|
|
174
|
+
for (const key of scrubKeys) {
|
|
175
|
+
if (!registry.keys.has(key) || !Object.hasOwn(set, key)) return invalid(file, `scrub key '${key}' must be a registered key also present in set`);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
try {
|
|
179
|
+
fsImpl.mkdirSync(path.dirname(file), { recursive: true, mode: 0o700 });
|
|
180
|
+
} catch (error ) {
|
|
181
|
+
return invalid(file, error?.message || 'could not be written');
|
|
182
|
+
}
|
|
183
|
+
const held = acquireLock(file, fsImpl);
|
|
184
|
+
if (!held.ok) return held;
|
|
185
|
+
try {
|
|
186
|
+
return replaceLayer({ file, registry, set, unsetKeys, scrubKeys, force, expect, backup, fsImpl });
|
|
187
|
+
} finally {
|
|
188
|
+
releaseLock(held, fsImpl);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
const INVALID = Symbol('invalid layer');
|
|
193
|
+
|
|
194
|
+
/** The read, merge and rename of `writeLayer`, run while it holds the layer's lock. */
|
|
195
|
+
function replaceLayer({ file, registry, set, unsetKeys, scrubKeys, force, expect, backup, fsImpl } ) {
|
|
196
|
+
let before = null;
|
|
197
|
+
let existing = {};
|
|
198
|
+
let layer ;
|
|
199
|
+
try {
|
|
200
|
+
layer = readLayerFile(file, { registry, fsImpl, includeDocument: true });
|
|
201
|
+
if (layer.status === 'invalid') {
|
|
202
|
+
if (!force) return invalid(file, `${layer.code}: ${layer.reason}`, layer.code);
|
|
203
|
+
} else if (layer.status === 'ok') {
|
|
204
|
+
if (!isObject(layer.document)) {
|
|
205
|
+
if (!force) return invalid(file, 'must be a JSON object', 'unparseable');
|
|
206
|
+
} else {
|
|
207
|
+
before = layer.document;
|
|
208
|
+
existing = scrub(layer.document, new Set(scrubKeys));
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} catch (error ) {
|
|
212
|
+
if (!force) return invalid(file, 'could not be read');
|
|
213
|
+
before = null;
|
|
214
|
+
existing = {};
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (expect !== undefined) {
|
|
218
|
+
const current = layer?.status === 'ok' ? layer.document : layer?.status === 'absent' ? null : INVALID;
|
|
219
|
+
if (!isDeepStrictEqual(current, expect)) {
|
|
220
|
+
return invalid(file, 'changed while this write was being checked; nothing was written', 'changed');
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const changed = layer?.status !== 'ok'
|
|
225
|
+
|| scrubKeys.length > 0
|
|
226
|
+
|| Object.entries(set).some(([key, value]) => !Object.hasOwn(existing, key) || existing[key] !== value)
|
|
227
|
+
|| [...unsetKeys].some((key) => Object.hasOwn(existing, key));
|
|
228
|
+
if (!changed) return { ok: true, file, before, after: existing };
|
|
229
|
+
|
|
230
|
+
const values = { ...existing };
|
|
231
|
+
for (const [key, value] of Object.entries(set)) values[key] = value;
|
|
232
|
+
for (const key of unsetKeys) delete values[key];
|
|
233
|
+
|
|
234
|
+
const after = layerDocument(registry, values);
|
|
235
|
+
const bytes = serializeLayer(after);
|
|
236
|
+
const tooLarge = oversize(file, bytes);
|
|
237
|
+
if (tooLarge) return tooLarge;
|
|
238
|
+
|
|
239
|
+
if (backup && layer?.status !== 'absent') {
|
|
240
|
+
const kept = replaceBackup(file, fsImpl);
|
|
241
|
+
if (!kept.ok) return kept;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
const directory = path.dirname(file);
|
|
245
|
+
let temporary = null;
|
|
246
|
+
try {
|
|
247
|
+
temporary = path.join(directory, `.${path.basename(file)}.${process.pid}.${randomBytes(8).toString('hex')}.tmp`);
|
|
248
|
+
fsImpl.writeFileSync(temporary, bytes, { flag: 'wx', mode: 0o600 });
|
|
249
|
+
fsImpl.chmodSync(temporary, 0o600);
|
|
250
|
+
if (path.basename(directory) === '.doflow') {
|
|
251
|
+
const root = path.dirname(directory);
|
|
252
|
+
if (!containedDoflow(root, fsImpl)) {
|
|
253
|
+
return invalid(file, '.doflow symlink points outside the project', 'scope');
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
fsImpl.renameSync(temporary, file);
|
|
257
|
+
temporary = null;
|
|
258
|
+
clearSettingsCache();
|
|
259
|
+
return { ok: true, file, before, after };
|
|
260
|
+
} catch (error ) {
|
|
261
|
+
return invalid(file, error?.message || 'could not be written');
|
|
262
|
+
} finally {
|
|
263
|
+
if (temporary !== null) {
|
|
264
|
+
try { fsImpl.rmSync(temporary, { force: true }); } catch { /* keep the primary result */ }
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
module.exports = { writeLayer, backupLayerFile, layerDocument, serializeLayer, oversize };
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
// TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The `slot` verb's argument checks and output forms (feature 115, IC-008). The queue itself is
|
|
7
|
+
* `./slots.js`; this file only maps flags to it and its results to exit codes.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { runSlot, listSlots, peekSlots, releaseSlot } = require('./slots.js');
|
|
11
|
+
const { finishRuntime } = require('./cli-result.js');
|
|
12
|
+
|
|
13
|
+
const TIMEOUT_MAX = 86400;
|
|
14
|
+
|
|
15
|
+
function refuse(message , json ) {
|
|
16
|
+
if (json) console.log(JSON.stringify({ ok: false, error: 'usage', message }));
|
|
17
|
+
else console.error(`doflow slot: ${message}`);
|
|
18
|
+
return finishRuntime(2);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
async function handleSlotCommand({
|
|
22
|
+
action, name, label, timeout, noWait = false, command, by, reason, positional = [], extraFlags = [], json = false, env = process.env, cwd = process.cwd(),
|
|
23
|
+
} = {}) {
|
|
24
|
+
const usage = 'usage: slot --action run|list|peek|release (see doflow --help)';
|
|
25
|
+
if (extraFlags.length > 0) return refuse(`unknown flag '${extraFlags[0]}'; ${usage}`, json);
|
|
26
|
+
if (positional.length > 0) return refuse(`unexpected argument '${positional[0]}'; ${usage}`, json);
|
|
27
|
+
if (action !== 'run' && action !== 'list' && action !== 'peek' && action !== 'release') return refuse(`--action must be run, list, peek or release; ${usage}`, json);
|
|
28
|
+
try {
|
|
29
|
+
if (action === 'run') {
|
|
30
|
+
if (!Array.isArray(command)) return refuse('slot run needs the command after --, as in: slot --action run --name <name> -- <command> [args...]', json);
|
|
31
|
+
let timeoutSeconds ;
|
|
32
|
+
if (timeout !== undefined) {
|
|
33
|
+
if (!Number.isInteger(timeout) || timeout < 1 || timeout > TIMEOUT_MAX) return refuse(`--timeout must be a whole number of seconds from 1 to ${TIMEOUT_MAX}`, json);
|
|
34
|
+
timeoutSeconds = timeout;
|
|
35
|
+
}
|
|
36
|
+
return finishRuntime(await runSlot({ name, command, label, timeoutSeconds, noWait, cwd, env }));
|
|
37
|
+
}
|
|
38
|
+
if (action === 'list') {
|
|
39
|
+
const listed = await listSlots({ env, json });
|
|
40
|
+
console.log(listed.output);
|
|
41
|
+
return finishRuntime(0);
|
|
42
|
+
}
|
|
43
|
+
if (action === 'peek') {
|
|
44
|
+
// 093 IC-018 b: a read with no lock, sweep or mkdir; no pid, start or cwd in either form.
|
|
45
|
+
const peeked = await peekSlots({ env, cwd });
|
|
46
|
+
if (json) console.log(JSON.stringify(peeked));
|
|
47
|
+
else if (peeked.slots.length === 0) console.log('no slots held');
|
|
48
|
+
else {
|
|
49
|
+
for (const slot of peeked.slots) {
|
|
50
|
+
const head = slot.holder
|
|
51
|
+
? `held (${slot.holder.label}) since ${slot.holder.since}, alive ${slot.holder.alive}${slot.holder.worktreeKey ? `, checkout ${slot.holder.worktreeKey}` : ', another directory'}`
|
|
52
|
+
: 'free';
|
|
53
|
+
console.log(`${slot.name} ${head}; ${slot.waiters} waiting`);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
if (!json && peeked.unreadable > 0) console.log(`unreadable: ${peeked.unreadable}`);
|
|
57
|
+
return finishRuntime(0);
|
|
58
|
+
}
|
|
59
|
+
const released = await releaseSlot({ name, by, reason, env });
|
|
60
|
+
if (json) console.log(JSON.stringify(released));
|
|
61
|
+
else {
|
|
62
|
+
const held = released.holder ? ` (holder ${released.holder.pid}, ${released.holder.label})` : '';
|
|
63
|
+
const line = `slot ${name}: ${released.disposition}${held}`;
|
|
64
|
+
if (released.ok) console.log(line); else console.error(line);
|
|
65
|
+
}
|
|
66
|
+
return finishRuntime(released.ok ? 0 : 1);
|
|
67
|
+
} catch (error ) {
|
|
68
|
+
if (error.code === 'usage') return refuse(error.message, json);
|
|
69
|
+
if (error.code === 'no-home' || error.code === 'slots-unsafe') {
|
|
70
|
+
if (json) console.log(JSON.stringify({ ok: false, error: error.code, message: error.message }));
|
|
71
|
+
else console.error(`doflow slot: ${error.message}`);
|
|
72
|
+
return finishRuntime(1);
|
|
73
|
+
}
|
|
74
|
+
throw error;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
module.exports = { handleSlotCommand };
|