@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.
- package/README.md +8 -4
- package/bin/doraval.js +112 -105
- 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
|
|
3
|
+
The context engineering toolkit for coding agent orchestrators.
|
|
4
4
|
|
|
5
|
-
If you'
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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
|
|
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(),
|
|
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
|
-
|
|
4556
|
-
|
|
4557
|
-
|
|
4558
|
-
|
|
4559
|
-
|
|
4560
|
-
|
|
4561
|
-
const
|
|
4562
|
-
|
|
4563
|
-
|
|
4564
|
-
|
|
4565
|
-
|
|
4566
|
-
|
|
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
|
-
|
|
4613
|
-
|
|
4614
|
-
|
|
4615
|
-
|
|
4616
|
-
|
|
4617
|
-
|
|
4618
|
-
|
|
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
|
-
|
|
4625
|
-
|
|
4626
|
-
|
|
4627
|
-
|
|
4628
|
-
|
|
4629
|
-
|
|
4630
|
-
|
|
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
|
-
|
|
4633
|
-
|
|
4634
|
-
|
|
4635
|
-
|
|
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
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
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
|
|
4642
|
-
|
|
4643
|
-
|
|
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
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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"
|