@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.
- package/assets/workshop-bundle/SKILL.md +8 -0
- package/assets/workshop-bundle/bundle-manifest.json +11 -11
- package/assets/workshop-bundle/content/challenge-cards/print-spec.md +1 -1
- package/assets/workshop-bundle/content/facilitation/master-guide.md +1 -1
- package/assets/workshop-bundle/content/talks/context-is-king.md +1 -1
- package/assets/workshop-bundle/workshop-skill/closing-skill.md +1 -1
- package/assets/workshop-bundle/workshop-skill/commands.md +4 -0
- package/assets/workshop-bundle/workshop-skill/locales/en/commands.md +4 -0
- package/assets/workshop-bundle/workshop-skill/locales/en/reference.md +1 -0
- package/assets/workshop-bundle/workshop-skill/reference.md +8 -0
- package/package.json +1 -1
- package/src/run-cli.js +102 -0
|
@@ -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.
|
|
5
|
-
"contentHash": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
169
|
+
"sha256": "db23689de38b1ebfc2d9dde5387c5386a774cc6e90ab3e6c95f7749601d6abb7"
|
|
170
170
|
},
|
|
171
171
|
{
|
|
172
172
|
"path": "workshop-skill/commands.md",
|
|
173
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
213
|
+
"sha256": "fe7d1c5f32d2a88b4673f565fda8bb389c4fe3b2a50aad5e2a9fafef7f75d658"
|
|
214
214
|
},
|
|
215
215
|
{
|
|
216
216
|
"path": "workshop-skill/setup.md",
|
|
@@ -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
|
-
##
|
|
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
|
|
22
|
+
3. Jaké 2–3 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
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
|
}
|