forge-workflow 0.0.9 → 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 +151 -61
- package/CHANGELOG.md +681 -0
- package/CLAUDE.md +9 -106
- 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 +466 -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/{TOOLCHAIN.md → forge/TOOLCHAIN.md} +56 -47
- 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/reference/TOOLCHAIN.md +658 -0
- 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 +225 -28
- package/lib/beads-sync-scaffold.js +36 -107
- package/lib/codex-skills.js +51 -1
- package/lib/commands/_issue.js +744 -70
- 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 +66 -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 +22 -2
- 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 +851 -979
- 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 +329 -11
- package/lib/commands/sync.js +34 -46
- package/lib/commands/team.js +15 -2
- package/lib/commands/test.js +58 -7
- package/lib/commands/update.js +2 -2
- package/lib/commands/upgrade.js +47 -0
- package/lib/commands/validate.js +56 -25
- package/lib/commands/worktree.js +308 -128
- 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 +184 -0
- package/lib/deprecated-sync-cleanup.js +362 -0
- package/lib/detect-agent.js +2 -28
- package/lib/detect-worktree.js +42 -17
- 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 +697 -0
- 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/issue-sync/authority.js +100 -0
- package/lib/issue-sync/github-pull.js +184 -0
- package/lib/issue-sync/import-primitives.js +98 -0
- package/lib/issue-sync/legacy-link-bridge.js +436 -0
- package/lib/issue-sync/link-store.js +292 -0
- package/lib/issue-sync/project-github.js +123 -0
- package/lib/issue-sync/reconcile.js +195 -0
- package/lib/issue-sync/schema.js +126 -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 +8 -4
- 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 +166 -0
- 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 +332 -23
- 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/conflicts.js +205 -0
- package/lib/smart-status/scoring.js +191 -0
- package/lib/status/beads-snapshot.js +145 -0
- package/lib/status/presenter.js +216 -0
- 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 +298 -47
- package/lib/workflow/stage-transition.js +115 -0
- package/lib/workflow/stages.js +30 -6
- package/lib/workflow/state-manager.js +159 -14
- package/lib/workflow/state.js +23 -1
- package/lib/workflow-profiles.js +17 -5
- package/package.json +46 -36
- 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 +165 -22
- package/scripts/beads-migrate-to-dolt.sh +7 -0
- package/scripts/beads-upgrade-smoke.sh +284 -0
- package/scripts/behavioral-judge.sh +115 -11
- package/scripts/benchmark.js +349 -63
- 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-analyze.js +52 -17
- package/scripts/dep-guard-keyword-ripple.js +29 -0
- package/scripts/dep-guard-render-review.js +86 -0
- package/scripts/dep-guard.sh +64 -232
- 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 +144 -47
- 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 +183 -79
- 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/beads-migrate-to-dolt.mjs +503 -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 +108 -0
- package/scripts/protected-state-check.js +104 -0
- package/scripts/smart-status-score.js +31 -0
- package/scripts/smart-status-sessions.js +51 -0
- package/scripts/smart-status.sh +117 -369
- 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-agentic-workflow.js +48 -0
- package/scripts/sync-utils.sh +3 -0
- package/scripts/test-ci-shard.js +251 -0
- package/scripts/test-dashboard.js +188 -52
- package/scripts/test-full-suite.js +186 -0
- package/scripts/test-profile.js +278 -0
- package/scripts/test.js +302 -28
- package/scripts/validate.js +143 -0
- package/scripts/validate.sh +18 -1
- 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} +46 -52
- 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} +134 -159
- 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/skills/ship/SKILL.md +251 -0
- 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/skills/status/SKILL.md +102 -0
- 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/ship.prompt.md +0 -215
- package/.github/prompts/status.prompt.md +0 -92
- package/.github/prompts/verify.prompt.md +0 -271
- package/.github/workflows/beads-to-github.yml +0 -56
- package/.github/workflows/github-to-beads.yml +0 -97
- 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 -255
- 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-health-check.js +0 -143
- package/lib/commands/commands-reset.js +0 -147
- package/opencode.json +0 -67
- package/scripts/beads-context.test.js +0 -567
- 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 -332
- package/scripts/github-beads-sync/label-mapper.mjs +0 -54
- package/scripts/github-beads-sync/mapping.mjs +0 -78
- package/scripts/github-beads-sync/reverse-sync-cli.mjs +0 -31
- package/scripts/github-beads-sync/reverse-sync.mjs +0 -138
- 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,330 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module skills-sync
|
|
3
|
+
*
|
|
4
|
+
* Skills-only surface population + drift detection for Forge.
|
|
5
|
+
*
|
|
6
|
+
* Canonical source of truth is the committed root `skills/` directory — each
|
|
7
|
+
* `skills/<name>/SKILL.md` (plus any sibling files) is an auto-surfacing skill.
|
|
8
|
+
* Agent harness skill directories (`.agents/skills/`, `.claude/skills/`,
|
|
9
|
+
* `.codex/skills/`, `.cursor/skills/`, `.hermes/skills/`) are GENERATED from that
|
|
10
|
+
* canonical source. `.agents/skills` is Codex's documented repo-local discovery
|
|
11
|
+
* path (scanned from cwd up to the repo root) and is the ONE mirror committed to
|
|
12
|
+
* the repo (kept byte-identical by a pre-commit sync hook + the drift gate), so a
|
|
13
|
+
* teammate who clones the repo WITHOUT running `forge setup` still gets the Forge
|
|
14
|
+
* skills/stages auto-discovered. The other mirrors (including `.codex/skills`) are
|
|
15
|
+
* gitignored and populated at setup only.
|
|
16
|
+
*
|
|
17
|
+
* This module is the Forge-internal port of the `@forge/skills` CLI sync step.
|
|
18
|
+
* Forge cannot depend on `@forge/skills` at runtime (the `packages/` workspace is
|
|
19
|
+
* excluded from the published npm package), so the minimal population + check
|
|
20
|
+
* logic lives here. It mirrors the CLI's whole-directory copy semantics so that
|
|
21
|
+
* `skills sync --check` and this module agree on what "in sync" means.
|
|
22
|
+
*
|
|
23
|
+
* Design constraints (locked in kernel-skill-surface-design.md §6):
|
|
24
|
+
* - Registry-independent: never requires `.skills/.registry.json` (the registry
|
|
25
|
+
* is gitignored and absent on a fresh checkout).
|
|
26
|
+
* - AGENTS.md-safe: never writes AGENTS.md (it is the protected workflow contract).
|
|
27
|
+
* - Existing-mirror scoped: `checkSkillsSync` only validates agent skill dirs
|
|
28
|
+
* that already exist on disk. The gitignored, setup-populated mirrors
|
|
29
|
+
* (`.claude/skills`, `.codex/skills`, `.cursor/skills`, `.hermes/skills`) are
|
|
30
|
+
* absent on a clean checkout and are correctly skipped; `.agents/skills` is the
|
|
31
|
+
* committed mirror that is always present and thus always enforced against the
|
|
32
|
+
* canonical source, which is what the gate guarantees.
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
const fs = require('node:fs');
|
|
36
|
+
const path = require('node:path');
|
|
37
|
+
|
|
38
|
+
/** Canonical skills live under this directory at the repo/package root. */
|
|
39
|
+
const CANONICAL_SKILLS_DIR = 'skills';
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Standard agent harness skill directories, relative to a project root.
|
|
43
|
+
* Order is stable for deterministic reporting.
|
|
44
|
+
*/
|
|
45
|
+
const AGENT_SKILL_DIRS = [
|
|
46
|
+
'.agents/skills',
|
|
47
|
+
'.claude/skills',
|
|
48
|
+
'.codex/skills',
|
|
49
|
+
'.cursor/skills',
|
|
50
|
+
'.hermes/skills',
|
|
51
|
+
];
|
|
52
|
+
|
|
53
|
+
/** Skill name must be a safe single path segment (no traversal). */
|
|
54
|
+
function isValidSkillName(name) {
|
|
55
|
+
return (
|
|
56
|
+
typeof name === 'string' &&
|
|
57
|
+
name.length > 0 &&
|
|
58
|
+
name !== '.' &&
|
|
59
|
+
name !== '..' &&
|
|
60
|
+
!name.includes('/') &&
|
|
61
|
+
!name.includes('\\') &&
|
|
62
|
+
!name.includes('\0')
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* List canonical skills under `<sourceRoot>/skills`.
|
|
68
|
+
*
|
|
69
|
+
* A skill is any direct subdirectory that contains a `SKILL.md` file.
|
|
70
|
+
*
|
|
71
|
+
* @param {string} sourceRoot - Directory containing the canonical `skills/` dir.
|
|
72
|
+
* @param {object} [options]
|
|
73
|
+
* @param {Set<string>|string[]} [options.only] - Restrict to these skill names.
|
|
74
|
+
* @returns {{name: string, sourcePath: string}[]} Sorted list of skills.
|
|
75
|
+
*/
|
|
76
|
+
function listCanonicalSkills(sourceRoot, options = {}) {
|
|
77
|
+
const skillsDir = path.join(sourceRoot, CANONICAL_SKILLS_DIR);
|
|
78
|
+
if (!fs.existsSync(skillsDir)) {
|
|
79
|
+
return [];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const only = options.only ? new Set(options.only) : null;
|
|
83
|
+
const skills = [];
|
|
84
|
+
|
|
85
|
+
for (const entry of fs.readdirSync(skillsDir, { withFileTypes: true })) {
|
|
86
|
+
if (!entry.isDirectory()) continue;
|
|
87
|
+
if (!isValidSkillName(entry.name)) continue;
|
|
88
|
+
if (only && !only.has(entry.name)) continue;
|
|
89
|
+
|
|
90
|
+
const sourcePath = path.join(skillsDir, entry.name);
|
|
91
|
+
if (!fs.existsSync(path.join(sourcePath, 'SKILL.md'))) continue;
|
|
92
|
+
|
|
93
|
+
skills.push({ name: entry.name, sourcePath });
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
skills.sort((a, b) => a.name.localeCompare(b.name));
|
|
97
|
+
return skills;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* List all files under a directory as paths relative to that directory,
|
|
102
|
+
* using forward slashes for stable cross-platform comparison.
|
|
103
|
+
*
|
|
104
|
+
* @param {string} dir - Directory to walk.
|
|
105
|
+
* @returns {string[]} Sorted relative file paths.
|
|
106
|
+
*/
|
|
107
|
+
function listFilesRecursive(dir) {
|
|
108
|
+
const out = [];
|
|
109
|
+
|
|
110
|
+
function walk(current, prefix) {
|
|
111
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
112
|
+
const abs = path.join(current, entry.name);
|
|
113
|
+
const rel = prefix ? `${prefix}/${entry.name}` : entry.name;
|
|
114
|
+
if (entry.isDirectory()) {
|
|
115
|
+
walk(abs, rel);
|
|
116
|
+
} else if (entry.isFile()) {
|
|
117
|
+
out.push(rel);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
if (fs.existsSync(dir)) {
|
|
123
|
+
walk(dir, '');
|
|
124
|
+
}
|
|
125
|
+
out.sort((a, b) => a.localeCompare(b));
|
|
126
|
+
return out;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Normalize line endings so Windows CRLF working-tree state is not false drift. */
|
|
130
|
+
function normalizeContent(buffer) {
|
|
131
|
+
return buffer.toString('utf8').replace(/\r\n/g, '\n');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Remove a symlink sitting at a target path so a real directory can be written
|
|
136
|
+
* in its place. Only ever removes the link entry itself — never a real
|
|
137
|
+
* directory (with content) and never the symlink's target.
|
|
138
|
+
*
|
|
139
|
+
* Guards against the exact cruft that broke local skill-sync: a pre-existing
|
|
140
|
+
* DANGLING symlink at a skill dir path, which makes `mkdirSync`/copy fail.
|
|
141
|
+
*
|
|
142
|
+
* @param {string} targetPath - Path that may hold a (possibly dangling) symlink.
|
|
143
|
+
* @returns {boolean} True if a symlink was cleared, false otherwise.
|
|
144
|
+
*/
|
|
145
|
+
function clearSymlinkAtPath(targetPath) {
|
|
146
|
+
let linkStat;
|
|
147
|
+
try {
|
|
148
|
+
// lstat does NOT follow the link, so a dangling symlink is still detected.
|
|
149
|
+
linkStat = fs.lstatSync(targetPath);
|
|
150
|
+
} catch {
|
|
151
|
+
return false; // nothing at this path
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if (!linkStat.isSymbolicLink()) {
|
|
155
|
+
return false; // a real file/dir — leave it for the normal copy path
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Remove ONLY the link. Neither unlink nor rm follow a symlink's target, so
|
|
159
|
+
// the (possibly dangling) target's contents are never touched.
|
|
160
|
+
try {
|
|
161
|
+
fs.unlinkSync(targetPath);
|
|
162
|
+
} catch {
|
|
163
|
+
// Windows directory symlinks/junctions can reject unlink; rmSync removes the
|
|
164
|
+
// link entry itself without recursing into the target.
|
|
165
|
+
fs.rmSync(targetPath, { recursive: true, force: true });
|
|
166
|
+
}
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Copy a single skill directory (recursively) from source to target,
|
|
172
|
+
* overwriting existing files. Byte-for-byte copy (matches CLI cpSync).
|
|
173
|
+
*
|
|
174
|
+
* @param {string} sourcePath - Source skill directory.
|
|
175
|
+
* @param {string} targetPath - Target skill directory.
|
|
176
|
+
*/
|
|
177
|
+
function copySkillDir(sourcePath, targetPath) {
|
|
178
|
+
fs.mkdirSync(targetPath, { recursive: true });
|
|
179
|
+
for (const entry of fs.readdirSync(sourcePath, { withFileTypes: true })) {
|
|
180
|
+
const src = path.join(sourcePath, entry.name);
|
|
181
|
+
const dest = path.join(targetPath, entry.name);
|
|
182
|
+
if (entry.isDirectory()) {
|
|
183
|
+
copySkillDir(src, dest);
|
|
184
|
+
} else if (entry.isFile()) {
|
|
185
|
+
fs.copyFileSync(src, dest);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Populate an agent harness skills directory from the canonical source.
|
|
192
|
+
*
|
|
193
|
+
* Writes `<targetSkillsDir>/<name>/...` for every canonical skill. Existing
|
|
194
|
+
* skills are overwritten; pre-existing unrelated skill dirs are left untouched
|
|
195
|
+
* unless `clean` is set.
|
|
196
|
+
*
|
|
197
|
+
* @param {object} params
|
|
198
|
+
* @param {string} params.sourceRoot - Root containing the canonical `skills/` dir.
|
|
199
|
+
* @param {string} params.targetSkillsDir - Absolute path to the agent skills dir.
|
|
200
|
+
* @param {Set<string>|string[]} [params.only] - Restrict to these skill names.
|
|
201
|
+
* @param {boolean} [params.clean=false] - Remove canonical-managed stale dirs first.
|
|
202
|
+
* @returns {{written: string[]}} Names of skills written.
|
|
203
|
+
*/
|
|
204
|
+
function populateAgentSkills({ sourceRoot, targetSkillsDir, only, clean = false }) {
|
|
205
|
+
const skills = listCanonicalSkills(sourceRoot, { only });
|
|
206
|
+
fs.mkdirSync(targetSkillsDir, { recursive: true });
|
|
207
|
+
|
|
208
|
+
if (clean) {
|
|
209
|
+
// Keep-set is the FULL canonical set, not the `only`-filtered subset, so a
|
|
210
|
+
// partial sync (only: [...]) never deletes other canonical skills' dirs.
|
|
211
|
+
const canonicalNames = new Set(listCanonicalSkills(sourceRoot).map((s) => s.name));
|
|
212
|
+
for (const entry of fs.readdirSync(targetSkillsDir, { withFileTypes: true })) {
|
|
213
|
+
if (entry.isDirectory() && !canonicalNames.has(entry.name)) {
|
|
214
|
+
fs.rmSync(path.join(targetSkillsDir, entry.name), { recursive: true, force: true });
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
const written = [];
|
|
220
|
+
for (const skill of skills) {
|
|
221
|
+
const targetPath = path.join(targetSkillsDir, skill.name);
|
|
222
|
+
// Defensive: clear a pre-existing (possibly dangling) symlink so the copy
|
|
223
|
+
// doesn't fail. Only symlinks are removed — real dirs are overwritten in place.
|
|
224
|
+
clearSymlinkAtPath(targetPath);
|
|
225
|
+
copySkillDir(skill.sourcePath, targetPath);
|
|
226
|
+
written.push(skill.name);
|
|
227
|
+
}
|
|
228
|
+
return { written };
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Compare a canonical skill directory against a target copy.
|
|
233
|
+
*
|
|
234
|
+
* @param {string} sourcePath - Canonical skill dir.
|
|
235
|
+
* @param {string} targetPath - Target (generated) skill dir.
|
|
236
|
+
* @returns {{file: string, status: 'missing'|'changed'|'extra'}[]} Drift entries.
|
|
237
|
+
*/
|
|
238
|
+
function diffSkillDir(sourcePath, targetPath) {
|
|
239
|
+
const drift = [];
|
|
240
|
+
const sourceFiles = new Set(listFilesRecursive(sourcePath));
|
|
241
|
+
const targetFiles = new Set(listFilesRecursive(targetPath));
|
|
242
|
+
|
|
243
|
+
for (const rel of sourceFiles) {
|
|
244
|
+
if (!targetFiles.has(rel)) {
|
|
245
|
+
drift.push({ file: rel, status: 'missing' });
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const a = normalizeContent(fs.readFileSync(path.join(sourcePath, rel)));
|
|
249
|
+
const b = normalizeContent(fs.readFileSync(path.join(targetPath, rel)));
|
|
250
|
+
if (a !== b) {
|
|
251
|
+
drift.push({ file: rel, status: 'changed' });
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
for (const rel of targetFiles) {
|
|
256
|
+
if (!sourceFiles.has(rel)) {
|
|
257
|
+
drift.push({ file: rel, status: 'extra' });
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
return drift;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Check that existing agent skill mirrors match the canonical source.
|
|
266
|
+
*
|
|
267
|
+
* Only agent skill dirs that already EXIST under `repoRoot` are validated. The
|
|
268
|
+
* gitignored, setup-populated mirrors (`.claude/skills`, `.codex/skills`, etc.)
|
|
269
|
+
* are absent on a clean checkout and correctly skipped; the committed
|
|
270
|
+
* `.agents/skills` mirror is always present and always enforced against the
|
|
271
|
+
* canonical source. Any mirror present locally must match canonical.
|
|
272
|
+
*
|
|
273
|
+
* @param {object} params
|
|
274
|
+
* @param {string} params.repoRoot - Repo root (contains `skills/` + agent dirs).
|
|
275
|
+
* @param {Set<string>|string[]} [params.only] - Restrict canonical set.
|
|
276
|
+
* @param {string[]} [params.agentSkillDirs] - Override the agent dirs to scan.
|
|
277
|
+
* @returns {{inSync: boolean, checkedAgents: string[], drift: object[]}}
|
|
278
|
+
*/
|
|
279
|
+
function checkSkillsSync({ repoRoot, only, agentSkillDirs = AGENT_SKILL_DIRS }) {
|
|
280
|
+
const skills = listCanonicalSkills(repoRoot, { only });
|
|
281
|
+
const canonicalByName = new Map(skills.map((s) => [s.name, s]));
|
|
282
|
+
const drift = [];
|
|
283
|
+
const checkedAgents = [];
|
|
284
|
+
|
|
285
|
+
for (const rel of agentSkillDirs) {
|
|
286
|
+
const agentDir = path.join(repoRoot, rel);
|
|
287
|
+
if (!fs.existsSync(agentDir)) continue; // populated at setup time; absence ≠ drift
|
|
288
|
+
checkedAgents.push(rel);
|
|
289
|
+
|
|
290
|
+
const targetNames = fs
|
|
291
|
+
.readdirSync(agentDir, { withFileTypes: true })
|
|
292
|
+
.filter((e) => e.isDirectory())
|
|
293
|
+
.map((e) => e.name);
|
|
294
|
+
|
|
295
|
+
// Skills present canonically but missing/changed in the mirror.
|
|
296
|
+
for (const skill of skills) {
|
|
297
|
+
const targetPath = path.join(agentDir, skill.name);
|
|
298
|
+
// A regular file at the skill path is corruption, not a valid mirror:
|
|
299
|
+
// treat it as drift instead of letting diffSkillDir() throw ENOTDIR.
|
|
300
|
+
if (!fs.existsSync(targetPath) || !fs.statSync(targetPath).isDirectory()) {
|
|
301
|
+
drift.push({ agent: rel, skill: skill.name, file: 'SKILL.md', status: 'missing' });
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
for (const entry of diffSkillDir(skill.sourcePath, targetPath)) {
|
|
305
|
+
drift.push({ agent: rel, skill: skill.name, ...entry });
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Skill dirs in the mirror with no canonical source = stale.
|
|
310
|
+
for (const name of targetNames) {
|
|
311
|
+
if (!canonicalByName.has(name)) {
|
|
312
|
+
drift.push({ agent: rel, skill: name, file: '*', status: 'stale' });
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
return { inSync: drift.length === 0, checkedAgents, drift };
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
module.exports = {
|
|
321
|
+
CANONICAL_SKILLS_DIR,
|
|
322
|
+
AGENT_SKILL_DIRS,
|
|
323
|
+
isValidSkillName,
|
|
324
|
+
listCanonicalSkills,
|
|
325
|
+
listFilesRecursive,
|
|
326
|
+
clearSymlinkAtPath,
|
|
327
|
+
populateAgentSkills,
|
|
328
|
+
diffSkillDir,
|
|
329
|
+
checkSkillsSync,
|
|
330
|
+
};
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function splitLines(value) {
|
|
4
|
+
if (!value) {
|
|
5
|
+
return [];
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
return String(value)
|
|
9
|
+
.replaceAll('\r', '')
|
|
10
|
+
.split('\n');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function uniqueStrings(values) {
|
|
14
|
+
const result = [];
|
|
15
|
+
const seen = new Set();
|
|
16
|
+
|
|
17
|
+
for (const value of values || []) {
|
|
18
|
+
if (typeof value !== 'string') {
|
|
19
|
+
continue;
|
|
20
|
+
}
|
|
21
|
+
const normalized = value.trim();
|
|
22
|
+
if (!normalized || seen.has(normalized)) {
|
|
23
|
+
continue;
|
|
24
|
+
}
|
|
25
|
+
seen.add(normalized);
|
|
26
|
+
result.push(normalized);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return result;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseWorktreePorcelain(porcelainText, baseBranch) {
|
|
33
|
+
const worktrees = [];
|
|
34
|
+
let currentPath = '';
|
|
35
|
+
let currentBranch = '';
|
|
36
|
+
|
|
37
|
+
for (const line of splitLines(porcelainText)) {
|
|
38
|
+
if (line.startsWith('worktree ')) {
|
|
39
|
+
currentPath = line.slice('worktree '.length);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (line.startsWith('branch refs/heads/')) {
|
|
44
|
+
currentBranch = line.slice('branch refs/heads/'.length);
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (line !== '') {
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (currentBranch && currentBranch !== baseBranch) {
|
|
53
|
+
worktrees.push({ branch: currentBranch, path: currentPath });
|
|
54
|
+
}
|
|
55
|
+
currentPath = '';
|
|
56
|
+
currentBranch = '';
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (currentBranch && currentBranch !== baseBranch) {
|
|
60
|
+
worktrees.push({ branch: currentBranch, path: currentPath });
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return worktrees;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function branchSlugToSearch(branch) {
|
|
67
|
+
const slug = String(branch || '').includes('/')
|
|
68
|
+
? String(branch).slice(String(branch).lastIndexOf('/') + 1)
|
|
69
|
+
: String(branch || '');
|
|
70
|
+
return slug.replaceAll('-', ' ').toLowerCase();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function matchInProgressIssues(worktrees, inProgressIssues) {
|
|
74
|
+
const issues = Array.isArray(inProgressIssues) ? inProgressIssues : [];
|
|
75
|
+
|
|
76
|
+
return (Array.isArray(worktrees) ? worktrees : []).map((worktree) => {
|
|
77
|
+
const slug = branchSlugToSearch(worktree.branch);
|
|
78
|
+
if (!slug) {
|
|
79
|
+
return {
|
|
80
|
+
branch: worktree.branch,
|
|
81
|
+
path: worktree.path,
|
|
82
|
+
issue_ids: [],
|
|
83
|
+
issue_count: 0,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
const issueIds = issues
|
|
87
|
+
.filter((issue) => typeof issue?.title === 'string' && issue.title.toLowerCase().includes(slug))
|
|
88
|
+
.map((issue) => issue?.id)
|
|
89
|
+
.filter((issueId) => typeof issueId === 'string' && issueId);
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
branch: worktree.branch,
|
|
93
|
+
path: worktree.path,
|
|
94
|
+
issue_ids: issueIds,
|
|
95
|
+
issue_count: issueIds.length,
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function normalizeBranchFiles(branchFiles) {
|
|
101
|
+
if (Array.isArray(branchFiles)) {
|
|
102
|
+
return branchFiles
|
|
103
|
+
.filter((entry) => entry && typeof entry.branch === 'string')
|
|
104
|
+
.map((entry) => ({
|
|
105
|
+
branch: entry.branch,
|
|
106
|
+
files: Array.isArray(entry.files) ? uniqueStrings(entry.files) : [],
|
|
107
|
+
}));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (branchFiles && typeof branchFiles === 'object') {
|
|
111
|
+
return Object.entries(branchFiles).map(([branch, files]) => ({
|
|
112
|
+
branch,
|
|
113
|
+
files: Array.isArray(files) ? uniqueStrings(files) : [],
|
|
114
|
+
}));
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return [];
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
function computeFileConflicts(sessions, branchFiles) {
|
|
121
|
+
const branchFileEntries = normalizeBranchFiles(branchFiles);
|
|
122
|
+
const filesByBranch = new Map(branchFileEntries.map((entry) => [entry.branch, entry.files]));
|
|
123
|
+
|
|
124
|
+
return (Array.isArray(sessions) ? sessions : []).map((session) => {
|
|
125
|
+
const changedFiles = filesByBranch.get(session.branch) || [];
|
|
126
|
+
const conflicts = [];
|
|
127
|
+
|
|
128
|
+
for (const other of branchFileEntries) {
|
|
129
|
+
if (other.branch === session.branch || other.files.length === 0 || changedFiles.length === 0) {
|
|
130
|
+
continue;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const otherSet = new Set(other.files);
|
|
134
|
+
const overlap = uniqueStrings(changedFiles.filter((file) => otherSet.has(file)));
|
|
135
|
+
if (overlap.length > 0) {
|
|
136
|
+
conflicts.push({ branch: other.branch, files: overlap });
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
...session,
|
|
142
|
+
changed_files: changedFiles,
|
|
143
|
+
conflicts,
|
|
144
|
+
};
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function parseMergeTreeNameOnly(stdout) {
|
|
149
|
+
const lines = splitLines(stdout).filter((line, index) => index > 0 && line.trim().length > 0);
|
|
150
|
+
return uniqueStrings(lines);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function applyMergeTreeConflicts(sessions, mergeTreeResults) {
|
|
154
|
+
const nextSessions = (Array.isArray(sessions) ? sessions : []).map((session) => ({
|
|
155
|
+
...session,
|
|
156
|
+
merge_conflicts: Array.isArray(session.merge_conflicts) ? [...session.merge_conflicts] : [],
|
|
157
|
+
}));
|
|
158
|
+
const sessionIndexes = new Map(nextSessions.map((session, index) => [session.branch, index]));
|
|
159
|
+
|
|
160
|
+
for (const result of Array.isArray(mergeTreeResults) ? mergeTreeResults : []) {
|
|
161
|
+
if (!result || typeof result.left !== 'string' || typeof result.right !== 'string') {
|
|
162
|
+
continue;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
const exitCode = Number(result.exitCode);
|
|
166
|
+
if (exitCode !== 1) {
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const files = parseMergeTreeNameOnly(result.output);
|
|
171
|
+
if (files.length === 0) {
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (const [targetBranch, otherBranch] of [[result.left, result.right], [result.right, result.left]]) {
|
|
176
|
+
const index = sessionIndexes.get(targetBranch);
|
|
177
|
+
if (index === undefined) {
|
|
178
|
+
continue;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
nextSessions[index].merge_conflicts.push({
|
|
182
|
+
branch: otherBranch,
|
|
183
|
+
files,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return nextSessions.map((session) => {
|
|
189
|
+
if (session.merge_conflicts.length === 0) {
|
|
190
|
+
const nextSession = { ...session };
|
|
191
|
+
delete nextSession.merge_conflicts;
|
|
192
|
+
return nextSession;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
return session;
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = {
|
|
200
|
+
applyMergeTreeConflicts,
|
|
201
|
+
computeFileConflicts,
|
|
202
|
+
matchInProgressIssues,
|
|
203
|
+
parseMergeTreeNameOnly,
|
|
204
|
+
parseWorktreePorcelain,
|
|
205
|
+
};
|