karajan-code 4.70.4 → 5.0.1
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/register-roles-skills.js +1 -1
- 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 +121 -55
- package/src/commands/review-gate.js +421 -341
- package/src/commands/skills.js +4 -1
- 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 +107 -26
- package/src/config/defaults.js +1 -0
- package/src/config/loader.js +34 -17
- package/src/config/role-resolver.js +1 -0
- package/src/config/schema.js +1 -0
- 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 +83 -13
- 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/handlers/rag-handler.js +9 -1
- 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 +71 -44
- package/src/rag/bootstrap-index.js +46 -54
- package/src/rag/indexer.js +9 -4
- package/src/rag/migrate.js +1 -1
- package/src/rag/onnx-fallback.js +2 -2
- package/src/rag/pid-lease.js +181 -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-cli.js +19 -1
- package/src/skills/atril-skills.js +5 -4
- package/src/skills/skills-search.js +72 -0
- package/src/skills/translate.js +62 -0
- 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
package/src/mcp/tools.js
CHANGED
|
@@ -48,91 +48,6 @@ export const tools = [
|
|
|
48
48
|
}
|
|
49
49
|
}
|
|
50
50
|
},
|
|
51
|
-
{
|
|
52
|
-
name: "kj_run",
|
|
53
|
-
description:
|
|
54
|
-
"Run the full Karajan pipeline. IMPORTANT: Pass the user's task description exactly as they wrote it. Do NOT group, split, reorder, or modify tasks yourself. Karajan handles decomposition, role activation, iteration, and quality gates internally. Do NOT override pipeline parameters (enableHuReviewer, mode, methodology) unless the user explicitly requested it. If you have observations about the task, use kj_suggest instead.",
|
|
55
|
-
inputSchema: {
|
|
56
|
-
type: "object",
|
|
57
|
-
properties: {
|
|
58
|
-
task: { type: "string", description: "Task description for the coder (can include a Planning Game card ID like KJC-TSK-0042). Either `task` or `taskFile` is required." },
|
|
59
|
-
taskFile: { type: "string", description: "Alternative to `task`: path to a .md (or any text file) whose contents become the task description. Relative paths resolve against projectDir." },
|
|
60
|
-
plan: { type: "string", description: "Plan ID from kj_plan. Loads persisted plan context and skips researcher/architect/planner stages." },
|
|
61
|
-
projectDir: { type: "string", description: "Absolute path to the project directory. Required when KJ MCP server runs from a different directory than the target project." },
|
|
62
|
-
pgTask: { type: "string", description: "Planning Game card ID (e.g., KJC-TSK-0042). If provided, fetches full card details as task context and updates card status on completion." },
|
|
63
|
-
pgProject: { type: "string", description: "Planning Game project ID (e.g., 'Karajan Code'). Required when pgTask is used." },
|
|
64
|
-
planner: { type: "string" },
|
|
65
|
-
coder: { type: "string" },
|
|
66
|
-
reviewer: { type: "string" },
|
|
67
|
-
refactorer: { type: "string" },
|
|
68
|
-
plannerModel: { type: "string" },
|
|
69
|
-
coderModel: { type: "string" },
|
|
70
|
-
reviewerModel: { type: "string" },
|
|
71
|
-
refactorerModel: { type: "string" },
|
|
72
|
-
enablePlanner: { type: "boolean" },
|
|
73
|
-
enableReviewer: { type: "boolean" },
|
|
74
|
-
enableRefactorer: { type: "boolean" },
|
|
75
|
-
enableResearcher: { type: "boolean" },
|
|
76
|
-
enableTester: { type: "boolean" },
|
|
77
|
-
enableSecurity: { type: "boolean" },
|
|
78
|
-
enableImpeccable: { type: "boolean" },
|
|
79
|
-
enablePerf: { type: "boolean" },
|
|
80
|
-
enableTriage: { type: "boolean" },
|
|
81
|
-
enableDiscover: { type: "boolean" },
|
|
82
|
-
enableArchitect: { type: "boolean" },
|
|
83
|
-
enableHuReviewer: { type: "boolean" },
|
|
84
|
-
architectModel: { type: "string" },
|
|
85
|
-
huFile: { type: "string", description: "Path to YAML file with HU stories to certify before coding" },
|
|
86
|
-
enableSerena: { type: "boolean" },
|
|
87
|
-
enableCi: { type: "boolean", description: "Enable Karajan CI (early PR + dispatch comments/reviews)" },
|
|
88
|
-
reviewerFallback: { type: "string" },
|
|
89
|
-
reviewerRetries: { type: "number" },
|
|
90
|
-
mode: { type: "string" },
|
|
91
|
-
maxIterations: { type: "number" },
|
|
92
|
-
maxIterationMinutes: { type: "number" },
|
|
93
|
-
maxTotalMinutes: { type: "number" },
|
|
94
|
-
baseBranch: { type: "string" },
|
|
95
|
-
baseRef: { type: "string" },
|
|
96
|
-
methodology: { type: "string", enum: ["tdd", "standard"] },
|
|
97
|
-
autoCommit: { type: "boolean" },
|
|
98
|
-
autoPush: { type: "boolean" },
|
|
99
|
-
autoPr: { type: "boolean" },
|
|
100
|
-
autoRebase: { type: "boolean" },
|
|
101
|
-
branchPrefix: { type: "string" },
|
|
102
|
-
autoSimplify: { type: "boolean", description: "Auto-simplify pipeline for simple tasks (level trivial/simple). Disable with false to force full pipeline." },
|
|
103
|
-
smartModels: { type: "boolean", description: "Enable/disable smart model selection based on triage complexity" },
|
|
104
|
-
checkpointInterval: { type: "number", description: "Minutes between interactive checkpoints (default: 5). Set 0 to disable." },
|
|
105
|
-
taskType: { type: "string", enum: ["sw", "infra", "doc", "add-tests", "refactor"], description: "Explicit task type for policy resolution. Overrides triage classification." },
|
|
106
|
-
quiet: { type: "boolean", description: "Suppress raw agent output lines, show only stage status (default: true). Set false for verbose output." },
|
|
107
|
-
noSonar: { type: "boolean" },
|
|
108
|
-
design: { type: "boolean", description: "Activate design refactoring mode. Impeccable role applies design changes instead of just auditing." },
|
|
109
|
-
enableSonarcloud: { type: "boolean", description: "Enable SonarCloud scan (complementary to SonarQube)" },
|
|
110
|
-
kjHome: { type: "string" },
|
|
111
|
-
sonarToken: { type: "string" },
|
|
112
|
-
timeoutMs: { type: "number" },
|
|
113
|
-
domain: { type: "string", description: "Domain knowledge: inline text describing the project domain, or absolute path to a .md file. Auto-saved to .karajan/domains/ for the curator." },
|
|
114
|
-
brain: { type: "string", enum: ["on", "off"], description: "Brain decisor: on|off. Controls whether intent-driven routing overrides pipeline flags." },
|
|
115
|
-
forceRole: { type: "array", items: { type: "string" }, description: "Force roles ON regardless of triage (e.g. [\"security\"])" },
|
|
116
|
-
skipRole: { type: "array", items: { type: "string" }, description: "Force roles OFF regardless of triage (e.g. [\"tester\"])" },
|
|
117
|
-
specReviewMode: { type: "string", enum: ["auto", "skip"], description: "Pre-pipeline spec-reviewer behaviour (KJC-PCS-0048). `auto` (default): the role runs, findings stream to the client via progress events; the pipeline continues regardless. `skip`: bypass the reviewer entirely (mirrors the --skip-spec-review CLI flag)." }
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
},
|
|
121
|
-
{
|
|
122
|
-
name: "kj_resume",
|
|
123
|
-
description:
|
|
124
|
-
"Resume a paused, stopped, or failed session by ID. For paused sessions, provide an answer. For stopped/failed sessions, re-runs the flow from scratch. Sends real-time progress notifications via MCP logging.",
|
|
125
|
-
inputSchema: {
|
|
126
|
-
type: "object",
|
|
127
|
-
required: ["sessionId"],
|
|
128
|
-
properties: {
|
|
129
|
-
sessionId: { type: "string", description: "Session ID to resume" },
|
|
130
|
-
answer: { type: "string", description: "Answer to the question that caused the pause" },
|
|
131
|
-
projectDir: { type: "string", description: "Absolute path to the project directory" },
|
|
132
|
-
kjHome: { type: "string" }
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
},
|
|
136
51
|
{
|
|
137
52
|
name: "kj_report",
|
|
138
53
|
description: "Read latest or list session reports. Use trace=true for chronological stage-by-stage breakdown with timing and token usage.",
|
|
@@ -176,7 +91,7 @@ export const tools = [
|
|
|
176
91
|
},
|
|
177
92
|
{
|
|
178
93
|
name: "kj_preflight",
|
|
179
|
-
description: "Confirm or adjust agent configuration before first
|
|
94
|
+
description: "Confirm or adjust agent configuration before first kj_code. REQUIRED before running any task via MCP. Show the config to the human, get their confirmation or adjustments, then call this tool with their response.",
|
|
180
95
|
inputSchema: {
|
|
181
96
|
type: "object",
|
|
182
97
|
required: ["humanResponse"],
|
|
@@ -207,50 +122,6 @@ export const tools = [
|
|
|
207
122
|
}
|
|
208
123
|
}
|
|
209
124
|
},
|
|
210
|
-
{
|
|
211
|
-
name: "kj_review",
|
|
212
|
-
description: "Run reviewer-only mode against current diff. Do NOT filter or pre-process the diff before passing it.",
|
|
213
|
-
inputSchema: {
|
|
214
|
-
type: "object",
|
|
215
|
-
properties: {
|
|
216
|
-
task: { type: "string", description: "Task description. Either `task` or `taskFile` is required." },
|
|
217
|
-
taskFile: { type: "string", description: "Alternative to `task`: path to a .md/text file." },
|
|
218
|
-
reviewer: { type: "string" },
|
|
219
|
-
reviewerModel: { type: "string" },
|
|
220
|
-
baseRef: { type: "string" },
|
|
221
|
-
projectDir: { type: "string", description: "Absolute path to the project directory" },
|
|
222
|
-
kjHome: { type: "string" }
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
},
|
|
226
|
-
{
|
|
227
|
-
name: "kj_status",
|
|
228
|
-
description: "Show real-time status and log of the current or last Karajan run. Returns a parsed status (current stage, agent, iteration, errors) plus recent log lines. Use this to monitor progress while kj_run/kj_plan/kj_code is executing.",
|
|
229
|
-
inputSchema: {
|
|
230
|
-
type: "object",
|
|
231
|
-
properties: {
|
|
232
|
-
lines: { type: "number", description: "Number of log lines to show (default 50)" }
|
|
233
|
-
}
|
|
234
|
-
}
|
|
235
|
-
},
|
|
236
|
-
{
|
|
237
|
-
name: "kj_plan",
|
|
238
|
-
description: "Generate implementation plan for a task. Pass the task as the user described it. Karajan's planner decides the approach.",
|
|
239
|
-
inputSchema: {
|
|
240
|
-
type: "object",
|
|
241
|
-
properties: {
|
|
242
|
-
task: { type: "string", description: "Task description. Either `task` or `taskFile` is required." },
|
|
243
|
-
taskFile: { type: "string", description: "Alternative to `task`: path to a .md/text file." },
|
|
244
|
-
planner: { type: "string" },
|
|
245
|
-
plannerModel: { type: "string" },
|
|
246
|
-
coder: { type: "string", description: "Legacy alias for planner" },
|
|
247
|
-
coderModel: { type: "string", description: "Legacy alias for plannerModel" },
|
|
248
|
-
projectDir: { type: "string", description: "Absolute path to the project directory" },
|
|
249
|
-
kjHome: { type: "string" },
|
|
250
|
-
specReviewMode: { type: "string", enum: ["auto", "skip"], description: "Pre-planner spec-reviewer behaviour (KJC-PCS-0048). `auto` (default): runs the reviewer, findings stream as progress, planner continues. `skip`: bypass entirely." }
|
|
251
|
-
}
|
|
252
|
-
}
|
|
253
|
-
},
|
|
254
125
|
{
|
|
255
126
|
name: "kj_discover",
|
|
256
127
|
description: "Analyze a task for gaps, ambiguities, and missing information before execution. Returns a verdict (ready/needs_validation) with structured gap list. Can read task details from Planning Game if pgTask is provided.",
|
|
@@ -268,46 +139,6 @@ export const tools = [
|
|
|
268
139
|
}
|
|
269
140
|
}
|
|
270
141
|
},
|
|
271
|
-
{
|
|
272
|
-
name: "kj_triage",
|
|
273
|
-
description: "Classify task complexity and recommend which pipeline roles to activate. Returns level (trivial/simple/medium/complex), taskType, recommended roles, and optional decomposition.",
|
|
274
|
-
inputSchema: {
|
|
275
|
-
type: "object",
|
|
276
|
-
properties: {
|
|
277
|
-
task: { type: "string", description: "Task description to classify. Either `task` or `taskFile` is required." },
|
|
278
|
-
taskFile: { type: "string", description: "Alternative to `task`: path to a .md/text file." },
|
|
279
|
-
projectDir: { type: "string", description: "Absolute path to the project directory" },
|
|
280
|
-
kjHome: { type: "string" }
|
|
281
|
-
}
|
|
282
|
-
}
|
|
283
|
-
},
|
|
284
|
-
{
|
|
285
|
-
name: "kj_researcher",
|
|
286
|
-
description: "Research the codebase for a task. Identifies affected files, patterns, constraints, prior decisions, risks, and test coverage.",
|
|
287
|
-
inputSchema: {
|
|
288
|
-
type: "object",
|
|
289
|
-
properties: {
|
|
290
|
-
task: { type: "string", description: "Task description to research. Either `task` or `taskFile` is required." },
|
|
291
|
-
taskFile: { type: "string", description: "Alternative to `task`: path to a .md/text file." },
|
|
292
|
-
projectDir: { type: "string", description: "Absolute path to the project directory" },
|
|
293
|
-
kjHome: { type: "string" }
|
|
294
|
-
}
|
|
295
|
-
}
|
|
296
|
-
},
|
|
297
|
-
{
|
|
298
|
-
name: "kj_architect",
|
|
299
|
-
description: "Design solution architecture for a task. Returns layers, patterns, data model, API contracts, tradeoffs, and a verdict (ready/needs_clarification).",
|
|
300
|
-
inputSchema: {
|
|
301
|
-
type: "object",
|
|
302
|
-
properties: {
|
|
303
|
-
task: { type: "string", description: "Task description to architect. Either `task` or `taskFile` is required." },
|
|
304
|
-
taskFile: { type: "string", description: "Alternative to `task`: path to a .md/text file." },
|
|
305
|
-
context: { type: "string", description: "Additional context (e.g., researcher output)" },
|
|
306
|
-
projectDir: { type: "string", description: "Absolute path to the project directory" },
|
|
307
|
-
kjHome: { type: "string" }
|
|
308
|
-
}
|
|
309
|
-
}
|
|
310
|
-
},
|
|
311
142
|
{
|
|
312
143
|
name: "kj_audit",
|
|
313
144
|
description: "Analyze codebase health without modifying files. Returns findings across security, code quality, performance, architecture, and testing dimensions.",
|
|
@@ -363,32 +194,6 @@ export const tools = [
|
|
|
363
194
|
}
|
|
364
195
|
}
|
|
365
196
|
},
|
|
366
|
-
{
|
|
367
|
-
name: "kj_skills",
|
|
368
|
-
description: "Manage OpenSkills for domain-specific agent knowledge. Install skills from the marketplace or GitHub repos to give coders domain expertise.",
|
|
369
|
-
inputSchema: {
|
|
370
|
-
type: "object",
|
|
371
|
-
properties: {
|
|
372
|
-
action: { type: "string", enum: ["install", "remove", "list", "read"], description: "Action to perform" },
|
|
373
|
-
source: { type: "string", description: "Skill source: marketplace name, GitHub URL, or local path (for install)" },
|
|
374
|
-
name: { type: "string", description: "Skill name (for remove/read)" },
|
|
375
|
-
global: { type: "boolean", description: "Install globally (~/.agent/skills/) instead of project-local" },
|
|
376
|
-
projectDir: { type: "string", description: "Project directory" }
|
|
377
|
-
},
|
|
378
|
-
required: ["action"]
|
|
379
|
-
}
|
|
380
|
-
},
|
|
381
|
-
{
|
|
382
|
-
name: "kj_undo",
|
|
383
|
-
description: "Revert the last pipeline run by resetting to the pre-pipeline commit. Default: soft reset (keeps changes staged). Use hard=true to discard all changes.",
|
|
384
|
-
inputSchema: {
|
|
385
|
-
type: "object",
|
|
386
|
-
properties: {
|
|
387
|
-
hard: { type: "boolean", description: "If true, discard all changes (git reset --hard). Default: false (soft reset, keeps changes staged)." },
|
|
388
|
-
projectDir: { type: "string", description: "Absolute path to the project directory" }
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
},
|
|
392
197
|
{
|
|
393
198
|
name: "kj_clean",
|
|
394
199
|
description: "Garbage-collect stale Karajan state: orphan plans (project dir no longer exists), finalised plans/sessions past their retention window, and HU story batches. Dry-run by default; pass yes=true to actually delete. Use nuke=true to wipe everything (retention=0 on every bucket + drop the HU board SQLite DB).",
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* #2566: a lane (<main>/.kj/worktrees/<x>, <main>/.claude/worktrees/<x>) kept plans under its own
|
|
3
|
+
* slug; `kj hu list` moves them to the main checkout's. A taken name is kept and reported.
|
|
4
|
+
*/
|
|
5
|
+
import fs from "node:fs/promises";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
import { plansDir } from "./plan-store.js";
|
|
8
|
+
import { mainCheckout } from "../utils/main-checkout.js";
|
|
9
|
+
import { MIRROR_PLAN_ID } from "../hu/team-board-mirror.js";
|
|
10
|
+
|
|
11
|
+
const LANE_DIRS = [".kj_worktrees_", ".claude_worktrees_"];
|
|
12
|
+
|
|
13
|
+
// Never overwrites: "wx" and errorOnExist fail with EEXIST if another session wrote dest first.
|
|
14
|
+
async function moveEntry(from, dest, main) {
|
|
15
|
+
const plan = from.endsWith(".json") ? await fs.readFile(from, "utf8").then(JSON.parse, () => null) : null;
|
|
16
|
+
if (plan) {
|
|
17
|
+
// TSK-0341: the stamp must outlive the lane, or the GC sees the plan as orphan.
|
|
18
|
+
await fs.writeFile(dest, JSON.stringify({ ...plan, projectDir: main }, null, 2), { flag: "wx" });
|
|
19
|
+
return fs.unlink(from);
|
|
20
|
+
}
|
|
21
|
+
await fs.cp(from, dest, { recursive: true, force: false, errorOnExist: true });
|
|
22
|
+
return fs.rm(from, { recursive: true });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export async function migrateLanePlans(projectDir) {
|
|
26
|
+
const [target, main] = [plansDir(projectDir), mainCheckout(projectDir)];
|
|
27
|
+
const root = path.dirname(target);
|
|
28
|
+
const prefixes = LANE_DIRS.map((d) => `${path.basename(target)}_${d}`); // the main checkout's slug
|
|
29
|
+
const moved = [];
|
|
30
|
+
const kept = [];
|
|
31
|
+
let entries;
|
|
32
|
+
try { entries = await fs.readdir(root, { withFileTypes: true }); } catch (err) {
|
|
33
|
+
if (err.code === "ENOENT") return { moved, kept };
|
|
34
|
+
throw err;
|
|
35
|
+
}
|
|
36
|
+
const isLaneFolder = (e) => e.isDirectory() && prefixes.some((p) => e.name.startsWith(p));
|
|
37
|
+
for (const lane of entries.filter(isLaneFolder)) {
|
|
38
|
+
const from = path.join(root, lane.name);
|
|
39
|
+
await fs.mkdir(target, { recursive: true });
|
|
40
|
+
for (const file of await fs.readdir(from)) {
|
|
41
|
+
// The team board mirror is no plan: `kj hu list` rewrites it from GitHub, a lane's copy is stale.
|
|
42
|
+
if (file === `${MIRROR_PLAN_ID}.json`) { await fs.unlink(path.join(from, file)); continue; }
|
|
43
|
+
try {
|
|
44
|
+
await moveEntry(path.join(from, file), path.join(target, file), main);
|
|
45
|
+
moved.push(file);
|
|
46
|
+
} catch (err) {
|
|
47
|
+
if (err.code !== "EEXIST" && err.code !== "ERR_FS_CP_EEXIST") throw err;
|
|
48
|
+
kept.push(path.join(from, file));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
await fs.rmdir(from).catch((err) => { if (err.code !== "ENOTEMPTY") throw err; }); // a collision stays
|
|
52
|
+
}
|
|
53
|
+
return { moved, kept };
|
|
54
|
+
}
|
package/src/plan/plan-store.js
CHANGED
|
@@ -10,6 +10,7 @@ import { writeJsonAtomic } from "../utils/atomic-write.js";
|
|
|
10
10
|
import { resolveHome } from "../utils/paths.js";
|
|
11
11
|
import { getSharedPlansDir } from "../utils/shared-paths.js";
|
|
12
12
|
import { generatePlanId, normaliseAlias } from "./plan-id.js";
|
|
13
|
+
import { mainCheckout } from "../utils/main-checkout.js";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* A plan JSON file failed to parse — typically an interrupted write
|
|
@@ -51,7 +52,7 @@ export function getKjHome() {
|
|
|
51
52
|
/**
|
|
52
53
|
* Deterministic slug for a project path — stable across plan-store writes,
|
|
53
54
|
* HU-board syncs and board URL routing. Keep in lockstep with
|
|
54
|
-
* `packages/hu-board/src/sync.js::deriveProjectIdFromDir
|
|
55
|
+
* `packages/hu-board/src/sync.js::deriveProjectIdFromDir` (kj slugs the main checkout, #2566).
|
|
55
56
|
* @param {string} projectDir - absolute project path
|
|
56
57
|
* @returns {string}
|
|
57
58
|
*/
|
|
@@ -64,12 +65,13 @@ export function projectSlug(projectDir) {
|
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
// KJC-TSK-1166: the board honours KJ_PLANS_DIR as its plans root; what kj writes must land
|
|
67
|
-
// in the same root, or the board shows none of it.
|
|
68
|
+
// in the same root, or the board shows none of it. #2566: one folder per main checkout, lanes included.
|
|
68
69
|
export function plansDir(projectDir) {
|
|
69
70
|
const root = process.env.KJ_PLANS_DIR || path.join(getKjHome(), "plans");
|
|
70
|
-
return path.join(root, projectSlug(projectDir));
|
|
71
|
+
return path.join(root, projectSlug(mainCheckout(projectDir)));
|
|
71
72
|
}
|
|
72
73
|
|
|
74
|
+
|
|
73
75
|
/**
|
|
74
76
|
* Save a plan to disk. Accepts both v1 and v2 schemas.
|
|
75
77
|
* @param {string} projectDir
|
|
@@ -88,7 +90,8 @@ export async function savePlan(projectDir, planResult) {
|
|
|
88
90
|
// plan so the GC can check `fs.exists(projectDir)` reliably. Reverse-
|
|
89
91
|
// engineering it from the slug is lossy (underscores in the original
|
|
90
92
|
// name collide with underscores introduced by slash-substitution).
|
|
91
|
-
|
|
93
|
+
// #2566: the main checkout, never a lane that is deleted after merge.
|
|
94
|
+
if (!planResult.projectDir && projectDir) planResult.projectDir = mainCheckout(projectDir);
|
|
92
95
|
// Humanización IDs: si el plan no tiene alias todavía, derívalo de
|
|
93
96
|
// plan.name (lo que el planner pidió al usuario). Resuelve
|
|
94
97
|
// colisiones con sufijo -2, -3… leyendo el dir antes.
|
package/src/policy/add.js
CHANGED
|
@@ -67,31 +67,29 @@ const naiveDiff = (oldText, newText) => {
|
|
|
67
67
|
].join("\n");
|
|
68
68
|
};
|
|
69
69
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
logger.error?.('policy add: falta la regla — kj policy add "el coder no toca los workflows"');
|
|
74
|
-
return 1;
|
|
75
|
-
}
|
|
76
|
-
const { translateFn = defaultTranslate } = deps;
|
|
77
|
-
const raw = await translateFn({ text, config, logger });
|
|
70
|
+
// The agent's answer as a policy fragment, or null (said) when it is not one:
|
|
71
|
+
// untranslatable, not YAML, or keys outside the closed vocabulary.
|
|
72
|
+
function fragmentOf(raw, logger) {
|
|
78
73
|
if (raw.includes("NO_TRADUCIBLE")) {
|
|
79
74
|
logger.error?.("policy add: la regla no cabe en el vocabulario cerrado v1 — no se inventa nada; exprésala como write/shell/diff-threshold o queda como instrucción de prompt");
|
|
80
|
-
return
|
|
75
|
+
return null;
|
|
81
76
|
}
|
|
82
77
|
let frag;
|
|
83
78
|
try {
|
|
84
79
|
frag = yaml.load(raw);
|
|
85
80
|
} catch (err) {
|
|
86
81
|
logger.error?.(`policy add: el agente no devolvió YAML válido (${err.message}) — nada que aplicar`);
|
|
87
|
-
return
|
|
82
|
+
return null;
|
|
88
83
|
}
|
|
89
84
|
if (typeof frag !== "object" || frag === null || Object.keys(frag).some((k) => !["roles", "invariants"].includes(k))) {
|
|
90
85
|
logger.error?.("policy add: el fragmento solo puede declarar roles y/o invariants — rechazado (vocabulario cerrado)");
|
|
91
|
-
return
|
|
86
|
+
return null;
|
|
92
87
|
}
|
|
88
|
+
return frag;
|
|
89
|
+
}
|
|
93
90
|
|
|
94
|
-
|
|
91
|
+
// The current policy merged with the fragment, or null (said) when the engine rejects it.
|
|
92
|
+
function proposePolicy(file, frag, logger) {
|
|
95
93
|
let current = "";
|
|
96
94
|
try {
|
|
97
95
|
current = readFileSync(file, "utf8");
|
|
@@ -102,8 +100,24 @@ export async function policyAddCommand({ text, config = {}, flags = {}, logger =
|
|
|
102
100
|
if (errors.length > 0) {
|
|
103
101
|
for (const e of errors) logger.error?.(`✗ ${e}`);
|
|
104
102
|
logger.error?.("policy add: la regla traducida no valida contra el vocabulario del motor — rechazada, no se inventa");
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
return { current, proposed };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export async function policyAddCommand({ text, config = {}, flags = {}, logger = console, deps = {} }) {
|
|
109
|
+
const projectDir = config?.projectDir || process.cwd();
|
|
110
|
+
if (!text?.trim()) {
|
|
111
|
+
logger.error?.('policy add: falta la regla — kj policy add "el coder no toca los workflows"');
|
|
105
112
|
return 1;
|
|
106
113
|
}
|
|
114
|
+
const { translateFn = defaultTranslate } = deps;
|
|
115
|
+
const frag = fragmentOf(await translateFn({ text, config, logger }), logger);
|
|
116
|
+
if (!frag) return 1;
|
|
117
|
+
const file = join(projectDir, ".karajan", "policy.yml");
|
|
118
|
+
const proposal = proposePolicy(file, frag, logger);
|
|
119
|
+
if (!proposal) return 1;
|
|
120
|
+
const { current, proposed } = proposal;
|
|
107
121
|
|
|
108
122
|
logger.info?.(`policy add — diff propuesto para .karajan/policy.yml:\n${naiveDiff(current, proposed)}`);
|
|
109
123
|
if (current.includes("#")) {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* HU-2792: two branches can add `.karajan/adrs/0037-a.md` and `0037-b.md` in
|
|
3
|
+
* parallel: the names differ, so git sees no conflict and both land with the
|
|
4
|
+
* same number. CI judges a PR that adds an ADR whose number the base already
|
|
5
|
+
* holds under another name (unless the same diff deletes that one: a rename).
|
|
6
|
+
*/
|
|
7
|
+
export const ADR_RULE = "defaults.adr.number";
|
|
8
|
+
const ADR_DIR = ".karajan/adrs/";
|
|
9
|
+
const ADR = /^\.karajan\/adrs\/(\d{4})-[^/]*\.md$/;
|
|
10
|
+
const lines = (text) => text.split("\n").map((s) => s.trim()).filter(Boolean);
|
|
11
|
+
const numberOf = (file) => Number(ADR.exec(file)[1]);
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* @param {{files: string[], range: string|null, run: (args: string[]) => Promise<string>}} opts
|
|
15
|
+
* @returns {Promise<object|null>} the violation, or null when no added ADR collides with the base
|
|
16
|
+
*/
|
|
17
|
+
export async function adrNumberViolation({ files, range, run }) {
|
|
18
|
+
if (!files.some((f) => ADR.test(f))) return null;
|
|
19
|
+
const baseRef = range ? range.split(/\.{2,3}/)[0] : "HEAD";
|
|
20
|
+
const changes = lines(await run(["diff", "--no-renames", "--name-status", ...(range ? ["--end-of-options", range] : ["--cached"])])).map((l) => l.split("\t"));
|
|
21
|
+
const added = changes.filter(([status, file]) => status === "A" && ADR.test(file)).map(([, file]) => file);
|
|
22
|
+
const deleted = new Set(changes.filter(([status]) => status === "D").map(([, file]) => file));
|
|
23
|
+
const onBase = lines(await run(["ls-tree", "--name-only", "--end-of-options", baseRef, ADR_DIR])).filter((f) => ADR.test(f));
|
|
24
|
+
for (const file of added) {
|
|
25
|
+
const clash = onBase.find((f) => f !== file && numberOf(f) === numberOf(file) && !deleted.has(f));
|
|
26
|
+
if (!clash) continue;
|
|
27
|
+
const next = String(Math.max(...onBase.map(numberOf), ...added.map(numberOf)) + 1).padStart(4, "0");
|
|
28
|
+
return { rule_id: ADR_RULE, enforcement: "deny", file, reason: `${file} reuses the ADR number of ${clash}, which the base already has; the next free number is ${next}` };
|
|
29
|
+
}
|
|
30
|
+
return null;
|
|
31
|
+
}
|