@dougharness/cli 1.0.1

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 (144) hide show
  1. package/LICENSE +21 -0
  2. package/dist/ablate.d.ts +96 -0
  3. package/dist/ablate.js +440 -0
  4. package/dist/ablate.js.map +1 -0
  5. package/dist/apply.d.ts +2 -0
  6. package/dist/apply.js +19 -0
  7. package/dist/apply.js.map +1 -0
  8. package/dist/bin.d.ts +2 -0
  9. package/dist/bin.js +80 -0
  10. package/dist/bin.js.map +1 -0
  11. package/dist/board-page.d.ts +15 -0
  12. package/dist/board-page.js +27 -0
  13. package/dist/board-page.js.map +1 -0
  14. package/dist/board-serve-process.d.ts +24 -0
  15. package/dist/board-serve-process.js +227 -0
  16. package/dist/board-serve-process.js.map +1 -0
  17. package/dist/board-serve.d.ts +9 -0
  18. package/dist/board-serve.js +302 -0
  19. package/dist/board-serve.js.map +1 -0
  20. package/dist/board.d.ts +6 -0
  21. package/dist/board.js +515 -0
  22. package/dist/board.js.map +1 -0
  23. package/dist/detect/fs.d.ts +6 -0
  24. package/dist/detect/fs.js +50 -0
  25. package/dist/detect/fs.js.map +1 -0
  26. package/dist/detect/index.d.ts +3 -0
  27. package/dist/detect/index.js +13 -0
  28. package/dist/detect/index.js.map +1 -0
  29. package/dist/detect/node.d.ts +3 -0
  30. package/dist/detect/node.js +179 -0
  31. package/dist/detect/node.js.map +1 -0
  32. package/dist/detect/python.d.ts +2 -0
  33. package/dist/detect/python.js +388 -0
  34. package/dist/detect/python.js.map +1 -0
  35. package/dist/detect/repo.d.ts +2 -0
  36. package/dist/detect/repo.js +102 -0
  37. package/dist/detect/repo.js.map +1 -0
  38. package/dist/detect/types.d.ts +98 -0
  39. package/dist/detect/types.js +2 -0
  40. package/dist/detect/types.js.map +1 -0
  41. package/dist/diff.d.ts +12 -0
  42. package/dist/diff.js +105 -0
  43. package/dist/diff.js.map +1 -0
  44. package/dist/generate/agents.d.ts +19 -0
  45. package/dist/generate/agents.js +184 -0
  46. package/dist/generate/agents.js.map +1 -0
  47. package/dist/generate/claude-md.d.ts +9 -0
  48. package/dist/generate/claude-md.js +111 -0
  49. package/dist/generate/claude-md.js.map +1 -0
  50. package/dist/generate/config.d.ts +51 -0
  51. package/dist/generate/config.js +155 -0
  52. package/dist/generate/config.js.map +1 -0
  53. package/dist/generate/proposal.d.ts +11 -0
  54. package/dist/generate/proposal.js +175 -0
  55. package/dist/generate/proposal.js.map +1 -0
  56. package/dist/generate/settings.d.ts +35 -0
  57. package/dist/generate/settings.js +213 -0
  58. package/dist/generate/settings.js.map +1 -0
  59. package/dist/generate/skills.d.ts +8 -0
  60. package/dist/generate/skills.js +264 -0
  61. package/dist/generate/skills.js.map +1 -0
  62. package/dist/init.d.ts +10 -0
  63. package/dist/init.js +69 -0
  64. package/dist/init.js.map +1 -0
  65. package/dist/trace.d.ts +9 -0
  66. package/dist/trace.js +106 -0
  67. package/dist/trace.js.map +1 -0
  68. package/node_modules/@dougharness/flow/.claude-plugin/plugin.json +10 -0
  69. package/node_modules/@dougharness/flow/LICENSE +21 -0
  70. package/node_modules/@dougharness/flow/agents/adversary-claude.md +33 -0
  71. package/node_modules/@dougharness/flow/agents/adversary.md +29 -0
  72. package/node_modules/@dougharness/flow/agents/implementer.md +23 -0
  73. package/node_modules/@dougharness/flow/agents/lead.md +35 -0
  74. package/node_modules/@dougharness/flow/agents/planner.md +70 -0
  75. package/node_modules/@dougharness/flow/agents/researcher.md +31 -0
  76. package/node_modules/@dougharness/flow/agents/reviewer.md +26 -0
  77. package/node_modules/@dougharness/flow/agents/verifier.md +24 -0
  78. package/node_modules/@dougharness/flow/lib/board.mjs +840 -0
  79. package/node_modules/@dougharness/flow/lib/code-index.mjs +629 -0
  80. package/node_modules/@dougharness/flow/lib/cost.mjs +392 -0
  81. package/node_modules/@dougharness/flow/lib/decisions.mjs +314 -0
  82. package/node_modules/@dougharness/flow/lib/embeddings.mjs +539 -0
  83. package/node_modules/@dougharness/flow/lib/land.mjs +219 -0
  84. package/node_modules/@dougharness/flow/lib/learn.mjs +742 -0
  85. package/node_modules/@dougharness/flow/lib/memory-import.mjs +145 -0
  86. package/node_modules/@dougharness/flow/lib/memory.mjs +1641 -0
  87. package/node_modules/@dougharness/flow/lib/models.mjs +148 -0
  88. package/node_modules/@dougharness/flow/lib/plan.mjs +773 -0
  89. package/node_modules/@dougharness/flow/lib/rehearse.mjs +825 -0
  90. package/node_modules/@dougharness/flow/lib/worktrees.mjs +215 -0
  91. package/node_modules/@dougharness/flow/package.json +19 -0
  92. package/node_modules/@dougharness/flow/scripts/board.mjs +260 -0
  93. package/node_modules/@dougharness/flow/scripts/cost.mjs +66 -0
  94. package/node_modules/@dougharness/flow/scripts/learn.mjs +161 -0
  95. package/node_modules/@dougharness/flow/scripts/memory.mjs +1274 -0
  96. package/node_modules/@dougharness/flow/scripts/plan.mjs +547 -0
  97. package/node_modules/@dougharness/flow/scripts/rehearse.mjs +129 -0
  98. package/node_modules/@dougharness/flow/skills/core-next/SKILL.md +92 -0
  99. package/node_modules/@dougharness/flow/skills/doug-approve/SKILL.md +15 -0
  100. package/node_modules/@dougharness/flow/skills/doug-decide/SKILL.md +26 -0
  101. package/node_modules/@dougharness/flow/skills/doug-implement/SKILL.md +22 -0
  102. package/node_modules/@dougharness/flow/skills/doug-learn/SKILL.md +20 -0
  103. package/node_modules/@dougharness/flow/skills/doug-next/SKILL.md +87 -0
  104. package/node_modules/@dougharness/flow/skills/doug-plan/SKILL.md +22 -0
  105. package/node_modules/@dougharness/flow/skills/doug-swarm/SKILL.md +55 -0
  106. package/node_modules/@dougharness/flow/skills/harness-fix/SKILL.md +85 -0
  107. package/node_modules/@dougharness/flow/skills/research/SKILL.md +41 -0
  108. package/node_modules/@dougharness/flow/skills/run-report/SKILL.md +51 -0
  109. package/node_modules/@dougharness/flow/skills/swarm-launch/SKILL.md +27 -0
  110. package/node_modules/@dougharness/flow/workflows/doug-implement.js +2687 -0
  111. package/node_modules/@dougharness/gates/.claude-plugin/plugin.json +10 -0
  112. package/node_modules/@dougharness/gates/LICENSE +21 -0
  113. package/node_modules/@dougharness/gates/hooks/hooks.json +184 -0
  114. package/node_modules/@dougharness/gates/lib/agent-context.mjs +111 -0
  115. package/node_modules/@dougharness/gates/lib/anchor.mjs +110 -0
  116. package/node_modules/@dougharness/gates/lib/baseline.mjs +160 -0
  117. package/node_modules/@dougharness/gates/lib/bash-rules.mjs +1522 -0
  118. package/node_modules/@dougharness/gates/lib/block-ledger.mjs +127 -0
  119. package/node_modules/@dougharness/gates/lib/checkpoint.mjs +97 -0
  120. package/node_modules/@dougharness/gates/lib/config.mjs +168 -0
  121. package/node_modules/@dougharness/gates/lib/evidence.mjs +59 -0
  122. package/node_modules/@dougharness/gates/lib/glob.mjs +86 -0
  123. package/node_modules/@dougharness/gates/lib/io.mjs +91 -0
  124. package/node_modules/@dougharness/gates/lib/paths.mjs +101 -0
  125. package/node_modules/@dougharness/gates/lib/proposals.mjs +201 -0
  126. package/node_modules/@dougharness/gates/lib/run.mjs +147 -0
  127. package/node_modules/@dougharness/gates/lib/scope.mjs +117 -0
  128. package/node_modules/@dougharness/gates/lib/secret-rules.mjs +268 -0
  129. package/node_modules/@dougharness/gates/lib/state.mjs +114 -0
  130. package/node_modules/@dougharness/gates/lib/trace.mjs +196 -0
  131. package/node_modules/@dougharness/gates/package.json +17 -0
  132. package/node_modules/@dougharness/gates/scripts/edit-loop.mjs +39 -0
  133. package/node_modules/@dougharness/gates/scripts/format-on-edit.mjs +45 -0
  134. package/node_modules/@dougharness/gates/scripts/guard-bash.mjs +32 -0
  135. package/node_modules/@dougharness/gates/scripts/protect-paths.mjs +49 -0
  136. package/node_modules/@dougharness/gates/scripts/reanchor.mjs +81 -0
  137. package/node_modules/@dougharness/gates/scripts/research-cap.mjs +295 -0
  138. package/node_modules/@dougharness/gates/scripts/secret-scan.mjs +26 -0
  139. package/node_modules/@dougharness/gates/scripts/session-start-baseline.mjs +57 -0
  140. package/node_modules/@dougharness/gates/scripts/statusline.mjs +133 -0
  141. package/node_modules/@dougharness/gates/scripts/stop-gate.mjs +1422 -0
  142. package/node_modules/@dougharness/gates/scripts/trace.mjs +75 -0
  143. package/package.json +33 -0
  144. package/templates/board-app.js +533 -0
package/dist/bin.js ADDED
@@ -0,0 +1,80 @@
1
+ #!/usr/bin/env node
2
+ import { runInit } from "./init.js";
3
+ import { runBoard } from "./board.js";
4
+ import { runAblate } from "./ablate.js";
5
+ import { runTrace } from "./trace.js";
6
+ const HELP = `doug — tailored, approval-gated harness install for Claude Code
7
+
8
+ Usage:
9
+ doug init [dir] [--yes] [--dry-run] [--no-color] [--show-hooks] [--quiet]
10
+ doug detect [dir] Print what would be detected, as JSON.
11
+ doug board init [dir]
12
+ doug board add <id> --title <t> --goal <g> [--component <c>] [--size S|M|L] [--track flow|hand] [--class <c>] [--deps a,b] [--tag a,b] [--column <col>] [dir]
13
+ doug board list [dir] [--column <id>] [--tag <t>] [--json]
14
+ doug board next [dir] [--track flow|hand] [--batch <n>] [--tag <t>]
15
+ doug board reorder <id> [dir] --before <other> | --after <other> | --top | --bottom
16
+ doug board move <id> <column> [dir] [--source <text>]
17
+ doug board edit <id> [dir] [--title <t>] [--goal <g> | --goal-file <path>] [--size S|M|L] [--component <c>] [--track flow|hand] [--class <c>] [--deps a,b] [--tag a,b] [--source <s>] [--force]
18
+ doug board remove <id> [dir] [--force]
19
+ doug board record <id> <report.json> [dir] [--cost <usd>] [--codex-cost <usd>] [--shared-cost <usd>] [--wall <text>] [--commit <sha>] [--adversary "<id>=<class>[: <reason>]"]...
20
+ doug board record <id> --hand [dir] [--commit <sha>] [--wall <text>] [--gate <text>] [--note <text>]
21
+ doug board summary <report.json> [--cost <usd>] [--codex-cost <usd>] [--wall <text>] [--commit <sha>] [--card <id>]
22
+ doug board build [dir] [--out <file>]
23
+ doug board serve [dir] [--port <n>] [--open] [--detach | --stop]
24
+ doug ablate --from <evals/out/file.json> [--from <file> ...] [--json]
25
+ doug ablate [dir] [--tasks a,b] [--conditions baseline,gates,full] [--runs <n>] [--max-turns <n>] [--suite <name>]
26
+ [--max-budget-usd <n>] [--judge auto|codex|claude|off] [--judge-model <name>] [--dry-run] [--json]
27
+ doug trace [dir] [--session <id>] [--json]
28
+ doug --help
29
+
30
+ doug init detects the project's tooling deterministically, prints a diff of every file it
31
+ would write, and writes nothing until you approve. Re-run it to refresh the vendored hooks.
32
+ `;
33
+ function parse(argv) {
34
+ const flags = new Set();
35
+ const positional = [];
36
+ for (const a of argv) {
37
+ if (a.startsWith("--"))
38
+ flags.add(a.slice(2));
39
+ else
40
+ positional.push(a);
41
+ }
42
+ return { flags, positional };
43
+ }
44
+ async function main() {
45
+ if (process.argv[2] === "board")
46
+ return runBoard(process.argv.slice(3));
47
+ if (process.argv[2] === "ablate")
48
+ return runAblate(process.argv.slice(3));
49
+ if (process.argv[2] === "trace")
50
+ return runTrace(process.argv.slice(3));
51
+ const { flags, positional } = parse(process.argv.slice(2));
52
+ const command = positional[0];
53
+ if (!command || flags.has("help") || command === "help") {
54
+ process.stdout.write(HELP);
55
+ return 0;
56
+ }
57
+ const dir = positional[1] || process.cwd();
58
+ if (command === "detect") {
59
+ const { detect } = await import("./detect/index.js");
60
+ process.stdout.write(JSON.stringify(detect(dir), null, 2) + "\n");
61
+ return 0;
62
+ }
63
+ if (command === "init") {
64
+ return runInit({
65
+ dir,
66
+ yes: flags.has("yes") || flags.has("y"),
67
+ dryRun: flags.has("dry-run"),
68
+ color: !flags.has("no-color") && !!process.stdout.isTTY,
69
+ quiet: flags.has("quiet"),
70
+ showHookFiles: flags.has("show-hooks"),
71
+ });
72
+ }
73
+ process.stderr.write(`Unknown command: ${command}\n\n${HELP}`);
74
+ return 2;
75
+ }
76
+ main().then((code) => process.exit(code), (err) => {
77
+ process.stderr.write(`doug: ${err && err.stack ? err.stack : String(err)}\n`);
78
+ process.exit(1);
79
+ });
80
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../src/bin.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAC;AAEtC,MAAM,IAAI,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;CA0BZ,CAAC;AAEF,SAAS,KAAK,CAAC,IAAc;IAC3B,MAAM,KAAK,GAAG,IAAI,GAAG,EAAU,CAAC;IAChC,MAAM,UAAU,GAAa,EAAE,CAAC;IAChC,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,IAAI,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC;YAAE,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;;YACzC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAC1B,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,UAAU,EAAE,CAAC;AAC/B,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACxE,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,QAAQ;QAAE,OAAO,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC1E,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,OAAO;QAAE,OAAO,QAAQ,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IACxE,MAAM,EAAE,KAAK,EAAE,UAAU,EAAE,GAAG,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;IAC3D,MAAM,OAAO,GAAG,UAAU,CAAC,CAAC,CAAC,CAAC;IAC9B,IAAI,CAAC,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;QACxD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAC3B,OAAO,CAAC,CAAC;IACX,CAAC;IACD,MAAM,GAAG,GAAG,UAAU,CAAC,CAAC,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC;IAC3C,IAAI,OAAO,KAAK,QAAQ,EAAE,CAAC;QACzB,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,MAAM,CAAC,mBAAmB,CAAC,CAAC;QACrD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC;QAClE,OAAO,CAAC,CAAC;IACX,CAAC;IACD,IAAI,OAAO,KAAK,MAAM,EAAE,CAAC;QACvB,OAAO,OAAO,CAAC;YACb,GAAG;YACH,GAAG,EAAE,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC;YACvC,MAAM,EAAE,KAAK,CAAC,GAAG,CAAC,SAAS,CAAC;YAC5B,KAAK,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK;YACvD,KAAK,EAAE,KAAK,CAAC,GAAG,CAAC,OAAO,CAAC;YACzB,aAAa,EAAE,KAAK,CAAC,GAAG,CAAC,YAAY,CAAC;SACvC,CAAC,CAAC;IACL,CAAC;IACD,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,oBAAoB,OAAO,OAAO,IAAI,EAAE,CAAC,CAAC;IAC/D,OAAO,CAAC,CAAC;AACX,CAAC;AAED,IAAI,EAAE,CAAC,IAAI,CACT,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,EAC5B,CAAC,GAAG,EAAE,EAAE;IACN,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,SAAS,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IAC9E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CACF,CAAC"}
@@ -0,0 +1,15 @@
1
+ import { type Board } from "@dougharness/flow/lib/board.mjs";
2
+ export type BoardPageMode = "local";
3
+ export interface BoardPageConfig {
4
+ mode: BoardPageMode;
5
+ record: string;
6
+ }
7
+ export declare function renderBoardPage(board: Board, opts: {
8
+ mode: BoardPageMode;
9
+ record: string;
10
+ }): string;
11
+ export declare function buildBoardPage(dir: string): {
12
+ html: string;
13
+ board: Board;
14
+ record: string;
15
+ };
@@ -0,0 +1,27 @@
1
+ // Renders the board page from the board record. The page's own source is templates/board-app.js;
2
+ // a move is written by `doug board serve`.
3
+ import { readFileSync } from "node:fs";
4
+ import { relative } from "node:path";
5
+ import { boardPath, loadBoard } from "@dougharness/flow/lib/board.mjs";
6
+ // The template is a classic script: it is embedded in the page verbatim and evaluated here for its
7
+ // renderDoc. A literal closing script tag in it would cut the page's own <script id="app"> short.
8
+ function templateSource() {
9
+ const source = readFileSync(new URL("../templates/board-app.js", import.meta.url), "utf8");
10
+ if (source.includes("</script>")) {
11
+ throw new Error("the board page template contains a literal </script>; write it as <\\/script>");
12
+ }
13
+ return source;
14
+ }
15
+ export function renderBoardPage(board, opts) {
16
+ const source = templateSource();
17
+ const renderDoc = new Function(source + "\nreturn renderDoc;")();
18
+ const appSource = source + "\napp();\n";
19
+ return renderDoc(board, appSource, { mode: opts.mode, record: opts.record });
20
+ }
21
+ // The complete document: what `doug board serve` and `doug board build` both send.
22
+ export function buildBoardPage(dir) {
23
+ const board = loadBoard(dir);
24
+ const record = relative(dir, boardPath(dir));
25
+ return { html: renderBoardPage(board, { mode: "local", record }), board, record };
26
+ }
27
+ //# sourceMappingURL=board-page.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-page.js","sourceRoot":"","sources":["../src/board-page.ts"],"names":[],"mappings":"AAAA,iGAAiG;AACjG,2CAA2C;AAC3C,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,QAAQ,EAAE,MAAM,WAAW,CAAC;AACrC,OAAO,EAAE,SAAS,EAAE,SAAS,EAAc,MAAM,iCAAiC,CAAC;AAWnF,mGAAmG;AACnG,kGAAkG;AAClG,SAAS,cAAc;IACrB,MAAM,MAAM,GAAG,YAAY,CAAC,IAAI,GAAG,CAAC,2BAA2B,EAAE,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC;IAC3F,IAAI,MAAM,CAAC,QAAQ,CAAC,WAAW,CAAC,EAAE,CAAC;QACjC,MAAM,IAAI,KAAK,CAAC,+EAA+E,CAAC,CAAC;IACnG,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,KAAY,EAAE,IAA6C;IACzF,MAAM,MAAM,GAAG,cAAc,EAAE,CAAC;IAChC,MAAM,SAAS,GAAG,IAAI,QAAQ,CAAC,MAAM,GAAG,qBAAqB,CAAC,EAAe,CAAC;IAC9E,MAAM,SAAS,GAAG,MAAM,GAAG,YAAY,CAAC;IACxC,OAAO,SAAS,CAAC,KAAK,EAAE,SAAS,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AAC/E,CAAC;AAED,mFAAmF;AACnF,MAAM,UAAU,cAAc,CAAC,GAAW;IACxC,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,CAAC;IAC7B,MAAM,MAAM,GAAG,QAAQ,CAAC,GAAG,EAAE,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC;IAC7C,OAAO,EAAE,IAAI,EAAE,eAAe,CAAC,KAAK,EAAE,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC;AACpF,CAAC"}
@@ -0,0 +1,24 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ export interface PidfileData {
3
+ pid: number;
4
+ port: number;
5
+ url: string;
6
+ record: string;
7
+ }
8
+ export declare function pidfilePath(dir: string): string;
9
+ export declare function logPath(dir: string): string;
10
+ export declare function readPidfile(dir: string): PidfileData | null;
11
+ export declare function writePidfile(dir: string, data: PidfileData): void;
12
+ export declare function removePidfile(dir: string): void;
13
+ export declare function removePidfileIfOwn(dir: string, pid: number): void;
14
+ export declare function isAlive(pid: number): boolean;
15
+ export declare function urlAnswers(url: string, timeoutMs: number): Promise<boolean>;
16
+ export declare function servingPid(url: string, timeoutMs: number): Promise<number | null>;
17
+ export declare function findRunningServer(dir: string): Promise<PidfileData | null>;
18
+ export declare function openBrowser(url: string, stderr: (s: string) => void): Promise<void>;
19
+ export declare function spawnDetached(dir: string, port: number): ChildProcess;
20
+ export declare function waitForDetached(dir: string, child: ChildProcess, timeoutMs: number): Promise<PidfileData>;
21
+ export declare function stopRunningServer(dir: string, timeoutMs: number): Promise<{
22
+ stopped: boolean;
23
+ data: PidfileData | null;
24
+ }>;
@@ -0,0 +1,227 @@
1
+ // Process-management helpers for `doug board serve`: the pidfile at .doug/.state/board-serve.json,
2
+ // detecting and reusing a running server, spawning a detached one, stopping one, and opening a
3
+ // browser. startBoardServer (board-serve.ts) stays free of all of this.
4
+ import { spawn } from "node:child_process";
5
+ import { closeSync, mkdirSync, openSync, readFileSync, unlinkSync, writeFileSync } from "node:fs";
6
+ import { dirname, join } from "node:path";
7
+ const PIDFILE_RELPATH = ".doug/.state/board-serve.json";
8
+ const LOG_RELPATH = ".doug/.state/board-serve.log";
9
+ export function pidfilePath(dir) {
10
+ return join(dir, PIDFILE_RELPATH);
11
+ }
12
+ export function logPath(dir) {
13
+ return join(dir, LOG_RELPATH);
14
+ }
15
+ export function readPidfile(dir) {
16
+ try {
17
+ const data = JSON.parse(readFileSync(pidfilePath(dir), "utf8"));
18
+ if (typeof data.pid === "number" &&
19
+ typeof data.port === "number" &&
20
+ typeof data.url === "string" &&
21
+ typeof data.record === "string") {
22
+ return data;
23
+ }
24
+ return null;
25
+ }
26
+ catch {
27
+ return null;
28
+ }
29
+ }
30
+ export function writePidfile(dir, data) {
31
+ const file = pidfilePath(dir);
32
+ mkdirSync(dirname(file), { recursive: true });
33
+ writeFileSync(file, JSON.stringify(data));
34
+ }
35
+ export function removePidfile(dir) {
36
+ try {
37
+ unlinkSync(pidfilePath(dir));
38
+ }
39
+ catch {
40
+ // already gone
41
+ }
42
+ }
43
+ // Removes the pidfile only while it still names this pid, so a fresh server that has since
44
+ // replaced it (or a stop that already removed it) is left alone.
45
+ export function removePidfileIfOwn(dir, pid) {
46
+ const data = readPidfile(dir);
47
+ if (data && data.pid === pid)
48
+ removePidfile(dir);
49
+ }
50
+ export function isAlive(pid) {
51
+ try {
52
+ process.kill(pid, 0);
53
+ return true;
54
+ }
55
+ catch {
56
+ return false;
57
+ }
58
+ }
59
+ export async function urlAnswers(url, timeoutMs) {
60
+ try {
61
+ const res = await fetch(new URL("api/board", url), { signal: AbortSignal.timeout(timeoutMs) });
62
+ return res.status === 200;
63
+ }
64
+ catch {
65
+ return false;
66
+ }
67
+ }
68
+ // Returns the pid a doug board serve at the URL names as its own, from the x-doug-board-serve-pid
69
+ // header it sets on every response (any status: even a 500 from a board it cannot load still
70
+ // carries the header). Returns null on a fetch error, a missing header, or a header that is not a
71
+ // positive decimal integer — including a reused pid with some unrelated listener bound at that URL.
72
+ export async function servingPid(url, timeoutMs) {
73
+ try {
74
+ const res = await fetch(new URL("api/board", url), { signal: AbortSignal.timeout(timeoutMs) });
75
+ try {
76
+ await res.body?.cancel();
77
+ }
78
+ catch {
79
+ // best effort
80
+ }
81
+ const header = res.headers.get("x-doug-board-serve-pid");
82
+ if (header && /^[1-9]\d*$/.test(header))
83
+ return Number(header);
84
+ return null;
85
+ }
86
+ catch {
87
+ return null;
88
+ }
89
+ }
90
+ // Reads the pidfile and validates it names a live, answering server. A pidfile that fails either
91
+ // check is stale and is removed; the caller then starts fresh.
92
+ export async function findRunningServer(dir) {
93
+ const data = readPidfile(dir);
94
+ if (!data)
95
+ return null;
96
+ if (!isAlive(data.pid) || !(await urlAnswers(data.url, 1000))) {
97
+ removePidfile(dir);
98
+ return null;
99
+ }
100
+ return data;
101
+ }
102
+ function lastLines(file, n) {
103
+ try {
104
+ const text = readFileSync(file, "utf8");
105
+ return text.split("\n").slice(-n).join("\n");
106
+ }
107
+ catch {
108
+ return "";
109
+ }
110
+ }
111
+ // Launches the URL in a browser: BROWSER when set (no shell, URL as its only argument), else the
112
+ // platform opener. A spawn failure is reported to stderr and never changes the caller's exit code.
113
+ // Callers await this: it resolves once the child has either spawned successfully or failed, so a
114
+ // failure is on stderr before a caller that exits right after (--detach, reuse) does so; it never
115
+ // waits past a short grace period otherwise, so it stays effectively fire-and-forget.
116
+ export async function openBrowser(url, stderr) {
117
+ let command;
118
+ let args;
119
+ let windowsVerbatimArguments = false;
120
+ const browserEnv = process.env.BROWSER;
121
+ if (browserEnv) {
122
+ command = browserEnv;
123
+ args = [url];
124
+ }
125
+ else if (process.platform === "darwin") {
126
+ command = "open";
127
+ args = [url];
128
+ }
129
+ else if (process.platform === "win32") {
130
+ command = "cmd";
131
+ args = ["/c", "start", '""', url];
132
+ // cmd.exe's `start` needs the empty-title arg passed through untouched; Node's normal
133
+ // Windows argument quoting would otherwise mangle the bare `""`.
134
+ windowsVerbatimArguments = true;
135
+ }
136
+ else {
137
+ command = "xdg-open";
138
+ args = [url];
139
+ }
140
+ try {
141
+ const child = spawn(command, args, { detached: true, stdio: "ignore", windowsVerbatimArguments });
142
+ await new Promise((resolve) => {
143
+ const finish = () => {
144
+ clearTimeout(timer);
145
+ child.off("error", onError);
146
+ child.off("spawn", onSpawn);
147
+ resolve();
148
+ };
149
+ const onError = () => {
150
+ stderr(`Could not open a browser; open ${url} yourself.\n`);
151
+ finish();
152
+ };
153
+ const onSpawn = () => finish();
154
+ const timer = setTimeout(finish, 500);
155
+ child.once("error", onError);
156
+ child.once("spawn", onSpawn);
157
+ });
158
+ child.unref();
159
+ }
160
+ catch {
161
+ stderr(`Could not open a browser; open ${url} yourself.\n`);
162
+ }
163
+ }
164
+ // Spawns a detached `doug board serve <dir> --port <port>` (no --detach: the child runs the plain
165
+ // foreground path, and writes its own pidfile once it listens). process.execArgv/argv[1] carry
166
+ // over the current entry point so this also works under `node --import tsx src/bin.ts`.
167
+ export function spawnDetached(dir, port) {
168
+ const file = logPath(dir);
169
+ mkdirSync(dirname(file), { recursive: true });
170
+ const fd = openSync(file, "a");
171
+ const child = spawn(process.execPath, [...process.execArgv, process.argv[1], "board", "serve", dir, "--port", String(port)], { detached: true, stdio: ["ignore", fd, fd] });
172
+ closeSync(fd);
173
+ child.unref();
174
+ return child;
175
+ }
176
+ // Waits for the detached child's pidfile to name its pid with a URL that answers, or throws with
177
+ // the log's tail if the child exits first or the wait times out.
178
+ export async function waitForDetached(dir, child, timeoutMs) {
179
+ let childExited = false;
180
+ let exitCode = null;
181
+ child.once("exit", (code) => {
182
+ childExited = true;
183
+ exitCode = code;
184
+ });
185
+ const deadline = Date.now() + timeoutMs;
186
+ while (Date.now() < deadline) {
187
+ if (childExited) {
188
+ throw new Error(`doug board serve --detach exited with ${exitCode} before it started listening\n${lastLines(logPath(dir), 40)}`);
189
+ }
190
+ const data = readPidfile(dir);
191
+ if (data && data.pid === child.pid && (await urlAnswers(data.url, 500)))
192
+ return data;
193
+ await new Promise((r) => setTimeout(r, 100));
194
+ }
195
+ throw new Error(`doug board serve --detach did not start within ${Math.round(timeoutMs / 1000)}s\n${lastLines(logPath(dir), 40)}`);
196
+ }
197
+ // Sends SIGTERM to a pidfile's process, but only once it is confirmed to be a live process AND the
198
+ // server answering at the pidfile's URL names that same pid in its x-doug-board-serve-pid header —
199
+ // not just any live pid the pidfile happens to name, and not just any listener bound at that URL (a
200
+ // crash can leave a pidfile whose pid was since reused by an unrelated process, with some unrelated
201
+ // listener now bound at its old URL). Unlike the reuse check, this does not require a 200: a
202
+ // detached server whose board record it cannot load is still ours to stop, even while it answers
203
+ // api/board with a 500. Waits up to timeoutMs for it to exit, removing the pidfile if the process
204
+ // is gone. Returns data: null when nothing was running (a stale pidfile, if any, is removed).
205
+ export async function stopRunningServer(dir, timeoutMs) {
206
+ const data = readPidfile(dir);
207
+ if (!data || !isAlive(data.pid) || (await servingPid(data.url, 1000)) !== data.pid) {
208
+ removePidfile(dir);
209
+ return { stopped: false, data: null };
210
+ }
211
+ try {
212
+ process.kill(data.pid, "SIGTERM");
213
+ }
214
+ catch {
215
+ removePidfile(dir);
216
+ return { stopped: false, data: null };
217
+ }
218
+ const deadline = Date.now() + timeoutMs;
219
+ while (Date.now() < deadline && isAlive(data.pid)) {
220
+ await new Promise((r) => setTimeout(r, 100));
221
+ }
222
+ const stopped = !isAlive(data.pid);
223
+ if (stopped)
224
+ removePidfile(dir);
225
+ return { stopped, data };
226
+ }
227
+ //# sourceMappingURL=board-serve-process.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"board-serve-process.js","sourceRoot":"","sources":["../src/board-serve-process.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,9 @@
1
+ export interface BoardServer {
2
+ port: number;
3
+ url: string;
4
+ close(): Promise<void>;
5
+ }
6
+ export declare function startBoardServer(dir: string, opts: {
7
+ port: number;
8
+ watch?: "fs" | "poll";
9
+ }): Promise<BoardServer>;