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,268 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Native SAFETY-surface renderer.
|
|
5
|
+
*
|
|
6
|
+
* Renders sane, non-surprising security defaults into the two safety surfaces
|
|
7
|
+
* Forge can write PROJECT-LOCALLY today:
|
|
8
|
+
*
|
|
9
|
+
* - Claude Code : `.claude/settings.json` -> `permissions` (allow/deny/ask)
|
|
10
|
+
* - Cursor : `.cursorignore` (gitignore-style AI read/index boundary)
|
|
11
|
+
*
|
|
12
|
+
* Both use read -> merge -> write (idempotent, preserves user entries). An existing
|
|
13
|
+
* `.claude/settings.json` that cannot be parsed is BACKED UP and left untouched
|
|
14
|
+
* (mirrors the MCP renderer's data-loss guard) rather than clobbering user settings.
|
|
15
|
+
*
|
|
16
|
+
* NOT rendered here (honest deferral): the Codex execution sandbox / approvals policy
|
|
17
|
+
* (`sandbox_mode` / `approval_policy`). Codex honors a project-local `.codex/config.toml`
|
|
18
|
+
* ONLY when the project is marked trusted in the GLOBAL `$CODEX_HOME/config.toml`
|
|
19
|
+
* (`projects.<path>.trust_level`). Forge cannot grant that global trust during
|
|
20
|
+
* project-local setup, so a rendered project-local sandbox policy would not take
|
|
21
|
+
* effect. It is deferred to global-scope wiring and marked `not-delivered` in the
|
|
22
|
+
* capability matrix (kernel epic 90f2f631 / #311).
|
|
23
|
+
*
|
|
24
|
+
* Defaults follow the principle: ALLOW the routine forge/dev workflow, DENY the
|
|
25
|
+
* obviously-dangerous, and let everything else fall through to the harness's normal
|
|
26
|
+
* "ask" behavior (Claude evaluates deny -> ask -> allow; unmatched tools still prompt).
|
|
27
|
+
* We intentionally do NOT set `defaultMode`, so nothing is silently auto-approved.
|
|
28
|
+
*
|
|
29
|
+
* Dependency-free beyond node builtins so it runs under `bun test` and release gates.
|
|
30
|
+
*
|
|
31
|
+
* @module safety-config-renderer
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const fs = require('node:fs');
|
|
35
|
+
const path = require('node:path');
|
|
36
|
+
|
|
37
|
+
const { backupFile } = require('./mcp-config-renderer');
|
|
38
|
+
|
|
39
|
+
/** Thrown when an existing settings file cannot be parsed — signals "do not overwrite". */
|
|
40
|
+
class SafetyConfigParseError extends Error {
|
|
41
|
+
constructor(message, cause) {
|
|
42
|
+
super(message);
|
|
43
|
+
this.name = 'SafetyConfigParseError';
|
|
44
|
+
this.cause = cause;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Safe Claude Code permission defaults. Rule syntax per Claude Code settings docs
|
|
49
|
+
// (https://docs.claude.com/en/docs/claude-code/settings ,
|
|
50
|
+
// https://docs.claude.com/en/docs/claude-code/permissions):
|
|
51
|
+
// Bash(<prefix>:*) — command-prefix match; Read(<glob>) — gitignore-style path.
|
|
52
|
+
// Evaluated deny -> ask -> allow (deny always wins), so the deny Read(...) rules
|
|
53
|
+
// protect secrets even though bare Read is not blanket-allowed.
|
|
54
|
+
const CLAUDE_PERMISSION_DEFAULTS = Object.freeze({
|
|
55
|
+
// Routine, reversible forge/dev workflow — safe to run without a prompt.
|
|
56
|
+
allow: Object.freeze([
|
|
57
|
+
'Bash(git status:*)',
|
|
58
|
+
'Bash(git log:*)',
|
|
59
|
+
'Bash(git diff:*)',
|
|
60
|
+
'Bash(git branch:*)',
|
|
61
|
+
'Bash(git show:*)',
|
|
62
|
+
'Bash(git add:*)',
|
|
63
|
+
'Bash(git commit:*)',
|
|
64
|
+
'Bash(git stash:*)',
|
|
65
|
+
'Bash(git checkout:*)',
|
|
66
|
+
'Bash(git switch:*)',
|
|
67
|
+
'Bash(git pull:*)',
|
|
68
|
+
'Bash(git fetch:*)',
|
|
69
|
+
'Bash(git push:*)',
|
|
70
|
+
'Bash(ls:*)',
|
|
71
|
+
'Bash(cat:*)',
|
|
72
|
+
'Bash(pwd)',
|
|
73
|
+
'Bash(which:*)',
|
|
74
|
+
'Bash(mkdir:*)',
|
|
75
|
+
'Bash(cp:*)',
|
|
76
|
+
'Bash(mv:*)',
|
|
77
|
+
'Bash(bun run:*)',
|
|
78
|
+
'Bash(bun test:*)',
|
|
79
|
+
'Bash(bun install)',
|
|
80
|
+
'Bash(npm run:*)',
|
|
81
|
+
'Bash(npm test:*)',
|
|
82
|
+
'Bash(forge:*)',
|
|
83
|
+
'Bash(gh pr view:*)',
|
|
84
|
+
'Bash(gh pr list:*)',
|
|
85
|
+
'Bash(gh issue list:*)',
|
|
86
|
+
]),
|
|
87
|
+
// Careful, rewrites-history / rewinds-state — worth a confirmation, not a hard block.
|
|
88
|
+
ask: Object.freeze([
|
|
89
|
+
'Bash(git rebase:*)',
|
|
90
|
+
'Bash(git reset:*)',
|
|
91
|
+
'Bash(git clean:*)',
|
|
92
|
+
]),
|
|
93
|
+
// Obviously-dangerous commands + secret reads. deny wins over allow/ask.
|
|
94
|
+
deny: Object.freeze([
|
|
95
|
+
'Bash(rm -rf:*)',
|
|
96
|
+
'Bash(rm -fr:*)',
|
|
97
|
+
'Bash(git push --force:*)',
|
|
98
|
+
'Bash(git push -f:*)',
|
|
99
|
+
'Bash(git reset --hard:*)',
|
|
100
|
+
// Read/secret boundary — private keys and secret env files are never readable.
|
|
101
|
+
'Read(./.env)',
|
|
102
|
+
'Read(**/.env)',
|
|
103
|
+
'Read(**/.env.local)',
|
|
104
|
+
'Read(**/.env.*.local)',
|
|
105
|
+
'Read(**/*.pem)',
|
|
106
|
+
'Read(**/*.key)',
|
|
107
|
+
'Read(**/id_rsa)',
|
|
108
|
+
'Read(**/id_ed25519)',
|
|
109
|
+
'Read(./secrets/**)',
|
|
110
|
+
'Read(**/credentials.json)',
|
|
111
|
+
]),
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
// Safe `.cursorignore` defaults (gitignore-style, per
|
|
115
|
+
// https://cursor.com/docs/reference/ignore-file). Cursor already ignores .gitignore
|
|
116
|
+
// and common lock/.env files by default; listing them here makes the secret/large-dir
|
|
117
|
+
// boundary explicit and self-documenting. `.cursorignore` blocks Cursor AI features +
|
|
118
|
+
// indexing (it is not a hard security boundary — terminal/MCP tools can still reach files).
|
|
119
|
+
const CURSORIGNORE_DEFAULTS = Object.freeze([
|
|
120
|
+
'.env',
|
|
121
|
+
'.env.local',
|
|
122
|
+
'.env.*.local',
|
|
123
|
+
'*.pem',
|
|
124
|
+
'*.key',
|
|
125
|
+
'id_rsa',
|
|
126
|
+
'id_ed25519',
|
|
127
|
+
'secrets/',
|
|
128
|
+
'node_modules/',
|
|
129
|
+
'dist/',
|
|
130
|
+
'build/',
|
|
131
|
+
'out/',
|
|
132
|
+
'coverage/',
|
|
133
|
+
'.next/',
|
|
134
|
+
'*.log',
|
|
135
|
+
]);
|
|
136
|
+
|
|
137
|
+
const CURSORIGNORE_HEADER = [
|
|
138
|
+
'# ---- Forge safe defaults (managed) ----',
|
|
139
|
+
'# Keeps secrets and large/generated dirs out of Cursor AI context + index.',
|
|
140
|
+
'# Edit freely — Forge only appends missing lines and never removes yours.',
|
|
141
|
+
];
|
|
142
|
+
|
|
143
|
+
function unionPreserve(existing, defaults) {
|
|
144
|
+
const base = Array.isArray(existing) ? existing.filter(v => typeof v === 'string') : [];
|
|
145
|
+
const seen = new Set(base);
|
|
146
|
+
for (const rule of defaults) {
|
|
147
|
+
if (!seen.has(rule)) {
|
|
148
|
+
base.push(rule);
|
|
149
|
+
seen.add(rule);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return base;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Merge safe permission defaults into an existing `.claude/settings.json` string
|
|
157
|
+
* (read -> merge -> write). Preserves every other settings key and every existing
|
|
158
|
+
* allow/deny/ask entry (union, no duplicates). Idempotent.
|
|
159
|
+
* @param {string} existingText
|
|
160
|
+
* @returns {string} pretty JSON with a trailing newline
|
|
161
|
+
*/
|
|
162
|
+
function mergeClaudePermissions(existingText) {
|
|
163
|
+
let obj = {};
|
|
164
|
+
if (existingText && existingText.trim()) {
|
|
165
|
+
try {
|
|
166
|
+
obj = JSON.parse(existingText);
|
|
167
|
+
} catch (err) {
|
|
168
|
+
// DATA-LOSS GUARD: never silently discard a populated-but-unparseable settings
|
|
169
|
+
// file (e.g. JSONC with comments). Signal the caller to back up and skip.
|
|
170
|
+
throw new SafetyConfigParseError('existing .claude/settings.json is not valid JSON', err);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
if (!obj || typeof obj !== 'object' || Array.isArray(obj)) obj = {};
|
|
174
|
+
const existingPerms = (obj.permissions && typeof obj.permissions === 'object' && !Array.isArray(obj.permissions))
|
|
175
|
+
? obj.permissions
|
|
176
|
+
: {};
|
|
177
|
+
existingPerms.allow = unionPreserve(existingPerms.allow, CLAUDE_PERMISSION_DEFAULTS.allow);
|
|
178
|
+
existingPerms.ask = unionPreserve(existingPerms.ask, CLAUDE_PERMISSION_DEFAULTS.ask);
|
|
179
|
+
existingPerms.deny = unionPreserve(existingPerms.deny, CLAUDE_PERMISSION_DEFAULTS.deny);
|
|
180
|
+
obj.permissions = existingPerms;
|
|
181
|
+
return JSON.stringify(obj, null, 2) + '\n';
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Merge safe ignore defaults into an existing `.cursorignore` string (read -> merge
|
|
186
|
+
* -> write). Preserves all user lines/comments verbatim and only appends default
|
|
187
|
+
* patterns that are not already present. Idempotent.
|
|
188
|
+
* @param {string} existingText
|
|
189
|
+
* @returns {string}
|
|
190
|
+
*/
|
|
191
|
+
function mergeCursorIgnore(existingText) {
|
|
192
|
+
const text = typeof existingText === 'string' ? existingText : '';
|
|
193
|
+
const existingPatterns = new Set(
|
|
194
|
+
text.split(/\r?\n/).map(line => line.trim()).filter(line => line && !line.startsWith('#')),
|
|
195
|
+
);
|
|
196
|
+
const missing = CURSORIGNORE_DEFAULTS.filter(pattern => !existingPatterns.has(pattern));
|
|
197
|
+
const base = text.replace(/\s+$/, '');
|
|
198
|
+
if (missing.length === 0) {
|
|
199
|
+
return base ? `${base}\n` : '';
|
|
200
|
+
}
|
|
201
|
+
const lines = [];
|
|
202
|
+
if (base) {
|
|
203
|
+
lines.push(base, '');
|
|
204
|
+
}
|
|
205
|
+
// Only add the Forge header block if it isn't already present — otherwise a later
|
|
206
|
+
// re-run (once CURSORIGNORE_DEFAULTS gains a new entry) would duplicate the header.
|
|
207
|
+
if (!text.includes(CURSORIGNORE_HEADER[0])) {
|
|
208
|
+
lines.push(...CURSORIGNORE_HEADER);
|
|
209
|
+
}
|
|
210
|
+
lines.push(...missing);
|
|
211
|
+
return lines.join('\n') + '\n';
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Render safe Claude permission defaults into `.claude/settings.json`.
|
|
216
|
+
* Read -> merge -> write. An unparseable existing file is BACKED UP and left
|
|
217
|
+
* untouched (never overwritten) to avoid destroying user settings.
|
|
218
|
+
* @param {object} params
|
|
219
|
+
* @param {string} params.targetRoot - Project root.
|
|
220
|
+
* @returns {{ file: string, existed: boolean, skipped: boolean, backup?: string }}
|
|
221
|
+
*/
|
|
222
|
+
function renderClaudePermissions({ targetRoot }) {
|
|
223
|
+
const filePath = path.join(targetRoot, '.claude', 'settings.json');
|
|
224
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
225
|
+
const existed = fs.existsSync(filePath);
|
|
226
|
+
const existing = existed ? fs.readFileSync(filePath, 'utf-8') : '';
|
|
227
|
+
|
|
228
|
+
let merged;
|
|
229
|
+
try {
|
|
230
|
+
merged = mergeClaudePermissions(existing);
|
|
231
|
+
} catch (err) {
|
|
232
|
+
if (err instanceof SafetyConfigParseError && existed) {
|
|
233
|
+
const backup = backupFile(filePath);
|
|
234
|
+
return { file: filePath, existed, skipped: true, backup };
|
|
235
|
+
}
|
|
236
|
+
throw err;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
fs.writeFileSync(filePath, merged, 'utf-8');
|
|
240
|
+
return { file: filePath, existed, skipped: false };
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* Render safe `.cursorignore` defaults at the project root.
|
|
245
|
+
* Read -> merge -> write, preserving user lines. Line-based, so there is no
|
|
246
|
+
* parse-failure path (never destructive).
|
|
247
|
+
* @param {object} params
|
|
248
|
+
* @param {string} params.targetRoot - Project root.
|
|
249
|
+
* @returns {{ file: string, existed: boolean, skipped: boolean }}
|
|
250
|
+
*/
|
|
251
|
+
function renderCursorIgnore({ targetRoot }) {
|
|
252
|
+
const filePath = path.join(targetRoot, '.cursorignore');
|
|
253
|
+
const existed = fs.existsSync(filePath);
|
|
254
|
+
const existing = existed ? fs.readFileSync(filePath, 'utf-8') : '';
|
|
255
|
+
const merged = mergeCursorIgnore(existing);
|
|
256
|
+
fs.writeFileSync(filePath, merged, 'utf-8');
|
|
257
|
+
return { file: filePath, existed, skipped: false };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
module.exports = {
|
|
261
|
+
SafetyConfigParseError,
|
|
262
|
+
CLAUDE_PERMISSION_DEFAULTS,
|
|
263
|
+
CURSORIGNORE_DEFAULTS,
|
|
264
|
+
mergeClaudePermissions,
|
|
265
|
+
mergeCursorIgnore,
|
|
266
|
+
renderClaudePermissions,
|
|
267
|
+
renderCursorIgnore,
|
|
268
|
+
};
|
package/lib/setup-action-log.js
CHANGED
|
@@ -11,15 +11,9 @@
|
|
|
11
11
|
const AGENT_PREFIXES = {
|
|
12
12
|
'.claude/': 'Claude Code',
|
|
13
13
|
'.cursor/': 'Cursor',
|
|
14
|
-
'.windsurf/': 'Windsurf',
|
|
15
|
-
'.cline/': 'Cline',
|
|
16
14
|
'.codex/': 'Codex',
|
|
17
15
|
'~/.codex/': 'Codex',
|
|
18
|
-
'$CODEX_HOME/': 'Codex'
|
|
19
|
-
'.opencode/': 'OpenCode',
|
|
20
|
-
'.kilocode/': 'Kilocode',
|
|
21
|
-
'.roo/': 'Roo Code',
|
|
22
|
-
'.github/prompts/': 'GitHub Copilot'
|
|
16
|
+
'$CODEX_HOME/': 'Codex'
|
|
23
17
|
};
|
|
24
18
|
|
|
25
19
|
/**
|
package/lib/setup.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const fs = require('node:fs');
|
|
2
2
|
const path = require('node:path');
|
|
3
|
+
const { cleanupDeprecatedSyncFiles } = require('./deprecated-sync-cleanup');
|
|
3
4
|
|
|
4
5
|
/**
|
|
5
6
|
* Save setup state to .forge/setup-state.json
|
|
@@ -110,76 +111,36 @@ async function markStepComplete(projectPath, stepName) {
|
|
|
110
111
|
}
|
|
111
112
|
|
|
112
113
|
/**
|
|
113
|
-
*
|
|
114
|
-
*
|
|
114
|
+
* Remove generated GitHub-Beads sync scaffold from existing installs.
|
|
115
|
+
*
|
|
116
|
+
* @param {string} projectPath - Target project root
|
|
117
|
+
* @returns {Promise<{removed: string[], skipped: string[]}>}
|
|
118
|
+
*/
|
|
119
|
+
async function cleanupDeprecatedGithubBeadsSync(projectPath, options = {}) {
|
|
120
|
+
return cleanupDeprecatedSyncFiles(projectPath, options);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Deprecated compatibility shim for the removed GitHub-Beads sync scaffold.
|
|
125
|
+
*
|
|
126
|
+
* Performs cleanup of generated legacy sync artifacts only. User-owned files
|
|
127
|
+
* at matching paths are preserved unless their content matches known generated
|
|
128
|
+
* templates.
|
|
115
129
|
*
|
|
116
130
|
* @param {string} projectPath - Target project root
|
|
117
131
|
* @param {string} pkgDir - Forge package directory (source of template files)
|
|
118
|
-
* @returns {Promise<{created: string[], skipped: string[]}>}
|
|
132
|
+
* @returns {Promise<{created: string[], skipped: string[], removed: string[], deprecated: boolean, message: string}>}
|
|
119
133
|
*/
|
|
120
134
|
async function scaffoldGithubBeadsSync(projectPath, pkgDir) {
|
|
121
|
-
const
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
'
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
const files = [
|
|
132
|
-
// Phase 1: GitHub → Beads
|
|
133
|
-
{
|
|
134
|
-
src: path.join(pkgDir, '.github', 'workflows', 'github-to-beads.yml'),
|
|
135
|
-
dest: path.join('.github', 'workflows', 'github-to-beads.yml'),
|
|
136
|
-
},
|
|
137
|
-
// Phase 2: Beads → GitHub
|
|
138
|
-
{
|
|
139
|
-
src: path.join(pkgDir, '.github', 'workflows', 'beads-to-github.yml'),
|
|
140
|
-
dest: path.join('.github', 'workflows', 'beads-to-github.yml'),
|
|
141
|
-
},
|
|
142
|
-
{
|
|
143
|
-
src: path.join(pkgDir, 'scripts', 'github-beads-sync.config.json'),
|
|
144
|
-
dest: path.join('scripts', 'github-beads-sync.config.json'),
|
|
145
|
-
},
|
|
146
|
-
{
|
|
147
|
-
src: null, // generated in-place (empty mapping template)
|
|
148
|
-
dest: path.join('.github', 'beads-mapping.json'),
|
|
149
|
-
content: '{}',
|
|
150
|
-
},
|
|
151
|
-
// Sync script modules
|
|
152
|
-
...syncScripts.map((name) => ({
|
|
153
|
-
src: path.join(pkgDir, 'scripts', 'github-beads-sync', name),
|
|
154
|
-
dest: path.join('scripts', 'github-beads-sync', name),
|
|
155
|
-
})),
|
|
156
|
-
];
|
|
157
|
-
|
|
158
|
-
for (const file of files) {
|
|
159
|
-
const destPath = path.join(projectPath, file.dest);
|
|
160
|
-
|
|
161
|
-
// Never overwrite existing files — preserve user customizations
|
|
162
|
-
if (fs.existsSync(destPath)) {
|
|
163
|
-
skipped.push(file.dest);
|
|
164
|
-
continue;
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
// Ensure parent directory exists
|
|
168
|
-
const destDir = path.dirname(destPath);
|
|
169
|
-
await fs.promises.mkdir(destDir, { recursive: true });
|
|
170
|
-
|
|
171
|
-
if (file.src) {
|
|
172
|
-
// Copy from template
|
|
173
|
-
await fs.promises.copyFile(file.src, destPath);
|
|
174
|
-
} else {
|
|
175
|
-
// Write generated content
|
|
176
|
-
await fs.promises.writeFile(destPath, file.content, 'utf-8');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
created.push(file.dest);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
return { created, skipped };
|
|
135
|
+
const cleanup = await cleanupDeprecatedGithubBeadsSync(projectPath, { packageDir: pkgDir });
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
created: [],
|
|
139
|
+
skipped: [],
|
|
140
|
+
removed: cleanup.removed,
|
|
141
|
+
deprecated: true,
|
|
142
|
+
message: 'GitHub-Beads sync scaffolding is deprecated; future GitHub issue sync must use Forge Kernel/server authority.'
|
|
143
|
+
};
|
|
183
144
|
}
|
|
184
145
|
|
|
185
146
|
module.exports = {
|
|
@@ -188,5 +149,6 @@ module.exports = {
|
|
|
188
149
|
isSetupComplete,
|
|
189
150
|
getNextStep,
|
|
190
151
|
markStepComplete,
|
|
152
|
+
cleanupDeprecatedGithubBeadsSync,
|
|
191
153
|
scaffoldGithubBeadsSync
|
|
192
154
|
};
|
package/lib/shell-utils.js
CHANGED
|
@@ -5,10 +5,62 @@
|
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
7
|
const { execFileSync, spawnSync } = require('node:child_process');
|
|
8
|
+
const path = require('node:path');
|
|
9
|
+
|
|
10
|
+
// Extensions Windows can spawn directly via CreateProcess. Anything else that
|
|
11
|
+
// `where.exe` returns — the extensionless POSIX shim (`npm`) or the batch shim
|
|
12
|
+
// (`npm.cmd`) — cannot be handed to execFileSync: the former is a bash script
|
|
13
|
+
// (spawnSync ENOENT, kernel issue 9997d516) and patched Node refuses .cmd/.bat
|
|
14
|
+
// without shell:true (CVE-2024-27980).
|
|
15
|
+
const WINDOWS_DIRECT_SPAWN_EXTS = new Set(['.exe', '.com']);
|
|
16
|
+
|
|
17
|
+
// Conservative allowlist for tokens executed through cmd.exe. Blocks every cmd
|
|
18
|
+
// metacharacter (& | < > ^ % ! " ' ` ( ) ; and whitespace) so shell execution
|
|
19
|
+
// cannot be turned into injection even if a caller ever passes dynamic input.
|
|
20
|
+
const SAFE_SHELL_TOKEN = /^[A-Za-z0-9@_+=:,./\\-]+$/;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Throw when any token is unsafe to pass through cmd.exe.
|
|
24
|
+
* @param {string} command - The command name
|
|
25
|
+
* @param {string[]} args - Command arguments
|
|
26
|
+
*/
|
|
27
|
+
function assertShellSafeTokens(command, args) {
|
|
28
|
+
for (const token of [command, ...args]) {
|
|
29
|
+
if (typeof token !== 'string' || !SAFE_SHELL_TOKEN.test(token)) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`Refusing to run "${command}" via shell: unsafe token ${JSON.stringify(token)}`
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Decide how to spawn a command on Windows from `where.exe` candidates.
|
|
39
|
+
* Prefers a directly spawnable .exe/.com — including when the caller already
|
|
40
|
+
* passed an absolute .exe/.com path (which `where.exe` may not re-enumerate);
|
|
41
|
+
* otherwise runs the BARE command name through cmd.exe (shell:true), which
|
|
42
|
+
* resolves npm.cmd/npx.cmd/lefthook.cmd via PATH/PATHEXT — so paths with spaces
|
|
43
|
+
* never hit shell parsing or the shell-safe token allowlist.
|
|
44
|
+
*
|
|
45
|
+
* @param {string} command - Original command name or absolute path
|
|
46
|
+
* @param {string[]} candidates - Resolved paths from `where.exe`, best first
|
|
47
|
+
* @returns {{ file: string, shell: boolean }}
|
|
48
|
+
*/
|
|
49
|
+
function resolveWindowsSpawnSpec(command, candidates) {
|
|
50
|
+
const direct = [command, ...candidates].find(
|
|
51
|
+
(candidate) => WINDOWS_DIRECT_SPAWN_EXTS.has(path.extname(candidate).toLowerCase())
|
|
52
|
+
);
|
|
53
|
+
if (direct) {
|
|
54
|
+
return { file: direct, shell: false };
|
|
55
|
+
}
|
|
56
|
+
return { file: command, shell: true };
|
|
57
|
+
}
|
|
8
58
|
|
|
9
59
|
/**
|
|
10
60
|
* Securely execute a command with PATH validation.
|
|
11
61
|
* Mitigates SonarCloud S4036: Ensures executables are from trusted locations.
|
|
62
|
+
* On Windows, handles npm/npx/lefthook-style cmd shims that cannot be spawned
|
|
63
|
+
* directly (see resolveWindowsSpawnSpec).
|
|
12
64
|
* @param {string} command - The command to execute
|
|
13
65
|
* @param {string[]} [args=[]] - Command arguments
|
|
14
66
|
* @param {object} [options={}] - execFileSync options
|
|
@@ -18,16 +70,16 @@ function secureExecFileSync(command, args = [], options = {}) {
|
|
|
18
70
|
const {
|
|
19
71
|
_execFileSync = execFileSync,
|
|
20
72
|
_spawnSync = spawnSync,
|
|
73
|
+
_platform = process.platform,
|
|
21
74
|
...execOptions
|
|
22
75
|
} = options;
|
|
23
76
|
|
|
24
|
-
|
|
77
|
+
const isWindows = _platform === 'win32';
|
|
78
|
+
const pathResolver = isWindows ? 'where.exe' : 'which';
|
|
25
79
|
|
|
80
|
+
let candidates = [];
|
|
26
81
|
try {
|
|
27
82
|
// Resolve command's full path to validate it's in a trusted location
|
|
28
|
-
const isWindows = process.platform === 'win32';
|
|
29
|
-
const pathResolver = isWindows ? 'where.exe' : 'which';
|
|
30
|
-
|
|
31
83
|
const result = _spawnSync(pathResolver, [command], {
|
|
32
84
|
encoding: 'utf8',
|
|
33
85
|
stdio: ['ignore', 'pipe', 'ignore']
|
|
@@ -35,14 +87,32 @@ function secureExecFileSync(command, args = [], options = {}) {
|
|
|
35
87
|
|
|
36
88
|
if (result.status === 0 && result.stdout) {
|
|
37
89
|
// Handle both CRLF (Windows) and LF (Unix) line endings
|
|
38
|
-
|
|
90
|
+
candidates = result.stdout
|
|
91
|
+
.trim()
|
|
92
|
+
.split(/\r?\n/)
|
|
93
|
+
.map((line) => line.trim())
|
|
94
|
+
.filter(Boolean);
|
|
39
95
|
}
|
|
40
96
|
} catch (_err) { // NOSONAR - S2486: Intentionally ignored; falls back to direct command execution below
|
|
41
97
|
}
|
|
42
98
|
|
|
99
|
+
if (isWindows) {
|
|
100
|
+
const spec = resolveWindowsSpawnSpec(command, candidates);
|
|
101
|
+
if (spec.shell) {
|
|
102
|
+
assertShellSafeTokens(command, args);
|
|
103
|
+
// Node 22 (DEP0190) deprecates passing an args array together with shell:true.
|
|
104
|
+
// assertShellSafeTokens has already guaranteed every token is free of shell
|
|
105
|
+
// metacharacters, so fold them into a single command line and pass no args array —
|
|
106
|
+
// identical execution through cmd.exe (npm.cmd/npx.cmd shims), no deprecation warning.
|
|
107
|
+
const commandLine = [command, ...args].join(' ');
|
|
108
|
+
return _execFileSync(commandLine, [], { ...execOptions, shell: true });
|
|
109
|
+
}
|
|
110
|
+
return _execFileSync(spec.file, args, execOptions);
|
|
111
|
+
}
|
|
112
|
+
|
|
43
113
|
// Fall back only when resolution failed. If execution of the resolved binary
|
|
44
114
|
// throws, propagate that error instead of retrying with the unresolved name.
|
|
45
|
-
return _execFileSync(
|
|
115
|
+
return _execFileSync(candidates[0] || command, args, execOptions);
|
|
46
116
|
}
|
|
47
117
|
|
|
48
118
|
module.exports = {
|