karajan-code 4.70.3 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +6 -14
- package/packages/hu-board/package.json +1 -1
- package/packages/hu-board/public/app.js +4 -18
- package/packages/hu-board/public/index.html +5 -2
- package/packages/hu-board/public/styles.css +26 -0
- package/packages/hu-board/public/utils/api.js +0 -74
- package/packages/hu-board/public/utils/board-tour.js +62 -0
- package/packages/hu-board/public/utils/board-view.js +49 -144
- package/packages/hu-board/public/utils/command-launcher.js +8 -70
- package/packages/hu-board/public/utils/formatters.js +10 -0
- package/packages/hu-board/public/utils/governance-view.js +13 -1
- package/packages/hu-board/public/utils/hu-actions.js +17 -68
- package/packages/hu-board/public/utils/init-listeners.js +7 -8
- package/packages/hu-board/public/utils/log-panel.js +3 -16
- package/packages/hu-board/public/utils/maggle-mode.js +2 -12
- package/packages/hu-board/public/utils/modals.js +7 -0
- package/packages/hu-board/public/utils/project-picker-view.js +1 -1
- package/packages/hu-board/public/utils/server-push.js +3 -22
- package/packages/hu-board/public/utils/shared-board-notice.js +33 -0
- package/packages/hu-board/public/utils/story-detail-view.js +5 -19
- package/packages/hu-board/src/cleanup-zombies.js +0 -1
- package/packages/hu-board/src/command-runner.js +36 -25
- package/packages/hu-board/src/db-connection.js +282 -0
- package/packages/hu-board/src/db-paths.js +113 -0
- package/packages/hu-board/src/db-queries.js +188 -0
- package/packages/hu-board/src/db-tombstones.js +51 -0
- package/packages/hu-board/src/db.js +28 -619
- package/packages/hu-board/src/ephemeral-cleaner.js +2 -3
- package/packages/hu-board/src/hu-zombie-reaper.js +5 -1
- package/packages/hu-board/src/plan-mutations.js +3 -153
- package/packages/hu-board/src/plans-service.js +55 -0
- package/packages/hu-board/src/routes/api/commands.js +122 -0
- package/packages/hu-board/src/routes/api/plans.js +55 -0
- package/packages/hu-board/src/routes/api/projects.js +267 -0
- package/packages/hu-board/src/routes/api/prompts.js +135 -0
- package/packages/hu-board/src/routes/api/sessions.js +95 -0
- package/packages/hu-board/src/routes/api/stories.js +183 -0
- package/packages/hu-board/src/routes/api/system.js +241 -0
- package/packages/hu-board/src/routes/api/tombstones.js +38 -0
- package/packages/hu-board/src/routes/api.js +24 -1371
- package/packages/hu-board/src/routes/governance.js +23 -1
- package/packages/hu-board/src/routes/lanes.js +40 -0
- package/packages/hu-board/src/routes/rag.js +4 -3
- package/packages/hu-board/src/server.js +20 -34
- package/packages/hu-board/src/sync-files.js +318 -0
- package/packages/hu-board/src/sync-project.js +99 -0
- package/packages/hu-board/src/sync.js +22 -566
- package/packages/hu-board/src/zombie-reaper.js +1 -1
- package/scripts/dual-publish.mjs +19 -3
- package/scripts/postinstall.js +15 -2
- package/scripts/verify-pack-mcp.mjs +11 -7
- package/scripts/verify-pack.mjs +4 -4
- package/src/audit/audit-history.js +3 -2
- package/src/audit/dead-exports.js +68 -59
- package/src/audit/deterministic-summary.js +33 -13
- package/src/audit/npm-signatures.js +41 -0
- package/src/audit/sbom.js +35 -0
- package/src/checks/agent-token.js +112 -0
- package/src/checks/ai-surface.js +9 -7
- package/src/checks/config-files.js +8 -7
- package/src/checks/dir-setup.js +34 -1
- package/src/checks/native-build.js +21 -39
- package/src/checks/runner.js +128 -142
- package/src/cli/advanced-commands.js +6 -6
- package/src/cli/register-meta.js +42 -82
- package/src/cli/register-pipeline.js +8 -140
- package/src/cli/register-plan.js +9 -100
- package/src/cli/retired-commands.js +49 -0
- package/src/cli.js +18 -5
- package/src/commands/agent-run.js +40 -26
- package/src/commands/audit.js +127 -124
- package/src/commands/board.js +5 -9
- package/src/commands/check.js +29 -16
- package/src/commands/doctor.js +2 -0
- package/src/commands/env.js +176 -148
- package/src/commands/go.js +76 -55
- package/src/commands/harden.js +105 -86
- package/src/commands/hu-start.js +52 -0
- package/src/commands/hu.js +288 -221
- package/src/commands/identity.js +111 -78
- package/src/commands/init-atril.js +47 -0
- package/src/commands/init.js +11 -40
- package/src/commands/install-tools.js +70 -85
- package/src/commands/main-guard.js +78 -0
- package/src/commands/policy.js +226 -184
- package/src/commands/qmd.js +27 -21
- package/src/commands/rag.js +75 -48
- package/src/commands/review-gate.js +422 -339
- package/src/commands/start.js +4 -5
- package/src/commands/steward.js +96 -47
- package/src/commands/tournament.js +46 -37
- package/src/commands/worktree.js +94 -25
- package/src/config/loader.js +34 -17
- package/src/config/overrides.js +2 -1
- package/src/config/secrets.js +36 -0
- package/src/config.js +1 -0
- package/src/environment/adr.js +16 -2
- package/src/environment/board-access.js +11 -9
- package/src/git/karajan-label.js +20 -8
- package/src/guards/main-guard.js +54 -0
- package/src/harden/branch-protection.js +36 -3
- package/src/harden/hook-templates.js +16 -1
- package/src/harden/sentinel/posttooluse.mjs +18 -6
- package/src/harden/sentinel/pretooluse-sentinel.mjs +81 -35
- package/src/harden/sentinel/sentinel-lib.mjs +149 -23
- package/src/harden/sentinel/sentinel-shell.mjs +64 -0
- package/src/harden/sentinel/sessionstart.mjs +3 -2
- package/src/harden/sentinel/stop.mjs +5 -4
- package/src/harden/workflow-engine.js +12 -1
- package/src/harden/workflow-templates.js +1 -1
- package/src/hu/board-lookup.js +29 -0
- package/src/hu/gh-identity.js +23 -0
- package/src/hu/github-board.js +4 -2
- package/src/hu/lane-prune.js +15 -7
- package/src/hu/lane-setup.js +65 -0
- package/src/hu/lanes.js +66 -0
- package/src/hu/team-board-mirror.js +3 -2
- package/src/infrastructure/command-runner.js +1 -1
- package/src/infrastructure/file-system-service.js +1 -1
- package/src/mcp/handlers/direct-handlers.js +2 -292
- package/src/mcp/handlers/hu-handlers.js +44 -55
- package/src/mcp/handlers/management-handlers.js +2 -42
- package/src/mcp/server-handlers.js +16 -22
- package/src/mcp/tools.js +5 -200
- package/src/plan/lane-plans.js +54 -0
- package/src/plan/plan-store.js +7 -4
- package/src/policy/add.js +26 -12
- package/src/policy/adr-number-check.js +31 -0
- package/src/prompts/audit.js +299 -339
- package/src/prompts/card-context.js +10 -5
- package/src/prompts/reviewer.js +5 -2
- package/src/rag/auto-update.js +56 -43
- package/src/rag/bootstrap-index.js +28 -54
- package/src/rag/migrate.js +1 -1
- package/src/rag/onnx-fallback.js +2 -2
- package/src/rag/pid-lease.js +70 -0
- package/src/rag/project-store.js +2 -12
- package/src/review/card-first.js +57 -46
- package/src/review/dual-review.js +5 -4
- package/src/review/gate-gitignore.js +23 -4
- package/src/review/gate-hook.js +47 -0
- package/src/review/one-shot-review.js +51 -37
- package/src/review/publish-pr.js +18 -3
- package/src/review/repo-docs.js +73 -0
- package/src/review/repo-stack.js +59 -0
- package/src/review/reviewer-fallback.js +34 -3
- package/src/review/seal-commit.js +29 -0
- package/src/review/sonar-pregate.js +59 -44
- package/src/review/third-party-review.js +90 -16
- package/src/review/third-party-size.js +43 -0
- package/src/review/third-party-skills.js +54 -0
- package/src/review/verify-pr.js +2 -26
- package/src/roles/audit-role.js +106 -125
- package/src/skills/atril-skills.js +5 -4
- package/src/sonar/scanner.js +116 -101
- package/src/steward/invariants.js +5 -4
- package/src/steward/phantom-sweep.js +66 -0
- package/src/tournament/judge.js +60 -53
- package/src/utils/agent-home.js +33 -0
- package/src/utils/gh-account.js +37 -0
- package/src/utils/git.js +0 -13
- package/src/utils/home-migration.js +7 -1
- package/src/utils/main-checkout.js +53 -0
- package/src/utils/qmd-collection.js +3 -1
- package/src/utils/welcome.js +3 -3
- package/templates/{skills → atril/commands}/kj-architect.md +5 -0
- package/templates/{skills → atril/commands}/kj-audit.md +5 -0
- package/templates/{skills → atril/commands}/kj-board.md +5 -0
- package/templates/{skills → atril/commands}/kj-code.md +5 -0
- package/templates/{skills → atril/commands}/kj-csv-transform.md +5 -0
- package/templates/{skills → atril/commands}/kj-data-report.md +5 -0
- package/templates/{skills → atril/commands}/kj-discover.md +5 -0
- package/templates/{skills → atril/commands}/kj-rag-query.md +5 -0
- package/templates/{skills → atril/commands}/kj-review.md +5 -0
- package/templates/{skills → atril/commands}/kj-security.md +5 -0
- package/templates/{skills → atril/commands}/kj-sonar.md +5 -0
- package/templates/{skills → atril/commands}/kj-sql-analysis.md +5 -0
- package/templates/{skills → atril/commands}/kj-test.md +5 -0
- package/packages/hu-board/public/utils/run-launcher.js +0 -169
- package/packages/hu-board/src/run-tracker.js +0 -121
- package/src/activity-log.js +0 -59
- package/src/agents/host-agent.js +0 -38
- package/src/agents/model-registry.js +0 -142
- package/src/audit/plan-adherence.js +0 -224
- package/src/autonomy/arbiter-role.js +0 -71
- package/src/autonomy/arbiter.js +0 -22
- package/src/autonomy/decider.js +0 -18
- package/src/autonomy/decision-resolver.js +0 -58
- package/src/autonomy/outcome-report.js +0 -55
- package/src/autonomy/stage-ask.js +0 -30
- package/src/brain/decision-tracker.js +0 -125
- package/src/brain/decisor.js +0 -243
- package/src/brain/solomon-consult.js +0 -171
- package/src/brain/standby-scheduler.js +0 -8
- package/src/budget/comparison.js +0 -96
- package/src/canvas/parse-md.js +0 -238
- package/src/canvas/schema.js +0 -101
- package/src/canvas/to-flat.js +0 -85
- package/src/canvas/validate.js +0 -163
- package/src/ci/dispatch.js +0 -99
- package/src/ci/index.js +0 -3
- package/src/ci/pr-diff.js +0 -26
- package/src/cli/register-standby.js +0 -26
- package/src/commands/architect.js +0 -111
- package/src/commands/autorun.js +0 -49
- package/src/commands/researcher.js +0 -54
- package/src/commands/resume.js +0 -72
- package/src/commands/review.js +0 -89
- package/src/commands/run.js +0 -185
- package/src/commands/standby.js +0 -117
- package/src/commands/status.js +0 -36
- package/src/commands/triage.js +0 -78
- package/src/commands/undo.js +0 -62
- package/src/domains/domain-loader.js +0 -245
- package/src/domains/domain-registry.js +0 -140
- package/src/domains/domain-synthesizer.js +0 -129
- package/src/git/automation.js +0 -433
- package/src/git/coder-commit-guard.js +0 -40
- package/src/git/hu-automation.js +0 -243
- package/src/git/hu-snapshot.js +0 -10
- package/src/git/symlink-targets.js +0 -57
- package/src/golden/asserter.js +0 -92
- package/src/golden/loader.js +0 -52
- package/src/golden/runner.js +0 -92
- package/src/golden/schema.js +0 -41
- package/src/guards/duplicate-members.js +0 -86
- package/src/guards/intent-guard.js +0 -161
- package/src/guards/perf-guard.js +0 -126
- package/src/guards/policy-guard.js +0 -90
- package/src/guards/policy-resolver.js +0 -85
- package/src/guards/secret-redactor.js +0 -71
- package/src/hu/acceptance-runner.js +0 -138
- package/src/hu/lazy-planner.js +0 -109
- package/src/hu/parallel-executor.js +0 -94
- package/src/hu/routing-metrics.js +0 -17
- package/src/hu/split-validator.js +0 -143
- package/src/hu/splitting-detector.js +0 -158
- package/src/hu/splitting-generator.js +0 -175
- package/src/hu/store.js +0 -394
- package/src/infrastructure/mocks.js +0 -182
- package/src/mcp/handlers/run-handler.js +0 -318
- package/src/mcp/sovereignty-guard.js +0 -113
- package/src/mcp/tool-arg-normalizers.js +0 -16
- package/src/mutate/reviewer-signal.js +0 -76
- package/src/orchestrator/agent-fallback.js +0 -81
- package/src/orchestrator/brain-coordinator.js +0 -186
- package/src/orchestrator/ci-integration.js +0 -139
- package/src/orchestrator/config-init.js +0 -426
- package/src/orchestrator/direct-actions.js +0 -217
- package/src/orchestrator/drivers/error-recovery.js +0 -287
- package/src/orchestrator/drivers/init-context.js +0 -238
- package/src/orchestrator/drivers/iteration-loop.js +0 -382
- package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +0 -69
- package/src/orchestrator/drivers/iteration-phases/guards.js +0 -91
- package/src/orchestrator/drivers/iteration-phases/handle-approved.js +0 -29
- package/src/orchestrator/drivers/iteration-phases/quality-gates.js +0 -125
- package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +0 -62
- package/src/orchestrator/drivers/post-loop.js +0 -404
- package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +0 -137
- package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +0 -47
- package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +0 -84
- package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +0 -393
- package/src/orchestrator/drivers/pre-loop.js +0 -524
- package/src/orchestrator/drivers/run-hu-batch.js +0 -468
- package/src/orchestrator/feedback-enrichment.js +0 -140
- package/src/orchestrator/feedback-queue.js +0 -151
- package/src/orchestrator/flow-control.js +0 -170
- package/src/orchestrator/flow-runner.js +0 -365
- package/src/orchestrator/fs-leak-detector.js +0 -228
- package/src/orchestrator/hu-scheduler.js +0 -114
- package/src/orchestrator/hu-sub-pipeline.js +0 -642
- package/src/orchestrator/iteration-gate.js +0 -73
- package/src/orchestrator/iteration-stages.js +0 -9
- package/src/orchestrator/parallel-limiter.js +0 -74
- package/src/orchestrator/pipeline-context.js +0 -72
- package/src/orchestrator/post-loop-stages.js +0 -468
- package/src/orchestrator/pre-loop-stages.js +0 -11
- package/src/orchestrator/preflight-checks.js +0 -487
- package/src/orchestrator/repair/repair-detector.js +0 -117
- package/src/orchestrator/repair/repair-runner.js +0 -142
- package/src/orchestrator/reviewer-fallback.js +0 -45
- package/src/orchestrator/role-output-compressor.js +0 -189
- package/src/orchestrator/session-journal.js +0 -308
- package/src/orchestrator/solomon-escalation.js +0 -331
- package/src/orchestrator/solomon-rules.js +0 -212
- package/src/orchestrator/stages/acceptance-coverage-stage.js +0 -47
- package/src/orchestrator/stages/architect-stage.js +0 -172
- package/src/orchestrator/stages/coder-stage.js +0 -506
- package/src/orchestrator/stages/domain-curator-stage.js +0 -82
- package/src/orchestrator/stages/format-stage.js +0 -61
- package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +0 -127
- package/src/orchestrator/stages/hu-reviewer-stage.js +0 -419
- package/src/orchestrator/stages/perf-stage.js +0 -140
- package/src/orchestrator/stages/planner-stage.js +0 -85
- package/src/orchestrator/stages/rag-context-stage.js +0 -80
- package/src/orchestrator/stages/rag-preload-decisor.js +0 -58
- package/src/orchestrator/stages/research-stage.js +0 -69
- package/src/orchestrator/stages/reviewer-stage.js +0 -431
- package/src/orchestrator/stages/sonar-stage.js +0 -439
- package/src/orchestrator/stages/stage-classes.js +0 -84
- package/src/orchestrator/stages/stage-executor.js +0 -155
- package/src/orchestrator/stages/suite-green-stage.js +0 -62
- package/src/orchestrator/stages/tdd-discipline-stage.js +0 -56
- package/src/orchestrator/stages/tool-judge-stage.js +0 -53
- package/src/orchestrator/stages/triage-stage.js +0 -172
- package/src/orchestrator/standby.js +0 -71
- package/src/orchestrator/verification-gate.js +0 -183
- package/src/orchestrator.js +0 -22
- package/src/plan/adr-generator.js +0 -170
- package/src/plan/adr-loader.js +0 -60
- package/src/plan/plan-executor.js +0 -100
- package/src/plan/plan-validation.js +0 -4
- package/src/prompts/acceptance-coverage.js +0 -32
- package/src/prompts/arbiter.js +0 -38
- package/src/prompts/architect.js +0 -103
- package/src/prompts/hu-reviewer.js +0 -244
- package/src/prompts/tool-correctness-judge.js +0 -31
- package/src/prompts/triage.js +0 -57
- package/src/rag/embedder.js +0 -2
- package/src/rag/embedders/_cloud-base.js +0 -2
- package/src/rag/embedders/cohere.js +0 -2
- package/src/rag/embedders/mistral.js +0 -2
- package/src/rag/embedders/onnx.js +0 -2
- package/src/rag/embedders/openai.js +0 -2
- package/src/rag/embedders/voyage.js +0 -2
- package/src/rag/rerank.js +0 -4
- package/src/rag/where-parser.js +0 -4
- package/src/repeat-detector.js +0 -88
- package/src/review/diff-generator.js +0 -112
- package/src/review/git-stash-helper.js +0 -104
- package/src/review/schema.js +0 -31
- package/src/review/scope-filter.js +0 -152
- package/src/review/snapshot-diff.js +0 -122
- package/src/review/tdd-discipline.js +0 -90
- package/src/review/tdd-policy.js +0 -69
- package/src/roles/acceptance-coverage-role.js +0 -78
- package/src/roles/architect-role.js +0 -77
- package/src/roles/coder-role.js +0 -113
- package/src/roles/commiter-role.js +0 -113
- package/src/roles/domain-curator-role.js +0 -134
- package/src/roles/hu-reviewer-role.js +0 -64
- package/src/roles/impeccable-role.js +0 -85
- package/src/roles/index.js +0 -18
- package/src/roles/karajan-brain-role.js +0 -134
- package/src/roles/perf-role.js +0 -122
- package/src/roles/planner-role.js +0 -151
- package/src/roles/refactorer-role.js +0 -30
- package/src/roles/repairer-role.js +0 -181
- package/src/roles/researcher-role.js +0 -70
- package/src/roles/reviewer-role.js +0 -115
- package/src/roles/security-role.js +0 -73
- package/src/roles/solomon-role.js +0 -271
- package/src/roles/sonar-role.js +0 -72
- package/src/roles/tester-role.js +0 -235
- package/src/roles/tool-correctness-judge-role.js +0 -78
- package/src/roles/triage-role.js +0 -99
- package/src/session/journal/decisions-writer.js +0 -200
- package/src/session/journal/iteration-logger.js +0 -194
- package/src/session/journal/summary-writer.js +0 -320
- package/src/session/journal/tree-writer.js +0 -164
- package/src/session/journal/write-all.js +0 -89
- package/src/session-cleanup.js +0 -116
- package/src/skills/semantic-detector.js +0 -167
- package/src/skills/skill-detector.js +0 -385
- package/src/sonar/cloud-scanner.js +0 -78
- package/src/tracker-bootstrap.js +0 -44
- package/src/types/index.js +0 -38
- package/src/types/policy.js +0 -43
- package/src/types/solomon.js +0 -87
- package/src/utils/async-lock.js +0 -31
- package/src/utils/budget.js +0 -229
- package/src/utils/cwd-confirm.js +0 -39
- package/src/utils/display/event-handlers.js +0 -403
- package/src/utils/display/formatters.js +0 -132
- package/src/utils/display/header.js +0 -47
- package/src/utils/display/session-summary.js +0 -118
- package/src/utils/display/solomon.js +0 -42
- package/src/utils/pricing.js +0 -29
- package/src/utils/quota-simulator.js +0 -56
- package/src/utils/role-assigner.js +0 -137
- package/src/utils/rtk-wrapper.js +0 -87
- package/src/utils/run-registry.js +0 -9
- package/src/utils/status-dashboard.js +0 -254
- package/src/utils/tool-call-extractor.js +0 -56
- package/src/webperf/devtools-detect.js +0 -112
- package/src/webperf/webperf-role-integration.js +0 -66
- package/templates/skills/kj-run.md +0 -73
package/src/git/hu-automation.js
DELETED
|
@@ -1,243 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Per-HU git automation: branch, commit, push, PR per HU story.
|
|
3
|
-
* Called from the HU sub-pipeline wrapper in orchestrator.js.
|
|
4
|
-
*/
|
|
5
|
-
import { commitAll, pushBranch, createPullRequest, hasChanges } from "../utils/git.js";
|
|
6
|
-
import { runCommand } from "../utils/process.js";
|
|
7
|
-
import { stampStagedVerdict } from "../review/verdict-store.js";
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* Build a git-safe branch name for an HU story.
|
|
11
|
-
* @param {string} prefix - e.g. "feat/"
|
|
12
|
-
* @param {object} story - HU story {id, title}
|
|
13
|
-
* @returns {string}
|
|
14
|
-
*/
|
|
15
|
-
export function buildHuBranchName(prefix, story) {
|
|
16
|
-
const baseSlug = String(story.title || story.id || "hu")
|
|
17
|
-
.toLowerCase()
|
|
18
|
-
.replaceAll(/[^a-z0-9]+/g, "-")
|
|
19
|
-
.replaceAll(/^-+|-+$/g, "")
|
|
20
|
-
.slice(0, 40);
|
|
21
|
-
return `${prefix}${story.id}-${baseSlug}`;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/**
|
|
25
|
-
* Resolve the base branch for a given HU based on its dependencies.
|
|
26
|
-
* If it has no blocked_by, uses config.base_branch.
|
|
27
|
-
* If it has parents, uses the last-created parent branch (assumes parents ran first).
|
|
28
|
-
*
|
|
29
|
-
* @param {object} story - the HU story
|
|
30
|
-
* @param {Map<string,string>} huBranches - map of huId → branchName (already created)
|
|
31
|
-
* @param {string} baseBranch - config.base_branch fallback
|
|
32
|
-
* @returns {string} base branch name
|
|
33
|
-
*/
|
|
34
|
-
export function resolveHuBase(story, huBranches, baseBranch) {
|
|
35
|
-
const parents = story.blocked_by || [];
|
|
36
|
-
if (parents.length === 0) return baseBranch;
|
|
37
|
-
// Walk parents in reverse order of declaration to find the most recent one
|
|
38
|
-
for (let i = parents.length - 1; i >= 0; i--) {
|
|
39
|
-
const parentBranch = huBranches.get(parents[i]);
|
|
40
|
-
if (parentBranch) return parentBranch;
|
|
41
|
-
}
|
|
42
|
-
return baseBranch;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* Resolve to an actually-existing local ref. Tries the preferred branch
|
|
47
|
-
* first, then `main` and `master`, then plain `HEAD` as last resort.
|
|
48
|
-
*
|
|
49
|
-
* Why: N6 plan-flow dogfooding (2026-05-07) on a `git init -q` repo
|
|
50
|
-
* with `init.defaultBranch=master` produced 7 identical warnings:
|
|
51
|
-
* `failed to create branch feat/...-001 from main:
|
|
52
|
-
* fatal: 'main' is not a commit and a branch can't be created`.
|
|
53
|
-
* Every HU then fell back to running on the original branch, which
|
|
54
|
-
* defeats the per-HU isolation the sub-pipeline was supposed to give.
|
|
55
|
-
*
|
|
56
|
-
* The configured base_branch is honoured when it exists; otherwise we
|
|
57
|
-
* pick the most likely default. HEAD always exists once the repo has
|
|
58
|
-
* any commit, so the fallback never throws past the gate.
|
|
59
|
-
*
|
|
60
|
-
* @param {string} preferred
|
|
61
|
-
* @returns {Promise<string>}
|
|
62
|
-
*/
|
|
63
|
-
async function resolveExistingBranchRef(preferred) {
|
|
64
|
-
const candidates = [preferred, "main", "master", "HEAD"]
|
|
65
|
-
.filter((c, i, arr) => c && arr.indexOf(c) === i); // unique, truthy
|
|
66
|
-
for (const ref of candidates) {
|
|
67
|
-
const probe = await runCommand("git", ["rev-parse", "--verify", "--quiet", ref], {});
|
|
68
|
-
if (probe.exitCode === 0) return ref;
|
|
69
|
-
}
|
|
70
|
-
return preferred; // give up; caller will surface the original error
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
/** Whether a branch name references the card, as a whole token (BB-002 is not BB-0022). */
|
|
74
|
-
export function branchReferencesCard(branch, cardId) {
|
|
75
|
-
const id = String(cardId).replaceAll(/[.*+?^${}()|[\]\\]/g, String.raw`\$&`);
|
|
76
|
-
return new RegExp(`(^|[^a-z0-9])${id}([^a-z0-9]|$)`, "i").test(String(branch || ""));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
async function cardBranch({ cardId, story, config, logger }) {
|
|
80
|
-
const head = await runCommand("git", ["rev-parse", "--abbrev-ref", "HEAD"], {});
|
|
81
|
-
const current = head.exitCode === 0 ? head.stdout.trim() : "";
|
|
82
|
-
if (branchReferencesCard(current, cardId)) {
|
|
83
|
-
logger.info(`HU ${story.id}: on '${current}', the branch of card ${cardId}`);
|
|
84
|
-
return current;
|
|
85
|
-
}
|
|
86
|
-
const branchName = buildHuBranchName(config.git?.branch_prefix || "feat/", { id: cardId, title: story.title });
|
|
87
|
-
// A card branch left by an earlier run keeps its commits: taken, never reset.
|
|
88
|
-
const exists = await runCommand("git", ["rev-parse", "--verify", "--quiet", branchName], {});
|
|
89
|
-
const base = exists.exitCode === 0 ? null : await resolveExistingBranchRef(config.base_branch || "main");
|
|
90
|
-
const how = base ? `created from '${base}'` : "taken";
|
|
91
|
-
const res = await runCommand("git", base ? ["checkout", "-b", branchName, base] : ["checkout", branchName], {});
|
|
92
|
-
if (res.exitCode !== 0) {
|
|
93
|
-
logger.warn(`HU git: branch ${branchName} could not be ${how}: ${res.stderr}`);
|
|
94
|
-
return null;
|
|
95
|
-
}
|
|
96
|
-
logger.info(`HU ${story.id}: branch '${branchName}' ${how} for card ${cardId}`);
|
|
97
|
-
return branchName;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
/**
|
|
101
|
-
* Create a branch for an HU starting from its resolved base.
|
|
102
|
-
* Returns the branch name created (or null if git automation is disabled).
|
|
103
|
-
*
|
|
104
|
-
* @param {object} params
|
|
105
|
-
* @param {object} params.story
|
|
106
|
-
* @param {Map} params.huBranches
|
|
107
|
-
* @param {object} params.config
|
|
108
|
-
* @param {object} params.logger
|
|
109
|
-
* @returns {Promise<string|null>}
|
|
110
|
-
*/
|
|
111
|
-
export async function prepareHuBranch({ story, huBranches, config, logger, cardId = null }) {
|
|
112
|
-
if (!config.git?.auto_commit && !config.git?.auto_push && !config.git?.auto_pr) {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
// KJC-BUG-0249 (#1896): with a Planning Game card the run is that card's
|
|
116
|
-
// work and lives on ONE branch named after it: the current one when it
|
|
117
|
-
// already references the card, else feat/<CARD-ID>-<slug>, created once
|
|
118
|
-
// and shared by every HU. HU ids name branches only when there is no card.
|
|
119
|
-
if (cardId) {
|
|
120
|
-
const known = huBranches.get(cardId);
|
|
121
|
-
const branch = known ?? (await cardBranch({ cardId, story, config, logger }));
|
|
122
|
-
if (!branch) return null;
|
|
123
|
-
huBranches.set(cardId, branch);
|
|
124
|
-
huBranches.set(story.id, branch);
|
|
125
|
-
return branch;
|
|
126
|
-
}
|
|
127
|
-
const baseBranch = resolveHuBase(story, huBranches, config.base_branch || "main");
|
|
128
|
-
const effectiveBase = await resolveExistingBranchRef(baseBranch);
|
|
129
|
-
if (effectiveBase !== baseBranch) {
|
|
130
|
-
logger.info(`HU git: configured base '${baseBranch}' not found locally — using '${effectiveBase}' instead`);
|
|
131
|
-
}
|
|
132
|
-
const prefix = config.git?.branch_prefix || "feat/";
|
|
133
|
-
const branchName = buildHuBranchName(prefix, story);
|
|
134
|
-
|
|
135
|
-
// Checkout from the resolved base. Use `git checkout -B` to overwrite if rerun.
|
|
136
|
-
const res = await runCommand("git", ["checkout", "-B", branchName, effectiveBase], {});
|
|
137
|
-
if (res.exitCode !== 0) {
|
|
138
|
-
logger.warn(`HU git: failed to create branch ${branchName} from ${effectiveBase}: ${res.stderr}`);
|
|
139
|
-
return null;
|
|
140
|
-
}
|
|
141
|
-
huBranches.set(story.id, branchName);
|
|
142
|
-
logger.info(`HU ${story.id}: branch '${branchName}' from '${effectiveBase}'`);
|
|
143
|
-
return branchName;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/**
|
|
147
|
-
* KJC-BUG-0248 (#1895): the remedy for a push git refused, read from its
|
|
148
|
-
* message. GitHub refuses a push that adds or changes a workflow file when
|
|
149
|
-
* the token lacks the `workflow` scope; that one has an exact fix.
|
|
150
|
-
*/
|
|
151
|
-
export function pushRemedy(message, branch) {
|
|
152
|
-
if (/workflow/i.test(message) && /scope/i.test(message)) {
|
|
153
|
-
return `the token lacks the workflow scope: run gh auth refresh -h github.com -s workflow, then git push origin ${branch}`;
|
|
154
|
-
}
|
|
155
|
-
return `push the branch yourself: git push origin ${branch}`;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/** The warnings a run reports when the push or the PR of any HU failed. */
|
|
159
|
-
export function collectGitWarnings(results) {
|
|
160
|
-
return results.flatMap((r) => (r.result?.git?.errors ?? []).map((e) => `${r.huId}: ${e.step} failed: ${e.message}. ${e.remedy}`));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
/**
|
|
164
|
-
* After an HU is approved, commit its changes and optionally push + create PR.
|
|
165
|
-
* A push or PR that fails is not only a warn line: it travels in `errors`
|
|
166
|
-
* with its remedy, so the run's result says it (KJC-BUG-0248).
|
|
167
|
-
*
|
|
168
|
-
* @param {object} params
|
|
169
|
-
* @param {object} params.story
|
|
170
|
-
* @param {string} params.branchName
|
|
171
|
-
* @param {object} params.config
|
|
172
|
-
* @param {object} params.logger
|
|
173
|
-
* @returns {Promise<{committed: boolean, pushed: boolean, prUrl: string|null, branch: string|null, errors: Array<{step: string, message: string, remedy: string}>}>}
|
|
174
|
-
*/
|
|
175
|
-
export async function finalizeHuCommit({ story, branchName, config, logger, cwd = null }) {
|
|
176
|
-
const result = { committed: false, pushed: false, prUrl: null, branch: branchName || null, errors: [] };
|
|
177
|
-
if (!branchName) return result;
|
|
178
|
-
|
|
179
|
-
const changed = await hasChanges(cwd);
|
|
180
|
-
if (!changed) {
|
|
181
|
-
logger.info(`HU ${story.id}: no changes to commit`);
|
|
182
|
-
return result;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const title = story.title || story.id;
|
|
186
|
-
const commitMsg = `feat(${story.id}): ${title}`;
|
|
187
|
-
if (config.git?.auto_commit) {
|
|
188
|
-
// KJC-BUG-0323: this commit runs after the HU's reviewer approved, so it
|
|
189
|
-
// records that verdict for the staged diff, as the other post-review commits do.
|
|
190
|
-
const commitRes = await commitAll(commitMsg, cwd, {
|
|
191
|
-
beforeCommit: ({ paths } = {}) => stampStagedVerdict({
|
|
192
|
-
projectDir: cwd || config.projectDir || process.cwd(),
|
|
193
|
-
paths,
|
|
194
|
-
reviewer: config.reviewer || "pipeline-reviewer",
|
|
195
|
-
summary: `kj run HU ${story.id}: reviewer approved`,
|
|
196
|
-
}),
|
|
197
|
-
});
|
|
198
|
-
if (commitRes) {
|
|
199
|
-
result.committed = true;
|
|
200
|
-
logger.info(`HU ${story.id}: committed on '${branchName}'`);
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
if (config.git?.auto_push && result.committed) {
|
|
205
|
-
try {
|
|
206
|
-
await pushBranch(branchName, cwd);
|
|
207
|
-
result.pushed = true;
|
|
208
|
-
logger.info(`HU ${story.id}: pushed '${branchName}'`);
|
|
209
|
-
} catch (err) {
|
|
210
|
-
const remedy = pushRemedy(err.message, branchName);
|
|
211
|
-
result.errors.push({ step: "push", message: err.message, remedy });
|
|
212
|
-
logger.warn(`HU ${story.id}: push failed: ${err.message} (${remedy})`);
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
if (config.git?.auto_pr && result.pushed) {
|
|
217
|
-
try {
|
|
218
|
-
const prBody = [
|
|
219
|
-
`## HU ${story.id}: ${title}`,
|
|
220
|
-
"",
|
|
221
|
-
story.certified?.text || "",
|
|
222
|
-
"",
|
|
223
|
-
"### Acceptance criteria",
|
|
224
|
-
...(story.acceptance_criteria || []).map(c => `- ${c}`)
|
|
225
|
-
].join("\n");
|
|
226
|
-
const url = await createPullRequest({
|
|
227
|
-
baseBranch: config.base_branch || "main",
|
|
228
|
-
branch: branchName,
|
|
229
|
-
title: commitMsg,
|
|
230
|
-
body: prBody,
|
|
231
|
-
cwd
|
|
232
|
-
});
|
|
233
|
-
result.prUrl = url;
|
|
234
|
-
logger.info(`HU ${story.id}: PR created ${url}`);
|
|
235
|
-
} catch (err) {
|
|
236
|
-
const remedy = `open the PR yourself: gh pr create --base ${config.base_branch || "main"} --head ${branchName}`;
|
|
237
|
-
result.errors.push({ step: "pr", message: err.message, remedy });
|
|
238
|
-
logger.warn(`HU ${story.id}: PR creation failed: ${err.message} (${remedy})`);
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
return result;
|
|
243
|
-
}
|
package/src/git/hu-snapshot.js
DELETED
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
// Shim de compatibilidad: hu-snapshot vive ahora en karajan-core.
|
|
2
|
-
// Mantenemos esta ruta para que callers internos del CLI (orchestrator,
|
|
3
|
-
// runners) sigan funcionando sin tocar imports.
|
|
4
|
-
export {
|
|
5
|
-
snapshotRefForHu,
|
|
6
|
-
createHuSnapshot,
|
|
7
|
-
hasHuSnapshot,
|
|
8
|
-
restoreHuSnapshot,
|
|
9
|
-
removeHuSnapshot,
|
|
10
|
-
} from "karajan-core/hu-snapshot";
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* KJC-BUG-0414 (#2606, from #2564): Solomon told the coder to drop a file from the commit as a
|
|
3
|
-
* mirror of another, but the other was a symlink to it: the link would be committed dangling.
|
|
4
|
-
* kj names the in-repo targets of the symlinks in a change, and a condition that takes one of
|
|
5
|
-
* them out of the commit is dropped before it reaches the coder.
|
|
6
|
-
*/
|
|
7
|
-
import { lstatSync, readlinkSync } from "node:fs";
|
|
8
|
-
import { dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
9
|
-
|
|
10
|
-
/** The in-repo target of each symlink among `paths` (relative to projectDir). */
|
|
11
|
-
export function symlinkTargets(projectDir, paths) {
|
|
12
|
-
const root = resolve(projectDir);
|
|
13
|
-
return paths.flatMap((link) => {
|
|
14
|
-
try {
|
|
15
|
-
const abs = join(root, link);
|
|
16
|
-
if (!lstatSync(abs).isSymbolicLink()) return [];
|
|
17
|
-
const target = relative(root, resolve(dirname(abs), readlinkSync(abs)));
|
|
18
|
-
const outside = target === ".." || target.startsWith(`..${sep}`) || isAbsolute(target);
|
|
19
|
-
return target && !outside ? [{ link, target: target.split(sep).join("/") }] : [];
|
|
20
|
-
} catch {
|
|
21
|
-
return [];
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
// A `*` matches within one path segment, as in the shell.
|
|
27
|
-
const segmentMatches = (pattern, name) => {
|
|
28
|
-
const parts = pattern.split("*");
|
|
29
|
-
if (parts.length === 1) return pattern === name;
|
|
30
|
-
if (!name.startsWith(parts[0]) || !name.endsWith(parts.at(-1))) return false;
|
|
31
|
-
let at = parts[0].length;
|
|
32
|
-
for (const part of parts.slice(1, -1)) {
|
|
33
|
-
const found = name.indexOf(part, at);
|
|
34
|
-
if (found < 0) return false;
|
|
35
|
-
at = found + part.length;
|
|
36
|
-
}
|
|
37
|
-
return at <= name.length - parts.at(-1).length;
|
|
38
|
-
};
|
|
39
|
-
const globMatches = (pattern, path) => {
|
|
40
|
-
const p = pattern.split("/");
|
|
41
|
-
const t = path.split("/");
|
|
42
|
-
return p.length === t.length && p.every((seg, i) => segmentMatches(seg, t[i]));
|
|
43
|
-
};
|
|
44
|
-
const REMOVES = /\b(unstage|remove|drop|exclude|delete|restore|reset|revert|rm|untrack|quitar|sacar|eliminar|excluir|borrar)\b/i;
|
|
45
|
-
// Quoted strings are one token (a path may hold spaces); bare tokens lose surrounding punctuation.
|
|
46
|
-
const TOKENS = /"([^"]+)"|'([^']+)'|`([^`]+)`|(\S+)/g;
|
|
47
|
-
const tokensOf = (text) => [...String(text).matchAll(TOKENS)]
|
|
48
|
-
.map((m) => (m[1] ?? m[2] ?? m[3] ?? m[4]).replace(/^[([{]+/, "").replace(/[)\]}:;,]+$/, ""));
|
|
49
|
-
// Named literally anywhere in the text (any character git allows), or by a glob token.
|
|
50
|
-
const names = (condition, target) => String(condition).includes(target) || tokensOf(condition).some((t) => globMatches(t, target));
|
|
51
|
-
|
|
52
|
-
/** Splits Solomon's conditions: the ones that would take a symlink's target out of the commit go to `dropped`. */
|
|
53
|
-
export function keepSymlinkTargets(conditions, targets) {
|
|
54
|
-
const takesTargetOut = (condition) => REMOVES.test(condition) && targets.some(({ target }) => names(condition, target));
|
|
55
|
-
const dropped = targets.length ? conditions.filter(takesTargetOut) : [];
|
|
56
|
-
return { kept: conditions.filter((c) => !dropped.includes(c)), dropped };
|
|
57
|
-
}
|
package/src/golden/asserter.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Golden tasks asserter (KJC-TSK-0374). Parses a `summary.md` produced
|
|
3
|
-
* by a real `kj run` and compares it against a GoldenTask's structural
|
|
4
|
-
* expectations. Pure: no I/O, no subprocess. Subprocess driver lands
|
|
5
|
-
* in PR-3a; the 3 fixtures + baseline + spec land in PR-3b.
|
|
6
|
-
*
|
|
7
|
-
* Three of the five assertion families live here (the ones derivable
|
|
8
|
-
* from summary.md alone): expected_commits_min, expected_audit_status,
|
|
9
|
-
* expected_plan_adherence_min. expected_test_files and
|
|
10
|
-
* allowed_loc_range need filesystem access — PR-3a wires those.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { escapeRegExp } from "../utils/escape-regexp.js";
|
|
14
|
-
|
|
15
|
-
const COMMIT_LINE = /^- `([^`]+)`\s+(.*)$/;
|
|
16
|
-
const SCORE_LINE = /^\*\*Score\*\*:\s*(\d+)\s*\/\s*100/m;
|
|
17
|
-
const AUDIT_ROW = /^\|\s*`audit`\s*\|\s*([^|]+?)\s*\|/m;
|
|
18
|
-
|
|
19
|
-
function extractSection(content, heading) {
|
|
20
|
-
const re = new RegExp(`^## ${escapeRegExp(heading)}\\s*$([\\s\\S]*?)(?=^## |\\Z)`, "m");
|
|
21
|
-
const m = content.match(re);
|
|
22
|
-
return m ? m[1].trim() : "";
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
function parseCommits(content) {
|
|
26
|
-
const section = extractSection(content, "Commits");
|
|
27
|
-
if (!section || /No commits/.test(section)) return [];
|
|
28
|
-
return section
|
|
29
|
-
.split("\n")
|
|
30
|
-
.map((line) => line.match(COMMIT_LINE))
|
|
31
|
-
.filter(Boolean)
|
|
32
|
-
.map(([, hash, message]) => ({ hash, message }));
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function parseAuditStatus(content) {
|
|
36
|
-
const m = content.match(AUDIT_ROW);
|
|
37
|
-
if (!m) return null;
|
|
38
|
-
const cell = m[1];
|
|
39
|
-
if (/✅|pass/i.test(cell)) return "pass";
|
|
40
|
-
if (/❌|fail/i.test(cell)) return "fail";
|
|
41
|
-
if (/⚠|warn/i.test(cell)) return "warning";
|
|
42
|
-
return null;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
function parsePlanAdherence(content) {
|
|
46
|
-
const section = extractSection(content, "Plan adherence");
|
|
47
|
-
if (!section) return null;
|
|
48
|
-
const m = section.match(SCORE_LINE);
|
|
49
|
-
if (!m) return null;
|
|
50
|
-
const score = Number(m[1]);
|
|
51
|
-
return Number.isFinite(score) ? score : null;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export function parseSummary(content) {
|
|
55
|
-
const text = String(content || "");
|
|
56
|
-
return {
|
|
57
|
-
commits: parseCommits(text),
|
|
58
|
-
auditStatus: parseAuditStatus(text),
|
|
59
|
-
planAdherenceScore: parsePlanAdherence(text),
|
|
60
|
-
};
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
export function assertFromSummary(task, parsed) {
|
|
64
|
-
const failures = [];
|
|
65
|
-
|
|
66
|
-
const fail = (kind, expected, actual, message) => failures.push({ kind, expected, actual, message });
|
|
67
|
-
|
|
68
|
-
if (parsed.commits.length < task.expected_commits_min) {
|
|
69
|
-
fail("commits_min", `>= ${task.expected_commits_min}`, parsed.commits.length,
|
|
70
|
-
`expected at least ${task.expected_commits_min} commit(s); got ${parsed.commits.length}`);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
if (parsed.auditStatus !== task.expected_audit_status) {
|
|
74
|
-
const got = parsed.auditStatus === null ? "no audit row in summary" : `"${parsed.auditStatus}"`;
|
|
75
|
-
fail("audit_status", task.expected_audit_status, parsed.auditStatus,
|
|
76
|
-
`expected audit "${task.expected_audit_status}"; got ${got}`);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
if (typeof task.expected_plan_adherence_min === "number") {
|
|
80
|
-
const score = parsed.planAdherenceScore;
|
|
81
|
-
const min = task.expected_plan_adherence_min;
|
|
82
|
-
if (score === null) {
|
|
83
|
-
fail("plan_adherence_missing", `>= ${min}`, null,
|
|
84
|
-
`expected plan adherence >= ${min}; summary has no Plan adherence section`);
|
|
85
|
-
} else if (score < min) {
|
|
86
|
-
fail("plan_adherence_below", `>= ${min}`, score,
|
|
87
|
-
`expected plan adherence >= ${min}; got ${score}`);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
return { ok: failures.length === 0, failures };
|
|
92
|
-
}
|
package/src/golden/loader.js
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Golden tasks loader (KJC-TSK-0374). Reads + validates JSON files
|
|
3
|
-
* under a directory; aggregates errors; rejects duplicate ids.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import fs from "node:fs/promises";
|
|
7
|
-
import path from "node:path";
|
|
8
|
-
import { safeParseGoldenTask } from "./schema.js";
|
|
9
|
-
|
|
10
|
-
export async function loadGoldenTasks(dir) {
|
|
11
|
-
const entries = await fs.readdir(dir, { withFileTypes: true }).catch((err) => {
|
|
12
|
-
throw new Error(`golden tasks directory not readable: ${dir} (${err.code || err.message})`);
|
|
13
|
-
});
|
|
14
|
-
const jsonFiles = entries
|
|
15
|
-
.filter((e) => e.isFile() && e.name.endsWith(".json"))
|
|
16
|
-
.map((e) => path.join(dir, e.name))
|
|
17
|
-
.sort();
|
|
18
|
-
|
|
19
|
-
const tasks = [];
|
|
20
|
-
const errors = [];
|
|
21
|
-
const seenIds = new Map();
|
|
22
|
-
|
|
23
|
-
for (const filePath of jsonFiles) {
|
|
24
|
-
let raw;
|
|
25
|
-
try {
|
|
26
|
-
const content = await fs.readFile(filePath, "utf8");
|
|
27
|
-
raw = JSON.parse(content);
|
|
28
|
-
} catch (err) {
|
|
29
|
-
errors.push(`${path.basename(filePath)}: invalid JSON (${err.message})`);
|
|
30
|
-
continue;
|
|
31
|
-
}
|
|
32
|
-
const result = safeParseGoldenTask(raw);
|
|
33
|
-
if (!result.ok) {
|
|
34
|
-
const summary = result.issues.map((iss) => ` - ${iss.path || "(root)"}: ${iss.message}`).join("\n");
|
|
35
|
-
errors.push(`${path.basename(filePath)}:\n${summary}`);
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
const existing = seenIds.get(result.task.id);
|
|
39
|
-
if (existing) {
|
|
40
|
-
errors.push(`${path.basename(filePath)}: duplicate id "${result.task.id}" (also in ${existing})`);
|
|
41
|
-
continue;
|
|
42
|
-
}
|
|
43
|
-
seenIds.set(result.task.id, path.basename(filePath));
|
|
44
|
-
tasks.push(result.task);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
if (errors.length > 0) {
|
|
48
|
-
throw new Error(`golden tasks failed validation:\n${errors.join("\n")}`);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
return { tasks };
|
|
52
|
-
}
|
package/src/golden/runner.js
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Golden tasks subprocess runner (KJC-TSK-0374). Spawns `kj run` in
|
|
3
|
-
* a sandbox dir, reads back summary.md, evaluates the schema's
|
|
4
|
-
* filesystem-dependent assertions (expected_test_files, allowed_loc_range),
|
|
5
|
-
* and merges them with `assertFromSummary` into a single result.
|
|
6
|
-
*
|
|
7
|
-
* The kj subprocess + diff calls are injected so unit tests can drive
|
|
8
|
-
* the runner without spawning anything (real e2e is opt-in via PR-3b).
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
import fs from "node:fs/promises";
|
|
12
|
-
import path from "node:path";
|
|
13
|
-
import { runCommand as defaultRun } from "../utils/process.js";
|
|
14
|
-
import { parseSummary, assertFromSummary } from "./asserter.js";
|
|
15
|
-
|
|
16
|
-
async function findLatestSummary(workDir) {
|
|
17
|
-
const journalRoot = path.join(workDir, ".karajan", "sessions");
|
|
18
|
-
const sessions = await fs.readdir(journalRoot).catch(() => []);
|
|
19
|
-
if (sessions.length === 0) return null;
|
|
20
|
-
const stats = await Promise.all(sessions.map(async (id) => {
|
|
21
|
-
const file = path.join(journalRoot, id, "summary.md");
|
|
22
|
-
const stat = await fs.stat(file).catch(() => null);
|
|
23
|
-
return stat ? { file, mtimeMs: stat.mtimeMs } : null;
|
|
24
|
-
}));
|
|
25
|
-
const valid = stats.filter(Boolean).sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
26
|
-
return valid[0]?.file ?? null;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async function findMissingTestFiles(workDir, paths) {
|
|
30
|
-
const checks = await Promise.all(paths.map(async (rel) =>
|
|
31
|
-
[rel, await fs.stat(path.join(workDir, rel)).then(() => true).catch(() => false)]));
|
|
32
|
-
return checks.filter(([, ok]) => !ok).map(([rel]) => rel);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
async function totalLocSince(workDir, baseSha, run) {
|
|
36
|
-
const res = await run("git", ["diff", "--numstat", `${baseSha}..HEAD`], { cwd: workDir }).catch(() => null);
|
|
37
|
-
if (!res || res.exitCode !== 0) return null;
|
|
38
|
-
return String(res.stdout || "").split("\n").filter(Boolean)
|
|
39
|
-
.map((l) => l.split("\t")).filter(([a]) => a !== "-")
|
|
40
|
-
.reduce((sum, [a]) => sum + Number(a || 0), 0);
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
export async function runGoldenTask(task, opts = {}) {
|
|
44
|
-
const run = opts.run || defaultRun;
|
|
45
|
-
const fsApi = opts.fs || fs;
|
|
46
|
-
const workDir = opts.workDir;
|
|
47
|
-
if (!workDir) throw new Error("runGoldenTask requires opts.workDir");
|
|
48
|
-
|
|
49
|
-
const baseSha = opts.baseSha || null;
|
|
50
|
-
const kjResult = await run("kj", ["run", task.prompt, "-y"], {
|
|
51
|
-
cwd: workDir,
|
|
52
|
-
timeout: (task.timeout_minutes || 30) * 60_000,
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
const summaryPath = opts.summaryPath || await findLatestSummary(workDir);
|
|
56
|
-
if (!summaryPath) {
|
|
57
|
-
return { ok: false, kjExit: kjResult?.exitCode ?? -1, failures: [{
|
|
58
|
-
kind: "no_summary", expected: "summary.md", actual: null,
|
|
59
|
-
message: "kj run finished without producing a summary.md",
|
|
60
|
-
}] };
|
|
61
|
-
}
|
|
62
|
-
const content = await fsApi.readFile(summaryPath, "utf8");
|
|
63
|
-
const parsed = parseSummary(content);
|
|
64
|
-
const summaryAssertion = assertFromSummary(task, parsed);
|
|
65
|
-
|
|
66
|
-
const failures = [...summaryAssertion.failures];
|
|
67
|
-
|
|
68
|
-
if (Array.isArray(task.expected_test_files) && task.expected_test_files.length > 0) {
|
|
69
|
-
const missing = await findMissingTestFiles(workDir, task.expected_test_files);
|
|
70
|
-
if (missing.length > 0) {
|
|
71
|
-
failures.push({ kind: "test_files_missing", expected: task.expected_test_files, actual: missing,
|
|
72
|
-
message: `expected test files not produced: ${missing.join(", ")}` });
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
if (baseSha) {
|
|
77
|
-
const loc = await totalLocSince(workDir, baseSha, run);
|
|
78
|
-
const [min, max] = task.allowed_loc_range;
|
|
79
|
-
if (loc !== null && (loc < min || loc > max)) {
|
|
80
|
-
failures.push({ kind: "loc_out_of_range", expected: `${min}..${max}`, actual: loc,
|
|
81
|
-
message: `LOC added (${loc}) is outside the allowed range [${min}, ${max}]` });
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
return {
|
|
86
|
-
ok: failures.length === 0,
|
|
87
|
-
kjExit: kjResult?.exitCode ?? null,
|
|
88
|
-
summaryPath,
|
|
89
|
-
parsed,
|
|
90
|
-
failures,
|
|
91
|
-
};
|
|
92
|
-
}
|
package/src/golden/schema.js
DELETED
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Golden tasks regression suite — schema (KJC-TSK-0374). Pure data,
|
|
3
|
-
* no I/O. See docs/golden-tasks.md (PR-3) for the full spec.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import * as v from "valibot";
|
|
7
|
-
|
|
8
|
-
const NonEmptyString = v.pipe(v.string(), v.minLength(1));
|
|
9
|
-
const Percent = v.pipe(v.number(), v.minValue(0), v.maxValue(100));
|
|
10
|
-
|
|
11
|
-
const NonNegInt = v.pipe(v.number(), v.integer(), v.minValue(0));
|
|
12
|
-
const LocRangeSchema = v.pipe(v.array(NonNegInt), v.length(2),
|
|
13
|
-
v.check((arr) => arr[0] <= arr[1], "allowed_loc_range: min must be <= max"));
|
|
14
|
-
|
|
15
|
-
const GoldenTaskSchema = v.looseObject({
|
|
16
|
-
id: v.pipe(NonEmptyString, v.regex(/^[a-z0-9_-]+$/, "id must be lowercase a-z0-9_-")),
|
|
17
|
-
title: NonEmptyString,
|
|
18
|
-
prompt: NonEmptyString,
|
|
19
|
-
description: v.optional(v.string()),
|
|
20
|
-
timeout_minutes: v.optional(v.pipe(v.number(), v.integer(), v.minValue(1)), 30),
|
|
21
|
-
expected_commits_min: NonNegInt,
|
|
22
|
-
expected_audit_status: v.picklist(["pass", "warning", "fail"]),
|
|
23
|
-
expected_test_files: v.array(NonEmptyString),
|
|
24
|
-
allowed_loc_range: LocRangeSchema,
|
|
25
|
-
expected_plan_adherence_min: v.optional(Percent),
|
|
26
|
-
});
|
|
27
|
-
|
|
28
|
-
/** @typedef {import("valibot").InferOutput<typeof GoldenTaskSchema>} GoldenTask */
|
|
29
|
-
|
|
30
|
-
export function parseGoldenTask(raw) {
|
|
31
|
-
return v.parse(GoldenTaskSchema, raw);
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export function safeParseGoldenTask(raw) {
|
|
35
|
-
const result = v.safeParse(GoldenTaskSchema, raw);
|
|
36
|
-
if (result.success) return { ok: true, task: result.output };
|
|
37
|
-
return { ok: false, issues: result.issues.map((iss) => ({
|
|
38
|
-
path: (iss.path || []).map((p) => p.key).join("."),
|
|
39
|
-
message: iss.message,
|
|
40
|
-
})) };
|
|
41
|
-
}
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Duplicate class members (KJC-TSK-0796) — a bug JavaScript refuses to report.
|
|
3
|
-
*
|
|
4
|
-
* Declaring the same member twice in a class is legal: the LAST one wins and the
|
|
5
|
-
* first disappears in silence. No runtime error, no lint warning, no compiler
|
|
6
|
-
* complaint. That is how a `updated()` that loaded the data was lost in GREBLA
|
|
7
|
-
* and a tab stayed empty in production for 17 days.
|
|
8
|
-
*
|
|
9
|
-
* What matters is not that there is a duplicate — it is WHAT WAS LOST: the body
|
|
10
|
-
* that will never run. That is what the finding says.
|
|
11
|
-
*
|
|
12
|
-
* Honest about its limits (the fourth verdict): a file this guard could not read,
|
|
13
|
-
* or one whose member names are computed at runtime, is reported as NOT OBSERVABLE
|
|
14
|
-
* — never as clean.
|
|
15
|
-
*/
|
|
16
|
-
import { parse } from "@babel/parser";
|
|
17
|
-
|
|
18
|
-
const plugins = (file) => [
|
|
19
|
-
...(/\.(ts|tsx|mts|cts)$/.test(file) ? ["typescript"] : []),
|
|
20
|
-
...(/\.(tsx|jsx)$/.test(file) || !/\.(ts|mts|cts)$/.test(file) ? ["jsx"] : []),
|
|
21
|
-
"decorators",
|
|
22
|
-
];
|
|
23
|
-
const isClass = (node) => node?.type === "ClassDeclaration" || node?.type === "ClassExpression";
|
|
24
|
-
|
|
25
|
-
/** Walks every node of the tree; enough for a syntax-only guard (no scope analysis). */
|
|
26
|
-
function walk(node, visit) {
|
|
27
|
-
if (!node || typeof node.type !== "string") return;
|
|
28
|
-
visit(node);
|
|
29
|
-
for (const key of Object.keys(node)) {
|
|
30
|
-
const child = node[key];
|
|
31
|
-
if (Array.isArray(child)) child.forEach((c) => walk(c, visit));
|
|
32
|
-
else if (child && typeof child.type === "string") walk(child, visit);
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const memberName = (member) => {
|
|
37
|
-
if (member.computed) return null; // `[expr]() {}` — the name is not known until it runs
|
|
38
|
-
const key = member.key ?? member.id;
|
|
39
|
-
if (key?.type === "Identifier") return key.name;
|
|
40
|
-
if (key?.type === "PrivateName") return `#${key.id.name}`;
|
|
41
|
-
if (key?.type === "StringLiteral" || key?.type === "NumericLiteral") return String(key.value);
|
|
42
|
-
return null;
|
|
43
|
-
};
|
|
44
|
-
|
|
45
|
-
/**
|
|
46
|
-
* @param {string} source
|
|
47
|
-
* @param {{file?: string}} [where]
|
|
48
|
-
* @returns {{findings: Array<object>, notObservable: {file: string, reason: string}|null}}
|
|
49
|
-
* Each finding: {file, className, member, lostLine, winnerLine, message}.
|
|
50
|
-
*/
|
|
51
|
-
export function findDuplicateMembers(source, { file = "<source>" } = {}) {
|
|
52
|
-
let tree;
|
|
53
|
-
try {
|
|
54
|
-
tree = parse(source, { sourceType: "unambiguous", allowReturnOutsideFunction: true, plugins: plugins(file) });
|
|
55
|
-
} catch (err) {
|
|
56
|
-
return { findings: [], notObservable: { file, reason: `could not be parsed (${err.message}) — not read as clean` } };
|
|
57
|
-
}
|
|
58
|
-
const findings = [];
|
|
59
|
-
let computed = 0;
|
|
60
|
-
walk(tree, (node) => {
|
|
61
|
-
if (!isClass(node)) return;
|
|
62
|
-
const className = node.id?.name ?? "(anonymous)";
|
|
63
|
-
const seen = new Map();
|
|
64
|
-
for (const member of node.body.body) {
|
|
65
|
-
// get/set of the same name are legal, and a static member is a different slot from an
|
|
66
|
-
// instance one — treating either as a duplicate would be the false positive that gets a
|
|
67
|
-
// guard switched off (the credibility rule of KJC-PCS-0082).
|
|
68
|
-
// A TypeScript overload signature (TSDeclareMethod) declares the same name on purpose and
|
|
69
|
-
// carries no body: counting it would flag every overloaded method in the codebase.
|
|
70
|
-
if (member.type === "StaticBlock" || member.type === "TSDeclareMethod" || member.type === "TSIndexSignature" || member.kind === "get" || member.kind === "set") continue;
|
|
71
|
-
const name = memberName(member);
|
|
72
|
-
if (name === null) { computed += 1; continue; }
|
|
73
|
-
const key = `${member.static ? "static " : ""}${name}`;
|
|
74
|
-
const line = member.loc.start.line;
|
|
75
|
-
const first = seen.get(key);
|
|
76
|
-
if (first === undefined) { seen.set(key, line); continue; }
|
|
77
|
-
findings.push({
|
|
78
|
-
file, className, member: key, lostLine: first, winnerLine: line,
|
|
79
|
-
message: `${file}:${line} — ${className}: the ${key} of line ${first} never runs, the one of line ${line} replaces it`,
|
|
80
|
-
});
|
|
81
|
-
seen.set(key, line); // a third copy is reported against the second, not against the first
|
|
82
|
-
}
|
|
83
|
-
});
|
|
84
|
-
const notObservable = computed > 0 && findings.length === 0 ? { file, reason: `${computed} member name(s) computed at runtime — this file cannot be fully checked` } : null;
|
|
85
|
-
return { findings, notObservable };
|
|
86
|
-
}
|