karajan-code 4.70.3 → 5.0.0
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/README.md +2 -2
- package/package.json +6 -14
- package/packages/hu-board/package.json +1 -1
- package/packages/hu-board/public/app.js +4 -18
- package/packages/hu-board/public/index.html +5 -2
- package/packages/hu-board/public/styles.css +26 -0
- package/packages/hu-board/public/utils/api.js +0 -74
- package/packages/hu-board/public/utils/board-tour.js +62 -0
- package/packages/hu-board/public/utils/board-view.js +49 -144
- package/packages/hu-board/public/utils/command-launcher.js +8 -70
- package/packages/hu-board/public/utils/formatters.js +10 -0
- package/packages/hu-board/public/utils/governance-view.js +13 -1
- package/packages/hu-board/public/utils/hu-actions.js +17 -68
- package/packages/hu-board/public/utils/init-listeners.js +7 -8
- package/packages/hu-board/public/utils/log-panel.js +3 -16
- package/packages/hu-board/public/utils/maggle-mode.js +2 -12
- package/packages/hu-board/public/utils/modals.js +7 -0
- package/packages/hu-board/public/utils/project-picker-view.js +1 -1
- package/packages/hu-board/public/utils/server-push.js +3 -22
- package/packages/hu-board/public/utils/shared-board-notice.js +33 -0
- package/packages/hu-board/public/utils/story-detail-view.js +5 -19
- package/packages/hu-board/src/cleanup-zombies.js +0 -1
- package/packages/hu-board/src/command-runner.js +36 -25
- package/packages/hu-board/src/db-connection.js +282 -0
- package/packages/hu-board/src/db-paths.js +113 -0
- package/packages/hu-board/src/db-queries.js +188 -0
- package/packages/hu-board/src/db-tombstones.js +51 -0
- package/packages/hu-board/src/db.js +28 -619
- package/packages/hu-board/src/ephemeral-cleaner.js +2 -3
- package/packages/hu-board/src/hu-zombie-reaper.js +5 -1
- package/packages/hu-board/src/plan-mutations.js +3 -153
- package/packages/hu-board/src/plans-service.js +55 -0
- package/packages/hu-board/src/routes/api/commands.js +122 -0
- package/packages/hu-board/src/routes/api/plans.js +55 -0
- package/packages/hu-board/src/routes/api/projects.js +267 -0
- package/packages/hu-board/src/routes/api/prompts.js +135 -0
- package/packages/hu-board/src/routes/api/sessions.js +95 -0
- package/packages/hu-board/src/routes/api/stories.js +183 -0
- package/packages/hu-board/src/routes/api/system.js +241 -0
- package/packages/hu-board/src/routes/api/tombstones.js +38 -0
- package/packages/hu-board/src/routes/api.js +24 -1371
- package/packages/hu-board/src/routes/governance.js +23 -1
- package/packages/hu-board/src/routes/lanes.js +40 -0
- package/packages/hu-board/src/routes/rag.js +4 -3
- package/packages/hu-board/src/server.js +20 -34
- package/packages/hu-board/src/sync-files.js +318 -0
- package/packages/hu-board/src/sync-project.js +99 -0
- package/packages/hu-board/src/sync.js +22 -566
- package/packages/hu-board/src/zombie-reaper.js +1 -1
- package/scripts/dual-publish.mjs +19 -3
- package/scripts/postinstall.js +15 -2
- package/scripts/verify-pack-mcp.mjs +11 -7
- package/scripts/verify-pack.mjs +4 -4
- package/src/audit/audit-history.js +3 -2
- package/src/audit/dead-exports.js +68 -59
- package/src/audit/deterministic-summary.js +33 -13
- package/src/audit/npm-signatures.js +41 -0
- package/src/audit/sbom.js +35 -0
- package/src/checks/agent-token.js +112 -0
- package/src/checks/ai-surface.js +9 -7
- package/src/checks/config-files.js +8 -7
- package/src/checks/dir-setup.js +34 -1
- package/src/checks/native-build.js +21 -39
- package/src/checks/runner.js +128 -142
- package/src/cli/advanced-commands.js +6 -6
- package/src/cli/register-meta.js +42 -82
- package/src/cli/register-pipeline.js +8 -140
- package/src/cli/register-plan.js +9 -100
- package/src/cli/retired-commands.js +49 -0
- package/src/cli.js +18 -5
- package/src/commands/agent-run.js +40 -26
- package/src/commands/audit.js +127 -124
- package/src/commands/board.js +5 -9
- package/src/commands/check.js +29 -16
- package/src/commands/doctor.js +2 -0
- package/src/commands/env.js +176 -148
- package/src/commands/go.js +76 -55
- package/src/commands/harden.js +105 -86
- package/src/commands/hu-start.js +52 -0
- package/src/commands/hu.js +288 -221
- package/src/commands/identity.js +111 -78
- package/src/commands/init-atril.js +47 -0
- package/src/commands/init.js +11 -40
- package/src/commands/install-tools.js +70 -85
- package/src/commands/main-guard.js +78 -0
- package/src/commands/policy.js +226 -184
- package/src/commands/qmd.js +27 -21
- package/src/commands/rag.js +75 -48
- package/src/commands/review-gate.js +422 -339
- package/src/commands/start.js +4 -5
- package/src/commands/steward.js +96 -47
- package/src/commands/tournament.js +46 -37
- package/src/commands/worktree.js +94 -25
- package/src/config/loader.js +34 -17
- package/src/config/overrides.js +2 -1
- package/src/config/secrets.js +36 -0
- package/src/config.js +1 -0
- package/src/environment/adr.js +16 -2
- package/src/environment/board-access.js +11 -9
- package/src/git/karajan-label.js +20 -8
- package/src/guards/main-guard.js +54 -0
- package/src/harden/branch-protection.js +36 -3
- package/src/harden/hook-templates.js +16 -1
- package/src/harden/sentinel/posttooluse.mjs +18 -6
- package/src/harden/sentinel/pretooluse-sentinel.mjs +81 -35
- package/src/harden/sentinel/sentinel-lib.mjs +149 -23
- package/src/harden/sentinel/sentinel-shell.mjs +64 -0
- package/src/harden/sentinel/sessionstart.mjs +3 -2
- package/src/harden/sentinel/stop.mjs +5 -4
- package/src/harden/workflow-engine.js +12 -1
- package/src/harden/workflow-templates.js +1 -1
- package/src/hu/board-lookup.js +29 -0
- package/src/hu/gh-identity.js +23 -0
- package/src/hu/github-board.js +4 -2
- package/src/hu/lane-prune.js +15 -7
- package/src/hu/lane-setup.js +65 -0
- package/src/hu/lanes.js +66 -0
- package/src/hu/team-board-mirror.js +3 -2
- package/src/infrastructure/command-runner.js +1 -1
- package/src/infrastructure/file-system-service.js +1 -1
- package/src/mcp/handlers/direct-handlers.js +2 -292
- package/src/mcp/handlers/hu-handlers.js +44 -55
- package/src/mcp/handlers/management-handlers.js +2 -42
- package/src/mcp/server-handlers.js +16 -22
- package/src/mcp/tools.js +5 -200
- package/src/plan/lane-plans.js +54 -0
- package/src/plan/plan-store.js +7 -4
- package/src/policy/add.js +26 -12
- package/src/policy/adr-number-check.js +31 -0
- package/src/prompts/audit.js +299 -339
- package/src/prompts/card-context.js +10 -5
- package/src/prompts/reviewer.js +5 -2
- package/src/rag/auto-update.js +56 -43
- package/src/rag/bootstrap-index.js +28 -54
- package/src/rag/migrate.js +1 -1
- package/src/rag/onnx-fallback.js +2 -2
- package/src/rag/pid-lease.js +70 -0
- package/src/rag/project-store.js +2 -12
- package/src/review/card-first.js +57 -46
- package/src/review/dual-review.js +5 -4
- package/src/review/gate-gitignore.js +23 -4
- package/src/review/gate-hook.js +47 -0
- package/src/review/one-shot-review.js +51 -37
- package/src/review/publish-pr.js +18 -3
- package/src/review/repo-docs.js +73 -0
- package/src/review/repo-stack.js +59 -0
- package/src/review/reviewer-fallback.js +34 -3
- package/src/review/seal-commit.js +29 -0
- package/src/review/sonar-pregate.js +59 -44
- package/src/review/third-party-review.js +90 -16
- package/src/review/third-party-size.js +43 -0
- package/src/review/third-party-skills.js +54 -0
- package/src/review/verify-pr.js +2 -26
- package/src/roles/audit-role.js +106 -125
- package/src/skills/atril-skills.js +5 -4
- package/src/sonar/scanner.js +116 -101
- package/src/steward/invariants.js +5 -4
- package/src/steward/phantom-sweep.js +66 -0
- package/src/tournament/judge.js +60 -53
- package/src/utils/agent-home.js +33 -0
- package/src/utils/gh-account.js +37 -0
- package/src/utils/git.js +0 -13
- package/src/utils/home-migration.js +7 -1
- package/src/utils/main-checkout.js +53 -0
- package/src/utils/qmd-collection.js +3 -1
- package/src/utils/welcome.js +3 -3
- package/templates/{skills → atril/commands}/kj-architect.md +5 -0
- package/templates/{skills → atril/commands}/kj-audit.md +5 -0
- package/templates/{skills → atril/commands}/kj-board.md +5 -0
- package/templates/{skills → atril/commands}/kj-code.md +5 -0
- package/templates/{skills → atril/commands}/kj-csv-transform.md +5 -0
- package/templates/{skills → atril/commands}/kj-data-report.md +5 -0
- package/templates/{skills → atril/commands}/kj-discover.md +5 -0
- package/templates/{skills → atril/commands}/kj-rag-query.md +5 -0
- package/templates/{skills → atril/commands}/kj-review.md +5 -0
- package/templates/{skills → atril/commands}/kj-security.md +5 -0
- package/templates/{skills → atril/commands}/kj-sonar.md +5 -0
- package/templates/{skills → atril/commands}/kj-sql-analysis.md +5 -0
- package/templates/{skills → atril/commands}/kj-test.md +5 -0
- package/packages/hu-board/public/utils/run-launcher.js +0 -169
- package/packages/hu-board/src/run-tracker.js +0 -121
- package/src/activity-log.js +0 -59
- package/src/agents/host-agent.js +0 -38
- package/src/agents/model-registry.js +0 -142
- package/src/audit/plan-adherence.js +0 -224
- package/src/autonomy/arbiter-role.js +0 -71
- package/src/autonomy/arbiter.js +0 -22
- package/src/autonomy/decider.js +0 -18
- package/src/autonomy/decision-resolver.js +0 -58
- package/src/autonomy/outcome-report.js +0 -55
- package/src/autonomy/stage-ask.js +0 -30
- package/src/brain/decision-tracker.js +0 -125
- package/src/brain/decisor.js +0 -243
- package/src/brain/solomon-consult.js +0 -171
- package/src/brain/standby-scheduler.js +0 -8
- package/src/budget/comparison.js +0 -96
- package/src/canvas/parse-md.js +0 -238
- package/src/canvas/schema.js +0 -101
- package/src/canvas/to-flat.js +0 -85
- package/src/canvas/validate.js +0 -163
- package/src/ci/dispatch.js +0 -99
- package/src/ci/index.js +0 -3
- package/src/ci/pr-diff.js +0 -26
- package/src/cli/register-standby.js +0 -26
- package/src/commands/architect.js +0 -111
- package/src/commands/autorun.js +0 -49
- package/src/commands/researcher.js +0 -54
- package/src/commands/resume.js +0 -72
- package/src/commands/review.js +0 -89
- package/src/commands/run.js +0 -185
- package/src/commands/standby.js +0 -117
- package/src/commands/status.js +0 -36
- package/src/commands/triage.js +0 -78
- package/src/commands/undo.js +0 -62
- package/src/domains/domain-loader.js +0 -245
- package/src/domains/domain-registry.js +0 -140
- package/src/domains/domain-synthesizer.js +0 -129
- package/src/git/automation.js +0 -433
- package/src/git/coder-commit-guard.js +0 -40
- package/src/git/hu-automation.js +0 -243
- package/src/git/hu-snapshot.js +0 -10
- package/src/git/symlink-targets.js +0 -57
- package/src/golden/asserter.js +0 -92
- package/src/golden/loader.js +0 -52
- package/src/golden/runner.js +0 -92
- package/src/golden/schema.js +0 -41
- package/src/guards/duplicate-members.js +0 -86
- package/src/guards/intent-guard.js +0 -161
- package/src/guards/perf-guard.js +0 -126
- package/src/guards/policy-guard.js +0 -90
- package/src/guards/policy-resolver.js +0 -85
- package/src/guards/secret-redactor.js +0 -71
- package/src/hu/acceptance-runner.js +0 -138
- package/src/hu/lazy-planner.js +0 -109
- package/src/hu/parallel-executor.js +0 -94
- package/src/hu/routing-metrics.js +0 -17
- package/src/hu/split-validator.js +0 -143
- package/src/hu/splitting-detector.js +0 -158
- package/src/hu/splitting-generator.js +0 -175
- package/src/hu/store.js +0 -394
- package/src/infrastructure/mocks.js +0 -182
- package/src/mcp/handlers/run-handler.js +0 -318
- package/src/mcp/sovereignty-guard.js +0 -113
- package/src/mcp/tool-arg-normalizers.js +0 -16
- package/src/mutate/reviewer-signal.js +0 -76
- package/src/orchestrator/agent-fallback.js +0 -81
- package/src/orchestrator/brain-coordinator.js +0 -186
- package/src/orchestrator/ci-integration.js +0 -139
- package/src/orchestrator/config-init.js +0 -426
- package/src/orchestrator/direct-actions.js +0 -217
- package/src/orchestrator/drivers/error-recovery.js +0 -287
- package/src/orchestrator/drivers/init-context.js +0 -238
- package/src/orchestrator/drivers/iteration-loop.js +0 -382
- package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +0 -69
- package/src/orchestrator/drivers/iteration-phases/guards.js +0 -91
- package/src/orchestrator/drivers/iteration-phases/handle-approved.js +0 -29
- package/src/orchestrator/drivers/iteration-phases/quality-gates.js +0 -125
- package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +0 -62
- package/src/orchestrator/drivers/post-loop.js +0 -404
- package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +0 -137
- package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +0 -47
- package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +0 -84
- package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +0 -393
- package/src/orchestrator/drivers/pre-loop.js +0 -524
- package/src/orchestrator/drivers/run-hu-batch.js +0 -468
- package/src/orchestrator/feedback-enrichment.js +0 -140
- package/src/orchestrator/feedback-queue.js +0 -151
- package/src/orchestrator/flow-control.js +0 -170
- package/src/orchestrator/flow-runner.js +0 -365
- package/src/orchestrator/fs-leak-detector.js +0 -228
- package/src/orchestrator/hu-scheduler.js +0 -114
- package/src/orchestrator/hu-sub-pipeline.js +0 -642
- package/src/orchestrator/iteration-gate.js +0 -73
- package/src/orchestrator/iteration-stages.js +0 -9
- package/src/orchestrator/parallel-limiter.js +0 -74
- package/src/orchestrator/pipeline-context.js +0 -72
- package/src/orchestrator/post-loop-stages.js +0 -468
- package/src/orchestrator/pre-loop-stages.js +0 -11
- package/src/orchestrator/preflight-checks.js +0 -487
- package/src/orchestrator/repair/repair-detector.js +0 -117
- package/src/orchestrator/repair/repair-runner.js +0 -142
- package/src/orchestrator/reviewer-fallback.js +0 -45
- package/src/orchestrator/role-output-compressor.js +0 -189
- package/src/orchestrator/session-journal.js +0 -308
- package/src/orchestrator/solomon-escalation.js +0 -331
- package/src/orchestrator/solomon-rules.js +0 -212
- package/src/orchestrator/stages/acceptance-coverage-stage.js +0 -47
- package/src/orchestrator/stages/architect-stage.js +0 -172
- package/src/orchestrator/stages/coder-stage.js +0 -506
- package/src/orchestrator/stages/domain-curator-stage.js +0 -82
- package/src/orchestrator/stages/format-stage.js +0 -61
- package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +0 -127
- package/src/orchestrator/stages/hu-reviewer-stage.js +0 -419
- package/src/orchestrator/stages/perf-stage.js +0 -140
- package/src/orchestrator/stages/planner-stage.js +0 -85
- package/src/orchestrator/stages/rag-context-stage.js +0 -80
- package/src/orchestrator/stages/rag-preload-decisor.js +0 -58
- package/src/orchestrator/stages/research-stage.js +0 -69
- package/src/orchestrator/stages/reviewer-stage.js +0 -431
- package/src/orchestrator/stages/sonar-stage.js +0 -439
- package/src/orchestrator/stages/stage-classes.js +0 -84
- package/src/orchestrator/stages/stage-executor.js +0 -155
- package/src/orchestrator/stages/suite-green-stage.js +0 -62
- package/src/orchestrator/stages/tdd-discipline-stage.js +0 -56
- package/src/orchestrator/stages/tool-judge-stage.js +0 -53
- package/src/orchestrator/stages/triage-stage.js +0 -172
- package/src/orchestrator/standby.js +0 -71
- package/src/orchestrator/verification-gate.js +0 -183
- package/src/orchestrator.js +0 -22
- package/src/plan/adr-generator.js +0 -170
- package/src/plan/adr-loader.js +0 -60
- package/src/plan/plan-executor.js +0 -100
- package/src/plan/plan-validation.js +0 -4
- package/src/prompts/acceptance-coverage.js +0 -32
- package/src/prompts/arbiter.js +0 -38
- package/src/prompts/architect.js +0 -103
- package/src/prompts/hu-reviewer.js +0 -244
- package/src/prompts/tool-correctness-judge.js +0 -31
- package/src/prompts/triage.js +0 -57
- package/src/rag/embedder.js +0 -2
- package/src/rag/embedders/_cloud-base.js +0 -2
- package/src/rag/embedders/cohere.js +0 -2
- package/src/rag/embedders/mistral.js +0 -2
- package/src/rag/embedders/onnx.js +0 -2
- package/src/rag/embedders/openai.js +0 -2
- package/src/rag/embedders/voyage.js +0 -2
- package/src/rag/rerank.js +0 -4
- package/src/rag/where-parser.js +0 -4
- package/src/repeat-detector.js +0 -88
- package/src/review/diff-generator.js +0 -112
- package/src/review/git-stash-helper.js +0 -104
- package/src/review/schema.js +0 -31
- package/src/review/scope-filter.js +0 -152
- package/src/review/snapshot-diff.js +0 -122
- package/src/review/tdd-discipline.js +0 -90
- package/src/review/tdd-policy.js +0 -69
- package/src/roles/acceptance-coverage-role.js +0 -78
- package/src/roles/architect-role.js +0 -77
- package/src/roles/coder-role.js +0 -113
- package/src/roles/commiter-role.js +0 -113
- package/src/roles/domain-curator-role.js +0 -134
- package/src/roles/hu-reviewer-role.js +0 -64
- package/src/roles/impeccable-role.js +0 -85
- package/src/roles/index.js +0 -18
- package/src/roles/karajan-brain-role.js +0 -134
- package/src/roles/perf-role.js +0 -122
- package/src/roles/planner-role.js +0 -151
- package/src/roles/refactorer-role.js +0 -30
- package/src/roles/repairer-role.js +0 -181
- package/src/roles/researcher-role.js +0 -70
- package/src/roles/reviewer-role.js +0 -115
- package/src/roles/security-role.js +0 -73
- package/src/roles/solomon-role.js +0 -271
- package/src/roles/sonar-role.js +0 -72
- package/src/roles/tester-role.js +0 -235
- package/src/roles/tool-correctness-judge-role.js +0 -78
- package/src/roles/triage-role.js +0 -99
- package/src/session/journal/decisions-writer.js +0 -200
- package/src/session/journal/iteration-logger.js +0 -194
- package/src/session/journal/summary-writer.js +0 -320
- package/src/session/journal/tree-writer.js +0 -164
- package/src/session/journal/write-all.js +0 -89
- package/src/session-cleanup.js +0 -116
- package/src/skills/semantic-detector.js +0 -167
- package/src/skills/skill-detector.js +0 -385
- package/src/sonar/cloud-scanner.js +0 -78
- package/src/tracker-bootstrap.js +0 -44
- package/src/types/index.js +0 -38
- package/src/types/policy.js +0 -43
- package/src/types/solomon.js +0 -87
- package/src/utils/async-lock.js +0 -31
- package/src/utils/budget.js +0 -229
- package/src/utils/cwd-confirm.js +0 -39
- package/src/utils/display/event-handlers.js +0 -403
- package/src/utils/display/formatters.js +0 -132
- package/src/utils/display/header.js +0 -47
- package/src/utils/display/session-summary.js +0 -118
- package/src/utils/display/solomon.js +0 -42
- package/src/utils/pricing.js +0 -29
- package/src/utils/quota-simulator.js +0 -56
- package/src/utils/role-assigner.js +0 -137
- package/src/utils/rtk-wrapper.js +0 -87
- package/src/utils/run-registry.js +0 -9
- package/src/utils/status-dashboard.js +0 -254
- package/src/utils/tool-call-extractor.js +0 -56
- package/src/webperf/devtools-detect.js +0 -112
- package/src/webperf/webperf-role-integration.js +0 -66
- package/templates/skills/kj-run.md +0 -73
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KJC-BUG-0454: one card of the HU Board, read where `kj hu` keeps it. `kj code --card`
|
|
3
|
+
* looked in the old kj run store, so every card `kj hu add` created was "not found".
|
|
4
|
+
* Shared board: the GitHub issue HU-<n>, as `kj hu list` reads it. Local board: the
|
|
5
|
+
* plan backlog, by id or short id.
|
|
6
|
+
*/
|
|
7
|
+
import { listPlans, loadPlan } from "../plan/plan-store.js";
|
|
8
|
+
import { readBoardConfig } from "./board-config.js";
|
|
9
|
+
import { listHus } from "./github-board.js";
|
|
10
|
+
import { pinGhIdentity } from "./gh-identity.js";
|
|
11
|
+
import { listWithCache } from "./team-board-cache.js";
|
|
12
|
+
|
|
13
|
+
const SHARED_ID = /^HU-[1-9]\d*$/i;
|
|
14
|
+
|
|
15
|
+
export async function findBoardHu(projectDir, ref, deps = {}) {
|
|
16
|
+
const board = await (deps.boardConfig ?? readBoardConfig)(projectDir);
|
|
17
|
+
if (board.backend === "github" && SHARED_ID.test(ref)) {
|
|
18
|
+
(deps.pinGhIdentity ?? pinGhIdentity)({ projectDir });
|
|
19
|
+
const { rows } = await (deps.cache?.list ?? listWithCache)({ projectDir, repo: board.repo, list: deps.listTeamHus ?? listHus });
|
|
20
|
+
const hu = rows.find((h) => h.short_id.toUpperCase() === ref.toUpperCase());
|
|
21
|
+
if (hu) return hu;
|
|
22
|
+
}
|
|
23
|
+
for (const meta of await (deps.listPlans ?? listPlans)(projectDir)) {
|
|
24
|
+
const plan = await (deps.loadPlan ?? loadPlan)(projectDir, meta.planId);
|
|
25
|
+
const hu = (plan.hus || []).find((h) => h.id === ref || h.short_id === ref);
|
|
26
|
+
if (hu) return hu;
|
|
27
|
+
}
|
|
28
|
+
throw new Error(`HU ${ref} is not on the HU Board — kj hu list shows its cards`);
|
|
29
|
+
}
|
package/src/hu/gh-identity.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
// accounts on one machine, another session switching the active one turned `kj hu move` into an
|
|
3
3
|
// opaque GraphQL permission error, three times in a day. The clone declares its account in
|
|
4
4
|
// .karajan/identity.local.yml (ADR 0005): with a declaration, the active account must be it.
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
5
6
|
import { readFileSync } from "node:fs";
|
|
6
7
|
import { homedir } from "node:os";
|
|
7
8
|
import { join } from "node:path";
|
|
@@ -42,3 +43,25 @@ export function assertGhIdentity({ projectDir, env = process.env }) {
|
|
|
42
43
|
}
|
|
43
44
|
return { declared, active };
|
|
44
45
|
}
|
|
46
|
+
|
|
47
|
+
/** The token gh keeps for `user`, read without switching the active account; null when it has none. */
|
|
48
|
+
function ghTokenOf(user, env) {
|
|
49
|
+
const res = spawnSync("gh", ["auth", "token", "--hostname", "github.com", "--user", user], { env, encoding: "utf8" });
|
|
50
|
+
return res.status === 0 ? res.stdout.trim() || null : null;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* KJC-BUG-0406: checking the active account and then calling gh was a race; another session could
|
|
55
|
+
* switch it in between. With an identity declared, GH_TOKEN is pinned to that account's token for
|
|
56
|
+
* the rest of the command, so every gh call goes out as it. A token already in the environment is
|
|
57
|
+
* the user's explicit choice and is left alone.
|
|
58
|
+
* @returns {{ declared: string | null, pinned: boolean }}
|
|
59
|
+
*/
|
|
60
|
+
export function pinGhIdentity({ projectDir, env = process.env, tokenOf = ghTokenOf }) {
|
|
61
|
+
const declared = readIdentity(projectDir)?.gh_user ?? null;
|
|
62
|
+
if (!declared || env.GH_TOKEN || env.GITHUB_TOKEN) return { declared, pinned: false };
|
|
63
|
+
const token = tokenOf(declared, env);
|
|
64
|
+
if (!token) throw new BoardRejectedError(`gh has no login for ${declared}, the account this clone declares: run gh auth login and retry`);
|
|
65
|
+
env.GH_TOKEN = token;
|
|
66
|
+
return { declared, pinned: true };
|
|
67
|
+
}
|
package/src/hu/github-board.js
CHANGED
|
@@ -70,8 +70,10 @@ export async function createHu({ repo, title, acceptanceCriteria = [], tests = [
|
|
|
70
70
|
|
|
71
71
|
/** Every HU of the repo, with its status and who holds it. */
|
|
72
72
|
export async function listHus({ repo, run = runCommand }) {
|
|
73
|
-
const issues = listOf(await gh(run, ["issue", "list", "-R", repo, "--label", HU_LABEL, "--state", "all", "--limit", "1000", "--json", "number,title,state,labels,assignees"]));
|
|
74
|
-
|
|
73
|
+
const issues = listOf(await gh(run, ["issue", "list", "-R", repo, "--label", HU_LABEL, "--state", "all", "--limit", "1000", "--json", "number,title,state,labels,assignees,body"]));
|
|
74
|
+
// KJC-BUG-0443: the tracker reference kj hu add --ref wrote in the body.
|
|
75
|
+
const refOf = (body) => /^Ref: (\S.*)$/m.exec(body ?? "")?.[1].trim() ?? null;
|
|
76
|
+
return issues.map((i) => ({ short_id: `HU-${i.number}`, number: i.number, title: i.title, status: statusOf(i), claimed_by: i.assignees[0]?.login ?? null, ref: refOf(i.body) }));
|
|
75
77
|
}
|
|
76
78
|
|
|
77
79
|
/** The one kj:<status> of an issue; none or several is a board someone broke by hand. */
|
package/src/hu/lane-prune.js
CHANGED
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import fs from "node:fs";
|
|
8
8
|
import path from "node:path";
|
|
9
|
+
import { laneWork } from "./lane-setup.js";
|
|
9
10
|
|
|
10
11
|
const IDLE_HOURS = 24;
|
|
11
12
|
const REGENERABLE = new Set(["node_modules", "dist", "build", "coverage", ".venv", "venv", "__pycache__", ".cache", "target"]);
|
|
@@ -55,15 +56,16 @@ export async function classifyLanes({ git, laneRoot, base, processes, now = Date
|
|
|
55
56
|
}
|
|
56
57
|
return Promise.all(lanes.map(async (lane) => {
|
|
57
58
|
const reasons = [];
|
|
58
|
-
|
|
59
|
-
const
|
|
59
|
+
// KJC-TSK-1371: the identity and Sentinel files lane setup wrote are not the lane's work.
|
|
60
|
+
const { stdout: status } = await git(["status", "--porcelain", "--ignored=matching", "--untracked-files=all"], lane.path);
|
|
61
|
+
const entries = laneWork(status, lane.path);
|
|
60
62
|
if (entries.some((e) => !e.startsWith("!! "))) reasons.push("uncommitted changes");
|
|
61
63
|
// Ignored files can be valuable (.env, local artifacts); only regenerable dirs are not.
|
|
62
64
|
const precious = entries.filter((e) => e.startsWith("!! ") && !REGENERABLE.has(e.slice(3).split("/")[0]));
|
|
63
65
|
if (precious.length > 0) reasons.push(`ignored files that are not regenerable (${precious[0].slice(3)}${precious.length > 1 ? ", …" : ""})`);
|
|
64
66
|
// The lane's HEAD, branch or detached: a detached lane's commits have no other name.
|
|
65
|
-
const
|
|
66
|
-
if (
|
|
67
|
+
const ahead = await commitsNotIn({ git, base, cwd: lane.path });
|
|
68
|
+
if (ahead > 0) reasons.push(`${ahead} commit(s) not in ${base}`);
|
|
67
69
|
if (processes === null) reasons.push("cannot check for live processes on this platform");
|
|
68
70
|
else if ([...processes.cwds].some((cwd) => inside(cwd, lane.path))) reasons.push("a live process works in it");
|
|
69
71
|
else if (processes.unseen > 0 && (await idleHours(git, lane.path, now)) < IDLE_HOURS) {
|
|
@@ -83,6 +85,12 @@ export async function resolveBase({ git, base }) {
|
|
|
83
85
|
}
|
|
84
86
|
}
|
|
85
87
|
|
|
88
|
+
/** Commits of `ref` (default HEAD, run in `cwd`) that `base` lacks; 0 means merged. */
|
|
89
|
+
export async function commitsNotIn({ git, base, ref = "HEAD", cwd }) {
|
|
90
|
+
const { stdout } = await git(["rev-list", "--count", `${base}..${ref}`], cwd);
|
|
91
|
+
return Number(stdout);
|
|
92
|
+
}
|
|
93
|
+
|
|
86
94
|
/** KJC-TSK-1037: loose kj-hu-* branches (no worktree): the empty ones, and the ones with work and how much. */
|
|
87
95
|
export async function looseLaneBranches({ git, base }) {
|
|
88
96
|
const { stdout: listed } = await git(["for-each-ref", "--format=%(refname:short) %(worktreepath)", "refs/heads/kj-hu-*"]);
|
|
@@ -90,9 +98,9 @@ export async function looseLaneBranches({ git, base }) {
|
|
|
90
98
|
const empty = [];
|
|
91
99
|
const withWork = [];
|
|
92
100
|
for (const branch of loose) {
|
|
93
|
-
const
|
|
94
|
-
if (
|
|
95
|
-
else withWork.push({ branch, reason: `${
|
|
101
|
+
const ahead = await commitsNotIn({ git, base, ref: branch });
|
|
102
|
+
if (ahead === 0) empty.push(branch);
|
|
103
|
+
else withWork.push({ branch, reason: `${ahead} commit(s) not in ${base}` });
|
|
96
104
|
}
|
|
97
105
|
return { empty, withWork };
|
|
98
106
|
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lane setup (KJC-TSK-1371, #2852, step 6 of #2566): a lane `kj worktree start` creates is
|
|
3
|
+
* ready on creation. Two things a fresh git worktree lacks, because git does not track them:
|
|
4
|
+
* - the clone identity (.karajan/identity.local.yml), which the hooks read from the lane root
|
|
5
|
+
* so the identity lock knows who pushes; copied from the main checkout when it has one;
|
|
6
|
+
* - the Sentinel wiring (.claude/settings.json hooks and .karajan/harness), installed in the
|
|
7
|
+
* lane with the same installers `kj harden` uses, so the first write there is governed.
|
|
8
|
+
* A step that fails stops the start loudly, naming the step: never a half-set lane in silence.
|
|
9
|
+
*/
|
|
10
|
+
import fs from "node:fs";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
|
|
13
|
+
import { installSentinelHooks } from "../harden/sentinel-hooks.js";
|
|
14
|
+
import { installHarnessHooks } from "../harden/harness-hooks.js";
|
|
15
|
+
|
|
16
|
+
const IDENTITY = path.join(".karajan", "identity.local.yml");
|
|
17
|
+
const INSTALLERS = { sentinel: installSentinelHooks, harness: installHarnessHooks };
|
|
18
|
+
|
|
19
|
+
/** What setupLane writes into a lane (and the Sentinel's runtime state): never the lane's work. */
|
|
20
|
+
export const isLaneSetupPath = (p) => p === IDENTITY || p === path.join(".claude", "settings.json") || p.startsWith(path.join(".karajan", "harness") + path.sep);
|
|
21
|
+
|
|
22
|
+
/** A folder git lists whole (an ignored `.claude/`) is setup only when every file in it is. */
|
|
23
|
+
function onlySetup(lanePath, dir) {
|
|
24
|
+
try {
|
|
25
|
+
const files = fs.readdirSync(path.join(lanePath, dir), { recursive: true, withFileTypes: true }).filter((d) => d.isFile());
|
|
26
|
+
return files.every((d) => isLaneSetupPath(path.relative(lanePath, path.join(d.parentPath, d.name))));
|
|
27
|
+
} catch {
|
|
28
|
+
return false;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** `git status --porcelain` lines that are the lane's own work, setup files left out. */
|
|
33
|
+
export const laneWork = (porcelain, lanePath) => porcelain.split("\n").filter(Boolean).filter((line) => {
|
|
34
|
+
const p = line.slice(3);
|
|
35
|
+
return !(isLaneSetupPath(p) || (p.endsWith("/") && lanePath && onlySetup(lanePath, p)));
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function step(name, fn) {
|
|
39
|
+
try {
|
|
40
|
+
return fn();
|
|
41
|
+
} catch (err) {
|
|
42
|
+
throw new Error(`lane setup failed at ${name}: ${err?.message || err}`, { cause: err });
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** @returns {Promise<{ identity: boolean, wired: boolean }>} */
|
|
47
|
+
export async function setupLane({ mainDir, lanePath, logger = console, install = INSTALLERS }) {
|
|
48
|
+
const source = path.join(mainDir, IDENTITY);
|
|
49
|
+
const identity = step("the clone identity", () => {
|
|
50
|
+
if (!fs.existsSync(source)) return false;
|
|
51
|
+
fs.mkdirSync(path.join(lanePath, ".karajan"), { recursive: true });
|
|
52
|
+
fs.copyFileSync(source, path.join(lanePath, IDENTITY));
|
|
53
|
+
return true;
|
|
54
|
+
});
|
|
55
|
+
if (identity) logger.info?.(` identity: ${IDENTITY} copied from the main checkout`);
|
|
56
|
+
else logger.warn?.(`! no ${IDENTITY} in the main checkout: run kj identity set in the lane before pushing`);
|
|
57
|
+
|
|
58
|
+
step("the Sentinel wiring", () => {
|
|
59
|
+
const quiet = { info: () => {}, warn: (m) => logger.warn?.(m) };
|
|
60
|
+
const results = [install.sentinel({ projectDir: lanePath, logger: quiet }), install.harness({ projectDir: lanePath, logger: quiet })];
|
|
61
|
+
if (results.some((r) => !r?.wired)) throw new Error("the hooks were not wired (unreadable .claude/settings.json?)");
|
|
62
|
+
});
|
|
63
|
+
logger.info?.(" Sentinel: hooks wired in the lane");
|
|
64
|
+
return { identity, wired: true };
|
|
65
|
+
}
|
package/src/hu/lanes.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* KJC-TSK-1313: which card each worktree lane works on. Read from git every time, never stored on
|
|
3
|
+
* the card, so a lane removed by hand never lingers on the board.
|
|
4
|
+
*/
|
|
5
|
+
import path from "node:path";
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* The worktrees with a branch in `git worktree list --porcelain`. The first entry is the main
|
|
9
|
+
* checkout and is left out, as are detached worktrees. With `root`, only the lanes under it.
|
|
10
|
+
* @returns {{slug: string, branch: string, path: string}[]}
|
|
11
|
+
*/
|
|
12
|
+
export function parseLanes(porcelain, { root = null } = {}) {
|
|
13
|
+
const lanes = [];
|
|
14
|
+
let current = null;
|
|
15
|
+
let seen = 0;
|
|
16
|
+
for (const line of porcelain.split("\n")) {
|
|
17
|
+
if (line.startsWith("worktree ")) {
|
|
18
|
+
current = seen++ === 0 ? null : { path: line.slice(9).trim() };
|
|
19
|
+
} else if (line.startsWith("branch ") && current) {
|
|
20
|
+
lanes.push({ slug: path.basename(current.path), branch: line.slice(7).trim().replace(/^refs\/heads\//, ""), path: current.path });
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return root ? lanes.filter((l) => l.path.startsWith(root + path.sep)) : lanes;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const escapeRe = (s) => s.replace(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* The lanes of each card: those whose branch carries the card id as a whole token (so HU-2 never
|
|
30
|
+
* claims HU-27). Cards without a lane have no entry.
|
|
31
|
+
* @returns {Map<string, {slug: string, branch: string, path: string}[]>}
|
|
32
|
+
*/
|
|
33
|
+
export function lanesByCard(lanes, cardIds) {
|
|
34
|
+
const byCard = new Map();
|
|
35
|
+
for (const id of new Set(cardIds)) {
|
|
36
|
+
const token = new RegExp(`(^|[^a-z0-9])${escapeRe(id)}($|[^a-z0-9])`, "i");
|
|
37
|
+
const hits = lanes.filter((l) => token.test(l.branch));
|
|
38
|
+
if (hits.length > 0) byCard.set(id, hits);
|
|
39
|
+
}
|
|
40
|
+
return byCard;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* The cards a lane works on: those whose id or any of whose references (a tracker card, an
|
|
45
|
+
* issue) its branch names. `cards` are `{id, refs}`.
|
|
46
|
+
* @returns {string[]}
|
|
47
|
+
*/
|
|
48
|
+
/**
|
|
49
|
+
* KJC-TSK-1316: one PR per branch from `gh pr list --json number,url,state,headRefName`: an open
|
|
50
|
+
* one wins, else the newest (a merged lane is what KJC-TSK-1314 warns about).
|
|
51
|
+
* @returns {Map<string, {number: number, url: string, state: string}>}
|
|
52
|
+
*/
|
|
53
|
+
export function prsByBranch(prs) {
|
|
54
|
+
const byBranch = new Map();
|
|
55
|
+
const open = (p) => (p.state === "OPEN" ? 1 : 0);
|
|
56
|
+
for (const p of prs) {
|
|
57
|
+
const cur = byBranch.get(p.headRefName);
|
|
58
|
+
if (!cur || open(p) > open(cur) || (open(p) === open(cur) && p.number > cur.number)) {
|
|
59
|
+
byBranch.set(p.headRefName, { number: p.number, url: p.url, state: p.state });
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return byBranch;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export const cardsOfLane = (lane, cards) =>
|
|
66
|
+
cards.filter((c) => lanesByCard([lane], [c.id, ...c.refs]).size > 0).map((c) => c.id);
|
|
@@ -19,10 +19,11 @@ export async function writeTeamMirror(projectDir, rows, { repo }) {
|
|
|
19
19
|
const plan = {
|
|
20
20
|
version: 2, planId: MIRROR_PLAN_ID, name: "github", alias: "github", projectDir,
|
|
21
21
|
task: `Shared HU Board: GitHub Issues of ${repo}`,
|
|
22
|
-
status: "ready", readonly: "github", createdAt: now,
|
|
22
|
+
status: "ready", readonly: "github", repo, createdAt: now,
|
|
23
23
|
hus: rows.map((r) => ({
|
|
24
24
|
id: r.short_id, short_id: r.short_id, title: r.title, status: r.status,
|
|
25
|
-
|
|
25
|
+
// KJC-BUG-0443: the tracker card it replicates too, so the gates find it by that reference.
|
|
26
|
+
claimed_by: r.claimed_by ?? null, created_by: "github", external_ref: [`${repo}#${r.number}`, r.ref].filter(Boolean).join(" · "),
|
|
26
27
|
acceptance_criteria: [], acceptance_tests: [], createdAt: now, updatedAt: now,
|
|
27
28
|
})),
|
|
28
29
|
};
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*
|
|
9
9
|
* This adapter preserves the runCommand API exactly, so current consumers
|
|
10
10
|
* can opt-in to DI without behavioural changes. A matching MockCommandRunner
|
|
11
|
-
* lives in
|
|
11
|
+
* lives in tests/support/infrastructure-mocks.js and lets tests script outputs per
|
|
12
12
|
* invocation.
|
|
13
13
|
*
|
|
14
14
|
* @typedef {Object} CommandRunResult
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* This adapter preserves the exact native API shapes for the handful of
|
|
9
9
|
* operations actually used in the codebase, so consumers can opt-in to DI
|
|
10
10
|
* without rewriting call sites. A matching MockFileSystem lives in
|
|
11
|
-
*
|
|
11
|
+
* tests/support/infrastructure-mocks.js.
|
|
12
12
|
*
|
|
13
13
|
* Scope for TSK-0316: intentionally minimal. Only the operations already used
|
|
14
14
|
* by orchestrator + agents. Additional methods added on demand.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Direct role handler logic (code,
|
|
3
|
-
*
|
|
2
|
+
* Direct role handler logic (code, audit, discover). KJC-TSK-1318: review,
|
|
3
|
+
* plan, triage, researcher and architect left with the kj run pipeline.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import fs from "node:fs/promises";
|
|
@@ -8,12 +8,7 @@ import { resolveRole } from "../../config.js";
|
|
|
8
8
|
import { createLogger } from "../../utils/logger.js";
|
|
9
9
|
import { assertAgentsAvailable } from "../../agents/availability.js";
|
|
10
10
|
import { createAgent } from "../../agents/index.js";
|
|
11
|
-
import { buildPlannerPrompt } from "../../prompts/planner.js";
|
|
12
11
|
import { buildCoderPrompt } from "../../prompts/coder.js";
|
|
13
|
-
import { buildReviewerPrompt } from "../../prompts/reviewer.js";
|
|
14
|
-
import { parseMaybeJsonString, agentAnswerText } from "../../review/parser.js";
|
|
15
|
-
import { computeBaseRef, generateDiff } from "../../review/diff-generator.js";
|
|
16
|
-
import { resolveReviewProfile } from "../../review/profiles.js";
|
|
17
12
|
import { createRunLog } from "../../utils/run-log.js";
|
|
18
13
|
import { createStallDetector } from "../../utils/stall-detector.js";
|
|
19
14
|
import { sendTrackerLog } from "../progress.js";
|
|
@@ -66,165 +61,6 @@ function applySessionOverrides(a, roleKeys) {
|
|
|
66
61
|
}
|
|
67
62
|
}
|
|
68
63
|
|
|
69
|
-
export async function handlePlanDirect(a, server, extra) {
|
|
70
|
-
// KJC-PCS-0048 PR 3b: same `specReviewMode: skip` → skipSpecReview
|
|
71
|
-
// mapping as kj_run. Default "auto" keeps the reviewer enabled.
|
|
72
|
-
if (a.specReviewMode === "skip") a.skipSpecReview = true;
|
|
73
|
-
const { normalizePlanArgs } = await import("../tool-arg-normalizers.js");
|
|
74
|
-
const options = normalizePlanArgs(a);
|
|
75
|
-
const config = await buildConfig(options, "plan");
|
|
76
|
-
const logger = createLogger(config.output.log_level, "mcp");
|
|
77
|
-
|
|
78
|
-
const plannerRole = resolveRole(config, "planner");
|
|
79
|
-
await assertAgentsAvailable([plannerRole.provider]);
|
|
80
|
-
|
|
81
|
-
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
82
|
-
await ensureTaskFromFile(a, projectDir);
|
|
83
|
-
const runLog = createRunLog(projectDir);
|
|
84
|
-
const silenceTimeoutMs = Number(config?.session?.max_agent_silence_minutes) > 0
|
|
85
|
-
? Math.round(Number(config.session.max_agent_silence_minutes) * 60 * 1000)
|
|
86
|
-
: undefined;
|
|
87
|
-
const plannerTimeoutMs = Number(config?.session?.max_planner_minutes) > 0
|
|
88
|
-
? Math.round(Number(config.session.max_planner_minutes) * 60 * 1000)
|
|
89
|
-
: undefined;
|
|
90
|
-
const silenceLabel = silenceTimeoutMs ? `${Math.round(silenceTimeoutMs / 1000)}s` : "disabled";
|
|
91
|
-
const runtimeLabel = plannerTimeoutMs ? `${Math.round(plannerTimeoutMs / 1000)}s` : "disabled";
|
|
92
|
-
runLog.logText(
|
|
93
|
-
`[kj_plan] started — provider=${plannerRole.provider}, max_silence=${silenceLabel}, max_runtime=${runtimeLabel}`
|
|
94
|
-
);
|
|
95
|
-
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
96
|
-
const eventBase = { sessionId: null, iteration: 0, startedAt: Date.now() };
|
|
97
|
-
|
|
98
|
-
// --- Phase 1: Researcher (codebase context) ---
|
|
99
|
-
let researchContext = null;
|
|
100
|
-
try {
|
|
101
|
-
const { ResearcherRole } = await import("../../roles/researcher-role.js");
|
|
102
|
-
const researcherRole = resolveRole(config, "researcher");
|
|
103
|
-
const researcherOnOutput = ({ stream, line }) => {
|
|
104
|
-
emitter.emit("progress", { type: "agent:output", stage: "researcher", message: line, detail: { stream, agent: researcherRole.provider } });
|
|
105
|
-
};
|
|
106
|
-
const researcherStall = createStallDetector({
|
|
107
|
-
onOutput: researcherOnOutput, emitter, eventBase, stage: "researcher", provider: researcherRole.provider
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
sendTrackerLog(server, "researcher", "running", researcherRole.provider);
|
|
111
|
-
runLog.logText(`[researcher] agent launched for plan context...`);
|
|
112
|
-
const researcher = new ResearcherRole({ config, logger, emitter });
|
|
113
|
-
await researcher.init({ task: a.task });
|
|
114
|
-
let researchOutput;
|
|
115
|
-
try {
|
|
116
|
-
researchOutput = await researcher.run({ task: a.task, onOutput: researcherStall.onOutput });
|
|
117
|
-
} finally {
|
|
118
|
-
researcherStall.stop();
|
|
119
|
-
}
|
|
120
|
-
if (researchOutput.ok) {
|
|
121
|
-
researchContext = researchOutput.result;
|
|
122
|
-
sendTrackerLog(server, "researcher", "done");
|
|
123
|
-
runLog.logText(`[researcher] completed — context available`);
|
|
124
|
-
} else {
|
|
125
|
-
sendTrackerLog(server, "researcher", "failed");
|
|
126
|
-
runLog.logText(`[researcher] failed: ${researchOutput.summary || "unknown"}`);
|
|
127
|
-
}
|
|
128
|
-
} catch (err) {
|
|
129
|
-
logger.warn(`Researcher phase skipped: ${err.message}`);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// --- Phase 2: Architect (solution design) ---
|
|
133
|
-
let architectContext = null;
|
|
134
|
-
try {
|
|
135
|
-
const { ArchitectRole } = await import("../../roles/architect-role.js");
|
|
136
|
-
const architectRole = resolveRole(config, "architect");
|
|
137
|
-
const architectOnOutput = ({ stream, line }) => {
|
|
138
|
-
emitter.emit("progress", { type: "agent:output", stage: "architect", message: line, detail: { stream, agent: architectRole.provider } });
|
|
139
|
-
};
|
|
140
|
-
const architectStall = createStallDetector({
|
|
141
|
-
onOutput: architectOnOutput, emitter, eventBase, stage: "architect", provider: architectRole.provider
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
sendTrackerLog(server, "architect", "running", architectRole.provider);
|
|
145
|
-
runLog.logText(`[architect] agent launched for plan context...`);
|
|
146
|
-
const architect = new ArchitectRole({ config, logger, emitter });
|
|
147
|
-
await architect.init({ task: a.task });
|
|
148
|
-
let architectOutput;
|
|
149
|
-
try {
|
|
150
|
-
architectOutput = await architect.run({ task: a.task, researchContext, onOutput: architectStall.onOutput });
|
|
151
|
-
} finally {
|
|
152
|
-
architectStall.stop();
|
|
153
|
-
}
|
|
154
|
-
if (architectOutput.ok) {
|
|
155
|
-
architectContext = architectOutput.result;
|
|
156
|
-
sendTrackerLog(server, "architect", "done");
|
|
157
|
-
runLog.logText(`[architect] completed — context available`);
|
|
158
|
-
} else {
|
|
159
|
-
sendTrackerLog(server, "architect", "failed");
|
|
160
|
-
runLog.logText(`[architect] failed: ${architectOutput.summary || "unknown"}`);
|
|
161
|
-
}
|
|
162
|
-
} catch (err) {
|
|
163
|
-
logger.warn(`Architect phase skipped: ${err.message}`);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
// --- Phase 3: Planner (with research + architecture context) ---
|
|
167
|
-
const onOutput = ({ stream, line }) => {
|
|
168
|
-
emitter.emit("progress", { type: "agent:output", stage: "planner", message: line, detail: { stream, agent: plannerRole.provider } });
|
|
169
|
-
};
|
|
170
|
-
const stallDetector = createStallDetector({
|
|
171
|
-
onOutput, emitter, eventBase, stage: "planner", provider: plannerRole.provider
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
const planner = createAgent(plannerRole.provider, config, logger);
|
|
175
|
-
const prompt = buildPlannerPrompt({ task: a.task, context: a.context, architectContext });
|
|
176
|
-
sendTrackerLog(server, "planner", "running", plannerRole.provider);
|
|
177
|
-
runLog.logText(`[planner] agent launched, waiting for response...`);
|
|
178
|
-
let result;
|
|
179
|
-
let plannerStats;
|
|
180
|
-
try {
|
|
181
|
-
result = await planner.runTask({
|
|
182
|
-
prompt,
|
|
183
|
-
role: "planner",
|
|
184
|
-
onOutput: stallDetector.onOutput,
|
|
185
|
-
silenceTimeoutMs,
|
|
186
|
-
timeoutMs: plannerTimeoutMs
|
|
187
|
-
});
|
|
188
|
-
} finally {
|
|
189
|
-
stallDetector.stop();
|
|
190
|
-
plannerStats = stallDetector.stats();
|
|
191
|
-
runLog.logText(
|
|
192
|
-
`[planner] finished — lines=${plannerStats.lineCount}, bytes=${plannerStats.bytesReceived}, elapsed=${Math.round(plannerStats.elapsedMs / 1000)}s`
|
|
193
|
-
);
|
|
194
|
-
runLog.close();
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
if (!result.ok) {
|
|
198
|
-
sendTrackerLog(server, "planner", "failed");
|
|
199
|
-
const baseError = result.error || result.output || "Planner failed";
|
|
200
|
-
const statsSuffix = plannerStats
|
|
201
|
-
? ` [lines=${plannerStats.lineCount}, bytes=${plannerStats.bytesReceived}, elapsed=${Math.round(plannerStats.elapsedMs / 1000)}s]`
|
|
202
|
-
: "";
|
|
203
|
-
throw new Error(`${baseError}${statsSuffix}`);
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
sendTrackerLog(server, "planner", "done");
|
|
207
|
-
const parsed = parseMaybeJsonString(result.output);
|
|
208
|
-
|
|
209
|
-
// --- Persist plan ---
|
|
210
|
-
let planId = null;
|
|
211
|
-
try {
|
|
212
|
-
const { savePlan } = await import("../../plan/plan-store.js");
|
|
213
|
-
planId = await savePlan(projectDir, {
|
|
214
|
-
task: a.task,
|
|
215
|
-
researchContext,
|
|
216
|
-
architectContext,
|
|
217
|
-
plan: parsed || result.output,
|
|
218
|
-
raw: result.output
|
|
219
|
-
});
|
|
220
|
-
runLog.logText(`[kj_plan] plan persisted — planId=${planId}`);
|
|
221
|
-
} catch (err) {
|
|
222
|
-
logger.warn(`Plan persistence failed (non-blocking): ${err.message}`);
|
|
223
|
-
}
|
|
224
|
-
|
|
225
|
-
return { ok: true, plan: parsed || result.output, raw: result.output, planId, researchContext, architectContext };
|
|
226
|
-
}
|
|
227
|
-
|
|
228
64
|
export async function handleCodeDirect(a, server, extra) {
|
|
229
65
|
const config = await buildConfig(a, "code");
|
|
230
66
|
await assertNotOnBaseBranch(config);
|
|
@@ -277,55 +113,6 @@ export async function handleCodeDirect(a, server, extra) {
|
|
|
277
113
|
return { ok: true, output: result.output, exitCode: result.exitCode };
|
|
278
114
|
}
|
|
279
115
|
|
|
280
|
-
export async function handleReviewDirect(a, server, extra) {
|
|
281
|
-
const config = await buildConfig(a, "review");
|
|
282
|
-
await assertNotOnBaseBranch(config);
|
|
283
|
-
const logger = createLogger(config.output.log_level, "mcp");
|
|
284
|
-
|
|
285
|
-
const reviewerRole = resolveRole(config, "reviewer");
|
|
286
|
-
await assertAgentsAvailable([reviewerRole.provider]);
|
|
287
|
-
|
|
288
|
-
const projectDir = await resolveProjectDir(server, a.projectDir);
|
|
289
|
-
await ensureTaskFromFile(a, projectDir);
|
|
290
|
-
const runLog = createRunLog(projectDir);
|
|
291
|
-
runLog.logText(`[kj_review] started — provider=${reviewerRole.provider}`);
|
|
292
|
-
const emitter = buildDirectEmitter(server, runLog, extra);
|
|
293
|
-
const eventBase = { sessionId: null, iteration: 0, startedAt: Date.now() };
|
|
294
|
-
const onOutput = ({ stream, line }) => {
|
|
295
|
-
emitter.emit("progress", { type: "agent:output", stage: "reviewer", message: line, detail: { stream, agent: reviewerRole.provider } });
|
|
296
|
-
};
|
|
297
|
-
const stallDetector = createStallDetector({
|
|
298
|
-
onOutput, emitter, eventBase, stage: "reviewer", provider: reviewerRole.provider
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
const reviewer = createAgent(reviewerRole.provider, config, logger);
|
|
302
|
-
const resolvedBase = await computeBaseRef({ baseBranch: config.base_branch, baseRef: a.baseRef });
|
|
303
|
-
const diff = await generateDiff({ baseRef: resolvedBase });
|
|
304
|
-
const { rules } = await resolveReviewProfile({ mode: config.review_mode, projectDir: process.cwd() });
|
|
305
|
-
|
|
306
|
-
const prompt = await buildReviewerPrompt({ task: a.task, diff, reviewRules: rules, mode: config.review_mode });
|
|
307
|
-
sendTrackerLog(server, "reviewer", "running", reviewerRole.provider);
|
|
308
|
-
runLog.logText(`[reviewer] agent launched, waiting for response...`);
|
|
309
|
-
let result;
|
|
310
|
-
try {
|
|
311
|
-
result = await reviewer.reviewTask({ prompt, role: "reviewer", onOutput: stallDetector.onOutput });
|
|
312
|
-
} finally {
|
|
313
|
-
stallDetector.stop();
|
|
314
|
-
const stats = stallDetector.stats();
|
|
315
|
-
runLog.logText(`[reviewer] finished — lines=${stats.lineCount}, bytes=${stats.bytesReceived}, elapsed=${Math.round(stats.elapsedMs / 1000)}s`);
|
|
316
|
-
runLog.close();
|
|
317
|
-
}
|
|
318
|
-
|
|
319
|
-
if (!result.ok) {
|
|
320
|
-
sendTrackerLog(server, "reviewer", "failed");
|
|
321
|
-
throw new Error(result.error || result.output || `Reviewer failed (exit ${result.exitCode})`);
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
sendTrackerLog(server, "reviewer", "done");
|
|
325
|
-
const parsed = parseMaybeJsonString(agentAnswerText(result.output));
|
|
326
|
-
return { ok: true, review: parsed || result.output, raw: result.output };
|
|
327
|
-
}
|
|
328
|
-
|
|
329
116
|
export async function handleDiscoverDirect(a, server, extra) {
|
|
330
117
|
// Build context from pgTask if provided
|
|
331
118
|
let context = a.context || null;
|
|
@@ -350,34 +137,6 @@ export async function handleDiscoverDirect(a, server, extra) {
|
|
|
350
137
|
});
|
|
351
138
|
}
|
|
352
139
|
|
|
353
|
-
export async function handleTriageDirect(a, server, extra) {
|
|
354
|
-
return runDirectRole({
|
|
355
|
-
roleName: "triage",
|
|
356
|
-
importRole: async () => {
|
|
357
|
-
const { TriageRole } = await import("../../roles/triage-role.js");
|
|
358
|
-
return { RoleClass: TriageRole };
|
|
359
|
-
},
|
|
360
|
-
initContext: { task: a.task },
|
|
361
|
-
runInput: { task: a.task },
|
|
362
|
-
args: a, server, extra,
|
|
363
|
-
resolveProjectDir, buildConfig, buildDirectEmitter
|
|
364
|
-
});
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
export async function handleResearcherDirect(a, server, extra) {
|
|
368
|
-
return runDirectRole({
|
|
369
|
-
roleName: "researcher",
|
|
370
|
-
importRole: async () => {
|
|
371
|
-
const { ResearcherRole } = await import("../../roles/researcher-role.js");
|
|
372
|
-
return { RoleClass: ResearcherRole };
|
|
373
|
-
},
|
|
374
|
-
initContext: { task: a.task },
|
|
375
|
-
runInput: { task: a.task },
|
|
376
|
-
args: a, server, extra,
|
|
377
|
-
resolveProjectDir, buildConfig, buildDirectEmitter
|
|
378
|
-
});
|
|
379
|
-
}
|
|
380
|
-
|
|
381
140
|
export async function handleAuditDirect(a, server, extra) {
|
|
382
141
|
const task = a.task || "Analyze the full codebase";
|
|
383
142
|
return runDirectRole({
|
|
@@ -394,20 +153,6 @@ export async function handleAuditDirect(a, server, extra) {
|
|
|
394
153
|
});
|
|
395
154
|
}
|
|
396
155
|
|
|
397
|
-
export async function handleArchitectDirect(a, server, extra) {
|
|
398
|
-
return runDirectRole({
|
|
399
|
-
roleName: "architect",
|
|
400
|
-
importRole: async () => {
|
|
401
|
-
const { ArchitectRole } = await import("../../roles/architect-role.js");
|
|
402
|
-
return { RoleClass: ArchitectRole };
|
|
403
|
-
},
|
|
404
|
-
initContext: { task: a.task },
|
|
405
|
-
runInput: { task: a.task, researchContext: a.context || null },
|
|
406
|
-
args: a, server, extra,
|
|
407
|
-
resolveProjectDir, buildConfig, buildDirectEmitter
|
|
408
|
-
});
|
|
409
|
-
}
|
|
410
|
-
|
|
411
156
|
export async function handleCode(a, server, extra) {
|
|
412
157
|
const missingTask = await requireTask(a, server);
|
|
413
158
|
if (missingTask) return missingTask;
|
|
@@ -422,20 +167,6 @@ export async function handleCode(a, server, extra) {
|
|
|
422
167
|
return handleCodeDirect(a, server, extra);
|
|
423
168
|
}
|
|
424
169
|
|
|
425
|
-
export async function handleReview(a, server, extra) {
|
|
426
|
-
const missingTask = await requireTask(a, server);
|
|
427
|
-
if (missingTask) return missingTask;
|
|
428
|
-
await runBootstrapGate(server, a);
|
|
429
|
-
return handleReviewDirect(a, server, extra);
|
|
430
|
-
}
|
|
431
|
-
|
|
432
|
-
export async function handlePlan(a, server, extra) {
|
|
433
|
-
const missingTask = await requireTask(a, server);
|
|
434
|
-
if (missingTask) return missingTask;
|
|
435
|
-
await runBootstrapGate(server, a);
|
|
436
|
-
return handlePlanDirect(a, server, extra);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
170
|
export async function handleDiscover(a, server, extra) {
|
|
440
171
|
const missingTask = await requireTask(a, server);
|
|
441
172
|
if (missingTask) return missingTask;
|
|
@@ -447,27 +178,6 @@ export async function handleDiscover(a, server, extra) {
|
|
|
447
178
|
return handleDiscoverDirect(a, server, extra);
|
|
448
179
|
}
|
|
449
180
|
|
|
450
|
-
export async function handleTriage(a, server, extra) {
|
|
451
|
-
const missingTask = await requireTask(a, server);
|
|
452
|
-
if (missingTask) return missingTask;
|
|
453
|
-
await runBootstrapGate(server, a);
|
|
454
|
-
return handleTriageDirect(a, server, extra);
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
export async function handleResearcher(a, server, extra) {
|
|
458
|
-
const missingTask = await requireTask(a, server);
|
|
459
|
-
if (missingTask) return missingTask;
|
|
460
|
-
await runBootstrapGate(server, a);
|
|
461
|
-
return handleResearcherDirect(a, server, extra);
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
export async function handleArchitect(a, server, extra) {
|
|
465
|
-
const missingTask = await requireTask(a, server);
|
|
466
|
-
if (missingTask) return missingTask;
|
|
467
|
-
await runBootstrapGate(server, a);
|
|
468
|
-
return handleArchitectDirect(a, server, extra);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
181
|
export async function handleAudit(a, server, extra) {
|
|
472
182
|
await runBootstrapGate(server, a);
|
|
473
183
|
const result = await handleAuditDirect(a, server, extra);
|