@hacksmith/doraval 0.2.45 → 0.2.46

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 (3) hide show
  1. package/README.md +8 -4
  2. package/bin/doraval.js +112 -105
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -1,10 +1,14 @@
1
1
  # doraval
2
2
 
3
- The context engineering toolkit for coding agents.
3
+ The context engineering toolkit for coding agent orchestrators.
4
4
 
5
- If you've ever shipped a Claude Code skill that stopped firing after a refactor, or wondered whether your plugin's structure actually matches what the agent expects doraval validates that before it becomes a runtime surprise.
5
+ If you're a senior engineer handing skills to new team members, a company publishing AI resources, or anyone who wants agents (and humans) to succeed on the first attempt instead of after days of debugging this is for you.
6
6
 
7
- > **Quick start:**
7
+ **The orchestrator problem:** Give 10 new engineers (or agents) a skill and only 3/10 succeed on the first try. 4/10 take hours. 7/10 take a day. 10/10 take days.
8
+
9
+ doraval helps you **left-shift success** — validate, scaffold, and manage context so the first attempt works across Claude, Cursor, Codex, Copilot, and whatever comes next.
10
+
11
+ > **Quick start (left-shift success in < 2 minutes):**
8
12
  > ```bash
9
13
  > # macOS
10
14
  > brew install saif-shines/tap/doraval
@@ -14,7 +18,7 @@ If you've ever shipped a Claude Code skill that stopped firing after a refactor,
14
18
  > npx @hacksmith/doraval validate .
15
19
  > ```
16
20
 
17
- Point it at any local directory or GitHub URL. It auto-detects what you have and tells you what's broken.
21
+ Validate before you hand a skill to a new engineer or publish it. It auto-detects issues across agents and tells you what's broken.
18
22
 
19
23
  ## Install
20
24
 
package/bin/doraval.js CHANGED
@@ -599,7 +599,7 @@ var init_dist = __esm(() => {
599
599
  var require_package = __commonJS((exports, module) => {
600
600
  module.exports = {
601
601
  name: "@hacksmith/doraval",
602
- version: "0.2.45",
602
+ version: "0.2.46",
603
603
  author: "Saif",
604
604
  repository: {
605
605
  type: "git",
@@ -612,7 +612,7 @@ var require_package = __commonJS((exports, module) => {
612
612
  doraval: "bin/doraval-wrapper.js",
613
613
  dora: "bin/doraval-wrapper.js"
614
614
  },
615
- description: "The context engineering toolkit for coding agents",
615
+ description: "The context engineering toolkit for coding agent orchestrators",
616
616
  engines: {
617
617
  bun: ">=1.2.0",
618
618
  node: ">=14.18.0"
@@ -4457,8 +4457,8 @@ async function killPort(port) {
4457
4457
  await new Promise((r) => setTimeout(r, 400));
4458
4458
  } catch {}
4459
4459
  }
4460
- function readPid() {
4461
- const file = getPidFile();
4460
+ function readPid(p) {
4461
+ const file = getPidFile(p);
4462
4462
  if (!existsSync19(file))
4463
4463
  return null;
4464
4464
  try {
@@ -4475,14 +4475,14 @@ function readPid() {
4475
4475
  return null;
4476
4476
  }
4477
4477
  }
4478
- function writePid(pid) {
4478
+ function writePid(pid, p) {
4479
4479
  ensureDoravalDirs();
4480
- writeFileSync6(getPidFile(), String(pid) + `
4480
+ writeFileSync6(getPidFile(p), String(pid) + `
4481
4481
  `);
4482
4482
  }
4483
- function removePid() {
4483
+ function removePid(p) {
4484
4484
  try {
4485
- unlinkSync2(getPidFile());
4485
+ unlinkSync2(getPidFile(p));
4486
4486
  } catch {}
4487
4487
  }
4488
4488
  async function getDashboardHtml() {
@@ -4495,7 +4495,7 @@ async function getDashboardHtml() {
4495
4495
  return `<!doctype html><meta charset="utf-8"><body style="font-family:monospace;background:#111;color:#ddd;padding:2rem"><h1>doraval ui</h1><p>Dashboard HTML missing.</p><pre>${String(err)}</pre></body>`;
4496
4496
  }
4497
4497
  }
4498
- var import_picocolors16, DEFAULT_PORT = 3737, getPidFile = () => join18(getDoravalDir(), "ui.pid"), ui_default;
4498
+ var import_picocolors16, DEFAULT_PORT = 3737, getPidFile = (p) => join18(getDoravalDir(), `ui.${p}.pid`), ui_default;
4499
4499
  var init_ui = __esm(() => {
4500
4500
  init_journal_config();
4501
4501
  init_journal_parse();
@@ -4510,7 +4510,7 @@ var init_ui = __esm(() => {
4510
4510
  const showStatusOnly = !!args.status;
4511
4511
  const force = !!args.force;
4512
4512
  ensureDoravalDirs();
4513
- const existingPid = readPid();
4513
+ const existingPid = readPid(port);
4514
4514
  if (showStatusOnly) {
4515
4515
  if (existingPid) {
4516
4516
  const url2 = `http://${host === "0.0.0.0" ? "localhost" : host}:${port}`;
@@ -4539,7 +4539,7 @@ var init_ui = __esm(() => {
4539
4539
  process.kill(existingPid, "SIGTERM");
4540
4540
  } catch {}
4541
4541
  await new Promise((r) => setTimeout(r, 400));
4542
- removePid();
4542
+ removePid(port);
4543
4543
  } else if (!existingPid) {
4544
4544
  await killPort(port);
4545
4545
  }
@@ -4552,106 +4552,113 @@ var init_ui = __esm(() => {
4552
4552
  project = undefined;
4553
4553
  }
4554
4554
  }
4555
- const server = Bun.serve({
4556
- port,
4557
- hostname: host,
4558
- async fetch(req) {
4559
- const url2 = new URL(req.url);
4560
- if (url2.pathname === "/" || url2.pathname === "/index.html") {
4561
- const html = await getDashboardHtml();
4562
- return new Response(html, {
4563
- headers: { "content-type": "text/html; charset=utf-8" }
4564
- });
4565
- }
4566
- if (url2.pathname === "/api/status") {
4567
- return Response.json({
4568
- project: project || null,
4569
- doravalDir: getJournalsDir(),
4570
- hasConfig: !!config,
4571
- repo: config?.journal?.repo ?? null
4572
- });
4573
- }
4574
- if (url2.pathname === "/api/entries") {
4575
- const { committed, staged } = await loadAllEntries(project || null);
4576
- return Response.json({ project, committed, staged });
4577
- }
4578
- if (url2.pathname === "/api/context") {
4579
- const { committed, staged } = await loadAllEntries(project || null);
4580
- const all = [...staged, ...committed].filter((e) => (e.status || "active") === "active");
4581
- const text = generateJournalContext(all, project || null, { minPushback: 1 });
4582
- return Response.json({ text, project });
4583
- }
4584
- if (url2.pathname === "/api/hooks/status" && req.method === "GET") {
4585
- const localPath = getLocalHooksPath();
4586
- const globalPath = getGlobalSettingsPath();
4587
- const localHas = hasHook(await readJson(localPath));
4588
- const globalHas = hasHook(await readJson(globalPath));
4589
- return Response.json({
4590
- local: { enabled: localHas, path: localPath },
4591
- global: { enabled: globalHas, path: globalPath }
4592
- });
4593
- }
4594
- if (url2.pathname === "/api/hooks/enable" && req.method === "POST") {
4595
- const body = await req.json().catch(() => ({}));
4596
- const useGlobal = !!body.global;
4597
- const target = useGlobal ? getGlobalSettingsPath() : getLocalHooksPath();
4598
- const res = await addHook(target);
4599
- return Response.json(res);
4600
- }
4601
- if (url2.pathname === "/api/hooks/disable" && req.method === "POST") {
4602
- const body = await req.json().catch(() => ({}));
4603
- const useGlobal = !!body.global;
4604
- const target = useGlobal ? getGlobalSettingsPath() : getLocalHooksPath();
4605
- const res = await removeHook(target);
4606
- return Response.json(res);
4607
- }
4608
- if (url2.pathname === "/api/add" && req.method === "POST") {
4609
- if (!project) {
4610
- return Response.json({ error: "No project configured. Run dora init or dora journal init first." }, { status: 400 });
4555
+ let server;
4556
+ try {
4557
+ server = Bun.serve({
4558
+ port,
4559
+ hostname: host,
4560
+ async fetch(req) {
4561
+ const url2 = new URL(req.url);
4562
+ if (url2.pathname === "/" || url2.pathname === "/index.html") {
4563
+ const html = await getDashboardHtml();
4564
+ return new Response(html, {
4565
+ headers: { "content-type": "text/html; charset=utf-8" }
4566
+ });
4611
4567
  }
4612
- const body = await req.json();
4613
- const title = String(body.title || "Untitled decision").trim();
4614
- const pushback = Number(body.pushback ?? 4);
4615
- const tags = Array.isArray(body.tags) ? body.tags.map((t) => String(t).trim()).filter(Boolean) : [];
4616
- const rationale = String(body.rationale || title).trim();
4617
- try {
4618
- const result = await writePendingEntry(project, { title, pushback, tags, rationale });
4619
- return Response.json({ ok: true, ...result });
4620
- } catch (e) {
4621
- return Response.json({ error: e.message }, { status: 500 });
4568
+ if (url2.pathname === "/api/status") {
4569
+ return Response.json({
4570
+ project: project || null,
4571
+ doravalDir: getJournalsDir(),
4572
+ hasConfig: !!config,
4573
+ repo: config?.journal?.repo ?? null
4574
+ });
4622
4575
  }
4623
- }
4624
- if (url2.pathname === "/api/refresh" && req.method === "POST") {
4625
- const { committed, staged } = await loadAllEntries(project || null);
4626
- return Response.json({ ok: true, committed, staged });
4627
- }
4628
- if (url2.pathname === "/api/delete-staged" && req.method === "POST") {
4629
- if (!project) {
4630
- return Response.json({ error: "No project" }, { status: 400 });
4576
+ if (url2.pathname === "/api/entries") {
4577
+ const { committed, staged } = await loadAllEntries(project || null);
4578
+ return Response.json({ project, committed, staged });
4579
+ }
4580
+ if (url2.pathname === "/api/context") {
4581
+ const { committed, staged } = await loadAllEntries(project || null);
4582
+ const all = [...staged, ...committed].filter((e) => (e.status || "active") === "active");
4583
+ const text = generateJournalContext(all, project || null, { minPushback: 1 });
4584
+ return Response.json({ text, project });
4585
+ }
4586
+ if (url2.pathname === "/api/hooks/status" && req.method === "GET") {
4587
+ const localPath = getLocalHooksPath();
4588
+ const globalPath = getGlobalSettingsPath();
4589
+ const localHas = hasHook(await readJson(localPath));
4590
+ const globalHas = hasHook(await readJson(globalPath));
4591
+ return Response.json({
4592
+ local: { enabled: localHas, path: localPath },
4593
+ global: { enabled: globalHas, path: globalPath }
4594
+ });
4631
4595
  }
4632
- const body = await req.json().catch(() => ({}));
4633
- const filename = body.filename;
4634
- if (!filename) {
4635
- return Response.json({ error: "filename required" }, { status: 400 });
4596
+ if (url2.pathname === "/api/hooks/enable" && req.method === "POST") {
4597
+ const body = await req.json().catch(() => ({}));
4598
+ const useGlobal = !!body.global;
4599
+ const target = useGlobal ? getGlobalSettingsPath() : getLocalHooksPath();
4600
+ const res = await addHook(target);
4601
+ return Response.json(res);
4636
4602
  }
4637
- const pdir = getPendingProjectDir(project);
4638
- const filePath = join18(pdir, filename);
4639
- if (existsSync19(filePath)) {
4603
+ if (url2.pathname === "/api/hooks/disable" && req.method === "POST") {
4604
+ const body = await req.json().catch(() => ({}));
4605
+ const useGlobal = !!body.global;
4606
+ const target = useGlobal ? getGlobalSettingsPath() : getLocalHooksPath();
4607
+ const res = await removeHook(target);
4608
+ return Response.json(res);
4609
+ }
4610
+ if (url2.pathname === "/api/add" && req.method === "POST") {
4611
+ if (!project) {
4612
+ return Response.json({ error: "No project configured. Run dora init or dora journal init first." }, { status: 400 });
4613
+ }
4614
+ const body = await req.json();
4615
+ const title = String(body.title || "Untitled decision").trim();
4616
+ const pushback = Number(body.pushback ?? 4);
4617
+ const tags = Array.isArray(body.tags) ? body.tags.map((t) => String(t).trim()).filter(Boolean) : [];
4618
+ const rationale = String(body.rationale || title).trim();
4640
4619
  try {
4641
- await Bun.file(filePath).unlink();
4642
- } catch {}
4643
- return Response.json({ ok: true });
4620
+ const result = await writePendingEntry(project, { title, pushback, tags, rationale });
4621
+ return Response.json({ ok: true, ...result });
4622
+ } catch (e) {
4623
+ return Response.json({ error: e.message }, { status: 500 });
4624
+ }
4644
4625
  }
4645
- return Response.json({ error: "not found" }, { status: 404 });
4646
- }
4647
- if (url2.pathname.startsWith("/api/")) {
4648
- return Response.json({ error: "Not found" }, { status: 404 });
4626
+ if (url2.pathname === "/api/refresh" && req.method === "POST") {
4627
+ const { committed, staged } = await loadAllEntries(project || null);
4628
+ return Response.json({ ok: true, committed, staged });
4629
+ }
4630
+ if (url2.pathname === "/api/delete-staged" && req.method === "POST") {
4631
+ if (!project) {
4632
+ return Response.json({ error: "No project" }, { status: 400 });
4633
+ }
4634
+ const body = await req.json().catch(() => ({}));
4635
+ const filename = body.filename;
4636
+ if (!filename) {
4637
+ return Response.json({ error: "filename required" }, { status: 400 });
4638
+ }
4639
+ const pdir = getPendingProjectDir(project);
4640
+ const filePath = join18(pdir, filename);
4641
+ if (existsSync19(filePath)) {
4642
+ try {
4643
+ await Bun.file(filePath).unlink();
4644
+ } catch {}
4645
+ return Response.json({ ok: true });
4646
+ }
4647
+ return Response.json({ error: "not found" }, { status: 404 });
4648
+ }
4649
+ if (url2.pathname.startsWith("/api/")) {
4650
+ return Response.json({ error: "Not found" }, { status: 404 });
4651
+ }
4652
+ return new Response("Not found", { status: 404 });
4649
4653
  }
4650
- return new Response("Not found", { status: 404 });
4651
- }
4652
- });
4654
+ });
4655
+ } catch (err) {
4656
+ removePid(port);
4657
+ console.error(` Failed to start dashboard on port ${port}: ${err?.message || err}`);
4658
+ process.exit(1);
4659
+ }
4653
4660
  const url = `http://${host === "0.0.0.0" ? "localhost" : host}:${server.port}`;
4654
- writePid(process.pid);
4661
+ writePid(process.pid, port);
4655
4662
  const msg = `
4656
4663
  ${import_picocolors16.default.blue("\u25C9")} dora local dashboard
4657
4664
  ${import_picocolors16.default.dim("Project:")} ${project ? import_picocolors16.default.white(project) : import_picocolors16.default.yellow("none (run dora init)")}
@@ -4669,7 +4676,7 @@ var init_ui = __esm(() => {
4669
4676
  }
4670
4677
  }
4671
4678
  const cleanup = () => {
4672
- removePid();
4679
+ removePid(port);
4673
4680
  console.error(`
4674
4681
  Stopping dashboard...`);
4675
4682
  server.stop();
@@ -8085,7 +8092,7 @@ var main = defineCommand({
8085
8092
  meta: {
8086
8093
  name: "doraval",
8087
8094
  version: import__package.default.version,
8088
- description: "The context engineering toolkit for coding agents"
8095
+ description: "The context engineering toolkit for coding agent orchestrators"
8089
8096
  },
8090
8097
  subCommands: {
8091
8098
  validate: () => Promise.resolve().then(() => (init_validate_top(), exports_validate_top)).then((m) => m.default),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hacksmith/doraval",
3
- "version": "0.2.45",
3
+ "version": "0.2.46",
4
4
  "author": "Saif",
5
5
  "repository": {
6
6
  "type": "git",
@@ -13,7 +13,7 @@
13
13
  "doraval": "bin/doraval-wrapper.js",
14
14
  "dora": "bin/doraval-wrapper.js"
15
15
  },
16
- "description": "The context engineering toolkit for coding agents",
16
+ "description": "The context engineering toolkit for coding agent orchestrators",
17
17
  "engines": {
18
18
  "bun": ">=1.2.0",
19
19
  "node": ">=14.18.0"