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,168 @@
1
+ import { createRequire } from 'node:module'; const require = createRequire(import.meta.url);
2
+
3
+ // ../packages/toolbox-core/dist/triage.js
4
+ import { readdirSync as readdirSync2 } from "node:fs";
5
+ import { join as join2 } from "node:path";
6
+
7
+ // ../packages/toolbox-core/dist/llm.js
8
+ var LlmError = class extends Error {
9
+ code;
10
+ constructor(code, message) {
11
+ super(message);
12
+ this.name = "LlmError";
13
+ this.code = code;
14
+ }
15
+ };
16
+
17
+ // ../packages/toolbox-core/dist/state.js
18
+ import { readdirSync, readFileSync, statSync } from "node:fs";
19
+ import { join, resolve, sep } from "node:path";
20
+ var INTAKE_MAX_BYTES = 256 * 1024;
21
+ var CSP_HEADER = "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; connect-src 'self'";
22
+ var HTML_CSP_HEADER = CSP_HEADER.replace("img-src 'self' data:", "img-src 'self' data: https://picsum.photos");
23
+ function readText(path) {
24
+ try {
25
+ return readFileSync(path, "utf-8");
26
+ } catch {
27
+ return null;
28
+ }
29
+ }
30
+ function readFeatures(workspace) {
31
+ const raw = readText(join(workspace, "feature_list.json"));
32
+ if (raw === null) {
33
+ return [];
34
+ }
35
+ let data;
36
+ try {
37
+ data = JSON.parse(raw);
38
+ } catch {
39
+ return [];
40
+ }
41
+ const features = data && typeof data === "object" && !Array.isArray(data) ? data.features : [];
42
+ if (!Array.isArray(features)) {
43
+ return [];
44
+ }
45
+ return features.filter((f) => !!f && typeof f === "object" && !Array.isArray(f)).map((f) => ({
46
+ id: typeof f.id === "number" ? f.id : null,
47
+ name: String(f.name ?? ""),
48
+ title: String(f.title ?? f.name ?? ""),
49
+ status: String(f.status ?? "pending"),
50
+ sprint: f.sprint ? String(f.sprint) : null,
51
+ blocked_reason: f.blocked_reason ? String(f.blocked_reason) : null,
52
+ depends_on: Array.isArray(f.depends_on) ? f.depends_on.filter(Number.isInteger) : []
53
+ }));
54
+ }
55
+ var MD_TOKEN_PATHS = {
56
+ current: (workspace) => join(workspace, "progress", "current.md"),
57
+ history: (workspace) => join(workspace, "progress", "history.md"),
58
+ index: (workspace) => join(workspace, "index.md"),
59
+ checkpoints: (_workspace, root) => join(root, "CHECKPOINTS.md")
60
+ };
61
+ var MD_TOKENS = Object.keys(MD_TOKEN_PATHS);
62
+
63
+ // ../packages/toolbox-core/dist/triage.js
64
+ var TRIAGE_EXCERPT_CHARS = 800;
65
+ function classify(name) {
66
+ for (const prefix of ["impl", "review", "explore"]) {
67
+ if (name.startsWith(`${prefix}_`)) {
68
+ return { kind: prefix, feature: name.slice(prefix.length + 1).replace(/\.md$/, "") };
69
+ }
70
+ }
71
+ return { kind: "other", feature: name.replace(/\.md$/, "") };
72
+ }
73
+ function listBacklogDocs(workspace) {
74
+ let names;
75
+ try {
76
+ names = readdirSync2(join2(workspace, "backlog")).filter((n) => n.endsWith(".md"));
77
+ } catch {
78
+ return [];
79
+ }
80
+ names.sort();
81
+ return names.map((name) => {
82
+ const body = readText(join2(workspace, "backlog", name)) ?? "";
83
+ return { id: name, ...classify(name), excerpt: body.slice(0, TRIAGE_EXCERPT_CHARS) };
84
+ });
85
+ }
86
+ function computeEvidenceDebt(workspace) {
87
+ const present = new Set(listBacklogDocs(workspace).filter((d) => d.kind === "review").map((d) => d.feature));
88
+ return readFeatures(workspace).filter((f) => f.status === "done" && f.name && !present.has(f.name)).map((f) => ({ id: f.id, name: f.name, missing: `review_${f.name}.md` }));
89
+ }
90
+ function composeTriageSystem() {
91
+ return [
92
+ "Eres un clasificador de backlog de un harness Handyman.",
93
+ "Clasificas documentos backlog/*.md y senalas posibles solapes.",
94
+ "",
95
+ "Responde SOLO un objeto JSON, sin texto alrededor y sin bloque de codigo:",
96
+ '{"report":[{"id":"<nombre de archivo>","categoria":"impl|review|explore|other","duplicado_de":"<id de otro doc, opcional>","confianza":<0.0-1.0>}]}',
97
+ "",
98
+ "Reglas:",
99
+ "- Un entry por documento recibido, usando su id exacto.",
100
+ "- duplicado_de solo si el solape es real y sustantivo; si dudas, omitelo.",
101
+ "- confianza es tu certeza en la clasificacion, no en el duplicado.",
102
+ "- No propongas fusionar ni borrar nada: un humano decide con mv/git.",
103
+ "- Si no hay evidencia suficiente para clasificar, usa categoria 'other' y confianza baja."
104
+ ].join("\n");
105
+ }
106
+ function composeTriagePrompt(docs) {
107
+ const body = docs.map((d) => `--- ${d.id} (prefijo: ${d.kind})
108
+ ${d.excerpt}`).join("\n\n");
109
+ return `Documentos del backlog (${docs.length}):
110
+
111
+ ${body}`;
112
+ }
113
+ function parseTriageReport(raw) {
114
+ const fenced = raw.match(/```(?:json)?\s*([\s\S]*?)```/);
115
+ const candidate = fenced?.[1] ?? raw;
116
+ const start = candidate.indexOf("{");
117
+ const end = candidate.lastIndexOf("}");
118
+ if (start === -1 || end <= start) {
119
+ return [];
120
+ }
121
+ let data;
122
+ try {
123
+ data = JSON.parse(candidate.slice(start, end + 1));
124
+ } catch {
125
+ return [];
126
+ }
127
+ const report = data?.report;
128
+ if (!Array.isArray(report)) {
129
+ return [];
130
+ }
131
+ return report.filter((e) => !!e && typeof e === "object" && !Array.isArray(e)).filter((e) => typeof e.id === "string" && e.id.length > 0).map((e) => {
132
+ const entry = {
133
+ id: String(e.id),
134
+ categoria: typeof e.categoria === "string" ? e.categoria : "other",
135
+ confianza: typeof e.confianza === "number" ? e.confianza : 0
136
+ };
137
+ if (typeof e.duplicado_de === "string" && e.duplicado_de.length > 0) {
138
+ entry.duplicado_de = e.duplicado_de;
139
+ }
140
+ return entry;
141
+ });
142
+ }
143
+ async function relayTriage(options) {
144
+ const { system, prompt, draft, evidenceDebt, onDelta, onResult, onError } = options;
145
+ try {
146
+ const result = await draft({ prompt, system }, onDelta);
147
+ onResult({
148
+ report: parseTriageReport(result.text),
149
+ evidence_debt: evidenceDebt,
150
+ model: result.model
151
+ });
152
+ } catch (error) {
153
+ if (error instanceof LlmError) {
154
+ onError(error);
155
+ return;
156
+ }
157
+ onError(new LlmError("provider_error", error instanceof Error ? error.message : String(error)));
158
+ }
159
+ }
160
+ export {
161
+ TRIAGE_EXCERPT_CHARS,
162
+ composeTriagePrompt,
163
+ composeTriageSystem,
164
+ computeEvidenceDebt,
165
+ listBacklogDocs,
166
+ parseTriageReport,
167
+ relayTriage
168
+ };