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
package/scripts/check-agents.js
CHANGED
|
@@ -5,11 +5,12 @@
|
|
|
5
5
|
*
|
|
6
6
|
* CLI command that validates all agent configs are complete and in sync.
|
|
7
7
|
*
|
|
8
|
-
* 1. Runs
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
8
|
+
* 1. Runs the skills drift check (lib/skills-sync) to verify generated agent
|
|
9
|
+
* skill mirrors (e.g. .codex/skills) match the canonical root skills/ source.
|
|
10
|
+
* 2. Reads lib/agents/*.plugin.json to validate each plugin's schema/parity
|
|
11
|
+
* (support metadata, rules/skills scaffold paths, deprecated-tier consistency).
|
|
12
|
+
* 3. Reports: skill drift, missing/stale skills, plugin parity errors.
|
|
13
|
+
* 4. Exits 0 if all clean, exits non-zero if issues found.
|
|
13
14
|
*
|
|
14
15
|
* Usage:
|
|
15
16
|
* node scripts/check-agents.js
|
|
@@ -18,51 +19,22 @@
|
|
|
18
19
|
* checkAgents(repoRoot) -> { errors: string[], warnings: string[] }
|
|
19
20
|
*/
|
|
20
21
|
|
|
21
|
-
const { syncCommands } = require('./sync-commands');
|
|
22
|
-
const { AGENT_ADAPTERS } = require('./sync-commands');
|
|
23
22
|
const path = require('path');
|
|
24
23
|
const fs = require('fs');
|
|
24
|
+
const { checkSkillsSync } = require('../lib/skills-sync');
|
|
25
25
|
const { validatePluginSchema } = require('../lib/plugin-manager');
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function normalizeRelativeDir(dir) {
|
|
39
|
-
if (!dir) return null;
|
|
40
|
-
return String(dir).replace(/\\/g, '/').replace(/\/+$/, '');
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function getDeclaredCommandDir(plugin) {
|
|
44
|
-
const dirs = plugin.directories || {};
|
|
45
|
-
return dirs.commands || dirs.workflows || dirs.prompts || dirs.skills || null;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function getExpectedCommandDir(plugin) {
|
|
49
|
-
const adapterId = SYNC_ADAPTER_BY_PLUGIN_ID[plugin.id];
|
|
50
|
-
if (!adapterId) {
|
|
51
|
-
return null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
const adapter = AGENT_ADAPTERS[adapterId];
|
|
55
|
-
if (!adapter) {
|
|
56
|
-
return null;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
if (plugin.id === 'codex') {
|
|
60
|
-
return normalizeRelativeDir(plugin.directories?.skills || adapter.baseDir);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
return normalizeRelativeDir(adapter.baseDir);
|
|
64
|
-
}
|
|
65
|
-
|
|
27
|
+
/**
|
|
28
|
+
* Validate a single plugin's schema + capability/scaffold parity.
|
|
29
|
+
*
|
|
30
|
+
* Command-surface parity is intentionally NOT validated: Forge is skills-only
|
|
31
|
+
* (the .claude/commands/.cursor/commands/.codex/skills command surface was
|
|
32
|
+
* removed in PR-A0). The surviving checks cover support metadata, the rules and
|
|
33
|
+
* skills scaffold paths, and deprecated-tier consistency.
|
|
34
|
+
*
|
|
35
|
+
* @param {object} plugin
|
|
36
|
+
* @param {string[]} errors - mutated with any parity errors
|
|
37
|
+
*/
|
|
66
38
|
function validatePluginParity(plugin, errors) {
|
|
67
39
|
const validation = validatePluginSchema(plugin);
|
|
68
40
|
if (!validation.valid) {
|
|
@@ -78,25 +50,6 @@ function validatePluginParity(plugin, errors) {
|
|
|
78
50
|
const dirs = plugin.directories || {};
|
|
79
51
|
const supportStatus = plugin.support?.status;
|
|
80
52
|
|
|
81
|
-
if (capabilities.commands) {
|
|
82
|
-
const expectedDir = getExpectedCommandDir(plugin);
|
|
83
|
-
const declaredDir = normalizeRelativeDir(getDeclaredCommandDir(plugin));
|
|
84
|
-
|
|
85
|
-
if (!expectedDir) {
|
|
86
|
-
errors.push(
|
|
87
|
-
`Plugin "${plugin.name}" (${plugin.id}) declares commands support but has no Forge sync adapter.`
|
|
88
|
-
);
|
|
89
|
-
} else if (!declaredDir) {
|
|
90
|
-
errors.push(
|
|
91
|
-
`Plugin "${plugin.name}" (${plugin.id}) declares commands support but has no command directory configured.`
|
|
92
|
-
);
|
|
93
|
-
} else if (declaredDir !== expectedDir) {
|
|
94
|
-
errors.push(
|
|
95
|
-
`Plugin "${plugin.name}" (${plugin.id}) command directory "${declaredDir}" does not match sync output "${expectedDir}".`
|
|
96
|
-
);
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
|
|
100
53
|
if (capabilities.rules && !dirs.rules) {
|
|
101
54
|
errors.push(
|
|
102
55
|
`Plugin "${plugin.name}" (${plugin.id}) declares rules capability but has no scaffold path configured.`
|
|
@@ -136,47 +89,34 @@ function checkAgents(repoRoot) {
|
|
|
136
89
|
/** @type {string[]} */
|
|
137
90
|
const warnings = [];
|
|
138
91
|
|
|
139
|
-
// ---- 1.
|
|
140
|
-
let
|
|
92
|
+
// ---- 1. Skills drift check ----
|
|
93
|
+
let driftResult;
|
|
141
94
|
try {
|
|
142
|
-
|
|
95
|
+
driftResult = checkSkillsSync({ repoRoot });
|
|
143
96
|
} catch (err) {
|
|
144
|
-
errors.push(`
|
|
97
|
+
errors.push(`Skills sync check failed: ${err.message}`);
|
|
145
98
|
return { errors, warnings };
|
|
146
99
|
}
|
|
147
100
|
|
|
148
|
-
|
|
149
|
-
|
|
101
|
+
const canonicalSkillsDir = path.join(repoRoot, 'skills');
|
|
102
|
+
if (!fs.existsSync(canonicalSkillsDir)) {
|
|
103
|
+
errors.push('No canonical skills found in skills/ — cannot verify skill sync.');
|
|
150
104
|
return { errors, warnings };
|
|
151
|
-
}
|
|
152
|
-
if (syncResult.manifestMissing) {
|
|
153
|
-
warnings.push(
|
|
154
|
-
'Sync manifest (.forge/sync-manifest.json) not found — stale file detection skipped. ' +
|
|
155
|
-
'Run "node scripts/sync-commands.js" to generate it.'
|
|
156
|
-
);
|
|
157
|
-
}
|
|
105
|
+
}
|
|
158
106
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
}
|
|
166
|
-
}
|
|
107
|
+
if (driftResult.checkedAgents.length === 0) {
|
|
108
|
+
warnings.push(
|
|
109
|
+
'No generated agent skill directories found (e.g. .codex/skills) — skill drift check skipped. ' +
|
|
110
|
+
'Run "forge setup" or "skills sync" to populate them.'
|
|
111
|
+
);
|
|
112
|
+
}
|
|
167
113
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
const relPath = path.relative(repoRoot, filePath);
|
|
171
|
-
errors.push(`Stale file (no longer generated, should be removed): ${relPath}`);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
114
|
+
for (const d of driftResult.drift) {
|
|
115
|
+
errors.push(`Out of sync [${d.agent}]: ${d.skill}/${d.file} (${d.status})`);
|
|
174
116
|
}
|
|
175
117
|
|
|
176
118
|
// ---- 2. Validate plugin catalog ----
|
|
177
119
|
const pluginDir = path.join(repoRoot, 'lib', 'agents');
|
|
178
|
-
/** @type {object[]} */
|
|
179
|
-
const plugins = [];
|
|
180
120
|
|
|
181
121
|
if (!fs.existsSync(pluginDir)) {
|
|
182
122
|
warnings.push('No plugin directory found at lib/agents/ — plugin catalog validation skipped.');
|
|
@@ -190,54 +130,11 @@ function checkAgents(repoRoot) {
|
|
|
190
130
|
for (const file of pluginFiles) {
|
|
191
131
|
try {
|
|
192
132
|
const plugin = JSON.parse(fs.readFileSync(path.join(pluginDir, file), 'utf8'));
|
|
193
|
-
plugins.push(plugin);
|
|
194
133
|
validatePluginParity(plugin, errors);
|
|
195
134
|
} catch (_err) {
|
|
196
135
|
errors.push(`Failed to parse plugin file: lib/agents/${file}`);
|
|
197
136
|
}
|
|
198
137
|
}
|
|
199
|
-
|
|
200
|
-
// For each plugin with commands: true, verify its command directory has files
|
|
201
|
-
for (const plugin of plugins) {
|
|
202
|
-
if (!plugin.capabilities || !plugin.capabilities.commands) {
|
|
203
|
-
continue;
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
// Find the command directory from the plugin's directories config
|
|
207
|
-
const dirs = plugin.directories || {};
|
|
208
|
-
// Plugins use different keys: commands, workflows, prompts, skills
|
|
209
|
-
const commandDir = dirs.commands || dirs.workflows || dirs.prompts;
|
|
210
|
-
|
|
211
|
-
if (!commandDir) {
|
|
212
|
-
// Plugin declares commands: true but has no command directory configured
|
|
213
|
-
// Codex is the only agent that uses skills as its command mechanism
|
|
214
|
-
if (plugin.id === 'codex' && dirs.skills) {
|
|
215
|
-
// Codex CLI: commands live in .codex/skills/<name>/SKILL.md — skip standard check
|
|
216
|
-
continue;
|
|
217
|
-
}
|
|
218
|
-
warnings.push(
|
|
219
|
-
`Plugin "${plugin.name}" (${plugin.id}) has commands: true but no command directory configured.`
|
|
220
|
-
);
|
|
221
|
-
continue;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const absCmdDir = path.join(repoRoot, commandDir);
|
|
225
|
-
|
|
226
|
-
if (!fs.existsSync(absCmdDir)) {
|
|
227
|
-
errors.push(
|
|
228
|
-
`Plugin "${plugin.name}" (${plugin.id}) has commands: true but directory "${commandDir}" does not exist.`
|
|
229
|
-
);
|
|
230
|
-
continue;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
// Check the directory has at least one file
|
|
234
|
-
const files = fs.readdirSync(absCmdDir).filter((f) => !f.startsWith('.'));
|
|
235
|
-
if (files.length === 0) {
|
|
236
|
-
errors.push(
|
|
237
|
-
`Plugin "${plugin.name}" (${plugin.id}) has commands: true but directory "${commandDir}" is empty.`
|
|
238
|
-
);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
138
|
}
|
|
242
139
|
|
|
243
140
|
return { errors, warnings };
|
package/scripts/commitlint.js
CHANGED
|
@@ -12,7 +12,9 @@ const isWindows = process.platform === 'win32';
|
|
|
12
12
|
|
|
13
13
|
const commitMsgFile = process.argv[2];
|
|
14
14
|
if (!commitMsgFile) {
|
|
15
|
-
|
|
15
|
+
// Write synchronously so the message is flushed before exit: on Windows the
|
|
16
|
+
// stderr pipe is async and process.exit() can otherwise truncate it.
|
|
17
|
+
require('node:fs').writeSync(2, '❌ No commit message file provided\n');
|
|
16
18
|
process.exit(1);
|
|
17
19
|
}
|
|
18
20
|
|
|
@@ -17,6 +17,9 @@
|
|
|
17
17
|
set -euo pipefail
|
|
18
18
|
|
|
19
19
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
20
|
+
if [[ -f "$SCRIPT_DIR/bootstrap-windows-tools.sh" ]]; then
|
|
21
|
+
source "$SCRIPT_DIR/bootstrap-windows-tools.sh"
|
|
22
|
+
fi
|
|
20
23
|
|
|
21
24
|
# Source file-index helpers (file_index_read, file_index_get)
|
|
22
25
|
source "$SCRIPT_DIR/file-index.sh" || { echo "FATAL: failed to source file-index.sh" >&2; exit 2; }
|
package/scripts/dep-guard.sh
CHANGED
|
@@ -14,9 +14,28 @@ set -euo pipefail
|
|
|
14
14
|
|
|
15
15
|
# Source shared sanitize library
|
|
16
16
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
17
|
+
if [[ -f "$SCRIPT_DIR/bootstrap-windows-tools.sh" ]]; then
|
|
18
|
+
source "$SCRIPT_DIR/bootstrap-windows-tools.sh"
|
|
19
|
+
fi
|
|
17
20
|
NODE_CMD="${NODE_CMD:-node}"
|
|
18
21
|
source "$SCRIPT_DIR/lib/sanitize.sh"
|
|
19
22
|
|
|
23
|
+
node_script_path() {
|
|
24
|
+
case "$1" in
|
|
25
|
+
/[A-Za-z]/*)
|
|
26
|
+
_drive="$(printf '%s' "${1:1:1}" | tr '[:lower:]' '[:upper:]')"
|
|
27
|
+
printf '%s:/%s\n' "$_drive" "${1:3}"
|
|
28
|
+
;;
|
|
29
|
+
*)
|
|
30
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
31
|
+
cygpath -m "$1"
|
|
32
|
+
else
|
|
33
|
+
printf '%s\n' "$1"
|
|
34
|
+
fi
|
|
35
|
+
;;
|
|
36
|
+
esac
|
|
37
|
+
}
|
|
38
|
+
|
|
20
39
|
# ---- Helpers ----------------------------------------------------------------
|
|
21
40
|
|
|
22
41
|
usage() {
|
|
@@ -226,12 +245,12 @@ run_phase3_analyzer() {
|
|
|
226
245
|
"${in_progress_json:-[]}" \
|
|
227
246
|
"$(printf '%s' "$task_file" | jq -R '.')" \
|
|
228
247
|
"$(printf '%s' "$repository_root" | jq -R '.')" \
|
|
229
|
-
| "$NODE_CMD" "$analyzer_script" --stdin
|
|
248
|
+
| "$NODE_CMD" "$(node_script_path "$analyzer_script")" --stdin
|
|
230
249
|
}
|
|
231
250
|
|
|
232
251
|
render_phase3_review() {
|
|
233
252
|
local renderer_script="${DEP_GUARD_RENDER_SCRIPT:-scripts/dep-guard-render-review.js}"
|
|
234
|
-
"$NODE_CMD" "$renderer_script"
|
|
253
|
+
"$NODE_CMD" "$(node_script_path "$renderer_script")"
|
|
235
254
|
}
|
|
236
255
|
|
|
237
256
|
# ---- Subcommands ------------------------------------------------------------
|
|
@@ -344,7 +363,7 @@ cmd_check_ripple_keyword_v1() {
|
|
|
344
363
|
ISSUE_ID="$issue_id" \
|
|
345
364
|
SOURCE_TITLE="$src_title" \
|
|
346
365
|
LIST_OUTPUT="$list_output" \
|
|
347
|
-
"$NODE_CMD" "$SCRIPT_DIR/dep-guard-keyword-ripple.js"
|
|
366
|
+
"$NODE_CMD" "$(node_script_path "$SCRIPT_DIR/dep-guard-keyword-ripple.js")"
|
|
348
367
|
return 0
|
|
349
368
|
|
|
350
369
|
}
|
package/scripts/file-index.sh
CHANGED
|
@@ -21,6 +21,9 @@ fi
|
|
|
21
21
|
|
|
22
22
|
# Source shared libraries
|
|
23
23
|
_FI_SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
24
|
+
if [[ -f "$_FI_SCRIPT_DIR/bootstrap-windows-tools.sh" ]]; then
|
|
25
|
+
source "$_FI_SCRIPT_DIR/bootstrap-windows-tools.sh"
|
|
26
|
+
fi
|
|
24
27
|
source "$_FI_SCRIPT_DIR/lib/sanitize.sh"
|
|
25
28
|
if [[ -f "$_FI_SCRIPT_DIR/lib/jsonl-lock.sh" ]]; then
|
|
26
29
|
source "$_FI_SCRIPT_DIR/lib/jsonl-lock.sh"
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
# forge_team_claim — Top-level dispatcher entry point
|
|
11
11
|
#
|
|
12
12
|
# Env overrides (for testing):
|
|
13
|
-
# GH_CMD
|
|
14
|
-
#
|
|
13
|
+
# GH_CMD — Path to gh binary
|
|
14
|
+
# FORGE_CMD — Path to forge binary
|
|
15
15
|
#
|
|
16
16
|
# This file does NOT set errexit/pipefail — callers manage their own shell options.
|
|
17
17
|
|
|
@@ -61,16 +61,22 @@ _claim_prompt() {
|
|
|
61
61
|
fi
|
|
62
62
|
}
|
|
63
63
|
|
|
64
|
-
# _get_github_issue_number <
|
|
65
|
-
# Extracts
|
|
64
|
+
# _get_github_issue_number <issue-id>
|
|
65
|
+
# Extracts the GitHub issue number from the issue's github_issue:<n> label.
|
|
66
|
+
# Returns the number or empty.
|
|
66
67
|
_get_github_issue_number() {
|
|
67
|
-
local
|
|
68
|
-
local
|
|
69
|
-
local
|
|
70
|
-
|
|
68
|
+
local issue_id="$1"
|
|
69
|
+
local forge_cmd="${FORGE_CMD:-forge}"
|
|
70
|
+
local show_output
|
|
71
|
+
show_output="$("$forge_cmd" issue show "$issue_id" --json 2>/dev/null)" || return 1
|
|
71
72
|
|
|
72
73
|
local issue_num
|
|
73
|
-
issue_num="$(printf '%s' "$
|
|
74
|
+
issue_num="$(printf '%s' "$show_output" | jq -r '
|
|
75
|
+
(.data.issue // .data // {})
|
|
76
|
+
| (.labels // [])
|
|
77
|
+
| map(select(startswith("github_issue:")))
|
|
78
|
+
| (.[0] // "")
|
|
79
|
+
| ltrimstr("github_issue:")' 2>/dev/null)"
|
|
74
80
|
if [[ -z "$issue_num" ]]; then
|
|
75
81
|
return 1
|
|
76
82
|
fi
|
|
@@ -90,7 +96,7 @@ _get_github_assignee() {
|
|
|
90
96
|
# _claim_lock_dir — Returns the path to the claim lock directory
|
|
91
97
|
_claim_lock_dir() {
|
|
92
98
|
local root="${TEAM_MAP_ROOT:-.}"
|
|
93
|
-
printf '%s' "$root/.
|
|
99
|
+
printf '%s' "$root/.forge"
|
|
94
100
|
}
|
|
95
101
|
|
|
96
102
|
# ── Public API ───────────────────────────────────────────────────────────
|
|
@@ -149,7 +155,7 @@ claim_with_lock() {
|
|
|
149
155
|
fi
|
|
150
156
|
|
|
151
157
|
local gh_cmd="${GH_CMD:-gh}"
|
|
152
|
-
local
|
|
158
|
+
local forge_cmd="${FORGE_CMD:-forge}"
|
|
153
159
|
local lock_dir
|
|
154
160
|
lock_dir="$(_claim_lock_dir)"
|
|
155
161
|
mkdir -p "$lock_dir"
|
|
@@ -186,8 +192,13 @@ claim_with_lock() {
|
|
|
186
192
|
exit 1
|
|
187
193
|
fi
|
|
188
194
|
fi
|
|
189
|
-
# Claim on both
|
|
190
|
-
|
|
195
|
+
# Claim on both the Forge issue backend and GitHub. The Forge claim is the
|
|
196
|
+
# authoritative backend update, so a failure must abort (exit the flock
|
|
197
|
+
# subshell non-zero); the GitHub assignee edit stays best-effort.
|
|
198
|
+
"$forge_cmd" issue claim "$beads_id" >/dev/null 2>&1 || {
|
|
199
|
+
_claim_error "Failed to claim $beads_id in Forge"
|
|
200
|
+
exit 1
|
|
201
|
+
}
|
|
191
202
|
"$gh_cmd" issue edit "$issue_num" --add-assignee "$current_user" >/dev/null 2>&1 || true
|
|
192
203
|
) 200>"$lock_file"
|
|
193
204
|
local subshell_rc=$?
|
|
@@ -215,8 +226,13 @@ claim_with_lock() {
|
|
|
215
226
|
return 1
|
|
216
227
|
fi
|
|
217
228
|
fi
|
|
218
|
-
# Claim on both
|
|
219
|
-
|
|
229
|
+
# Claim on both the Forge issue backend and GitHub. The Forge claim is the
|
|
230
|
+
# authoritative backend update, so a failure must abort (return non-zero so
|
|
231
|
+
# the RETURN trap releases the lock); the GitHub assignee edit stays best-effort.
|
|
232
|
+
"$forge_cmd" issue claim "$beads_id" >/dev/null 2>&1 || {
|
|
233
|
+
_claim_error "Failed to claim $beads_id in Forge"
|
|
234
|
+
return 1
|
|
235
|
+
}
|
|
220
236
|
"$gh_cmd" issue edit "$issue_num" --add-assignee "$current_user" >/dev/null 2>&1 || true
|
|
221
237
|
fi
|
|
222
238
|
|
|
@@ -234,13 +250,13 @@ forge_team_claim() {
|
|
|
234
250
|
return 1
|
|
235
251
|
fi
|
|
236
252
|
|
|
237
|
-
local
|
|
253
|
+
local forge_cmd="${FORGE_CMD:-forge}"
|
|
238
254
|
|
|
239
255
|
if [[ "$force_flag" == "--force" ]]; then
|
|
240
256
|
# Skip pre-claim check, claim directly (pass --force to skip lock re-check too)
|
|
241
257
|
claim_with_lock "$beads_id" "--force" || return 1
|
|
242
|
-
# Log override
|
|
243
|
-
"$
|
|
258
|
+
# Log the override as an issue comment
|
|
259
|
+
"$forge_cmd" issue comment "$beads_id" "Force-claimed by $(get_github_user 2>/dev/null || echo 'unknown')" >/dev/null 2>&1 || true
|
|
244
260
|
_claim_info "$beads_id force-claimed successfully"
|
|
245
261
|
return 0
|
|
246
262
|
fi
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
# dashboard.sh — Team health dashboard for forge-team.
|
|
3
3
|
#
|
|
4
4
|
# Provides cmd_dashboard [--format=json] which aggregates team issue data
|
|
5
|
-
# from
|
|
6
|
-
# blocked issues.
|
|
5
|
+
# from the Forge issue backend and displays per-developer stats, stale
|
|
6
|
+
# assignments, and blocked issues.
|
|
7
7
|
|
|
8
8
|
# ── Dependencies ─────────────────────────────────────────────────────────
|
|
9
|
-
#
|
|
10
|
-
|
|
9
|
+
# FORGE_CMD — path to forge binary (default: forge)
|
|
10
|
+
FORGE_CMD="${FORGE_CMD:-forge}"
|
|
11
11
|
|
|
12
12
|
# FORGE_NOW — override current epoch for testing (default: real time)
|
|
13
13
|
_now() {
|
|
@@ -45,73 +45,46 @@ _iso_to_epoch() {
|
|
|
45
45
|
|
|
46
46
|
# ── Data Collection ──────────────────────────────────────────────────────
|
|
47
47
|
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
echo "
|
|
59
|
-
done
|
|
48
|
+
# _forge_active_issues_json — Merged JSON array of open + in_progress issues.
|
|
49
|
+
# The Kernel list filter does not accept comma-joined statuses, so query each
|
|
50
|
+
# status separately and concatenate the issue arrays.
|
|
51
|
+
_forge_active_issues_json() {
|
|
52
|
+
local open_json inprog_json
|
|
53
|
+
open_json="$("$FORGE_CMD" issue list --status=open --json 2>/dev/null)" || open_json=""
|
|
54
|
+
inprog_json="$("$FORGE_CMD" issue list --status=in_progress --json 2>/dev/null)" || inprog_json=""
|
|
55
|
+
jq -n \
|
|
56
|
+
--argjson a "${open_json:-null}" \
|
|
57
|
+
--argjson b "${inprog_json:-null}" \
|
|
58
|
+
'(((($a.data.issues) // []) + (($b.data.issues) // [])))' 2>/dev/null || echo "[]"
|
|
60
59
|
}
|
|
61
60
|
|
|
62
|
-
#
|
|
63
|
-
#
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
fi
|
|
90
|
-
|
|
91
|
-
# DEPENDS ON section
|
|
92
|
-
if [[ "$line" == "DEPENDS ON" ]]; then
|
|
93
|
-
in_depends=1
|
|
94
|
-
continue
|
|
95
|
-
fi
|
|
96
|
-
|
|
97
|
-
if [[ $in_depends -eq 1 && "$line" =~ →.*([a-z]+-[a-z]+):.* ]]; then
|
|
98
|
-
# Extract dependency issue ID
|
|
99
|
-
local dep_id
|
|
100
|
-
dep_id="$(echo "$line" | sed -E 's/.*→ +[^ ]+ +([a-z]+-[a-z0-9]+):.*/\1/')"
|
|
101
|
-
if [[ -n "$blocked_by" ]]; then
|
|
102
|
-
blocked_by="$blocked_by,$dep_id"
|
|
103
|
-
else
|
|
104
|
-
blocked_by="$dep_id"
|
|
105
|
-
fi
|
|
106
|
-
fi
|
|
107
|
-
done <<< "$show_output"
|
|
108
|
-
|
|
109
|
-
local updated_epoch=0
|
|
110
|
-
if [[ -n "$updated" ]]; then
|
|
111
|
-
updated_epoch="$(_iso_to_epoch "$updated")"
|
|
112
|
-
fi
|
|
113
|
-
|
|
114
|
-
echo "$issue_id|$title|$status|$owner|$updated_epoch|$blocked_by"
|
|
61
|
+
# _issue_detail_lines <issues-json>
|
|
62
|
+
# Emit one tab-delimited (jq @tsv) line per issue:
|
|
63
|
+
# id<TAB>title<TAB>STATUS<TAB>owner<TAB>updated_iso<TAB>blocked_by
|
|
64
|
+
# @tsv escapes any tab/newline/CR inside fields (issue titles are user-controlled
|
|
65
|
+
# and may contain '|', tabs, or newlines), so parsing stays field-aligned.
|
|
66
|
+
# STATUS is the upper-cased Kernel status; owner is assignee/claimed_by.
|
|
67
|
+
# A Kernel issue's `.dependencies` lists the issues it BLOCKS (outgoing edges),
|
|
68
|
+
# so blocked_by is computed by reverse scan: the issues (within this active set)
|
|
69
|
+
# whose `.dependencies` include this issue.
|
|
70
|
+
_issue_detail_lines() {
|
|
71
|
+
printf '%s' "$1" | jq -r '
|
|
72
|
+
. as $all
|
|
73
|
+
| $all[]
|
|
74
|
+
| . as $x
|
|
75
|
+
| [ $x.id,
|
|
76
|
+
($x.title // ""),
|
|
77
|
+
(($x.status // "open") | ascii_upcase),
|
|
78
|
+
($x.assignee // $x.claimed_by // ""),
|
|
79
|
+
(($x.updated_at // "") | sub("\\.[0-9]+Z$"; "Z")),
|
|
80
|
+
( [ $all[]
|
|
81
|
+
| select( (.dependencies // [])
|
|
82
|
+
| map(if type == "object" then (.id // .to // "") else . end)
|
|
83
|
+
| any(. == $x.id) )
|
|
84
|
+
| .id ]
|
|
85
|
+
| join(",") )
|
|
86
|
+
]
|
|
87
|
+
| @tsv' | tr -d '\r'
|
|
115
88
|
}
|
|
116
89
|
|
|
117
90
|
# ── Dashboard Command ────────────────────────────────────────────────────
|
|
@@ -128,12 +101,14 @@ cmd_dashboard() {
|
|
|
128
101
|
esac
|
|
129
102
|
done
|
|
130
103
|
|
|
131
|
-
# 1. Get all open/in_progress issues
|
|
132
|
-
local
|
|
133
|
-
|
|
104
|
+
# 1. Get all open/in_progress issues as a merged JSON array
|
|
105
|
+
local issues_json
|
|
106
|
+
issues_json="$(_forge_active_issues_json)"
|
|
134
107
|
|
|
135
108
|
# Check for empty
|
|
136
|
-
|
|
109
|
+
local active_count
|
|
110
|
+
active_count="$(printf '%s' "$issues_json" | jq 'length' 2>/dev/null || echo 0)"
|
|
111
|
+
if [[ "$active_count" -eq 0 ]]; then
|
|
137
112
|
if [[ "$format" == "json" ]]; then
|
|
138
113
|
echo '{"developers":{},"total":0,"stale":[],"blocked":[],"message":"No active issues — team is clear"}'
|
|
139
114
|
else
|
|
@@ -142,12 +117,12 @@ cmd_dashboard() {
|
|
|
142
117
|
return 0
|
|
143
118
|
fi
|
|
144
119
|
|
|
145
|
-
# 2.
|
|
120
|
+
# 2. Build per-issue detail lines: id|title|STATUS|owner|updated_iso|blocked_by
|
|
146
121
|
local issues=()
|
|
147
122
|
while IFS= read -r entry; do
|
|
148
123
|
[[ -z "$entry" ]] && continue
|
|
149
124
|
issues+=("$entry")
|
|
150
|
-
done <<< "$(
|
|
125
|
+
done <<< "$(_issue_detail_lines "$issues_json")"
|
|
151
126
|
|
|
152
127
|
# 3. Collect details for each issue
|
|
153
128
|
local now_epoch
|
|
@@ -163,12 +138,12 @@ cmd_dashboard() {
|
|
|
163
138
|
local total_open=0 total_inprogress=0 total_blocked=0 total_stale=0
|
|
164
139
|
|
|
165
140
|
for entry in "${issues[@]}"; do
|
|
166
|
-
|
|
141
|
+
IFS=$'\t' read -r _id _title _status _owner _updated_iso _blocked_by <<< "$entry"
|
|
167
142
|
|
|
168
|
-
local
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
143
|
+
local _updated_epoch=0
|
|
144
|
+
if [[ -n "$_updated_iso" ]]; then
|
|
145
|
+
_updated_epoch="$(_iso_to_epoch "$_updated_iso")"
|
|
146
|
+
fi
|
|
172
147
|
|
|
173
148
|
# Default owner if empty
|
|
174
149
|
[[ -z "$_owner" ]] && _owner="unassigned"
|
|
@@ -199,7 +174,7 @@ cmd_dashboard() {
|
|
|
199
174
|
if [[ -n "$_blocked_by" ]]; then
|
|
200
175
|
dev_blocked[$_owner]=$(( ${dev_blocked[$_owner]} + 1 ))
|
|
201
176
|
total_blocked=$((total_blocked + 1))
|
|
202
|
-
blocked_entries+=("$_id
|
|
177
|
+
blocked_entries+=("$_id"$'\t'"$_owner"$'\t'"$_title"$'\t'"$_blocked_by")
|
|
203
178
|
fi
|
|
204
179
|
|
|
205
180
|
# Check for stale (>48h since last update)
|
|
@@ -209,7 +184,7 @@ cmd_dashboard() {
|
|
|
209
184
|
local hours=$(( age / 3600 ))
|
|
210
185
|
dev_stale[$_owner]=$(( ${dev_stale[$_owner]} + 1 ))
|
|
211
186
|
total_stale=$((total_stale + 1))
|
|
212
|
-
stale_entries+=("$_id
|
|
187
|
+
stale_entries+=("$_id"$'\t'"$_owner"$'\t'"$_title"$'\t'"${hours}h since last update")
|
|
213
188
|
fi
|
|
214
189
|
fi
|
|
215
190
|
done
|
|
@@ -275,7 +250,7 @@ _dashboard_text() {
|
|
|
275
250
|
echo "Stale assignments (>48h):"
|
|
276
251
|
local entry
|
|
277
252
|
for entry in "${stale_entries[@]}"; do
|
|
278
|
-
IFS
|
|
253
|
+
IFS=$'\t' read -r _id _owner _title _age <<< "$entry"
|
|
279
254
|
echo " ⚠ $_id [$_owner] — $_title ($_age)"
|
|
280
255
|
done
|
|
281
256
|
fi
|
|
@@ -286,7 +261,7 @@ _dashboard_text() {
|
|
|
286
261
|
echo "Blocked issues:"
|
|
287
262
|
local entry
|
|
288
263
|
for entry in "${blocked_entries[@]}"; do
|
|
289
|
-
IFS
|
|
264
|
+
IFS=$'\t' read -r _id _owner _title _deps <<< "$entry"
|
|
290
265
|
echo " ⚠ $_id [$_owner] — $_title (blocked by $_deps)"
|
|
291
266
|
done
|
|
292
267
|
fi
|
|
@@ -321,7 +296,7 @@ _dashboard_json() {
|
|
|
321
296
|
for entry in "${stale_entries[@]}"; do
|
|
322
297
|
[[ $first -eq 0 ]] && stale_json+=","
|
|
323
298
|
first=0
|
|
324
|
-
IFS
|
|
299
|
+
IFS=$'\t' read -r _id _owner _title _age <<< "$entry"
|
|
325
300
|
stale_json+="$(jq -n -c --arg id "$_id" --arg owner "$_owner" --arg title "$_title" --arg age "$_age" '{id:$id,owner:$owner,title:$title,age:$age}')"
|
|
326
301
|
done
|
|
327
302
|
stale_json+="]"
|
|
@@ -332,7 +307,7 @@ _dashboard_json() {
|
|
|
332
307
|
for entry in "${blocked_entries[@]}"; do
|
|
333
308
|
[[ $first -eq 0 ]] && blocked_json+=","
|
|
334
309
|
first=0
|
|
335
|
-
IFS
|
|
310
|
+
IFS=$'\t' read -r _id _owner _title _deps <<< "$entry"
|
|
336
311
|
blocked_json+="$(jq -n -c --arg id "$_id" --arg owner "$_owner" --arg title "$_title" --arg deps "$_deps" '{id:$id,owner:$owner,title:$title,blocked_by:$deps}')"
|
|
337
312
|
done
|
|
338
313
|
blocked_json+="]"
|