@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
|
@@ -9,18 +9,17 @@
|
|
|
9
9
|
* joined by any event that names more than one, and a unit goes only as a whole: when every item in
|
|
10
10
|
* it is settled in the fold (a follow-up done or dismissed, a goal done) and its newest event is
|
|
11
11
|
* older than the window. A unit whose removal would change anything else the fold shows (another
|
|
12
|
-
* item, a feature, a release, a conflict) is kept
|
|
12
|
+
* item, a feature, a release, a conflict) is kept, and so is a unit that an event of a type this
|
|
13
|
+
* version does not know (written by a newer DoFlow) names by id.
|
|
13
14
|
*
|
|
14
|
-
* `parseWindow` remains pure. `retentionWindow` composes
|
|
15
|
-
*
|
|
15
|
+
* `parseWindow` remains pure. `retentionWindow` composes the resolver-owned default with the project's
|
|
16
|
+
* prune setting; explicit values retain their existing meaning while pruning is off.
|
|
16
17
|
*/
|
|
17
18
|
|
|
18
19
|
const nodeFs = require('node:fs');
|
|
19
20
|
const { isDeepStrictEqual } = require('node:util');
|
|
20
|
-
const {
|
|
21
|
-
const {
|
|
22
|
-
const { resolveRoots, readPruneSetting } = require('../storage/roots.js');
|
|
23
|
-
const { foldInto, finalize } = require('./fold.js');
|
|
21
|
+
const { loadSettings } = require('../settings/resolve.js');
|
|
22
|
+
const { foldInto, finalize, EVENT_TYPES } = require('./fold.js');
|
|
24
23
|
|
|
25
24
|
const HOUR_MS = 3600000;
|
|
26
25
|
|
|
@@ -35,23 +34,46 @@ function parseWindow(env = process.env)
|
|
|
35
34
|
return { state: 'invalid', raw: String(raw) };
|
|
36
35
|
}
|
|
37
36
|
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
function invalidRaw(resolved , env ) {
|
|
38
|
+
for (let i = resolved.candidates.length - 1; i >= 0; i -= 1) {
|
|
39
|
+
const candidate = resolved.candidates[i];
|
|
40
|
+
if (candidate.status !== 'invalid') continue;
|
|
41
|
+
if (candidate.layer === 'env') return String(env.DOFLOW_RETENTION_HOURS ?? '');
|
|
42
|
+
try {
|
|
43
|
+
const raw = JSON.parse(candidate.display);
|
|
44
|
+
if (typeof raw === 'string' || typeof raw === 'number') return String(raw);
|
|
45
|
+
} catch { /* a malformed whole-file candidate has no display value */ }
|
|
46
|
+
return candidate.display || candidate.reason || candidate.code || '';
|
|
47
|
+
}
|
|
48
|
+
return '';
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Composes the retention setting and prune gate through the shared settings resolver. */
|
|
52
|
+
function retentionWindow({ env = process.env, projectRoot, fsImpl = nodeFs } = {}) {
|
|
40
53
|
const parsed = parseWindow(env);
|
|
41
54
|
if (parsed.state === 'invalid') return parsed;
|
|
42
|
-
if (parsed.state === 'on') return { ...parsed, source: 'env' };
|
|
43
|
-
let activeRegistry ;
|
|
44
55
|
try {
|
|
45
|
-
|
|
56
|
+
let settingsRegistry;
|
|
57
|
+
if (fsImpl !== nodeFs) {
|
|
58
|
+
const { loadSettingsRegistry } = require('../settings/registry.js');
|
|
59
|
+
const loaded = loadSettingsRegistry({ fsImpl });
|
|
60
|
+
if (!loaded.ok) throw new Error(loaded.detail);
|
|
61
|
+
settingsRegistry = loaded.registry;
|
|
62
|
+
}
|
|
63
|
+
const view = loadSettings({ projectRoot, env, fsImpl, ...(settingsRegistry ? { registry: settingsRegistry } : {}) });
|
|
64
|
+
const retention = view.get('storage.retentionHours', 'project');
|
|
65
|
+
if (retention.state === 'invalid') return { state: 'invalid', raw: invalidRaw(retention, env) };
|
|
66
|
+
if (retention.explicit) {
|
|
67
|
+
const hours = retention.value;
|
|
68
|
+
return { state: 'on', hours, ms: hours * HOUR_MS, source: retention.origin.layer };
|
|
69
|
+
}
|
|
70
|
+
if (view.get('storage.prune', 'project').value !== 'on') return { state: 'off' };
|
|
71
|
+
const hours = retention.value;
|
|
72
|
+
return { state: 'on', hours, ms: hours * HOUR_MS, source: 'default' };
|
|
46
73
|
} catch {
|
|
47
|
-
|
|
74
|
+
// Preserve an explicit environment window when the resolver registry is unavailable.
|
|
75
|
+
return parsed.state === 'on' ? { ...parsed, source: 'env' } : { state: 'off' };
|
|
48
76
|
}
|
|
49
|
-
const roots = resolveRoots({ registry: activeRegistry, projectRoot, env });
|
|
50
|
-
if (readPruneSetting({ registry: activeRegistry, roots, rootId: 'project-state', fsImpl }).setting !== 'on') return { state: 'off' };
|
|
51
|
-
const lifecycleStore = activeRegistry.stores.find((store ) => store.id === 'lifecycle-events');
|
|
52
|
-
if (!lifecycleStore) throw new Error('Storage registry has no lifecycle-events store');
|
|
53
|
-
const hours = lifecycleStore.bound.defaultHours;
|
|
54
|
-
return { state: 'on', hours, ms: hours * HOUR_MS, source: 'default' };
|
|
55
77
|
}
|
|
56
78
|
|
|
57
79
|
function isText(value ) { return typeof value === 'string' && value.trim() !== ''; }
|
|
@@ -113,6 +135,34 @@ function unitsOf(events , shouldStop ) {
|
|
|
113
135
|
return [...units.values()];
|
|
114
136
|
}
|
|
115
137
|
|
|
138
|
+
/**
|
|
139
|
+
* Every string value and every object key in the data of the events whose type this version's fold
|
|
140
|
+
* does not know (a newer DoFlow wrote them). The fold ignores them, so retention cannot tell what they change; a unit that
|
|
141
|
+
* one of them names by id is kept whole rather than deleted from under it. Exact matches only:
|
|
142
|
+
* keeping too little is a lost record, keeping too much only a file that stays.
|
|
143
|
+
* @returns {Set<string>|null} null when `shouldStop` fired
|
|
144
|
+
*/
|
|
145
|
+
function unknownTypeStrings(events , shouldStop ) {
|
|
146
|
+
const known = new Set (EVENT_TYPES);
|
|
147
|
+
const found = new Set ();
|
|
148
|
+
for (const event of events) {
|
|
149
|
+
if (shouldStop?.()) return null;
|
|
150
|
+
if (known.has(event.type)) continue;
|
|
151
|
+
const pending = [event.data];
|
|
152
|
+
while (pending.length > 0) {
|
|
153
|
+
if (shouldStop?.()) return null;
|
|
154
|
+
const value = pending.pop();
|
|
155
|
+
if (typeof value === 'string') found.add(value);
|
|
156
|
+
else if (Array.isArray(value)) pending.push(...value);
|
|
157
|
+
else if (value && typeof value === 'object') {
|
|
158
|
+
for (const key of Object.keys(value)) found.add(key);
|
|
159
|
+
pending.push(...Object.values(value));
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
return found;
|
|
164
|
+
}
|
|
165
|
+
|
|
116
166
|
function isSettled(state , key ) {
|
|
117
167
|
const [kind, id] = [key.slice(0, key.indexOf(':')), key.slice(key.indexOf(':') + 1)];
|
|
118
168
|
if (kind === 'followup') {
|
|
@@ -173,6 +223,12 @@ function selectExpired(events , { now, windowMs, shouldStop } = {})
|
|
|
173
223
|
}
|
|
174
224
|
const named = unitsOf(events, shouldStop);
|
|
175
225
|
if (!named || shouldStop?.()) return incomplete();
|
|
226
|
+
const mentioned = unknownTypeStrings(events, shouldStop);
|
|
227
|
+
if (!mentioned || shouldStop?.()) return incomplete();
|
|
228
|
+
// Selected exactly as 5bc9407b did (every settled, expired, unserved unit is a candidate), and only
|
|
229
|
+
// then are the units an unknown-type event names dropped. Protection removes units from that
|
|
230
|
+
// selection; it never changes which of the others are picked, so what is deleted is always a subset
|
|
231
|
+
// of what base would delete on the same events.
|
|
176
232
|
const candidates = [];
|
|
177
233
|
for (const unit of named) {
|
|
178
234
|
if (shouldStop?.()) return incomplete();
|
|
@@ -186,20 +242,28 @@ function selectExpired(events , { now, windowMs, shouldStop } = {})
|
|
|
186
242
|
if (eligible) candidates.push({ ...unit, newestAt: newest });
|
|
187
243
|
}
|
|
188
244
|
const full = finalize(state);
|
|
189
|
-
|
|
190
|
-
const
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
245
|
+
/** The refold-safe subset of `list`: all of it when that holds, else the oldest-first greedy pass. */
|
|
246
|
+
const safeSubset = (list ) => {
|
|
247
|
+
const allSafe = refoldEqual(events, full, list, now, shouldStop);
|
|
248
|
+
if (allSafe === null || shouldStop?.()) return null;
|
|
249
|
+
if (allSafe) return list;
|
|
250
|
+
const chosen = [];
|
|
251
|
+
const ordered = [...list].sort((a , b ) => Date.parse(a.newestAt) - Date.parse(b.newestAt));
|
|
252
|
+
if (shouldStop?.()) return null;
|
|
196
253
|
for (const unit of ordered) {
|
|
197
|
-
if (shouldStop?.()) return
|
|
198
|
-
const safe = refoldEqual(events, full, [...
|
|
199
|
-
if (safe === null) return
|
|
200
|
-
if (safe)
|
|
254
|
+
if (shouldStop?.()) return null;
|
|
255
|
+
const safe = refoldEqual(events, full, [...chosen, unit], now, shouldStop);
|
|
256
|
+
if (safe === null) return null;
|
|
257
|
+
if (safe) chosen.push(unit);
|
|
201
258
|
}
|
|
202
|
-
|
|
259
|
+
return chosen;
|
|
260
|
+
};
|
|
261
|
+
const baseSelected = safeSubset(candidates);
|
|
262
|
+
if (!baseSelected) return incomplete();
|
|
263
|
+
const unprotected = baseSelected.filter((unit ) => ![...unit.items].some((key ) => mentioned.has(key.slice(key.indexOf(':') + 1))));
|
|
264
|
+
// Dropping units can leave a set the refold no longer accepts; check it again, inside the subset.
|
|
265
|
+
const selected = unprotected.length === baseSelected.length ? baseSelected : safeSubset(unprotected);
|
|
266
|
+
if (!selected) return incomplete();
|
|
203
267
|
const units = [];
|
|
204
268
|
for (const unit of selected) {
|
|
205
269
|
if (shouldStop?.()) return incomplete();
|
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
const fs = require('node:fs');
|
|
11
11
|
const path = require('node:path');
|
|
12
12
|
const { execFileSync } = require('node:child_process');
|
|
13
|
+
const { memoGit } = require('../git-memo.js');
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
|
|
@@ -61,11 +62,18 @@ function showToplevel(cwd ) {
|
|
|
61
62
|
* @returns {string} an absolute, real path
|
|
62
63
|
*/
|
|
63
64
|
function projectRoot(cwd = process.cwd()) {
|
|
65
|
+
// Inside a call scope (git-memo.ts) the answer git gave for this path is kept for the call. The
|
|
66
|
+
// fallback `real(cwd)` is not git's answer, so it is never kept: the path may become a repository.
|
|
67
|
+
return memoGit(`projectRoot:${path.resolve(cwd)}`, () => gitProjectRoot(cwd)) ?? real(cwd);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** The root git names for `cwd`, or undefined when git names none (outside a repository). */
|
|
71
|
+
function gitProjectRoot(cwd ) {
|
|
64
72
|
let entries = [];
|
|
65
73
|
try { entries = worktreeEntries(git(cwd, ['worktree', 'list', '--porcelain'])); } catch { /* not a repository */ }
|
|
66
74
|
const first = entries[0];
|
|
67
75
|
if (first && !first.bare && isRealWorkTree(first)) return real(first.path);
|
|
68
|
-
return showToplevel(cwd) ||
|
|
76
|
+
return showToplevel(cwd) || undefined;
|
|
69
77
|
}
|
|
70
78
|
|
|
71
79
|
module.exports = { projectRoot, worktreeEntries };
|
|
@@ -11,19 +11,20 @@
|
|
|
11
11
|
* the release tags and the base tag) come from `do-git-state.sh --lifecycle`, so bash and Node never
|
|
12
12
|
* derive them twice. The rest is a fixed number of plain `git` calls, however many features are
|
|
13
13
|
* tracked: one `rev-parse` pins the ref, one `for-each-ref` lists the branches, one
|
|
14
|
-
* `git log --first-parent --merges` lists the merges
|
|
15
|
-
* release tags' commits, and one `git rev-list --parents`
|
|
16
|
-
* and those tags. Every ancestry question (which merge
|
|
17
|
-
* merge) is then answered from that graph in memory,
|
|
14
|
+
* `git log --first-parent --merges` lists the merges and their `DoFlow-Row` trailers, one
|
|
15
|
+
* `git tag --list` (tagged mode) gives the release tags' commits, and one `git rev-list --parents`
|
|
16
|
+
* reads the commit graph of the pinned ref and those tags. Every ancestry question (which merge
|
|
17
|
+
* brought a branch tip in, which tag contains a merge) is then answered from that graph in memory,
|
|
18
|
+
* not by a git call per feature.
|
|
18
19
|
*
|
|
19
20
|
* Nothing here writes, fetches or reaches a network.
|
|
20
21
|
*
|
|
21
22
|
* Accepted ceilings, stated rather than hidden: a feature's merge evidence is only as good as the
|
|
22
|
-
* merge commit's subject and the branch name (a squash, a rebase and a
|
|
23
|
-
* so they reach the user through `notDetected` and `lifecycle --action
|
|
24
|
-
* the graph are read in full, without `--since` or a lower cut (a skewed
|
|
25
|
-
* merge), so their cost grows with the history of the integration ref, once
|
|
26
|
-
* have one-second resolution, so the lower bound is floored to the second.
|
|
23
|
+
* merge commit's `DoFlow-Row` trailer or subject and the branch name (a squash, a rebase and a
|
|
24
|
+
* fast-forward leave none, so they reach the user through `notDetected` and `lifecycle --action
|
|
25
|
+
* merged`); the merge log and the graph are read in full, without `--since` or a lower cut (a skewed
|
|
26
|
+
* clock must not hide a merge), so their cost grows with the history of the integration ref, once
|
|
27
|
+
* per call; git dates have one-second resolution, so the lower bound is floored to the second.
|
|
27
28
|
*/
|
|
28
29
|
|
|
29
30
|
const fs = require('node:fs');
|
|
@@ -69,13 +70,18 @@ function subjectNames(subject , slug ) {
|
|
|
69
70
|
return new RegExp(`(?:^|[^a-z0-9-])${escapeRegExp(slug)}(?:$|[^a-z0-9-])`).test(subject);
|
|
70
71
|
}
|
|
71
72
|
|
|
72
|
-
/** First-parent merges of `ref`, oldest first. */
|
|
73
|
-
function readMerges(root , ref )
|
|
74
|
-
const out = git(root, ['log', '--first-parent', '--merges', '--format=%H%
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
73
|
+
/** First-parent merges of `ref`, oldest first, with any `DoFlow-Row` trailer. */
|
|
74
|
+
function readMerges(root , ref ) {
|
|
75
|
+
const out = git(root, ['log', '--first-parent', '--merges', '--format=%H%x00%P%x00%ct%x00%s%x00%(trailers:key=DoFlow-Row,separator=%x1f)%x00', ref]);
|
|
76
|
+
const fields = out.split('\0');
|
|
77
|
+
const merges = [];
|
|
78
|
+
for (let i = 0; i + 4 < fields.length; i += 5) {
|
|
79
|
+
const [rawSha, parents, ct, subject, rowTrailer] = fields.slice(i, i + 5);
|
|
80
|
+
const sha = rawSha.trim();
|
|
81
|
+
if (!sha) continue;
|
|
82
|
+
merges.push({ sha, parents: parents.split(' ').filter(Boolean), ct: Number(ct), subject, rowTrailer: rowTrailer.trim() });
|
|
83
|
+
}
|
|
84
|
+
return merges.reverse();
|
|
79
85
|
}
|
|
80
86
|
|
|
81
87
|
/** Branch refs by short name (`feat/x`, `origin/feat/x`) with their tips; local branches first. */
|
|
@@ -221,7 +227,18 @@ function findEvidence({ slug, lowerBound, merges, mergeBySha, newestCt, history,
|
|
|
221
227
|
if (merge) return { kind: 'branch', ref: tip.short, commit: merge.sha };
|
|
222
228
|
}
|
|
223
229
|
for (let i = merges.length - 1; i >= 0; i -= 1) {
|
|
224
|
-
|
|
230
|
+
const merge = merges[i];
|
|
231
|
+
if (merge.ct < lowerBound) continue;
|
|
232
|
+
if (merge.rowTrailer) {
|
|
233
|
+
// A present DoFlow-Row is authoritative: it can match only on an exact two-parent merge,
|
|
234
|
+
// and a mismatch must not fall through to a subject that names a different row.
|
|
235
|
+
const match = /^DoFlow-Row:\s*(.*?)\s*$/i.exec(merge.rowTrailer);
|
|
236
|
+
if (merge.parents.length === 2 && match && match[1] === slug) {
|
|
237
|
+
return { kind: 'trailer', ref: merge.sha.slice(0, 7), commit: merge.sha };
|
|
238
|
+
}
|
|
239
|
+
continue;
|
|
240
|
+
}
|
|
241
|
+
if (subjectNames(merge.subject, slug)) return { kind: 'merge-subject', ref: merge.sha.slice(0, 7), commit: merge.sha };
|
|
225
242
|
}
|
|
226
243
|
if (confirmed) return { kind: 'confirmed', ref: null, commit: null };
|
|
227
244
|
return null;
|
|
@@ -36,8 +36,18 @@ const LEGACY_EVENTS_REL = path.join(LEGACY_LIFECYCLE_REL, 'events');
|
|
|
36
36
|
const MARKER_REL = path.join(LIFECYCLE_REL, 'migrated.json');
|
|
37
37
|
const MIGRATING_PREFIX = 'events.migrating-';
|
|
38
38
|
const NOTICE = 'note: the lifecycle store is now .doflow/state/lifecycle/events; agent-docs/lifecycle/ is no longer read and can be deleted';
|
|
39
|
-
const removedLine = (count , hours ) => `retention: removed ${count} event files older than ${hours} h`;
|
|
40
|
-
|
|
39
|
+
const removedLine = (count , hours ) => `retention: removed ${count} event ${count === 1 ? 'file' : 'files'} older than ${hours} h`;
|
|
40
|
+
// A leading zero is rejected on purpose (`024` could be read as octal); the warning names it.
|
|
41
|
+
const invalidLine = (raw ) => `warning: DOFLOW_RETENTION_HOURS='${raw}' is not a positive whole number of hours${/^0[0-9]+$/.test(String(raw)) ? ' (no leading zeros)' : ''}; ignored, nothing removed`;
|
|
42
|
+
/**
|
|
43
|
+
* What `writeAtomic` leaves behind when a verb dies between creating its temp file and renaming it:
|
|
44
|
+
* `.retention-<pid>-<chars>.tmp` (the journal) and `.migrated-<pid>-<chars>.tmp` (the marker), in the
|
|
45
|
+
* lifecycle folder. The storage engine's work-store sweep matches `<name>.json.<pid>.tmp` inside
|
|
46
|
+
* record stores only, so nothing else ever removes them.
|
|
47
|
+
*/
|
|
48
|
+
const WRITE_TMP = /^\.(?:retention|migrated)-[0-9]+-[A-Za-z0-9]+\.tmp$/;
|
|
49
|
+
/** The age past which such a file is abandoned; the storage engine passes `settings.staleTmpHours`, and a test pins this to it. */
|
|
50
|
+
const STALE_TMP_MS = 24 * 3600000;
|
|
41
51
|
/** Bounds one pass, so its journal stays far below the reader's cap and its lock is held briefly. */
|
|
42
52
|
const MAX_PASS_FILES = 20000;
|
|
43
53
|
/** Unlinks between two refreshes of the lock's age. */
|
|
@@ -100,7 +110,8 @@ function fsyncCopy(fsImpl , file ) {
|
|
|
100
110
|
function fsyncFolder(fsImpl , dir ) {
|
|
101
111
|
let fd;
|
|
102
112
|
try { fd = fsImpl.openSync(dir, 'r'); } catch { return; }
|
|
103
|
-
|
|
113
|
+
// Windows opens a folder but refuses to flush it (EPERM); there is nothing to flush there.
|
|
114
|
+
try { fsImpl.fsyncSync(fd); } catch (error ) { if (!['EPERM', 'EINVAL', 'EISDIR', 'ENOTSUP'].includes(error?.code)) throw error; } finally { fsImpl.closeSync(fd); }
|
|
104
115
|
}
|
|
105
116
|
|
|
106
117
|
const migrationFailed = (code ) => ({
|
|
@@ -208,6 +219,30 @@ function refreshLock(fsImpl , root ) {
|
|
|
208
219
|
try { fsImpl.utimesSync(`${lockTarget(root)}.lock`, now, now); } catch { /* the lock folder is ours; a failed touch only shortens its life */ }
|
|
209
220
|
}
|
|
210
221
|
|
|
222
|
+
/**
|
|
223
|
+
* Removes temp files a crashed writer left in the lifecycle folder, once they are older than
|
|
224
|
+
* `staleMs`. Runs under the store lock, which every writer of these files holds, so none of them
|
|
225
|
+
* belongs to a live pass unless its lock was taken over as stale. Best effort: a file that cannot
|
|
226
|
+
* be removed is left for the next pass. `shouldStop` is checked before each unlink and each unlink
|
|
227
|
+
* counts against `maxFiles`, so a bounded caller's budget and stop hold here too.
|
|
228
|
+
* @returns {{removed: number, partial: boolean}} partial: stopped or out of budget with files left
|
|
229
|
+
*/
|
|
230
|
+
function sweepStaleTmp(fsImpl , root , now , staleMs = STALE_TMP_MS, { maxFiles = Infinity, shouldStop } = {}) {
|
|
231
|
+
const dir = path.join(root, LIFECYCLE_REL);
|
|
232
|
+
let list ;
|
|
233
|
+
try { list = namesIn(fsImpl, dir); } catch { return { removed: 0, partial: false }; }
|
|
234
|
+
let removed = 0;
|
|
235
|
+
for (const name of list) {
|
|
236
|
+
if (!WRITE_TMP.test(name)) continue;
|
|
237
|
+
const file = path.join(dir, name);
|
|
238
|
+
const st = lstatOrNull(fsImpl, file);
|
|
239
|
+
if (!st || !st.isFile() || now.getTime() - st.mtimeMs <= staleMs) continue;
|
|
240
|
+
if (shouldStop?.() || removed >= maxFiles) return { removed, partial: true };
|
|
241
|
+
try { fsImpl.unlinkSync(file); removed += 1; } catch { /* left for the next pass */ }
|
|
242
|
+
}
|
|
243
|
+
return { removed, partial: false };
|
|
244
|
+
}
|
|
245
|
+
|
|
211
246
|
/** Unlinks pending event files without clearing the journal until every hidden name is finished. */
|
|
212
247
|
function removePending(fsImpl , root , names , onRemoved = () => {}, { maxFiles = Infinity, shouldStop } = {}) {
|
|
213
248
|
const dir = path.join(root, EVENTS_REL);
|
|
@@ -294,7 +329,7 @@ function retain(fsImpl , root , window , now , { maxFiles = MAX_
|
|
|
294
329
|
|
|
295
330
|
/** Runs a bounded lifecycle retention pass under a non-waiting store lock for the storage engine. */
|
|
296
331
|
function retainLocked(root , options = {}) {
|
|
297
|
-
const { window, now = new Date(), fsImpl = nodeFs, maxFiles = MAX_PASS_FILES, shouldStop } = options;
|
|
332
|
+
const { window, now = new Date(), fsImpl = nodeFs, maxFiles = MAX_PASS_FILES, shouldStop, staleTmpMs = STALE_TMP_MS } = options;
|
|
298
333
|
const bounded = Object.hasOwn(options, 'maxFiles') || Object.hasOwn(options, 'shouldStop');
|
|
299
334
|
const outcome = (removed , warning , partial = false, selected = [], code ) => bounded
|
|
300
335
|
? { removed, warning, partial, selected, ...(code ? { code } : {}) }
|
|
@@ -306,7 +341,9 @@ function retainLocked(root , options = {}) {
|
|
|
306
341
|
const release = tryAcquireLock(fsImpl, lockTarget(root));
|
|
307
342
|
if (!release) return outcome(0, [], true);
|
|
308
343
|
try {
|
|
309
|
-
const
|
|
344
|
+
const swept = sweepStaleTmp(fsImpl, root, now, staleTmpMs, { maxFiles, shouldStop });
|
|
345
|
+
if (swept.partial) return outcome(0, [], true);
|
|
346
|
+
const result = retain(fsImpl, root, window, now, { maxFiles: Math.max(0, maxFiles - swept.removed), shouldStop });
|
|
310
347
|
return outcome(result.removedCount, result.warning, result.partial, result.selected, result.code);
|
|
311
348
|
} finally {
|
|
312
349
|
release();
|
|
@@ -326,7 +363,7 @@ function needsPrune(fsImpl , root , window , now ) {
|
|
|
326
363
|
* @param {string} root projectRoot() result
|
|
327
364
|
* @param {{env?: Object, now?: Date, fsImpl?: Object}} [options] defaults process.env, new Date(), node:fs
|
|
328
365
|
* @returns {{ok: true, lines: string[]}
|
|
329
|
-
* | {ok: false, finding: 'store-migration-failed'|'store-locked', message: string, lines: string[]}}
|
|
366
|
+
* | {ok: false, finding: 'store-migration-pending'|'store-migration-failed'|'store-locked', message: string, lines: string[]}}
|
|
330
367
|
* `lines` are for stderr, without the verb prefix.
|
|
331
368
|
* @throws {StoreUnsafeError} a symlinked new store folder, or a symlinked old folder when a copy must read it
|
|
332
369
|
*/
|
|
@@ -345,6 +382,17 @@ function prepareStore(root , { env = process.env, now = new Date(), fsImp
|
|
|
345
382
|
try { migrate = legacyEventFiles(fsImpl, root).length > 0; } catch (error ) { return legacyUnreadable(error.code || error.message); }
|
|
346
383
|
}
|
|
347
384
|
}
|
|
385
|
+
if (env.DOFLOW_UPKEEP === 'off') {
|
|
386
|
+
if (migrate) {
|
|
387
|
+
return {
|
|
388
|
+
ok: false,
|
|
389
|
+
finding: 'store-migration-pending',
|
|
390
|
+
message: 'the lifecycle store has not been copied yet; run any followup, lifecycle or goal command once without DOFLOW_UPKEEP=off. Nothing was written.',
|
|
391
|
+
lines: [],
|
|
392
|
+
};
|
|
393
|
+
}
|
|
394
|
+
return { ok: true, lines: [...notice, ...invalid] };
|
|
395
|
+
}
|
|
348
396
|
const retentionDue = readJournal(root, fsImpl).pending.length > 0 || needsPrune(fsImpl, root, window, now);
|
|
349
397
|
if (!migrate && !markerDue && !retentionDue) return { ok: true, lines: [...notice, ...invalid] };
|
|
350
398
|
if (migrate) refuseLinks(fsImpl, root, [LEGACY_LIFECYCLE_REL, LEGACY_EVENTS_REL]);
|
|
@@ -367,6 +415,7 @@ function prepareStore(root , { env = process.env, now = new Date(), fsImp
|
|
|
367
415
|
throw error;
|
|
368
416
|
}
|
|
369
417
|
try {
|
|
418
|
+
sweepStaleTmp(fsImpl, root, now);
|
|
370
419
|
// Decided again under the lock: another verb may have finished the copy while this one waited.
|
|
371
420
|
let marked = hasMarker(fsImpl, root);
|
|
372
421
|
if (!marked && storeHasEvents(fsImpl, root)) {
|
|
@@ -390,4 +439,4 @@ function prepareStore(root , { env = process.env, now = new Date(), fsImp
|
|
|
390
439
|
}
|
|
391
440
|
}
|
|
392
441
|
|
|
393
|
-
module.exports = { prepareStore, retainLocked, LEGACY_LIFECYCLE_REL, LEGACY_EVENTS_REL, MARKER_REL };
|
|
442
|
+
module.exports = { prepareStore, retainLocked, removedLine, STALE_TMP_MS, LEGACY_LIFECYCLE_REL, LEGACY_EVENTS_REL, MARKER_REL };
|
package/src/runtime/mask.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* the project store. Masking only narrows what a statement or an excerpt can carry.
|
|
9
9
|
*
|
|
10
10
|
* Two profiles. The line profile is for one-line statements and failure messages; the body profile
|
|
11
|
-
* is for report bodies and excerpts and runs every line rule plus the body-only rules 7 to 9.
|
|
11
|
+
* is for report bodies and excerpts and runs every line rule plus the body-only rules 7 to 9 and 14 to 17.
|
|
12
12
|
* Rules run in the table's order. Normalisation (rules 10 to 13) is a separate step applied to
|
|
13
13
|
* failure messages only, after masking.
|
|
14
14
|
*
|
|
@@ -36,7 +36,9 @@ const TOKEN_SHAPES = [
|
|
|
36
36
|
/xox[abprs]-[A-Za-z0-9-]{10,}/g,
|
|
37
37
|
/npm_[A-Za-z0-9]{24,}/g,
|
|
38
38
|
/AIza[0-9A-Za-z_-]{35}/g,
|
|
39
|
-
|
|
39
|
+
// Left-anchored (DEC-069): without it every `eyJ` inside a long dotless run starts a scan to the
|
|
40
|
+
// run's end, which makes masking one line quadratic in its length.
|
|
41
|
+
/(?<![A-Za-z0-9_-])eyJ[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+/g,
|
|
40
42
|
];
|
|
41
43
|
|
|
42
44
|
const PEM = /-----BEGIN [^\n-]*-----(?:[\s\S]*?-----END [^\n-]*-----|[\s\S]*$)/g;
|
|
@@ -52,13 +54,107 @@ const KEY_VALUE_BODY = new RegExp(`(${KEY_NAME}["']?[ \\t]*[=:][ \\t]*)("[^"\\r\
|
|
|
52
54
|
const AUTH_HEADER = /((?:Proxy-)?Authorization:)[ \t]*([^\r\n]*)/gi;
|
|
53
55
|
const SECRET_FLAG = /(--(?:password|passwd|token|secret|api-key|client-secret)(?:=|[ \t]+))("[^"\r\n]*"|'[^'\r\n]*'|[^\s"']+)/gi;
|
|
54
56
|
|
|
57
|
+
// Rules 14 to 17 (body profile only, FU-ht8b3r). Command shapes that carry a secret without a key
|
|
58
|
+
// word: `curl -u user:pw` (also glued, `-uuser:pw`), `mysql -psecret` (value glued to the flag; a bare
|
|
59
|
+
// `-p` prompts and carries none), and the phrase `password hunter2`. The flags are masked by one
|
|
60
|
+
// left-to-right scan (rules 14 and 15, `maskCommandFlags`) that knows which command it is in, so every
|
|
61
|
+
// flag of a command is masked and `sort -u`, `git push -u` and `mysql -P 3306` stay as written.
|
|
62
|
+
// A command word counts only at command position (see `maskCommandFlags`), and quoted text is scanned,
|
|
63
|
+
// not skipped. Alternatives, in order: a quote, a command break (`|`, `;`, `&`, parentheses, a
|
|
64
|
+
// backtick, a line end), curl's `--user` and `-u` (the short one may be glued to its value), mysql's
|
|
65
|
+
// `-p` glued to its value, any other word.
|
|
66
|
+
const COMMAND_SCAN = /(?<quote>["'])|(?<brk>[|;&()`\r\n])|(?<=[ \t])(?:(?<long>--user)(?:=|[ \t]+)|(?<short>-u)(?:=|[ \t]*)(?=[^\s|;&]))|(?<=[ \t])(?<pass>-p)(?=[^\s|;&-])|(?<word>[^\s|;&()`"']+)/g;
|
|
67
|
+
// The closing quote of a quoted span, on the same line.
|
|
68
|
+
const QUOTE_CLOSE = { '"': /[^"\r\n]*"/y, "'": /[^'\r\n]*'/y };
|
|
69
|
+
// A word that is the command: bare or as a path (never a URL, which holds `:`).
|
|
70
|
+
const COMMAND_WORD = /^(?:(?:\.{0,2}|~)?\/(?:[^\s:]*\/)?)?(?:(curl)|(mysql(?:dump|admin|pump|import)?))$/;
|
|
71
|
+
// Words after which the next word is the command: wrappers, remote and container runners, shell keywords.
|
|
72
|
+
const WRAPPER_WORDS = new Set(['sudo', 'doas', 'su', 'env', 'exec', 'nohup', 'time', 'timeout', 'nice', 'ionice', 'xargs', 'watch',
|
|
73
|
+
'command', 'builtin', 'eval', 'setsid', 'stdbuf', 'flock', 'chroot', 'ssh', 'docker', 'podman', 'kubectl', 'sh', 'bash', 'zsh', 'dash', 'ksh',
|
|
74
|
+
'if', 'then', 'else', 'elif', 'do', 'while', 'until', '!', '{']);
|
|
75
|
+
// A first word that does not use up the command position: a prompt mark, a variable assignment or a
|
|
76
|
+
// label such as `Running:`.
|
|
77
|
+
const PROMPT_OR_ASSIGNMENT = /^(?:[$#%>+]+|[A-Za-z_][A-Za-z0-9_]*=[^=]*|[A-Za-z][A-Za-z0-9_-]*:)$/;
|
|
78
|
+
// A flag value: quoted, or unquoted up to a space, quote or command break.
|
|
79
|
+
const COMMAND_FLAG_VALUE = /"[^"\r\n]*"|'[^'\r\n]*'|[^\s"'|;&]+/y;
|
|
80
|
+
// The value after the phrase must hold a non-letter, as in rule 4, so ordinary prose ("the password is
|
|
81
|
+
// required") survives; a plain-letter value after a bare `password ` is a known gap, and so is a
|
|
82
|
+
// password of one to three digits (`reset password 2 times` is prose).
|
|
83
|
+
const PASSWORD_PHRASE = /(?<![A-Za-z0-9_.-])((?:password|passwd)[ \t]+)(?!<masked>|[=:])([^\s,;&"'`)\]}]+)/gi;
|
|
84
|
+
// Another user's home directory (the running user's is rule 0). `Shared` is not a person and `runner`
|
|
85
|
+
// is the CI runner account (`/Users/runner/work/...`), so neither is masked.
|
|
86
|
+
const OTHER_USER_HOME = /(?<![A-Za-z0-9_.-])\/Users\/(?!(?:Shared|runner)(?![^/\s"'`<>:;,()[\]{}])|<)[^/\s"'`<>:;,()[\]{}]+/g;
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Rules 14 and 15. Scans left to right and masks the value of every `-u`/`--user` of a curl command
|
|
90
|
+
* and every `-p<value>` of a mysql command. A command word (`curl`, `mysql*`) is the command of its
|
|
91
|
+
* segment only at command position: the first word of the text, of a line or of a segment after a
|
|
92
|
+
* break (`|`, `;`, `&`, a parenthesis, a backtick), of a quoted span, or after a wrapper word
|
|
93
|
+
* (`sudo`, `env`, `ssh`, `docker`, `sh`, `bash`, ...) at the start of the segment. A word that holds `:`
|
|
94
|
+
* (a URL) or is followed by more of its token (`mysql-8`) is not the command. A segment has one
|
|
95
|
+
* command. Quoted text is scanned: its start is a command position, and a break inside it ends only
|
|
96
|
+
* the command inside it, so `curl -H "a|b" -u x:y` keeps its command. One pass; every step consumes
|
|
97
|
+
* at least one character and a closing quote is searched on its own line, so the cost is linear.
|
|
98
|
+
*/
|
|
99
|
+
function maskCommandFlags(text ) {
|
|
100
|
+
let out = '';
|
|
101
|
+
let copied = 0;
|
|
102
|
+
let command = null;
|
|
103
|
+
let atStart = true; // the next word is the first of its segment
|
|
104
|
+
let wrapped = false; // the segment began with a wrapper word
|
|
105
|
+
const spans = [];
|
|
106
|
+
COMMAND_SCAN.lastIndex = 0;
|
|
107
|
+
let found ;
|
|
108
|
+
while ((found = COMMAND_SCAN.exec(text)) !== null) {
|
|
109
|
+
const groups = found.groups || {};
|
|
110
|
+
const end = found.index + found[0].length;
|
|
111
|
+
if (groups.quote) {
|
|
112
|
+
const top = spans[spans.length - 1];
|
|
113
|
+
if (top && top.quote === groups.quote) {
|
|
114
|
+
spans.pop();
|
|
115
|
+
({ command, atStart, wrapped } = top);
|
|
116
|
+
atStart = false;
|
|
117
|
+
} else {
|
|
118
|
+
const close = QUOTE_CLOSE[groups.quote];
|
|
119
|
+
close.lastIndex = end;
|
|
120
|
+
if (close.test(text)) {
|
|
121
|
+
spans.push({ quote: groups.quote, command, atStart, wrapped });
|
|
122
|
+
command = null; atStart = true; wrapped = false;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
} else if (groups.brk) {
|
|
126
|
+
if (!(groups.brk === '&' && /[<>]/.test(text[found.index - 1] || ''))) { command = null; atStart = true; wrapped = false; }
|
|
127
|
+
} else if (groups.word) {
|
|
128
|
+
const word = groups.word;
|
|
129
|
+
if (atStart && PROMPT_OR_ASSIGNMENT.test(word)) continue;
|
|
130
|
+
const named = COMMAND_WORD.exec(word);
|
|
131
|
+
if (command === null && (atStart || wrapped) && named) command = named[1] ? 'curl' : 'mysql';
|
|
132
|
+
else if (atStart) wrapped = WRAPPER_WORDS.has(word.slice(word.lastIndexOf('/') + 1));
|
|
133
|
+
atStart = false;
|
|
134
|
+
} else if (((groups.long || groups.short) && command === 'curl') || (groups.pass && command === 'mysql')) {
|
|
135
|
+
atStart = false;
|
|
136
|
+
COMMAND_FLAG_VALUE.lastIndex = end;
|
|
137
|
+
const value = COMMAND_FLAG_VALUE.exec(text);
|
|
138
|
+
if (value && !isMasked(value[0]) && !(command === 'mysql' && value[0].startsWith(MASKED))) {
|
|
139
|
+
out += text.slice(copied, end) + MASKED;
|
|
140
|
+
copied = end + value[0].length;
|
|
141
|
+
COMMAND_SCAN.lastIndex = copied;
|
|
142
|
+
}
|
|
143
|
+
} else {
|
|
144
|
+
atStart = false; // another command's flag: rescan what follows it
|
|
145
|
+
COMMAND_SCAN.lastIndex = end;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return out + text.slice(copied);
|
|
149
|
+
}
|
|
150
|
+
|
|
55
151
|
function escapeRegExp(text ) { return text.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); }
|
|
56
152
|
|
|
57
153
|
/** The home directory to abbreviate: an absolute path other than the file system root. */
|
|
58
154
|
function homeOf(options ) {
|
|
59
155
|
const raw = options && 'home' in options ? options.home : process.env.HOME;
|
|
60
|
-
if (typeof raw !== 'string' || !raw.startsWith('/')) return null;
|
|
61
|
-
const home = raw.replace(
|
|
156
|
+
if (typeof raw !== 'string' || !(raw.startsWith('/') || /^[A-Za-z]:[\\/]/.test(raw))) return null;
|
|
157
|
+
const home = raw.replace(/[\\/]+$/, '');
|
|
62
158
|
return home.length > 1 ? home : null;
|
|
63
159
|
}
|
|
64
160
|
|
|
@@ -89,6 +185,30 @@ function mask40(run , before ) {
|
|
|
89
185
|
looksLikeSecretRun(value, 1) && !isPathRun(value, index === 0 ? before : run[index - 1]) ? MASKED : value));
|
|
90
186
|
}
|
|
91
187
|
|
|
188
|
+
const TS_TYPE_NAMES = new Set([
|
|
189
|
+
'string', 'number', 'boolean', 'any', 'unknown', 'void',
|
|
190
|
+
'never', 'null', 'undefined', 'symbol', 'bigint',
|
|
191
|
+
]);
|
|
192
|
+
|
|
193
|
+
function isCodePreserved(head , value , offset , fullString ) {
|
|
194
|
+
if (value.startsWith('"') || value.startsWith("'")) return false;
|
|
195
|
+
const isColon = /:[ \t]*$/.test(head);
|
|
196
|
+
if (isColon) {
|
|
197
|
+
const baseType = value.replace(/\[\s*\]?$|\[$/, '');
|
|
198
|
+
if (TS_TYPE_NAMES.has(baseType)) return true;
|
|
199
|
+
if (/^[A-Z]|^\[/.test(value)) return true;
|
|
200
|
+
if (/^(?:new\s|[A-Za-z0-9_$]+\s*\(|[A-Za-z0-9_$]+\.[A-Za-z0-9_$]+)/.test(value)) return true;
|
|
201
|
+
}
|
|
202
|
+
const isEquals = /=[ \t]*$/.test(head);
|
|
203
|
+
if (isEquals) {
|
|
204
|
+
const before = fullString.slice(Math.max(0, offset - 30), offset);
|
|
205
|
+
if (/(?:const|let|var|readonly)\s+$/.test(before)) return true;
|
|
206
|
+
if (/\bnew\s|[A-Za-z0-9_$]+\.[A-Za-z0-9_$]+|\(/.test(value)) return true;
|
|
207
|
+
if (/^[\[{]/.test(value)) return true;
|
|
208
|
+
}
|
|
209
|
+
return false;
|
|
210
|
+
}
|
|
211
|
+
|
|
92
212
|
function isMasked(value ) { return value.replace(/^["']|["']$/g, '') === MASKED; }
|
|
93
213
|
|
|
94
214
|
function count(text , token ) { return text.split(token).length - 1; }
|
|
@@ -119,8 +239,8 @@ function mask(text , profile , options = {})
|
|
|
119
239
|
// 3. Bearer tokens and URL credentials.
|
|
120
240
|
out = out.replace(BEARER, `$1$2${MASKED}`).replace(URL_CREDENTIALS, `://${MASKED}@`);
|
|
121
241
|
// 4. Key=value, unquoted, when the value is not plain letters.
|
|
122
|
-
out = out.replace(KEY_VALUE_LINE, (match , key , separator , value ) => (
|
|
123
|
-
isMasked(value) || value.startsWith(MASKED) || !/[^A-Za-z]/.test(value) ? match : `${key}${separator}${MASKED}`));
|
|
242
|
+
out = out.replace(KEY_VALUE_LINE, (match , key , separator , value , offset , fullString ) => (
|
|
243
|
+
isMasked(value) || value.startsWith(MASKED) || !/[^A-Za-z]/.test(value) || isCodePreserved(key + separator, value, offset, fullString) ? match : `${key}${separator}${MASKED}`));
|
|
124
244
|
// 5. Long secret-looking runs that are not paths.
|
|
125
245
|
out = out.replace(LONG_RUN, (run , offset ) => (
|
|
126
246
|
looksLikeSecretRun(run, 2) && !isPathRun(run, out[offset - 1]) ? MASKED : mask40(run, out[offset - 1])));
|
|
@@ -129,8 +249,9 @@ function mask(text , profile , options = {})
|
|
|
129
249
|
|
|
130
250
|
if (profile === 'body') {
|
|
131
251
|
// 7. Key and value, quoted or not, whatever the value looks like.
|
|
132
|
-
out = out.replace(KEY_VALUE_BODY, (match , head , value ) => {
|
|
252
|
+
out = out.replace(KEY_VALUE_BODY, (match , head , value , offset , fullString ) => {
|
|
133
253
|
if (isMasked(value) || value.startsWith(MASKED)) return match;
|
|
254
|
+
if (isCodePreserved(head, value, offset, fullString)) return match;
|
|
134
255
|
const quote = value[0] === '"' || value[0] === "'" ? value[0] : '';
|
|
135
256
|
return `${head}${quote}${MASKED}${quote}`;
|
|
136
257
|
});
|
|
@@ -138,6 +259,12 @@ function mask(text , profile , options = {})
|
|
|
138
259
|
out = out.replace(AUTH_HEADER, (match , name , rest ) => (rest === '' || rest === MASKED ? match : `${name} ${MASKED}`));
|
|
139
260
|
// 9. Secret-bearing command-line flags.
|
|
140
261
|
out = out.replace(SECRET_FLAG, (match , flag , value ) => (isMasked(value) ? match : `${flag}${MASKED}`));
|
|
262
|
+
// 14 and 15. `curl -u user:pw`, `curl --user user:pw`, `curl -uuser:pw` and `mysql -psecret`, every flag of a command.
|
|
263
|
+
out = maskCommandFlags(out);
|
|
264
|
+
// 16. `password hunter2`: the key word, a space and a value with a non-letter in it.
|
|
265
|
+
out = out.replace(PASSWORD_PHRASE, (match , head , value ) => (/[^A-Za-z]/.test(value) && !/^[0-9]{1,3}$/.test(value) ? `${head}${MASKED}` : match));
|
|
266
|
+
// 17. Another user's home directory: `/Users/<name>` becomes `/Users/<masked>`.
|
|
267
|
+
out = out.replace(OTHER_USER_HOME, `/Users/${MASKED}`);
|
|
141
268
|
}
|
|
142
269
|
|
|
143
270
|
const masked = Math.max(0, [MASKED, EMAIL, IP].reduce((sum, token) => sum + count(out, token) - count(input, token), 0));
|