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,863 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('node:fs');
|
|
4
|
+
const path = require('node:path');
|
|
5
|
+
const { fenceUntrusted } = require('./untrusted-content');
|
|
6
|
+
|
|
7
|
+
const DEFAULT_BUDGET_TOKENS = 2000;
|
|
8
|
+
const MIN_BUDGET_TOKENS = 40;
|
|
9
|
+
const APPROX_CHARS_PER_TOKEN = 4;
|
|
10
|
+
|
|
11
|
+
function estimateTokens(value) {
|
|
12
|
+
return Math.ceil(String(value || '').length / APPROX_CHARS_PER_TOKEN);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function normalizeBudgetTokens(value) {
|
|
16
|
+
const parsed = Number(value);
|
|
17
|
+
if (!Number.isFinite(parsed)) return DEFAULT_BUDGET_TOKENS;
|
|
18
|
+
return Math.max(MIN_BUDGET_TOKENS, Math.floor(parsed));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function toPosix(relativePath) {
|
|
22
|
+
return relativePath.replaceAll(path.sep, '/');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function relativePath(projectRoot, filePath) {
|
|
26
|
+
return toPosix(path.relative(projectRoot, filePath));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function readText(projectRoot, relativeFilePath) {
|
|
30
|
+
const filePath = path.join(projectRoot, relativeFilePath);
|
|
31
|
+
if (!fs.existsSync(filePath)) return null;
|
|
32
|
+
return fs.readFileSync(filePath, 'utf8');
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function readJsonFile(projectRoot, relativeFilePath) {
|
|
36
|
+
const text = readText(projectRoot, relativeFilePath);
|
|
37
|
+
if (!text) return null;
|
|
38
|
+
try {
|
|
39
|
+
return JSON.parse(text);
|
|
40
|
+
} catch (_error) {
|
|
41
|
+
// Malformed JSON in a source file is treated as absent rather than fatal.
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function readJsonl(projectRoot, relativeFilePath) {
|
|
47
|
+
const text = readText(projectRoot, relativeFilePath);
|
|
48
|
+
if (!text) return [];
|
|
49
|
+
return text
|
|
50
|
+
.split(/\r?\n/)
|
|
51
|
+
.map(line => line.trim())
|
|
52
|
+
.filter(Boolean)
|
|
53
|
+
.map((line, index) => {
|
|
54
|
+
try {
|
|
55
|
+
return JSON.parse(line);
|
|
56
|
+
} catch (error) {
|
|
57
|
+
return {
|
|
58
|
+
_parseError: true,
|
|
59
|
+
line: index + 1,
|
|
60
|
+
error: error.message,
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function firstLine(value) {
|
|
67
|
+
return String(value || '')
|
|
68
|
+
.split(/\r?\n/)
|
|
69
|
+
.map(line => line.trim())
|
|
70
|
+
.find(Boolean) || '';
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function getRepositoryUrl(packageJson) {
|
|
74
|
+
const repository = packageJson?.repository;
|
|
75
|
+
if (typeof repository === 'string') return repository;
|
|
76
|
+
if (repository && typeof repository.url === 'string') return repository.url;
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function buildProjectIdentity(projectRoot) {
|
|
81
|
+
const packageJson = readJsonFile(projectRoot, 'package.json') || {};
|
|
82
|
+
const designTitle = firstLine(readText(projectRoot, 'docs/PROJECT_DESIGN.md'));
|
|
83
|
+
const projectName = packageJson.name || path.basename(projectRoot);
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
name: projectName,
|
|
87
|
+
version: packageJson.version || null,
|
|
88
|
+
repository: getRepositoryUrl(packageJson),
|
|
89
|
+
root: projectRoot,
|
|
90
|
+
design_title: designTitle.replace(/^#\s*/, '') || null,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function source(pathValue, kind, authority, role) {
|
|
95
|
+
return {
|
|
96
|
+
path: pathValue,
|
|
97
|
+
source_kind: kind,
|
|
98
|
+
authority,
|
|
99
|
+
role,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function buildSection({
|
|
104
|
+
id,
|
|
105
|
+
title,
|
|
106
|
+
content,
|
|
107
|
+
sources = [],
|
|
108
|
+
priority = 100,
|
|
109
|
+
preserve = false,
|
|
110
|
+
data = null,
|
|
111
|
+
untrustedSource = null,
|
|
112
|
+
}) {
|
|
113
|
+
return {
|
|
114
|
+
id,
|
|
115
|
+
title,
|
|
116
|
+
content: String(content || ''),
|
|
117
|
+
sources,
|
|
118
|
+
priority,
|
|
119
|
+
preserve,
|
|
120
|
+
truncated: false,
|
|
121
|
+
estimated_tokens: estimateTokens(content),
|
|
122
|
+
...(data ? { data } : {}),
|
|
123
|
+
// Carries a provenance label for sections whose content is untrusted external
|
|
124
|
+
// data (e.g. remembered notes). The assembler fences these AFTER applyBudget.
|
|
125
|
+
...(untrustedSource ? { untrustedSource } : {}),
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Provenance-fence any section carrying an `untrustedSource`, IN PLACE, AFTER applyBudget
|
|
131
|
+
* has truncated it — so the ⟦END UNTRUSTED⟧ close marker always survives the budget cut
|
|
132
|
+
* (fencing before truncation would let the budget sever the terminator). A planted
|
|
133
|
+
* memory note or issue title is DATA, not instructions, once it reaches agent context.
|
|
134
|
+
*
|
|
135
|
+
* @param {object[]} sections
|
|
136
|
+
* @returns {object[]} the same array, mutated.
|
|
137
|
+
*/
|
|
138
|
+
function fenceUntrustedSections(sections) {
|
|
139
|
+
for (const section of sections) {
|
|
140
|
+
if (section.untrustedSource && section.content) {
|
|
141
|
+
section.content = fenceUntrusted(section.content, { source: section.untrustedSource });
|
|
142
|
+
section.estimated_tokens = estimateTokens(section.content);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return sections;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function extractCurrentDesignSnapshot(projectDesignText) {
|
|
149
|
+
if (!projectDesignText) return '';
|
|
150
|
+
|
|
151
|
+
const snapshotStart = projectDesignText.indexOf('## Current design snapshot');
|
|
152
|
+
if (snapshotStart === -1) return '';
|
|
153
|
+
|
|
154
|
+
const registryStart = projectDesignText.indexOf('## Decision registry', snapshotStart);
|
|
155
|
+
const snapshot = registryStart === -1
|
|
156
|
+
? projectDesignText.slice(snapshotStart)
|
|
157
|
+
: projectDesignText.slice(snapshotStart, registryStart);
|
|
158
|
+
|
|
159
|
+
return snapshot.trim();
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function parseYamlBlockValue(block, key) {
|
|
163
|
+
const line = block
|
|
164
|
+
.split(/\r?\n/)
|
|
165
|
+
.find(candidate => candidate.trim().startsWith(`${key}:`));
|
|
166
|
+
if (!line) return null;
|
|
167
|
+
return line.slice(line.indexOf(':') + 1).trim().replace(/^['"]|['"]$/g, '') || null;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function extractDecisionEntries(projectDesignText) {
|
|
171
|
+
if (!projectDesignText) return [];
|
|
172
|
+
|
|
173
|
+
const entries = [];
|
|
174
|
+
const headingRegex = /^###\s+(PD-[^\r\n]+)/gm;
|
|
175
|
+
const matches = [...projectDesignText.matchAll(headingRegex)];
|
|
176
|
+
|
|
177
|
+
for (let index = 0; index < matches.length; index += 1) {
|
|
178
|
+
const match = matches[index];
|
|
179
|
+
const start = match.index;
|
|
180
|
+
const end = matches[index + 1]?.index ?? projectDesignText.length;
|
|
181
|
+
const block = projectDesignText.slice(start, end);
|
|
182
|
+
const yaml = extractFencedYaml(block);
|
|
183
|
+
const decisionText = extractCurrentDecision(block);
|
|
184
|
+
entries.push({
|
|
185
|
+
id: parseYamlBlockValue(yaml, 'id') || match[1].trim(),
|
|
186
|
+
topic: parseYamlBlockValue(yaml, 'topic'),
|
|
187
|
+
status: parseYamlBlockValue(yaml, 'status'),
|
|
188
|
+
decision: decisionText,
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return entries;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
function buildProjectDesignSections(projectRoot) {
|
|
196
|
+
const projectDesignPath = 'docs/PROJECT_DESIGN.md';
|
|
197
|
+
const projectDesignText = readText(projectRoot, projectDesignPath);
|
|
198
|
+
if (!projectDesignText) return [];
|
|
199
|
+
|
|
200
|
+
const projectDesignSource = source(projectDesignPath, 'project_doc', 'project_registry', 'project_design');
|
|
201
|
+
const snapshot = extractCurrentDesignSnapshot(projectDesignText);
|
|
202
|
+
const decisions = extractDecisionEntries(projectDesignText);
|
|
203
|
+
const decisionContent = decisions
|
|
204
|
+
.map(decision => [
|
|
205
|
+
`- ${decision.id}`,
|
|
206
|
+
decision.topic ? ` topic: ${decision.topic}` : null,
|
|
207
|
+
decision.status ? ` status: ${decision.status}` : null,
|
|
208
|
+
decision.decision ? ` decision: ${decision.decision}` : null,
|
|
209
|
+
].filter(Boolean).join('\n'))
|
|
210
|
+
.join('\n');
|
|
211
|
+
|
|
212
|
+
return [
|
|
213
|
+
buildSection({
|
|
214
|
+
id: 'current_design_snapshot',
|
|
215
|
+
title: 'Current Design Snapshot',
|
|
216
|
+
content: snapshot,
|
|
217
|
+
sources: [projectDesignSource],
|
|
218
|
+
priority: 70,
|
|
219
|
+
preserve: false,
|
|
220
|
+
}),
|
|
221
|
+
buildSection({
|
|
222
|
+
id: 'headline_decisions',
|
|
223
|
+
title: 'Headline Decisions',
|
|
224
|
+
content: decisionContent,
|
|
225
|
+
sources: [projectDesignSource],
|
|
226
|
+
priority: 10,
|
|
227
|
+
preserve: true,
|
|
228
|
+
data: { decisions },
|
|
229
|
+
}),
|
|
230
|
+
];
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function listWorkFolders(projectRoot) {
|
|
234
|
+
const docsWork = path.join(projectRoot, 'docs', 'work');
|
|
235
|
+
if (!fs.existsSync(docsWork)) return [];
|
|
236
|
+
|
|
237
|
+
return fs.readdirSync(docsWork, { withFileTypes: true })
|
|
238
|
+
.filter(entry => entry.isDirectory())
|
|
239
|
+
.map(entry => path.join(docsWork, entry.name))
|
|
240
|
+
.filter(folder => ['plan.md', 'design.md', 'tasks.md', 'decisions.md'].some(file => fs.existsSync(path.join(folder, file))))
|
|
241
|
+
.sort((a, b) => {
|
|
242
|
+
const aScore = ['plan.md', 'tasks.md', 'decisions.md'].filter(file => fs.existsSync(path.join(a, file))).length;
|
|
243
|
+
const bScore = ['plan.md', 'tasks.md', 'decisions.md'].filter(file => fs.existsSync(path.join(b, file))).length;
|
|
244
|
+
if (aScore !== bScore) return bScore - aScore;
|
|
245
|
+
return relativePath(projectRoot, b).localeCompare(relativePath(projectRoot, a));
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// Locate a work-folder by its machine-readable `.forge-issue` marker (dropped by
|
|
250
|
+
// `forge worktree create --work-folder`). Deterministic folder → issue resolution that,
|
|
251
|
+
// unlike the "most-complete folder" heuristic, does not break under parallel features.
|
|
252
|
+
function findWorkFolderByIssue(projectRoot, issueId) {
|
|
253
|
+
for (const folder of listWorkFolders(projectRoot)) {
|
|
254
|
+
const markerPath = path.join(folder, '.forge-issue');
|
|
255
|
+
try {
|
|
256
|
+
if (fs.existsSync(markerPath) && fs.readFileSync(markerPath, 'utf8').trim() === issueId) {
|
|
257
|
+
return folder;
|
|
258
|
+
}
|
|
259
|
+
} catch { /* unreadable marker — skip */ }
|
|
260
|
+
}
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Read the current worktree's linkage row from the Kernel. Prefers an injected driver
|
|
265
|
+
// (tests / callers already holding one, never closed here); otherwise opens the Kernel
|
|
266
|
+
// DB read-only and closes it. Never throws: a missing/locked Kernel, an un-migrated DB,
|
|
267
|
+
// or a non-git project all return null so the caller falls back to the heuristic.
|
|
268
|
+
function readWorktreeLinkageRow(projectRoot, worktreePath, options) {
|
|
269
|
+
if (options._kernelDriver) {
|
|
270
|
+
try { return options._kernelDriver.getWorktreeLinkage({ path: worktreePath }) || null; }
|
|
271
|
+
catch { return null; }
|
|
272
|
+
}
|
|
273
|
+
let driver;
|
|
274
|
+
try {
|
|
275
|
+
if (!fs.existsSync(path.join(projectRoot, '.git'))) return null; // no repo → no Kernel
|
|
276
|
+
const { resolveKernelDatabasePath, buildKernelIssueDeps } = require('./kernel/cli-broker-factory');
|
|
277
|
+
const databasePath = resolveKernelDatabasePath({ projectRoot });
|
|
278
|
+
if (!databasePath || !fs.existsSync(databasePath)) return null; // no Kernel DB → heuristic
|
|
279
|
+
driver = buildKernelIssueDeps({ projectRoot, databasePath }).kernelDriver;
|
|
280
|
+
return driver.getWorktreeLinkage({ path: worktreePath }) || null;
|
|
281
|
+
} catch {
|
|
282
|
+
return null;
|
|
283
|
+
} finally {
|
|
284
|
+
if (driver && typeof driver.close === 'function') {
|
|
285
|
+
try { driver.close(); } catch { /* ignore */ }
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Resolve the work-folder the Kernel linked to the current worktree (row.work_folder,
|
|
291
|
+
// else row.issue_id → `.forge-issue` marker), or null when there is no usable linkage.
|
|
292
|
+
function resolveKernelLinkedWorkFolder(projectRoot, options = {}) {
|
|
293
|
+
const worktreePath = path.resolve(options.worktreePath || projectRoot);
|
|
294
|
+
const row = readWorktreeLinkageRow(projectRoot, worktreePath, options);
|
|
295
|
+
if (!row) return null;
|
|
296
|
+
|
|
297
|
+
if (row.work_folder) {
|
|
298
|
+
const resolved = path.resolve(projectRoot, row.work_folder);
|
|
299
|
+
if (fs.existsSync(resolved)) return resolved;
|
|
300
|
+
}
|
|
301
|
+
if (row.issue_id) {
|
|
302
|
+
const byMarker = findWorkFolderByIssue(projectRoot, row.issue_id);
|
|
303
|
+
if (byMarker) return byMarker;
|
|
304
|
+
}
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
function discoverWorkFolder(projectRoot, options = {}) {
|
|
309
|
+
if (options.workFolder) {
|
|
310
|
+
const explicit = path.resolve(projectRoot, options.workFolder);
|
|
311
|
+
if (fs.existsSync(explicit)) return explicit;
|
|
312
|
+
}
|
|
313
|
+
// Kernel linkage (issue → worktree → work-folder) is the backbone: when the current
|
|
314
|
+
// worktree has a recorded row, it wins over the filesystem heuristic. Absent a row,
|
|
315
|
+
// fall back to the most-complete-folder guess.
|
|
316
|
+
const linked = resolveKernelLinkedWorkFolder(projectRoot, options);
|
|
317
|
+
if (linked) return linked;
|
|
318
|
+
return listWorkFolders(projectRoot)[0] || null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
function buildWorkArtifactSection(projectRoot, workFolder, artifact) {
|
|
322
|
+
const filePath = path.join(workFolder, artifact.file);
|
|
323
|
+
if (!fs.existsSync(filePath)) return null;
|
|
324
|
+
|
|
325
|
+
const relative = relativePath(projectRoot, filePath);
|
|
326
|
+
const artifactSource = source(relative, artifact.sourceKind, 'work_artifact', artifact.role);
|
|
327
|
+
return buildSection({
|
|
328
|
+
id: artifact.id,
|
|
329
|
+
title: artifact.title,
|
|
330
|
+
content: fs.readFileSync(filePath, 'utf8').trim(),
|
|
331
|
+
sources: [artifactSource],
|
|
332
|
+
priority: artifact.priority,
|
|
333
|
+
preserve: artifact.preserve,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function buildWorkSections(projectRoot, options = {}) {
|
|
338
|
+
const workFolder = discoverWorkFolder(projectRoot, options);
|
|
339
|
+
if (!workFolder) return [];
|
|
340
|
+
|
|
341
|
+
const artifacts = [
|
|
342
|
+
{
|
|
343
|
+
file: 'decisions.md',
|
|
344
|
+
id: 'active_work_decisions',
|
|
345
|
+
title: 'Active Work Decisions',
|
|
346
|
+
sourceKind: 'decision_log',
|
|
347
|
+
role: 'active_work_decisions',
|
|
348
|
+
priority: 20,
|
|
349
|
+
preserve: true,
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
file: 'plan.md',
|
|
353
|
+
id: 'active_work_plan',
|
|
354
|
+
title: 'Active Work Plan',
|
|
355
|
+
sourceKind: 'work_plan',
|
|
356
|
+
role: 'active_work_plan',
|
|
357
|
+
priority: 30,
|
|
358
|
+
preserve: false,
|
|
359
|
+
},
|
|
360
|
+
{
|
|
361
|
+
file: 'tasks.md',
|
|
362
|
+
id: 'active_work_tasks',
|
|
363
|
+
title: 'Active Work Tasks',
|
|
364
|
+
sourceKind: 'task_list',
|
|
365
|
+
role: 'active_work_tasks',
|
|
366
|
+
priority: 40,
|
|
367
|
+
preserve: false,
|
|
368
|
+
},
|
|
369
|
+
{
|
|
370
|
+
file: 'design.md',
|
|
371
|
+
id: 'active_work_legacy_design',
|
|
372
|
+
title: 'Active Work Legacy Design',
|
|
373
|
+
sourceKind: 'legacy_work_design',
|
|
374
|
+
role: 'active_work_legacy_design',
|
|
375
|
+
priority: 80,
|
|
376
|
+
preserve: false,
|
|
377
|
+
},
|
|
378
|
+
];
|
|
379
|
+
|
|
380
|
+
return artifacts
|
|
381
|
+
.map(artifact => buildWorkArtifactSection(projectRoot, workFolder, artifact))
|
|
382
|
+
.filter(Boolean)
|
|
383
|
+
.map(section => ({
|
|
384
|
+
...section,
|
|
385
|
+
work_folder: relativePath(projectRoot, workFolder),
|
|
386
|
+
}));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function placeholderSection(id, title, content, role, preserve = false, priority = 90) {
|
|
390
|
+
return buildSection({
|
|
391
|
+
id,
|
|
392
|
+
title,
|
|
393
|
+
content,
|
|
394
|
+
sources: [source(`kernel.${id}`, 'kernel_placeholder', 'future_kernel_read_model', role)],
|
|
395
|
+
priority,
|
|
396
|
+
preserve,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Cap on remembered notes surfaced in the orientation MEMORY section — a bounded
|
|
401
|
+
// nudge (applyBudget caps it further), not a full memory dump.
|
|
402
|
+
const MEMORY_SECTION_NOTE_LIMIT = 5;
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Best-effort MEMORY section: the newest remembered notes from the kernel-backed memory
|
|
406
|
+
* store (the read half of `forge remember`/`recall` that was previously ORPHANED — an
|
|
407
|
+
* agent only saw memory if it typed `forge recall`). Recent DECISIONS are already
|
|
408
|
+
* surfaced by `headline_decisions` (from PROJECT_DESIGN.md), so this adds the missing
|
|
409
|
+
* remembered-notes half without duplicating them. Never throws and never blocks orient:
|
|
410
|
+
* a missing/locked kernel, or no notes, yields [] (the section is simply absent).
|
|
411
|
+
*
|
|
412
|
+
* @param {string} projectRoot
|
|
413
|
+
* @param {object} [options] - forwarded to the memory read path (e.g. an injected `store`).
|
|
414
|
+
* @returns {object[]} zero or one section.
|
|
415
|
+
*/
|
|
416
|
+
function buildMemorySection(projectRoot, options = {}) {
|
|
417
|
+
// Own the store's lifecycle: when we open a kernel driver we MUST close it, so orient
|
|
418
|
+
// never leaves a lingering DB handle (which strands Windows temp cleanup and is a
|
|
419
|
+
// resource leak). An injected store (tests) is used as-is and never closed by us.
|
|
420
|
+
const injected = options.store;
|
|
421
|
+
const store = injected || openMemoryStore(projectRoot);
|
|
422
|
+
if (!store) return [];
|
|
423
|
+
try {
|
|
424
|
+
// Lazy require: keep the default orient path free of the memory stack until needed.
|
|
425
|
+
const memoryRouter = require('./memory/router');
|
|
426
|
+
const result = memoryRouter.recall(projectRoot, { limit: MEMORY_SECTION_NOTE_LIMIT }, { store });
|
|
427
|
+
const notes = Array.isArray(result && result.notes) ? result.notes : [];
|
|
428
|
+
if (notes.length === 0) return [];
|
|
429
|
+
return [buildSection({
|
|
430
|
+
id: 'remembered_notes',
|
|
431
|
+
title: 'Remembered Notes',
|
|
432
|
+
content: notes.map(formatMemoryNote).join('\n'),
|
|
433
|
+
sources: [source('kernel.remembered_notes', 'kernel_memory', 'project_memory', 'remembered_notes')],
|
|
434
|
+
priority: 45,
|
|
435
|
+
preserve: false,
|
|
436
|
+
// Remembered notes are untrusted DATA (a planted note must not read as directives).
|
|
437
|
+
// Raw here; the assembler fences it AFTER applyBudget truncation.
|
|
438
|
+
untrustedSource: 'memory',
|
|
439
|
+
})];
|
|
440
|
+
} catch {
|
|
441
|
+
return [];
|
|
442
|
+
} finally {
|
|
443
|
+
if (!injected && store && typeof store.close === 'function') {
|
|
444
|
+
try { store.close(); } catch { /* ignore */ }
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
/**
|
|
450
|
+
* Open a fresh, closeable kernel driver for a read of remembered notes, or null when
|
|
451
|
+
* there is no reachable kernel DB (no repo / no DB file / open failure). Mirrors
|
|
452
|
+
* readWorktreeLinkageRow: never creates the DB, never throws.
|
|
453
|
+
*
|
|
454
|
+
* @param {string} projectRoot
|
|
455
|
+
* @returns {object|null}
|
|
456
|
+
*/
|
|
457
|
+
function openMemoryStore(projectRoot) {
|
|
458
|
+
try {
|
|
459
|
+
if (!fs.existsSync(path.join(projectRoot, '.git'))) return null;
|
|
460
|
+
const { resolveKernelDatabasePath } = require('./kernel/cli-broker-factory');
|
|
461
|
+
const databasePath = resolveKernelDatabasePath({ projectRoot });
|
|
462
|
+
if (!databasePath || !fs.existsSync(databasePath)) return null;
|
|
463
|
+
const { createBuiltinSQLiteDriver } = require('./kernel/sqlite-driver');
|
|
464
|
+
return createBuiltinSQLiteDriver({ databasePath });
|
|
465
|
+
} catch {
|
|
466
|
+
return null;
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/** Render one recall note as a compact `- [date] text` line. */
|
|
471
|
+
function formatMemoryNote(note) {
|
|
472
|
+
const date = typeof note.timestamp === 'string' && note.timestamp ? note.timestamp.slice(0, 10) : '';
|
|
473
|
+
const prefix = date ? `${date} ` : '';
|
|
474
|
+
return `- ${prefix}${note.note}`;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function buildQueueSections() {
|
|
478
|
+
return [
|
|
479
|
+
placeholderSection(
|
|
480
|
+
'ready_queue',
|
|
481
|
+
'Ready Queue',
|
|
482
|
+
'Run `forge ready` to list work that is ready to start (issues with no open blockers).',
|
|
483
|
+
'ready_queue',
|
|
484
|
+
false,
|
|
485
|
+
50
|
|
486
|
+
),
|
|
487
|
+
placeholderSection(
|
|
488
|
+
'active_claims',
|
|
489
|
+
'Active Claims',
|
|
490
|
+
'Run `forge issue list --status in_progress` to see work that is currently claimed.',
|
|
491
|
+
'active_claims',
|
|
492
|
+
true,
|
|
493
|
+
60
|
|
494
|
+
),
|
|
495
|
+
];
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function truncateText(content, targetTokens) {
|
|
499
|
+
if (estimateTokens(content) <= targetTokens) {
|
|
500
|
+
return { content, truncated: false };
|
|
501
|
+
}
|
|
502
|
+
const suffix = '\n[truncated deterministically by token budget]';
|
|
503
|
+
// If even the truncation marker cannot fit in the allowance, drop the section
|
|
504
|
+
// entirely rather than emit a section that exceeds the requested cap.
|
|
505
|
+
if (estimateTokens(suffix) > targetTokens) {
|
|
506
|
+
return { content: '', truncated: true };
|
|
507
|
+
}
|
|
508
|
+
const targetChars = Math.max(0, targetTokens * APPROX_CHARS_PER_TOKEN);
|
|
509
|
+
let nextContent = String(content).slice(0, Math.max(0, targetChars - suffix.length)).trimEnd();
|
|
510
|
+
let result = `${nextContent}${suffix}`;
|
|
511
|
+
// Guard against ceil() rounding pushing the marker-inclusive result over the cap.
|
|
512
|
+
while (nextContent.length > 0 && estimateTokens(result) > targetTokens) {
|
|
513
|
+
nextContent = nextContent.slice(0, -1).trimEnd();
|
|
514
|
+
result = `${nextContent}${suffix}`;
|
|
515
|
+
}
|
|
516
|
+
return { content: result, truncated: true };
|
|
517
|
+
}
|
|
518
|
+
|
|
519
|
+
function applyBudget(sections, budgetTokens) {
|
|
520
|
+
const budget = normalizeBudgetTokens(budgetTokens);
|
|
521
|
+
const sectionCopies = sections.map(section => ({ ...section }));
|
|
522
|
+
const preserved = sectionCopies.filter(section => section.preserve);
|
|
523
|
+
const flexible = sectionCopies.filter(section => !section.preserve)
|
|
524
|
+
.sort((a, b) => a.priority - b.priority || a.id.localeCompare(b.id));
|
|
525
|
+
|
|
526
|
+
let used = preserved.reduce((sum, section) => sum + estimateTokens(section.content), 0);
|
|
527
|
+
let remaining = Math.max(0, budget - used);
|
|
528
|
+
let truncated = false;
|
|
529
|
+
|
|
530
|
+
for (const section of flexible) {
|
|
531
|
+
const fullTokens = estimateTokens(section.content);
|
|
532
|
+
const allowance = Math.min(fullTokens, remaining);
|
|
533
|
+
const next = truncateText(section.content, allowance);
|
|
534
|
+
section.content = next.content;
|
|
535
|
+
section.truncated = next.truncated;
|
|
536
|
+
section.estimated_tokens = estimateTokens(section.content);
|
|
537
|
+
used += section.estimated_tokens;
|
|
538
|
+
remaining = Math.max(0, budget - used);
|
|
539
|
+
if (next.truncated) truncated = true;
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
if (used > budget) {
|
|
543
|
+
const preservedByPriority = preserved
|
|
544
|
+
.sort((a, b) => b.priority - a.priority || b.id.localeCompare(a.id));
|
|
545
|
+
for (const section of preservedByPriority) {
|
|
546
|
+
if (used <= budget) break;
|
|
547
|
+
const targetTokens = Math.max(0, estimateTokens(section.content) - (used - budget));
|
|
548
|
+
const next = truncateText(section.content, targetTokens);
|
|
549
|
+
used -= estimateTokens(section.content);
|
|
550
|
+
section.content = next.content;
|
|
551
|
+
section.truncated = next.truncated;
|
|
552
|
+
section.estimated_tokens = estimateTokens(section.content);
|
|
553
|
+
used += section.estimated_tokens;
|
|
554
|
+
if (next.truncated) truncated = true;
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
const ordered = sectionCopies.sort((a, b) => a.priority - b.priority || a.id.localeCompare(b.id));
|
|
559
|
+
for (const section of ordered) {
|
|
560
|
+
section.estimated_tokens = estimateTokens(section.content);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
return {
|
|
564
|
+
sections: ordered,
|
|
565
|
+
token_budget: {
|
|
566
|
+
requested: budget,
|
|
567
|
+
used: Math.min(budget, ordered.reduce((sum, section) => sum + section.estimated_tokens, 0)),
|
|
568
|
+
approximate: true,
|
|
569
|
+
chars_per_token: APPROX_CHARS_PER_TOKEN,
|
|
570
|
+
truncated: truncated || ordered.some(section => section.truncated),
|
|
571
|
+
truncation_order: [
|
|
572
|
+
'active_work_plan',
|
|
573
|
+
'active_work_tasks',
|
|
574
|
+
'active_work_legacy_design',
|
|
575
|
+
'current_design_snapshot',
|
|
576
|
+
'ready_queue',
|
|
577
|
+
'headline_decisions',
|
|
578
|
+
'active_work_decisions',
|
|
579
|
+
'active_claims',
|
|
580
|
+
'key_commands',
|
|
581
|
+
],
|
|
582
|
+
},
|
|
583
|
+
};
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
function collectSources(sections) {
|
|
587
|
+
const byKey = new Map();
|
|
588
|
+
for (const section of sections) {
|
|
589
|
+
for (const sectionSource of section.sources || []) {
|
|
590
|
+
const key = `${sectionSource.path}|${sectionSource.source_kind}|${sectionSource.role}`;
|
|
591
|
+
if (!byKey.has(key)) byKey.set(key, sectionSource);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
return [...byKey.values()].sort((a, b) => a.path.localeCompare(b.path) || a.role.localeCompare(b.role));
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
function cleanupSections(sections) {
|
|
598
|
+
return sections.map(section => {
|
|
599
|
+
const rest = { ...section };
|
|
600
|
+
delete rest.priority;
|
|
601
|
+
delete rest.preserve;
|
|
602
|
+
delete rest.untrustedSource; // internal fencing marker — not part of the emitted shape
|
|
603
|
+
return rest;
|
|
604
|
+
});
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
function buildOrientationSections(projectRoot, options = {}) {
|
|
608
|
+
const project = buildProjectIdentity(projectRoot);
|
|
609
|
+
const sections = [
|
|
610
|
+
buildSection({
|
|
611
|
+
id: 'project_identity',
|
|
612
|
+
title: 'Project Identity',
|
|
613
|
+
content: [
|
|
614
|
+
`name: ${project.name}`,
|
|
615
|
+
project.version ? `version: ${project.version}` : null,
|
|
616
|
+
project.repository ? `repository: ${project.repository}` : null,
|
|
617
|
+
project.design_title ? `design: ${project.design_title}` : null,
|
|
618
|
+
].filter(Boolean).join('\n'),
|
|
619
|
+
sources: [
|
|
620
|
+
source('package.json', 'project_manifest', 'project_file', 'project_identity'),
|
|
621
|
+
source('docs/PROJECT_DESIGN.md', 'project_doc', 'project_registry', 'project_identity'),
|
|
622
|
+
],
|
|
623
|
+
priority: 0,
|
|
624
|
+
preserve: true,
|
|
625
|
+
data: project,
|
|
626
|
+
}),
|
|
627
|
+
...buildProjectDesignSections(projectRoot),
|
|
628
|
+
...buildMemorySection(projectRoot, options),
|
|
629
|
+
...buildWorkSections(projectRoot, options),
|
|
630
|
+
...buildQueueSections(),
|
|
631
|
+
];
|
|
632
|
+
return { project, sections };
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
function assembleOrientationResult(project, sections, options) {
|
|
636
|
+
const budgeted = applyBudget(sections, options.budgetTokens ?? options.budget);
|
|
637
|
+
fenceUntrustedSections(budgeted.sections); // provenance-fence untrusted sections post-budget
|
|
638
|
+
|
|
639
|
+
return {
|
|
640
|
+
schema_version: 1,
|
|
641
|
+
kind: 'orientation',
|
|
642
|
+
generated_at: new Date().toISOString(),
|
|
643
|
+
assembly: 'deterministic-file-assembly-v1',
|
|
644
|
+
project,
|
|
645
|
+
token_budget: budgeted.token_budget,
|
|
646
|
+
sections: cleanupSections(budgeted.sections),
|
|
647
|
+
sources: collectSources(budgeted.sections),
|
|
648
|
+
next_commands: [
|
|
649
|
+
'forge status --json',
|
|
650
|
+
'forge issue ready --json',
|
|
651
|
+
'forge recap <issue> --json',
|
|
652
|
+
'forge show <issue> --json',
|
|
653
|
+
],
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function buildOrientation(projectRoot, options = {}) {
|
|
658
|
+
const { project, sections } = buildOrientationSections(projectRoot, options);
|
|
659
|
+
return assembleOrientationResult(project, sections, options);
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
// Short, fixed reference of capabilities that are easy to miss at session entry:
|
|
663
|
+
// closing the remember/recall memory loop, and commands with 0 mentions elsewhere
|
|
664
|
+
// in the generated onboarding surface (merge, insights, upgrade, gate, role).
|
|
665
|
+
// Kept intentionally tiny (a handful of one-liners) and `preserve: true` with a
|
|
666
|
+
// low priority number so it survives prime's token budget even on data-heavy
|
|
667
|
+
// projects — this is a fixed reference, not a manual, so it must not grow.
|
|
668
|
+
const PRIME_KEY_COMMANDS_CONTENT = [
|
|
669
|
+
'forge remember <note> / forge recall [query] — write a memory note, then read it back',
|
|
670
|
+
'forge insights — detect recurring evidence patterns, suggest workflow follow-ups',
|
|
671
|
+
'forge upgrade [--dry-run] — preview/self-heal safe Forge upgrade readiness',
|
|
672
|
+
'forge gate <verb> <gate-id> — toggle a workflow gate, or approve/reject a human gate',
|
|
673
|
+
'forge role <role> --use <skill> — bind a role to a skill/ideology',
|
|
674
|
+
'forge merge --auto <pr> — opt-in conditional auto-merge (off by default)',
|
|
675
|
+
].map(line => `- ${line}`).join('\n');
|
|
676
|
+
|
|
677
|
+
function buildPrimeKeyCommandsSection() {
|
|
678
|
+
return buildSection({
|
|
679
|
+
id: 'key_commands',
|
|
680
|
+
title: 'Key Commands',
|
|
681
|
+
content: PRIME_KEY_COMMANDS_CONTENT,
|
|
682
|
+
sources: [source('AGENTS.md', 'static_reference', 'cli_help_summary', 'key_commands')],
|
|
683
|
+
priority: 2,
|
|
684
|
+
preserve: true,
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
function findIssue(projectRoot, issueId) {
|
|
689
|
+
const issues = readJsonl(projectRoot, '.beads/issues.jsonl')
|
|
690
|
+
.filter(row => row && !row._parseError && row._type === 'issue');
|
|
691
|
+
return issues.find(issue => issue.id === issueId) || null;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
function buildIssueSection(projectRoot, issueId) {
|
|
695
|
+
const issue = findIssue(projectRoot, issueId);
|
|
696
|
+
const content = issue
|
|
697
|
+
? [
|
|
698
|
+
`id: ${issue.id}`,
|
|
699
|
+
issue.title ? `title: ${issue.title}` : null,
|
|
700
|
+
issue.status ? `status: ${issue.status}` : null,
|
|
701
|
+
issue.description ? `description: ${issue.description}` : null,
|
|
702
|
+
].filter(Boolean).join('\n')
|
|
703
|
+
: `id: ${issueId}\nstatus: unknown\nIssue not found in compatibility projection.`;
|
|
704
|
+
|
|
705
|
+
return {
|
|
706
|
+
issue: issue ? {
|
|
707
|
+
id: issue.id,
|
|
708
|
+
title: issue.title || null,
|
|
709
|
+
status: issue.status || null,
|
|
710
|
+
} : {
|
|
711
|
+
id: issueId,
|
|
712
|
+
title: null,
|
|
713
|
+
status: 'unknown',
|
|
714
|
+
},
|
|
715
|
+
section: buildSection({
|
|
716
|
+
id: 'issue_summary',
|
|
717
|
+
title: 'Issue Summary',
|
|
718
|
+
content,
|
|
719
|
+
sources: [source('.beads/issues.jsonl', 'beads_compat', 'compatibility_projection', 'issue_summary')],
|
|
720
|
+
priority: 5,
|
|
721
|
+
preserve: true,
|
|
722
|
+
}),
|
|
723
|
+
};
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function buildIssueRecap(projectRoot, issueId, options = {}) {
|
|
727
|
+
if (!issueId || typeof issueId !== 'string') {
|
|
728
|
+
throw new Error('Issue id is required for issue-scoped recap.');
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const issueResult = buildIssueSection(projectRoot, issueId);
|
|
732
|
+
const sections = [
|
|
733
|
+
issueResult.section,
|
|
734
|
+
...buildProjectDesignSections(projectRoot),
|
|
735
|
+
...buildWorkSections(projectRoot, options),
|
|
736
|
+
...buildQueueSections(),
|
|
737
|
+
];
|
|
738
|
+
const budgeted = applyBudget(sections, options.budgetTokens ?? options.budget);
|
|
739
|
+
fenceUntrustedSections(budgeted.sections); // provenance-fence untrusted sections post-budget
|
|
740
|
+
|
|
741
|
+
return {
|
|
742
|
+
schema_version: 1,
|
|
743
|
+
kind: 'issue_recap',
|
|
744
|
+
generated_at: new Date().toISOString(),
|
|
745
|
+
assembly: 'deterministic-file-assembly-v1',
|
|
746
|
+
scope: {
|
|
747
|
+
issue_id: issueId,
|
|
748
|
+
},
|
|
749
|
+
issue: issueResult.issue,
|
|
750
|
+
token_budget: budgeted.token_budget,
|
|
751
|
+
sections: cleanupSections(budgeted.sections),
|
|
752
|
+
sources: collectSources(budgeted.sections),
|
|
753
|
+
next_commands: [
|
|
754
|
+
`forge show ${issueId} --json`,
|
|
755
|
+
`forge comment ${issueId} "<handoff note>"`,
|
|
756
|
+
'forge orient --json',
|
|
757
|
+
'forge status --json',
|
|
758
|
+
],
|
|
759
|
+
};
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
function buildPrime(projectRoot, options = {}) {
|
|
763
|
+
const { project, sections } = buildOrientationSections(projectRoot, options);
|
|
764
|
+
const orientation = assembleOrientationResult(
|
|
765
|
+
project,
|
|
766
|
+
[...sections, buildPrimeKeyCommandsSection()],
|
|
767
|
+
options
|
|
768
|
+
);
|
|
769
|
+
return {
|
|
770
|
+
schema_version: 1,
|
|
771
|
+
kind: 'prime',
|
|
772
|
+
generated_at: orientation.generated_at,
|
|
773
|
+
purpose: 'session-entry',
|
|
774
|
+
token_budget: orientation.token_budget,
|
|
775
|
+
orientation,
|
|
776
|
+
sources: orientation.sources,
|
|
777
|
+
next_commands: [
|
|
778
|
+
'forge orient --json',
|
|
779
|
+
'forge status --json',
|
|
780
|
+
'forge issue ready --json',
|
|
781
|
+
],
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
|
|
785
|
+
function formatOrientationText(result) {
|
|
786
|
+
const lines = [
|
|
787
|
+
orientationTitle(result.kind),
|
|
788
|
+
`Assembly: ${result.assembly || result.orientation?.assembly || 'deterministic-file-assembly-v1'}`,
|
|
789
|
+
`Budget: ${result.token_budget.used}/${result.token_budget.requested} estimated tokens${result.token_budget.truncated ? ' (truncated)' : ''}`,
|
|
790
|
+
'',
|
|
791
|
+
];
|
|
792
|
+
|
|
793
|
+
const sections = result.orientation?.sections || result.sections || [];
|
|
794
|
+
for (const section of sections) {
|
|
795
|
+
if (!section.content) continue;
|
|
796
|
+
lines.push(`## ${section.title}`, section.content, '');
|
|
797
|
+
}
|
|
798
|
+
|
|
799
|
+
lines.push('Next commands:');
|
|
800
|
+
for (const command of result.next_commands || []) {
|
|
801
|
+
lines.push(`- ${command}`);
|
|
802
|
+
}
|
|
803
|
+
return `${lines.join('\n')}\n`;
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
function orientationTitle(kind) {
|
|
807
|
+
if (kind === 'prime') return 'Forge prime';
|
|
808
|
+
if (kind === 'issue_recap') return 'Forge issue recap';
|
|
809
|
+
return 'Forge orientation';
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function extractFencedYaml(block) {
|
|
813
|
+
const open = block.indexOf('```yaml');
|
|
814
|
+
if (open === -1) return '';
|
|
815
|
+
const start = open + '```yaml'.length;
|
|
816
|
+
const close = block.indexOf('```', start);
|
|
817
|
+
return close === -1 ? '' : block.slice(start, close).trim();
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
function extractCurrentDecision(block) {
|
|
821
|
+
const marker = '**Current decision:**';
|
|
822
|
+
const at = block.indexOf(marker);
|
|
823
|
+
if (at === -1) return '';
|
|
824
|
+
const after = block.slice(at + marker.length);
|
|
825
|
+
const blank = after.indexOf('\n\n');
|
|
826
|
+
return (blank === -1 ? after : after.slice(0, blank)).trim();
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
function readOption(args, name, fallback) {
|
|
830
|
+
const equals = args.find(arg => arg.startsWith(`${name}=`));
|
|
831
|
+
if (equals) return equals.slice(name.length + 1);
|
|
832
|
+
const index = args.indexOf(name);
|
|
833
|
+
if (index >= 0 && args[index + 1] && !args[index + 1].startsWith('-')) return args[index + 1];
|
|
834
|
+
return fallback;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function runOrientationCommand(build, args, projectRoot) {
|
|
838
|
+
const result = build(projectRoot, {
|
|
839
|
+
budgetTokens: readOption(args, '--budget', undefined),
|
|
840
|
+
});
|
|
841
|
+
return {
|
|
842
|
+
success: true,
|
|
843
|
+
output: args.includes('--json') ? `${JSON.stringify(result, null, 2)}\n` : formatOrientationText(result),
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
module.exports = {
|
|
848
|
+
DEFAULT_BUDGET_TOKENS,
|
|
849
|
+
applyBudget,
|
|
850
|
+
buildSection,
|
|
851
|
+
buildIssueRecap,
|
|
852
|
+
buildMemorySection,
|
|
853
|
+
buildOrientation,
|
|
854
|
+
buildOrientationSections,
|
|
855
|
+
buildPrime,
|
|
856
|
+
discoverWorkFolder,
|
|
857
|
+
estimateTokens,
|
|
858
|
+
formatOrientationText,
|
|
859
|
+
normalizeBudgetTokens,
|
|
860
|
+
readOption,
|
|
861
|
+
runOrientationCommand,
|
|
862
|
+
source,
|
|
863
|
+
};
|