@letterstory/cli 0.73.0 → 0.73.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.
package/lib/cli.mjs CHANGED
@@ -56,7 +56,7 @@ import {
56
56
  } from "./commands.mjs";
57
57
 
58
58
  // Keep in sync with cli/package.json.
59
- export const VERSION = "0.73.0";
59
+ export const VERSION = "0.73.1";
60
60
 
61
61
  // Flags that never take a value. Listing them explicitly means `deploy get --json <id>`
62
62
  // can't accidentally swallow the id as --json's value.
@@ -639,8 +639,9 @@ Admin (STAFF key — --staff-key / LETTERSTORY_STAFF_API_KEY; acts before an org
639
639
  candidate, compare, comparisons, picks, pick,
640
640
  ledger, reading-list, finalists,
641
641
  finalists-queue, pair, side-by-side,
642
- evals, eval-add, eval-set, proposals,
643
- propose-eval, capture-eval, reject-eval, promote,
642
+ checklist, checklist-add, checklist-set,
643
+ checklist-labels, feedback, feedback-add,
644
+ feedback-file, feedback-reject, promote,
644
645
  sessions, session-new, propose, trial-add,
645
646
  trial-run, trial-save, the Write input bank
646
647
  (inputs, input, inputs-harvest, input-save),
@@ -8,8 +8,9 @@
8
8
  // admin tuning-context [--q "<question>"] (tuning:experiment)
9
9
  // admin tuning-import <brief-results.json> [--experiment <id>] (tuning:experiment)
10
10
  // admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|criteria|promote … (tuning:experiment)
11
- // admin tuning evals|eval-add|eval-set|proposals|propose-eval|capture-eval|reject-eval … (tuning:experiment)
12
- // (propose-eval --fix "…" files the reader's rewrite; the diff is classified to a lever, or to a schema gap: kind lever)
11
+ // admin tuning checklist|checklist-add|checklist-set|checklist-labels|feedback|feedback-add|feedback-file|feedback-reject … (tuning:experiment)
12
+ // (feedback-add --fix "…" files the reader's rewrite; the diff is classified and a disposition suggested.
13
+ // The old names — evals, eval-add, eval-set, proposals, propose-eval, capture-eval, reject-eval — still work.)
13
14
  // admin evals scoreboard|cases|runs|run|triage|guard|retire … (fleet:read / tuning:experiment)
14
15
  // admin crm import|capture|signals|record|signal|accounts|account|score|slack-channels|link-slack|white-glove|set-white-glove|churn|record-churn|undo-churn|onboard|onboarding|retry-onboarding|onboard-plan|payment-link … (crm:read / crm:write)
15
16
  // admin replays [--org <id>] [--target <id>] [--status …] [--limit n] (fleet:read)
@@ -449,15 +450,19 @@ async function cmdAdminTuningContext(ctx) {
449
450
  * admin tuning criteria the Eye Test's criteria: id, label and the question each asks of a pair
450
451
  * admin tuning loop where the loop stands: what waits at each station, and where to go next
451
452
  * admin tuning ledger <comparison-id> the defect ledger: per arm, what the diagnosis read named, the floor and the ceiling, the candidate's next arm; then the reader edits per arm by lever, and the schema gaps
452
- * admin tuning evals the eval list: every defect category with its status and the picks' agreement (n, agreement, suggestion)
453
- * admin tuning eval-add --id … --label … --how … --layer … --lever … --locality local|structural --axis … [--example "span"] add a category (starts unvetted)
454
- * admin tuning eval-set <id> [--status confirmed|rejected|unvetted] [--example "span" [--fix "…"]] confirm / reject (a person's call), or add an example
455
- * admin tuning proposals [--status proposed|captured|rejected] the eval proposals: spans readers marked in a pair's text with their words, and what became of each
456
- * admin tuning propose-eval --span "…" --comment "…" [--fix "…"] [--comparison <id> --cell <key> --side control|candidate] [--kind narrow|wide] mark a span as an eval (no --side from a blind read); --fix is the reader's rewrite, classified to a lever (or a schema gap: kind lever)
457
- * admin tuning capture-eval <proposal-id> --as category --id … --label … --how … --layer … --lever … --locality local|structural --axis … capture it into the eval list, the span (and its fix) as the category's example (appends when the id exists)
458
- * admin tuning capture-eval <proposal-id> --as rule file it as narrow; the rule itself is written in the kernels rule lab
459
- * admin tuning capture-eval <proposal-id> --as lever [--note "…"] file it as a schema gap: an edit no kernel-file lever can produce, for the kernels repo
460
- * admin tuning reject-eval <proposal-id> a span that was not an eval after all
453
+ * admin tuning checklist the checklist: every defect category with its status, the picks' agreement (n, agreement, suggestion) and its labels (+/−) (alias: evals)
454
+ * admin tuning checklist-add --id … --label … --how … --layer … --lever … --locality local|structural --axis … [--example "span"] add a category (starts unvetted) (alias: eval-add)
455
+ * admin tuning checklist-set <id> [--status confirmed|rejected|unvetted] [--example "span" [--fix "…"]] confirm / reject (a person's call), or add an example (alias: eval-set)
456
+ * admin tuning checklist-labels <id> the labelled set of one category: positives (examples + feedback filed into it), negatives (rejected feedback suggested for it), grid overrules
457
+ * admin tuning feedback [--status proposed|captured|rejected] the feedback: spans readers marked in a pair's text with their words, each with its disposition (alias: proposals)
458
+ * admin tuning feedback-add --span "…" --comment "…" [--fix "…"] [--comparison <id> --cell <key> --side control|candidate] [--kind narrow|wide] add feedback (no --side from a blind read); --fix is the reader's rewrite, classified and a disposition suggested (alias: propose-eval)
459
+ * admin tuning feedback-file <id> --as checklist --id … --label … --how … --layer … --lever … --locality local|structural --axis … file it on the checklist, the span (and its fix) as the category's example (appends when the id exists) (alias: capture-eval; --as category)
460
+ * admin tuning feedback-file <id> --as rule file it as a rule; the rule itself is written in the kernels rule lab
461
+ * admin tuning feedback-file <id> --as config [--note "…"] file it as a lever this candidate should pull
462
+ * admin tuning feedback-file <id> --as implementation [--note "…"] file it as a schema gap no setting can express, for the kernels repo (--as lever still works)
463
+ * admin tuning feedback-file <id> --as material [--note "…"] file it as the material's: the input never carried it
464
+ * admin tuning feedback-file <id> --as note keep it; no action
465
+ * admin tuning feedback-reject <id> feedback that was not worth keeping after all (alias: reject-eval)
461
466
  * admin tuning reading-list <comparison-id> [--n 10] your next blind pairs as links, to read off the screen
462
467
  * admin tuning finalists [--limit 10] the finalists feed: done, gates clean, no craft loss, not yet decided by the picks; ranked by the objective's order
463
468
  * admin tuning finalists-queue [--n 10] your next blind pairs round-robin across the finalists (a key reads the queue of the person who owns it; recording a pick still needs a person)
@@ -507,8 +512,8 @@ async function cmdAdminTuningContext(ctx) {
507
512
  * admin tuning screens | screen <id> list / one screen: per candidate, each measure against the Base, with its noise
508
513
  * admin tuning screen-advance <id> queue what the caps allow now (the cron does it every minute)
509
514
  *
510
- * The bake-off: a screen's versions graded item by item against the checklist (the eval list's
511
- * confirmed and unvetted categories, and the craft axes commitment / economy / movement), the winner
515
+ * The bake-off: a screen's versions graded item by item against the checklist's confirmed and
516
+ * unvetted categories and the craft axes commitment / economy / movement, the winner
512
517
  * per item tallied across the inputs, and a merge that composes the best of each into one candidate.
513
518
  * Deterministic; a person overrides a grade and confirms the merge, which is a hypothesis until the
514
519
  * eye test confirms it.
@@ -793,6 +798,7 @@ async function cmdAdminTuning(ctx) {
793
798
  })
794
799
  );
795
800
  }
801
+ case "checklist":
796
802
  case "evals": {
797
803
  const r = await staffRequest(ctx, "GET", `${T}/evals`);
798
804
  if (json) return printResult(io, flags, r);
@@ -800,16 +806,29 @@ async function cmdAdminTuning(ctx) {
800
806
  if (!rows.length) return io.log("(no categories)");
801
807
  const cell = (v, w) => String(v ?? "—").padEnd(w);
802
808
  io.log(
803
- `${cell("id", 24)} ${cell("status", 10)} ${cell("layer", 10)} ${cell("lever", 30)} ${"n".padStart(4)} ${"agree".padStart(6)} ${cell("suggest", 10)} ${"ex".padStart(3)}`
809
+ `${cell("id", 24)} ${cell("status", 10)} ${cell("layer", 10)} ${cell("lever", 30)} ${"n".padStart(4)} ${"agree".padStart(6)} ${cell("suggest", 10)} ${"+".padStart(3)} ${"−".padStart(3)}`
804
810
  );
805
811
  for (const c of rows) {
806
812
  const v = c.vetting ?? {};
813
+ const l = c.labels ?? { positives: (c.examples ?? []).length, negatives: 0 };
807
814
  io.log(
808
- `${cell(c.id, 24)} ${cell(c.status, 10)} ${cell(c.layer, 10)} ${cell(c.lever || "—", 30)} ${String(v.n ?? 0).padStart(4)} ${String(v.agreement ?? "—").padStart(6)} ${cell(v.suggestion, 10)} ${String((c.examples ?? []).length).padStart(3)}`
815
+ `${cell(c.id, 24)} ${cell(c.status, 10)} ${cell(c.layer, 10)} ${cell(c.lever || "—", 30)} ${String(v.n ?? 0).padStart(4)} ${String(v.agreement ?? "—").padStart(6)} ${cell(v.suggestion, 10)} ${String(l.positives).padStart(3)} ${String(l.negatives).padStart(3)}`
809
816
  );
810
817
  }
811
818
  return;
812
819
  }
820
+ case "checklist-labels": {
821
+ if (!id) throw new CliError("Usage: admin tuning checklist-labels <category-id>");
822
+ const r = await staffRequest(ctx, "GET", `${T}/evals/${encodeURIComponent(id)}/labels`);
823
+ if (json) return printResult(io, flags, r);
824
+ io.log(
825
+ `${r.category_id}: ${r.counts.positives} positive, ${r.counts.negatives} negative (${r.grid_overrules} grid overrule${r.grid_overrules === 1 ? "" : "s"})`
826
+ );
827
+ for (const x of r.positives ?? []) io.log(` + [${x.source}] "${x.span}"${x.fix ? ` → "${x.fix}"` : ""}`);
828
+ for (const x of r.negatives ?? []) io.log(` − [${x.source}] "${x.span}"${x.fix ? ` → "${x.fix}"` : ""}`);
829
+ return;
830
+ }
831
+ case "checklist-add":
813
832
  case "eval-add": {
814
833
  const body = {
815
834
  id: flagStr(flags.id),
@@ -830,25 +849,26 @@ async function cmdAdminTuning(ctx) {
830
849
  !["local", "structural"].includes(body.locality ?? "")
831
850
  )
832
851
  throw new CliError(
833
- 'Usage: admin tuning eval-add --id … --label … --how … --layer … --lever … --locality local|structural --axis … [--example "span"]'
852
+ 'Usage: admin tuning checklist-add --id … --label … --how … --layer … --lever … --locality local|structural --axis … [--example "span"]'
834
853
  );
835
854
  if (example) body.examples = [{ span: example, ...(flags.fix ? { fix: flagStr(flags.fix) } : {}) }];
836
855
  const r = await staffRequest(ctx, "POST", `${T}/evals`, body);
837
856
  return json ? printResult(io, flags, r) : io.log(`added ${r.category.id} (${r.category.status})`);
838
857
  }
858
+ case "checklist-set":
839
859
  case "eval-set": {
840
860
  const status = flagStr(flags.status);
841
861
  const example = flagStr(flags.example);
842
862
  if (!id || (!status && !example) || (status && !["confirmed", "rejected", "unvetted"].includes(status)))
843
863
  throw new CliError(
844
- 'Usage: admin tuning eval-set <id> [--status confirmed|rejected|unvetted] [--example "span" [--fix "…"]]'
864
+ 'Usage: admin tuning checklist-set <id> [--status confirmed|rejected|unvetted] [--example "span" [--fix "…"]]'
845
865
  );
846
866
  const patch = {};
847
867
  if (status) patch.status = status;
848
868
  if (example) {
849
869
  // The PATCH replaces the whole list: read what is stored and append.
850
870
  const current = (await staffRequest(ctx, "GET", `${T}/evals`)).categories?.find((c) => c.id === id);
851
- if (!current) throw new CliError(`No category ${id} in the eval list`);
871
+ if (!current) throw new CliError(`No category ${id} on the checklist`);
852
872
  const fix = flagStr(flags.fix);
853
873
  patch.examples = [...(current.examples ?? []), { span: example, ...(fix ? { fix } : {}) }];
854
874
  }
@@ -857,27 +877,32 @@ async function cmdAdminTuning(ctx) {
857
877
  ? printResult(io, flags, r)
858
878
  : io.log(`${r.category.id}: ${r.category.status}, ${(r.category.examples ?? []).length} examples`);
859
879
  }
880
+ case "feedback":
860
881
  case "proposals": {
861
882
  const status = flagStr(flags.status);
862
883
  if (status && !["proposed", "captured", "rejected"].includes(status))
863
- throw new CliError("Usage: admin tuning proposals [--status proposed|captured|rejected]");
884
+ throw new CliError("Usage: admin tuning feedback [--status proposed|captured|rejected]");
864
885
  const qs = status ? `?${new URLSearchParams({ status })}` : "";
865
886
  const r = await staffRequest(ctx, "GET", `${T}/proposals${qs}`);
866
887
  if (json) return printResult(io, flags, r);
867
888
  const rows = r.proposals ?? [];
868
- if (!rows.length) return io.log("(no proposals)");
889
+ if (!rows.length) return io.log("(no feedback)");
869
890
  for (const p of rows) {
870
891
  const where = [p.cell_key, p.side].filter(Boolean).join(" / ") || "—";
892
+ const suggested = p.suggested_disposition
893
+ ? `; suggested ${p.suggested_disposition}${p.suggested_category ? ` (${p.suggested_category})` : ""}`
894
+ : "";
871
895
  const edit =
872
896
  p.fix != null
873
- ? `\n → "${p.fix}" [${p.edit_class ?? "?"} → ${p.lever ?? "no lever: schema gap"}]`
897
+ ? `\n → "${p.fix}" [${p.edit_class ?? "?"} → ${p.lever ?? "no lever: schema gap"}${suggested}]`
874
898
  : "";
875
899
  io.log(
876
- `${p.id} ${p.status.padEnd(9)} ${p.kind.padEnd(6)} ${where}${p.category_id ? ` → ${p.category_id}` : ""}\n "${p.span}"${edit}${p.comment ? `\n ${p.comment}` : ""}`
900
+ `${p.id} ${p.status.padEnd(9)} ${(p.disposition ?? p.kind).padEnd(14)} ${where}${p.category_id ? ` → ${p.category_id}` : ""}\n "${p.span}"${edit}${p.comment ? `\n ${p.comment}` : ""}`
877
901
  );
878
902
  }
879
903
  return;
880
904
  }
905
+ case "feedback-add":
881
906
  case "propose-eval": {
882
907
  const span = flagStr(flags.span);
883
908
  const side = flagStr(flags.side);
@@ -888,7 +913,7 @@ async function cmdAdminTuning(ctx) {
888
913
  (kind && !["narrow", "wide"].includes(kind))
889
914
  )
890
915
  throw new CliError(
891
- 'Usage: admin tuning propose-eval --span "…" --comment "…" [--fix "…"] [--comparison <id> --cell <key> --side control|candidate] [--kind narrow|wide]'
916
+ 'Usage: admin tuning feedback-add --span "…" --comment "…" [--fix "…"] [--comparison <id> --cell <key> --side control|candidate] [--kind narrow|wide]'
892
917
  );
893
918
  const r = await staffRequest(
894
919
  ctx,
@@ -906,17 +931,27 @@ async function cmdAdminTuning(ctx) {
906
931
  );
907
932
  if (json) return printResult(io, flags, r);
908
933
  const p = r.proposal;
909
- const edit = p.fix != null ? ` — ${p.edit_class}: ${p.lever ?? "no lever maps: a schema gap"}` : "";
910
- return io.log(`proposed ${p.id} (${p.kind})${edit}`);
911
- }
934
+ const suggested = p.suggested_disposition
935
+ ? `; suggested ${p.suggested_disposition}${p.suggested_category ? ` (${p.suggested_category})` : ""}`
936
+ : "";
937
+ const edit =
938
+ p.fix != null ? ` — ${p.edit_class}: ${p.lever ?? "no lever maps: a schema gap"}${suggested}` : "";
939
+ return io.log(`added feedback ${p.id} (${p.disposition ?? p.kind})${edit}`);
940
+ }
941
+ case "feedback-file":
912
942
  case "capture-eval": {
913
- const as = flagStr(flags.as);
943
+ // The first names of checklist and implementation, still accepted.
944
+ const AS_ALIAS = { category: "checklist", lever: "implementation" };
945
+ const asGiven = flagStr(flags.as);
946
+ const as = AS_ALIAS[asGiven] ?? asGiven;
914
947
  const usage =
915
- 'Usage: admin tuning capture-eval <proposal-id> --as category --id … --label … --how … --layer … --lever … --locality local|structural --axis … | --as rule | --as lever [--note "…"]';
916
- if (!id || !["category", "rule", "lever"].includes(as ?? "")) throw new CliError(usage);
948
+ 'Usage: admin tuning feedback-file <id> --as checklist --id … --label … --how … --layer … --lever … --locality local|structural --axis … | --as rule | --as config|implementation|material [--note "…"] | --as note';
949
+ if (!id || !["checklist", "rule", "config", "implementation", "material", "note"].includes(as ?? ""))
950
+ throw new CliError(usage);
917
951
  let body = { as };
918
- if (as === "lever") body = compact({ as, note: flagStr(flags.note) });
919
- if (as === "category") {
952
+ if (as === "config" || as === "implementation" || as === "material")
953
+ body = compact({ as, note: flagStr(flags.note) });
954
+ if (as === "checklist") {
920
955
  const category = {
921
956
  id: flagStr(flags.id),
922
957
  label: flagStr(flags.label),
@@ -938,14 +973,22 @@ async function cmdAdminTuning(ctx) {
938
973
  body = { as, category };
939
974
  }
940
975
  const r = await staffRequest(ctx, "POST", `${T}/proposals/${id}/capture`, body);
941
- return json
942
- ? printResult(io, flags, r)
943
- : io.log(
944
- `captured ${r.proposal.id} as ${r.proposal.kind}${r.category ? ` → ${r.category.id} (${(r.category.examples ?? []).length} examples)` : r.proposal.kind === "lever" ? "; a schema gap, filed for the kernels repo" : "; write the rule in the kernels rule lab"}`
945
- );
976
+ if (json) return printResult(io, flags, r);
977
+ const FILED = {
978
+ rule: "; write the rule in the kernels rule lab",
979
+ implementation: "; a schema gap, filed for the kernels repo",
980
+ config: "; a lever for this candidate",
981
+ material: "; the input never carried it",
982
+ note: "; kept, no action",
983
+ };
984
+ const d = r.proposal.disposition ?? as;
985
+ return io.log(
986
+ `filed ${r.proposal.id} as ${d}${r.category ? ` → ${r.category.id} (${(r.category.examples ?? []).length} examples)` : (FILED[d] ?? "")}`
987
+ );
946
988
  }
989
+ case "feedback-reject":
947
990
  case "reject-eval": {
948
- if (!id) throw new CliError("Usage: admin tuning reject-eval <proposal-id>");
991
+ if (!id) throw new CliError("Usage: admin tuning feedback-reject <id>");
949
992
  const r = await staffRequest(ctx, "POST", `${T}/proposals/${id}/reject`, {});
950
993
  return json ? printResult(io, flags, r) : io.log(`rejected ${r.proposal.id}`);
951
994
  }
@@ -958,11 +1001,13 @@ async function cmdAdminTuning(ctx) {
958
1001
  case "loop": {
959
1002
  const r = await staffRequest(ctx, "GET", `${T}/loop`);
960
1003
  if (json) return printResult(io, flags, r);
961
- io.log(`propose ${r.propose.running} running`);
1004
+ const week = r.propose.candidates_week ?? "?";
1005
+ const progs = r.propose.programmes_running ?? "?";
1006
+ io.log(`propose ${r.propose.running} running, ${week} candidates this week, ${progs} programmes running`);
962
1007
  io.log(`read ${r.read.settled_unread} settled, unread`);
963
1008
  io.log(`eye test ${r.eye_test.finalists} finalists, ${r.eye_test.pairs_to_read} pairs to read`);
964
1009
  io.log(
965
- `evals ${r.evals.proposals_waiting} proposals waiting, ${r.evals.unvetted} unvetted, ${r.evals.suggested} with a suggestion${r.evals.reachable ? "" : " (the eval list could not be read)"}`
1010
+ `checklist ${r.evals.proposals_waiting} feedback waiting, ${r.evals.unvetted} unvetted, ${r.evals.suggested} with a suggestion${r.evals.reachable ? "" : " (the checklist could not be read)"}`
966
1011
  );
967
1012
  io.log(`promote ${r.promote.promotable} won their picks`);
968
1013
  io.log(`\nnext: ${r.next}`);
@@ -2188,7 +2233,7 @@ async function cmdAdminTuning(ctx) {
2188
2233
  }
2189
2234
  default:
2190
2235
  throw new CliError(
2191
- "Usage: admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|loop|ledger|evals|eval-add|eval-set|proposals|propose-eval|capture-eval|reject-eval|reading-list|finalists|finalists-queue|pair|bank|bank-eval|bank-draft|bank-shape|bank-pool|bank-judge|bank-score|bank-try|people|invite|revoke|assignments|assign|unassign|tasks|side-by-side|candidates|base-kernels|base-versions|sessions|session|session-new|propose|trial-add|trial-run|trial-save|promote|arenas|arena|arena-candidate|arena-dials|comparison-extend|research-compare|research-inputs|research-split|research-standing|promote-arena|shred-compare|shred-inputs|shred-split|shred-standing|shredders|shredder|shredder-candidate|promote-shredder|enhance-compare|enhance-inputs|enhance-split|enhance-standing|enhancers|enhancer|enhancer-candidate|promote-enhancer (see --help)"
2236
+ "Usage: admin tuning kernels|kernel|candidate|compare|comparisons|comparison|picks|pick|loop|ledger|checklist|checklist-add|checklist-set|checklist-labels|feedback|feedback-add|feedback-file|feedback-reject|reading-list|finalists|finalists-queue|pair|bank|bank-eval|bank-draft|bank-shape|bank-pool|bank-judge|bank-score|bank-try|people|invite|revoke|assignments|assign|unassign|tasks|side-by-side|candidates|base-kernels|base-versions|sessions|session|session-new|propose|trial-add|trial-run|trial-save|promote|arenas|arena|arena-candidate|arena-dials|comparison-extend|research-compare|research-inputs|research-split|research-standing|promote-arena|shred-compare|shred-inputs|shred-split|shred-standing|shredders|shredder|shredder-candidate|promote-shredder|enhance-compare|enhance-inputs|enhance-split|enhance-standing|enhancers|enhancer|enhancer-candidate|promote-enhancer (see --help)"
2192
2237
  );
2193
2238
  }
2194
2239
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@letterstory/cli",
3
- "version": "0.73.0",
3
+ "version": "0.73.1",
4
4
  "description": "Spin up and manage Letterstory phantom blogs from your terminal.",
5
5
  "type": "module",
6
6
  "license": "UNLICENSED",