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/hu/store.js
DELETED
|
@@ -1,394 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HU (Historia de Usuario / User Story) storage. Persists HU batches and
|
|
3
|
-
* their stories to the session directory.
|
|
4
|
-
*
|
|
5
|
-
* @typedef {import("../types/hu.js").HUStory} HUStory
|
|
6
|
-
* @typedef {import("../types/hu.js").HUBatch} HUBatch
|
|
7
|
-
* @typedef {import("../types/hu.js").HUStatus} HUStatus
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import fs from "node:fs/promises";
|
|
11
|
-
import path from "node:path";
|
|
12
|
-
import { execFile } from "node:child_process";
|
|
13
|
-
import { promisify } from "node:util";
|
|
14
|
-
|
|
15
|
-
const execFileAsync = promisify(execFile);
|
|
16
|
-
import { getKarajanHome } from "../utils/paths.js";
|
|
17
|
-
|
|
18
|
-
// FUTURE: hu-storage adapter for PG/Trello/etc — currently local files only
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* Valid HU story status values.
|
|
22
|
-
* @type {Readonly<{PENDING: "pending", CODING: "coding", REVIEWING: "reviewing", DONE: "done", FAILED: "failed", BLOCKED: "blocked", CERTIFIED: "certified", NEEDS_CONTEXT: "needs_context"}>}
|
|
23
|
-
*/
|
|
24
|
-
export const HU_STATUS = Object.freeze({
|
|
25
|
-
PENDING: "pending",
|
|
26
|
-
CODING: "coding",
|
|
27
|
-
REVIEWING: "reviewing",
|
|
28
|
-
DONE: "done",
|
|
29
|
-
FAILED: "failed",
|
|
30
|
-
BLOCKED: "blocked",
|
|
31
|
-
CERTIFIED: "certified",
|
|
32
|
-
NEEDS_CONTEXT: "needs_context"
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
/** @returns {string} Path to the hu-stories directory (evaluated at call time). */
|
|
36
|
-
function getHuDir() {
|
|
37
|
-
return path.join(getKarajanHome(), "hu-stories");
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/**
|
|
41
|
-
* Create a new HU batch from an array of story definitions.
|
|
42
|
-
* @param {string} sessionId - The session identifier.
|
|
43
|
-
* @param {Array<{id?: string, text: string, blocked_by?: string[]}>} stories - Raw story inputs.
|
|
44
|
-
* @returns {Promise<object>} The created batch object.
|
|
45
|
-
*/
|
|
46
|
-
export async function createHuBatch(sessionId, stories) {
|
|
47
|
-
const dir = path.join(getHuDir(), sessionId);
|
|
48
|
-
await fs.mkdir(dir, { recursive: true });
|
|
49
|
-
|
|
50
|
-
const batch = {
|
|
51
|
-
session_id: sessionId,
|
|
52
|
-
created_at: new Date().toISOString(),
|
|
53
|
-
stories: stories.map((s, i) => ({
|
|
54
|
-
id: s.id || `HU-${Date.now()}-${i}`,
|
|
55
|
-
status: "pending",
|
|
56
|
-
original: { text: s.text },
|
|
57
|
-
blocked_by: s.blocked_by || [],
|
|
58
|
-
// KJC-TSK-0698: the tier travels with the HU; a story without one (single auto-story) carries none.
|
|
59
|
-
...(s.complexity ? { complexity: s.complexity } : {}),
|
|
60
|
-
certified: null,
|
|
61
|
-
quality: null,
|
|
62
|
-
context_requests: [],
|
|
63
|
-
created_at: new Date().toISOString(),
|
|
64
|
-
updated_at: new Date().toISOString()
|
|
65
|
-
}))
|
|
66
|
-
};
|
|
67
|
-
|
|
68
|
-
await fs.writeFile(path.join(dir, "batch.json"), JSON.stringify(batch, null, 2));
|
|
69
|
-
return batch;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/**
|
|
73
|
-
* Load an existing HU batch from disk.
|
|
74
|
-
* @param {string} sessionId - The session identifier.
|
|
75
|
-
* @returns {Promise<object>} The loaded batch object.
|
|
76
|
-
*/
|
|
77
|
-
export async function loadHuBatch(sessionId) {
|
|
78
|
-
const file = path.join(getHuDir(), sessionId, "batch.json");
|
|
79
|
-
const raw = await fs.readFile(file, "utf8");
|
|
80
|
-
return JSON.parse(raw);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Save a batch back to disk.
|
|
85
|
-
*
|
|
86
|
-
* Ensures the target directory exists before writing — the previous
|
|
87
|
-
* implementation assumed createHuBatch had already done the mkdir,
|
|
88
|
-
* which holds for the auto-HU path but NOT for plan-driven runs:
|
|
89
|
-
* `kj run --plan <id>` derives a batchSessionId from the plan id,
|
|
90
|
-
* loads the (non-existent) batch.json, falls back to in-memory, then
|
|
91
|
-
* crashes here with ENOENT on the first save attempt because no one
|
|
92
|
-
* created `~/.karajan/hu-stories/<sessionId>/`. The fix is one
|
|
93
|
-
* mkdir -p; cheap and idempotent.
|
|
94
|
-
*
|
|
95
|
-
* @param {string} sessionId - The session identifier.
|
|
96
|
-
* @param {object} batch - The batch object to persist.
|
|
97
|
-
* @returns {Promise<void>}
|
|
98
|
-
*/
|
|
99
|
-
export async function saveHuBatch(sessionId, batch) {
|
|
100
|
-
const dir = path.join(getHuDir(), sessionId);
|
|
101
|
-
await fs.mkdir(dir, { recursive: true });
|
|
102
|
-
batch.updated_at = new Date().toISOString();
|
|
103
|
-
await fs.writeFile(path.join(dir, "batch.json"), JSON.stringify(batch, null, 2));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Update the status of a single story within a batch.
|
|
108
|
-
* @param {object} batch - The batch object.
|
|
109
|
-
* @param {string} storyId - Story ID to update.
|
|
110
|
-
* @param {string} status - New status value.
|
|
111
|
-
* @param {object} [extra={}] - Additional fields to merge.
|
|
112
|
-
* @returns {object} The updated story.
|
|
113
|
-
*/
|
|
114
|
-
export function updateStoryStatus(batch, storyId, status, extra = {}) {
|
|
115
|
-
const story = batch.stories.find(s => s.id === storyId);
|
|
116
|
-
if (!story) throw new Error(`Story ${storyId} not found`);
|
|
117
|
-
story.status = status;
|
|
118
|
-
const now = new Date().toISOString();
|
|
119
|
-
story.statusChangedAt = now;
|
|
120
|
-
story.updated_at = now;
|
|
121
|
-
Object.assign(story, extra);
|
|
122
|
-
return story;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
/**
|
|
126
|
-
* Store quality scores on a story.
|
|
127
|
-
* @param {object} batch - The batch object.
|
|
128
|
-
* @param {string} storyId - Story ID.
|
|
129
|
-
* @param {object} quality - Quality scores object.
|
|
130
|
-
* @returns {object} The updated story.
|
|
131
|
-
*/
|
|
132
|
-
export function updateStoryQuality(batch, storyId, quality) {
|
|
133
|
-
const story = batch.stories.find(s => s.id === storyId);
|
|
134
|
-
if (!story) throw new Error(`Story ${storyId} not found`);
|
|
135
|
-
story.quality = { ...quality, evaluated_at: new Date().toISOString() };
|
|
136
|
-
story.updated_at = new Date().toISOString();
|
|
137
|
-
return story;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
/**
|
|
141
|
-
* Mark a story as certified with the provided certified data.
|
|
142
|
-
* @param {object} batch - The batch object.
|
|
143
|
-
* @param {string} storyId - Story ID.
|
|
144
|
-
* @param {object} certified - Certified HU data.
|
|
145
|
-
* @returns {object} The updated story.
|
|
146
|
-
*/
|
|
147
|
-
export function updateStoryCertified(batch, storyId, certified) {
|
|
148
|
-
const story = batch.stories.find(s => s.id === storyId);
|
|
149
|
-
if (!story) throw new Error(`Story ${storyId} not found`);
|
|
150
|
-
story.certified = certified;
|
|
151
|
-
story.status = "certified";
|
|
152
|
-
story.updated_at = new Date().toISOString();
|
|
153
|
-
return story;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/**
|
|
157
|
-
* Add a context request to a story and set its status to needs_context.
|
|
158
|
-
* @param {object} batch - The batch object.
|
|
159
|
-
* @param {string} storyId - Story ID.
|
|
160
|
-
* @param {{fields_needed: string[], question: string}} request - Context request.
|
|
161
|
-
* @returns {object} The updated story.
|
|
162
|
-
*/
|
|
163
|
-
export function addContextRequest(batch, storyId, request) {
|
|
164
|
-
const story = batch.stories.find(s => s.id === storyId);
|
|
165
|
-
if (!story) throw new Error(`Story ${storyId} not found`);
|
|
166
|
-
story.context_requests.push({
|
|
167
|
-
requested_at: new Date().toISOString(),
|
|
168
|
-
fields_needed: request.fields_needed,
|
|
169
|
-
question_to_fde: request.question,
|
|
170
|
-
answered_at: null,
|
|
171
|
-
answer: null
|
|
172
|
-
});
|
|
173
|
-
story.status = "needs_context";
|
|
174
|
-
story.updated_at = new Date().toISOString();
|
|
175
|
-
return story;
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
/**
|
|
179
|
-
* Answer the most recent pending context request and reset status to pending.
|
|
180
|
-
* @param {object} batch - The batch object.
|
|
181
|
-
* @param {string} storyId - Story ID.
|
|
182
|
-
* @param {string} answer - The FDE's answer.
|
|
183
|
-
* @returns {object} The updated story.
|
|
184
|
-
*/
|
|
185
|
-
export function answerContextRequest(batch, storyId, answer) {
|
|
186
|
-
const story = batch.stories.find(s => s.id === storyId);
|
|
187
|
-
if (!story) throw new Error(`Story ${storyId} not found`);
|
|
188
|
-
const pending = story.context_requests.find(r => !r.answered_at);
|
|
189
|
-
if (pending) {
|
|
190
|
-
pending.answered_at = new Date().toISOString();
|
|
191
|
-
pending.answer = answer;
|
|
192
|
-
}
|
|
193
|
-
story.status = "pending"; // back to pending for re-evaluation
|
|
194
|
-
story.updated_at = new Date().toISOString();
|
|
195
|
-
return story;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/**
|
|
199
|
-
* Create a lightweight history record for a pipeline run.
|
|
200
|
-
* Stores a minimal single-HU batch in the same hu-stories directory
|
|
201
|
-
* so the HU Board can pick it up alongside full batches.
|
|
202
|
-
* @param {string} sessionId - The session identifier.
|
|
203
|
-
* @param {{task: string, result: string, approved: boolean, summary?: string, timestamp?: string}} data
|
|
204
|
-
* @returns {Promise<object>} The created batch object.
|
|
205
|
-
*/
|
|
206
|
-
export async function createHistoryRecord(sessionId, { task, result, approved, summary, timestamp }) {
|
|
207
|
-
const ts = timestamp || new Date().toISOString();
|
|
208
|
-
const dir = path.join(getHuDir(), sessionId);
|
|
209
|
-
await fs.mkdir(dir, { recursive: true });
|
|
210
|
-
|
|
211
|
-
const batch = {
|
|
212
|
-
session_id: sessionId,
|
|
213
|
-
created_at: ts,
|
|
214
|
-
stories: [
|
|
215
|
-
{
|
|
216
|
-
id: `HU-hist-${sessionId}`,
|
|
217
|
-
status: approved ? "certified" : "failed",
|
|
218
|
-
original: { text: task },
|
|
219
|
-
blocked_by: [],
|
|
220
|
-
certified: approved ? { summary: summary || result } : null,
|
|
221
|
-
quality: null,
|
|
222
|
-
context_requests: [],
|
|
223
|
-
created_at: ts,
|
|
224
|
-
updated_at: ts
|
|
225
|
-
}
|
|
226
|
-
],
|
|
227
|
-
history: {
|
|
228
|
-
task,
|
|
229
|
-
result,
|
|
230
|
-
approved,
|
|
231
|
-
summary: summary || null,
|
|
232
|
-
timestamp: ts
|
|
233
|
-
}
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
await fs.writeFile(path.join(dir, "batch.json"), JSON.stringify(batch, null, 2));
|
|
237
|
-
return batch;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/* ── Splitting metadata ──────────────────────────────────────────── */
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Add splitting metadata to a story within a batch.
|
|
244
|
-
* Used to track the relationship between an original HU and its sub-HUs
|
|
245
|
-
* after a split operation.
|
|
246
|
-
*
|
|
247
|
-
* @param {object} batch - The batch object.
|
|
248
|
-
* @param {string} huId - Story ID to annotate.
|
|
249
|
-
* @param {{ original_hu_id?: string, split_children?: string[], indicators_detected?: string[], heuristic_applied?: string, split_confirmed_by_fde?: boolean, validation?: object }} metadata - Splitting metadata.
|
|
250
|
-
* @returns {object} The updated story.
|
|
251
|
-
*/
|
|
252
|
-
export function addSplittingMetadata(batch, huId, metadata) {
|
|
253
|
-
const story = batch.stories.find(s => s.id === huId);
|
|
254
|
-
if (!story) throw new Error(`Story ${huId} not found`);
|
|
255
|
-
story.splitting = { ...(story.splitting || {}), ...metadata };
|
|
256
|
-
story.updated_at = new Date().toISOString();
|
|
257
|
-
return story;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
/* ── Manual HU management ─────────────────────────────────────────── */
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Detect project info from the given directory.
|
|
264
|
-
* @param {string} cwd - Directory to detect project from.
|
|
265
|
-
* @returns {Promise<{ name: string, remoteUrl: string|null }>}
|
|
266
|
-
*/
|
|
267
|
-
export async function detectProject(cwd) {
|
|
268
|
-
const name = path.basename(cwd);
|
|
269
|
-
let remoteUrl = null;
|
|
270
|
-
try {
|
|
271
|
-
const { stdout } = await execFileAsync("git", ["remote", "get-url", "origin"], { cwd, encoding: "utf8" });
|
|
272
|
-
remoteUrl = stdout.trim();
|
|
273
|
-
} catch { /* not a git repo or no remote */ }
|
|
274
|
-
return { name, remoteUrl };
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
/**
|
|
278
|
-
* Derive a project slug from a directory path.
|
|
279
|
-
* @param {string} projectDir - Absolute path to the project.
|
|
280
|
-
* @returns {string}
|
|
281
|
-
*/
|
|
282
|
-
function projectSlug(projectDir) {
|
|
283
|
-
return path.basename(projectDir);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
/**
|
|
287
|
-
* Get the batch file path for a project.
|
|
288
|
-
* @param {string} projectDir
|
|
289
|
-
* @returns {string}
|
|
290
|
-
*/
|
|
291
|
-
function projectBatchPath(projectDir) {
|
|
292
|
-
return path.join(getHuDir(), projectSlug(projectDir), "batch.json");
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
/**
|
|
296
|
-
* Load the project batch, or return a fresh empty batch if none exists.
|
|
297
|
-
* @param {string} projectDir
|
|
298
|
-
* @returns {Promise<object>}
|
|
299
|
-
*/
|
|
300
|
-
async function loadProjectBatch(projectDir) {
|
|
301
|
-
const filePath = projectBatchPath(projectDir);
|
|
302
|
-
try {
|
|
303
|
-
const raw = await fs.readFile(filePath, "utf8");
|
|
304
|
-
return JSON.parse(raw);
|
|
305
|
-
} catch { /* no saved batch for this project */
|
|
306
|
-
return {
|
|
307
|
-
project: projectSlug(projectDir),
|
|
308
|
-
created_at: new Date().toISOString(),
|
|
309
|
-
stories: []
|
|
310
|
-
};
|
|
311
|
-
}
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
/**
|
|
315
|
-
* Save a project batch to disk (auto-creates directory).
|
|
316
|
-
* @param {string} projectDir
|
|
317
|
-
* @param {object} batch
|
|
318
|
-
*/
|
|
319
|
-
async function saveProjectBatch(projectDir, batch) {
|
|
320
|
-
const filePath = projectBatchPath(projectDir);
|
|
321
|
-
await fs.mkdir(path.dirname(filePath), { recursive: true });
|
|
322
|
-
batch.updated_at = new Date().toISOString();
|
|
323
|
-
await fs.writeFile(filePath, JSON.stringify(batch, null, 2));
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
/**
|
|
327
|
-
* Create a manual HU (user story) for a project.
|
|
328
|
-
* Auto-creates the batch file if it doesn't exist.
|
|
329
|
-
* @param {string} projectDir
|
|
330
|
-
* @param {{ title: string, description?: string, status?: string, acceptanceCriteria?: string }} data
|
|
331
|
-
* @returns {Promise<object>} The created HU.
|
|
332
|
-
*/
|
|
333
|
-
export async function createManualHu(projectDir, { title, description, status, acceptanceCriteria }) {
|
|
334
|
-
if (!title) throw new Error("title is required to create a HU");
|
|
335
|
-
const batch = await loadProjectBatch(projectDir);
|
|
336
|
-
const now = new Date().toISOString();
|
|
337
|
-
const hu = {
|
|
338
|
-
id: `HU-${Date.now()}-${batch.stories.length}`,
|
|
339
|
-
title,
|
|
340
|
-
description: description || "",
|
|
341
|
-
status: status || HU_STATUS.PENDING,
|
|
342
|
-
acceptanceCriteria: acceptanceCriteria || "",
|
|
343
|
-
createdAt: now,
|
|
344
|
-
updatedAt: now
|
|
345
|
-
};
|
|
346
|
-
batch.stories.push(hu);
|
|
347
|
-
await saveProjectBatch(projectDir, batch);
|
|
348
|
-
return hu;
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
/**
|
|
352
|
-
* List all HUs for a project.
|
|
353
|
-
* @param {string} projectDir
|
|
354
|
-
* @returns {Promise<Array<{ id: string, title: string, status: string, createdAt: string }>>}
|
|
355
|
-
*/
|
|
356
|
-
export async function listHus(projectDir) {
|
|
357
|
-
const batch = await loadProjectBatch(projectDir);
|
|
358
|
-
return batch.stories.map(s => ({
|
|
359
|
-
id: s.id,
|
|
360
|
-
title: s.title,
|
|
361
|
-
status: s.status,
|
|
362
|
-
createdAt: s.createdAt
|
|
363
|
-
}));
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
/**
|
|
367
|
-
* Update the status of a specific HU.
|
|
368
|
-
* @param {string} projectDir
|
|
369
|
-
* @param {string} huId
|
|
370
|
-
* @param {string} status
|
|
371
|
-
* @returns {Promise<object>} The updated HU.
|
|
372
|
-
*/
|
|
373
|
-
export async function updateHuStatus(projectDir, huId, status) {
|
|
374
|
-
const batch = await loadProjectBatch(projectDir);
|
|
375
|
-
const hu = batch.stories.find(s => s.id === huId);
|
|
376
|
-
if (!hu) throw new Error(`HU ${huId} not found`);
|
|
377
|
-
hu.status = status;
|
|
378
|
-
hu.updatedAt = new Date().toISOString();
|
|
379
|
-
await saveProjectBatch(projectDir, batch);
|
|
380
|
-
return hu;
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
/**
|
|
384
|
-
* Get a single HU by id.
|
|
385
|
-
* @param {string} projectDir
|
|
386
|
-
* @param {string} huId
|
|
387
|
-
* @returns {Promise<object>}
|
|
388
|
-
*/
|
|
389
|
-
export async function getHu(projectDir, huId) {
|
|
390
|
-
const batch = await loadProjectBatch(projectDir);
|
|
391
|
-
const hu = batch.stories.find(s => s.id === huId);
|
|
392
|
-
if (!hu) throw new Error(`HU ${huId} not found`);
|
|
393
|
-
return hu;
|
|
394
|
-
}
|
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Test doubles for the DI surfaces. Intentionally dumb — tests stage inputs
|
|
3
|
-
* through simple setters and assert on recorded calls.
|
|
4
|
-
*
|
|
5
|
-
* @typedef {import("./file-system-service.js").FileSystemService} FileSystemService
|
|
6
|
-
* @typedef {import("./command-runner.js").CommandRunner} CommandRunner
|
|
7
|
-
* @typedef {import("./command-runner.js").CommandRunResult} CommandRunResult
|
|
8
|
-
* @typedef {import("./command-runner.js").CommandRunOptions} CommandRunOptions
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* In-memory file system. Paths are stored as-is (no normalization) to keep
|
|
13
|
-
* behaviour predictable in tests.
|
|
14
|
-
*/
|
|
15
|
-
export class MockFileSystem {
|
|
16
|
-
constructor(initial = {}) {
|
|
17
|
-
/** @type {Map<string, string|Buffer>} */
|
|
18
|
-
this.files = new Map(Object.entries(initial));
|
|
19
|
-
/** @type {Set<string>} */
|
|
20
|
-
this.directories = new Set();
|
|
21
|
-
/** @type {Array<{op: string, path: string, args?: unknown}>} */
|
|
22
|
-
this.calls = [];
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
_record(op, path, args) {
|
|
26
|
-
this.calls.push({ op, path, args });
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async readFile(path, encoding = "utf8") {
|
|
30
|
-
this._record("readFile", path, { encoding });
|
|
31
|
-
if (!this.files.has(path)) {
|
|
32
|
-
const err = new Error(`ENOENT: no such file or directory, open '${path}'`);
|
|
33
|
-
err.code = "ENOENT";
|
|
34
|
-
throw err;
|
|
35
|
-
}
|
|
36
|
-
const data = this.files.get(path);
|
|
37
|
-
if (Buffer.isBuffer(data) && encoding) return data.toString(encoding);
|
|
38
|
-
return data;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async writeFile(path, data, encoding = "utf8") {
|
|
42
|
-
this._record("writeFile", path, { encoding });
|
|
43
|
-
this.files.set(path, data);
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
async appendFile(path, data, encoding = "utf8") {
|
|
47
|
-
this._record("appendFile", path, { encoding });
|
|
48
|
-
const prev = this.files.get(path) ?? "";
|
|
49
|
-
this.files.set(path, String(prev) + String(data));
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
async exists(path) {
|
|
53
|
-
this._record("exists", path);
|
|
54
|
-
return this.files.has(path) || this.directories.has(path);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
async ensureDir(path) {
|
|
58
|
-
this._record("ensureDir", path);
|
|
59
|
-
this.directories.add(path);
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
async readdir(path) {
|
|
63
|
-
this._record("readdir", path);
|
|
64
|
-
const prefix = path.endsWith("/") ? path : path + "/";
|
|
65
|
-
const entries = new Set();
|
|
66
|
-
for (const p of this.files.keys()) {
|
|
67
|
-
if (p.startsWith(prefix)) {
|
|
68
|
-
const tail = p.slice(prefix.length).split("/")[0];
|
|
69
|
-
if (tail) entries.add(tail);
|
|
70
|
-
}
|
|
71
|
-
}
|
|
72
|
-
if (!entries.size && !this.directories.has(path)) {
|
|
73
|
-
const err = new Error(`ENOENT: no such file or directory, scandir '${path}'`);
|
|
74
|
-
err.code = "ENOENT";
|
|
75
|
-
throw err;
|
|
76
|
-
}
|
|
77
|
-
return [...entries];
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
async copyFile(src, dest) {
|
|
81
|
-
this._record("copyFile", src, { dest });
|
|
82
|
-
if (!this.files.has(src)) {
|
|
83
|
-
const err = new Error(`ENOENT: no such file or directory, copyfile '${src}' -> '${dest}'`);
|
|
84
|
-
err.code = "ENOENT";
|
|
85
|
-
throw err;
|
|
86
|
-
}
|
|
87
|
-
this.files.set(dest, this.files.get(src));
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
async stat(path) {
|
|
91
|
-
this._record("stat", path);
|
|
92
|
-
if (this.files.has(path)) {
|
|
93
|
-
const data = this.files.get(path);
|
|
94
|
-
const size = Buffer.isBuffer(data) ? data.length : Buffer.byteLength(String(data));
|
|
95
|
-
return {
|
|
96
|
-
size,
|
|
97
|
-
isDirectory: () => false,
|
|
98
|
-
isFile: () => true
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
if (this.directories.has(path)) {
|
|
102
|
-
return {
|
|
103
|
-
size: 0,
|
|
104
|
-
isDirectory: () => true,
|
|
105
|
-
isFile: () => false
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
const err = new Error(`ENOENT: no such file or directory, stat '${path}'`);
|
|
109
|
-
err.code = "ENOENT";
|
|
110
|
-
throw err;
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* Scripted command runner. Tests call enqueue() to stage responses in order,
|
|
116
|
-
* or setResponder() for a per-invocation decision. Every run() is recorded.
|
|
117
|
-
*/
|
|
118
|
-
export class MockCommandRunner {
|
|
119
|
-
constructor() {
|
|
120
|
-
/** @type {Array<CommandRunResult>} */
|
|
121
|
-
this.queue = [];
|
|
122
|
-
/** @type {Array<{command: string, args: string[], options: CommandRunOptions}>} */
|
|
123
|
-
this.calls = [];
|
|
124
|
-
/** @type {null | ((args: {command: string, args: string[], options: CommandRunOptions}) => CommandRunResult|Promise<CommandRunResult>)} */
|
|
125
|
-
this.responder = null;
|
|
126
|
-
/** @type {CommandRunResult} */
|
|
127
|
-
this.defaultResponse = { exitCode: 0, stdout: "", stderr: "" };
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Queue a response. FIFO — first enqueued is returned on the first run().
|
|
132
|
-
* @param {Partial<CommandRunResult>} response
|
|
133
|
-
*/
|
|
134
|
-
enqueue(response) {
|
|
135
|
-
this.queue.push({ exitCode: 0, stdout: "", stderr: "", ...response });
|
|
136
|
-
return this;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
* Replace FIFO queue with a function that decides per-call.
|
|
141
|
-
* @param {(args: {command: string, args: string[], options: CommandRunOptions}) => CommandRunResult|Promise<CommandRunResult>} fn
|
|
142
|
-
*/
|
|
143
|
-
setResponder(fn) {
|
|
144
|
-
this.responder = fn;
|
|
145
|
-
return this;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* @param {string} command
|
|
150
|
-
* @param {string[]} args
|
|
151
|
-
* @param {CommandRunOptions} options
|
|
152
|
-
* @returns {Promise<CommandRunResult>}
|
|
153
|
-
*/
|
|
154
|
-
async run(command, args = [], options = {}) {
|
|
155
|
-
this.calls.push({ command, args: [...args], options: { ...options } });
|
|
156
|
-
if (this.responder) return this.responder({ command, args, options });
|
|
157
|
-
if (this.queue.length > 0) return this.queue.shift();
|
|
158
|
-
return { ...this.defaultResponse };
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
/**
|
|
162
|
-
* Convenience: last recorded invocation, or null.
|
|
163
|
-
*/
|
|
164
|
-
get lastCall() {
|
|
165
|
-
return this.calls.at(-1) ?? null;
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
/**
|
|
170
|
-
* Build an Environment backed by the mocks above. Useful shorthand in tests:
|
|
171
|
-
*
|
|
172
|
-
* const { env, fs, runner } = buildMockEnvironment();
|
|
173
|
-
* const agent = new BaseAgent(..., env);
|
|
174
|
-
*
|
|
175
|
-
* @param {{files?: Record<string, string|Buffer>}} [opts]
|
|
176
|
-
*/
|
|
177
|
-
export function buildMockEnvironment({ files } = {}) {
|
|
178
|
-
const fs = new MockFileSystem(files);
|
|
179
|
-
const runner = new MockCommandRunner();
|
|
180
|
-
const env = Object.freeze({ fs, runner });
|
|
181
|
-
return { env, fs, runner };
|
|
182
|
-
}
|