karajan-code 4.70.4 → 5.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/package.json +6 -14
- package/packages/hu-board/package.json +1 -1
- package/packages/hu-board/public/app.js +4 -18
- package/packages/hu-board/public/index.html +5 -2
- package/packages/hu-board/public/styles.css +26 -0
- package/packages/hu-board/public/utils/api.js +0 -74
- package/packages/hu-board/public/utils/board-tour.js +62 -0
- package/packages/hu-board/public/utils/board-view.js +49 -144
- package/packages/hu-board/public/utils/command-launcher.js +8 -70
- package/packages/hu-board/public/utils/formatters.js +10 -0
- package/packages/hu-board/public/utils/governance-view.js +13 -1
- package/packages/hu-board/public/utils/hu-actions.js +17 -68
- package/packages/hu-board/public/utils/init-listeners.js +7 -8
- package/packages/hu-board/public/utils/log-panel.js +3 -16
- package/packages/hu-board/public/utils/maggle-mode.js +2 -12
- package/packages/hu-board/public/utils/modals.js +7 -0
- package/packages/hu-board/public/utils/project-picker-view.js +1 -1
- package/packages/hu-board/public/utils/server-push.js +3 -22
- package/packages/hu-board/public/utils/shared-board-notice.js +33 -0
- package/packages/hu-board/public/utils/story-detail-view.js +5 -19
- package/packages/hu-board/src/cleanup-zombies.js +0 -1
- package/packages/hu-board/src/command-runner.js +36 -25
- package/packages/hu-board/src/db-connection.js +282 -0
- package/packages/hu-board/src/db-paths.js +113 -0
- package/packages/hu-board/src/db-queries.js +188 -0
- package/packages/hu-board/src/db-tombstones.js +51 -0
- package/packages/hu-board/src/db.js +28 -619
- package/packages/hu-board/src/ephemeral-cleaner.js +2 -3
- package/packages/hu-board/src/hu-zombie-reaper.js +5 -1
- package/packages/hu-board/src/plan-mutations.js +3 -153
- package/packages/hu-board/src/plans-service.js +55 -0
- package/packages/hu-board/src/routes/api/commands.js +122 -0
- package/packages/hu-board/src/routes/api/plans.js +55 -0
- package/packages/hu-board/src/routes/api/projects.js +267 -0
- package/packages/hu-board/src/routes/api/prompts.js +135 -0
- package/packages/hu-board/src/routes/api/sessions.js +95 -0
- package/packages/hu-board/src/routes/api/stories.js +183 -0
- package/packages/hu-board/src/routes/api/system.js +241 -0
- package/packages/hu-board/src/routes/api/tombstones.js +38 -0
- package/packages/hu-board/src/routes/api.js +24 -1371
- package/packages/hu-board/src/routes/governance.js +23 -1
- package/packages/hu-board/src/routes/lanes.js +40 -0
- package/packages/hu-board/src/routes/rag.js +4 -3
- package/packages/hu-board/src/server.js +20 -34
- package/packages/hu-board/src/sync-files.js +318 -0
- package/packages/hu-board/src/sync-project.js +99 -0
- package/packages/hu-board/src/sync.js +22 -566
- package/packages/hu-board/src/zombie-reaper.js +1 -1
- package/scripts/dual-publish.mjs +19 -3
- package/scripts/postinstall.js +15 -2
- package/scripts/verify-pack-mcp.mjs +11 -7
- package/scripts/verify-pack.mjs +4 -4
- package/src/audit/audit-history.js +3 -2
- package/src/audit/dead-exports.js +68 -59
- package/src/audit/deterministic-summary.js +33 -13
- package/src/audit/npm-signatures.js +41 -0
- package/src/audit/sbom.js +35 -0
- package/src/checks/agent-token.js +112 -0
- package/src/checks/dir-setup.js +34 -1
- package/src/checks/native-build.js +21 -39
- package/src/checks/runner.js +128 -142
- package/src/cli/advanced-commands.js +6 -6
- package/src/cli/register-meta.js +42 -82
- package/src/cli/register-pipeline.js +8 -143
- package/src/cli/register-plan.js +9 -100
- package/src/cli/retired-commands.js +49 -0
- package/src/cli.js +18 -5
- package/src/commands/agent-run.js +40 -26
- package/src/commands/audit.js +127 -124
- package/src/commands/board.js +5 -9
- package/src/commands/check.js +29 -16
- package/src/commands/doctor.js +2 -0
- package/src/commands/env.js +176 -150
- package/src/commands/go.js +71 -53
- package/src/commands/harden.js +90 -81
- package/src/commands/hu-start.js +52 -0
- package/src/commands/hu.js +288 -221
- package/src/commands/identity.js +111 -78
- package/src/commands/init-atril.js +47 -0
- package/src/commands/init.js +11 -40
- package/src/commands/install-tools.js +70 -85
- package/src/commands/main-guard.js +78 -0
- package/src/commands/policy.js +226 -184
- package/src/commands/qmd.js +27 -21
- package/src/commands/rag.js +75 -48
- package/src/commands/review-gate.js +421 -341
- package/src/commands/start.js +4 -5
- package/src/commands/steward.js +96 -47
- package/src/commands/tournament.js +46 -37
- package/src/commands/worktree.js +94 -25
- package/src/config/loader.js +34 -17
- package/src/config/secrets.js +36 -0
- package/src/config.js +1 -0
- package/src/environment/adr.js +16 -2
- package/src/git/karajan-label.js +20 -8
- package/src/guards/main-guard.js +54 -0
- package/src/harden/hook-templates.js +16 -1
- package/src/harden/sentinel/posttooluse.mjs +18 -6
- package/src/harden/sentinel/pretooluse-sentinel.mjs +64 -36
- package/src/harden/sentinel/sentinel-lib.mjs +129 -26
- package/src/harden/sentinel/sentinel-shell.mjs +5 -16
- package/src/harden/sentinel/sessionstart.mjs +3 -2
- package/src/harden/sentinel/stop.mjs +5 -4
- package/src/harden/workflow-templates.js +1 -1
- package/src/hu/board-lookup.js +29 -0
- package/src/hu/gh-identity.js +23 -0
- package/src/hu/github-board.js +4 -2
- package/src/hu/lane-prune.js +15 -7
- package/src/hu/lane-setup.js +65 -0
- package/src/hu/lanes.js +66 -0
- package/src/hu/team-board-mirror.js +3 -2
- package/src/infrastructure/command-runner.js +1 -1
- package/src/infrastructure/file-system-service.js +1 -1
- package/src/mcp/handlers/direct-handlers.js +2 -292
- package/src/mcp/handlers/hu-handlers.js +1 -35
- package/src/mcp/handlers/management-handlers.js +2 -42
- package/src/mcp/server-handlers.js +16 -22
- package/src/mcp/tools.js +1 -196
- package/src/plan/lane-plans.js +54 -0
- package/src/plan/plan-store.js +7 -4
- package/src/policy/add.js +26 -12
- package/src/policy/adr-number-check.js +31 -0
- package/src/prompts/audit.js +299 -339
- package/src/prompts/card-context.js +10 -5
- package/src/prompts/reviewer.js +5 -2
- package/src/rag/auto-update.js +56 -43
- package/src/rag/bootstrap-index.js +28 -54
- package/src/rag/migrate.js +1 -1
- package/src/rag/onnx-fallback.js +2 -2
- package/src/rag/pid-lease.js +70 -0
- package/src/rag/project-store.js +2 -12
- package/src/review/card-first.js +57 -46
- package/src/review/gate-gitignore.js +23 -4
- package/src/review/gate-hook.js +12 -4
- package/src/review/one-shot-review.js +45 -35
- package/src/review/publish-pr.js +14 -2
- package/src/review/repo-docs.js +73 -0
- package/src/review/repo-stack.js +59 -0
- package/src/review/reviewer-fallback.js +19 -4
- package/src/review/seal-commit.js +29 -0
- package/src/review/sonar-pregate.js +59 -44
- package/src/review/third-party-review.js +90 -16
- package/src/review/third-party-size.js +43 -0
- package/src/review/third-party-skills.js +54 -0
- package/src/review/verify-pr.js +1 -24
- package/src/roles/audit-role.js +106 -125
- package/src/skills/atril-skills.js +5 -4
- package/src/sonar/scanner.js +116 -101
- package/src/steward/invariants.js +5 -4
- package/src/steward/phantom-sweep.js +66 -0
- package/src/tournament/judge.js +60 -53
- package/src/utils/gh-account.js +37 -0
- package/src/utils/git.js +0 -13
- package/src/utils/home-migration.js +7 -1
- package/src/utils/main-checkout.js +53 -0
- package/src/utils/qmd-collection.js +3 -1
- package/src/utils/welcome.js +3 -3
- package/templates/{skills → atril/commands}/kj-architect.md +5 -0
- package/templates/{skills → atril/commands}/kj-audit.md +5 -0
- package/templates/{skills → atril/commands}/kj-board.md +5 -0
- package/templates/{skills → atril/commands}/kj-code.md +5 -0
- package/templates/{skills → atril/commands}/kj-csv-transform.md +5 -0
- package/templates/{skills → atril/commands}/kj-data-report.md +5 -0
- package/templates/{skills → atril/commands}/kj-discover.md +5 -0
- package/templates/{skills → atril/commands}/kj-rag-query.md +5 -0
- package/templates/{skills → atril/commands}/kj-review.md +5 -0
- package/templates/{skills → atril/commands}/kj-security.md +5 -0
- package/templates/{skills → atril/commands}/kj-sonar.md +5 -0
- package/templates/{skills → atril/commands}/kj-sql-analysis.md +5 -0
- package/templates/{skills → atril/commands}/kj-test.md +5 -0
- package/packages/hu-board/public/utils/run-launcher.js +0 -169
- package/packages/hu-board/src/run-tracker.js +0 -121
- package/src/activity-log.js +0 -59
- package/src/agents/host-agent.js +0 -38
- package/src/agents/model-registry.js +0 -142
- package/src/audit/plan-adherence.js +0 -224
- package/src/autonomy/arbiter-role.js +0 -71
- package/src/autonomy/arbiter.js +0 -22
- package/src/autonomy/decider.js +0 -18
- package/src/autonomy/decision-resolver.js +0 -58
- package/src/autonomy/outcome-report.js +0 -55
- package/src/autonomy/stage-ask.js +0 -30
- package/src/brain/decision-tracker.js +0 -125
- package/src/brain/decisor.js +0 -243
- package/src/brain/solomon-consult.js +0 -171
- package/src/brain/standby-scheduler.js +0 -8
- package/src/budget/comparison.js +0 -96
- package/src/canvas/parse-md.js +0 -238
- package/src/canvas/schema.js +0 -101
- package/src/canvas/to-flat.js +0 -85
- package/src/canvas/validate.js +0 -163
- package/src/ci/dispatch.js +0 -99
- package/src/ci/index.js +0 -3
- package/src/ci/pr-diff.js +0 -26
- package/src/cli/register-standby.js +0 -26
- package/src/commands/architect.js +0 -111
- package/src/commands/autorun.js +0 -49
- package/src/commands/researcher.js +0 -54
- package/src/commands/resume.js +0 -72
- package/src/commands/review.js +0 -89
- package/src/commands/run.js +0 -185
- package/src/commands/standby.js +0 -117
- package/src/commands/status.js +0 -36
- package/src/commands/triage.js +0 -78
- package/src/commands/undo.js +0 -62
- package/src/domains/domain-loader.js +0 -245
- package/src/domains/domain-registry.js +0 -140
- package/src/domains/domain-synthesizer.js +0 -129
- package/src/git/automation.js +0 -433
- package/src/git/coder-commit-guard.js +0 -40
- package/src/git/hu-automation.js +0 -243
- package/src/git/hu-snapshot.js +0 -10
- package/src/git/symlink-targets.js +0 -57
- package/src/golden/asserter.js +0 -92
- package/src/golden/loader.js +0 -52
- package/src/golden/runner.js +0 -92
- package/src/golden/schema.js +0 -41
- package/src/guards/duplicate-members.js +0 -86
- package/src/guards/intent-guard.js +0 -161
- package/src/guards/perf-guard.js +0 -126
- package/src/guards/policy-guard.js +0 -90
- package/src/guards/policy-resolver.js +0 -85
- package/src/guards/secret-redactor.js +0 -71
- package/src/hu/acceptance-runner.js +0 -138
- package/src/hu/lazy-planner.js +0 -109
- package/src/hu/parallel-executor.js +0 -94
- package/src/hu/routing-metrics.js +0 -17
- package/src/hu/split-validator.js +0 -143
- package/src/hu/splitting-detector.js +0 -158
- package/src/hu/splitting-generator.js +0 -175
- package/src/hu/store.js +0 -394
- package/src/infrastructure/mocks.js +0 -182
- package/src/mcp/handlers/run-handler.js +0 -318
- package/src/mcp/sovereignty-guard.js +0 -113
- package/src/mcp/tool-arg-normalizers.js +0 -16
- package/src/mutate/reviewer-signal.js +0 -76
- package/src/orchestrator/agent-fallback.js +0 -81
- package/src/orchestrator/brain-coordinator.js +0 -186
- package/src/orchestrator/ci-integration.js +0 -139
- package/src/orchestrator/config-init.js +0 -426
- package/src/orchestrator/direct-actions.js +0 -217
- package/src/orchestrator/drivers/error-recovery.js +0 -287
- package/src/orchestrator/drivers/init-context.js +0 -238
- package/src/orchestrator/drivers/iteration-loop.js +0 -382
- package/src/orchestrator/drivers/iteration-phases/coder-and-refactorer.js +0 -69
- package/src/orchestrator/drivers/iteration-phases/guards.js +0 -91
- package/src/orchestrator/drivers/iteration-phases/handle-approved.js +0 -29
- package/src/orchestrator/drivers/iteration-phases/quality-gates.js +0 -125
- package/src/orchestrator/drivers/iteration-phases/reviewer-gate.js +0 -62
- package/src/orchestrator/drivers/post-loop.js +0 -404
- package/src/orchestrator/drivers/pre-loop-phases/auto-hu-batch.js +0 -137
- package/src/orchestrator/drivers/pre-loop-phases/config-deprecations.js +0 -47
- package/src/orchestrator/drivers/pre-loop-phases/ensure-addyosmani-skills.js +0 -84
- package/src/orchestrator/drivers/pre-loop-phases/inject-loaded-plan.js +0 -393
- package/src/orchestrator/drivers/pre-loop.js +0 -524
- package/src/orchestrator/drivers/run-hu-batch.js +0 -468
- package/src/orchestrator/feedback-enrichment.js +0 -140
- package/src/orchestrator/feedback-queue.js +0 -151
- package/src/orchestrator/flow-control.js +0 -170
- package/src/orchestrator/flow-runner.js +0 -365
- package/src/orchestrator/fs-leak-detector.js +0 -228
- package/src/orchestrator/hu-scheduler.js +0 -114
- package/src/orchestrator/hu-sub-pipeline.js +0 -642
- package/src/orchestrator/iteration-gate.js +0 -73
- package/src/orchestrator/iteration-stages.js +0 -9
- package/src/orchestrator/parallel-limiter.js +0 -74
- package/src/orchestrator/pipeline-context.js +0 -72
- package/src/orchestrator/post-loop-stages.js +0 -468
- package/src/orchestrator/pre-loop-stages.js +0 -11
- package/src/orchestrator/preflight-checks.js +0 -487
- package/src/orchestrator/repair/repair-detector.js +0 -117
- package/src/orchestrator/repair/repair-runner.js +0 -142
- package/src/orchestrator/reviewer-fallback.js +0 -45
- package/src/orchestrator/role-output-compressor.js +0 -189
- package/src/orchestrator/session-journal.js +0 -308
- package/src/orchestrator/solomon-escalation.js +0 -331
- package/src/orchestrator/solomon-rules.js +0 -212
- package/src/orchestrator/stages/acceptance-coverage-stage.js +0 -47
- package/src/orchestrator/stages/architect-stage.js +0 -172
- package/src/orchestrator/stages/coder-stage.js +0 -506
- package/src/orchestrator/stages/domain-curator-stage.js +0 -82
- package/src/orchestrator/stages/format-stage.js +0 -61
- package/src/orchestrator/stages/hu-reviewer-phases/detect-and-apply-splits.js +0 -127
- package/src/orchestrator/stages/hu-reviewer-stage.js +0 -419
- package/src/orchestrator/stages/perf-stage.js +0 -140
- package/src/orchestrator/stages/planner-stage.js +0 -85
- package/src/orchestrator/stages/rag-context-stage.js +0 -80
- package/src/orchestrator/stages/rag-preload-decisor.js +0 -58
- package/src/orchestrator/stages/research-stage.js +0 -69
- package/src/orchestrator/stages/reviewer-stage.js +0 -431
- package/src/orchestrator/stages/sonar-stage.js +0 -439
- package/src/orchestrator/stages/stage-classes.js +0 -84
- package/src/orchestrator/stages/stage-executor.js +0 -155
- package/src/orchestrator/stages/suite-green-stage.js +0 -62
- package/src/orchestrator/stages/tdd-discipline-stage.js +0 -56
- package/src/orchestrator/stages/tool-judge-stage.js +0 -53
- package/src/orchestrator/stages/triage-stage.js +0 -172
- package/src/orchestrator/standby.js +0 -71
- package/src/orchestrator/verification-gate.js +0 -183
- package/src/orchestrator.js +0 -22
- package/src/plan/adr-generator.js +0 -170
- package/src/plan/adr-loader.js +0 -60
- package/src/plan/plan-executor.js +0 -100
- package/src/plan/plan-validation.js +0 -4
- package/src/prompts/acceptance-coverage.js +0 -32
- package/src/prompts/arbiter.js +0 -38
- package/src/prompts/architect.js +0 -103
- package/src/prompts/hu-reviewer.js +0 -244
- package/src/prompts/tool-correctness-judge.js +0 -31
- package/src/prompts/triage.js +0 -57
- package/src/rag/embedder.js +0 -2
- package/src/rag/embedders/_cloud-base.js +0 -2
- package/src/rag/embedders/cohere.js +0 -2
- package/src/rag/embedders/mistral.js +0 -2
- package/src/rag/embedders/onnx.js +0 -2
- package/src/rag/embedders/openai.js +0 -2
- package/src/rag/embedders/voyage.js +0 -2
- package/src/rag/rerank.js +0 -4
- package/src/rag/where-parser.js +0 -4
- package/src/repeat-detector.js +0 -88
- package/src/review/diff-generator.js +0 -112
- package/src/review/git-stash-helper.js +0 -104
- package/src/review/schema.js +0 -31
- package/src/review/scope-filter.js +0 -152
- package/src/review/snapshot-diff.js +0 -122
- package/src/review/tdd-discipline.js +0 -90
- package/src/review/tdd-policy.js +0 -69
- package/src/roles/acceptance-coverage-role.js +0 -78
- package/src/roles/architect-role.js +0 -77
- package/src/roles/coder-role.js +0 -113
- package/src/roles/commiter-role.js +0 -113
- package/src/roles/domain-curator-role.js +0 -134
- package/src/roles/hu-reviewer-role.js +0 -64
- package/src/roles/impeccable-role.js +0 -85
- package/src/roles/index.js +0 -18
- package/src/roles/karajan-brain-role.js +0 -134
- package/src/roles/perf-role.js +0 -122
- package/src/roles/planner-role.js +0 -151
- package/src/roles/refactorer-role.js +0 -30
- package/src/roles/repairer-role.js +0 -181
- package/src/roles/researcher-role.js +0 -70
- package/src/roles/reviewer-role.js +0 -115
- package/src/roles/security-role.js +0 -73
- package/src/roles/solomon-role.js +0 -271
- package/src/roles/sonar-role.js +0 -72
- package/src/roles/tester-role.js +0 -235
- package/src/roles/tool-correctness-judge-role.js +0 -78
- package/src/roles/triage-role.js +0 -99
- package/src/session/journal/decisions-writer.js +0 -200
- package/src/session/journal/iteration-logger.js +0 -194
- package/src/session/journal/summary-writer.js +0 -320
- package/src/session/journal/tree-writer.js +0 -164
- package/src/session/journal/write-all.js +0 -89
- package/src/session-cleanup.js +0 -116
- package/src/skills/semantic-detector.js +0 -167
- package/src/skills/skill-detector.js +0 -385
- package/src/sonar/cloud-scanner.js +0 -78
- package/src/tracker-bootstrap.js +0 -44
- package/src/types/index.js +0 -38
- package/src/types/policy.js +0 -43
- package/src/types/solomon.js +0 -87
- package/src/utils/async-lock.js +0 -31
- package/src/utils/budget.js +0 -229
- package/src/utils/cwd-confirm.js +0 -39
- package/src/utils/display/event-handlers.js +0 -403
- package/src/utils/display/formatters.js +0 -132
- package/src/utils/display/header.js +0 -47
- package/src/utils/display/session-summary.js +0 -118
- package/src/utils/display/solomon.js +0 -42
- package/src/utils/pricing.js +0 -29
- package/src/utils/quota-simulator.js +0 -56
- package/src/utils/role-assigner.js +0 -137
- package/src/utils/rtk-wrapper.js +0 -87
- package/src/utils/run-registry.js +0 -9
- package/src/utils/status-dashboard.js +0 -254
- package/src/utils/tool-call-extractor.js +0 -56
- package/src/webperf/devtools-detect.js +0 -112
- package/src/webperf/webperf-role-integration.js +0 -66
- package/templates/skills/kj-run.md +0 -73
|
@@ -73,10 +73,14 @@ export function reapZombieHus({ db, setHuStatus, setHuFailResult, opts = {}, now
|
|
|
73
73
|
const localHuId = hu.id.includes("::") ? hu.id.split("::").slice(1).join("::") : hu.id;
|
|
74
74
|
let planPersisted = false;
|
|
75
75
|
if (hu.plan_id) {
|
|
76
|
+
let result = null;
|
|
76
77
|
try {
|
|
77
|
-
|
|
78
|
+
result = setHuStatus({ planId: hu.plan_id, huId: localHuId, status: "pending", projectId: hu.project_id });
|
|
78
79
|
planPersisted = !!(result && result.ok);
|
|
79
80
|
} catch { /* swallow — never block startup */ }
|
|
81
|
+
// KJC-BUG-0405: a read-only plan mirrors the shared board; its running HU is a
|
|
82
|
+
// teammate's live work, not a zombie. Neither the plan nor the DB is touched.
|
|
83
|
+
if (result?.readonly) continue;
|
|
80
84
|
// KJC-TSK-0404: además de resetear status=pending, marcar la HU con
|
|
81
85
|
// result=fail + blocker. El siguiente `kj run` ve el blocker y se
|
|
82
86
|
// lo inyecta al coder como "## PREVIOUS ATTEMPT FAILED" para que
|
|
@@ -14,26 +14,14 @@
|
|
|
14
14
|
* Steps 2-4 must share a single on-disk write: we never ack the mutation to
|
|
15
15
|
* the UI without persisting it, otherwise a reload would silently revert.
|
|
16
16
|
*/
|
|
17
|
-
import { readFileSync, existsSync, readdirSync
|
|
18
|
-
import { join
|
|
17
|
+
import { readFileSync, existsSync, readdirSync } from 'node:fs';
|
|
18
|
+
import { join } from 'node:path';
|
|
19
19
|
import { writeJsonAtomicSync } from 'karajan-core/atomic-write';
|
|
20
|
-
import { spawn } from 'node:child_process';
|
|
21
|
-
import { trackRun, untrack } from './run-tracker.js';
|
|
22
|
-
import { getHuBoardRunsDir } from './db.js';
|
|
23
|
-
import { fileURLToPath } from 'node:url';
|
|
24
20
|
import { updateHuStatus, certifyAllHus, updateHu } from 'karajan-core/plan-hu-ops';
|
|
25
21
|
import { validateBlockedByChange } from 'karajan-core/plan-validation';
|
|
26
22
|
import { syncPlanFile } from './sync.js';
|
|
27
23
|
import { getDb } from './db.js';
|
|
28
24
|
|
|
29
|
-
// Repo root → so we can spawn `node <repo>/src/cli.js run ...` without
|
|
30
|
-
// depending on `kj` being in $PATH of the board process.
|
|
31
|
-
const REPO_ROOT = (() => {
|
|
32
|
-
const here = dirname(fileURLToPath(import.meta.url)); // packages/hu-board/src
|
|
33
|
-
return join(here, '..', '..', '..'); // → repo root
|
|
34
|
-
})();
|
|
35
|
-
const KJ_CLI = join(REPO_ROOT, 'src', 'cli.js');
|
|
36
|
-
|
|
37
25
|
/**
|
|
38
26
|
* Root directory where `kj plan` writes `<slug>/<planId>.json`. Honours
|
|
39
27
|
* `KJ_PLANS_DIR` (tests) then `KJ_HOME/plans` (power users) then
|
|
@@ -90,7 +78,7 @@ function readPlan(filePath) {
|
|
|
90
78
|
// in the issues; the board shows it and refuses to edit it, so the two never diverge.
|
|
91
79
|
function mirrorGuard(plan) {
|
|
92
80
|
if (!plan?.readonly) return null;
|
|
93
|
-
return { ok: false, error: `this plan mirrors the shared board (${plan.readonly}) and is read-only here: change it with kj hu move` };
|
|
81
|
+
return { ok: false, readonly: true, error: `this plan mirrors the shared board (${plan.readonly}) and is read-only here: change it with kj hu move` };
|
|
94
82
|
}
|
|
95
83
|
|
|
96
84
|
function writePlan(filePath, plan) {
|
|
@@ -218,45 +206,6 @@ export function setHuFailResult({ planId, huId, blocker, projectId }) {
|
|
|
218
206
|
return { ok: true, hu };
|
|
219
207
|
}
|
|
220
208
|
|
|
221
|
-
/**
|
|
222
|
-
* KJC-TSK-0408 step 2/3: deshace los cambios de una HU restaurando
|
|
223
|
-
* el snapshot git tomado pre-run. Marca status=pending, result=null
|
|
224
|
-
* y outcome.reverted=true para que la UI muestre "deshecha".
|
|
225
|
-
*
|
|
226
|
-
* @param {object} args
|
|
227
|
-
* @param {string} args.planId
|
|
228
|
-
* @param {string} args.huId
|
|
229
|
-
* @param {string} [args.projectId]
|
|
230
|
-
* @returns {Promise<{ ok: boolean, sha?: string, error?: string }>}
|
|
231
|
-
*/
|
|
232
|
-
export async function revertHuFromSnapshot({ planId, huId, projectId }) {
|
|
233
|
-
const filePath = findPlanFilePath(planId, projectId);
|
|
234
|
-
if (!filePath) return { ok: false, error: `plan not found: ${planId}` };
|
|
235
|
-
const plan = readPlan(filePath);
|
|
236
|
-
const mirrored = mirrorGuard(plan);
|
|
237
|
-
if (mirrored) return mirrored;
|
|
238
|
-
const hu = plan.hus?.find(h => h.id === huId);
|
|
239
|
-
if (!hu) return { ok: false, error: `hu not found: ${huId}` };
|
|
240
|
-
const snapshotSha = hu.outcome?.snapshot_sha;
|
|
241
|
-
if (!snapshotSha) {
|
|
242
|
-
return { ok: false, error: 'esta HU no tiene snapshot — solo HUs ejecutadas con KJC-TSK-0408 pueden deshacerse' };
|
|
243
|
-
}
|
|
244
|
-
const projectDir = plan.projectDir;
|
|
245
|
-
if (!projectDir) return { ok: false, error: 'plan sin projectDir' };
|
|
246
|
-
|
|
247
|
-
const { restoreHuSnapshot } = await import('karajan-core/hu-snapshot');
|
|
248
|
-
const restore = await restoreHuSnapshot({ projectDir, huId });
|
|
249
|
-
if (!restore.ok) return { ok: false, error: `restore falló: ${restore.error}` };
|
|
250
|
-
|
|
251
|
-
hu.status = 'pending';
|
|
252
|
-
hu.result = null;
|
|
253
|
-
hu.outcome = { ...(hu.outcome || {}), reverted: true, revertedAt: new Date().toISOString() };
|
|
254
|
-
hu.updatedAt = new Date().toISOString();
|
|
255
|
-
writePlan(filePath, plan);
|
|
256
|
-
syncPlanFile(filePath);
|
|
257
|
-
return { ok: true, sha: restore.sha };
|
|
258
|
-
}
|
|
259
|
-
|
|
260
209
|
/**
|
|
261
210
|
* Bulk certify: equivalent to `kj plan ready <planId>` over HTTP. Flips
|
|
262
211
|
* every pending HU to certified and sets `plan.status = "ready"`. No-op
|
|
@@ -344,102 +293,3 @@ export function markPlanReady({ planId, projectId }) {
|
|
|
344
293
|
syncPlanFile(filePath);
|
|
345
294
|
return { ok: true, count, planStatus: plan.status };
|
|
346
295
|
}
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Launch `kj run --plan <planId>` as a detached child. This is the
|
|
350
|
-
* "Run plan" button's server-side handler: the board no longer asks the
|
|
351
|
-
* user to drop to the terminal to kick off execution.
|
|
352
|
-
*
|
|
353
|
-
* We spawn against the repo's own CLI (`node <repo>/src/cli.js`) instead
|
|
354
|
-
* of a `kj` binary on $PATH — the board is always shipped with the repo
|
|
355
|
-
* that hosts it, so that entry point is guaranteed to exist. Tests set
|
|
356
|
-
* `KJ_RUN_SPAWN_MODE=echo` to replace the spawn with a no-op that just
|
|
357
|
-
* returns a fake pid, so we can assert payload shape without booting the
|
|
358
|
-
* whole orchestrator inside vitest.
|
|
359
|
-
*
|
|
360
|
-
* Output is redirected to `~/.karajan/hu-board-runs/<planId>.log` so the
|
|
361
|
-
* board can tail it later if we wire up a live log viewer — for now it's
|
|
362
|
-
* just a durable trail. stdin is `ignore` (coder subprocess requirement
|
|
363
|
-
* documented in CLAUDE.md).
|
|
364
|
-
*
|
|
365
|
-
* @param {object} args
|
|
366
|
-
* @param {string} args.planId
|
|
367
|
-
* @param {string} [args.projectId]
|
|
368
|
-
* @param {string} [args.taskOverride] - optional custom task string
|
|
369
|
-
* @returns {{ ok: true, pid: number, logPath: string } | { ok: false, error: string }}
|
|
370
|
-
*/
|
|
371
|
-
export function runPlan({ planId, projectId, taskOverride, huIds = null } = {}) {
|
|
372
|
-
const filePath = findPlanFilePath(planId, projectId);
|
|
373
|
-
if (!filePath) return { ok: false, error: `plan not found: ${planId}` };
|
|
374
|
-
|
|
375
|
-
const plan = readPlan(filePath);
|
|
376
|
-
const mirrored = mirrorGuard(plan);
|
|
377
|
-
if (mirrored) return mirrored;
|
|
378
|
-
if (!Array.isArray(plan.hus) || plan.hus.length === 0) {
|
|
379
|
-
return { ok: false, error: 'plan has no hus[]' };
|
|
380
|
-
}
|
|
381
|
-
const projectDir = plan.projectDir;
|
|
382
|
-
if (!projectDir) {
|
|
383
|
-
return {
|
|
384
|
-
ok: false,
|
|
385
|
-
error:
|
|
386
|
-
'plan has no projectDir stamped — re-run `kj plan` to regenerate it, '
|
|
387
|
-
+ 'then try again (plans pre-v2.7.4 did not persist the dir).',
|
|
388
|
-
};
|
|
389
|
-
}
|
|
390
|
-
|
|
391
|
-
// PR4: when huIds is set, validate they exist before spawning so
|
|
392
|
-
// we surface "unknown HU" as an HTTP 400 instead of letting the
|
|
393
|
-
// CLI fail mid-startup with a less obvious message.
|
|
394
|
-
let huFlag = null;
|
|
395
|
-
if (Array.isArray(huIds) && huIds.length > 0) {
|
|
396
|
-
const validIds = new Set(plan.hus.map((h) => h.id));
|
|
397
|
-
const unknown = huIds.filter((id) => !validIds.has(id));
|
|
398
|
-
if (unknown.length > 0) {
|
|
399
|
-
return { ok: false, error: `unknown HU id(s) in plan ${planId}: ${unknown.join(', ')}` };
|
|
400
|
-
}
|
|
401
|
-
huFlag = huIds.join(',');
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const runsDir = getHuBoardRunsDir(); // KJC-TSK-0421 — centralised in db.js
|
|
405
|
-
mkdirSync(runsDir, { recursive: true });
|
|
406
|
-
// PR4: when running a single HU, suffix the log with the HU id so
|
|
407
|
-
// a subsequent full-plan run doesn't overwrite the per-HU log.
|
|
408
|
-
const logName = huFlag
|
|
409
|
-
? `${planId}--hu-${huIds.join('-').replace(/[^a-zA-Z0-9_-]/g, '_').slice(0, 60)}.log`
|
|
410
|
-
: `${planId}.log`;
|
|
411
|
-
const logPath = join(runsDir, logName);
|
|
412
|
-
|
|
413
|
-
const task = taskOverride || plan.task || `run plan ${planId}`;
|
|
414
|
-
const args = [KJ_CLI, 'run', '--plan', planId];
|
|
415
|
-
if (huFlag) args.push('--hu', huFlag);
|
|
416
|
-
args.push(task);
|
|
417
|
-
|
|
418
|
-
// Escape hatch for tests — hard-kill the spawn so vitest doesn't boot
|
|
419
|
-
// the full orchestrator. The shape of the response is what we assert.
|
|
420
|
-
if (process.env.KJ_RUN_SPAWN_MODE === 'echo') {
|
|
421
|
-
return { ok: true, pid: 0, logPath, argv: [process.execPath, ...args], projectDir, huIds: huIds || null };
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
const out = openSync(logPath, 'a');
|
|
425
|
-
const err = openSync(logPath, 'a');
|
|
426
|
-
const child = spawn(process.execPath, args, {
|
|
427
|
-
cwd: projectDir,
|
|
428
|
-
detached: true,
|
|
429
|
-
stdio: ['ignore', out, err],
|
|
430
|
-
// Strip CLAUDECODE so the Claude agent subprocess trick in
|
|
431
|
-
// claude-agent.js doesn't misbehave (documented in CLAUDE.md).
|
|
432
|
-
env: { ...process.env, CLAUDECODE: undefined },
|
|
433
|
-
});
|
|
434
|
-
child.unref();
|
|
435
|
-
// KJC-TSK-0396: registrar el PID para que el botón ⏹ Stop del board
|
|
436
|
-
// sepa qué procesos matar. trackRun usa Set, así que llamadas
|
|
437
|
-
// duplicadas no inflan el registro. Cuando el proceso muera, el
|
|
438
|
-
// event 'exit' lo des-registra (defensa adicional: reaper periódico
|
|
439
|
-
// que limpia PIDs muertos cada 30s).
|
|
440
|
-
if (child.pid) {
|
|
441
|
-
trackRun(planId, { pid: child.pid, huIds: huIds || null });
|
|
442
|
-
child.on('exit', () => { untrack(planId, child.pid); });
|
|
443
|
-
}
|
|
444
|
-
return { ok: true, pid: child.pid ?? 0, logPath };
|
|
445
|
-
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// KJC-TSK-0541: reading the plan JSONs, shared by the API routers and testable without Express.
|
|
2
|
+
// Every scan covers the canonical and the legacy plans roots (KJC-BUG-0059).
|
|
3
|
+
import fsp from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
import { getHuBoardPlansDirs } from './db.js';
|
|
7
|
+
|
|
8
|
+
/** Async existence probe (KJC-TSK-0539: no sync fs in handlers). */
|
|
9
|
+
export const pathExists = (p) => fsp.access(p).then(() => true, () => false);
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* mtime-keyed cache for parsed plan JSON (KJC-TSK-0539). The plan scans
|
|
13
|
+
* used to re-read and re-parse every plan file on EVERY request; with N
|
|
14
|
+
* plans and the board polling, that was O(N) parses per poll. The cache
|
|
15
|
+
* serves the parsed object while the file's mtime is unchanged and
|
|
16
|
+
* re-reads transparently when kj writes the plan.
|
|
17
|
+
*/
|
|
18
|
+
const planFileCache = new Map();
|
|
19
|
+
export async function readPlanCached(p) {
|
|
20
|
+
const { mtimeMs } = await fsp.stat(p);
|
|
21
|
+
const hit = planFileCache.get(p);
|
|
22
|
+
if (hit && hit.mtimeMs === mtimeMs) return hit.plan;
|
|
23
|
+
const plan = JSON.parse(await fsp.readFile(p, 'utf8'));
|
|
24
|
+
planFileCache.set(p, { mtimeMs, plan });
|
|
25
|
+
return plan;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Every readable plan JSON of a project; an unreadable file is skipped. */
|
|
29
|
+
export async function* projectPlans(projectId) {
|
|
30
|
+
for (const root of getHuBoardPlansDirs()) {
|
|
31
|
+
const plansDir = path.join(root, projectId);
|
|
32
|
+
if (!(await pathExists(plansDir))) continue;
|
|
33
|
+
for (const f of (await fsp.readdir(plansDir)).filter((n) => n.endsWith('.json'))) {
|
|
34
|
+
let plan;
|
|
35
|
+
try {
|
|
36
|
+
plan = await readPlanCached(path.join(plansDir, f));
|
|
37
|
+
} catch {
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
40
|
+
yield plan;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** The path of `plan-<planId>.json` under any project of any plans root, or null. */
|
|
46
|
+
export async function findPlanFile(planId) {
|
|
47
|
+
for (const plansRoot of getHuBoardPlansDirs()) {
|
|
48
|
+
if (!(await pathExists(plansRoot))) continue;
|
|
49
|
+
for (const projSlug of await fsp.readdir(plansRoot)) {
|
|
50
|
+
const candidate = path.join(plansRoot, projSlug, `plan-${planId}.json`);
|
|
51
|
+
if (await pathExists(candidate)) return candidate;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
// KJC-TSK-0541: the kj commands the board launches, their run logs and the retired `kj run`
|
|
2
|
+
// routes, composed by routes/api.js.
|
|
3
|
+
import { Router } from 'express';
|
|
4
|
+
import fsp from 'node:fs/promises';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
|
|
7
|
+
import { getHuBoardRunsDir } from '../../db.js';
|
|
8
|
+
import { runKjCommand, listSupportedCommands, startCardInLane } from '../../command-runner.js';
|
|
9
|
+
import { pathExists } from '../../plans-service.js';
|
|
10
|
+
import { requestProjectDir } from '../../project-dir.js';
|
|
11
|
+
|
|
12
|
+
const router = Router();
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* KJC-TSK-1319: the board launched `kj run` for a plan, an HU or a whole
|
|
16
|
+
* project. kj run is retired in 5.0 (your agent does the work, kj governs
|
|
17
|
+
* it), so these routes say so and launch nothing. The board never imports
|
|
18
|
+
* the CLI tree (KJC-TSK-0632): the notice lives here.
|
|
19
|
+
*/
|
|
20
|
+
const RUN_RETIRED = {
|
|
21
|
+
error: 'kj run was retired in 5.0. Your agent (Claude Code, OpenCode...) does the work now and kj governs it: split the task with kj brief planner + kj hu add, one lane per card with kj worktree start, and kj review --staged before every commit.',
|
|
22
|
+
guide: 'https://github.com/manufosela/karajan-code/blob/main/MIGRATION-v5.md',
|
|
23
|
+
};
|
|
24
|
+
router.post(['/plans/:planId/run', '/hus/:huId/run', '/projects/:id/run'], (_req, res) => {
|
|
25
|
+
res.status(410).json(RUN_RETIRED);
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* GET /api/commands - List the kj commands the board can launch.
|
|
30
|
+
* The UI uses this to render the Actions panel without hardcoding
|
|
31
|
+
* the command list on the client.
|
|
32
|
+
*/
|
|
33
|
+
router.get('/commands', (_req, res) => {
|
|
34
|
+
res.json({ commands: listSupportedCommands() });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* POST /api/commands/:command - Spawn a kj <command> as a detached
|
|
39
|
+
* child. Body fields depend on the command (see command-runner.js
|
|
40
|
+
* for the per-command input schema). Returns a commandId the client
|
|
41
|
+
* can pass to GET /api/runs/:commandId/log to tail the output.
|
|
42
|
+
*
|
|
43
|
+
* The board never executes free-form arg arrays — the command name
|
|
44
|
+
* must be in the whitelist and each command builds its argv from a
|
|
45
|
+
* typed schema. Anything outside that fails 400.
|
|
46
|
+
*/
|
|
47
|
+
router.post('/commands/:command', (req, res) => {
|
|
48
|
+
try {
|
|
49
|
+
const result = runKjCommand({ command: req.params.command, input: req.body || {} });
|
|
50
|
+
if (!result.ok) return res.status(400).json({ error: result.error });
|
|
51
|
+
res.json({
|
|
52
|
+
ok: true,
|
|
53
|
+
commandId: result.commandId,
|
|
54
|
+
pid: result.pid,
|
|
55
|
+
logPath: result.logPath,
|
|
56
|
+
});
|
|
57
|
+
} catch (err) {
|
|
58
|
+
res.status(500).json({ error: err.message });
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* POST /api/hu/:id/start - KJC-TSK-1324: the ▶ of a card. The body names the board project;
|
|
64
|
+
* its directory is resolved here, never taken from the client (ADR 0011). A card that already
|
|
65
|
+
* has a lane gets 409 with that lane and nothing is launched; otherwise `kj hu start <id>
|
|
66
|
+
* --code` runs detached and the response carries the commandId to tail.
|
|
67
|
+
*/
|
|
68
|
+
router.post('/hu/:id/start', (req, res) => {
|
|
69
|
+
let projectDir;
|
|
70
|
+
try {
|
|
71
|
+
projectDir = requestProjectDir(req.body?.project);
|
|
72
|
+
} catch (err) {
|
|
73
|
+
return res.status(400).json({ error: err.message });
|
|
74
|
+
}
|
|
75
|
+
if (!projectDir) return res.status(400).json({ error: 'name the board project the card belongs to' });
|
|
76
|
+
try {
|
|
77
|
+
const result = startCardInLane({ id: req.params.id, projectDir });
|
|
78
|
+
if (result.lane) return res.status(409).json({ error: result.error, lane: result.lane });
|
|
79
|
+
if (!result.ok) return res.status(400).json({ error: result.error });
|
|
80
|
+
return res.json({ ok: true, commandId: result.commandId, pid: result.pid, logPath: result.logPath });
|
|
81
|
+
} catch (err) {
|
|
82
|
+
return res.status(500).json({ error: err.message });
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* GET /api/runs/:commandId/log - Generalisation of the per-plan log
|
|
88
|
+
* tail. The original `/api/plans/:planId/log` reads
|
|
89
|
+
* `<runsDir>/<planId>.log`; this one reads `<runsDir>/<commandId>.log`
|
|
90
|
+
* where `commandId` is what `/api/commands/:command` returned.
|
|
91
|
+
*
|
|
92
|
+
* Same offset semantics + 1 MiB cap as the plans endpoint.
|
|
93
|
+
*/
|
|
94
|
+
router.get('/runs/:commandId/log', async (req, res) => {
|
|
95
|
+
try {
|
|
96
|
+
const runsDir = getHuBoardRunsDir(); // KJC-TSK-0421
|
|
97
|
+
const logPath = path.join(runsDir, `${req.params.commandId}.log`);
|
|
98
|
+
if (!(await pathExists(logPath))) {
|
|
99
|
+
return res.json({ exists: false, size: 0, content: '' });
|
|
100
|
+
}
|
|
101
|
+
const stat = await fsp.stat(logPath);
|
|
102
|
+
const offset = Math.max(0, Math.min(stat.size, Number(req.query.offset) || 0));
|
|
103
|
+
const MAX_READ = 1024 * 1024;
|
|
104
|
+
const length = Math.min(MAX_READ, stat.size - offset);
|
|
105
|
+
let content = '';
|
|
106
|
+
if (length > 0) {
|
|
107
|
+
const fh = await fsp.open(logPath, 'r');
|
|
108
|
+
try {
|
|
109
|
+
const buf = Buffer.alloc(length);
|
|
110
|
+
await fh.read(buf, 0, length, offset);
|
|
111
|
+
content = buf.toString('utf-8');
|
|
112
|
+
} finally {
|
|
113
|
+
await fh.close();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
res.json({ exists: true, size: stat.size, content });
|
|
117
|
+
} catch (err) {
|
|
118
|
+
res.status(500).json({ error: err.message });
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
export default router;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// KJC-TSK-0541: the plan endpoints of /api, composed by routes/api.js.
|
|
2
|
+
import { Router } from 'express';
|
|
3
|
+
import fsp from 'node:fs/promises';
|
|
4
|
+
|
|
5
|
+
import { addTombstone } from '../../db.js';
|
|
6
|
+
import { markPlanReady } from '../../plan-mutations.js';
|
|
7
|
+
import { findPlanFile } from '../../plans-service.js';
|
|
8
|
+
|
|
9
|
+
const router = Router();
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* DELETE /api/plans/:planId - Delete a single plan file.
|
|
13
|
+
*
|
|
14
|
+
* Removes `~/.kj/plans/<projectSlug>/plan-<planId>.json` and tombstones
|
|
15
|
+
* the plan id so the next watcher event doesn't re-sync it. Does NOT
|
|
16
|
+
* delete the project: a project can have multiple plans, deleting one
|
|
17
|
+
* plan only removes that plan's HUs from the board.
|
|
18
|
+
*/
|
|
19
|
+
router.delete('/plans/:planId', async (req, res) => {
|
|
20
|
+
try {
|
|
21
|
+
const planId = req.params.planId;
|
|
22
|
+
// Locate the plan file by scanning both canonical and legacy plans
|
|
23
|
+
// roots — KJC-BUG-0059 (v2.19.3). Pre-fix this hard-coded
|
|
24
|
+
// `getKjHome()/../.kj/plans` which silently misses plans saved by
|
|
25
|
+
// post-2.19.0 callers under `~/.karajan/plans/`.
|
|
26
|
+
let foundPath = null;
|
|
27
|
+
try {
|
|
28
|
+
foundPath = await findPlanFile(planId);
|
|
29
|
+
} catch { /* plansRoot missing */ }
|
|
30
|
+
addTombstone('plan', planId, { source: 'api', fsPaths: foundPath ? [foundPath] : [] });
|
|
31
|
+
if (foundPath) { try { await fsp.unlink(foundPath); } catch { /* */ } }
|
|
32
|
+
res.json({ deleted: true, fileRemoved: !!foundPath });
|
|
33
|
+
} catch (err) {
|
|
34
|
+
res.status(500).json({ error: err.message });
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* POST /api/plans/:planId/ready - Bulk-certify every pending HU of a plan.
|
|
40
|
+
* Equivalent to the CLI `kj plan ready <planId>`. Body is optional — if the
|
|
41
|
+
* client knows the projectId we use it as a fast path, otherwise we fall
|
|
42
|
+
* back to scanning the plans dir.
|
|
43
|
+
*/
|
|
44
|
+
router.post('/plans/:planId/ready', (req, res) => {
|
|
45
|
+
try {
|
|
46
|
+
const { projectId } = req.body || {};
|
|
47
|
+
const result = markPlanReady({ planId: req.params.planId, projectId });
|
|
48
|
+
if (!result.ok) return res.status(404).json({ error: result.error });
|
|
49
|
+
res.json({ ready: true, planId: req.params.planId, count: result.count, planStatus: result.planStatus });
|
|
50
|
+
} catch (err) {
|
|
51
|
+
res.status(500).json({ error: err.message });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
export default router;
|