@harness-lab/cli 0.3.0 → 0.3.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.
@@ -326,6 +326,14 @@ Rules for rich scenes:
326
326
 
327
327
  Archive the current workshop instance with optional notes.
328
328
 
329
+ ### `workshop facilitator learnings`
330
+
331
+ Query the cross-cohort learnings log to review rotation signals from past and current workshops.
332
+ Prefer invoking `harness --json workshop learnings` for machine-readable output.
333
+ Supports flags: `--tag TAG`, `--instance ID`, `--cohort NAME`, `--limit N` (default 20).
334
+ When the facilitator asks for rotation signals, captured observations, or what happened during past handoffs, use this command.
335
+ If the learnings log is empty, say so and suggest capturing the first signal using the rotation capture panel in the facilitator dashboard.
336
+
329
337
  ### `workshop analyze`
330
338
 
331
339
  Review the team's repo against the handoff criteria in `workshop-skill/analyze-checklist.md`.
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "manifestVersion": 1,
3
3
  "bundleName": "harness-lab-workshop",
4
- "bundleVersion": "0.3.0",
5
- "contentHash": "b0fd5ae303c75a44d04b923e25f7a28584c8fc63940b142fdfbba4f2f8ff255c",
4
+ "bundleVersion": "0.3.1",
5
+ "contentHash": "d52937fa7d980cdd71345b725b68bc934e939bc5b48cabd4d5bcfb55da03362f",
6
6
  "files": [
7
7
  {
8
8
  "path": "content/challenge-cards/.gitkeep",
@@ -18,7 +18,7 @@
18
18
  },
19
19
  {
20
20
  "path": "content/challenge-cards/print-spec.md",
21
- "sha256": "be7ecfdae9ada4145272f12e2d6018ccde7cfaeb9f48feb58aca7da7236815c0"
21
+ "sha256": "0c6bc2953d6296b8901705d938a29c1ef859490e53cc265fe401bb9585611e16"
22
22
  },
23
23
  {
24
24
  "path": "content/codex-craft.md",
@@ -34,7 +34,7 @@
34
34
  },
35
35
  {
36
36
  "path": "content/facilitation/master-guide.md",
37
- "sha256": "2c8033a43459dad42a88c1c842e5e915e6bb7d6dbdea38412d4a9c06b93544d7"
37
+ "sha256": "b5e107e88ab335aaa8870f51cf9c8b8dadb619d3b0f61dab087dacdcdef4c136"
38
38
  },
39
39
  {
40
40
  "path": "content/project-briefs/.gitkeep",
@@ -90,7 +90,7 @@
90
90
  },
91
91
  {
92
92
  "path": "content/talks/context-is-king.md",
93
- "sha256": "33583f7982dd8f2d4d1bcbb637c0408562d6e0e20825eea06f897b34fe5efe16"
93
+ "sha256": "31fb6a02037c50e0b3fa2ae4bc39f658924bd164279fb490fc192d604763ff9f"
94
94
  },
95
95
  {
96
96
  "path": "docs/harness-cli-foundation.md",
@@ -130,7 +130,7 @@
130
130
  },
131
131
  {
132
132
  "path": "SKILL.md",
133
- "sha256": "a8b8f76f8eeef2a7e7cd0d2906f33a7ab7a146a14383f5f679fddc6b3bdf8242"
133
+ "sha256": "0188da9de8f517a09dc4d0e2eb14f395aa465048253bf0c719995e8a232a33e2"
134
134
  },
135
135
  {
136
136
  "path": "workshop-blueprint/agenda.json",
@@ -166,11 +166,11 @@
166
166
  },
167
167
  {
168
168
  "path": "workshop-skill/closing-skill.md",
169
- "sha256": "7019aa2afe4ac68869f0ec0c4bf9509f35695d171b66292d874e41355da178e0"
169
+ "sha256": "db23689de38b1ebfc2d9dde5387c5386a774cc6e90ab3e6c95f7749601d6abb7"
170
170
  },
171
171
  {
172
172
  "path": "workshop-skill/commands.md",
173
- "sha256": "837c795baf133f220d3badf329c2484028f87209fb4e39cc729df54754b9a0a3"
173
+ "sha256": "d0af2bcc53c9752be3b013da002c22d4a8c7a09dea1995075b82550947141736"
174
174
  },
175
175
  {
176
176
  "path": "workshop-skill/facilitator.md",
@@ -186,7 +186,7 @@
186
186
  },
187
187
  {
188
188
  "path": "workshop-skill/locales/en/commands.md",
189
- "sha256": "17856fa19a44c3336709186cba171505db4b503d11bc16ad2fbc49f67d6c78a0"
189
+ "sha256": "596af69cff67b770c284b4485c94bc39b511a60f3ff1b2b34f91f2c58a9f8b03"
190
190
  },
191
191
  {
192
192
  "path": "workshop-skill/locales/en/follow-up-package.md",
@@ -198,7 +198,7 @@
198
198
  },
199
199
  {
200
200
  "path": "workshop-skill/locales/en/reference.md",
201
- "sha256": "f5931048a100e7da07b2b0cbfcf40d3f7744014cb0658df51e34930d23692903"
201
+ "sha256": "2e941f974ae86c79131770d40302eec0e7968e217f36d8ff166065c7819c47b3"
202
202
  },
203
203
  {
204
204
  "path": "workshop-skill/locales/en/setup.md",
@@ -210,7 +210,7 @@
210
210
  },
211
211
  {
212
212
  "path": "workshop-skill/reference.md",
213
- "sha256": "89ef69ce4380468557a2f43576286937baf891bb0dc19035f0f06da69a3d0e38"
213
+ "sha256": "fe7d1c5f32d2a88b4673f565fda8bb389c4fe3b2a50aad5e2a9fafef7f75d658"
214
214
  },
215
215
  {
216
216
  "path": "workshop-skill/setup.md",
@@ -17,7 +17,7 @@
17
17
 
18
18
  1. nahoře název karty
19
19
  2. pod ním kategorie
20
- 3. 2-3 věty zadání
20
+ 3. 23 věty zadání
21
21
  4. dole malý štítek:
22
22
  - `Před obědem`
23
23
  - `Po rotaci`
@@ -108,7 +108,7 @@ Pointa:
108
108
  - přenos mantinelů
109
109
  - přenos done criteria
110
110
 
111
- ### Most do Build fáze 1
111
+ ### Co si odnesete do build fáze
112
112
 
113
113
  Na konci talku má být jasné:
114
114
 
@@ -62,7 +62,7 @@ Vezmeme stejný malý task ve dvou podmínkách. Jedna varianta bude prompt blob
62
62
  - Když agent dělá víc, já musím lépe ověřovat.
63
63
  - Handoff není závěr dne. Je to průběžná podmínka celé práce.
64
64
 
65
- ## Most do Build fáze 1
65
+ ## Co si odnesete do build fáze
66
66
 
67
67
  Po tomhle talku se tým nemá vracet k repu s pocitem, že potřebuje jen chytřejší prompt. Má se vracet s jedním jasným očekáváním:
68
68
 
@@ -19,7 +19,7 @@ Na konci dne vytvoř krátké shrnutí workshopu, které je:
19
19
 
20
20
  1. Co pomohlo týmům pokračovat po handoffu
21
21
  2. Co opakovaně chybělo
22
- 3. Jaké 2-3 principy si má místnost odnést do příštího týdne
22
+ 3. Jaké 23 principy si má místnost odnést do příštího týdne
23
23
  4. Jedna závěrečná věta, která propojí workshop s reálnou prací v týmu
24
24
 
25
25
  ## Styl
@@ -39,6 +39,10 @@
39
39
  - Když si nejste jistí workflow: `workshop commands`
40
40
  - Když chcete materiály i po workshopu: `workshop resources`, `workshop follow-up`, `workshop gallery`
41
41
 
42
+ ## Facilitátorské příkazy
43
+
44
+ - `workshop facilitator learnings` — dotaz do learnings logu: záznamy z rotace z minulých i aktuálních workshopů. Podporuje filtrování podle tagu, instance a cohortu.
45
+
42
46
  ## Důležitá poznámka
43
47
 
44
48
  `workshop` skill je garantovaný výchozí nástroj workshopu. Další workflow skills a veřejné toolkity jsou doporučené rozšíření, ne podmínka účasti.
@@ -39,6 +39,10 @@
39
39
  - If you are unsure about workflow: `workshop commands`
40
40
  - If you want materials after the workshop too: `workshop resources`, `workshop follow-up`, `workshop gallery`
41
41
 
42
+ ## Facilitator commands
43
+
44
+ - `workshop facilitator learnings` — query the cross-cohort learnings log: rotation signals from past and current workshops. Supports filtering by tag, instance, and cohort.
45
+
42
46
  ## Important note
43
47
 
44
48
  The `workshop` skill is the guaranteed workshop default. Additional workflow skills and public toolkits are recommended accelerators, not a requirement for participation.
@@ -59,6 +59,7 @@
59
59
  - `Codex: $workshop` for orientation during the day
60
60
  - `Codex: $workshop template` when the repo is missing baseline context
61
61
  - `Codex: $workshop analyze` before handoff or after rotation when you want to expose blind spots in the repo quickly
62
+ - `Codex: $workshop facilitator learnings` or `harness workshop learnings` to query the cross-cohort learnings log (rotation signals from past workshops). Prefers the CLI path for machine-readable output with filtering by tag, instance, or cohort.
62
63
 
63
64
  ## Recommended participant loop
64
65
 
@@ -87,6 +87,14 @@
87
87
 
88
88
  Dobrý prompt nestačí. Když má práce přežít handoff, musí být kontext uložený v repu, další bezpečný krok musí být dohledatelný a ověření musí zůstat čitelné i pro cizí tým.
89
89
 
90
+ ## Learnings log — pro facilitátory
91
+
92
+ - Po rotaci můžete procházet zachycené signály přímo v CLI: `harness workshop learnings`
93
+ - Filtrujte podle tagu: `harness --json workshop learnings --tag missing_runbook`
94
+ - Filtrujte podle kohorty nebo instance: `--cohort 2026-Q2`, `--instance <id>`
95
+ - V dashboardu: rotační panel v detailu instance obsahuje capture formulář i přehled posledních záznamů
96
+ - Learnings log přežije smazání instance a postupně buduje evidenci pro vylepšení blueprintu
97
+
90
98
  ## Kam dál po workshopu
91
99
 
92
100
  - Oficiální docs, OpenAI článek o Harness Engineering a ověřené veřejné skill repozitáře najdete v [`docs/learner-reference-gallery.md`](../docs/learner-reference-gallery.md).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@harness-lab/cli",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Participant-facing Harness Lab CLI for facilitator auth and workshop operations",
5
5
  "license": "MIT",
6
6
  "type": "module",
package/src/run-cli.js CHANGED
@@ -1,3 +1,5 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
1
3
  import { getDefaultDashboardUrl } from "./config.js";
2
4
  import { createHarnessClient, HarnessApiError } from "./client.js";
3
5
  import { createCliUi, prompt, writeLine } from "./io.js";
@@ -231,6 +233,7 @@ function printUsage(io, ui) {
231
233
  "harness workshop prepare <instance-id>",
232
234
  "harness workshop remove-instance <instance-id>",
233
235
  "harness workshop phase set <phase-id>",
236
+ "harness workshop learnings [--tag TAG] [--instance ID] [--cohort NAME] [--limit N]",
234
237
  ]);
235
238
  }
236
239
 
@@ -1142,6 +1145,101 @@ async function handleWorkshopPhaseSet(io, ui, env, positionals, deps) {
1142
1145
  }
1143
1146
  }
1144
1147
 
1148
+ async function handleWorkshopLearningsQuery(io, ui, env, flags) {
1149
+ const dataDir = env.HARNESS_DATA_DIR ?? path.join(process.cwd(), "data");
1150
+ const logPath = env.HARNESS_LEARNINGS_LOG_PATH ?? path.join(dataDir, "learnings-log.jsonl");
1151
+
1152
+ let rawLines;
1153
+ try {
1154
+ const content = await fs.readFile(logPath, "utf8");
1155
+ rawLines = content.split("\n").filter((line) => line.trim().length > 0);
1156
+ } catch (error) {
1157
+ if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
1158
+ ui.json("Workshop Learnings", { ok: true, signals: [], totalMatched: 0, source: logPath });
1159
+ return 0;
1160
+ }
1161
+ ui.status("error", `Could not read learnings log at ${logPath}: ${error instanceof Error ? error.message : String(error)}`, { stream: "stderr" });
1162
+ return 1;
1163
+ }
1164
+
1165
+ let entries;
1166
+ try {
1167
+ entries = rawLines.map((line) => JSON.parse(line));
1168
+ } catch (error) {
1169
+ ui.status("error", `Learnings log has malformed JSON lines: ${error instanceof Error ? error.message : String(error)}`, { stream: "stderr" });
1170
+ return 1;
1171
+ }
1172
+
1173
+ const filterTag = readStringFlag(flags, "tag");
1174
+ const filterInstance = readStringFlag(flags, "instance");
1175
+ const filterCohort = readStringFlag(flags, "cohort");
1176
+ const limit = Number(readStringFlag(flags, "limit") ?? "20");
1177
+
1178
+ let matched = entries;
1179
+ if (filterTag) {
1180
+ matched = matched.filter((entry) =>
1181
+ Array.isArray(entry.signal?.tags) && entry.signal.tags.some((tag) => tag === filterTag),
1182
+ );
1183
+ }
1184
+ if (filterInstance) {
1185
+ matched = matched.filter((entry) => entry.instanceId === filterInstance);
1186
+ }
1187
+ if (filterCohort) {
1188
+ matched = matched.filter((entry) => entry.cohort === filterCohort);
1189
+ }
1190
+
1191
+ const totalMatched = matched.length;
1192
+ const limited = Number.isFinite(limit) && limit > 0 ? matched.slice(-limit) : matched;
1193
+
1194
+ if (ui.jsonMode) {
1195
+ ui.json("Workshop Learnings", {
1196
+ ok: true,
1197
+ totalMatched,
1198
+ returned: limited.length,
1199
+ source: logPath,
1200
+ signals: limited.map((entry) => ({
1201
+ cohort: entry.cohort,
1202
+ instanceId: entry.instanceId,
1203
+ capturedAt: entry.signal?.capturedAt ?? entry.loggedAt,
1204
+ capturedBy: entry.signal?.capturedBy ?? "unknown",
1205
+ teamId: entry.signal?.teamId ?? null,
1206
+ tags: entry.signal?.tags ?? [],
1207
+ freeText: entry.signal?.freeText ?? "",
1208
+ })),
1209
+ });
1210
+ return 0;
1211
+ }
1212
+
1213
+ ui.heading("Workshop Learnings");
1214
+ if (limited.length === 0) {
1215
+ ui.paragraph(totalMatched === 0
1216
+ ? "No signals captured yet. Use the rotation capture panel in the facilitator dashboard during the continuation shift."
1217
+ : `No signals matched the current filters (${totalMatched} total in log).`,
1218
+ );
1219
+ ui.blank();
1220
+ ui.keyValue("Source", logPath);
1221
+ return 0;
1222
+ }
1223
+
1224
+ ui.paragraph(`${totalMatched} signal${totalMatched === 1 ? "" : "s"} matched${totalMatched > limited.length ? ` (showing last ${limited.length})` : ""}`);
1225
+ ui.blank();
1226
+
1227
+ for (const entry of limited) {
1228
+ const signal = entry.signal ?? {};
1229
+ const capturedAt = signal.capturedAt ?? entry.loggedAt ?? "";
1230
+ const time = capturedAt ? new Date(capturedAt).toLocaleString("en-US", { dateStyle: "short", timeStyle: "short" }) : "";
1231
+ const team = signal.teamId ? ` [${signal.teamId}]` : "";
1232
+ const tags = Array.isArray(signal.tags) && signal.tags.length > 0 ? ` {${signal.tags.join(", ")}}` : "";
1233
+
1234
+ ui.section(`${entry.cohort ?? "?"} · ${time}${team}${tags}`);
1235
+ ui.paragraph(signal.freeText ?? "(no observation text)", { indent: " " });
1236
+ ui.blank();
1237
+ }
1238
+
1239
+ ui.keyValue("Source", logPath);
1240
+ return 0;
1241
+ }
1242
+
1145
1243
  export async function runCli(argv, io, deps = {}) {
1146
1244
  const fetchFn = deps.fetchFn ?? globalThis.fetch;
1147
1245
  const mergedDeps = { fetchFn, sleepFn: deps.sleepFn, openUrl: deps.openUrl, cwd: deps.cwd };
@@ -1241,6 +1339,10 @@ export async function runCli(argv, io, deps = {}) {
1241
1339
  return handleWorkshopPhaseSet(io, ui, io.env, positionals, mergedDeps);
1242
1340
  }
1243
1341
 
1342
+ if (scope === "workshop" && action === "learnings") {
1343
+ return handleWorkshopLearningsQuery(io, ui, io.env, flags);
1344
+ }
1345
+
1244
1346
  printUsage(io, ui);
1245
1347
  return 1;
1246
1348
  }