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,487 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Preflight environment checks for kj_run.
|
|
3
|
-
*
|
|
4
|
-
* Runs AFTER policy resolution (so we know which stages are active)
|
|
5
|
-
* and BEFORE session iteration loop (so we fail fast).
|
|
6
|
-
*
|
|
7
|
-
* Design: SonarQube checks are BLOCKING when enabled — if SonarQube is
|
|
8
|
-
* configured but not available, the pipeline STOPS with a clear error.
|
|
9
|
-
* Security agent checks remain graceful (warning, auto-disable).
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { checkBinary } from "../utils/agent-detect.js";
|
|
13
|
-
import { isSonarReachable, sonarUp } from "../sonar/manager.js";
|
|
14
|
-
import { resolveSonarProjectKey, canResolveSonarProjectKey } from "../sonar/project-key.js";
|
|
15
|
-
import { runCommand } from "../utils/process.js";
|
|
16
|
-
import { emitProgress, makeEvent } from "../utils/events.js";
|
|
17
|
-
import { msg, getLang } from "../utils/messages.js";
|
|
18
|
-
import {
|
|
19
|
-
resolveSonarHost,
|
|
20
|
-
resolveSonarTokenAsync,
|
|
21
|
-
resolveSonarCredentials,
|
|
22
|
-
sonarTokenSources,
|
|
23
|
-
} from "../sonar/config-resolver.js";
|
|
24
|
-
import { saveSonarToken } from "../sonar/credentials.js";
|
|
25
|
-
import { withDocLink } from "../utils/doc-links.js";
|
|
26
|
-
import { runChecks as runCheckPipeline } from "../checks/runner.js";
|
|
27
|
-
import { STATUS } from "../checks/types.js";
|
|
28
|
-
import { getNodeChecks } from "../checks/node.js";
|
|
29
|
-
import { getDirSetupChecks } from "../checks/dir-setup.js";
|
|
30
|
-
import { getPortChecks } from "../checks/ports.js";
|
|
31
|
-
import { getTokenChecks } from "../checks/tokens.js";
|
|
32
|
-
import { getMcpHealthChecks } from "../checks/mcp-health.js";
|
|
33
|
-
import { getSkillsChecks } from "../checks/skills.js";
|
|
34
|
-
import { getProjectChecks } from "../checks/project-checks.js";
|
|
35
|
-
import { resolveTestHarness } from "../config/test-harness.js";
|
|
36
|
-
import { collectGuaranteeFacts } from "../checks/guarantee-level.js";
|
|
37
|
-
|
|
38
|
-
function parseJsonSafe(text) {
|
|
39
|
-
try {
|
|
40
|
-
return JSON.parse(text);
|
|
41
|
-
} catch { /* invalid JSON */
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async function checkDocker() {
|
|
47
|
-
const result = await checkBinary("docker");
|
|
48
|
-
return {
|
|
49
|
-
name: "docker",
|
|
50
|
-
ok: result.ok,
|
|
51
|
-
detail: result.ok ? `Docker ${result.version}` : "Docker not found",
|
|
52
|
-
};
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
const SONAR_STARTUP_POLL_MS = 5000;
|
|
56
|
-
const SONAR_STARTUP_MAX_WAIT_MS = 60000;
|
|
57
|
-
|
|
58
|
-
async function waitForSonar(host, maxWaitMs = SONAR_STARTUP_MAX_WAIT_MS) {
|
|
59
|
-
const deadline = Date.now() + maxWaitMs;
|
|
60
|
-
while (Date.now() < deadline) {
|
|
61
|
-
if (await isSonarReachable(host)) return true;
|
|
62
|
-
await new Promise(r => setTimeout(r, SONAR_STARTUP_POLL_MS));
|
|
63
|
-
}
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
async function checkSonarReachable(host) {
|
|
68
|
-
const reachable = await isSonarReachable(host);
|
|
69
|
-
if (reachable) {
|
|
70
|
-
return { name: "sonar-reachable", ok: true, detail: `SonarQube reachable at ${host}`, remediated: false };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// Auto-remediation: start SonarQube and wait for it to be ready
|
|
74
|
-
try {
|
|
75
|
-
const upResult = await sonarUp(host);
|
|
76
|
-
if (upResult.exitCode === 0) {
|
|
77
|
-
// SonarQube needs 20-40s to start. Poll until ready.
|
|
78
|
-
if (await waitForSonar(host)) {
|
|
79
|
-
return { name: "sonar-reachable", ok: true, detail: `SonarQube started and reachable at ${host}`, remediated: true };
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
} catch {
|
|
83
|
-
// sonarUp failed, fall through
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
return { name: "sonar-reachable", ok: false, detail: `SonarQube not reachable at ${host} (auto-start failed after ${SONAR_STARTUP_MAX_WAIT_MS / 1000}s)` };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
async function checkSonarAuth(config) {
|
|
90
|
-
const host = resolveSonarHost(config.sonarqube?.host);
|
|
91
|
-
|
|
92
|
-
// Check explicit token first (including persisted tokens from credentials file)
|
|
93
|
-
const explicitToken = await resolveSonarTokenAsync(config);
|
|
94
|
-
if (explicitToken) {
|
|
95
|
-
// Validate the token works
|
|
96
|
-
const res = await runCommand("curl", [
|
|
97
|
-
"-sS", "-o", "/dev/null", "-w", "%{http_code}",
|
|
98
|
-
"-H", `Authorization: Bearer ${explicitToken}`,
|
|
99
|
-
"--max-time", "5",
|
|
100
|
-
`${host}/api/authentication/validate`
|
|
101
|
-
]);
|
|
102
|
-
if (res.exitCode === 0 && res.stdout.trim().startsWith("2")) {
|
|
103
|
-
return { name: "sonar-auth", ok: true, detail: "Sonar token valid", token: explicitToken };
|
|
104
|
-
}
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
// Try admin credentials via centralized resolver
|
|
108
|
-
const { user: adminUser, passwords } = await resolveSonarCredentials(config);
|
|
109
|
-
|
|
110
|
-
if (!adminUser || passwords.length === 0) {
|
|
111
|
-
return { name: "sonar-auth", ok: false, detail: `No Sonar token or admin credentials found. Looked in: ${sonarTokenSources()}. With a custom KARAJAN_HOME/KJ_HOME, the token must be there (or pass kjHome).` };
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
for (const password of passwords) {
|
|
115
|
-
const validateRes = await runCommand("curl", [
|
|
116
|
-
"-sS", "-u", `${adminUser}:${password}`,
|
|
117
|
-
`${host}/api/authentication/validate`
|
|
118
|
-
]);
|
|
119
|
-
if (validateRes.exitCode !== 0) continue;
|
|
120
|
-
const parsed = parseJsonSafe(validateRes.stdout);
|
|
121
|
-
if (!parsed?.valid) continue;
|
|
122
|
-
|
|
123
|
-
// Generate a user token
|
|
124
|
-
const tokenName = `karajan-preflight-${Date.now()}`;
|
|
125
|
-
const tokenRes = await runCommand("curl", [
|
|
126
|
-
"-sS", "-u", `${adminUser}:${password}`,
|
|
127
|
-
"-X", "POST",
|
|
128
|
-
"--data-urlencode", `name=${tokenName}`,
|
|
129
|
-
`${host}/api/user_tokens/generate`
|
|
130
|
-
]);
|
|
131
|
-
if (tokenRes.exitCode !== 0) continue;
|
|
132
|
-
const tokenParsed = parseJsonSafe(tokenRes.stdout);
|
|
133
|
-
if (tokenParsed?.token) {
|
|
134
|
-
// Persist the generated token for future sessions
|
|
135
|
-
try { await saveSonarToken(tokenParsed.token); } catch { /* non-blocking */ }
|
|
136
|
-
return { name: "sonar-auth", ok: true, detail: "Sonar token generated and saved", token: tokenParsed.token };
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
return { name: "sonar-auth", ok: false, detail: "Could not validate or generate Sonar token" };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* KJC-BUG-0083 — run the real project-key resolver at preflight time so a
|
|
145
|
-
* repo Sonar cannot scan aborts BEFORE the first coder iteration instead
|
|
146
|
-
* of dying in sonar_repeat after burning tokens. Sonar stays mandatory
|
|
147
|
-
* for code tasks (v2.7.4 contract); this only moves the failure earlier.
|
|
148
|
-
*/
|
|
149
|
-
async function checkSonarProjectKey(config) {
|
|
150
|
-
// No remote at all (fresh local project): the SonarStage already skips
|
|
151
|
-
// cleanly via canResolveSonarProjectKey, so preflight must not block.
|
|
152
|
-
// The hard-fail case is the inconsistent one: a remote EXISTS but the
|
|
153
|
-
// resolver cannot parse it — that's what looped into sonar_repeat.
|
|
154
|
-
if (!(await canResolveSonarProjectKey(config))) {
|
|
155
|
-
return { name: "sonar-project-key", ok: true, detail: "No git remote — Sonar stage will skip cleanly" };
|
|
156
|
-
}
|
|
157
|
-
try {
|
|
158
|
-
const key = await resolveSonarProjectKey(config);
|
|
159
|
-
return { name: "sonar-project-key", ok: true, detail: `Project key resolved: ${key}` };
|
|
160
|
-
} catch (err) {
|
|
161
|
-
return { name: "sonar-project-key", ok: false, detail: err.message };
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
async function checkSecurityAgent(config) {
|
|
166
|
-
const provider = config.roles?.security?.provider
|
|
167
|
-
|| config.roles?.coder?.provider
|
|
168
|
-
|| config.coder
|
|
169
|
-
|| "claude";
|
|
170
|
-
|
|
171
|
-
const result = await checkBinary(provider);
|
|
172
|
-
return {
|
|
173
|
-
name: "security-agent",
|
|
174
|
-
ok: result.ok,
|
|
175
|
-
detail: result.ok ? `Security agent "${provider}" available (${result.version})` : `Security agent "${provider}" not found`,
|
|
176
|
-
provider,
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
/**
|
|
181
|
-
* Run preflight environment checks.
|
|
182
|
-
*
|
|
183
|
-
* SonarQube checks are BLOCKING: if SonarQube is enabled but not available,
|
|
184
|
-
* ok will be false and errors[] will contain actionable fix instructions.
|
|
185
|
-
* Security agent checks remain graceful (auto-disable via configOverrides).
|
|
186
|
-
*
|
|
187
|
-
* @param {object} opts
|
|
188
|
-
* @param {object} opts.config - Karajan config
|
|
189
|
-
* @param {object} opts.logger - Logger instance
|
|
190
|
-
* @param {object|null} opts.emitter - Event emitter
|
|
191
|
-
* @param {object} opts.eventBase - Base event data
|
|
192
|
-
* @param {object} opts.resolvedPolicies - Output from applyPolicies()
|
|
193
|
-
* @param {boolean} opts.securityEnabled - Whether security stage is enabled
|
|
194
|
-
* @returns {{ ok: boolean, checks: object[], remediations: string[], configOverrides: object, warnings: string[], errors: object[] }}
|
|
195
|
-
*/
|
|
196
|
-
export async function runPreflightChecks({ config, logger, emitter, eventBase, resolvedPolicies, securityEnabled }) {
|
|
197
|
-
// Defensive test-harness resolution for direct callers that bypass loadConfig()
|
|
198
|
-
// (notably tests/preflight-checks.test.js and tests/sonar-token-flow.test.js
|
|
199
|
-
// which construct raw configs). Production callers go through loadConfig
|
|
200
|
-
// which already populates config.testHarness once via resolveTestHarness().
|
|
201
|
-
// Idempotent — no-op when already present.
|
|
202
|
-
if (config && !config.testHarness) {
|
|
203
|
-
config.testHarness = resolveTestHarness(config.testHarness);
|
|
204
|
-
}
|
|
205
|
-
// Sonar is intrinsic to Karajan for code tasks (sw/refactor/add-tests).
|
|
206
|
-
// Since v2.7.4 it is NOT toggleable via `config.sonarqube.enabled` —
|
|
207
|
-
// that field is ignored (deprecation warning emitted at config load).
|
|
208
|
-
// The taskType policy (resolved_policies.sonar from DEFAULT_POLICIES in
|
|
209
|
-
// src/guards/policy-resolver.js) is the single source of truth: sw /
|
|
210
|
-
// refactor / add-tests run Sonar, audit / doc / infra / analysis /
|
|
211
|
-
// no-code skip it. Solomon may decide to skip a single iteration via
|
|
212
|
-
// rule alerts — that's a runtime decision, not a config option.
|
|
213
|
-
//
|
|
214
|
-
// Test-harness escape hatch via config.testHarness.disableSonarStage
|
|
215
|
-
// — production code reads `config?.testHarness?.disableSonarStage`,
|
|
216
|
-
// never `globalThis.*`. The legacy `globalThis.__KJ_DISABLE_SONAR_STAGE`
|
|
217
|
-
// shape is documented (and exclusively read) in
|
|
218
|
-
// src/config/test-harness.js, where the loader translates it into
|
|
219
|
-
// the typed config slot above. ESLint rule (#557) blocks any
|
|
220
|
-
// re-introduction outside that one file.
|
|
221
|
-
const sonarStageDisabledForTest = config?.testHarness?.disableSonarStage === true;
|
|
222
|
-
const sonarEnabled = !sonarStageDisabledForTest && resolvedPolicies.sonar !== false;
|
|
223
|
-
const isExternalSonar = Boolean(config.sonarqube?.external);
|
|
224
|
-
const sonarHost = resolveSonarHost(config.sonarqube?.host);
|
|
225
|
-
|
|
226
|
-
const result = {
|
|
227
|
-
ok: true,
|
|
228
|
-
checks: [],
|
|
229
|
-
remediations: [],
|
|
230
|
-
configOverrides: {},
|
|
231
|
-
warnings: [],
|
|
232
|
-
errors: [],
|
|
233
|
-
};
|
|
234
|
-
|
|
235
|
-
// KJC-BUG-0252 (#1899): on a Claude host without the Sentinel harness nothing
|
|
236
|
-
// governs the host's own actions (issues by hand, commits, pushes, merges);
|
|
237
|
-
// a run reported Tier A OFF and the agent routed around every gate. kj never
|
|
238
|
-
// fakes that supervision: the run stops here, with the remedy.
|
|
239
|
-
const guarantee = collectGuaranteeFacts({ projectDir: config.projectDir || process.cwd() });
|
|
240
|
-
if (guarantee.host === "claude" && !guarantee.tierA.active) {
|
|
241
|
-
result.ok = false;
|
|
242
|
-
result.checks.push({ name: "sentinel-harness", ok: false, detail: guarantee.tierA.reason });
|
|
243
|
-
result.errors.push({
|
|
244
|
-
check: "sentinel-harness",
|
|
245
|
-
message: "Tier A tool-time is OFF: a Claude host without the Sentinel harness, so nothing governs the host's own actions outside this run.",
|
|
246
|
-
fix: "Run kj harden (kj init installs it) and seal it with kj harden --commit; then run again.",
|
|
247
|
-
});
|
|
248
|
-
logger.error("Preflight: Sentinel harness not installed on a Claude host — run kj harden");
|
|
249
|
-
emitProgress(emitter, makeEvent("preflight:end", { ...eventBase, stage: "preflight" }, {
|
|
250
|
-
status: "fail", message: "Preflight FAILED — Sentinel harness not installed", detail: result
|
|
251
|
-
}));
|
|
252
|
-
return result;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
// Resolve extended preflight opt-in early so the short-circuit below can see it.
|
|
256
|
-
// Post-v2.7.5 this no longer reads globalThis directly — config.testHarness
|
|
257
|
-
// is populated by the loader from the global or the default.
|
|
258
|
-
const extendedDefault = config?.testHarness?.defaultPreflightExtended ?? true;
|
|
259
|
-
const extendedEnabled = config?.preflight?.extended ?? extendedDefault;
|
|
260
|
-
|
|
261
|
-
// Preserve legacy short-circuit: if nothing at all needs checking, skip
|
|
262
|
-
// emitting the whole preflight bracket.
|
|
263
|
-
if (!sonarEnabled && !securityEnabled && !extendedEnabled) {
|
|
264
|
-
logger.info("Preflight: skipped (no sonar, no security, extended disabled)");
|
|
265
|
-
emitProgress(emitter, makeEvent("preflight:end", { ...eventBase, stage: "preflight" }, {
|
|
266
|
-
message: "Preflight skipped (no checks needed)",
|
|
267
|
-
detail: { ...result, executorType: "local" }
|
|
268
|
-
}));
|
|
269
|
-
return result;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
emitProgress(emitter, makeEvent("preflight:start", { ...eventBase, stage: "preflight" }, {
|
|
273
|
-
message: "Running preflight environment checks",
|
|
274
|
-
detail: { sonarEnabled, securityEnabled, extendedEnabled, executorType: "local" }
|
|
275
|
-
}));
|
|
276
|
-
|
|
277
|
-
// --- 1. Docker (only if sonar enabled and not external) ---
|
|
278
|
-
if (sonarEnabled && !isExternalSonar) {
|
|
279
|
-
const dockerCheck = await checkDocker();
|
|
280
|
-
result.checks.push(dockerCheck);
|
|
281
|
-
|
|
282
|
-
emitProgress(emitter, makeEvent("preflight:check", { ...eventBase, stage: "preflight" }, {
|
|
283
|
-
status: dockerCheck.ok ? "ok" : "fail",
|
|
284
|
-
message: `Docker: ${dockerCheck.detail}`,
|
|
285
|
-
detail: dockerCheck
|
|
286
|
-
}));
|
|
287
|
-
|
|
288
|
-
if (!dockerCheck.ok) {
|
|
289
|
-
result.ok = false;
|
|
290
|
-
result.errors.push({
|
|
291
|
-
check: "docker",
|
|
292
|
-
message: "Docker not available but SonarQube is enabled.",
|
|
293
|
-
fix: withDocLink("Start Docker, or disable SonarQube: set sonarqube.enabled: false in kj.config.yml, or pass --no-sonar.", "sonar_docker")
|
|
294
|
-
});
|
|
295
|
-
logger.error("Preflight: Docker not found — SonarQube requires Docker");
|
|
296
|
-
|
|
297
|
-
// Skip remaining sonar checks, continue to security
|
|
298
|
-
if (!securityEnabled) {
|
|
299
|
-
emitProgress(emitter, makeEvent("preflight:end", { ...eventBase, stage: "preflight" }, {
|
|
300
|
-
status: "fail", message: "Preflight FAILED — environment not ready", detail: result
|
|
301
|
-
}));
|
|
302
|
-
return result;
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
// --- 2. SonarQube reachable ---
|
|
308
|
-
if (sonarEnabled && result.ok) {
|
|
309
|
-
const reachableCheck = await checkSonarReachable(sonarHost);
|
|
310
|
-
result.checks.push(reachableCheck);
|
|
311
|
-
|
|
312
|
-
if (reachableCheck.remediated) {
|
|
313
|
-
result.remediations.push("SonarQube auto-started via docker compose");
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
emitProgress(emitter, makeEvent("preflight:check", { ...eventBase, stage: "preflight" }, {
|
|
317
|
-
status: reachableCheck.ok ? "ok" : "fail",
|
|
318
|
-
message: `SonarQube reachability: ${reachableCheck.detail}`,
|
|
319
|
-
detail: reachableCheck
|
|
320
|
-
}));
|
|
321
|
-
|
|
322
|
-
if (!reachableCheck.ok) {
|
|
323
|
-
result.ok = false;
|
|
324
|
-
result.errors.push({
|
|
325
|
-
check: "sonar-reachable",
|
|
326
|
-
message: `SonarQube not reachable at ${sonarHost}.`,
|
|
327
|
-
fix: withDocLink("Start SonarQube: 'docker start karajan-sonarqube', or disable it: set sonarqube.enabled: false in kj.config.yml, or pass --no-sonar.", "sonar_docker")
|
|
328
|
-
});
|
|
329
|
-
logger.error("Preflight: SonarQube not reachable after remediation attempt");
|
|
330
|
-
}
|
|
331
|
-
}
|
|
332
|
-
|
|
333
|
-
// --- 3. SonarQube auth/token ---
|
|
334
|
-
if (sonarEnabled && result.ok) {
|
|
335
|
-
const authCheck = await checkSonarAuth(config);
|
|
336
|
-
result.checks.push(authCheck);
|
|
337
|
-
|
|
338
|
-
emitProgress(emitter, makeEvent("preflight:check", { ...eventBase, stage: "preflight" }, {
|
|
339
|
-
status: authCheck.ok ? "ok" : "fail",
|
|
340
|
-
message: `SonarQube auth: ${authCheck.detail}`,
|
|
341
|
-
detail: { name: authCheck.name, ok: authCheck.ok, detail: authCheck.detail }
|
|
342
|
-
}));
|
|
343
|
-
|
|
344
|
-
if (authCheck.ok && authCheck.token) {
|
|
345
|
-
process.env.KJ_SONAR_TOKEN = authCheck.token;
|
|
346
|
-
result.remediations.push("Sonar token resolved and cached in KJ_SONAR_TOKEN");
|
|
347
|
-
logger.info("Preflight: Sonar token resolved and cached");
|
|
348
|
-
} else if (!authCheck.ok) {
|
|
349
|
-
result.ok = false;
|
|
350
|
-
result.errors.push({
|
|
351
|
-
check: "sonar-auth",
|
|
352
|
-
message: `SonarQube is running but no authentication token was found. Looked in: ${sonarTokenSources()}.`,
|
|
353
|
-
fix: withDocLink("Fix: run 'kj init' to configure it, or set KJ_SONAR_TOKEN env var, or add sonarqube.token to the kj.config.yml of the active Karajan home.", "sonar_token")
|
|
354
|
-
});
|
|
355
|
-
logger.error("Preflight: Sonar auth failed");
|
|
356
|
-
}
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
// --- 3b. Sonar project key derivable (KJC-BUG-0083) ---
|
|
360
|
-
// The scanner derives its project key from git remote.origin.url. With
|
|
361
|
-
// an unparseable remote (local-path bare, exotic URL) and no explicit
|
|
362
|
-
// sonarqube.project_key, every iteration's scan threw, the run burned
|
|
363
|
-
// coder tokens and died in sonar_repeat. Run the REAL resolver here —
|
|
364
|
-
// canResolveSonarProjectKey() is weaker (any non-empty remote passes) —
|
|
365
|
-
// so the run fails fast with the actionable message before iteration 1.
|
|
366
|
-
if (sonarEnabled && result.ok) {
|
|
367
|
-
const keyCheck = await checkSonarProjectKey(config);
|
|
368
|
-
result.checks.push(keyCheck);
|
|
369
|
-
|
|
370
|
-
emitProgress(emitter, makeEvent("preflight:check", { ...eventBase, stage: "preflight" }, {
|
|
371
|
-
status: keyCheck.ok ? "ok" : "fail",
|
|
372
|
-
message: `Sonar project key: ${keyCheck.detail}`,
|
|
373
|
-
detail: keyCheck
|
|
374
|
-
}));
|
|
375
|
-
|
|
376
|
-
if (!keyCheck.ok) {
|
|
377
|
-
result.ok = false;
|
|
378
|
-
result.errors.push({
|
|
379
|
-
check: "sonar-project-key",
|
|
380
|
-
message: "Sonar cannot scan this repository: no project key can be derived.",
|
|
381
|
-
fix: withDocLink("Set sonarqube.project_key in kj.config.yml, or point remote.origin.url at a valid SSH/HTTPS remote.", "sonar_docker")
|
|
382
|
-
});
|
|
383
|
-
logger.error("Preflight: Sonar project key not derivable — failing fast before burning iterations");
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
// --- 4. Security agent (graceful — only warning, not blocking) ---
|
|
388
|
-
if (securityEnabled) {
|
|
389
|
-
const secCheck = await checkSecurityAgent(config);
|
|
390
|
-
result.checks.push(secCheck);
|
|
391
|
-
|
|
392
|
-
emitProgress(emitter, makeEvent("preflight:check", { ...eventBase, stage: "preflight" }, {
|
|
393
|
-
status: secCheck.ok ? "ok" : "warn",
|
|
394
|
-
message: `Security agent: ${secCheck.detail}`,
|
|
395
|
-
detail: secCheck
|
|
396
|
-
}));
|
|
397
|
-
|
|
398
|
-
if (!secCheck.ok) {
|
|
399
|
-
result.configOverrides.securityDisabled = true;
|
|
400
|
-
result.warnings.push(`Security agent "${secCheck.provider}" not found — security stage auto-disabled`);
|
|
401
|
-
logger.warn(`Preflight: Security agent "${secCheck.provider}" not found, disabling security stage`);
|
|
402
|
-
}
|
|
403
|
-
}
|
|
404
|
-
|
|
405
|
-
// --- 5. Complementary doctor-style checks (Node version, ports, tokens, MCP
|
|
406
|
-
// health, ~/.karajan dirs, openskills). These are auto-remediated
|
|
407
|
-
// with yes:true because preflight runs non-interactively inside kj run.
|
|
408
|
-
// Opt-out via `config.preflight.extended: false`. Defaults to true
|
|
409
|
-
// in production; the test harness (tests/setup.js) defaults it off.
|
|
410
|
-
if (extendedEnabled) {
|
|
411
|
-
await runExtendedPreflight({ config, result, emitter, eventBase, logger });
|
|
412
|
-
}
|
|
413
|
-
|
|
414
|
-
const hasErrors = result.errors.length > 0;
|
|
415
|
-
const hasWarnings = result.warnings.length > 0;
|
|
416
|
-
const preflightLang = getLang(config);
|
|
417
|
-
emitProgress(emitter, makeEvent("preflight:end", { ...eventBase, stage: "preflight" }, {
|
|
418
|
-
status: hasErrors ? "fail" : hasWarnings ? "warn" : "ok",
|
|
419
|
-
message: hasErrors
|
|
420
|
-
? `Preflight FAILED — ${result.errors.length} blocking issue(s)`
|
|
421
|
-
: hasWarnings
|
|
422
|
-
? `Preflight completed with ${result.warnings.length} warning(s)`
|
|
423
|
-
: msg("preflight_passed", preflightLang),
|
|
424
|
-
detail: { ...result, executorType: "local" }
|
|
425
|
-
}));
|
|
426
|
-
|
|
427
|
-
return result;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
/**
|
|
431
|
-
* Run the doctor-style complementary checks and merge their outcomes into
|
|
432
|
-
* the existing preflight result shape. Runs with yes:true (non-interactive)
|
|
433
|
-
* and auto-remediation enabled. FAIL is appended to errors (blocking), WARN and
|
|
434
|
-
* TIMEOUT to warnings (non-blocking: a timeout is "not checked"), FIXED status is recorded as a
|
|
435
|
-
* remediation, and runtime overrides from auto-fixes are merged into
|
|
436
|
-
* configOverrides.
|
|
437
|
-
*/
|
|
438
|
-
async function runExtendedPreflight({ config, result, emitter, eventBase, logger }) {
|
|
439
|
-
const checks = [
|
|
440
|
-
...getNodeChecks(),
|
|
441
|
-
...getDirSetupChecks(),
|
|
442
|
-
...getPortChecks(),
|
|
443
|
-
...getTokenChecks(config),
|
|
444
|
-
...getMcpHealthChecks(),
|
|
445
|
-
...getSkillsChecks(),
|
|
446
|
-
// KJC-TSK-0393: project-aware checks (signal detection, write perms,
|
|
447
|
-
// tools per signal, .env consistency, gh remote access). Saltables con
|
|
448
|
-
// flag --no-project-checks en kj run.
|
|
449
|
-
...(config?.flags?.noProjectChecks ? [] : getProjectChecks({ projectDir: config?.projectDir || process.cwd() })),
|
|
450
|
-
];
|
|
451
|
-
let report;
|
|
452
|
-
try {
|
|
453
|
-
report = await runCheckPipeline(checks, { config }, { mode: "fix", yes: true, timeoutMs: 3000, logger });
|
|
454
|
-
} catch (err) {
|
|
455
|
-
logger.warn(`Preflight: extended checks failed to run (${err.message})`);
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
for (const c of report.checks) {
|
|
460
|
-
result.checks.push({ name: c.name, ok: c.status === STATUS.OK || c.status === STATUS.FIXED || c.status === STATUS.SKIPPED, detail: c.detail });
|
|
461
|
-
// KJC-BUG-0287 (#1982): a timeout is "not checked", never "broken". GitHub
|
|
462
|
-
// unreachable for the SHA-pinned actions blocked a docs-only run that needed
|
|
463
|
-
// no network at all; the runner itself calls TIMEOUT warn-level.
|
|
464
|
-
let eventStatus = "ok";
|
|
465
|
-
if (c.status === STATUS.FAIL) eventStatus = "fail";
|
|
466
|
-
else if (c.status === STATUS.WARN || c.status === STATUS.TIMEOUT) eventStatus = "warn";
|
|
467
|
-
emitProgress(emitter, makeEvent("preflight:check", { ...eventBase, stage: "preflight" }, {
|
|
468
|
-
status: eventStatus,
|
|
469
|
-
message: `${c.label}: ${c.detail}`,
|
|
470
|
-
detail: { name: c.name, status: c.status, detail: c.detail, fix: c.fix }
|
|
471
|
-
}));
|
|
472
|
-
if (c.status === STATUS.FIXED) {
|
|
473
|
-
result.remediations.push(`${c.label}: ${c.detail}`);
|
|
474
|
-
} else if (c.status === STATUS.FAIL) {
|
|
475
|
-
result.ok = false;
|
|
476
|
-
result.errors.push({ check: c.name, message: `${c.label}: ${c.detail}`, fix: c.fix });
|
|
477
|
-
} else if (c.status === STATUS.TIMEOUT) {
|
|
478
|
-
result.warnings.push(`${c.label}: not checked, it did not answer in time (${c.detail}); the run goes on`);
|
|
479
|
-
} else if (c.status === STATUS.WARN) {
|
|
480
|
-
result.warnings.push(`${c.label}: ${c.detail}`);
|
|
481
|
-
}
|
|
482
|
-
}
|
|
483
|
-
|
|
484
|
-
if (report.overrides && Object.keys(report.overrides).length > 0) {
|
|
485
|
-
Object.assign(result.configOverrides, report.overrides);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Heuristics for classifying an acceptance-test failure as "possibly
|
|
3
|
-
* infeasible" — i.e. the test itself looks broken, not the code.
|
|
4
|
-
*
|
|
5
|
-
* Used by the HU sub-pipeline to decide when to invoke the Repairer
|
|
6
|
-
* role instead of feeding the failure back to the coder for ANOTHER
|
|
7
|
-
* iteration. Each iteration costs real money + time; classifying
|
|
8
|
-
* misroutes is the difference between a 3-minute repair and a
|
|
9
|
-
* 30-minute budget burn.
|
|
10
|
-
*
|
|
11
|
-
* The classifier is deliberately CONSERVATIVE: false negatives
|
|
12
|
-
* (treating a broken test as just-failed) cost an extra iteration;
|
|
13
|
-
* false positives (treating a real failure as infeasible) waste a
|
|
14
|
-
* Repairer call AND give the coder a spec change it shouldn't
|
|
15
|
-
* receive. We err toward false negatives.
|
|
16
|
-
*/
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Signatures that strongly suggest the TEST is broken, not the code.
|
|
20
|
-
* Each is a regex applied to the failing command's stderr+stdout.
|
|
21
|
-
*/
|
|
22
|
-
const INFEASIBLE_SIGNATURES = [
|
|
23
|
-
// jq: `length as $n | .field` after a transform — the binding doesn't
|
|
24
|
-
// change the value flowing through, so `.field` is applied to the wrong
|
|
25
|
-
// thing. Real-world example from 2026-04-29: "Cannot index array with
|
|
26
|
-
// string \"filesOver300LOC\"".
|
|
27
|
-
{ kind: "jq-as-binding-misuse", re: /Cannot index array with string/i },
|
|
28
|
-
// bash -n / shell parse errors before the command even runs.
|
|
29
|
-
{ kind: "shell-parse-error", re: /syntax error|unexpected token|unexpected EOF/i },
|
|
30
|
-
// jq: invalid query syntax detected by jq itself.
|
|
31
|
-
{ kind: "jq-syntax", re: /jq: error \(at .*\): (?!Cannot index)/i },
|
|
32
|
-
// command not found = test references a tool that doesn't exist in
|
|
33
|
-
// this environment. Could be a fixable typo or a real env mismatch.
|
|
34
|
-
{ kind: "missing-tool", re: /command not found|: not found$|No such file or directory.*\.(sh|mjs|js|py)/im },
|
|
35
|
-
// jq fails to parse the JSON input — the input file is wrong, the
|
|
36
|
-
// path is wrong, or the assumed schema is off.
|
|
37
|
-
{ kind: "jq-parse-input", re: /jq: error: Could not (open|parse)/i },
|
|
38
|
-
];
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Compute a stable signature for a single failing test outcome — the
|
|
42
|
-
* test command + a normalized error fingerprint. Used to detect when
|
|
43
|
-
* the SAME test is failing the SAME way across iterations; that's the
|
|
44
|
-
* pattern that argues for a Repairer call (vs. a coder retry).
|
|
45
|
-
*
|
|
46
|
-
* @param {string} cmd
|
|
47
|
-
* @param {string} errorOutput
|
|
48
|
-
* @returns {string}
|
|
49
|
-
*/
|
|
50
|
-
export function failureSignature(cmd, errorOutput) {
|
|
51
|
-
const cmdNorm = String(cmd || "").trim().slice(0, 200);
|
|
52
|
-
const errNorm = String(errorOutput || "")
|
|
53
|
-
.replaceAll(/\s+/g, " ") // collapse whitespace
|
|
54
|
-
.replaceAll(/\d+/g, "N") // numbers tend to drift; line numbers, counts
|
|
55
|
-
.replaceAll(/\/[^\s]+/g, "/PATH") // absolute paths drift across runs
|
|
56
|
-
.trim()
|
|
57
|
-
.slice(0, 300);
|
|
58
|
-
return `${cmdNorm}|||${errNorm}`;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
/**
|
|
62
|
-
* Classify a single test failure as `infeasible` or `regular`.
|
|
63
|
-
*
|
|
64
|
-
* @param {object} args
|
|
65
|
-
* @param {string} args.cmd — the command that ran
|
|
66
|
-
* @param {string} args.errorOutput — stdout + stderr from the failing run
|
|
67
|
-
* @param {number} args.consecutive — how many iterations in a row this
|
|
68
|
-
* same signature has failed
|
|
69
|
-
* @param {number} [args.threshold=2] — min consecutive failures before
|
|
70
|
-
* infeasibility is even considered.
|
|
71
|
-
* Below this, we always say "regular"
|
|
72
|
-
* (let the coder try once more).
|
|
73
|
-
* @returns {{infeasible: boolean, kind: string|null, signature: string}}
|
|
74
|
-
*/
|
|
75
|
-
export function classifyFailure({ cmd, errorOutput, consecutive, threshold = 2 }) {
|
|
76
|
-
const signature = failureSignature(cmd, errorOutput);
|
|
77
|
-
if (!Number.isFinite(consecutive) || consecutive < threshold) {
|
|
78
|
-
return { infeasible: false, kind: null, signature };
|
|
79
|
-
}
|
|
80
|
-
for (const sig of INFEASIBLE_SIGNATURES) {
|
|
81
|
-
if (sig.re.test(errorOutput || "")) {
|
|
82
|
-
return { infeasible: true, kind: sig.kind, signature };
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
return { infeasible: false, kind: null, signature };
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* Track consecutive identical failures across iterations of the same HU.
|
|
90
|
-
* Caller passes the same instance across iterations; the tracker
|
|
91
|
-
* remembers the last signature per command.
|
|
92
|
-
*/
|
|
93
|
-
export function createFailureTracker() {
|
|
94
|
-
// Map<cmd, { signature, count }>
|
|
95
|
-
const state = new Map();
|
|
96
|
-
return {
|
|
97
|
-
/**
|
|
98
|
-
* Record a failure and return how many consecutive iterations have
|
|
99
|
-
* produced the same signature for this cmd.
|
|
100
|
-
* @returns {number}
|
|
101
|
-
*/
|
|
102
|
-
record(cmd, errorOutput) {
|
|
103
|
-
const signature = failureSignature(cmd, errorOutput);
|
|
104
|
-
const prev = state.get(cmd);
|
|
105
|
-
if (prev && prev.signature === signature) {
|
|
106
|
-
prev.count += 1;
|
|
107
|
-
return prev.count;
|
|
108
|
-
}
|
|
109
|
-
state.set(cmd, { signature, count: 1 });
|
|
110
|
-
return 1;
|
|
111
|
-
},
|
|
112
|
-
/** Reset tracker for a cmd that just passed (stops false positives). */
|
|
113
|
-
reset(cmd) { state.delete(cmd); },
|
|
114
|
-
/** Diagnostic: dump current state. */
|
|
115
|
-
snapshot() { return Object.fromEntries(state); },
|
|
116
|
-
};
|
|
117
|
-
}
|