karajan-code 3.12.1 → 3.12.2

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.
Files changed (57) hide show
  1. package/package.json +2 -5
  2. package/packages/hu-board/package.json +1 -1
  3. package/packages/hu-board/src/plan-mutations.js +4 -4
  4. package/packages/hu-board/src/routes/api.js +1 -1
  5. package/packages/hu-board/src/routes/rag.js +1 -1
  6. package/packages/hu-board/src/routes/wiki.js +1 -1
  7. package/packages/hu-board/src/run-tracker.js +1 -1
  8. package/packages/hu-board/src/server.js +2 -2
  9. package/packages/hu-board/src/sync.js +1 -1
  10. package/scripts/verify-pack.mjs +38 -6
  11. package/src/brain/standby-scheduler.js +2 -2
  12. package/src/brain/standby-store.js +2 -2
  13. package/src/git/hu-snapshot.js +2 -2
  14. package/src/plan/plan-hu-ops.js +2 -2
  15. package/src/plan/plan-id.js +2 -2
  16. package/src/plan/plan-validation.js +2 -2
  17. package/src/rag/vec-store.js +2 -2
  18. package/src/utils/atomic-write.js +3 -3
  19. package/src/utils/paths.js +2 -2
  20. package/src/utils/port-check.js +3 -3
  21. package/src/utils/process.js +2 -2
  22. package/src/utils/run-registry.js +2 -2
  23. package/src/utils/shared-paths.js +3 -3
  24. package/node_modules/@karajan/core/README.md +0 -42
  25. package/node_modules/@karajan/core/package.json +0 -49
  26. package/node_modules/@karajan/core/src/atomic-write.js +0 -43
  27. package/node_modules/@karajan/core/src/cost/index.js +0 -102
  28. package/node_modules/@karajan/core/src/hu-snapshot.js +0 -105
  29. package/node_modules/@karajan/core/src/index.js +0 -18
  30. package/node_modules/@karajan/core/src/paths.js +0 -123
  31. package/node_modules/@karajan/core/src/plan-hu-ops.js +0 -338
  32. package/node_modules/@karajan/core/src/plan-id.js +0 -45
  33. package/node_modules/@karajan/core/src/plan-validation.js +0 -82
  34. package/node_modules/@karajan/core/src/port-check.js +0 -45
  35. package/node_modules/@karajan/core/src/pricing/index.js +0 -64
  36. package/node_modules/@karajan/core/src/pricing/model-pricing.json +0 -28
  37. package/node_modules/@karajan/core/src/process.js +0 -188
  38. package/node_modules/@karajan/core/src/run-registry.js +0 -115
  39. package/node_modules/@karajan/core/src/shared-paths.js +0 -41
  40. package/node_modules/@karajan/core/src/standby-scheduler.js +0 -92
  41. package/node_modules/@karajan/core/src/standby-store.js +0 -168
  42. package/node_modules/@karajan/core/src/vec-store.js +0 -253
  43. package/node_modules/@karajan/core/tests/atomic-write.test.js +0 -30
  44. package/node_modules/@karajan/core/tests/cost/index.test.js +0 -89
  45. package/node_modules/@karajan/core/tests/hu-snapshot.test.js +0 -39
  46. package/node_modules/@karajan/core/tests/paths.test.js +0 -51
  47. package/node_modules/@karajan/core/tests/plan-hu-ops.test.js +0 -68
  48. package/node_modules/@karajan/core/tests/plan-id.test.js +0 -21
  49. package/node_modules/@karajan/core/tests/plan-validation.test.js +0 -39
  50. package/node_modules/@karajan/core/tests/port-check.test.js +0 -47
  51. package/node_modules/@karajan/core/tests/pricing/index.test.js +0 -95
  52. package/node_modules/@karajan/core/tests/process.test.js +0 -22
  53. package/node_modules/@karajan/core/tests/run-registry.test.js +0 -71
  54. package/node_modules/@karajan/core/tests/shared-paths.test.js +0 -30
  55. package/node_modules/@karajan/core/tests/standby.test.js +0 -66
  56. package/node_modules/@karajan/core/tests/vec-store.test.js +0 -98
  57. package/node_modules/@karajan/core/vitest.config.js +0 -12
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "karajan-code",
3
- "version": "3.12.1",
3
+ "version": "3.12.2",
4
4
  "description": "Local multi-agent coding orchestrator with TDD, SonarQube, and code review pipeline",
5
5
  "type": "module",
6
6
  "license": "AGPL-3.0",
@@ -98,7 +98,6 @@
98
98
  },
99
99
  "dependencies": {
100
100
  "@babel/parser": "^7.29.7",
101
- "@karajan/core": "*",
102
101
  "@modelcontextprotocol/sdk": "^1.29.0",
103
102
  "better-sqlite3": "^12.10.0",
104
103
  "chokidar": "^5.0.0",
@@ -108,15 +107,13 @@
108
107
  "express-rate-limit": "^8.5.0",
109
108
  "helmet": "^8.1.0",
110
109
  "js-yaml": "^4.2.0",
110
+ "karajan-core": "^1.0.0",
111
111
  "knip": "^6.15.0",
112
112
  "madge": "^8.0.0",
113
113
  "sqlite-vec": "^0.1.9",
114
114
  "valibot": "^1.4.1",
115
115
  "web-tree-sitter": "^0.26.9"
116
116
  },
117
- "bundleDependencies": [
118
- "@karajan/core"
119
- ],
120
117
  "devDependencies": {
121
118
  "@eslint/js": "^10.0.1",
122
119
  "@vitest/coverage-v8": "^4.1.8",
@@ -13,7 +13,7 @@
13
13
  "test": "vitest run"
14
14
  },
15
15
  "dependencies": {
16
- "@karajan/core": "*",
16
+ "karajan-core": "*",
17
17
  "better-sqlite3": "^12.10.0",
18
18
  "chokidar": "^5.0.0",
19
19
  "express": "^5.1.0",
@@ -17,13 +17,13 @@
17
17
  import { readFileSync, existsSync, readdirSync, mkdirSync, openSync } from 'node:fs';
18
18
  import { join, dirname } from 'node:path';
19
19
  import { homedir } from 'node:os';
20
- import { writeJsonAtomicSync } from '@karajan/core/atomic-write';
20
+ import { writeJsonAtomicSync } from 'karajan-core/atomic-write';
21
21
  import { spawn } from 'node:child_process';
22
22
  import { trackRun, untrack } from './run-tracker.js';
23
23
  import { getHuBoardRunsDir } from './db.js';
24
24
  import { fileURLToPath } from 'node:url';
25
- import { updateHuStatus, certifyAllHus, updateHu } from '@karajan/core/plan-hu-ops';
26
- import { validateBlockedByChange } from '@karajan/core/plan-validation';
25
+ import { updateHuStatus, certifyAllHus, updateHu } from 'karajan-core/plan-hu-ops';
26
+ import { validateBlockedByChange } from 'karajan-core/plan-validation';
27
27
  import { syncPlanFile } from './sync.js';
28
28
  import { getDb } from './db.js';
29
29
 
@@ -230,7 +230,7 @@ export async function revertHuFromSnapshot({ planId, huId, projectId }) {
230
230
  const projectDir = plan.projectDir;
231
231
  if (!projectDir) return { ok: false, error: 'plan sin projectDir' };
232
232
 
233
- const { restoreHuSnapshot } = await import('@karajan/core/hu-snapshot');
233
+ const { restoreHuSnapshot } = await import('karajan-core/hu-snapshot');
234
234
  const restore = await restoreHuSnapshot({ projectDir, huId });
235
235
  if (!restore.ok) return { ok: false, error: `restore falló: ${restore.error}` };
236
236
 
@@ -1361,7 +1361,7 @@ router.post('/rag/query', async (req, res) => {
1361
1361
  return res.status(400).json({ error: "'text' is required (non-empty string)" });
1362
1362
  }
1363
1363
  try {
1364
- const { openVecStore, countChunks } = await import('@karajan/core/vec-store');
1364
+ const { openVecStore, countChunks } = await import('karajan-core/vec-store');
1365
1365
  const { makeEmbedder } = await import('../../../../src/rag/embedders/factory.js');
1366
1366
  const { query } = await import('../../../../src/rag/retriever.js');
1367
1367
  const db = openVecStore({ dim: 768 });
@@ -5,7 +5,7 @@
5
5
  import { Router } from "express";
6
6
  import { existsSync, statSync } from "node:fs";
7
7
  import Database from "better-sqlite3";
8
- import { dbPath } from "@karajan/core/vec-store";
8
+ import { dbPath } from "karajan-core/vec-store";
9
9
  import { readConfig } from "../config-yaml.js";
10
10
 
11
11
  const router = Router();
@@ -4,7 +4,7 @@
4
4
  // 503 with installable:true so the front renders an "Install QMD" CTA
5
5
  // instead of a generic error when the binary is missing.
6
6
  import { Router } from "express";
7
- import { runCommand } from "@karajan/core/process";
7
+ import { runCommand } from "karajan-core/process";
8
8
 
9
9
  const router = Router();
10
10
  const QMD_NOT_INSTALLED = /command not found|ENOENT|not recognized as/i;
@@ -16,7 +16,7 @@
16
16
  * se borran como cleanup (responsabilidad de run-registry).
17
17
  */
18
18
 
19
- import { listActiveRuns as fsList } from "@karajan/core/run-registry";
19
+ import { listActiveRuns as fsList } from "karajan-core/run-registry";
20
20
 
21
21
  const runs = new Map();
22
22
  let reaperTimer = null;
@@ -17,7 +17,7 @@ import { reapZombieSessions } from './zombie-reaper.js';
17
17
  import { reapZombieHus } from './hu-zombie-reaper.js';
18
18
  import { setHuStatus as setHuStatusPlanMutation, setHuFailResult as setHuFailResultPlanMutation } from './plan-mutations.js';
19
19
  import { cleanupEphemeralProjects } from './ephemeral-cleaner.js';
20
- import { findAvailablePort as findAvailablePortBase } from '@karajan/core/port-check';
20
+ import { findAvailablePort as findAvailablePortBase } from 'karajan-core/port-check';
21
21
 
22
22
  /**
23
23
  * Path to the PID file the CLI's `kj board start` / `kj board stop`
@@ -238,7 +238,7 @@ async function main() {
238
238
  // - cooldownUntil > now → re-programa setTimeout que dispara en cooldown
239
239
  // Idempotente (lockfile per session). Cero polling.
240
240
  try {
241
- const { reconcileAll } = await import('@karajan/core/standby-scheduler');
241
+ const { reconcileAll } = await import('karajan-core/standby-scheduler');
242
242
  const { spawn } = await import('node:child_process');
243
243
  const { join, dirname } = await import('node:path');
244
244
  const { fileURLToPath } = await import('node:url');
@@ -14,7 +14,7 @@ import {
14
14
  removeTombstone,
15
15
  } from './db.js';
16
16
  import { publish as publishEvent } from './event-bus.js';
17
- import { getSharedPlansDir, SHARED_DIR_NAME, isSharedPath } from '@karajan/core/shared-paths';
17
+ import { getSharedPlansDir, SHARED_DIR_NAME, isSharedPath } from 'karajan-core/shared-paths';
18
18
  import { readConfig } from './config-yaml.js';
19
19
 
20
20
  /**
@@ -3,7 +3,7 @@
3
3
  * Pre-publish tarball smoke gate (KJC-TSK-0553).
4
4
  *
5
5
  * WHY: the test suite runs against the linked workspace, where
6
- * `@karajan/core` and its deps resolve via symlink — so a broken
6
+ * `karajan-core` and its deps resolve via symlink — so a broken
7
7
  * PUBLISHED artifact (bundleDependencies dragging in sqlite-vec without
8
8
  * its entry point, KJC-BUG-0082 / 0086) passed CI yet failed on every
9
9
  * clean `npm install`. v3.2.0, v3.3.0 and v3.4.1 all shipped unable to
@@ -50,6 +50,7 @@ function fail(msg, detail) {
50
50
 
51
51
  let tgzPath = null;
52
52
  let tmpDir = null;
53
+ let gTmp = null;
53
54
  let pnpmTmp = null;
54
55
  try {
55
56
  console.log(`verify-pack: packing karajan-code@${expectedVersion}…`);
@@ -92,15 +93,16 @@ try {
92
93
  console.log("verify-pack: kj --help ✓");
93
94
 
94
95
  // 3. The deps that broke before must resolve in the installed tree.
95
- const installedRoot = path.join(tmpDir, "node_modules", "karajan-code");
96
+ // karajan-core is a registry dep since KJC-BUG-0103 (no bundling), so it
97
+ // hoists to the consumer's top-level node_modules like everything else.
96
98
  const checks = [
97
99
  path.join(tmpDir, "node_modules", "sqlite-vec", "index.mjs"),
98
- path.join(installedRoot, "node_modules", "@karajan", "core", "src", "vec-store.js"),
100
+ path.join(tmpDir, "node_modules", "karajan-core", "src", "vec-store.js"),
99
101
  ];
100
102
  for (const p of checks) {
101
103
  if (!fs.existsSync(p)) fail(`expected resolved path missing in install: ${p}`);
102
104
  }
103
- console.log("verify-pack: sqlite-vec + @karajan/core resolve ✓");
105
+ console.log("verify-pack: sqlite-vec + karajan-core resolve ✓");
104
106
 
105
107
  // 4. kj-trash (ai-trash safety binary) must ship + link onto PATH and boot
106
108
  // without a module-resolution crash. It was silently absent from the tarball
@@ -115,9 +117,38 @@ try {
115
117
  }
116
118
  console.log("verify-pack: kj-trash ships + boots ✓");
117
119
 
118
- // 5. pnpm install smoke (KJC-TSK-0580). pnpm's layout differs from npm's
120
+ // 5. GLOBAL install smoke (KJC-BUG-0103). `npm install -g` nests every dep
121
+ // under karajan-code/node_modules; with bundleDependencies present, npm
122
+ // treated that whole subtree as "already in the tarball" and skipped
123
+ // fetching better-sqlite3 & friends — EMPTY dirs whose install scripts then
124
+ // crashed. Local installs hoist and never hit it, which is exactly why the
125
+ // check above stayed green for ~15 broken releases. A fresh global install
126
+ // must succeed and kj must boot.
127
+ gTmp = fs.mkdtempSync(path.join(os.tmpdir(), "kj-verify-g-"));
128
+ console.log(`verify-pack: installing the tarball GLOBALLY into ${gTmp}…`);
129
+ try {
130
+ run("npm", ["install", "-g", tgzPath, "--no-audit", "--no-fund", "--silent", "--prefix", gTmp]);
131
+ } catch (err) {
132
+ fail("`npm install -g` of the tarball failed (KJC-BUG-0103 class)", err.stderr || err.message);
133
+ }
134
+ const gBin = process.platform === "win32"
135
+ ? path.join(gTmp, "kj.cmd")
136
+ : path.join(gTmp, "bin", "kj");
137
+ if (!fs.existsSync(gBin)) fail(`kj binary missing after global install: ${gBin}`);
138
+ let gVersion;
139
+ try {
140
+ gVersion = run(gBin, ["--version"]).trim();
141
+ } catch (err) {
142
+ fail("`kj --version` crashed on a fresh GLOBAL install", err.stderr || err.message);
143
+ }
144
+ if (!gVersion.includes(expectedVersion)) {
145
+ fail(`kj --version (global) returned "${gVersion}", expected ${expectedVersion}`);
146
+ }
147
+ console.log(`verify-pack: global install + kj --version → ${gVersion} ✓`);
148
+
149
+ // 6. pnpm install smoke (KJC-TSK-0580). pnpm's layout differs from npm's
119
150
  // (a symlinked virtual store), so it can break resolution of the bundled
120
- // @karajan/core the way npm packaging breakage did before (KJC-BUG-0082/0086).
151
+ // karajan-core the way npm packaging breakage did before (KJC-BUG-0082/0086).
121
152
  // Verify a pnpm install resolves the JS tree and `kj --version` boots — every
122
153
  // release catches a pnpm packaging regression. (pnpm also blocks
123
154
  // better-sqlite3's native build by default, so DB-backed commands need
@@ -158,5 +189,6 @@ try {
158
189
  } finally {
159
190
  if (tgzPath && fs.existsSync(tgzPath)) fs.rmSync(tgzPath, { force: true });
160
191
  if (tmpDir && fs.existsSync(tmpDir)) fs.rmSync(tmpDir, { recursive: true, force: true });
192
+ if (gTmp && fs.existsSync(gTmp)) fs.rmSync(gTmp, { recursive: true, force: true });
161
193
  if (pnpmTmp && fs.existsSync(pnpmTmp)) fs.rmSync(pnpmTmp, { recursive: true, force: true });
162
194
  }
@@ -1,8 +1,8 @@
1
- // Shim de compatibilidad: standby-scheduler vive ahora en @karajan/core.
1
+ // Shim de compatibilidad: standby-scheduler vive ahora en karajan-core.
2
2
  // Mantiene la ruta para callers internos del CLI.
3
3
  export {
4
4
  _resetScheduledForTests,
5
5
  scheduleResume,
6
6
  cancelScheduled,
7
7
  reconcileAll,
8
- } from "@karajan/core/standby-scheduler";
8
+ } from "karajan-core/standby-scheduler";
@@ -1,4 +1,4 @@
1
- // Shim de compatibilidad: standby-store vive ahora en @karajan/core.
1
+ // Shim de compatibilidad: standby-store vive ahora en karajan-core.
2
2
  // Mantiene la ruta para callers internos del CLI.
3
3
  export {
4
4
  standbyDir,
@@ -9,4 +9,4 @@ export {
9
9
  listPendingStandby,
10
10
  markStandbyDone,
11
11
  acquireStandbyLock,
12
- } from "@karajan/core/standby-store";
12
+ } from "karajan-core/standby-store";
@@ -1,4 +1,4 @@
1
- // Shim de compatibilidad: hu-snapshot vive ahora en @karajan/core.
1
+ // Shim de compatibilidad: hu-snapshot vive ahora en karajan-core.
2
2
  // Mantenemos esta ruta para que callers internos del CLI (orchestrator,
3
3
  // runners) sigan funcionando sin tocar imports.
4
4
  export {
@@ -7,4 +7,4 @@ export {
7
7
  hasHuSnapshot,
8
8
  restoreHuSnapshot,
9
9
  removeHuSnapshot,
10
- } from "@karajan/core/hu-snapshot";
10
+ } from "karajan-core/hu-snapshot";
@@ -1,4 +1,4 @@
1
- // Shim: plan-hu-ops now lives in @karajan/core/plan-hu-ops so the
1
+ // Shim: plan-hu-ops now lives in karajan-core/plan-hu-ops so the
2
2
  // hu-board workspace can consume it without a relative dep on the CLI
3
3
  // src tree. KJC-TSK-0511 PR5.
4
4
  export {
@@ -13,4 +13,4 @@ export {
13
13
  computePlanOutcome,
14
14
  certifyAllHus,
15
15
  reorderHus,
16
- } from "@karajan/core/plan-hu-ops";
16
+ } from "karajan-core/plan-hu-ops";
@@ -1,4 +1,4 @@
1
- // Shim: plan-id primitives now live in @karajan/core/plan-id so the
1
+ // Shim: plan-id primitives now live in karajan-core/plan-id so the
2
2
  // hu-board workspace can consume them without a relative dep on the
3
3
  // CLI src tree. KJC-TSK-0511 PR5.
4
- export { generatePlanId, generateHuId, normaliseAlias } from "@karajan/core/plan-id";
4
+ export { generatePlanId, generateHuId, normaliseAlias } from "karajan-core/plan-id";
@@ -1,4 +1,4 @@
1
- // Shim: plan-validation now lives in @karajan/core/plan-validation so
1
+ // Shim: plan-validation now lives in karajan-core/plan-validation so
2
2
  // the hu-board workspace can consume it without a relative dep on the
3
3
  // CLI src tree. KJC-TSK-0511 PR6.
4
- export { validateBlockedByChange } from "@karajan/core/plan-validation";
4
+ export { validateBlockedByChange } from "karajan-core/plan-validation";
@@ -1,4 +1,4 @@
1
- // Shim: vec-store primitives now live in @karajan/core/vec-store so
1
+ // Shim: vec-store primitives now live in karajan-core/vec-store so
2
2
  // the hu-board workspace can consume them without a relative dep on the
3
3
  // CLI src tree. KJC-TSK-0511 PR4.
4
4
  export {
@@ -14,4 +14,4 @@ export {
14
14
  findChunkByHash,
15
15
  countChunks,
16
16
  searchBM25,
17
- } from "@karajan/core/vec-store";
17
+ } from "karajan-core/vec-store";
@@ -1,6 +1,6 @@
1
- // Shim — the real implementation now lives in @karajan/core/atomic-write.
1
+ // Shim — the real implementation now lives in karajan-core/atomic-write.
2
2
  // Kept here so CLI callers (src/**/*.js) keep importing
3
3
  // "#utils/atomic-write.js" without churn during KJC-TSK-0511 (decouple
4
4
  // @karajan/hu-board). Will be retired once every consumer migrates to
5
- // the @karajan/core import.
6
- export { writeJsonAtomic, writeJsonAtomicSync } from "@karajan/core/atomic-write";
5
+ // the karajan-core import.
6
+ export { writeJsonAtomic, writeJsonAtomicSync } from "karajan-core/atomic-write";
@@ -1,4 +1,4 @@
1
- // Shim: paths primitives now live in @karajan/core/paths so the
1
+ // Shim: paths primitives now live in karajan-core/paths so the
2
2
  // hu-board workspace can consume them without a relative dep on the
3
3
  // CLI src tree. KJC-TSK-0511 PR3.
4
4
  export {
@@ -10,4 +10,4 @@ export {
10
10
  getOllamaComposePath,
11
11
  getWebperfDir,
12
12
  getPromptsDir,
13
- } from "@karajan/core/paths";
13
+ } from "karajan-core/paths";
@@ -1,6 +1,6 @@
1
- // Shim — the real implementation now lives in @karajan/core/port-check.
1
+ // Shim — the real implementation now lives in karajan-core/port-check.
2
2
  // Kept here so CLI callers (src/**/*.js) keep importing
3
3
  // "../utils/port-check.js" without churn during KJC-TSK-0511 (decouple
4
4
  // @karajan/hu-board). Will be retired once every consumer migrates to
5
- // the @karajan/core import.
6
- export { isPortAvailable, findAvailablePort } from "@karajan/core/port-check";
5
+ // the karajan-core import.
6
+ export { isPortAvailable, findAvailablePort } from "karajan-core/port-check";
@@ -1,4 +1,4 @@
1
- // Shim: process runner now lives in @karajan/core/process so the
1
+ // Shim: process runner now lives in karajan-core/process so the
2
2
  // hu-board workspace can consume it without a relative dep on the CLI
3
3
  // src tree. KJC-TSK-0511 PR6.
4
- export { runCommand } from "@karajan/core/process";
4
+ export { runCommand } from "karajan-core/process";
@@ -1,4 +1,4 @@
1
- // Shim: run-registry now lives in @karajan/core/run-registry so the
1
+ // Shim: run-registry now lives in karajan-core/run-registry so the
2
2
  // hu-board workspace can consume it without a relative dep on the CLI
3
3
  // src tree. KJC-TSK-0511 PR3.
4
4
  export {
@@ -6,4 +6,4 @@ export {
6
6
  registerRun,
7
7
  unregisterRun,
8
8
  listActiveRuns,
9
- } from "@karajan/core/run-registry";
9
+ } from "karajan-core/run-registry";
@@ -1,12 +1,12 @@
1
- // Shim — the real implementation now lives in @karajan/core/shared-paths.
1
+ // Shim — the real implementation now lives in karajan-core/shared-paths.
2
2
  // Kept here so CLI callers (src/**/*.js) keep importing
3
3
  // "../utils/shared-paths.js" without churn during KJC-TSK-0511 (decouple
4
4
  // @karajan/hu-board). Will be retired once every consumer migrates to
5
- // the @karajan/core import.
5
+ // the karajan-core import.
6
6
  export {
7
7
  SHARED_DIR_NAME,
8
8
  getSharedRoot,
9
9
  getSharedPlansDir,
10
10
  getSharedHuStoriesDir,
11
11
  isSharedPath,
12
- } from "@karajan/core/shared-paths";
12
+ } from "karajan-core/shared-paths";
@@ -1,42 +0,0 @@
1
- # @karajan/core
2
-
3
- Shared modules for the Karajan Code CLI and `@karajan/hu-board`. Private
4
- workspace package — not published to npm.
5
-
6
- ## Scope
7
-
8
- `@karajan/core` is the extraction destination for code that both the CLI
9
- package (`karajan-code`) and the HU Board package (`@karajan/hu-board`)
10
- need. Today it ships the atomic JSON writer; subsequent commits of
11
- KJC-TSK-0511 will land the shared paths, port-check, run-registry,
12
- process runner, plan validation, vector store, and plan/HU ops modules
13
- here.
14
-
15
- ## Exports
16
-
17
- | Subpath | Module |
18
- | --- | --- |
19
- | `@karajan/core/atomic-write` | `writeJsonAtomic`, `writeJsonAtomicSync` |
20
- | `@karajan/core/shared-paths` | `SHARED_DIR_NAME`, `getSharedRoot`, `getSharedPlansDir`, `getSharedHuStoriesDir`, `isSharedPath` |
21
- | `@karajan/core/port-check` | `isPortAvailable`, `findAvailablePort` |
22
- | `@karajan/core/paths` | `resolveHome`, `getKarajanHome`, `getSessionRoot`, `getSonarComposePath`, `getOllamaComposePath`, `getWebperfDir`, `getRunsDir`, `getPromptsDir`, `__resetKjHomeWarningForTests` |
23
- | `@karajan/core/run-registry` | `runsDir`, `registerRun`, `unregisterRun`, `listActiveRuns` |
24
- | `@karajan/core/vec-store` | `dbPath`, `openVecStore`, `insertChunk`, `searchSimilar`, `searchBM25`, `projectSlug`, `deleteChunksBySource`, `findChunkByHash`, `getEmbeddingsByIds`, `getLastIndexedCommit`, `setLastIndexedCommit`, `countChunks` |
25
- | `@karajan/core/plan-id` | `generatePlanId`, `generateHuId`, `normaliseAlias` |
26
- | `@karajan/core/plan-hu-ops` | `addHu`, `removeHu`, `updateHu`, `updateHuStatus`, `setHuOutcome`, `setPlanOutcome`, `autoCertifyPendingHus`, `assertPlanRunnable`, `computePlanOutcome`, `certifyAllHus`, `reorderHus` |
27
- | `@karajan/core/plan-validation` | `validateBlockedByChange` |
28
- | `@karajan/core/process` | `runCommand` (execa wrapper with output streaming, silence/total timeouts, ENOENT enrichment) |
29
- | `@karajan/core/hu-snapshot` | `snapshotRefForHu`, `createHuSnapshot`, `hasHuSnapshot`, `restoreHuSnapshot`, `removeHuSnapshot` |
30
- | `@karajan/core/standby-store` | `persistStandby`, `loadStandby`, `listPendingStandby`, `markStandbyDone`, `acquireStandbyLock`, `buildStandbyState`, `standbyDir`, `standbyDoneDir` |
31
- | `@karajan/core/standby-scheduler` | `scheduleResume`, `cancelScheduled`, `reconcileAll`, `acquireStandbyLock`, `markStandbyDone`, `_resetScheduledForTests` |
32
-
33
- The root export (`@karajan/core`) re-exports everything via the barrel
34
- in `src/index.js`, but prefer the subpath form so the bundler can
35
- tree-shake unused modules.
36
-
37
- ## Status
38
-
39
- KJC-TSK-0511 PR8 — `standby-store` + `standby-scheduler` extracted.
40
- With this PR `@karajan/hu-board` no longer reaches into `../../../src`
41
- from any static **or dynamic** import. PR9 will re-add
42
- `packages/hu-board` to the root `workspaces` array.
@@ -1,49 +0,0 @@
1
- {
2
- "name": "@karajan/core",
3
- "version": "0.1.0",
4
- "description": "Shared modules for Karajan Code CLI and @karajan/hu-board (atomic-write, paths, RAG store, plan ops, process runner).",
5
- "type": "module",
6
- "private": true,
7
- "engines": {
8
- "node": ">=22.22.1"
9
- },
10
- "exports": {
11
- "./atomic-write": "./src/atomic-write.js",
12
- "./shared-paths": "./src/shared-paths.js",
13
- "./port-check": "./src/port-check.js",
14
- "./paths": "./src/paths.js",
15
- "./run-registry": "./src/run-registry.js",
16
- "./vec-store": "./src/vec-store.js",
17
- "./plan-id": "./src/plan-id.js",
18
- "./plan-hu-ops": "./src/plan-hu-ops.js",
19
- "./plan-validation": "./src/plan-validation.js",
20
- "./process": "./src/process.js",
21
- "./hu-snapshot": "./src/hu-snapshot.js",
22
- "./standby-store": "./src/standby-store.js",
23
- "./standby-scheduler": "./src/standby-scheduler.js",
24
- "./pricing": "./src/pricing/index.js",
25
- "./cost": "./src/cost/index.js"
26
- },
27
- "scripts": {
28
- "test": "vitest run"
29
- },
30
- "peerDependencies": {
31
- "better-sqlite3": "^12.10.0",
32
- "execa": "^9.6.0",
33
- "sqlite-vec": "^0.1.9"
34
- },
35
- "peerDependenciesMeta": {
36
- "better-sqlite3": {
37
- "optional": true
38
- },
39
- "execa": {
40
- "optional": true
41
- },
42
- "sqlite-vec": {
43
- "optional": true
44
- }
45
- },
46
- "devDependencies": {
47
- "vitest": "^4.0.0"
48
- }
49
- }
@@ -1,43 +0,0 @@
1
- // Atomic JSON writes for persistent state (resilience audit, Phase 2).
2
- //
3
- // A plain `writeFile(path, JSON.stringify(...))` truncates the live file
4
- // before the new content lands. If the process is killed mid-write
5
- // (SIGKILL, crash, power loss) the file is left truncated and the only
6
- // good copy is already gone — the plan / session / standby JSON
7
- // silently corrupts.
8
- //
9
- // write-temp + rename fixes it: `rename` is atomic on the same
10
- // filesystem, so a reader sees either the old file or the complete new
11
- // one, never a half-written one. The temp file sits next to the target
12
- // (same dir → same FS) and is uniquely named so concurrent writers do
13
- // not clobber each other's temp. A crash before the rename leaves an
14
- // orphan `.tmp` but never a corrupt target.
15
-
16
- import { writeFileSync, renameSync } from "node:fs";
17
- import { writeFile, rename } from "node:fs/promises";
18
-
19
- function tempPath(target) {
20
- return `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}.tmp`;
21
- }
22
-
23
- /**
24
- * Atomically write `obj` as pretty JSON to `target` (synchronous).
25
- * @param {string} target - destination path; its directory must exist
26
- * @param {unknown} obj
27
- */
28
- export function writeJsonAtomicSync(target, obj) {
29
- const tmp = tempPath(target);
30
- writeFileSync(tmp, JSON.stringify(obj, null, 2), "utf-8");
31
- renameSync(tmp, target);
32
- }
33
-
34
- /**
35
- * Atomically write `obj` as pretty JSON to `target` (async).
36
- * @param {string} target - destination path; its directory must exist
37
- * @param {unknown} obj
38
- */
39
- export async function writeJsonAtomic(target, obj) {
40
- const tmp = tempPath(target);
41
- await writeFile(tmp, JSON.stringify(obj, null, 2), "utf-8");
42
- await rename(tmp, target);
43
- }
@@ -1,102 +0,0 @@
1
- /**
2
- * Cost aggregator (Cost B — KJC-TSK-0513).
3
- *
4
- * Turns a list of token-usage events emitted by the orchestrator into
5
- * a USD breakdown for a single run / HU. The pipeline calls this once
6
- * when a HU finishes; the result is what Cost C will persist in
7
- * board.db and Cost E will expose as `/api/projects/:id/cost`.
8
- *
9
- * Inputs are deliberately permissive: any extra field on an event is
10
- * ignored. We only require `role`, `model`, `tokensIn`, `tokensOut`.
11
- *
12
- * Unknown models do NOT throw. Their token counts accumulate into
13
- * `unknownModelTokens` and the function returns a flag so the caller
14
- * can surface it in the UI (orange badge instead of `$X.XXXX`).
15
- */
16
-
17
- import { getModelPricing } from "../pricing/index.js";
18
-
19
- const DEFAULT_ROUND_DECIMALS = 4;
20
-
21
- function round(n, decimals = DEFAULT_ROUND_DECIMALS) {
22
- const f = 10 ** decimals;
23
- return Math.round(n * f) / f;
24
- }
25
-
26
- function priceTokens(pricing, tokensIn, tokensOut) {
27
- const inUsd = (tokensIn / 1_000_000) * pricing.inputPerMTok;
28
- const outUsd = (tokensOut / 1_000_000) * pricing.outputPerMTok;
29
- return inUsd + outUsd;
30
- }
31
-
32
- /**
33
- * Aggregate cost of a run.
34
- *
35
- * @param {object} args
36
- * @param {Array<{role:string, model:string, tokensIn:number, tokensOut:number}>} args.events
37
- * @param {(level:"warn"|"info", msg:string, ctx?:object)=>void} [args.logger]
38
- * Optional structured logger. When a model is unknown we call
39
- * logger("warn", ...) so the user sees it once per run.
40
- * @returns {{
41
- * totalUsd:number,
42
- * byRole:Record<string,number>,
43
- * byModel:Record<string,number>,
44
- * unknownModelTokens:{ tokensIn:number, tokensOut:number, models:string[] },
45
- * hasUnknown:boolean,
46
- * currency:string
47
- * }}
48
- */
49
- export function aggregateRunCost({ events, logger } = {}) {
50
- const safeEvents = Array.isArray(events) ? events : [];
51
- const byRole = {};
52
- const byModel = {};
53
- const unknownModels = new Set();
54
- let totalUsd = 0;
55
- let unknownIn = 0;
56
- let unknownOut = 0;
57
-
58
- for (const ev of safeEvents) {
59
- if (!ev || typeof ev !== "object") continue;
60
- const role = typeof ev.role === "string" && ev.role.length > 0 ? ev.role : "unknown";
61
- const model = typeof ev.model === "string" ? ev.model : "";
62
- const tokensIn = Number.isFinite(ev.tokensIn) ? ev.tokensIn : 0;
63
- const tokensOut = Number.isFinite(ev.tokensOut) ? ev.tokensOut : 0;
64
- if (tokensIn === 0 && tokensOut === 0) continue;
65
-
66
- const pricing = getModelPricing(model);
67
- if (!pricing) {
68
- unknownIn += tokensIn;
69
- unknownOut += tokensOut;
70
- if (model.length > 0) unknownModels.add(model);
71
- if (typeof logger === "function") {
72
- logger("warn", "cost-aggregator: unknown model pricing", { model, role, tokensIn, tokensOut });
73
- }
74
- continue;
75
- }
76
-
77
- const cost = priceTokens(pricing, tokensIn, tokensOut);
78
- totalUsd += cost;
79
- byRole[role] = (byRole[role] || 0) + cost;
80
- byModel[model] = (byModel[model] || 0) + cost;
81
- }
82
-
83
- const roundedByRole = Object.fromEntries(
84
- Object.entries(byRole).map(([k, v]) => [k, round(v)])
85
- );
86
- const roundedByModel = Object.fromEntries(
87
- Object.entries(byModel).map(([k, v]) => [k, round(v)])
88
- );
89
-
90
- return {
91
- totalUsd: round(totalUsd),
92
- byRole: roundedByRole,
93
- byModel: roundedByModel,
94
- unknownModelTokens: {
95
- tokensIn: unknownIn,
96
- tokensOut: unknownOut,
97
- models: Array.from(unknownModels).sort(),
98
- },
99
- hasUnknown: unknownIn > 0 || unknownOut > 0,
100
- currency: "USD",
101
- };
102
- }