@hjr15/blaze-board 0.2.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.
Files changed (46) hide show
  1. package/AGENTS.md +141 -0
  2. package/LICENSE +21 -0
  3. package/README.md +111 -0
  4. package/package.json +38 -0
  5. package/scripts/cli.mjs +31 -0
  6. package/scripts/commit-or-queue.mjs +22 -0
  7. package/scripts/commit-runner.mjs +40 -0
  8. package/scripts/config.mjs +135 -0
  9. package/scripts/edit-runner.mjs +18 -0
  10. package/scripts/edit.mjs +85 -0
  11. package/scripts/event-bus.mjs +16 -0
  12. package/scripts/log-runner.mjs +38 -0
  13. package/scripts/log.mjs +44 -0
  14. package/scripts/loops/groomer.mjs +215 -0
  15. package/scripts/migrate/audit.mjs +92 -0
  16. package/scripts/migrate/jira-client.mjs +26 -0
  17. package/scripts/migrate/jira-import.mjs +96 -0
  18. package/scripts/migrate/map.mjs +110 -0
  19. package/scripts/migrate/merge.mjs +103 -0
  20. package/scripts/migrate/normalize.mjs +60 -0
  21. package/scripts/migrate/report.mjs +67 -0
  22. package/scripts/migrate/restructure.mjs +49 -0
  23. package/scripts/migrate-runner.mjs +51 -0
  24. package/scripts/model/.gitkeep +0 -0
  25. package/scripts/model/ids.mjs +32 -0
  26. package/scripts/model/index.mjs +63 -0
  27. package/scripts/model/move-plan.mjs +25 -0
  28. package/scripts/model/rollup.mjs +55 -0
  29. package/scripts/model/rules.mjs +64 -0
  30. package/scripts/model/schema.mjs +30 -0
  31. package/scripts/model/ticket.mjs +136 -0
  32. package/scripts/model/time.mjs +38 -0
  33. package/scripts/model/workflows.mjs +54 -0
  34. package/scripts/move-runner.mjs +18 -0
  35. package/scripts/move.mjs +56 -0
  36. package/scripts/new-runner.mjs +43 -0
  37. package/scripts/new.mjs +54 -0
  38. package/scripts/pending-ledger.mjs +36 -0
  39. package/scripts/reconcile.mjs +181 -0
  40. package/scripts/reindex.mjs +22 -0
  41. package/scripts/resolve-runner.mjs +17 -0
  42. package/scripts/resolve.mjs +21 -0
  43. package/scripts/rollup-runner.mjs +53 -0
  44. package/scripts/serve-commit.mjs +18 -0
  45. package/scripts/serve.mjs +658 -0
  46. package/scripts/supervisor.mjs +192 -0
@@ -0,0 +1,21 @@
1
+ // scripts/resolve.mjs — `blaze resolve <id> <resolution>`: override the resolution
2
+ // field independently of status (the non-Done close path). Does NOT move the file.
3
+ import { writeFileSync } from "node:fs";
4
+ import { walkTickets } from "./model/index.mjs";
5
+ import { serializeTicket } from "./model/ticket.mjs";
6
+ import { RESOLUTIONS } from "./model/workflows.mjs";
7
+
8
+ export function applyResolve(projectsDir, id, resolution, opts = {}) {
9
+ const { today = null } = opts;
10
+ if (!RESOLUTIONS.includes(resolution)) {
11
+ return { ok: false, errors: [`invalid resolution: ${resolution} (expected ${RESOLUTIONS.join(", ")})`] };
12
+ }
13
+ let found = null;
14
+ for (const t of walkTickets(projectsDir)) { if (t.frontmatter.id === id) { found = t; break; } }
15
+ if (!found) return { ok: false, errors: [`ticket not found: ${id}`] };
16
+
17
+ const fm = { ...found.frontmatter, resolution };
18
+ if (today) fm.updated = today;
19
+ writeFileSync(found.file, serializeTicket({ frontmatter: fm, body: found.body }));
20
+ return { ok: true, id, resolution, file: found.file };
21
+ }
@@ -0,0 +1,53 @@
1
+ // scripts/rollup-runner.mjs — `blaze rollup [<id>]`: read-only time roll-up view.
2
+ // Builds the index from the resolved data tree's projects/, computes rollUp,
3
+ // and prints either one node's own/rolled totals + child breakdown, or a
4
+ // summary of all goals/epics. No writes.
5
+ import { fileURLToPath } from "node:url";
6
+ import { buildIndex } from "./model/index.mjs";
7
+ import { rollUp } from "./model/rollup.mjs";
8
+ import { formatMinutes } from "./model/time.mjs";
9
+ import { resolveRoots } from "./config.mjs";
10
+
11
+ // Pure formatter (exported for tests). index needs { rows, get(id) }.
12
+ export function rollupLines(index, rollupMap, id) {
13
+ if (id) {
14
+ const row = index.get(id);
15
+ const r = rollupMap.get(id);
16
+ if (!row || !r) return [`rollup: id not found: ${id}`];
17
+ const out = [
18
+ `${id} ${row.type} ${row.title ?? ""}`.trimEnd(),
19
+ ` own: estimate ${formatMinutes(r.own_estimate) || "—"} · logged ${formatMinutes(r.own_worklog) || "—"}`,
20
+ ` rolled: estimate ${formatMinutes(r.rolled_estimate) || "0m"} · logged ${formatMinutes(r.rolled_worklog) || "0m"} (${r.descendant_count} descendant${r.descendant_count === 1 ? "" : "s"})`,
21
+ ];
22
+ const kids = index.rows.filter((x) => x.parent === id);
23
+ if (kids.length) {
24
+ out.push(" children:");
25
+ for (const k of kids.sort((a, b) => String(a.id).localeCompare(String(b.id)))) {
26
+ const kr = rollupMap.get(k.id);
27
+ out.push(` ${k.id} ${k.type} rolled est ${formatMinutes(kr.rolled_estimate) || "0m"} · logged ${formatMinutes(kr.rolled_worklog) || "0m"}`);
28
+ }
29
+ }
30
+ return out;
31
+ }
32
+ // No id: summarise every goal/epic, grouped by project then id.
33
+ const parents = index.rows
34
+ .filter((x) => x.type === "goal" || x.type === "epic")
35
+ .sort((a, b) => String(a.project).localeCompare(String(b.project)) || String(a.id).localeCompare(String(b.id)));
36
+ if (!parents.length) return ["rollup: no goals or epics found."];
37
+ const out = ["Roll-up (goals + epics):"];
38
+ for (const p of parents) {
39
+ const r = rollupMap.get(p.id);
40
+ out.push(` ${p.id} ${p.type.padEnd(5)} rolled est ${formatMinutes(r.rolled_estimate) || "0m"} · logged ${formatMinutes(r.rolled_worklog) || "0m"} — ${p.title ?? ""}`.trimEnd());
41
+ }
42
+ return out;
43
+ }
44
+
45
+ function main() {
46
+ const { projectsDir } = resolveRoots();
47
+ const id = process.argv.slice(2).find((a) => !a.startsWith("--")) || null;
48
+ const index = buildIndex(projectsDir);
49
+ const lines = rollupLines(index, rollUp(index), id);
50
+ console.log(lines.join("\n"));
51
+ }
52
+
53
+ if (process.argv[1] && process.argv[1] === fileURLToPath(import.meta.url)) main();
@@ -0,0 +1,18 @@
1
+ // scripts/serve-commit.mjs — commit exactly one file, locally, never push.
2
+ // The board's only git surface. Deliberately NOT `git add -A` (that would sweep
3
+ // unrelated working-tree changes on the real 765-ticket tree).
4
+ import { spawnSync } from "node:child_process";
5
+
6
+ export function commitFile(root, file, message, extraFiles = []) {
7
+ const filesToAdd = [file, ...extraFiles];
8
+ const add = spawnSync("git", ["-C", root, "add", ...filesToAdd], { stdio: "ignore" });
9
+ if (add.status !== 0) return { ok: false, status: add.status };
10
+ const commit = spawnSync("git", ["-C", root, "commit", "-m", message, "--", ...filesToAdd], { stdio: "ignore" });
11
+ // status 1 with nothing to commit is a benign no-op (idempotent re-write).
12
+ if (commit.status !== 0) {
13
+ const clean = spawnSync("git", ["-C", root, "diff", "--cached", "--quiet"], { stdio: "ignore" });
14
+ if (clean.status === 0) return { ok: true, status: 0 };
15
+ return { ok: false, status: commit.status };
16
+ }
17
+ return { ok: true, status: 0 };
18
+ }