karajan-code 4.70.4 → 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/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 -143
- 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 -150
- package/src/commands/go.js +71 -53
- package/src/commands/harden.js +90 -81
- 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 +421 -341
- 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/secrets.js +36 -0
- package/src/config.js +1 -0
- package/src/environment/adr.js +16 -2
- package/src/git/karajan-label.js +20 -8
- package/src/guards/main-guard.js +54 -0
- package/src/harden/hook-templates.js +16 -1
- package/src/harden/sentinel/posttooluse.mjs +18 -6
- package/src/harden/sentinel/pretooluse-sentinel.mjs +64 -36
- package/src/harden/sentinel/sentinel-lib.mjs +129 -26
- package/src/harden/sentinel/sentinel-shell.mjs +5 -16
- package/src/harden/sentinel/sessionstart.mjs +3 -2
- package/src/harden/sentinel/stop.mjs +5 -4
- 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 +1 -35
- package/src/mcp/handlers/management-handlers.js +2 -42
- package/src/mcp/server-handlers.js +16 -22
- package/src/mcp/tools.js +1 -196
- 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/gate-gitignore.js +23 -4
- package/src/review/gate-hook.js +12 -4
- package/src/review/one-shot-review.js +45 -35
- package/src/review/publish-pr.js +14 -2
- package/src/review/repo-docs.js +73 -0
- package/src/review/repo-stack.js +59 -0
- package/src/review/reviewer-fallback.js +19 -4
- 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 +1 -24
- 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/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
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Extracted from `src/orchestrator/drivers/pre-loop.js` in TSK-0337
|
|
3
|
-
* (audit recommendation #6). Previously an internal helper inside
|
|
4
|
-
* pre-loop.js; moved here so the driver stays under 600 LOC.
|
|
5
|
-
* Behaviour unchanged.
|
|
6
|
-
*
|
|
7
|
-
* Auto-generates an HU batch from triage's decomposition recommendation
|
|
8
|
-
* when no manual `huFile` is present. Runs after researcher/architect/
|
|
9
|
-
* planner so context is available for better HU generation. Sets
|
|
10
|
-
* `stageResults.huReviewer` so `needsSubPipeline` picks it up later.
|
|
11
|
-
*
|
|
12
|
-
* Side effects:
|
|
13
|
-
* - Persists `batch.json` to `~/.karajan/hu-stories/<batchSessionId>/`
|
|
14
|
-
* so the sub-pipeline can update story status via `saveHuBatch`.
|
|
15
|
-
* - Auto-starts the HU board (skipped under VITEST / NODE_ENV=test).
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
import { emitProgress, makeEvent } from "#utils/events.js";
|
|
19
|
-
import { detectProjectStack } from "#utils/stack-detect.js";
|
|
20
|
-
|
|
21
|
-
export async function maybeGenerateAutoHuBatch({
|
|
22
|
-
flags, stageResults, task, logger, emitter, eventBase, projectDir, session,
|
|
23
|
-
}) {
|
|
24
|
-
// Skip if user passed a manual hu-file
|
|
25
|
-
if (flags?.huFile) return;
|
|
26
|
-
// Skip if hu-reviewer already produced a batch (manual enable + PG stories)
|
|
27
|
-
if (stageResults.huReviewer) return;
|
|
28
|
-
// Need triage decomposition recommendation
|
|
29
|
-
const shouldDecompose = stageResults.triage?.shouldDecompose;
|
|
30
|
-
const subtasks = stageResults.triage?.subtasks;
|
|
31
|
-
if (!shouldDecompose || !Array.isArray(subtasks) || subtasks.length < 2) return;
|
|
32
|
-
|
|
33
|
-
const { generateHuBatch } = await import("#hu/auto-generator.js");
|
|
34
|
-
|
|
35
|
-
// Detect if project is new: empty dir or only .git/.karajan/.gitignore
|
|
36
|
-
let isNewProject = false;
|
|
37
|
-
try {
|
|
38
|
-
const fs = await import("node:fs/promises");
|
|
39
|
-
const entries = await fs.readdir(projectDir);
|
|
40
|
-
const relevant = entries.filter(e => !e.startsWith(".git") && e !== ".karajan" && e !== ".gitignore");
|
|
41
|
-
isNewProject = relevant.length === 0;
|
|
42
|
-
} catch { /* ignore */ }
|
|
43
|
-
|
|
44
|
-
// Stack hints from planner + architect text. Now multi-ecosystem
|
|
45
|
-
// (was 13/16 JS keywords — Python/Go/Rust barely existed). Combined
|
|
46
|
-
// with `detectProjectStack(projectDir)` below to override the text
|
|
47
|
-
// heuristic with the actual filesystem reality when available.
|
|
48
|
-
const stackHints = [];
|
|
49
|
-
const combined = `${stageResults.planner?.plan || ""} ${stageResults.architect?.architecture ? JSON.stringify(stageResults.architect.architecture) : ""} ${task}`.toLowerCase();
|
|
50
|
-
const stackKeywords = [
|
|
51
|
-
"express", "vite", "vitest", "jest", "next", "astro", "react", "vue", "svelte", "nestjs", "monorepo", "workspaces",
|
|
52
|
-
"pytest", "flask", "fastapi", "django", "numpy", "pandas",
|
|
53
|
-
"gin", "fiber", "go",
|
|
54
|
-
"cargo", "rust",
|
|
55
|
-
"spring",
|
|
56
|
-
];
|
|
57
|
-
for (const kw of stackKeywords) {
|
|
58
|
-
if (combined.includes(kw)) stackHints.push(kw);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
// Filesystem trumps text. detectProjectStack inspects package.json /
|
|
62
|
-
// pyproject.toml / go.mod / Cargo.toml, so a pure-python repo gets
|
|
63
|
-
// `language: "python"` regardless of what the task text says.
|
|
64
|
-
let detectedLanguage = null;
|
|
65
|
-
try {
|
|
66
|
-
const fsStack = await detectProjectStack(projectDir);
|
|
67
|
-
if (fsStack?.language) detectedLanguage = fsStack.language;
|
|
68
|
-
} catch { /* best-effort */ }
|
|
69
|
-
|
|
70
|
-
const batch = generateHuBatch({
|
|
71
|
-
originalTask: task,
|
|
72
|
-
subtasks,
|
|
73
|
-
stackHints,
|
|
74
|
-
isNewProject,
|
|
75
|
-
language: detectedLanguage,
|
|
76
|
-
researcherContext: stageResults.researcher?.summary || null,
|
|
77
|
-
architectContext: stageResults.architect?.architecture ? JSON.stringify(stageResults.architect.architecture) : null
|
|
78
|
-
});
|
|
79
|
-
|
|
80
|
-
// Persist batch to HU store so hu-sub-pipeline can update story status via saveHuBatch.
|
|
81
|
-
// Use session.id as batchSessionId.
|
|
82
|
-
const batchSessionId = `auto-${session.id}`;
|
|
83
|
-
try {
|
|
84
|
-
const fs = await import("node:fs/promises");
|
|
85
|
-
const path = await import("node:path");
|
|
86
|
-
const { getKarajanHome } = await import("#utils/paths.js");
|
|
87
|
-
const huDir = path.join(getKarajanHome(), "hu-stories", batchSessionId);
|
|
88
|
-
await fs.mkdir(huDir, { recursive: true });
|
|
89
|
-
const persistBatch = {
|
|
90
|
-
session_id: batchSessionId,
|
|
91
|
-
project_id: batchSessionId,
|
|
92
|
-
project_name: batch.projectName,
|
|
93
|
-
created_at: new Date().toISOString(),
|
|
94
|
-
updated_at: new Date().toISOString(),
|
|
95
|
-
stories: batch.stories
|
|
96
|
-
};
|
|
97
|
-
await fs.writeFile(path.join(huDir, "batch.json"), JSON.stringify(persistBatch, null, 2));
|
|
98
|
-
} catch (err) {
|
|
99
|
-
logger.warn(`Auto-HU: failed to persist batch (${err.message}) — sub-pipeline will use in-memory fallback`);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// Wrap in format compatible with needsSubPipeline + runHuSubPipeline
|
|
103
|
-
stageResults.huReviewer = {
|
|
104
|
-
ok: true,
|
|
105
|
-
stories: batch.stories,
|
|
106
|
-
total: batch.total,
|
|
107
|
-
certified: batch.certified,
|
|
108
|
-
batchSessionId,
|
|
109
|
-
auto_generated: true,
|
|
110
|
-
source: batch.source
|
|
111
|
-
};
|
|
112
|
-
|
|
113
|
-
logger.info(`Auto-HU: generated ${batch.total} stories (${batch.source.triage_subtasks} subtasks${isNewProject ? ", new project" : ""}${stackHints.length ? `, stack: ${stackHints.join(",")}` : ""})`);
|
|
114
|
-
emitProgress(emitter, makeEvent("hu:auto-generated", { ...eventBase, stage: "hu-auto-gen" }, {
|
|
115
|
-
message: `Auto-generated ${batch.total} HU(s) from triage decomposition`,
|
|
116
|
-
detail: { total: batch.total, subtasks: batch.source.triage_subtasks, isNewProject, stackHints, projectName: batch.projectName }
|
|
117
|
-
}));
|
|
118
|
-
|
|
119
|
-
// Auto-start the board so the user can see the generated HUs.
|
|
120
|
-
// Always fires when auto-HU runs, independent of hu_board.auto_start flag.
|
|
121
|
-
// Never during vitest — would race the PID file and leave a detached
|
|
122
|
-
// node process around after the suite (TSK-0273).
|
|
123
|
-
if (process.env.VITEST || process.env.NODE_ENV === "test") return;
|
|
124
|
-
try {
|
|
125
|
-
const { startBoard, renderBoardBanner, boardSlug } = await import("../../../commands/board.js");
|
|
126
|
-
const desiredPort = session.config_snapshot?.hu_board?.port ?? 4000;
|
|
127
|
-
// KJC-TSK-0884: its project's scoped view, not the global dashboard.
|
|
128
|
-
const boardResult = await startBoard(desiredPort, { projectSlug: projectDir ? boardSlug(projectDir) : null });
|
|
129
|
-
const url = boardResult.url;
|
|
130
|
-
const status = boardResult.alreadyRunning ? "already running" : "started";
|
|
131
|
-
const projectName = batch.projectName || "Auto-generated HUs";
|
|
132
|
-
console.log(renderBoardBanner({ url, status, projectName }));
|
|
133
|
-
logger.info(`HU Board ${status} at ${url} (project: ${projectName})`);
|
|
134
|
-
} catch (err) {
|
|
135
|
-
logger.warn(`HU Board auto-start failed (non-blocking): ${err.message}`);
|
|
136
|
-
}
|
|
137
|
-
}
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Extracted from `src/orchestrator/drivers/pre-loop.js` in TSK-0337
|
|
3
|
-
* (audit recommendation #6: continue pre-loop extraction). pre-loop.js
|
|
4
|
-
* had grown to 626 LOC, over the 600-LOC ceiling that the file's own
|
|
5
|
-
* docstring set. Splitting the deprecation warnings, addyosmani skill
|
|
6
|
-
* bootstrap, and auto-HU batch generation into their own modules brings
|
|
7
|
-
* the driver back to ~440 LOC.
|
|
8
|
-
*
|
|
9
|
-
* Behaviour unchanged. The function is exported with the same name and
|
|
10
|
-
* signature so the move is mechanical.
|
|
11
|
-
*
|
|
12
|
-
* Emits one-shot warnings for config keys ignored since v2.7.4. Currently:
|
|
13
|
-
* - `sonarqube.enabled` (kj.config.yml) → ignored, sonar is intrinsic
|
|
14
|
-
* - `--no-sonar` CLI flag → ignored
|
|
15
|
-
*/
|
|
16
|
-
|
|
17
|
-
import { emitProgress, makeEvent } from "#utils/events.js";
|
|
18
|
-
|
|
19
|
-
export function emitConfigDeprecations(config, logger, emitter, eventBase) {
|
|
20
|
-
const dep = config?._deprecated;
|
|
21
|
-
if (!dep) return;
|
|
22
|
-
|
|
23
|
-
if (dep.sonarqubeEnabledKey) {
|
|
24
|
-
const m =
|
|
25
|
-
"DEPRECATED: `sonarqube.enabled` in kj.config.yml is ignored since v2.7.4. " +
|
|
26
|
-
"Sonar is intrinsic to Karajan for code tasks (sw/refactor/add-tests) and " +
|
|
27
|
-
"skipped for non-code tasks (audit/doc/infra/analysis/no-code) by policy. " +
|
|
28
|
-
"Remove the key from your config to silence this warning.";
|
|
29
|
-
logger.warn(m);
|
|
30
|
-
emitProgress(emitter, makeEvent("config:deprecated", { ...eventBase, stage: "config" }, {
|
|
31
|
-
message: m,
|
|
32
|
-
detail: { key: "sonarqube.enabled", since: "v2.7.4" },
|
|
33
|
-
}));
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
if (dep.noSonarFlag) {
|
|
37
|
-
const m =
|
|
38
|
-
"DEPRECATED: `--no-sonar` flag is ignored since v2.7.4. Sonar runs for code " +
|
|
39
|
-
"tasks by policy. To skip Sonar on a one-off run, use a non-code task type " +
|
|
40
|
-
"(e.g. `--task-type doc`) or rely on Solomon's runtime override.";
|
|
41
|
-
logger.warn(m);
|
|
42
|
-
emitProgress(emitter, makeEvent("config:deprecated", { ...eventBase, stage: "config" }, {
|
|
43
|
-
message: m,
|
|
44
|
-
detail: { flag: "--no-sonar", since: "v2.7.4" },
|
|
45
|
-
}));
|
|
46
|
-
}
|
|
47
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Extracted from `src/orchestrator/drivers/pre-loop.js` in TSK-0337
|
|
3
|
-
* (audit recommendation #6). Previously a 60-line export inside
|
|
4
|
-
* pre-loop.js; moved here to keep that driver under the 600-LOC
|
|
5
|
-
* ceiling its own docstring set. Behaviour and signature unchanged.
|
|
6
|
-
*
|
|
7
|
-
* Refreshes the addyosmani/agent-skills catalog (clones or pulls,
|
|
8
|
-
* gated by `refreshDays`) and resolves which slugs are relevant to
|
|
9
|
-
* the current task. Sets the result on the session via the
|
|
10
|
-
* `setAddyosmaniSkills` mutator so downstream stages can read it
|
|
11
|
-
* without re-running git.
|
|
12
|
-
*
|
|
13
|
-
* Failures are non-blocking: a missing git, a network blip, or an
|
|
14
|
-
* unparseable catalog all just mark the catalog unavailable and
|
|
15
|
-
* emit a `skills:addyosmani-unavailable` event. The pipeline keeps
|
|
16
|
-
* going — the addyosmani sources are an optional supplement.
|
|
17
|
-
*/
|
|
18
|
-
|
|
19
|
-
import {
|
|
20
|
-
refreshIfStale as refreshAddyosmaniCatalog,
|
|
21
|
-
listAvailableSlugs as listAddyosmaniSlugs,
|
|
22
|
-
} from "#skills/addyosmani-catalog.js";
|
|
23
|
-
import { resolveAddyosmaniSlugs } from "#skills/addyosmani-role-map.js";
|
|
24
|
-
import { setAddyosmaniSkills } from "#session/mutators.js";
|
|
25
|
-
import { emitProgress, makeEvent } from "#utils/events.js";
|
|
26
|
-
|
|
27
|
-
export async function ensureAddyosmaniSkills({ task, config, logger, session, emitter, eventBase }) {
|
|
28
|
-
const skillsConfig = config?.skills || {};
|
|
29
|
-
const sources = Array.isArray(skillsConfig.sources) ? skillsConfig.sources : ["addyosmani", "openskills", "local"];
|
|
30
|
-
const addyConfig = skillsConfig.addyosmani || {};
|
|
31
|
-
// Test harness override: config.testHarness.defaultAddyosmaniEnabled=false
|
|
32
|
-
// prevents orchestrator tests from spawning git. Tests that need the real
|
|
33
|
-
// catalog opt in by setting config.skills.addyosmani.enabled = true.
|
|
34
|
-
// Post-v2.7.5 no longer reads globalThis directly — config.testHarness is
|
|
35
|
-
// populated by the loader from the global or the production default.
|
|
36
|
-
const harnessDefault = config?.testHarness?.defaultAddyosmaniEnabled;
|
|
37
|
-
const enabledFromConfig = addyConfig.enabled === true
|
|
38
|
-
|| (addyConfig.enabled !== false && harnessDefault !== false);
|
|
39
|
-
const enabled = enabledFromConfig && sources.includes("addyosmani");
|
|
40
|
-
if (!enabled) return;
|
|
41
|
-
|
|
42
|
-
const refreshDays = Number.isFinite(addyConfig.refreshDays) ? addyConfig.refreshDays : 7;
|
|
43
|
-
const refreshMs = Math.max(0, refreshDays) * 24 * 60 * 60 * 1000;
|
|
44
|
-
|
|
45
|
-
try {
|
|
46
|
-
const refreshResult = await refreshAddyosmaniCatalog({
|
|
47
|
-
refreshMs,
|
|
48
|
-
repoUrl: addyConfig.repoUrl,
|
|
49
|
-
logger,
|
|
50
|
-
});
|
|
51
|
-
|
|
52
|
-
if (!refreshResult.ok) {
|
|
53
|
-
setAddyosmaniSkills(session, { available: false, reason: refreshResult.error || "refresh failed" });
|
|
54
|
-
emitProgress(emitter, makeEvent("skills:addyosmani-unavailable", { ...eventBase, stage: "skills" }, {
|
|
55
|
-
message: `addyosmani/agent-skills catalog unavailable: ${refreshResult.error || refreshResult.action}`,
|
|
56
|
-
detail: { action: refreshResult.action, hint: "Install git to enable process skills from addyosmani/agent-skills" },
|
|
57
|
-
}));
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const available = new Set(await listAddyosmaniSlugs());
|
|
62
|
-
const resolved = resolveAddyosmaniSlugs({ role: null, task }); // role resolution happens per-stage later
|
|
63
|
-
const valid = resolved.filter((slug) => available.has(slug));
|
|
64
|
-
|
|
65
|
-
setAddyosmaniSkills(session, {
|
|
66
|
-
available: true,
|
|
67
|
-
action: refreshResult.action,
|
|
68
|
-
resolvedSlugs: valid,
|
|
69
|
-
allAvailable: Array.from(available),
|
|
70
|
-
});
|
|
71
|
-
|
|
72
|
-
emitProgress(emitter, makeEvent("skills:addyosmani-ready", { ...eventBase, stage: "skills" }, {
|
|
73
|
-
message: `addyosmani/agent-skills ${refreshResult.action} — ${available.size} slug(s) available, ${valid.length} relevant to task`,
|
|
74
|
-
detail: {
|
|
75
|
-
action: refreshResult.action,
|
|
76
|
-
relevantSlugs: valid,
|
|
77
|
-
availableCount: available.size,
|
|
78
|
-
},
|
|
79
|
-
}));
|
|
80
|
-
} catch (err) {
|
|
81
|
-
logger.warn(`addyosmani catalog step failed (non-blocking): ${err.message}`);
|
|
82
|
-
setAddyosmaniSkills(session, { available: false, reason: err.message });
|
|
83
|
-
}
|
|
84
|
-
}
|
|
@@ -1,393 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pre-loop phase: load a persisted plan and inject it into stageResults.
|
|
3
|
-
*
|
|
4
|
-
* Extracted from `src/orchestrator/drivers/pre-loop.js` in PR-K (audit
|
|
5
|
-
* follow-up) to drag runPreLoopStages out of god-function territory
|
|
6
|
-
* (was 496 LOC; extracting this 189-LOC block brings it to ~310).
|
|
7
|
-
*
|
|
8
|
-
* Behaviour is byte-for-byte identical to the inlined version. The
|
|
9
|
-
* extraction keeps the dynamic-import pattern (deferred load of
|
|
10
|
-
* plan-store / plan-schema / plan-executor / plan-hu-ops) so the
|
|
11
|
-
* dynamic-imports budget test stays unchanged.
|
|
12
|
-
*
|
|
13
|
-
* Returns { handled, plannedTask }:
|
|
14
|
-
* - handled=true → caller short-circuits (plan was loaded; skip
|
|
15
|
-
* researcher/architect/planner phases below).
|
|
16
|
-
* - handled=false → caller continues with the regular planning flow.
|
|
17
|
-
* Either flags.plan was unset OR loadPlan returned null OR the
|
|
18
|
-
* try/catch swallowed an error (logged as a warning).
|
|
19
|
-
*
|
|
20
|
-
* The function still mutates session via the existing mutators
|
|
21
|
-
* (setPlanRef, setPlanSyncCallback, setLiveStatusUpdater,
|
|
22
|
-
* setLiveOutcomeUpdater, setPreLoopContext) and stageResults
|
|
23
|
-
* (researcher / architect / planner / huReviewer slots). That
|
|
24
|
-
* mutation contract is unchanged from the inlined version — see
|
|
25
|
-
* tests/architecture/session-write-boundary.test.js for the
|
|
26
|
-
* mutator-routing invariant.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import { saveSession } from "#session/store.js";
|
|
30
|
-
import {
|
|
31
|
-
setPlanRef, setPlanSyncCallback, setLiveStatusUpdater,
|
|
32
|
-
setLiveOutcomeUpdater, setPreLoopContext,
|
|
33
|
-
} from "#session/mutators.js";
|
|
34
|
-
import { emitProgress, makeEvent } from "#utils/events.js";
|
|
35
|
-
import { listActiveRuns } from "../../../utils/run-registry.js";
|
|
36
|
-
import { keepRoutingMetrics } from "../../../hu/routing-metrics.js";
|
|
37
|
-
|
|
38
|
-
/**
|
|
39
|
-
* @param {object} args
|
|
40
|
-
* @param {object} args.flags — runFlow flags (reads flags.plan, flags.hu)
|
|
41
|
-
* @param {object} args.updatedConfig — the running config (reads projectDir)
|
|
42
|
-
* @param {object} args.session
|
|
43
|
-
* @param {object} args.stageResults — mutated: researcher / architect / planner / huReviewer slots
|
|
44
|
-
* @param {object} args.eventBase
|
|
45
|
-
* @param {object} args.emitter
|
|
46
|
-
* @param {object} args.logger
|
|
47
|
-
* @param {string} args.task — original task description, used to build plannedTask
|
|
48
|
-
* @returns {Promise<{handled: boolean, plannedTask: string|null}>}
|
|
49
|
-
*/
|
|
50
|
-
export async function injectLoadedPlan({ flags, updatedConfig, session, stageResults, eventBase, emitter, logger, task }) {
|
|
51
|
-
if (!flags.plan) return { handled: false, plannedTask: null };
|
|
52
|
-
|
|
53
|
-
try {
|
|
54
|
-
const { loadPlan, savePlan: savePlanToDisk } = await import("../../../plan/plan-store.js");
|
|
55
|
-
const { isPlanV2 } = await import("../../../plan/plan-schema.js");
|
|
56
|
-
const { planToHuBatch, syncResultsToPlan } = await import("../../../plan/plan-executor.js");
|
|
57
|
-
const projectDir = updatedConfig.projectDir || process.cwd();
|
|
58
|
-
const loadedPlan = await loadPlan(projectDir, flags.plan);
|
|
59
|
-
if (!loadedPlan) {
|
|
60
|
-
logger.warn(`Plan ${flags.plan} not found — falling back to normal pipeline`);
|
|
61
|
-
return { handled: false, plannedTask: null };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
logger.info(`Loaded persisted plan: ${flags.plan} (v${loadedPlan.version || 1})`);
|
|
65
|
-
emitProgress(emitter, makeEvent("plan:loaded", { ...eventBase, stage: "plan" }, {
|
|
66
|
-
message: `Plan loaded: ${flags.plan}`,
|
|
67
|
-
detail: { planId: flags.plan, task: loadedPlan.task, version: loadedPlan.version, huCount: loadedPlan.hus?.length || 0 }
|
|
68
|
-
}));
|
|
69
|
-
|
|
70
|
-
// Zombie reconciliation: a previous run may have died mid-flight,
|
|
71
|
-
// leaving HUs stuck in coding/reviewing/running. The board-side
|
|
72
|
-
// reaper only runs inside the board; without this, a headless
|
|
73
|
-
// `kj run --plan` would refuse to pick those HUs up.
|
|
74
|
-
const reaped = await reconcilePlanHuZombies({ loadedPlan, planId: flags.plan, logger });
|
|
75
|
-
if (reaped > 0) {
|
|
76
|
-
await savePlanToDisk(projectDir, loadedPlan);
|
|
77
|
-
emitProgress(emitter, makeEvent("plan:zombies-reaped", { ...eventBase, stage: "plan" }, {
|
|
78
|
-
message: `Reset ${reaped} stale HU(s) from a previous run that did not finish`,
|
|
79
|
-
detail: { planId: flags.plan, count: reaped }
|
|
80
|
-
}));
|
|
81
|
-
}
|
|
82
|
-
stageResults.researcher = { ok: true, summary: "Loaded from persisted plan", fromPlan: flags.plan };
|
|
83
|
-
stageResults.architect = { ok: true, summary: "Loaded from persisted plan", fromPlan: flags.plan };
|
|
84
|
-
stageResults.planner = { ok: true, summary: "Loaded from persisted plan", fromPlan: flags.plan };
|
|
85
|
-
|
|
86
|
-
// Tests-first gate (v2.7.5): refuse to run a plan when any HU
|
|
87
|
-
// has no acceptance_tests. Mirrors the gate in `kj plan ready`
|
|
88
|
-
// so the CLI path and the board's "▶ Run plan" button enforce
|
|
89
|
-
// the same contract.
|
|
90
|
-
if (isPlanV2(loadedPlan) && Array.isArray(loadedPlan.hus)) {
|
|
91
|
-
assertEveryHuHasTests({ loadedPlan, planId: flags.plan, eventBase, emitter, logger });
|
|
92
|
-
|
|
93
|
-
// PR-E (auto-certify + anomaly guards): the user expects
|
|
94
|
-
// pending HUs to be runnable; "certified" was an invisible
|
|
95
|
-
// wall. Helpers in plan-hu-ops.js so they can be unit-tested
|
|
96
|
-
// without spinning up the orchestrator. Pulled from the same
|
|
97
|
-
// dynamic import we use a few lines below for updateHuStatus +
|
|
98
|
-
// setHuOutcome — no extra import budget.
|
|
99
|
-
const planHuOps = await import("../../../plan/plan-hu-ops.js");
|
|
100
|
-
await autoCertifyAndAssertRunnable({ planHuOps, loadedPlan, planId: flags.plan, projectDir, savePlanToDisk, eventBase, emitter, logger });
|
|
101
|
-
|
|
102
|
-
// v2 plan with HUs → inject as huReviewer so sub-pipeline
|
|
103
|
-
// picks them up. Sets every callback (sync, live status, live
|
|
104
|
-
// outcome) and stamps plan.status = "running".
|
|
105
|
-
if (loadedPlan.hus?.length > 0) {
|
|
106
|
-
const huBatch = planToHuBatch(loadedPlan);
|
|
107
|
-
applyHuFilterIfRequested({ huBatch, flags, eventBase, emitter, logger });
|
|
108
|
-
if (huBatch.ok) {
|
|
109
|
-
stageResults.huReviewer = huBatch;
|
|
110
|
-
wirePlanCallbacks({ session, loadedPlan, projectDir, syncResultsToPlan, savePlanToDisk, planHuOps, logger });
|
|
111
|
-
logger.info(`Plan ${flags.plan}: ${huBatch.certified} HUs ready for execution`);
|
|
112
|
-
emitProgress(emitter, makeEvent("plan:hus-loaded", { ...eventBase, stage: "plan" }, {
|
|
113
|
-
message: `${huBatch.certified} HUs loaded from plan`,
|
|
114
|
-
detail: { planId: flags.plan, total: huBatch.total, certified: huBatch.certified }
|
|
115
|
-
}));
|
|
116
|
-
loadedPlan.status = "running";
|
|
117
|
-
await savePlanToDisk(projectDir, loadedPlan);
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// Inject contexts that the legacy planning phases would have
|
|
123
|
-
// produced. Then build a plannedTask string for the non-HU /
|
|
124
|
-
// fallback iteration path.
|
|
125
|
-
const ctx = loadedPlan.context || {};
|
|
126
|
-
setPreLoopContext(session, {
|
|
127
|
-
research: ctx.researchContext || loadedPlan.researchContext || null,
|
|
128
|
-
architect: ctx.architectContext || loadedPlan.architectContext || null,
|
|
129
|
-
plan: loadedPlan.approach || loadedPlan.plan || null,
|
|
130
|
-
});
|
|
131
|
-
await saveSession(session);
|
|
132
|
-
|
|
133
|
-
return { handled: true, plannedTask: buildPlannedTaskFromLoadedPlan({ task, loadedPlan }) };
|
|
134
|
-
} catch (err) {
|
|
135
|
-
logger.warn(`Plan loading failed: ${err.message} — falling back to normal pipeline`);
|
|
136
|
-
return { handled: false, plannedTask: null };
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Reset HUs that look mid-execution but have no live run behind them.
|
|
142
|
-
*
|
|
143
|
-
* Resilience audit, Phase 3 (Hallazgo A1 / status-zombi). A previous
|
|
144
|
-
* `kj run --plan` flipped HUs to `coding`/`reviewing`/`running` in the
|
|
145
|
-
* plan JSON, then died (SIGKILL, terminal closed, OOM, crash). The
|
|
146
|
-
* board-side `hu-zombie-reaper` only queries SQLite and only runs
|
|
147
|
-
* inside the board process, so the plan JSON on disk kept stale
|
|
148
|
-
* in-flight statuses forever. The next `kj run --plan` then refused to
|
|
149
|
-
* pick those HUs up.
|
|
150
|
-
*
|
|
151
|
-
* Cross-check via `run-registry`: if there is a live run with our
|
|
152
|
-
* planId, do NOT touch — that run owns the in-flight HUs. Otherwise
|
|
153
|
-
* any in-flight status with no live owner is a zombie → reset to
|
|
154
|
-
* `pending` so it can run again.
|
|
155
|
-
*
|
|
156
|
-
* Mutates `loadedPlan.hus` in place. Returns the number of HUs reset
|
|
157
|
-
* so the caller can persist + emit a progress event.
|
|
158
|
-
*
|
|
159
|
-
* @param {object} args
|
|
160
|
-
* @param {object} args.loadedPlan
|
|
161
|
-
* @param {string} args.planId
|
|
162
|
-
* @param {object} [args.logger]
|
|
163
|
-
* @returns {number}
|
|
164
|
-
*/
|
|
165
|
-
export function reconcilePlanHuZombies({ loadedPlan, planId, logger }) {
|
|
166
|
-
if (!Array.isArray(loadedPlan?.hus) || loadedPlan.hus.length === 0) return 0;
|
|
167
|
-
const activeRuns = listActiveRuns({ planId });
|
|
168
|
-
if (activeRuns.length > 0) return 0;
|
|
169
|
-
const ZOMBIE_STATES = new Set(["coding", "reviewing", "running"]);
|
|
170
|
-
let reaped = 0;
|
|
171
|
-
for (const hu of loadedPlan.hus) {
|
|
172
|
-
if (ZOMBIE_STATES.has(hu.status)) {
|
|
173
|
-
const previous = hu.status;
|
|
174
|
-
hu.status = "pending";
|
|
175
|
-
reaped += 1;
|
|
176
|
-
logger?.warn?.(`[plan] HU ${hu.id || hu.hu_id || "?"} was '${previous}' but no live run owns the plan — reset to 'pending'.`);
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
return reaped;
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Throws when any HU lacks an acceptance_tests array with at least
|
|
184
|
-
* one entry. Logs + emits the same `plan:tests-missing` progress
|
|
185
|
-
* event as the inlined version did.
|
|
186
|
-
*/
|
|
187
|
-
function assertEveryHuHasTests({ loadedPlan, planId, eventBase, emitter, logger }) {
|
|
188
|
-
const missing = loadedPlan.hus.filter(
|
|
189
|
-
(h) => !Array.isArray(h.acceptance_tests) || h.acceptance_tests.length === 0
|
|
190
|
-
);
|
|
191
|
-
if (missing.length === 0) return;
|
|
192
|
-
const offenders = missing.map((h) => ` - ${h.id} ${(h.title || "").slice(0, 60)}`).join("\n");
|
|
193
|
-
const msg =
|
|
194
|
-
`Refusing to run plan ${planId}: ${missing.length}/${loadedPlan.hus.length} HU(s) have no acceptance_tests.\n`
|
|
195
|
-
+ `${offenders}\n\n`
|
|
196
|
-
+ "Tests-first flow requires a test contract per HU. Edit each HU on the board\n"
|
|
197
|
-
+ "(http://localhost:4000) via the ✎ Edit button, or re-run `kj plan` to regenerate.";
|
|
198
|
-
logger.error(msg);
|
|
199
|
-
emitProgress(emitter, makeEvent("plan:tests-missing", { ...eventBase, stage: "plan" }, {
|
|
200
|
-
status: "fail",
|
|
201
|
-
message: `Plan ${planId}: ${missing.length} HU(s) missing acceptance_tests`,
|
|
202
|
-
detail: { planId, missingHuIds: missing.map((h) => h.id) }
|
|
203
|
-
}));
|
|
204
|
-
throw new Error(`Plan ${planId} has ${missing.length} HU(s) with no acceptance_tests — see the run log for details.`);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
/**
|
|
208
|
-
* Promotes pending HUs with tests to certified, then asserts at
|
|
209
|
-
* least one HU is runnable. Throws (with `plan:no-runnable` event)
|
|
210
|
-
* when nothing is left to do, so the orchestrator never silently
|
|
211
|
-
* falls back to single-task mode and burns tokens.
|
|
212
|
-
*/
|
|
213
|
-
async function autoCertifyAndAssertRunnable({ planHuOps, loadedPlan, planId, projectDir, savePlanToDisk, eventBase, emitter, logger }) {
|
|
214
|
-
const { autoCertifyPendingHus, assertPlanRunnable } = planHuOps;
|
|
215
|
-
const promoted = autoCertifyPendingHus(loadedPlan);
|
|
216
|
-
if (promoted > 0) {
|
|
217
|
-
await savePlanToDisk(projectDir, loadedPlan);
|
|
218
|
-
logger.info(`Plan ${planId}: auto-certified ${promoted} pending HU(s) — ready to run`);
|
|
219
|
-
emitProgress(emitter, makeEvent("plan:auto-certified", { ...eventBase, stage: "plan" }, {
|
|
220
|
-
message: `Auto-certificadas ${promoted} HU(s) pendientes`,
|
|
221
|
-
detail: { planId, count: promoted }
|
|
222
|
-
}));
|
|
223
|
-
}
|
|
224
|
-
try {
|
|
225
|
-
assertPlanRunnable(loadedPlan, planId);
|
|
226
|
-
} catch (err) {
|
|
227
|
-
logger.error(err.message);
|
|
228
|
-
emitProgress(emitter, makeEvent("plan:no-runnable", { ...eventBase, stage: "plan" }, {
|
|
229
|
-
status: "fail", message: err.message,
|
|
230
|
-
detail: { planId }
|
|
231
|
-
}));
|
|
232
|
-
throw err;
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
/**
|
|
237
|
-
* PR4: when --hu <ids> is set, narrow the batch to those HU ids by
|
|
238
|
-
* marking the rest as `done` in memory. The plan JSON on disk is
|
|
239
|
-
* NOT touched — this is a run-time filter only. Throws when none of
|
|
240
|
-
* the requested ids exist.
|
|
241
|
-
*/
|
|
242
|
-
function applyHuFilterIfRequested({ huBatch, flags, eventBase, emitter, logger }) {
|
|
243
|
-
if (!huBatch.ok || !flags.hu) return;
|
|
244
|
-
const requested = String(flags.hu).split(",").map((s) => s.trim()).filter(Boolean);
|
|
245
|
-
if (requested.length === 0) {
|
|
246
|
-
logger.warn(`--hu was empty after parsing — falling back to running every certified HU`);
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
249
|
-
// Humanización IDs: resolver cada ref a un hu.id largo aceptando 3 formas:
|
|
250
|
-
// 1. `hu_<planId>_<NNN>` (id largo, exacto)
|
|
251
|
-
// 2. `INFRA-001` (short_id que el planner asignó)
|
|
252
|
-
// 3. `001` (sufijo numérico — busca un hu.id que termine en `_001`)
|
|
253
|
-
// El resolver es case-insensitive para short_id y sufijo.
|
|
254
|
-
const resolvedIds = new Set();
|
|
255
|
-
const unresolved = [];
|
|
256
|
-
for (const ref of requested) {
|
|
257
|
-
const id = resolveHuRef(ref, huBatch.stories);
|
|
258
|
-
if (id) resolvedIds.add(id);
|
|
259
|
-
else unresolved.push(ref);
|
|
260
|
-
}
|
|
261
|
-
const found = huBatch.stories.filter((s) => resolvedIds.has(s.id));
|
|
262
|
-
if (unresolved.length > 0) {
|
|
263
|
-
logger.warn(`--hu: ignored ${unresolved.length} unknown HU ref(s): ${unresolved.join(", ")}`);
|
|
264
|
-
}
|
|
265
|
-
if (found.length === 0) {
|
|
266
|
-
throw new Error(`--hu: none of the requested HU ref(s) exist in plan ${flags.plan}: ${requested.join(", ")}`);
|
|
267
|
-
}
|
|
268
|
-
for (const story of huBatch.stories) {
|
|
269
|
-
if (!resolvedIds.has(story.id)) story.status = "done";
|
|
270
|
-
}
|
|
271
|
-
huBatch.certified = found.length;
|
|
272
|
-
logger.info(`Plan ${flags.plan}: --hu filter active — ${found.length}/${huBatch.total} HU(s) will run (${found.map((s) => s.id).join(", ")})`);
|
|
273
|
-
emitProgress(emitter, makeEvent("plan:hu-filter", { ...eventBase, stage: "plan" }, {
|
|
274
|
-
message: `--hu filter: ${found.length} HU(s) selected, ${huBatch.total - found.length} skipped`,
|
|
275
|
-
detail: { planId: flags.plan, requested, ignored: unresolved, willRun: found.map((s) => s.id) }
|
|
276
|
-
}));
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
/**
|
|
280
|
-
* Wire the four session-scoped callbacks the run will invoke as it
|
|
281
|
-
* progresses: PR1 live status (per HU), PR3 live outcome (per HU
|
|
282
|
-
* exit), and the syncResultsToPlan rollup (end-of-run).
|
|
283
|
-
*/
|
|
284
|
-
function wirePlanCallbacks({ session, loadedPlan, projectDir, syncResultsToPlan, savePlanToDisk, planHuOps, logger }) {
|
|
285
|
-
setPlanRef(session, { planId: loadedPlan.planId, projectDir });
|
|
286
|
-
setPlanSyncCallback(session, async (subResult) => {
|
|
287
|
-
syncResultsToPlan(loadedPlan, subResult);
|
|
288
|
-
await savePlanToDisk(projectDir, loadedPlan);
|
|
289
|
-
});
|
|
290
|
-
// PR1: live HU status. Without this the plan only updates at
|
|
291
|
-
// end-of-run and the board's Kanban looks frozen during execution.
|
|
292
|
-
// (`planHuOps` was loaded above for PR-E auto-certify — reuse the
|
|
293
|
-
// reference to stay under the dynamic-import budget.)
|
|
294
|
-
const { updateHuStatus: updateHuStatusFn, setHuOutcome: setHuOutcomeFn } = planHuOps;
|
|
295
|
-
setLiveStatusUpdater(session, async (huId, status) => {
|
|
296
|
-
try {
|
|
297
|
-
if (!updateHuStatusFn(loadedPlan, huId, status)) return;
|
|
298
|
-
await savePlanToDisk(projectDir, loadedPlan);
|
|
299
|
-
} catch (err) {
|
|
300
|
-
logger?.warn?.(`Live status update failed for ${huId}: ${err.message}`);
|
|
301
|
-
}
|
|
302
|
-
});
|
|
303
|
-
// PR3: per-HU outcome. Stamped once per HU at runSingleHu's exit
|
|
304
|
-
// so the board can show the 📄 indicator as soon as each HU
|
|
305
|
-
// finishes (iterations / duration / commits / blockers / summary).
|
|
306
|
-
//
|
|
307
|
-
// Cost D (KJC-TSK-0515): the outcome may carry `cost_usd` (USD
|
|
308
|
-
// spent on this HU). When present and finite, also persist it to
|
|
309
|
-
// board.db so the per-card badge (Cost F) and the project total
|
|
310
|
-
// chip (Cost G) have data to show — without this write, the
|
|
311
|
-
// `cost_usd` column would always stay NULL and the UI would render
|
|
312
|
-
// nothing. Lazy-import to keep the pre-loop phase free of board
|
|
313
|
-
// deps until we actually need them.
|
|
314
|
-
setLiveOutcomeUpdater(session, async (huId, outcome) => {
|
|
315
|
-
try {
|
|
316
|
-
if (!setHuOutcomeFn(loadedPlan, huId, outcome)) return;
|
|
317
|
-
keepRoutingMetrics(loadedPlan, huId, outcome); // KJC-TSK-0702: the measured fields core does not keep
|
|
318
|
-
await savePlanToDisk(projectDir, loadedPlan);
|
|
319
|
-
if (outcome && Number.isFinite(outcome.cost_usd)) {
|
|
320
|
-
try {
|
|
321
|
-
const { ensureDb, setStoryCost, setStoryCachedTokens } = await import("../../../../packages/hu-board/src/db.js");
|
|
322
|
-
ensureDb(); // KJC-BUG-0090: autorun/run may not have started the board
|
|
323
|
-
setStoryCost(huId, outcome.cost_usd);
|
|
324
|
-
// Φ0-G (KJC-TSK-0525): cached/in tokens travel in the same
|
|
325
|
-
// outcome blob as cost. Write them together so the board
|
|
326
|
-
// can render the "🎯 N% cached" badge on the same render.
|
|
327
|
-
// Either may be null (older runs, no cache) — setter handles it.
|
|
328
|
-
if (Number.isFinite(outcome.cached_tokens) || Number.isFinite(outcome.tokens_in)) {
|
|
329
|
-
setStoryCachedTokens(
|
|
330
|
-
huId,
|
|
331
|
-
Number.isFinite(outcome.cached_tokens) ? outcome.cached_tokens : null,
|
|
332
|
-
Number.isFinite(outcome.tokens_in) ? outcome.tokens_in : null,
|
|
333
|
-
);
|
|
334
|
-
}
|
|
335
|
-
} catch (err) {
|
|
336
|
-
logger?.warn?.(`Cost write failed for ${huId}: ${err.message}`);
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
} catch (err) {
|
|
340
|
-
logger?.warn?.(`Live outcome update failed for ${huId}: ${err.message}`);
|
|
341
|
-
}
|
|
342
|
-
});
|
|
343
|
-
}
|
|
344
|
-
|
|
345
|
-
/**
|
|
346
|
-
* Resuelve una referencia humana a HU al `id` canónico largo. Acepta:
|
|
347
|
-
* - id largo exacto: `hu_<planId>_<NNN>` → match directo.
|
|
348
|
-
* - short_id: `INFRA-001`, `AUTH-SIGNUP` → match case-insensitive contra hu.short_id.
|
|
349
|
-
* - sufijo numérico: `001` → match contra hu.id terminado en `_001`.
|
|
350
|
-
* Devuelve el hu.id largo o null si no resuelve.
|
|
351
|
-
*
|
|
352
|
-
* @param {string} ref
|
|
353
|
-
* @param {Array<{id: string, short_id?: string|null}>} hus
|
|
354
|
-
* @returns {string|null}
|
|
355
|
-
*/
|
|
356
|
-
function resolveHuRef(ref, hus) {
|
|
357
|
-
if (!ref || !Array.isArray(hus)) return null;
|
|
358
|
-
// Match exacto contra id largo (camino caliente).
|
|
359
|
-
const exact = hus.find((s) => s.id === ref);
|
|
360
|
-
if (exact) return exact.id;
|
|
361
|
-
// Match case-insensitive contra short_id.
|
|
362
|
-
const lower = ref.toLowerCase();
|
|
363
|
-
const byShort = hus.find((s) => s.short_id && String(s.short_id).toLowerCase() === lower);
|
|
364
|
-
if (byShort) return byShort.id;
|
|
365
|
-
// Match por sufijo numérico (`001` → cualquier hu cuyo id termine en `_001`).
|
|
366
|
-
if (/^\d+$/.test(ref)) {
|
|
367
|
-
const padded = ref.padStart(3, "0");
|
|
368
|
-
const bySuffix = hus.find((s) => s.id.endsWith(`_${padded}`));
|
|
369
|
-
if (bySuffix) return bySuffix.id;
|
|
370
|
-
}
|
|
371
|
-
return null;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
/**
|
|
375
|
-
* Build a single plannedTask string for the non-HU / fallback
|
|
376
|
-
* iteration path. The HU sub-pipeline doesn't read this; only the
|
|
377
|
-
* legacy single-iteration loop does. Identical to the inlined
|
|
378
|
-
* version's behaviour.
|
|
379
|
-
*/
|
|
380
|
-
function buildPlannedTaskFromLoadedPlan({ task, loadedPlan }) {
|
|
381
|
-
const plan = loadedPlan.plan || (loadedPlan.approach ? { approach: loadedPlan.approach } : null);
|
|
382
|
-
if (plan && typeof plan === "object" && plan.steps) {
|
|
383
|
-
const stepList = plan.steps.map((s, idx) => `${idx + 1}. ${s.description || s}`).join("\n");
|
|
384
|
-
return `${task}\n\n## Implementation Plan\n${plan.approach || ""}\n\n## Steps\n${stepList}`;
|
|
385
|
-
}
|
|
386
|
-
if (typeof plan === "string") {
|
|
387
|
-
return `${task}\n\n## Implementation Plan\n${plan}`;
|
|
388
|
-
}
|
|
389
|
-
if (loadedPlan.approach) {
|
|
390
|
-
return `${task}\n\n## Approach\n${loadedPlan.approach}`;
|
|
391
|
-
}
|
|
392
|
-
return task;
|
|
393
|
-
}
|