@harness-lab/cli 0.2.9 → 0.3.0
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/README.md +5 -0
- package/assets/workshop-bundle/SKILL.md +8 -0
- package/assets/workshop-bundle/bundle-manifest.json +44 -52
- package/assets/workshop-bundle/content/challenge-cards/deck.md +19 -17
- package/assets/workshop-bundle/content/challenge-cards/locales/en/deck.md +7 -5
- package/assets/workshop-bundle/content/codex-craft.md +190 -0
- package/assets/workshop-bundle/content/facilitation/codex-setup-verification.md +5 -5
- package/assets/workshop-bundle/content/facilitation/master-guide.md +132 -65
- package/assets/workshop-bundle/content/project-briefs/code-review-helper.md +9 -9
- package/assets/workshop-bundle/content/project-briefs/devtoolbox-cli.md +11 -9
- package/assets/workshop-bundle/content/project-briefs/doc-generator.md +10 -8
- package/assets/workshop-bundle/content/project-briefs/locales/en/devtoolbox-cli.md +4 -2
- package/assets/workshop-bundle/content/project-briefs/locales/en/doc-generator.md +5 -3
- package/assets/workshop-bundle/content/project-briefs/locales/en/metrics-dashboard.md +4 -2
- package/assets/workshop-bundle/content/project-briefs/locales/en/standup-bot.md +4 -2
- package/assets/workshop-bundle/content/project-briefs/metrics-dashboard.md +14 -12
- package/assets/workshop-bundle/content/project-briefs/standup-bot.md +11 -9
- package/assets/workshop-bundle/content/talks/codex-demo-script.md +12 -10
- package/assets/workshop-bundle/content/talks/context-is-king.md +24 -24
- package/assets/workshop-bundle/docs/harness-cli-foundation.md +2 -0
- package/assets/workshop-bundle/docs/learner-resource-kit.md +37 -37
- package/assets/workshop-bundle/materials/coaching-codex.md +76 -0
- package/assets/workshop-bundle/materials/locales/en/participant-resource-kit.md +14 -2
- package/assets/workshop-bundle/materials/participant-resource-kit.md +23 -11
- package/assets/workshop-bundle/workshop-blueprint/README.md +2 -5
- package/assets/workshop-bundle/workshop-blueprint/day-structure.md +14 -0
- package/assets/workshop-bundle/workshop-skill/analyze-checklist.md +3 -3
- package/assets/workshop-bundle/workshop-skill/closing-skill.md +5 -5
- package/assets/workshop-bundle/workshop-skill/commands.md +13 -13
- package/assets/workshop-bundle/workshop-skill/facilitator.md +33 -0
- package/assets/workshop-bundle/workshop-skill/follow-up-package.md +13 -8
- package/assets/workshop-bundle/workshop-skill/install.md +8 -8
- package/assets/workshop-bundle/workshop-skill/locales/en/follow-up-package.md +8 -3
- package/assets/workshop-bundle/workshop-skill/locales/en/recap.md +8 -1
- package/assets/workshop-bundle/workshop-skill/locales/en/reference.md +19 -3
- package/assets/workshop-bundle/workshop-skill/locales/en/setup.md +1 -1
- package/assets/workshop-bundle/workshop-skill/recap.md +12 -5
- package/assets/workshop-bundle/workshop-skill/reference.md +45 -29
- package/assets/workshop-bundle/workshop-skill/setup.md +11 -11
- package/assets/workshop-bundle/workshop-skill/template-agents.md +4 -4
- package/package.json +1 -1
- package/src/client.js +9 -0
- package/src/io.js +1 -0
- package/src/run-cli.js +95 -0
- package/src/skill-install.js +108 -7
- package/src/workshop-bundle.js +30 -2
- package/assets/workshop-bundle/content/czech-editorial-review-checklist.md +0 -88
- package/assets/workshop-bundle/content/style-examples.md +0 -127
- package/assets/workshop-bundle/content/style-guide.md +0 -108
- package/assets/workshop-bundle/workshop-blueprint/edit-boundaries.md +0 -64
package/src/run-cli.js
CHANGED
|
@@ -166,6 +166,19 @@ function summarizeWorkshopInstance(instance) {
|
|
|
166
166
|
};
|
|
167
167
|
}
|
|
168
168
|
|
|
169
|
+
function summarizeParticipantAccess(participantAccess) {
|
|
170
|
+
return {
|
|
171
|
+
instanceId: participantAccess?.instanceId ?? null,
|
|
172
|
+
active: participantAccess?.active ?? false,
|
|
173
|
+
version: participantAccess?.version ?? null,
|
|
174
|
+
codeId: participantAccess?.codeId ?? null,
|
|
175
|
+
expiresAt: participantAccess?.expiresAt ?? null,
|
|
176
|
+
canRevealCurrent: participantAccess?.canRevealCurrent ?? false,
|
|
177
|
+
source: participantAccess?.source ?? "missing",
|
|
178
|
+
currentCode: participantAccess?.currentCode ?? null,
|
|
179
|
+
};
|
|
180
|
+
}
|
|
181
|
+
|
|
169
182
|
function resolveCurrentInstanceTarget(session, env) {
|
|
170
183
|
if (typeof session?.selectedInstanceId === "string" && session.selectedInstanceId.trim().length > 0) {
|
|
171
184
|
return {
|
|
@@ -210,6 +223,7 @@ function printUsage(io, ui) {
|
|
|
210
223
|
"harness workshop status",
|
|
211
224
|
"harness workshop list-instances",
|
|
212
225
|
"harness workshop show-instance <instance-id>",
|
|
226
|
+
"harness workshop participant-access [<instance-id>] [--rotate] [--code VALUE]",
|
|
213
227
|
"harness workshop archive [--notes TEXT]",
|
|
214
228
|
"harness workshop create-instance [<instance-id>] [--template-id ID] [--content-lang cs|en] [--event-title TEXT] [--city CITY]",
|
|
215
229
|
"harness workshop update-instance <instance-id> [--content-lang cs|en] [--event-title TEXT] [--city CITY]",
|
|
@@ -592,6 +606,22 @@ async function handleAuthLogout(io, ui, env, deps) {
|
|
|
592
606
|
return 0;
|
|
593
607
|
}
|
|
594
608
|
|
|
609
|
+
function renderSelectedInstanceBanner(ui, target) {
|
|
610
|
+
if (ui.jsonMode) {
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
const instanceId = target.instanceId ?? "none";
|
|
614
|
+
const label = target.source === "session"
|
|
615
|
+
? "Selected instance (locally selected)"
|
|
616
|
+
: target.instanceId
|
|
617
|
+
? `Selected instance (from ${target.source})`
|
|
618
|
+
: "Selected instance";
|
|
619
|
+
ui.keyValue(label, instanceId);
|
|
620
|
+
if (!target.instanceId) {
|
|
621
|
+
ui.keyValue("", "no instance is currently selected — run `harness workshop select-instance <id>` to pin one");
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
|
|
595
625
|
async function handleWorkshopStatus(io, ui, env, deps) {
|
|
596
626
|
const session = await requireSession(io, ui, env);
|
|
597
627
|
if (!session) {
|
|
@@ -602,6 +632,8 @@ async function handleWorkshopStatus(io, ui, env, deps) {
|
|
|
602
632
|
const client = createHarnessClient({ fetchFn: deps.fetchFn, session });
|
|
603
633
|
const target = resolveCurrentInstanceTarget(session, env);
|
|
604
634
|
|
|
635
|
+
renderSelectedInstanceBanner(ui, target);
|
|
636
|
+
|
|
605
637
|
if (target.source === "session" && target.instanceId) {
|
|
606
638
|
const [instanceResult, agenda] = await Promise.all([
|
|
607
639
|
client.getWorkshopInstance(target.instanceId),
|
|
@@ -609,6 +641,11 @@ async function handleWorkshopStatus(io, ui, env, deps) {
|
|
|
609
641
|
]);
|
|
610
642
|
ui.json("Workshop Status", {
|
|
611
643
|
ok: true,
|
|
644
|
+
selectedInstance: {
|
|
645
|
+
instanceId: target.instanceId,
|
|
646
|
+
source: target.source,
|
|
647
|
+
selected: true,
|
|
648
|
+
},
|
|
612
649
|
targetInstanceId: target.instanceId,
|
|
613
650
|
targetSource: target.source,
|
|
614
651
|
...summarizeWorkshopInstance(instanceResult.instance),
|
|
@@ -622,6 +659,11 @@ async function handleWorkshopStatus(io, ui, env, deps) {
|
|
|
622
659
|
const [workshop, agenda] = await Promise.all([client.getWorkshopStatus(), client.getAgenda()]);
|
|
623
660
|
ui.json("Workshop Status", {
|
|
624
661
|
ok: true,
|
|
662
|
+
selectedInstance: {
|
|
663
|
+
instanceId: target.instanceId ?? null,
|
|
664
|
+
source: target.source,
|
|
665
|
+
selected: Boolean(target.instanceId),
|
|
666
|
+
},
|
|
625
667
|
targetInstanceId: target.instanceId,
|
|
626
668
|
targetSource: target.source,
|
|
627
669
|
workshopId: workshop.workshopId,
|
|
@@ -801,6 +843,55 @@ async function handleWorkshopShowInstance(io, ui, env, positionals, flags, deps)
|
|
|
801
843
|
}
|
|
802
844
|
}
|
|
803
845
|
|
|
846
|
+
async function handleWorkshopParticipantAccess(io, ui, env, positionals, flags, deps) {
|
|
847
|
+
const session = await requireSession(io, ui, env);
|
|
848
|
+
if (!session) {
|
|
849
|
+
return 1;
|
|
850
|
+
}
|
|
851
|
+
|
|
852
|
+
const instanceId = await readRequiredCommandValue(
|
|
853
|
+
io,
|
|
854
|
+
flags,
|
|
855
|
+
["id", "instance-id"],
|
|
856
|
+
"Instance id: ",
|
|
857
|
+
readOptionalPositional(positionals, 2) ?? session.selectedInstanceId,
|
|
858
|
+
);
|
|
859
|
+
if (!instanceId) {
|
|
860
|
+
ui.status("error", "Instance id is required.", { stream: "stderr" });
|
|
861
|
+
return 1;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
try {
|
|
865
|
+
const client = createHarnessClient({ fetchFn: deps.fetchFn, session });
|
|
866
|
+
if (flags.rotate === true) {
|
|
867
|
+
const result = await client.issueWorkshopParticipantAccess(instanceId, {
|
|
868
|
+
...(typeof flags.code === "string" ? { code: flags.code } : {}),
|
|
869
|
+
});
|
|
870
|
+
ui.json("Workshop Participant Access", {
|
|
871
|
+
ok: true,
|
|
872
|
+
issuedCode: result.issuedCode ?? null,
|
|
873
|
+
...summarizeParticipantAccess(result.participantAccess),
|
|
874
|
+
participantAccess: result.participantAccess,
|
|
875
|
+
});
|
|
876
|
+
return 0;
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
const result = await client.getWorkshopParticipantAccess(instanceId);
|
|
880
|
+
ui.json("Workshop Participant Access", {
|
|
881
|
+
ok: true,
|
|
882
|
+
...summarizeParticipantAccess(result.participantAccess),
|
|
883
|
+
participantAccess: result.participantAccess,
|
|
884
|
+
});
|
|
885
|
+
return 0;
|
|
886
|
+
} catch (error) {
|
|
887
|
+
if (error instanceof HarnessApiError) {
|
|
888
|
+
ui.status("error", `Participant access failed: ${error.message}`, { stream: "stderr" });
|
|
889
|
+
return 1;
|
|
890
|
+
}
|
|
891
|
+
throw error;
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
|
|
804
895
|
async function handleWorkshopArchive(io, ui, env, flags, deps) {
|
|
805
896
|
const session = await requireSession(io, ui, env);
|
|
806
897
|
if (!session) {
|
|
@@ -1118,6 +1209,10 @@ export async function runCli(argv, io, deps = {}) {
|
|
|
1118
1209
|
return handleWorkshopShowInstance(io, ui, io.env, positionals, flags, mergedDeps);
|
|
1119
1210
|
}
|
|
1120
1211
|
|
|
1212
|
+
if (scope === "workshop" && action === "participant-access") {
|
|
1213
|
+
return handleWorkshopParticipantAccess(io, ui, io.env, positionals, flags, mergedDeps);
|
|
1214
|
+
}
|
|
1215
|
+
|
|
1121
1216
|
if (scope === "workshop" && action === "archive") {
|
|
1122
1217
|
return handleWorkshopArchive(io, ui, io.env, flags, mergedDeps);
|
|
1123
1218
|
}
|
package/src/skill-install.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import process from "node:process";
|
|
3
4
|
import {
|
|
4
5
|
createWorkshopBundleManifestFromDirectory,
|
|
5
6
|
createWorkshopBundleManifestFromSource,
|
|
@@ -12,6 +13,8 @@ import {
|
|
|
12
13
|
WORKSHOP_SKILL_NAME,
|
|
13
14
|
} from "./workshop-bundle.js";
|
|
14
15
|
|
|
16
|
+
const MIN_NODE_MAJOR = 22;
|
|
17
|
+
|
|
15
18
|
export class SkillInstallError extends Error {
|
|
16
19
|
constructor(message, options = {}) {
|
|
17
20
|
super(message);
|
|
@@ -20,6 +23,81 @@ export class SkillInstallError extends Error {
|
|
|
20
23
|
}
|
|
21
24
|
}
|
|
22
25
|
|
|
26
|
+
function assertSupportedNodeVersion() {
|
|
27
|
+
const raw = process.versions?.node;
|
|
28
|
+
if (!raw) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const major = Number.parseInt(raw.split(".")[0], 10);
|
|
32
|
+
if (Number.isFinite(major) && major < MIN_NODE_MAJOR) {
|
|
33
|
+
throw new SkillInstallError(
|
|
34
|
+
`Harness CLI requires Node.js ${MIN_NODE_MAJOR} or newer. This process is running Node.js ${raw}. Upgrade with your version manager (for example \`nvm install --lts\`) and re-run \`harness skill install\`.`,
|
|
35
|
+
{ code: "unsupported_node_version" },
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function translateFileSystemError(error, context) {
|
|
41
|
+
if (!error || typeof error !== "object" || !("code" in error)) {
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const targetPath = error.path ? ` (${error.path})` : "";
|
|
46
|
+
|
|
47
|
+
if (error.code === "EACCES" || error.code === "EPERM") {
|
|
48
|
+
return new SkillInstallError(
|
|
49
|
+
`Harness CLI could not ${context}${targetPath} because the current user does not have write permission. Try running from a directory you own, or adjust the directory permissions. On macOS and Linux, that usually means avoiding system paths like /usr. On Windows, avoid running from a protected location such as C:\\Program Files.`,
|
|
50
|
+
{ code: "install_permission_denied" },
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (error.code === "ENOSPC") {
|
|
55
|
+
return new SkillInstallError(
|
|
56
|
+
`Harness CLI could not ${context}${targetPath} because the disk is full. Free some space and re-run \`harness skill install\`.`,
|
|
57
|
+
{ code: "install_no_space" },
|
|
58
|
+
);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if (error.code === "ENAMETOOLONG" || error.code === "ENOTDIR") {
|
|
62
|
+
return new SkillInstallError(
|
|
63
|
+
`Harness CLI could not ${context}${targetPath}. The target path is too long or part of it is not a directory. On Windows, this often happens when the repository lives in a deeply nested folder — move the repo closer to the drive root (for example C:\\repos\\your-project) and try again.`,
|
|
64
|
+
{ code: "install_path_invalid" },
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
if (error.code === "EROFS") {
|
|
69
|
+
return new SkillInstallError(
|
|
70
|
+
`Harness CLI could not ${context}${targetPath} because the file system is read-only. Re-run \`harness skill install\` from a writable directory.`,
|
|
71
|
+
{ code: "install_read_only" },
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (error.code === "EBUSY") {
|
|
76
|
+
return new SkillInstallError(
|
|
77
|
+
`Harness CLI could not ${context}${targetPath} because the target is busy (another process may hold it open). Close editors or agents pointing at \`.agents/skills/\` and re-run \`harness skill install\`.`,
|
|
78
|
+
{ code: "install_target_busy" },
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async function fsWithActionableError(operation, context) {
|
|
86
|
+
try {
|
|
87
|
+
return await operation();
|
|
88
|
+
} catch (error) {
|
|
89
|
+
if (error instanceof SkillInstallError) {
|
|
90
|
+
throw error;
|
|
91
|
+
}
|
|
92
|
+
const translated = translateFileSystemError(error, context);
|
|
93
|
+
if (translated) {
|
|
94
|
+
translated.cause = error;
|
|
95
|
+
throw translated;
|
|
96
|
+
}
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
23
101
|
async function resolveBundleSource() {
|
|
24
102
|
const packagedBundlePath = getPackagedWorkshopBundlePath();
|
|
25
103
|
if (await pathExists(path.join(packagedBundlePath, "SKILL.md"))) {
|
|
@@ -43,8 +121,14 @@ async function resolveBundleSource() {
|
|
|
43
121
|
}
|
|
44
122
|
|
|
45
123
|
async function ensureDirectory(targetPath) {
|
|
46
|
-
await
|
|
47
|
-
|
|
124
|
+
await fsWithActionableError(
|
|
125
|
+
() => fs.mkdir(targetPath, { recursive: true }),
|
|
126
|
+
`create the install target directory`,
|
|
127
|
+
);
|
|
128
|
+
const stat = await fsWithActionableError(
|
|
129
|
+
() => fs.stat(targetPath),
|
|
130
|
+
`inspect the install target directory`,
|
|
131
|
+
);
|
|
48
132
|
if (!stat.isDirectory()) {
|
|
49
133
|
throw new SkillInstallError(`Install target is not a directory: ${targetPath}`, {
|
|
50
134
|
code: "invalid_target",
|
|
@@ -71,14 +155,22 @@ async function getSourceBundleManifest(resolvedBundle) {
|
|
|
71
155
|
|
|
72
156
|
async function installFromResolvedBundle(resolvedBundle, installPath) {
|
|
73
157
|
if (resolvedBundle.mode === "packaged_bundle") {
|
|
74
|
-
await
|
|
158
|
+
await fsWithActionableError(
|
|
159
|
+
() => fs.cp(resolvedBundle.sourcePath, installPath, { recursive: true }),
|
|
160
|
+
`copy the workshop bundle into the install target`,
|
|
161
|
+
);
|
|
75
162
|
return;
|
|
76
163
|
}
|
|
77
164
|
|
|
78
|
-
await
|
|
165
|
+
await fsWithActionableError(
|
|
166
|
+
() => createWorkshopBundleFromSource(resolvedBundle.sourceRoot, installPath),
|
|
167
|
+
`build the workshop bundle from source into the install target`,
|
|
168
|
+
);
|
|
79
169
|
}
|
|
80
170
|
|
|
81
171
|
export async function installWorkshopSkill(startDir, options = {}) {
|
|
172
|
+
assertSupportedNodeVersion();
|
|
173
|
+
|
|
82
174
|
const resolvedBundle = await resolveBundleSource();
|
|
83
175
|
if (!resolvedBundle) {
|
|
84
176
|
throw new SkillInstallError(
|
|
@@ -106,7 +198,10 @@ export async function installWorkshopSkill(startDir, options = {}) {
|
|
|
106
198
|
};
|
|
107
199
|
}
|
|
108
200
|
|
|
109
|
-
await
|
|
201
|
+
await fsWithActionableError(
|
|
202
|
+
() => fs.rm(installPath, { recursive: true, force: true }),
|
|
203
|
+
`remove the previous workshop bundle before refreshing it`,
|
|
204
|
+
);
|
|
110
205
|
|
|
111
206
|
return {
|
|
112
207
|
...(await installFreshBundle(resolvedBundle, installPath, targetRoot)),
|
|
@@ -115,7 +210,10 @@ export async function installWorkshopSkill(startDir, options = {}) {
|
|
|
115
210
|
}
|
|
116
211
|
|
|
117
212
|
if (existingInstall && options.force === true) {
|
|
118
|
-
await
|
|
213
|
+
await fsWithActionableError(
|
|
214
|
+
() => fs.rm(installPath, { recursive: true, force: true }),
|
|
215
|
+
`remove the previous workshop bundle before reinstalling`,
|
|
216
|
+
);
|
|
119
217
|
}
|
|
120
218
|
|
|
121
219
|
const result = await installFreshBundle(resolvedBundle, installPath, targetRoot);
|
|
@@ -130,7 +228,10 @@ export async function installWorkshopSkill(startDir, options = {}) {
|
|
|
130
228
|
}
|
|
131
229
|
|
|
132
230
|
async function installFreshBundle(resolvedBundle, installPath, targetRoot) {
|
|
133
|
-
await
|
|
231
|
+
await fsWithActionableError(
|
|
232
|
+
() => fs.mkdir(path.dirname(installPath), { recursive: true }),
|
|
233
|
+
`create the parent directory for the installed skill`,
|
|
234
|
+
);
|
|
134
235
|
await installFromResolvedBundle(resolvedBundle, installPath);
|
|
135
236
|
|
|
136
237
|
return {
|
package/src/workshop-bundle.js
CHANGED
|
@@ -26,8 +26,26 @@ const FILE_COPIES = [
|
|
|
26
26
|
["docs/locales/en/learner-reference-gallery.md", "docs/locales/en/learner-reference-gallery.md"],
|
|
27
27
|
["materials/participant-resource-kit.md", "materials/participant-resource-kit.md"],
|
|
28
28
|
["materials/locales/en/participant-resource-kit.md", "materials/locales/en/participant-resource-kit.md"],
|
|
29
|
+
["materials/coaching-codex.md", "materials/coaching-codex.md"],
|
|
29
30
|
];
|
|
30
31
|
|
|
32
|
+
// Files that live inside DIRECTORY_COPIES source trees but must not ship to
|
|
33
|
+
// participants. These are author/maintainer/copy-editor artifacts that have no
|
|
34
|
+
// runtime purpose inside the installed workshop skill. Keep the set minimal —
|
|
35
|
+
// every entry should be a file that (a) has no workshop-skill reference and
|
|
36
|
+
// (b) is clearly authoring/governance content rather than participant-facing.
|
|
37
|
+
const EXCLUDED_BUNDLE_PATHS = new Set([
|
|
38
|
+
"content/style-guide.md",
|
|
39
|
+
"content/style-examples.md",
|
|
40
|
+
"content/czech-reject-list.md",
|
|
41
|
+
"content/czech-editorial-review-checklist.md",
|
|
42
|
+
"workshop-blueprint/edit-boundaries.md",
|
|
43
|
+
]);
|
|
44
|
+
|
|
45
|
+
function isExcludedBundlePath(bundleRelativePath) {
|
|
46
|
+
return EXCLUDED_BUNDLE_PATHS.has(normalizePathForManifest(bundleRelativePath));
|
|
47
|
+
}
|
|
48
|
+
|
|
31
49
|
export function getPackageRoot() {
|
|
32
50
|
return packageRoot;
|
|
33
51
|
}
|
|
@@ -66,24 +84,31 @@ async function copyDirectoryTree(sourceRoot, targetRoot) {
|
|
|
66
84
|
await copyDirectoryRecursive(
|
|
67
85
|
path.join(sourceRoot, sourceRelativePath),
|
|
68
86
|
path.join(targetRoot, targetRelativePath),
|
|
87
|
+
targetRelativePath,
|
|
69
88
|
);
|
|
70
89
|
}
|
|
71
90
|
}
|
|
72
91
|
|
|
73
|
-
async function copyDirectoryRecursive(sourcePath, targetPath) {
|
|
92
|
+
async function copyDirectoryRecursive(sourcePath, targetPath, bundleRelativePrefix) {
|
|
74
93
|
const entries = await fs.readdir(sourcePath, { withFileTypes: true });
|
|
75
94
|
await fs.mkdir(targetPath, { recursive: true });
|
|
76
95
|
|
|
77
96
|
for (const entry of entries) {
|
|
78
97
|
const sourceEntryPath = path.join(sourcePath, entry.name);
|
|
79
98
|
const targetEntryPath = path.join(targetPath, entry.name);
|
|
99
|
+
const entryBundleRelativePath = bundleRelativePrefix
|
|
100
|
+
? path.join(bundleRelativePrefix, entry.name)
|
|
101
|
+
: entry.name;
|
|
80
102
|
|
|
81
103
|
if (entry.isDirectory()) {
|
|
82
|
-
await copyDirectoryRecursive(sourceEntryPath, targetEntryPath);
|
|
104
|
+
await copyDirectoryRecursive(sourceEntryPath, targetEntryPath, entryBundleRelativePath);
|
|
83
105
|
continue;
|
|
84
106
|
}
|
|
85
107
|
|
|
86
108
|
if (entry.isFile()) {
|
|
109
|
+
if (isExcludedBundlePath(entryBundleRelativePath)) {
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
87
112
|
await fs.copyFile(sourceEntryPath, targetEntryPath);
|
|
88
113
|
}
|
|
89
114
|
}
|
|
@@ -187,6 +212,9 @@ export async function createWorkshopBundleManifestFromSource(sourceRoot) {
|
|
|
187
212
|
if (targetRelative === "workshop-skill/SKILL.md") {
|
|
188
213
|
continue;
|
|
189
214
|
}
|
|
215
|
+
if (isExcludedBundlePath(targetRelative)) {
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
190
218
|
|
|
191
219
|
entries.push({
|
|
192
220
|
absolutePath: file.absolutePath,
|
|
@@ -1,88 +0,0 @@
|
|
|
1
|
-
# Czech Editorial Review Checklist
|
|
2
|
-
|
|
3
|
-
Použijte tento checklist při revizi českého obsahu pro účastníky, hlavně pro:
|
|
4
|
-
|
|
5
|
-
- workshop agenda a presenter scenes
|
|
6
|
-
- pokyny pro participant room
|
|
7
|
-
- project briefs
|
|
8
|
-
- challenge cards
|
|
9
|
-
- setup, reference, recap, follow-up a learner-kit materiály
|
|
10
|
-
|
|
11
|
-
Tento checklist doplňuje [`content/style-guide.md`](./style-guide.md) a [`content/style-examples.md`](./style-examples.md). Není to náhrada. Je to poslední quality gate před tím, než se text bere jako workshop-ready.
|
|
12
|
-
|
|
13
|
-
## 1. Přirozenost češtiny
|
|
14
|
-
|
|
15
|
-
- Zní text jako přirozená čeština, ne jako překlad?
|
|
16
|
-
- Přečetl by to český developer bez škobrtnutí?
|
|
17
|
-
- Nejsou ve větách doslovné anglické kalky?
|
|
18
|
-
- Nejsou věty zbytečně dlouhé nebo toporné?
|
|
19
|
-
|
|
20
|
-
## 2. Kvalita míchání češtiny a angličtiny
|
|
21
|
-
|
|
22
|
-
- Zůstává česká věta česká?
|
|
23
|
-
- Jsou anglické termíny použité jen tam, kde jsou v developerské praxi opravdu přirozené?
|
|
24
|
-
- Není v textu náhodně promíchaná angličtina jen proto, že původní zdroj byl anglický?
|
|
25
|
-
- Když se méně známý anglický termín objevuje poprvé, je stručně ukotvený česky?
|
|
26
|
-
|
|
27
|
-
## 3. Jasnost instrukce
|
|
28
|
-
|
|
29
|
-
- Je z textu jasné, co má člověk udělat právě teď?
|
|
30
|
-
- Mají věty konkrétní slovesa jako `spusťte`, `zkontrolujte`, `doplňte`, `ověřte`?
|
|
31
|
-
- Neschovává se akce za abstraktní formulace typu „je vhodné realizovat“?
|
|
32
|
-
- Nejsou odstavce přeplněné více cíli najednou?
|
|
33
|
-
|
|
34
|
-
## 4. Workshop voice
|
|
35
|
-
|
|
36
|
-
- Zní text jako zkušený peer, ne jako marketér nebo korporát?
|
|
37
|
-
- Drží se text klidného, věcného a praktického tónu?
|
|
38
|
-
- Nezní text jako slide, slogan nebo generický AI obsah?
|
|
39
|
-
- Je z textu cítit disciplína workshopu: kontext zapsaný v repu, ověření, handoff?
|
|
40
|
-
|
|
41
|
-
## 5. Terminologická disciplína
|
|
42
|
-
|
|
43
|
-
- Jsou opakované workshop terms použité konzistentně?
|
|
44
|
-
- Neobjevují se slabé nebo matoucí fráze jen proto, že znějí „AI-ish“?
|
|
45
|
-
- Jsou výrazy jako `safe move`, `handoff`, `checkpoint`, `workflow`, `review`, `skill`, `runbook` použité záměrně, ne náhodně?
|
|
46
|
-
- Není stejná věc jednou česky a podruhé napůl anglicky bez důvodu?
|
|
47
|
-
|
|
48
|
-
## 6. Vyhněte se těmto signálům
|
|
49
|
-
|
|
50
|
-
Pokud se v textu objeví něco z toho, vraťte ho do editace:
|
|
51
|
-
|
|
52
|
-
- doslovný překlad anglické vazby
|
|
53
|
-
- česká věta s náhodně vloženými anglickými slovy mimo technické termíny
|
|
54
|
-
- korporátní nebo školometský tón
|
|
55
|
-
- fráze, které nic nekotví k akci
|
|
56
|
-
- generické AI obraty bez konkrétního významu
|
|
57
|
-
|
|
58
|
-
## 7. Spoken check
|
|
59
|
-
|
|
60
|
-
Přečtěte text nahlas nebo aspoň polohlasem.
|
|
61
|
-
|
|
62
|
-
Text vrátit do editace, pokud:
|
|
63
|
-
|
|
64
|
-
- se nedá říct plynule
|
|
65
|
-
- obsahuje nepřirozený slovosled
|
|
66
|
-
- zní tvrdě přeloženě
|
|
67
|
-
- potřebuje v hlavě „opravovat“, co tím autor asi myslel
|
|
68
|
-
|
|
69
|
-
## 8. Cross-locale check
|
|
70
|
-
|
|
71
|
-
Pokud text vznikal z anglického source:
|
|
72
|
-
|
|
73
|
-
- není to doslovný překlad?
|
|
74
|
-
- drží česká verze stejný význam, ale přirozenější formulací?
|
|
75
|
-
- není česká verze výrazně slabší, plošší nebo méně konkrétní než anglická?
|
|
76
|
-
- není anglický source sám o sobě slabý a nehodí se nejdřív přepsat?
|
|
77
|
-
|
|
78
|
-
## 9. Before publish
|
|
79
|
-
|
|
80
|
-
Před schválením českého textu pro účastníky musí být možné říct `ano` na všechno:
|
|
81
|
-
|
|
82
|
-
1. Je to přirozená čeština?
|
|
83
|
-
2. Rozumí tomu český developer napoprvé?
|
|
84
|
-
3. Je jasné, co má člověk udělat nebo pochopit?
|
|
85
|
-
4. Drží text workshop voice bez hype a bez korporátu?
|
|
86
|
-
5. Je míchání češtiny a angličtiny disciplinované?
|
|
87
|
-
6. Neobsahuje text slabé fráze nebo „AI slop“?
|
|
88
|
-
7. Obstojí text při čtení nahlas?
|
|
@@ -1,127 +0,0 @@
|
|
|
1
|
-
# Czech Style Examples
|
|
2
|
-
|
|
3
|
-
## Do / Don't
|
|
4
|
-
|
|
5
|
-
### 1. Instrukce
|
|
6
|
-
|
|
7
|
-
Do:
|
|
8
|
-
|
|
9
|
-
- „Otevřete repozitář a nejdřív doplňte `AGENTS.md`.“
|
|
10
|
-
|
|
11
|
-
Don't:
|
|
12
|
-
|
|
13
|
-
- „V prvním kroku je vhodné provést vytvoření souboru `AGENTS.md`.“
|
|
14
|
-
|
|
15
|
-
### 2. Setup
|
|
16
|
-
|
|
17
|
-
Do:
|
|
18
|
-
|
|
19
|
-
- „Když se setup sekne, přejděte na Codex App nebo web fallback.“
|
|
20
|
-
|
|
21
|
-
Don't:
|
|
22
|
-
|
|
23
|
-
- „V případě komplikací v procesu instalace je možné využít alternativní variantu přístupu.“
|
|
24
|
-
|
|
25
|
-
### 3. Facilitation
|
|
26
|
-
|
|
27
|
-
Do:
|
|
28
|
-
|
|
29
|
-
- „Prvních 10 minut po rotaci jen čtěte a mapujte repo.“
|
|
30
|
-
|
|
31
|
-
Don't:
|
|
32
|
-
|
|
33
|
-
- „Po realizaci rotace doporučujeme zahájit aktivitu detailní analýzou repozitáře.“
|
|
34
|
-
|
|
35
|
-
### 4. Workshop framing
|
|
36
|
-
|
|
37
|
-
Do:
|
|
38
|
-
|
|
39
|
-
- „Dnes nejde o to být nejrychlejší. Jde o to předat práci tak, aby ji cizí tým dokázal převzít.“
|
|
40
|
-
|
|
41
|
-
Don't:
|
|
42
|
-
|
|
43
|
-
- „Hlavním cílem dnešního dne je maximalizace schopnosti efektivního předávání výstupů mezi týmy.“
|
|
44
|
-
|
|
45
|
-
### 5. Czech + English
|
|
46
|
-
|
|
47
|
-
Do:
|
|
48
|
-
|
|
49
|
-
- „Nejdřív spusťte `/plan`, potom implementujte malý krok a nakonec udělejte `/review`.“
|
|
50
|
-
|
|
51
|
-
Don't:
|
|
52
|
-
|
|
53
|
-
- „Nejdříve vytvořte plán, pak proveďte implementaci a následně zrevidujte změnu.“
|
|
54
|
-
|
|
55
|
-
Poznámka:
|
|
56
|
-
|
|
57
|
-
Tady je angličtina přirozenější, protože jde o konkrétní commands a workflow terms.
|
|
58
|
-
|
|
59
|
-
### 6. Technical explanation
|
|
60
|
-
|
|
61
|
-
Do:
|
|
62
|
-
|
|
63
|
-
- „Monitoring teď běží jako manuální MVP. Ondřej spustí scan repozitářů a z výsledku připraví intermezzo brief.“
|
|
64
|
-
|
|
65
|
-
Don't:
|
|
66
|
-
|
|
67
|
-
- „Monitoring je aktuálně realizován prostřednictvím minimální životaschopné verze manuálního typu.“
|
|
68
|
-
|
|
69
|
-
### 7. Briefs
|
|
70
|
-
|
|
71
|
-
Do:
|
|
72
|
-
|
|
73
|
-
- „Mock data jsou v pořádku, pokud workflow působí realisticky.“
|
|
74
|
-
|
|
75
|
-
Don't:
|
|
76
|
-
|
|
77
|
-
- „Použití simulovaných dat je akceptovatelné za předpokladu, že výsledný proces bude působit realisticky.“
|
|
78
|
-
|
|
79
|
-
### 8. Error / fallback messaging
|
|
80
|
-
|
|
81
|
-
Do:
|
|
82
|
-
|
|
83
|
-
- „Pokud vám nefunguje CLI, pokračujte přes App a neztrácejte dalších 20 minut.“
|
|
84
|
-
|
|
85
|
-
Don't:
|
|
86
|
-
|
|
87
|
-
- „V případě nefunkčnosti rozhraní příkazové řádky doporučujeme zvážit alternativní postup.“
|
|
88
|
-
|
|
89
|
-
## Approved English Terms
|
|
90
|
-
|
|
91
|
-
Tyto výrazy se v textech pro účastníky běžně nechávají v angličtině:
|
|
92
|
-
|
|
93
|
-
- `prompt`
|
|
94
|
-
- `review`
|
|
95
|
-
- `workflow`
|
|
96
|
-
- `skill`
|
|
97
|
-
- `runbook`
|
|
98
|
-
- `build`
|
|
99
|
-
- `deploy`
|
|
100
|
-
- `checkpoint`
|
|
101
|
-
- `fallback`
|
|
102
|
-
- `handoff`
|
|
103
|
-
- `CLI`
|
|
104
|
-
- `App`
|
|
105
|
-
- `repo`
|
|
106
|
-
- `README`
|
|
107
|
-
- `AGENTS.md`
|
|
108
|
-
- `Done When`
|
|
109
|
-
|
|
110
|
-
## Usually Better In Czech
|
|
111
|
-
|
|
112
|
-
Tyto části bývají lepší česky:
|
|
113
|
-
|
|
114
|
-
- cíle a instrukce
|
|
115
|
-
- facilitační věty
|
|
116
|
-
- reflexe a uzavírání
|
|
117
|
-
- vysvětlení, proč něco děláme
|
|
118
|
-
- očekávání a pravidla workshopu
|
|
119
|
-
|
|
120
|
-
## Rewrite Heuristic
|
|
121
|
-
|
|
122
|
-
Když text zní moc tvrdě přeloženě, uprav ho takto:
|
|
123
|
-
|
|
124
|
-
1. zkrať větu
|
|
125
|
-
2. vrať české sloveso do aktivního rodu
|
|
126
|
-
3. nech technický termín v angličtině
|
|
127
|
-
4. zkontroluj, že věta vede k akci
|