@method-ai-hq/sdk 0.19.0 → 0.20.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 (31) hide show
  1. package/README.md +1 -1
  2. package/dist/packages/sdk/package.json +2 -2
  3. package/dist/packages/sdk/src/authoring-instructions.d.ts +1 -1
  4. package/dist/packages/sdk/src/authoring-instructions.js +1 -1
  5. package/dist/packages/sdk/src/current-inspection.js +3 -0
  6. package/dist/packages/sdk/src/fix-lines.d.ts +82 -0
  7. package/dist/packages/sdk/src/fix-lines.js +63 -0
  8. package/dist/packages/sdk/src/fix-outcomes.d.ts +72 -0
  9. package/dist/packages/sdk/src/fix-outcomes.js +201 -0
  10. package/dist/packages/sdk/src/improve.d.ts +132 -34
  11. package/dist/packages/sdk/src/improve.js +523 -176
  12. package/dist/packages/sdk/src/judge.d.ts +12 -11
  13. package/dist/packages/sdk/src/judge.js +63 -39
  14. package/dist/packages/sdk/src/learned.d.ts +43 -0
  15. package/dist/packages/sdk/src/learned.js +97 -0
  16. package/dist/packages/sdk/src/local-cli.js +8 -1
  17. package/dist/packages/sdk/src/local-judgments.d.ts +2 -0
  18. package/dist/packages/sdk/src/method-files.d.ts +3 -2
  19. package/dist/packages/sdk/src/method-files.js +7 -3
  20. package/dist/packages/sdk/src/method-help.d.ts +1 -1
  21. package/dist/packages/sdk/src/method-help.js +16 -12
  22. package/dist/packages/sdk/src/method.js +51 -9
  23. package/dist/packages/sdk/src/quality.d.ts +62 -11
  24. package/dist/packages/sdk/src/quality.js +56 -21
  25. package/dist/packages/sdk/src/undo.d.ts +50 -0
  26. package/dist/packages/sdk/src/undo.js +108 -0
  27. package/dist/packages/sdk/src/watch.d.ts +20 -0
  28. package/dist/packages/sdk/src/watch.js +86 -0
  29. package/dist/packages/workflow-language/src/inspection.d.ts +30 -0
  30. package/dist/packages/workflow-language/src/inspection.js +3 -1
  31. package/package.json +2 -2
@@ -0,0 +1,108 @@
1
+ import { existsSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from 'node:fs';
2
+ import { basename, dirname, join, resolve, sep } from 'node:path';
3
+ import { randomBytes } from 'node:crypto';
4
+ import { parseArgs } from 'node:util';
5
+ import { packagePath } from '../../contracts/src/method-package.js';
6
+ import { authoringPath, readDocument } from './authoring.js';
7
+ import { DEFAULT_SERVER } from './method-client.js';
8
+ import { fileHash } from './method-files.js';
9
+ import { writePrivateJson } from './files.js';
10
+ import { localMethodFolder } from './local-judgments.js';
11
+ import { lastChangedFile, recordLastChange } from './fix-outcomes.js';
12
+ import { undoneLine } from './fix-lines.js';
13
+ import { methodIdPattern } from './versions.js';
14
+ const undoFolder = (file) => join(dirname(resolve(file)), '.method', 'undo');
15
+ const hashOf = (path) => existsSync(path) ? fileHash(readFileSync(path)) : null;
16
+ const inFolder = (file, path) => {
17
+ const folder = dirname(resolve(file)), full = resolve(folder, packagePath(path));
18
+ if (!full.startsWith(folder + sep))
19
+ throw Error(`The change names a file outside the Method folder: ${path}`);
20
+ return full;
21
+ };
22
+ /** Take what the file and the named files are now, before a change is written. */
23
+ export function snapshot(file, paths) {
24
+ return { text: readFileSync(file, 'utf8'), files: Object.fromEntries(paths.map(path => { const full = inFolder(file, path); return [path, existsSync(full) ? readFileSync(full).toString('base64') : null]; })) };
25
+ }
26
+ /** Record a change after it was written, so method undo can restore the state before it. */
27
+ export function recordChange(file, kind, cause, what, before) {
28
+ localMethodFolder(file);
29
+ mkdirSync(undoFolder(file), { recursive: true, mode: 0o700 });
30
+ const record = { id: `un_${Date.now().toString(36)}_${randomBytes(4).toString('hex')}`, file: basename(file), kind, created_at: new Date().toISOString(), cause, what, before,
31
+ after: { sha256: fileHash(readFileSync(file)), files: Object.fromEntries(Object.keys(before.files).map(path => [path, hashOf(inFolder(file, path))])) } };
32
+ writePrivateJson(join(undoFolder(file), `${record.id}.json`), record);
33
+ recordLastChange(file);
34
+ return record;
35
+ }
36
+ /** The changes of this file that can be undone, newest first. */
37
+ export function undoRecords(file) {
38
+ const folder = undoFolder(file);
39
+ if (!existsSync(folder))
40
+ return [];
41
+ return readdirSync(folder).filter(name => name.endsWith('.json')).map(name => ({ ...JSON.parse(readFileSync(join(folder, name), 'utf8')), path: join(folder, name) }))
42
+ .filter(record => record.file === basename(file) && !record.undone_at).sort((a, b) => b.created_at.localeCompare(a.created_at));
43
+ }
44
+ /**
45
+ * Restore the Method file, and the files the change wrote, as they were before the last change Method made (or before
46
+ * the change `id`, which must be the last one). A file that changed after that change is not overwritten. Returns the
47
+ * undone record; the caller saves the restored file as a new version.
48
+ */
49
+ export function undoLast(file, id) {
50
+ const last = undoRecords(file)[0];
51
+ if (!last || (id && last.id !== id))
52
+ throw Error(`Method has not changed ${basename(file)} on this computer${id ? ' since that change' : ''}, so there is nothing to undo.`);
53
+ if (fileHash(readFileSync(file)) !== last.after.sha256 || Object.entries(last.after.files).some(([path, sha]) => hashOf(inFolder(file, path)) !== sha))
54
+ throw Error(`${basename(file)} changed after Method's last change, so undo would lose that work. Undo the change by hand, or restore an earlier version on the Method page.`);
55
+ writeFileSync(file, last.before.text);
56
+ for (const [path, content] of Object.entries(last.before.files)) {
57
+ const full = inFolder(file, path);
58
+ if (content === null)
59
+ rmSync(full, { force: true });
60
+ else {
61
+ mkdirSync(dirname(full), { recursive: true });
62
+ writeFileSync(full, Buffer.from(content, 'base64'));
63
+ }
64
+ }
65
+ writePrivateJson(last.path, { ...stripPath(last), undone_at: new Date().toISOString() });
66
+ return last;
67
+ }
68
+ /**
69
+ * `method undo [FILE] [--json]`: restore the Method file, and the files the change wrote, as they were before the last
70
+ * fix, pruning change, or removed learned rule. The restored file is saved as a new version.
71
+ */
72
+ export async function undoCommand(args, clientFactory) {
73
+ const { values, positionals } = parseArgs({ args, allowPositionals: true, options: { json: { type: 'boolean' }, server: { type: 'string' } } });
74
+ if (positionals.length > 1)
75
+ throw Error('Use method undo [FILE].');
76
+ const file = positionals[0] ? authoringPath(positionals[0]) : defaultFile();
77
+ const last = undoLast(file);
78
+ const version = await saveVersion(clientFactory(values.server ?? DEFAULT_SERVER), file, `Undid Method's change: ${last.cause}`.slice(0, 500));
79
+ const line = undoneLine(last.what);
80
+ process.stdout.write(values.json ? JSON.stringify({ undone: { kind: last.kind, cause: last.cause, created_at: last.created_at }, version: version ?? null, line }, null, 2) + '\n' : line + '\n');
81
+ }
82
+ const stripPath = ({ path: _path, ...record }) => record;
83
+ function defaultFile() {
84
+ const here = readdirSync(process.cwd()).filter(name => name.endsWith('.method'));
85
+ if (here.length === 1)
86
+ return resolve(here[0]);
87
+ const last = lastChangedFile();
88
+ if (last)
89
+ return last;
90
+ throw Error('Name the Method file: method undo FILE.');
91
+ }
92
+ /** Save a changed file as a new version when this computer is signed in. Undefined when it cannot be saved now. */
93
+ export async function saveVersion(client, file, reason) {
94
+ const id = readDocument(file).id;
95
+ if (typeof id !== 'string' || !methodIdPattern.test(id) || !client.token())
96
+ return undefined;
97
+ try {
98
+ const { Outbox } = await import('./outbox.js');
99
+ const { prepareVersion } = await import('./versions.js');
100
+ const outbox = new Outbox(client);
101
+ const version = await prepareVersion(client, outbox, file, reason);
102
+ await outbox.drain();
103
+ return await outbox.whenSaved(version.version_id) ? { version_id: version.version_id, url: version.url } : undefined;
104
+ }
105
+ catch {
106
+ return undefined;
107
+ }
108
+ }
@@ -0,0 +1,20 @@
1
+ import type { MethodClient } from './method-client.js';
2
+ export type Watched = {
3
+ judgment: string;
4
+ caseId: string;
5
+ };
6
+ /** The corrections to watch: those that a fix answered, and those whose note became a learned rule, in the last 30 days. */
7
+ export declare function watchedCorrections(file: string, now?: number): Watched[];
8
+ type Judge = (expect: any[], outcome: any, caseDir: string, file: string) => Promise<{
9
+ status: string;
10
+ }>;
11
+ /**
12
+ * Judge one finished run against the watched corrections and send {held, run_id} for each. When the account reopens a
13
+ * correction (its problem came back on 2 runs), the next run of the Method says so and offers undo. A correction that
14
+ * the run cannot show (its output is missing) is skipped.
15
+ */
16
+ export declare function watchRun(client: MethodClient, file: string, runDir: string, judge?: Judge): Promise<{
17
+ judgment: string;
18
+ held: boolean;
19
+ }[]>;
20
+ export {};
@@ -0,0 +1,86 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { readDocument } from './authoring.js';
4
+ import { methodIdPattern } from './versions.js';
5
+ import { correctionCaseId } from './improve.js';
6
+ import { saveNotice, storedOutcomes } from './fix-outcomes.js';
7
+ import { cameBackLine } from './fix-lines.js';
8
+ import { activeRules, readLearned } from './learned.js';
9
+ // Watching fixes: after a normal run, the output that the run already made is judged against the corrections that
10
+ // fixes and learned rules of the last 30 days answered. Nothing runs again. The account gets {held, run_id} per
11
+ // correction and opens a correction again when its problem came back on 2 runs; the next run of the Method says so.
12
+ const watchDays = 30;
13
+ /** The corrections to watch: those that a fix answered, and those whose note became a learned rule, in the last 30 days. */
14
+ export function watchedCorrections(file, now = Date.now()) {
15
+ const id = readDocument(file).id;
16
+ if (typeof id !== 'string' || !methodIdPattern.test(id))
17
+ return [];
18
+ const since = now - watchDays * 86_400_000, found = new Map();
19
+ for (const record of storedOutcomes(id)) {
20
+ const o = record.outcome;
21
+ if (record.status !== 'done' || !o || Date.parse(record.finished_at ?? record.started_at) < since)
22
+ continue;
23
+ if (o.outcome === 'fixed' || (o.outcome === 'could_not_test' && o.rule_added))
24
+ found.set(o.id, { judgment: o.id, caseId: correctionCaseId(o.id) });
25
+ }
26
+ try {
27
+ for (const { rule } of activeRules(readLearned(file))) {
28
+ const judgment = rule.from?.judgment;
29
+ if (judgment && Date.parse(rule.created) >= since)
30
+ found.set(judgment, { judgment, caseId: rule.from?.case ?? correctionCaseId(judgment) });
31
+ }
32
+ }
33
+ catch { /* no readable learned rules */ }
34
+ const cases = join(dirname(file), 'cases');
35
+ return [...found.values()].filter(w => existsSync(join(cases, w.caseId, 'case.json')));
36
+ }
37
+ /** Judge with the prepared judges of the Method, two when both Claude Code and Codex are signed in. */
38
+ const defaultJudge = async (expect, outcome, caseDir, file) => {
39
+ const { preparedConfig, judgeProfiles, evaluateWithJudges } = await import('./quality.js');
40
+ const prepared = await preparedConfig(file, {}, { judge: true });
41
+ return evaluateWithJudges(expect, outcome, { caseDir, config: prepared.config, options: { runOptions: { processPath: prepared.processPath, ...(prepared.hostedModels ? { hostedModels: prepared.hostedModels } : {}) } } }, await judgeProfiles(prepared.config));
42
+ };
43
+ /**
44
+ * Judge one finished run against the watched corrections and send {held, run_id} for each. When the account reopens a
45
+ * correction (its problem came back on 2 runs), the next run of the Method says so and offers undo. A correction that
46
+ * the run cannot show (its output is missing) is skipped.
47
+ */
48
+ export async function watchRun(client, file, runDir, judge = defaultJudge) {
49
+ const methodId = readDocument(file).id;
50
+ const watched = watchedCorrections(file);
51
+ if (!watched.length || !client.token())
52
+ return [];
53
+ const { outcomeOfRun } = await import('@withmethod/runtime/cases.js');
54
+ const outcome = await outcomeOfRun(runDir);
55
+ const results = [];
56
+ const runId = (() => { try {
57
+ return JSON.parse(readFileSync(join(runDir, 'run-id.json'), 'utf8')).id;
58
+ }
59
+ catch {
60
+ return undefined;
61
+ } })();
62
+ for (const w of watched) {
63
+ const caseDir = join(dirname(file), 'cases', w.caseId);
64
+ const made = JSON.parse(readFileSync(join(caseDir, 'case.json'), 'utf8'));
65
+ let judged;
66
+ try {
67
+ judged = await judge(made.expect ?? [], outcome, caseDir, file);
68
+ }
69
+ catch {
70
+ continue;
71
+ }
72
+ if (judged.status === 'unverifiable')
73
+ continue;
74
+ const held = judged.status === 'pass';
75
+ results.push({ judgment: w.judgment, held });
76
+ // The account counts the runs where the problem came back; on the second it opens the correction again.
77
+ let reply;
78
+ try {
79
+ reply = await client.request(`/api/cli/judgments/${encodeURIComponent(w.judgment)}/watch`, 'POST', { held, ...(runId ? { run_id: runId } : {}) });
80
+ }
81
+ catch { /* the next run sends its own */ }
82
+ if (reply?.reopened)
83
+ saveNotice(methodId, file, 'came_back', cameBackLine(made.note));
84
+ }
85
+ return results;
86
+ }
@@ -141,6 +141,8 @@ export declare const RunEventSchema: z.ZodObject<{
141
141
  key: z.ZodOptional<z.ZodString>;
142
142
  attempt: z.ZodOptional<z.ZodNumber>;
143
143
  attempts: z.ZodOptional<z.ZodNumber>;
144
+ considered: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
145
+ applied: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
144
146
  }, "strict", z.ZodTypeAny, {
145
147
  at: string;
146
148
  type: string;
@@ -171,6 +173,8 @@ export declare const RunEventSchema: z.ZodObject<{
171
173
  unit?: string | undefined;
172
174
  attempt?: number | undefined;
173
175
  attempts?: number | undefined;
176
+ considered?: string[] | undefined;
177
+ applied?: string[] | undefined;
174
178
  }, {
175
179
  at: string;
176
180
  type: string;
@@ -201,6 +205,8 @@ export declare const RunEventSchema: z.ZodObject<{
201
205
  unit?: string | undefined;
202
206
  attempt?: number | undefined;
203
207
  attempts?: number | undefined;
208
+ considered?: string[] | undefined;
209
+ applied?: string[] | undefined;
204
210
  }>;
205
211
  export declare const InspectionSchema: z.ZodObject<{
206
212
  started_at: z.ZodOptional<z.ZodString>;
@@ -429,6 +435,8 @@ export declare const InspectionSchema: z.ZodObject<{
429
435
  key: z.ZodOptional<z.ZodString>;
430
436
  attempt: z.ZodOptional<z.ZodNumber>;
431
437
  attempts: z.ZodOptional<z.ZodNumber>;
438
+ considered: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
439
+ applied: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
432
440
  }, "strict", z.ZodTypeAny, {
433
441
  at: string;
434
442
  type: string;
@@ -459,6 +467,8 @@ export declare const InspectionSchema: z.ZodObject<{
459
467
  unit?: string | undefined;
460
468
  attempt?: number | undefined;
461
469
  attempts?: number | undefined;
470
+ considered?: string[] | undefined;
471
+ applied?: string[] | undefined;
462
472
  }, {
463
473
  at: string;
464
474
  type: string;
@@ -489,6 +499,8 @@ export declare const InspectionSchema: z.ZodObject<{
489
499
  unit?: string | undefined;
490
500
  attempt?: number | undefined;
491
501
  attempts?: number | undefined;
502
+ considered?: string[] | undefined;
503
+ applied?: string[] | undefined;
492
504
  }>, "many">>;
493
505
  invocations: z.ZodRecord<z.ZodString, z.ZodObject<{
494
506
  step_id: z.ZodString;
@@ -570,6 +582,8 @@ export declare const InspectionSchema: z.ZodObject<{
570
582
  key: z.ZodOptional<z.ZodString>;
571
583
  attempt: z.ZodOptional<z.ZodNumber>;
572
584
  attempts: z.ZodOptional<z.ZodNumber>;
585
+ considered: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
586
+ applied: z.ZodOptional<z.ZodArray<z.ZodString, "many">>;
573
587
  }, "strict", z.ZodTypeAny, {
574
588
  at: string;
575
589
  type: string;
@@ -600,6 +614,8 @@ export declare const InspectionSchema: z.ZodObject<{
600
614
  unit?: string | undefined;
601
615
  attempt?: number | undefined;
602
616
  attempts?: number | undefined;
617
+ considered?: string[] | undefined;
618
+ applied?: string[] | undefined;
603
619
  }, {
604
620
  at: string;
605
621
  type: string;
@@ -630,6 +646,8 @@ export declare const InspectionSchema: z.ZodObject<{
630
646
  unit?: string | undefined;
631
647
  attempt?: number | undefined;
632
648
  attempts?: number | undefined;
649
+ considered?: string[] | undefined;
650
+ applied?: string[] | undefined;
633
651
  }>, "many">;
634
652
  observed_effects: z.ZodOptional<z.ZodUnion<[z.ZodObject<{
635
653
  network: z.ZodArray<z.ZodString, "many">;
@@ -699,6 +717,8 @@ export declare const InspectionSchema: z.ZodObject<{
699
717
  unit?: string | undefined;
700
718
  attempt?: number | undefined;
701
719
  attempts?: number | undefined;
720
+ considered?: string[] | undefined;
721
+ applied?: string[] | undefined;
702
722
  }[];
703
723
  step_id: string;
704
724
  inputs?: Record<string, Json> | undefined;
@@ -760,6 +780,8 @@ export declare const InspectionSchema: z.ZodObject<{
760
780
  unit?: string | undefined;
761
781
  attempt?: number | undefined;
762
782
  attempts?: number | undefined;
783
+ considered?: string[] | undefined;
784
+ applied?: string[] | undefined;
763
785
  }[];
764
786
  step_id: string;
765
787
  inputs?: Record<string, Json> | undefined;
@@ -833,6 +855,8 @@ export declare const InspectionSchema: z.ZodObject<{
833
855
  unit?: string | undefined;
834
856
  attempt?: number | undefined;
835
857
  attempts?: number | undefined;
858
+ considered?: string[] | undefined;
859
+ applied?: string[] | undefined;
836
860
  }[];
837
861
  step_id: string;
838
862
  inputs?: Record<string, Json> | undefined;
@@ -937,6 +961,8 @@ export declare const InspectionSchema: z.ZodObject<{
937
961
  unit?: string | undefined;
938
962
  attempt?: number | undefined;
939
963
  attempts?: number | undefined;
964
+ considered?: string[] | undefined;
965
+ applied?: string[] | undefined;
940
966
  }[] | undefined;
941
967
  }, {
942
968
  status: "running" | "waiting_for_you" | "succeeded" | "unconfirmed" | "failed";
@@ -989,6 +1015,8 @@ export declare const InspectionSchema: z.ZodObject<{
989
1015
  unit?: string | undefined;
990
1016
  attempt?: number | undefined;
991
1017
  attempts?: number | undefined;
1018
+ considered?: string[] | undefined;
1019
+ applied?: string[] | undefined;
992
1020
  }[];
993
1021
  step_id: string;
994
1022
  inputs?: Record<string, Json> | undefined;
@@ -1093,6 +1121,8 @@ export declare const InspectionSchema: z.ZodObject<{
1093
1121
  unit?: string | undefined;
1094
1122
  attempt?: number | undefined;
1095
1123
  attempts?: number | undefined;
1124
+ considered?: string[] | undefined;
1125
+ applied?: string[] | undefined;
1096
1126
  }[] | undefined;
1097
1127
  }>;
1098
1128
  export type RunInspection = z.infer<typeof InspectionSchema>;
@@ -39,6 +39,8 @@ export const RunEventSchema = z.strictObject({
39
39
  total: z.number().int().nonnegative().optional(), unit: z.string().max(50).optional(),
40
40
  // Notice events: key is the same for each repeat of one problem, so a newer notice replaces an older one.
41
41
  key: z.string().max(500).optional(), attempt: z.number().int().positive().optional(), attempts: z.number().int().positive().optional(),
42
+ // learned_applied: the IDs of the learned rules that the step considered and the ones it added to its prompt.
43
+ considered: z.array(z.string().max(100)).max(500).optional(), applied: z.array(z.string().max(100)).max(500).optional(),
42
44
  });
43
45
  export const InspectionSchema = z.strictObject({
44
46
  started_at: z.string().optional(), device_name: z.string().optional(),
@@ -137,7 +139,7 @@ export function observedValue(item, inspection, invocation) {
137
139
  */
138
140
  export function deviceOnly(inspection) {
139
141
  const failure = inspection.failure ? (({ evidence, details, diagnostics, ...kept }) => ({ failure: kept }))(inspection.failure) : {};
140
- const event = ({ at, type, sequence, phase, duration_ms, usage, provider, model, kind, exit_code, completed, total, key, attempt, attempts }) => Object.fromEntries(Object.entries({ at, type, sequence, phase, duration_ms, usage, provider, model, kind, exit_code, completed, total, key, attempt, attempts }).filter(([, value]) => value !== undefined));
142
+ const event = ({ at, type, sequence, phase, duration_ms, usage, provider, model, kind, exit_code, completed, total, key, attempt, attempts, considered, applied }) => Object.fromEntries(Object.entries({ at, type, sequence, phase, duration_ms, usage, provider, model, kind, exit_code, completed, total, key, attempt, attempts, considered, applied }).filter(([, value]) => value !== undefined));
141
143
  return {
142
144
  schema: inspection.schema, content: 'device', workflow: inspection.workflow, run_id: inspection.run_id, status: inspection.status, ...failure,
143
145
  ...(inspection.started_at ? { started_at: inspection.started_at } : {}), ...(inspection.device_name ? { device_name: inspection.device_name } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@method-ai-hq/sdk",
3
- "version": "0.19.0",
3
+ "version": "0.20.1",
4
4
  "description": "Build and run reusable .method files with checked steps and saved results.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -35,7 +35,7 @@
35
35
  "prepack": "npm --prefix ../.. run build"
36
36
  },
37
37
  "dependencies": {
38
- "@withmethod/runtime": "https://codeload.github.com/method-ai-hq/method-spec/tar.gz/v0.15.0",
38
+ "@withmethod/runtime": "https://codeload.github.com/method-ai-hq/method-spec/tar.gz/v0.16.2",
39
39
  "fflate": "^0.8.3",
40
40
  "yaml": "^2.9.0",
41
41
  "zod": "^3.25.76"