patchwork-os 0.2.0-beta.13.canary.293 → 0.2.0-beta.13.canary.295

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.
@@ -8,6 +8,7 @@ import "./git.js";
8
8
  import "./diagnostics.js";
9
9
  import "./http.js";
10
10
  import "./fanOut.js";
11
+ import "./outcomes.js";
11
12
  import "./asana.js";
12
13
  import "./gmail.js";
13
14
  import "./googleDrive.js";
@@ -9,6 +9,7 @@ import "./git.js";
9
9
  import "./diagnostics.js";
10
10
  import "./http.js";
11
11
  import "./fanOut.js";
12
+ import "./outcomes.js";
12
13
  // Connector-based tools
13
14
  import "./asana.js";
14
15
  import "./gmail.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/recipes/tools/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,aAAa;AACb,OAAO,WAAW,CAAC;AACnB,OAAO,UAAU,CAAC;AAClB,OAAO,kBAAkB,CAAC;AAC1B,OAAO,WAAW,CAAC;AACnB,OAAO,aAAa,CAAC;AAErB,wBAAwB;AACxB,OAAO,YAAY,CAAC;AACpB,OAAO,YAAY,CAAC;AACpB,OAAO,kBAAkB,CAAC;AAC1B,OAAO,aAAa,CAAC;AACrB,OAAO,aAAa,CAAC;AACrB,OAAO,aAAa,CAAC;AACrB,OAAO,eAAe,CAAC;AACvB,OAAO,YAAY,CAAC;AACpB,OAAO,aAAa,CAAC;AACrB,OAAO,iBAAiB,CAAC;AACzB,OAAO,cAAc,CAAC;AACtB,OAAO,eAAe,CAAC;AACvB,OAAO,cAAc,CAAC;AACtB,OAAO,cAAc,CAAC;AACtB,OAAO,cAAc,CAAC;AACtB,OAAO,WAAW,CAAC;AACnB,OAAO,gBAAgB,CAAC;AACxB,OAAO,aAAa,CAAC;AACrB,OAAO,aAAa,CAAC;AACrB,OAAO,WAAW,CAAC;AACnB,OAAO,aAAa,CAAC;AACrB,OAAO,iBAAiB,CAAC;AACzB,OAAO,oBAAoB,CAAC;AAC5B,OAAO,eAAe,CAAC;AACvB,OAAO,YAAY,CAAC;AACpB,OAAO,gBAAgB,CAAC;AACxB,OAAO,cAAc,CAAC;AACtB,OAAO,aAAa,CAAC;AACrB,OAAO,iBAAiB,CAAC;AACzB,OAAO,YAAY,CAAC;AACpB,OAAO,eAAe,CAAC;AACvB,OAAO,eAAe,CAAC;AACvB,OAAO,eAAe,CAAC;AACvB,OAAO,cAAc,CAAC;AACtB,OAAO,kBAAkB,CAAC;AAC1B,OAAO,eAAe,CAAC;AACvB,OAAO,cAAc,CAAC;AACtB,OAAO,gBAAgB,CAAC;AACxB,OAAO,cAAc,CAAC;AACtB,OAAO,cAAc,CAAC;AACtB,OAAO,cAAc,CAAC;AACtB,OAAO,eAAe,CAAC;AACvB,OAAO,aAAa,CAAC;AACrB,OAAO,eAAe,CAAC;AACvB,OAAO,aAAa,CAAC;AACrB,OAAO,aAAa,CAAC;AACrB,OAAO,mBAAmB,CAAC;AAQ3B,qCAAqC;AACrC,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,WAAW,EACX,aAAa,EACb,OAAO,EACP,OAAO,EACP,SAAS,EACT,YAAY,GACb,MAAM,oBAAoB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/recipes/tools/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,aAAa;AACb,OAAO,WAAW,CAAC;AACnB,OAAO,UAAU,CAAC;AAClB,OAAO,kBAAkB,CAAC;AAC1B,OAAO,WAAW,CAAC;AACnB,OAAO,aAAa,CAAC;AACrB,OAAO,eAAe,CAAC;AAEvB,wBAAwB;AACxB,OAAO,YAAY,CAAC;AACpB,OAAO,YAAY,CAAC;AACpB,OAAO,kBAAkB,CAAC;AAC1B,OAAO,aAAa,CAAC;AACrB,OAAO,aAAa,CAAC;AACrB,OAAO,aAAa,CAAC;AACrB,OAAO,eAAe,CAAC;AACvB,OAAO,YAAY,CAAC;AACpB,OAAO,aAAa,CAAC;AACrB,OAAO,iBAAiB,CAAC;AACzB,OAAO,cAAc,CAAC;AACtB,OAAO,eAAe,CAAC;AACvB,OAAO,cAAc,CAAC;AACtB,OAAO,cAAc,CAAC;AACtB,OAAO,cAAc,CAAC;AACtB,OAAO,WAAW,CAAC;AACnB,OAAO,gBAAgB,CAAC;AACxB,OAAO,aAAa,CAAC;AACrB,OAAO,aAAa,CAAC;AACrB,OAAO,WAAW,CAAC;AACnB,OAAO,aAAa,CAAC;AACrB,OAAO,iBAAiB,CAAC;AACzB,OAAO,oBAAoB,CAAC;AAC5B,OAAO,eAAe,CAAC;AACvB,OAAO,YAAY,CAAC;AACpB,OAAO,gBAAgB,CAAC;AACxB,OAAO,cAAc,CAAC;AACtB,OAAO,aAAa,CAAC;AACrB,OAAO,iBAAiB,CAAC;AACzB,OAAO,YAAY,CAAC;AACpB,OAAO,eAAe,CAAC;AACvB,OAAO,eAAe,CAAC;AACvB,OAAO,eAAe,CAAC;AACvB,OAAO,cAAc,CAAC;AACtB,OAAO,kBAAkB,CAAC;AAC1B,OAAO,eAAe,CAAC;AACvB,OAAO,cAAc,CAAC;AACtB,OAAO,gBAAgB,CAAC;AACxB,OAAO,cAAc,CAAC;AACtB,OAAO,cAAc,CAAC;AACtB,OAAO,cAAc,CAAC;AACtB,OAAO,eAAe,CAAC;AACvB,OAAO,aAAa,CAAC;AACrB,OAAO,eAAe,CAAC;AACvB,OAAO,aAAa,CAAC;AACrB,OAAO,aAAa,CAAC;AACrB,OAAO,mBAAmB,CAAC;AAQ3B,qCAAqC;AACrC,OAAO,EACL,mBAAmB,EACnB,aAAa,EACb,aAAa,EACb,WAAW,EACX,aAAa,EACb,OAAO,EACP,OAAO,EACP,SAAS,EACT,YAAY,GACb,MAAM,oBAAoB,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * outcomes.classify_issues — deterministic disposition classification for
3
+ * the outcome-ingester recipe, replacing an earlier LLM-judge agent step.
4
+ *
5
+ * The agent step read raw issue JSON and freehanded a disposition +
6
+ * checkedAt epoch per-issue. In production cron runs it flipped the SAME
7
+ * closed/COMPLETED issues between "confirmed" and "unknown" on alternating
8
+ * fires, and hallucinated checkedAt values spanning 2023–2027. Trust-replay
9
+ * reads this log to decide good:true/false, so non-deterministic
10
+ * classification directly corrupts the trust signal.
11
+ *
12
+ * This tool has no LLM in the loop: classifyIssueDisposition() is a pure
13
+ * function of state/labels, and checkedAt is the real clock.
14
+ */
15
+ export {};
@@ -0,0 +1,114 @@
1
+ /**
2
+ * outcomes.classify_issues — deterministic disposition classification for
3
+ * the outcome-ingester recipe, replacing an earlier LLM-judge agent step.
4
+ *
5
+ * The agent step read raw issue JSON and freehanded a disposition +
6
+ * checkedAt epoch per-issue. In production cron runs it flipped the SAME
7
+ * closed/COMPLETED issues between "confirmed" and "unknown" on alternating
8
+ * fires, and hallucinated checkedAt values spanning 2023–2027. Trust-replay
9
+ * reads this log to decide good:true/false, so non-deterministic
10
+ * classification directly corrupts the trust signal.
11
+ *
12
+ * This tool has no LLM in the loop: classifyIssueDisposition() is a pure
13
+ * function of state/labels, and checkedAt is the real clock.
14
+ */
15
+ import os from "node:os";
16
+ import path from "node:path";
17
+ import { assertWriteAllowed } from "../../featureFlags.js";
18
+ import { classifyIssueDisposition, OutcomeStore, } from "../../workers/outcomeStore.js";
19
+ import { registerTool } from "../toolRegistry.js";
20
+ registerTool({
21
+ id: "outcomes.classify_issues",
22
+ namespace: "outcomes",
23
+ description: "Deterministically classify a batch of GitHub issues (from github.list_issues / github.search_issues output) as confirmed/junk/unknown and persist to the outcome-log for trust-replay. No LLM judgment — pure function of state/stateReason/labels.",
24
+ paramsSchema: {
25
+ type: "object",
26
+ properties: {
27
+ issues: {
28
+ type: "string",
29
+ description: "JSON array of issue objects (each with url/state/stateReason/labels), typically piped in as {{issues}} from a prior list_issues/search_issues step.",
30
+ },
31
+ recipeName: {
32
+ type: "string",
33
+ description: "Optional recipe name to stamp on each outcome record.",
34
+ },
35
+ workerClass: {
36
+ type: "string",
37
+ description: "Optional worker action-class to stamp on each record.",
38
+ },
39
+ },
40
+ required: ["issues"],
41
+ },
42
+ outputSchema: {
43
+ type: "object",
44
+ properties: {
45
+ count: { type: "number" },
46
+ confirmed: { type: "number" },
47
+ junk: { type: "number" },
48
+ unknown: { type: "number" },
49
+ error: { type: "string" },
50
+ },
51
+ },
52
+ riskDefault: "medium",
53
+ isWrite: true,
54
+ execute: async ({ params }) => {
55
+ assertWriteAllowed("outcomes.classify_issues");
56
+ let issues;
57
+ try {
58
+ const raw = params.issues ? String(params.issues) : "[]";
59
+ const parsed = JSON.parse(raw);
60
+ issues = Array.isArray(parsed) ? parsed : [];
61
+ }
62
+ catch (err) {
63
+ return JSON.stringify({
64
+ count: 0,
65
+ confirmed: 0,
66
+ junk: 0,
67
+ unknown: 0,
68
+ error: `outcomes.classify_issues: invalid issues JSON — ${err instanceof Error ? err.message : String(err)}`,
69
+ });
70
+ }
71
+ const patchworkDir = process.env.PATCHWORK_HOME ?? path.join(os.homedir(), ".patchwork");
72
+ const store = new OutcomeStore(patchworkDir);
73
+ const recipeName = params.recipeName
74
+ ? String(params.recipeName)
75
+ : undefined;
76
+ const workerClass = params.workerClass
77
+ ? String(params.workerClass)
78
+ : undefined;
79
+ let confirmed = 0;
80
+ let junk = 0;
81
+ let unknown = 0;
82
+ const checkedAt = Date.now();
83
+ for (const issue of issues) {
84
+ const issueUrl = issue.url ?? issue.html_url;
85
+ if (!issueUrl)
86
+ continue;
87
+ const disposition = classifyIssueDisposition({
88
+ state: issue.state,
89
+ state_reason: issue.stateReason,
90
+ labels: issue.labels,
91
+ });
92
+ if (disposition === "confirmed")
93
+ confirmed++;
94
+ else if (disposition === "junk")
95
+ junk++;
96
+ else
97
+ unknown++;
98
+ store.upsert({
99
+ issueUrl,
100
+ disposition,
101
+ checkedAt,
102
+ ...(recipeName && { recipeName }),
103
+ ...(workerClass && { workerClass }),
104
+ });
105
+ }
106
+ return JSON.stringify({
107
+ count: confirmed + junk + unknown,
108
+ confirmed,
109
+ junk,
110
+ unknown,
111
+ });
112
+ },
113
+ });
114
+ //# sourceMappingURL=outcomes.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"outcomes.js","sourceRoot":"","sources":["../../../src/recipes/tools/outcomes.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "patchwork-os",
3
- "version": "0.2.0-beta.13.canary.293",
3
+ "version": "0.2.0-beta.13.canary.295",
4
4
  "description": "Your personal AI runtime, local-first. Patchwork OS gives any AI model a consistent set of tools, YAML recipes, a delegation policy with approval queue, and a durable trace memory — all on your machine, all under your policy.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -36,33 +36,16 @@ steps:
36
36
  max: 50
37
37
  into: issues
38
38
 
39
- - id: classify
40
- when: "{{issues}}"
41
- agent:
42
- prompt: |
43
- You are classifying GitHub issues to determine whether they were useful filings or junk.
44
-
45
- Issues JSON:
46
- {{issues}}
47
-
48
- For each issue, determine its disposition:
49
- - "confirmed" — issue is closed with stateReason "completed", OR has a label containing "confirmed", "verified", or "patchwork:valid". This means the filing was correct and accepted.
50
- - "junk" — issue has stateReason "not_planned", OR has a label containing "invalid", "duplicate", "wontfix", "won't fix", "not a bug", "by design", or "spam". This means the filing was noise.
51
- - "unknown" — issue is still open, or closed with no clear signal.
52
-
53
- Output ONLY raw JSONL — one JSON object per line, no markdown, no explanation:
54
- {"issueUrl":"<url>","disposition":"<confirmed|junk|unknown>","checkedAt":<epoch_ms>}
55
-
56
- Rules:
57
- - issueUrl is the issue's html_url field
58
- - checkedAt is the current Unix epoch in milliseconds (approximate — use the issue's updated_at epoch if current time unavailable)
59
- - Emit one line per issue. No trailing commas, no array brackets, no other text.
60
- - If there are no issues to classify, output nothing (empty string).
61
- model: claude-haiku-4-5-20251001
62
- into: outcome_lines
63
-
64
- - id: persist
65
- when: "{{outcome_lines}}"
66
- tool: file.append
67
- path: ~/.patchwork/outcome-log.jsonl
68
- content: "{{outcome_lines}}\n"
39
+ # Deterministic — no LLM in the classification path. An earlier LLM-judge
40
+ # agent step here was non-deterministic in production: the SAME closed/
41
+ # COMPLETED issues flipped between "confirmed" and "unknown" on alternating
42
+ # cron fires, and checkedAt was a hallucinated epoch spanning 2023-2027.
43
+ # Trust-replay reads this log to decide good:true/false, so the
44
+ # classification step MUST be pure data (state/stateReason/labels) plus
45
+ # the real clock, not a model's freehand judgment.
46
+ - id: classify_and_persist
47
+ when: "{{issues.json}}"
48
+ tool: outcomes.classify_issues
49
+ issues: "{{issues.json}}"
50
+ recipeName: outcome-ingester
51
+ into: outcome_result