handyman-harness 3.0.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 (54) hide show
  1. package/LICENSE +21 -0
  2. package/NOTICE +17 -0
  3. package/README.md +32 -0
  4. package/assets/AGENTS.template.md +36 -0
  5. package/assets/CHECKPOINTS.template.md +32 -0
  6. package/assets/backlog-explore.template.md +29 -0
  7. package/assets/backlog-impl.template.md +24 -0
  8. package/assets/backlog-review.template.md +35 -0
  9. package/assets/docs-architecture.template.md +23 -0
  10. package/assets/docs-business.template.md +67 -0
  11. package/assets/docs-conventions.template.md +28 -0
  12. package/assets/docs-verification.template.md +25 -0
  13. package/assets/feature-request.template.md +170 -0
  14. package/assets/feature_list.template.json +33 -0
  15. package/assets/harness.config.global.template.json +27 -0
  16. package/assets/harness.config.local.template.json +27 -0
  17. package/assets/harness.gitignore.template +11 -0
  18. package/assets/index.template.md +34 -0
  19. package/assets/init.template.sh +330 -0
  20. package/assets/progress-current.template.md +30 -0
  21. package/assets/progress-history.template.md +19 -0
  22. package/assets/role-explorer.template.md +17 -0
  23. package/assets/role-implementer.template.md +22 -0
  24. package/assets/role-leader.template.md +20 -0
  25. package/assets/role-reviewer.template.md +21 -0
  26. package/assets/schemas/feature_list.schema.json +97 -0
  27. package/assets/schemas/harness.config.schema.json +65 -0
  28. package/assets/schemas/registry.schema.json +24 -0
  29. package/assets/schemas/sprint.schema.json +20 -0
  30. package/assets/schemas/trigger_eval.schema.json +18 -0
  31. package/assets/sprint.template.md +50 -0
  32. package/dist/backlog.js +7124 -0
  33. package/dist/cli.js +43 -0
  34. package/dist/evals.js +7189 -0
  35. package/dist/feature.js +8085 -0
  36. package/dist/index_md.js +6852 -0
  37. package/dist/metrics.js +6996 -0
  38. package/dist/preflight.js +6873 -0
  39. package/dist/sprint.js +7388 -0
  40. package/dist/toolbox.js +9733 -0
  41. package/dist/toolbox_acceptance.js +77 -0
  42. package/dist/toolbox_assets.js +119 -0
  43. package/dist/toolbox_draft.js +2166 -0
  44. package/dist/toolbox_llm.js +291 -0
  45. package/dist/toolbox_retro.js +191 -0
  46. package/dist/toolbox_review_notes.js +169 -0
  47. package/dist/toolbox_review_notes_cli.js +598 -0
  48. package/dist/toolbox_state.js +9986 -0
  49. package/dist/toolbox_triage.js +168 -0
  50. package/dist/tools_discovery.js +7751 -0
  51. package/dist/update_harness.js +7531 -0
  52. package/dist/upgrade_harness.js +7355 -0
  53. package/dist/validate_harness.js +7304 -0
  54. package/package.json +33 -0
@@ -0,0 +1,77 @@
1
+ import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
2
+
3
+ // ../packages/toolbox-core/dist/llm.js
4
+ var LlmError = class extends Error {
5
+ code;
6
+ constructor(code, message) {
7
+ super(message);
8
+ this.name = "LlmError";
9
+ this.code = code;
10
+ }
11
+ };
12
+
13
+ // ../packages/toolbox-core/dist/acceptance.js
14
+ var ACCEPTANCE_SPEC_MAX_CHARS = 6e4;
15
+ function composeAcceptanceSystem() {
16
+ return [
17
+ "Redactas los criterios de aceptacion de una feature para un harness Handyman.",
18
+ "",
19
+ "Salida: una lista markdown de balas, nada mas. Sin preambulo ni cierre.",
20
+ "",
21
+ "Reglas duras:",
22
+ "- Cada bala debe ser OBSERVABLE y TESTEABLE: usa verbos verificables",
23
+ " (corre, sale con exit 0, grepea, devuelve, responde 400, escribe el archivo X).",
24
+ "- PROHIBIDO lo vago: nada de 'deberia funcionar', 'esta bien integrado',",
25
+ " 'es robusto', 'mejora la experiencia'. Si no se puede verificar desde",
26
+ " afuera, no es un criterio de aceptacion.",
27
+ "- Cada bala nombra el artefacto concreto (ruta, comando, archivo, endpoint).",
28
+ "- La ULTIMA bala debe ser siempre el gate verde, exactamente:",
29
+ " 'bash tests/run_tests.sh passes y ./init.sh exits 0.'",
30
+ "- Si el material no alcanza para justificar un criterio, no lo inventes:",
31
+ " di que falta evidencia para esa parte."
32
+ ].join("\n");
33
+ }
34
+ function composeAcceptancePrompt(source, content, truncated) {
35
+ const header = source === "diff" ? "---- diff de trabajo (git diff HEAD): deduce que cambio y que se puede observar" : "---- especificacion / issue crudo: deduce los criterios observables que la cumplen";
36
+ const body = content.length > 0 ? content : source === "diff" ? "(diff vacio o el root no es un repo git)" : "(especificacion vacia)";
37
+ const parts = [header, body];
38
+ if (truncated) {
39
+ parts.push("");
40
+ parts.push("[el material fue truncado por tamano: no afirmes nada sobre lo que no viste]");
41
+ }
42
+ return parts.join("\n");
43
+ }
44
+ function lastBulletIsGreenGate(md) {
45
+ const bullets = md.split("\n").map((line) => line.trim()).filter((line) => /^[-*+]\s+/.test(line) || /^\d+[.)]\s+/.test(line));
46
+ const last = bullets[bullets.length - 1];
47
+ if (!last) {
48
+ return false;
49
+ }
50
+ return last.includes("./init.sh") || last.includes("tests/run_tests.sh");
51
+ }
52
+ async function relayAcceptance(options) {
53
+ const { system, prompt, draft, source, truncated, onDelta, onResult, onError } = options;
54
+ try {
55
+ const result = await draft({ prompt, system }, onDelta);
56
+ onResult({
57
+ acceptance_md: result.text,
58
+ model: result.model,
59
+ source,
60
+ gate_last: lastBulletIsGreenGate(result.text),
61
+ diff_truncated: truncated
62
+ });
63
+ } catch (error) {
64
+ if (error instanceof LlmError) {
65
+ onError(error);
66
+ return;
67
+ }
68
+ onError(new LlmError("provider_error", error instanceof Error ? error.message : String(error)));
69
+ }
70
+ }
71
+ export {
72
+ ACCEPTANCE_SPEC_MAX_CHARS,
73
+ composeAcceptancePrompt,
74
+ composeAcceptanceSystem,
75
+ lastBulletIsGreenGate,
76
+ relayAcceptance
77
+ };
@@ -0,0 +1,119 @@
1
+ import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
2
+
3
+ // src/toolbox_assets.ts
4
+ import { createRequire } from "node:module";
5
+ import { dirname, join as join3, sep as sep2 } from "node:path";
6
+
7
+ // ../packages/toolbox-core/dist/registry.js
8
+ import { mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
9
+ import { join, resolve } from "node:path";
10
+ function registryPath(hroot) {
11
+ return join(hroot, "registry.json");
12
+ }
13
+ function loadRegistry(hroot) {
14
+ const empty = { version: 1, harnesses: [] };
15
+ const path = registryPath(hroot);
16
+ let raw;
17
+ try {
18
+ raw = readFileSync(path, "utf-8");
19
+ } catch {
20
+ return [empty, null];
21
+ }
22
+ let data;
23
+ try {
24
+ data = JSON.parse(raw);
25
+ } catch {
26
+ return [empty, `registry does not parse: ${path}`];
27
+ }
28
+ if (!data || typeof data !== "object" || Array.isArray(data) || !Array.isArray(data.harnesses)) {
29
+ return [empty, `registry has no 'harnesses' array: ${path}`];
30
+ }
31
+ const record = data;
32
+ const harnesses = record.harnesses.filter((e) => !!e && typeof e === "object" && !Array.isArray(e)).map((e) => ({
33
+ project_root: String(e.project_root ?? ""),
34
+ registered: String(e.registered ?? "")
35
+ }));
36
+ return [{ version: Number(record.version ?? 1), harnesses }, null];
37
+ }
38
+
39
+ // ../packages/toolbox-core/dist/state.js
40
+ import { readdirSync, readFileSync as readFileSync2, statSync as statSync2 } from "node:fs";
41
+ import { join as join2, resolve as resolve2, sep } from "node:path";
42
+ var INTAKE_MAX_BYTES = 256 * 1024;
43
+ var CSP_HEADER = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'";
44
+ var HTML_CSP_HEADER = CSP_HEADER.replace("img-src 'self' data:", "img-src 'self' data: https://picsum.photos");
45
+ function readText(path) {
46
+ try {
47
+ return readFileSync2(path, "utf-8");
48
+ } catch {
49
+ return null;
50
+ }
51
+ }
52
+ var MD_TOKEN_PATHS = {
53
+ current: (workspace) => join2(workspace, "progress", "current.md"),
54
+ history: (workspace) => join2(workspace, "progress", "history.md"),
55
+ index: (workspace) => join2(workspace, "index.md"),
56
+ checkpoints: (_workspace, root) => join2(root, "CHECKPOINTS.md")
57
+ };
58
+ var MD_TOKENS = Object.keys(MD_TOKEN_PATHS);
59
+
60
+ // src/toolbox_assets.ts
61
+ var require2 = createRequire(import.meta.url);
62
+ var vendorFiles = {
63
+ "vis-network.js": ["vis-network", "standalone/umd/vis-network.min.js"]
64
+ };
65
+ function packageRoot(pkg) {
66
+ try {
67
+ return dirname(require2.resolve(`${pkg}/package.json`));
68
+ } catch {
69
+ try {
70
+ const entry = require2.resolve(pkg);
71
+ const marker = `${sep2}node_modules${sep2}${pkg}${sep2}`;
72
+ const idx = entry.lastIndexOf(marker);
73
+ return idx === -1 ? null : entry.slice(0, idx + marker.length - 1);
74
+ } catch {
75
+ return null;
76
+ }
77
+ }
78
+ }
79
+ function vendorText(name) {
80
+ const spec = vendorFiles[name];
81
+ if (!spec) {
82
+ return null;
83
+ }
84
+ const root = packageRoot(spec[0]);
85
+ return root === null ? null : readText(join3(root, spec[1]));
86
+ }
87
+ function graphFile(hroot, wanted, kind) {
88
+ const [registry] = loadRegistry(hroot);
89
+ const entry = registry.harnesses.find((e) => {
90
+ const raw = readText(join3(e.project_root, "harness.config.json"));
91
+ let name = e.project_root.split("/").pop() ?? e.project_root;
92
+ if (raw) {
93
+ try {
94
+ name = String(JSON.parse(raw).project_name ?? name);
95
+ } catch {
96
+ }
97
+ }
98
+ return name === wanted;
99
+ });
100
+ if (!entry) {
101
+ return { status: "unknown_harness" };
102
+ }
103
+ let text = readText(join3(entry.project_root, "graphify-out", `graph.${kind}`));
104
+ if (text === null) {
105
+ return { status: "no_export" };
106
+ }
107
+ if (kind === "html") {
108
+ text = text.replace(
109
+ /<script\s+src="https:\/\/unpkg\.com\/vis-network@[^"]*"[^>]*><\/script>/,
110
+ '<script src="/vendor/vis-network.js"></script>'
111
+ );
112
+ }
113
+ const type = kind === "html" ? "text/html; charset=utf-8" : "application/json; charset=utf-8";
114
+ return { status: "ok", text, type };
115
+ }
116
+ export {
117
+ graphFile,
118
+ vendorText
119
+ };