forge-workflow 0.0.10 → 0.1.0-beta.2
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/.claude/rules/{greptile-review-process.md → review-process.md} +56 -41
- package/.claude/scripts/{greptile-resolve.sh → review-resolve.sh} +13 -3
- package/.cursor/rules/permissions-guidance.mdc +2 -2
- package/.forge/hooks/check-tdd.js +3 -0
- package/.forge/hooks/forge-native-hook.js +245 -0
- package/.forge/protected-paths.yaml +157 -0
- package/AGENTS.md +150 -61
- package/CHANGELOG.md +681 -0
- package/CLAUDE.md +9 -118
- package/QUICKSTART.md +171 -0
- package/README.md +271 -363
- package/bin/forge-cmd.js +120 -9
- package/bin/forge-preflight.js +26 -5
- package/bin/forge.js +461 -489
- package/docs/INDEX.md +93 -0
- package/docs/PROJECT_DESIGN.md +685 -0
- package/docs/architecture/index.md +66 -0
- package/docs/architecture/notes/README.md +35 -0
- package/docs/architecture/subsystems/README.md +46 -0
- package/docs/forge/TOOLCHAIN.md +670 -0
- package/docs/forge/VALIDATION.md +82 -0
- package/docs/{AGENT_INSTALL_PROMPT.md → guides/AGENT_INSTALL_PROMPT.md} +3 -3
- package/docs/guides/BEADS_GITHUB_SYNC.md +32 -0
- package/docs/{ENHANCED_ONBOARDING.md → guides/ENHANCED_ONBOARDING.md} +16 -12
- package/docs/guides/GREPTILE_SETUP.md +46 -0
- package/docs/guides/MANUAL_REVIEW_GUIDE.md +58 -0
- package/docs/guides/MIGRATION.md +56 -0
- package/docs/guides/SETUP.md +118 -0
- package/docs/guides/SUPPORT.md +185 -0
- package/docs/guides/WORKFLOW_TEMPLATES.md +74 -0
- package/docs/guides/memory-backends.md +183 -0
- package/docs/reference/ADAPTERS.md +128 -0
- package/docs/reference/AGENT_SKILL_PARITY.md +175 -0
- package/docs/reference/COMMANDS.md +205 -0
- package/docs/reference/DECISION_DRIFT_GUARDS.md +97 -0
- package/docs/{EXAMPLES.md → reference/EXAMPLES.md} +7 -5
- package/docs/reference/FORGE_KERNEL_STORAGE_MODEL.md +135 -0
- package/docs/reference/HERMES_INTEGRATION.md +118 -0
- package/docs/reference/INSIGHTS_RECAP.md +63 -0
- package/docs/reference/INSTALL.md +164 -0
- package/docs/reference/KERNEL_TAXONOMY_VALIDATION.md +161 -0
- package/docs/reference/PROTECTED_PATH_MANIFEST.md +25 -0
- package/docs/reference/RELEASE.md +68 -0
- package/docs/reference/RESEARCH_TEMPLATE.md +292 -0
- package/docs/{ROADMAP.md → reference/ROADMAP.md} +12 -9
- package/docs/reference/SKILLS.md +35 -0
- package/docs/reference/STATUS_BOARD.md +80 -0
- package/docs/reference/TEMPLATES.md +106 -0
- package/docs/{TOOLCHAIN.md → reference/TOOLCHAIN.md} +62 -47
- package/docs/reference/VALIDATION.md +82 -0
- package/docs/reference/agent-permissions.md +169 -0
- package/docs/reference/beads-to-kernel-migration-ux.md +61 -0
- package/docs/reference/control-plane-guarantees.md +125 -0
- package/docs/reference/dependency-chain.md +331 -0
- package/docs/reference/forge-kernel-issue-command-contract.md +161 -0
- package/docs/reference/forge-kernel-schema.md +72 -0
- package/docs/reference/kernel-conflict-evaluators.md +27 -0
- package/docs/reference/patch-md-format.md +77 -0
- package/docs/reference/protected-state-surfaces.md +59 -0
- package/docs/reference/shepherd.md +115 -0
- package/docs/reference/superpowers-analysis.md +320 -0
- package/docs/reference/superpowers-integration-options.md +404 -0
- package/docs/reference/test-environment.md +519 -0
- package/docs/reference/upgrade-safety.md +59 -0
- package/lefthook.yml +18 -0
- package/lib/adapter-cli.js +307 -0
- package/lib/adapters/beads-issue-adapter.js +127 -0
- package/lib/adapters/beads-kernel-compat.js +1042 -0
- package/lib/adapters/greptile-review-adapter.js +141 -0
- package/lib/adapters/kernel-issue-adapter.js +101 -0
- package/lib/adapters/pr-state-adapter.js +484 -0
- package/lib/adoption-profiles.js +126 -0
- package/lib/agents/README.md +2 -6
- package/lib/agents/claude.plugin.json +3 -8
- package/lib/agents/codex.plugin.json +9 -1
- package/lib/agents/cursor.plugin.json +2 -6
- package/lib/agents/hermes.plugin.json +22 -0
- package/lib/agents-config.js +39 -1236
- package/lib/audit-evidence.js +282 -0
- package/lib/beads-setup.js +121 -0
- package/lib/beads-sync-scaffold.js +25 -101
- package/lib/codex-skills.js +51 -1
- package/lib/commands/_issue.js +741 -77
- package/lib/commands/_manifest.js +91 -0
- package/lib/commands/_registry.js +85 -34
- package/lib/commands/_resolve-command-opts.js +261 -0
- package/lib/commands/_serve-security.js +270 -0
- package/lib/commands/adapter.js +12 -0
- package/lib/commands/add.js +118 -0
- package/lib/commands/audit.js +70 -0
- package/lib/commands/blocked.js +5 -0
- package/lib/commands/board.js +64 -0
- package/lib/commands/claim.js +21 -2
- package/lib/commands/claims.js +7 -0
- package/lib/commands/clean.js +485 -75
- package/lib/commands/close.js +2 -2
- package/lib/commands/comment.js +5 -0
- package/lib/commands/control.js +148 -0
- package/lib/commands/create.js +2 -2
- package/lib/commands/dev.js +185 -7
- package/lib/commands/doc-gate.js +336 -0
- package/lib/commands/doctor.js +156 -0
- package/lib/commands/explain.js +15 -0
- package/lib/commands/export.js +237 -0
- package/lib/commands/gate.js +192 -0
- package/lib/commands/hooks.js +242 -0
- package/lib/commands/inbox.js +118 -0
- package/lib/commands/init.js +598 -0
- package/lib/commands/insights.js +79 -0
- package/lib/commands/issue.js +12 -1
- package/lib/commands/issues.js +17 -0
- package/lib/commands/lint.js +5 -0
- package/lib/commands/list.js +2 -2
- package/lib/commands/merge.js +312 -0
- package/lib/commands/migrate.js +523 -0
- package/lib/commands/new.js +12 -0
- package/lib/commands/options.js +241 -0
- package/lib/commands/orient.js +13 -0
- package/lib/commands/orphans.js +5 -0
- package/lib/commands/patch.js +67 -0
- package/lib/commands/plan.js +436 -24
- package/lib/commands/preflight.js +211 -0
- package/lib/commands/prime.js +13 -0
- package/lib/commands/push.js +69 -2
- package/lib/commands/ready.js +2 -2
- package/lib/commands/recall.js +116 -0
- package/lib/commands/recap.js +61 -0
- package/lib/commands/recommend.js +0 -1
- package/lib/commands/release.js +91 -0
- package/lib/commands/remember.js +74 -0
- package/lib/commands/role.js +99 -0
- package/lib/commands/serve.js +581 -0
- package/lib/commands/setup.js +838 -972
- package/lib/commands/shepherd.js +436 -0
- package/lib/commands/ship.js +23 -1
- package/lib/commands/show.js +2 -2
- package/lib/commands/stage.js +192 -0
- package/lib/commands/stale.js +5 -0
- package/lib/commands/status.js +158 -21
- package/lib/commands/sync.js +34 -46
- package/lib/commands/team.js +4 -1
- package/lib/commands/test.js +43 -27
- package/lib/commands/update.js +2 -2
- package/lib/commands/upgrade.js +47 -0
- package/lib/commands/validate.js +43 -18
- package/lib/commands/worktree.js +307 -100
- package/lib/config-writer.js +202 -0
- package/lib/control-plane.js +236 -0
- package/lib/core/runtime-graph.js +946 -0
- package/lib/dep-guard/keyword-ripple.js +2 -2
- package/lib/deprecated-sync-cleanup.js +362 -0
- package/lib/detect-agent.js +2 -28
- package/lib/detect-worktree.js +35 -9
- package/lib/doc-gate/declaration.js +177 -0
- package/lib/doc-gate/detect.js +289 -0
- package/lib/doc-gate/gate.js +375 -0
- package/lib/doc-gate/okf-config.js +128 -0
- package/lib/doc-gate/okf.js +429 -0
- package/lib/docs-command.js +1161 -6
- package/lib/forge-issues.js +382 -11
- package/lib/forge-lock.js +262 -0
- package/lib/gate-events.js +193 -0
- package/lib/global-flags.js +74 -0
- package/lib/greptile-match.js +7 -63
- package/lib/harness-capability-matrix.js +380 -0
- package/lib/hook-global-installer.js +347 -0
- package/lib/hook-renderer.js +451 -0
- package/lib/inbox.js +391 -0
- package/lib/insights.js +397 -0
- package/lib/issue-adapter.js +156 -0
- package/lib/issue-backend.js +145 -0
- package/lib/issue-render.js +220 -0
- package/lib/kernel/backing-issue.js +305 -0
- package/lib/kernel/broker.js +1218 -0
- package/lib/kernel/cli-broker-factory.js +130 -0
- package/lib/kernel/conflict-signal.js +82 -0
- package/lib/kernel/evaluators.js +195 -0
- package/lib/kernel/fs-class.js +495 -0
- package/lib/kernel/issue-command-contract.js +559 -0
- package/lib/kernel/issue-id-resolver.js +186 -0
- package/lib/kernel/lease-enforcer.js +158 -0
- package/lib/kernel/migrations.js +333 -0
- package/lib/kernel/planning-buckets-schema.js +109 -0
- package/lib/kernel/projection-jsonl-writer.js +450 -0
- package/lib/kernel/readiness-model.js +329 -0
- package/lib/kernel/schema.js +356 -0
- package/lib/kernel/sqlite-driver.js +2504 -0
- package/lib/kernel/taxonomy-validator.js +394 -0
- package/lib/lefthook-check.js +3 -2
- package/lib/lefthook-wiring.js +413 -0
- package/lib/mcp-config-renderer.js +288 -0
- package/lib/memory/graphiti-mcp.js +106 -0
- package/lib/memory/router.js +387 -0
- package/lib/memory/typed-api.js +102 -0
- package/lib/memory-digest.js +195 -0
- package/lib/merge-rules.js +395 -0
- package/lib/migrate-dry-run.js +466 -0
- package/lib/orientation.js +863 -0
- package/lib/package-manager-remediation.js +103 -0
- package/lib/package-root.js +381 -0
- package/lib/patch-intent.js +890 -0
- package/lib/plugin-catalog.js +3 -4
- package/lib/plugin-manager.js +0 -5
- package/lib/pr-bundle.js +186 -0
- package/lib/pr-monitor/differ.js +195 -0
- package/lib/pr-monitor/events.js +0 -0
- package/lib/pr-monitor/gather.js +124 -0
- package/lib/pr-monitor/journal.js +299 -0
- package/lib/pr-monitor/monitor.js +146 -0
- package/lib/pr-monitor/render-sticky.js +157 -0
- package/lib/pr-monitor/watch-lifecycle.js +95 -0
- package/lib/pr-monitor/watch.js +247 -0
- package/lib/pr-pull.js +1273 -0
- package/lib/pr-shepherd.js +494 -0
- package/lib/pr-state-validator.js +59 -0
- package/lib/preflight/gates.js +237 -0
- package/lib/preflight/runner.js +116 -0
- package/lib/project-discovery.js +0 -53
- package/lib/project-memory.js +99 -497
- package/lib/protected-path-manifest.js +281 -0
- package/lib/protected-state-surfaces.js +387 -0
- package/lib/release-readiness.js +2089 -0
- package/lib/reset.js +59 -45
- package/lib/review-adapter.js +68 -0
- package/lib/rules-sync.js +260 -0
- package/lib/runtime-health.js +241 -20
- package/lib/safety-config-renderer.js +268 -0
- package/lib/setup-action-log.js +1 -7
- package/lib/setup.js +27 -65
- package/lib/shell-utils.js +76 -6
- package/lib/skills-sync.js +330 -0
- package/lib/smart-status/scoring.js +17 -3
- package/lib/status/beads-snapshot.js +45 -2
- package/lib/status/presenter.js +169 -18
- package/lib/status/snapshot.js +186 -0
- package/lib/sync-backend.js +202 -0
- package/lib/untrusted-content.js +52 -0
- package/lib/upgrade-safety.js +199 -0
- package/lib/workflow/enforce-stage.js +296 -47
- package/lib/workflow/stage-transition.js +115 -0
- package/lib/workflow/stages.js +30 -6
- package/lib/workflow/state-manager.js +11 -22
- package/lib/workflow/state.js +23 -1
- package/lib/workflow-profiles.js +17 -5
- package/package.json +37 -35
- package/rules/documentation.md +19 -0
- package/rules/kernel-tracking.md +26 -0
- package/rules/security.md +22 -0
- package/rules/tdd.md +20 -0
- package/rules/workflow.md +27 -0
- package/scripts/auto-backing-issue.js +47 -0
- package/scripts/beads-context.sh +81 -57
- package/scripts/beads-upgrade-smoke.sh +24 -3
- package/scripts/bootstrap-windows-tools.sh +78 -0
- package/scripts/branch-protection.js +2 -3
- package/scripts/check-agents.js +34 -137
- package/scripts/commitlint.js +3 -1
- package/scripts/conflict-detect.sh +3 -0
- package/scripts/dep-guard.sh +22 -3
- package/scripts/file-index.sh +3 -0
- package/scripts/forge-team/lib/claim.sh +34 -18
- package/scripts/forge-team/lib/dashboard.sh +61 -86
- package/scripts/forge-team/lib/epic.sh +99 -263
- package/scripts/forge-team/lib/hooks.sh +26 -28
- package/scripts/forge-team/lib/identity.sh +4 -4
- package/scripts/forge-team/lib/sync-github.sh +49 -84
- package/scripts/forge-team/lib/verify.sh +93 -83
- package/scripts/forge-team/lib/workload.sh +41 -65
- package/scripts/forge-team/tests/claim.test.sh +25 -19
- package/scripts/forge-team/tests/dashboard.test.sh +31 -46
- package/scripts/forge-team/tests/epic.test.sh +52 -71
- package/scripts/forge-team/tests/hooks.test.sh +38 -50
- package/scripts/forge-team/tests/identity.test.sh +3 -3
- package/scripts/forge-team/tests/integration.test.sh +44 -66
- package/scripts/forge-team/tests/sync-github.test.sh +50 -83
- package/scripts/forge-team/tests/verify.test.sh +37 -46
- package/scripts/forge-team/tests/workflow-integration.test.sh +4 -4
- package/scripts/forge-team/tests/workload.test.sh +32 -66
- package/scripts/gen-command-manifest.js +153 -0
- package/scripts/gen-embedded-assets.mjs +129 -0
- package/scripts/install.ps1 +139 -0
- package/scripts/install.sh +268 -0
- package/scripts/lib/release-asset.mjs +84 -0
- package/scripts/parity-check.mjs +145 -0
- package/scripts/parity-check.test.mjs +58 -0
- package/scripts/pin-agentic-workflow-images.js +112 -0
- package/scripts/pr-coordinator.sh +3 -0
- package/scripts/preflight-sonar.eslint.config.mjs +44 -0
- package/scripts/preflight.sh +21 -94
- package/scripts/protected-state-check.js +104 -0
- package/scripts/smart-status.sh +60 -57
- package/scripts/spikes/config-race-bench.js +111 -0
- package/scripts/spikes/harness-capability-matrix.js +13 -0
- package/scripts/spikes/patch-anchor-stability-bench.js +125 -0
- package/scripts/spikes/protected-path-manifest.js +20 -0
- package/scripts/spikes/skill-auto-invoke-parity.js +292 -0
- package/scripts/sync-agent-skills.js +62 -0
- package/scripts/sync-utils.sh +3 -0
- package/scripts/test-ci-shard.js +13 -6
- package/scripts/test.js +95 -12
- package/skills/claim-safety/SKILL.md +102 -0
- package/skills/claim-safety/evals/evals.json +46 -0
- package/{.github/prompts/dev.prompt.md → skills/dev/SKILL.md} +44 -50
- package/skills/dev/evals/evals.json +50 -0
- package/skills/hermes-forge/SKILL.md +185 -0
- package/skills/hermes-forge/evals/evals.json +46 -0
- package/skills/issue-basics/SKILL.md +111 -0
- package/skills/issue-basics/evals/evals.json +46 -0
- package/skills/kernel/SKILL.md +166 -0
- package/skills/kernel/evals/evals.json +50 -0
- package/skills/memory/SKILL.md +102 -0
- package/skills/parallel-deep-research/SKILL.md +14 -11
- package/skills/parallel-deep-research/evals/evals.json +11 -27
- package/{.github/prompts/plan.prompt.md → skills/plan/SKILL.md} +132 -157
- package/skills/plan/evals/evals.json +42 -0
- package/skills/research/SKILL.md +195 -0
- package/skills/research/evals/evals.json +42 -0
- package/{.github/prompts/review.prompt.md → skills/review/SKILL.md} +98 -62
- package/skills/review/evals/evals.json +42 -0
- package/skills/rollback/SKILL.md +110 -0
- package/skills/rollback/evals/evals.json +46 -0
- package/skills/rollback/references/methods.md +204 -0
- package/{.cursor/commands/rollback.md → skills/rollback/references/workflow-integration.md} +10 -284
- package/skills/shepherd/SKILL.md +66 -0
- package/skills/shepherd/evals/evals.json +42 -0
- package/{.github/prompts/ship.prompt.md → skills/ship/SKILL.md} +81 -45
- package/skills/ship/evals/evals.json +42 -0
- package/skills/smith/SKILL.md +142 -0
- package/skills/smith/evals/evals.json +46 -0
- package/skills/smith/references/autonomy-and-gates.md +94 -0
- package/{.github/prompts/sonarcloud.prompt.md → skills/sonarcloud/SKILL.md} +14 -3
- package/skills/sonarcloud/evals/evals.json +46 -0
- package/skills/sonarcloud-analysis/SKILL.md +18 -13
- package/skills/sonarcloud-analysis/evals/evals.json +11 -15
- package/{.github/prompts/status.prompt.md → skills/status/SKILL.md} +20 -10
- package/skills/status/evals/evals.json +50 -0
- package/skills/triage-ready/SKILL.md +121 -0
- package/skills/triage-ready/evals/evals.json +42 -0
- package/{.github/prompts/validate.prompt.md → skills/validate/SKILL.md} +52 -29
- package/skills/validate/evals/evals.json +42 -0
- package/skills/verify/SKILL.md +299 -0
- package/skills/verify/evals/evals.json +50 -0
- package/.claude/commands/dev.md +0 -345
- package/.claude/commands/plan.md +0 -566
- package/.claude/commands/premerge.md +0 -186
- package/.claude/commands/research.md +0 -42
- package/.claude/commands/review.md +0 -451
- package/.claude/commands/rollback.md +0 -721
- package/.claude/commands/ship.md +0 -213
- package/.claude/commands/sonarcloud.md +0 -152
- package/.claude/commands/status.md +0 -90
- package/.claude/commands/validate.md +0 -288
- package/.claude/commands/verify.md +0 -269
- package/.claude/rules/workflow.md +0 -121
- package/.cline/workflows/dev.md +0 -342
- package/.cline/workflows/plan.md +0 -563
- package/.cline/workflows/premerge.md +0 -183
- package/.cline/workflows/research.md +0 -39
- package/.cline/workflows/review.md +0 -448
- package/.cline/workflows/rollback.md +0 -718
- package/.cline/workflows/ship.md +0 -210
- package/.cline/workflows/sonarcloud.md +0 -146
- package/.cline/workflows/status.md +0 -87
- package/.cline/workflows/validate.md +0 -285
- package/.cline/workflows/verify.md +0 -266
- package/.codex/config.toml +0 -11
- package/.codex/skills/dev/SKILL.md +0 -345
- package/.codex/skills/plan/SKILL.md +0 -566
- package/.codex/skills/premerge/SKILL.md +0 -186
- package/.codex/skills/research/SKILL.md +0 -42
- package/.codex/skills/review/SKILL.md +0 -451
- package/.codex/skills/rollback/SKILL.md +0 -721
- package/.codex/skills/ship/SKILL.md +0 -213
- package/.codex/skills/sonarcloud/SKILL.md +0 -149
- package/.codex/skills/status/SKILL.md +0 -90
- package/.codex/skills/validate/SKILL.md +0 -288
- package/.codex/skills/verify/SKILL.md +0 -269
- package/.cursor/commands/dev.md +0 -342
- package/.cursor/commands/plan.md +0 -563
- package/.cursor/commands/premerge.md +0 -183
- package/.cursor/commands/research.md +0 -39
- package/.cursor/commands/review.md +0 -448
- package/.cursor/commands/ship.md +0 -210
- package/.cursor/commands/sonarcloud.md +0 -146
- package/.cursor/commands/status.md +0 -87
- package/.cursor/commands/validate.md +0 -285
- package/.cursor/commands/verify.md +0 -266
- package/.cursorrules +0 -149
- package/.github/prompts/premerge.prompt.md +0 -188
- package/.github/prompts/research.prompt.md +0 -44
- package/.github/prompts/rollback.prompt.md +0 -723
- package/.github/prompts/verify.prompt.md +0 -271
- package/.github/workflows/beads-to-github.yml +0 -89
- package/.github/workflows/github-to-beads.yml +0 -100
- package/.kilocode/workflows/dev.md +0 -346
- package/.kilocode/workflows/plan.md +0 -567
- package/.kilocode/workflows/premerge.md +0 -187
- package/.kilocode/workflows/research.md +0 -43
- package/.kilocode/workflows/review.md +0 -452
- package/.kilocode/workflows/rollback.md +0 -722
- package/.kilocode/workflows/ship.md +0 -214
- package/.kilocode/workflows/sonarcloud.md +0 -150
- package/.kilocode/workflows/status.md +0 -91
- package/.kilocode/workflows/validate.md +0 -289
- package/.kilocode/workflows/verify.md +0 -270
- package/.opencode/commands/dev.md +0 -345
- package/.opencode/commands/plan.md +0 -566
- package/.opencode/commands/premerge.md +0 -186
- package/.opencode/commands/research.md +0 -42
- package/.opencode/commands/review.md +0 -451
- package/.opencode/commands/rollback.md +0 -721
- package/.opencode/commands/ship.md +0 -213
- package/.opencode/commands/sonarcloud.md +0 -149
- package/.opencode/commands/status.md +0 -90
- package/.opencode/commands/validate.md +0 -288
- package/.opencode/commands/verify.md +0 -269
- package/.roo/commands/dev.md +0 -346
- package/.roo/commands/plan.md +0 -567
- package/.roo/commands/premerge.md +0 -187
- package/.roo/commands/research.md +0 -43
- package/.roo/commands/review.md +0 -452
- package/.roo/commands/rollback.md +0 -722
- package/.roo/commands/ship.md +0 -214
- package/.roo/commands/sonarcloud.md +0 -150
- package/.roo/commands/status.md +0 -91
- package/.roo/commands/validate.md +0 -289
- package/.roo/commands/verify.md +0 -270
- package/docs/BEADS_GITHUB_SYNC.md +0 -281
- package/docs/GREPTILE_SETUP.md +0 -400
- package/docs/MANUAL_REVIEW_GUIDE.md +0 -106
- package/docs/SETUP.md +0 -663
- package/docs/VALIDATION.md +0 -363
- package/lib/agents/cline.plugin.json +0 -29
- package/lib/agents/copilot.plugin.json +0 -24
- package/lib/agents/kilocode.plugin.json +0 -22
- package/lib/agents/opencode.plugin.json +0 -23
- package/lib/agents/roo.plugin.json +0 -30
- package/lib/beads-bootstrap.js +0 -225
- package/lib/beads-health-check.js +0 -188
- package/lib/commands/commands-reset.js +0 -147
- package/opencode.json +0 -67
- package/scripts/beads-context.test.js +0 -584
- package/scripts/github-beads-sync/comment.mjs +0 -64
- package/scripts/github-beads-sync/config.mjs +0 -148
- package/scripts/github-beads-sync/github-api.mjs +0 -131
- package/scripts/github-beads-sync/index.mjs +0 -356
- package/scripts/github-beads-sync/label-mapper.mjs +0 -54
- package/scripts/github-beads-sync/mapping.mjs +0 -132
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
- package/scripts/github-beads-sync/reverse-sync.mjs +0 -162
- package/scripts/github-beads-sync/run-bd.mjs +0 -161
- package/scripts/github-beads-sync/sanitize.mjs +0 -121
- package/scripts/github-beads-sync.config.json +0 -26
- package/scripts/sync-commands.js +0 -600
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const { ISSUE_TYPES, ISSUE_STATUSES } = require('./issue-command-contract');
|
|
4
|
+
|
|
5
|
+
// done/cancelled are terminal: no transitions leave them, and the readiness model
|
|
6
|
+
// reports them as `closed` rather than ready/blocked.
|
|
7
|
+
const TERMINAL_STATUSES = Object.freeze(['done', 'cancelled']);
|
|
8
|
+
|
|
9
|
+
// A type only earns existence if it changes Kernel behavior (routing, gates, board
|
|
10
|
+
// grouping, rollup) — D18. `feature`/`story`/`chore`/`spike` are labels, not types.
|
|
11
|
+
const TYPE_BEHAVIORS = Object.freeze({
|
|
12
|
+
epic: Object.freeze({ container: true, canParent: true, claimable: false, rollup: true, blocksOthers: false, board: 'roadmap' }),
|
|
13
|
+
task: Object.freeze({ container: false, canParent: false, claimable: true, rollup: false, blocksOthers: false, board: 'backlog' }),
|
|
14
|
+
bug: Object.freeze({ container: false, canParent: false, claimable: true, rollup: false, blocksOthers: false, board: 'backlog' }),
|
|
15
|
+
decision: Object.freeze({ container: false, canParent: false, claimable: false, rollup: false, blocksOthers: true, board: 'decisions' }),
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
// Stored status lifecycle. Forward path plus rework (backward) and cancellation.
|
|
19
|
+
// ready/blocked are NEVER here — they are derived facts (see readiness-model).
|
|
20
|
+
// `backlog` is the parked state: an open idea can be parked (open -> backlog), a parked
|
|
21
|
+
// idea promoted to scheduled (backlog -> open) or dropped (backlog -> cancelled). A parked
|
|
22
|
+
// idea must be scheduled before it can enter active work (no backlog -> in_progress jump).
|
|
23
|
+
const STATUS_TRANSITIONS = Object.freeze({
|
|
24
|
+
open: Object.freeze(['in_progress', 'cancelled', 'backlog']),
|
|
25
|
+
in_progress: Object.freeze(['review', 'open', 'cancelled']),
|
|
26
|
+
review: Object.freeze(['done', 'in_progress', 'cancelled']),
|
|
27
|
+
done: Object.freeze([]),
|
|
28
|
+
cancelled: Object.freeze([]),
|
|
29
|
+
backlog: Object.freeze(['open', 'cancelled']),
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
const CLAIM_STATES = Object.freeze(['active', 'released', 'expired']);
|
|
33
|
+
|
|
34
|
+
const DEFAULT_PRIORITY_RANK = 2;
|
|
35
|
+
const MAX_DISPLAY_PRIORITY_RANK = 4;
|
|
36
|
+
|
|
37
|
+
class TaxonomyValidationError extends Error {
|
|
38
|
+
constructor(message, details = {}) {
|
|
39
|
+
super(message);
|
|
40
|
+
this.name = 'TaxonomyValidationError';
|
|
41
|
+
this.code = details.code || 'taxonomy_validation_error';
|
|
42
|
+
this.field = details.field;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @returns {boolean} true if `type` is one of the four canonical issue types. */
|
|
47
|
+
function isValidIssueType(type) {
|
|
48
|
+
return ISSUE_TYPES.includes(type);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** @returns {boolean} true if `status` is one of the six stored statuses (ready/blocked are derived, not stored). */
|
|
52
|
+
function isValidIssueStatus(status) {
|
|
53
|
+
return ISSUE_STATUSES.includes(status);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** @returns {boolean} true if `status` is terminal (done/cancelled). */
|
|
57
|
+
function isTerminalStatus(status) {
|
|
58
|
+
return TERMINAL_STATUSES.includes(status);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** @returns {object|null} the behavior mapping for `type`, or null for unknown types. */
|
|
62
|
+
function getTypeBehavior(type) {
|
|
63
|
+
return Object.hasOwn(TYPE_BEHAVIORS, type) ? TYPE_BEHAVIORS[type] : null;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** @returns {boolean} true if issues of `type` may carry an active claim (task/bug). */
|
|
67
|
+
function isClaimableType(type) {
|
|
68
|
+
const behavior = getTypeBehavior(type);
|
|
69
|
+
return Boolean(behavior?.claimable);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** @returns {boolean} true if `from`->`to` is a legal status move (same status is an idempotent no-op). */
|
|
73
|
+
function isValidStatusTransition(from, to) {
|
|
74
|
+
if (!isValidIssueStatus(from) || !isValidIssueStatus(to)) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
if (from === to) {
|
|
78
|
+
return true; // idempotent no-op
|
|
79
|
+
}
|
|
80
|
+
return STATUS_TRANSITIONS[from].includes(to);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Assert a status transition is legal.
|
|
85
|
+
* @throws {TaxonomyValidationError} on an unknown status or an illegal move.
|
|
86
|
+
* @returns {true} when the transition is allowed.
|
|
87
|
+
*/
|
|
88
|
+
function validateStatusTransition(from, to) {
|
|
89
|
+
if (!isValidIssueStatus(from)) {
|
|
90
|
+
throw new TaxonomyValidationError(`Unknown source status: ${from}`, { code: 'unknown_status', field: 'status' });
|
|
91
|
+
}
|
|
92
|
+
if (!isValidIssueStatus(to)) {
|
|
93
|
+
throw new TaxonomyValidationError(`Unknown target status: ${to}`, { code: 'unknown_status', field: 'status' });
|
|
94
|
+
}
|
|
95
|
+
if (!isValidStatusTransition(from, to)) {
|
|
96
|
+
throw new TaxonomyValidationError(`Illegal status transition: ${from} -> ${to}`, {
|
|
97
|
+
code: 'illegal_transition',
|
|
98
|
+
field: 'status',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return true;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function isSelfParent(issue) {
|
|
105
|
+
return Boolean(issue.parent_id) && issue.parent_id === issue.id;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function selfParentError() {
|
|
109
|
+
return { code: 'self_parent', field: 'parent_id', message: 'An issue cannot be its own parent' };
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Validate an issue's type/status enums and self-parent rule.
|
|
114
|
+
* @returns {{valid: boolean, errors: Array<{code: string, field: string, message: string}>}}
|
|
115
|
+
*/
|
|
116
|
+
function validateIssueTaxonomy(issue = {}) {
|
|
117
|
+
const errors = [];
|
|
118
|
+
if (!isValidIssueType(issue.type)) {
|
|
119
|
+
errors.push({ code: 'invalid_type', field: 'type', message: `Unknown issue type: ${issue.type}` });
|
|
120
|
+
}
|
|
121
|
+
if (!isValidIssueStatus(issue.status)) {
|
|
122
|
+
errors.push({ code: 'invalid_status', field: 'status', message: `Unknown issue status: ${issue.status}` });
|
|
123
|
+
}
|
|
124
|
+
if (isSelfParent(issue)) {
|
|
125
|
+
errors.push(selfParentError());
|
|
126
|
+
}
|
|
127
|
+
return { valid: errors.length === 0, errors };
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function isBlockingDependency(dependency) {
|
|
131
|
+
const type = dependency.dependency_type || dependency.type || 'blocks';
|
|
132
|
+
return type === 'blocks';
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function buildDependencyAdjacency(dependencies) {
|
|
136
|
+
const adjacency = new Map();
|
|
137
|
+
for (const dependency of dependencies || []) {
|
|
138
|
+
if (!isBlockingDependency(dependency)) continue;
|
|
139
|
+
const from = dependency.issue_id;
|
|
140
|
+
const to = dependency.blocks_issue_id;
|
|
141
|
+
if (!from || !to) continue;
|
|
142
|
+
if (!adjacency.has(from)) adjacency.set(from, []);
|
|
143
|
+
adjacency.get(from).push(to);
|
|
144
|
+
}
|
|
145
|
+
return adjacency;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Rotate a cycle so its smallest member leads: a deterministic, entry-point
|
|
149
|
+
// independent representation used for both deduplication and the returned cycle.
|
|
150
|
+
function canonicalRotation(cycle) {
|
|
151
|
+
let minIndex = 0;
|
|
152
|
+
for (let i = 1; i < cycle.length; i += 1) {
|
|
153
|
+
if (cycle[i] < cycle[minIndex]) minIndex = i;
|
|
154
|
+
}
|
|
155
|
+
return [...cycle.slice(minIndex), ...cycle.slice(0, minIndex)];
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Iterative (explicit-stack) DFS with white/gray/black coloring. Iterative — not
|
|
159
|
+
// recursive — so a deep dependency chain (thousands of nodes, or a crafted import)
|
|
160
|
+
// cannot overflow the call stack while computing the readiness/validation read model.
|
|
161
|
+
const NODE_WHITE = 0;
|
|
162
|
+
const NODE_GRAY = 1;
|
|
163
|
+
const NODE_BLACK = 2;
|
|
164
|
+
|
|
165
|
+
function recordBackEdgeCycle(path, reentryNode, accumulator) {
|
|
166
|
+
const startIndex = path.indexOf(reentryNode);
|
|
167
|
+
if (startIndex < 0) return;
|
|
168
|
+
const cycle = canonicalRotation(path.slice(startIndex));
|
|
169
|
+
const key = cycle.join(' ');
|
|
170
|
+
if (accumulator.seen.has(key)) return;
|
|
171
|
+
accumulator.seen.add(key);
|
|
172
|
+
accumulator.cycles.push(cycle);
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function exploreFromRoot(root, adjacency, color, accumulator) {
|
|
176
|
+
const path = [root];
|
|
177
|
+
const frames = [{ node: root, neighborIndex: 0 }];
|
|
178
|
+
color.set(root, NODE_GRAY);
|
|
179
|
+
|
|
180
|
+
while (frames.length > 0) {
|
|
181
|
+
const frame = frames.at(-1);
|
|
182
|
+
const neighbors = adjacency.get(frame.node) || [];
|
|
183
|
+
if (frame.neighborIndex >= neighbors.length) {
|
|
184
|
+
color.set(frame.node, NODE_BLACK);
|
|
185
|
+
frames.pop();
|
|
186
|
+
path.pop();
|
|
187
|
+
continue;
|
|
188
|
+
}
|
|
189
|
+
const next = neighbors[frame.neighborIndex];
|
|
190
|
+
frame.neighborIndex += 1;
|
|
191
|
+
const nextColor = color.get(next) || NODE_WHITE;
|
|
192
|
+
if (nextColor === NODE_GRAY) {
|
|
193
|
+
recordBackEdgeCycle(path, next, accumulator);
|
|
194
|
+
} else if (nextColor === NODE_WHITE) {
|
|
195
|
+
color.set(next, NODE_GRAY);
|
|
196
|
+
path.push(next);
|
|
197
|
+
frames.push({ node: next, neighborIndex: 0 });
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Find dependency cycles among `blocks` edges using an iterative (stack-safe) DFS.
|
|
204
|
+
* @param {Array<{issue_id: string, blocks_issue_id: string, dependency_type?: string}>} dependencies
|
|
205
|
+
* @returns {string[][]} each detected cycle, canonically rotated (smallest id first), deduplicated.
|
|
206
|
+
*/
|
|
207
|
+
function findDependencyCycles(dependencies) {
|
|
208
|
+
const adjacency = buildDependencyAdjacency(dependencies);
|
|
209
|
+
const color = new Map();
|
|
210
|
+
const accumulator = { cycles: [], seen: new Set() };
|
|
211
|
+
for (const root of adjacency.keys()) {
|
|
212
|
+
if ((color.get(root) || NODE_WHITE) === NODE_WHITE) {
|
|
213
|
+
exploreFromRoot(root, adjacency, color, accumulator);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return accumulator.cycles;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Assert the dependency graph is acyclic.
|
|
221
|
+
* @throws {TaxonomyValidationError} with the first detected cycle.
|
|
222
|
+
* @returns {true} when no cycle exists.
|
|
223
|
+
*/
|
|
224
|
+
function assertAcyclicDependencies(dependencies) {
|
|
225
|
+
const cycles = findDependencyCycles(dependencies);
|
|
226
|
+
if (cycles.length > 0) {
|
|
227
|
+
throw new TaxonomyValidationError(`Dependency cycle detected: ${cycles[0].join(' -> ')}`, {
|
|
228
|
+
code: 'dependency_cycle',
|
|
229
|
+
field: 'dependencies',
|
|
230
|
+
});
|
|
231
|
+
}
|
|
232
|
+
return true;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* Validate a child issue against its resolved parent (existence, container type, no self-parent).
|
|
237
|
+
* @returns {{valid: boolean, errors: Array<{code: string, field: string, message: string}>}}
|
|
238
|
+
*/
|
|
239
|
+
function validateParentChild(issue = {}, parent = null) {
|
|
240
|
+
const errors = [];
|
|
241
|
+
if (!issue.parent_id) {
|
|
242
|
+
return { valid: true, errors };
|
|
243
|
+
}
|
|
244
|
+
if (isSelfParent(issue)) {
|
|
245
|
+
errors.push(selfParentError());
|
|
246
|
+
return { valid: false, errors };
|
|
247
|
+
}
|
|
248
|
+
if (!parent) {
|
|
249
|
+
errors.push({ code: 'missing_parent', field: 'parent_id', message: `Parent ${issue.parent_id} not found` });
|
|
250
|
+
return { valid: false, errors };
|
|
251
|
+
}
|
|
252
|
+
const behavior = getTypeBehavior(parent.type);
|
|
253
|
+
if (!behavior?.canParent) {
|
|
254
|
+
errors.push({
|
|
255
|
+
code: 'invalid_parent_type',
|
|
256
|
+
field: 'parent_id',
|
|
257
|
+
message: `Type ${parent.type} cannot contain children`,
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
return { valid: errors.length === 0, errors };
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Walk the parent_id chain from `startId`, detecting a cycle.
|
|
265
|
+
* @returns {string[]} the cycle path if one exists, otherwise an empty array.
|
|
266
|
+
*/
|
|
267
|
+
function findParentCycle(issuesById, startId) {
|
|
268
|
+
const nodes = issuesById || {};
|
|
269
|
+
const visited = [];
|
|
270
|
+
const seen = new Set();
|
|
271
|
+
let current = startId;
|
|
272
|
+
while (current && nodes[current]) {
|
|
273
|
+
if (seen.has(current)) {
|
|
274
|
+
return visited.slice(visited.indexOf(current));
|
|
275
|
+
}
|
|
276
|
+
seen.add(current);
|
|
277
|
+
visited.push(current);
|
|
278
|
+
current = nodes[current].parent_id;
|
|
279
|
+
}
|
|
280
|
+
return [];
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/** @returns {number|null} epoch millis for a date-ish value, or null if absent/unparseable. */
|
|
284
|
+
function toEpochMillis(value) {
|
|
285
|
+
if (value === null || value === undefined) return null;
|
|
286
|
+
const millis = new Date(value).getTime();
|
|
287
|
+
return Number.isFinite(millis) ? millis : null;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Validate claim-lease invariants (actor, state, claimable type, lease expiry). Fails closed on
|
|
292
|
+
* an unparseable expiry. Complements — does not replace — broker/DB lease enforcement.
|
|
293
|
+
* @returns {{valid: boolean, errors: Array<{code: string, field: string, message: string}>}}
|
|
294
|
+
*/
|
|
295
|
+
function validateClaim(claim = {}, options = {}) {
|
|
296
|
+
const errors = [];
|
|
297
|
+
if (!claim.actor || String(claim.actor).trim() === '') {
|
|
298
|
+
errors.push({ code: 'missing_actor', field: 'actor', message: 'Claim requires an actor' });
|
|
299
|
+
}
|
|
300
|
+
const state = claim.state || 'active';
|
|
301
|
+
if (!CLAIM_STATES.includes(state)) {
|
|
302
|
+
errors.push({ code: 'invalid_claim_state', field: 'state', message: `Unknown claim state: ${state}` });
|
|
303
|
+
}
|
|
304
|
+
if (options.issueType && !isClaimableType(options.issueType)) {
|
|
305
|
+
errors.push({ code: 'unclaimable_type', field: 'issue_type', message: `Type ${options.issueType} is not claimable` });
|
|
306
|
+
}
|
|
307
|
+
const expiresMillis = toEpochMillis(claim.expires_at);
|
|
308
|
+
const hasExpiry = claim.expires_at !== null && claim.expires_at !== undefined;
|
|
309
|
+
if (hasExpiry && expiresMillis === null) {
|
|
310
|
+
// Fail closed: a lease invariant cannot accept an unparseable expiry date.
|
|
311
|
+
errors.push({ code: 'invalid_lease_date', field: 'expires_at', message: `Unparseable lease expiry: ${claim.expires_at}` });
|
|
312
|
+
}
|
|
313
|
+
const nowMillis = toEpochMillis(options.now);
|
|
314
|
+
if (state === 'active' && nowMillis !== null && expiresMillis !== null && expiresMillis <= nowMillis) {
|
|
315
|
+
// Inclusive boundary: a lease whose expiry instant has arrived is expired, so the
|
|
316
|
+
// issue is released to others rather than held by a just-elapsed lease.
|
|
317
|
+
errors.push({ code: 'lease_expired', field: 'expires_at', message: 'Active claim lease has expired' });
|
|
318
|
+
}
|
|
319
|
+
return { valid: errors.length === 0, errors };
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Check that no issue has more than one active claim.
|
|
324
|
+
* @returns {{valid: boolean, errors: object[], conflictingIssueIds: string[]}}
|
|
325
|
+
*/
|
|
326
|
+
function validateActiveClaimUniqueness(claims = []) {
|
|
327
|
+
const activeByIssue = new Map();
|
|
328
|
+
for (const claim of claims) {
|
|
329
|
+
if ((claim.state || 'active') !== 'active') continue;
|
|
330
|
+
activeByIssue.set(claim.issue_id, (activeByIssue.get(claim.issue_id) || 0) + 1);
|
|
331
|
+
}
|
|
332
|
+
const conflictingIssueIds = [];
|
|
333
|
+
for (const [issueId, count] of activeByIssue) {
|
|
334
|
+
if (count > 1) conflictingIssueIds.push(issueId);
|
|
335
|
+
}
|
|
336
|
+
const errors = conflictingIssueIds.map(issueId => ({
|
|
337
|
+
code: 'multiple_active_claims',
|
|
338
|
+
field: 'issue_id',
|
|
339
|
+
message: `Issue ${issueId} has more than one active claim`,
|
|
340
|
+
}));
|
|
341
|
+
return { valid: conflictingIssueIds.length === 0, errors, conflictingIssueIds };
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
/** @returns {number} `value` coerced to a non-negative integer rank (0 for non-numeric). */
|
|
345
|
+
function normalizeRank(value) {
|
|
346
|
+
const numeric = Number(value);
|
|
347
|
+
if (!Number.isFinite(numeric)) return 0;
|
|
348
|
+
return Math.max(0, Math.trunc(numeric));
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/** @returns {number} the authoritative numeric rank for a priority label/number (default rank for unknown labels). */
|
|
352
|
+
function rankForPriorityLabel(label) {
|
|
353
|
+
if (typeof label === 'number' && Number.isFinite(label)) {
|
|
354
|
+
return normalizeRank(label);
|
|
355
|
+
}
|
|
356
|
+
const raw = String(label ?? '').trim().toUpperCase();
|
|
357
|
+
const match = /^P?(\d+)$/.exec(raw);
|
|
358
|
+
if (!match) return DEFAULT_PRIORITY_RANK;
|
|
359
|
+
return normalizeRank(match[1]);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
/** @returns {string} the P0-P4 display label projected from a numeric rank (clamped for display). */
|
|
363
|
+
function priorityLabelForRank(rank) {
|
|
364
|
+
const clamped = Math.min(normalizeRank(rank), MAX_DISPLAY_PRIORITY_RANK);
|
|
365
|
+
return `P${clamped}`;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
module.exports = {
|
|
369
|
+
CLAIM_STATES,
|
|
370
|
+
STATUS_TRANSITIONS,
|
|
371
|
+
TERMINAL_STATUSES,
|
|
372
|
+
TYPE_BEHAVIORS,
|
|
373
|
+
TaxonomyValidationError,
|
|
374
|
+
ISSUE_STATUSES,
|
|
375
|
+
ISSUE_TYPES,
|
|
376
|
+
assertAcyclicDependencies,
|
|
377
|
+
findDependencyCycles,
|
|
378
|
+
findParentCycle,
|
|
379
|
+
getTypeBehavior,
|
|
380
|
+
isClaimableType,
|
|
381
|
+
isTerminalStatus,
|
|
382
|
+
isValidIssueStatus,
|
|
383
|
+
isValidIssueType,
|
|
384
|
+
isValidStatusTransition,
|
|
385
|
+
normalizeRank,
|
|
386
|
+
priorityLabelForRank,
|
|
387
|
+
rankForPriorityLabel,
|
|
388
|
+
toEpochMillis,
|
|
389
|
+
validateActiveClaimUniqueness,
|
|
390
|
+
validateClaim,
|
|
391
|
+
validateIssueTaxonomy,
|
|
392
|
+
validateParentChild,
|
|
393
|
+
validateStatusTransition,
|
|
394
|
+
};
|
package/lib/lefthook-check.js
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
|
|
6
6
|
const fs = require('node:fs');
|
|
7
7
|
const path = require('node:path');
|
|
8
|
+
const { getInstallCommand, getAddDevCommand } = require('./package-manager-remediation');
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Check whether lefthook is declared in package.json AND whether the binary
|
|
@@ -55,7 +56,7 @@ function checkLefthookStatus(projectRoot) {
|
|
|
55
56
|
installed: false,
|
|
56
57
|
binaryAvailable: false,
|
|
57
58
|
state: 'missing-dependency',
|
|
58
|
-
message:
|
|
59
|
+
message: `lefthook not found. Run: ${getAddDevCommand(root, 'lefthook')}`,
|
|
59
60
|
};
|
|
60
61
|
}
|
|
61
62
|
|
|
@@ -74,7 +75,7 @@ function checkLefthookStatus(projectRoot) {
|
|
|
74
75
|
binaryAvailable: false,
|
|
75
76
|
state: 'missing-binary',
|
|
76
77
|
message:
|
|
77
|
-
|
|
78
|
+
`lefthook is in package.json but not installed. Run: ${getInstallCommand(root)}`,
|
|
78
79
|
};
|
|
79
80
|
}
|
|
80
81
|
|