@versatly/workgraph 0.3.0 → 1.0.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.
@@ -1,1062 +0,0 @@
1
- import {
2
- __export,
3
- allClaims,
4
- append,
5
- checkpoint,
6
- create,
7
- createRun,
8
- createThread,
9
- historyOf,
10
- keywordSearch,
11
- list,
12
- listTypes,
13
- loadPolicyRegistry,
14
- loadRegistry,
15
- read,
16
- recent,
17
- refreshWikiLinkGraphIndex,
18
- saveRegistry,
19
- update
20
- } from "./chunk-65ZMX2WM.js";
21
-
22
- // src/workspace.ts
23
- var workspace_exports = {};
24
- __export(workspace_exports, {
25
- initWorkspace: () => initWorkspace,
26
- isWorkgraphWorkspace: () => isWorkgraphWorkspace,
27
- workspaceConfigPath: () => workspaceConfigPath
28
- });
29
- import fs2 from "fs";
30
- import path2 from "path";
31
-
32
- // src/bases.ts
33
- var bases_exports = {};
34
- __export(bases_exports, {
35
- generateBasesFromPrimitiveRegistry: () => generateBasesFromPrimitiveRegistry,
36
- primitiveRegistryManifestPath: () => primitiveRegistryManifestPath,
37
- readPrimitiveRegistryManifest: () => readPrimitiveRegistryManifest,
38
- syncPrimitiveRegistryManifest: () => syncPrimitiveRegistryManifest
39
- });
40
- import fs from "fs";
41
- import path from "path";
42
- import YAML from "yaml";
43
- var REGISTRY_MANIFEST_FILE = ".workgraph/primitive-registry.yaml";
44
- var DEFAULT_BASES_DIR = ".workgraph/bases";
45
- function primitiveRegistryManifestPath(workspacePath) {
46
- return path.join(workspacePath, REGISTRY_MANIFEST_FILE);
47
- }
48
- function readPrimitiveRegistryManifest(workspacePath) {
49
- const manifestPath = primitiveRegistryManifestPath(workspacePath);
50
- if (!fs.existsSync(manifestPath)) {
51
- throw new Error(`Primitive registry manifest not found: ${manifestPath}`);
52
- }
53
- const raw = fs.readFileSync(manifestPath, "utf-8");
54
- return YAML.parse(raw);
55
- }
56
- function syncPrimitiveRegistryManifest(workspacePath) {
57
- const registry = loadRegistry(workspacePath);
58
- const manifest = {
59
- version: 1,
60
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
61
- primitives: Object.values(registry.types).map((primitive) => ({
62
- name: primitive.name,
63
- directory: primitive.directory,
64
- canonical: primitive.builtIn,
65
- builtIn: primitive.builtIn,
66
- fields: Object.entries(primitive.fields).map(([name, field]) => ({
67
- name,
68
- type: field.type,
69
- ...field.required ? { required: true } : {},
70
- ...field.description ? { description: field.description } : {}
71
- }))
72
- })).sort((a, b) => a.name.localeCompare(b.name))
73
- };
74
- const manifestPath = primitiveRegistryManifestPath(workspacePath);
75
- ensureDirectory(path.dirname(manifestPath));
76
- fs.writeFileSync(manifestPath, YAML.stringify(manifest), "utf-8");
77
- return manifest;
78
- }
79
- function generateBasesFromPrimitiveRegistry(workspacePath, options = {}) {
80
- const manifest = readPrimitiveRegistryManifest(workspacePath);
81
- const includeNonCanonical = options.includeNonCanonical === true;
82
- const outputDirectory = path.join(workspacePath, options.outputDirectory ?? DEFAULT_BASES_DIR);
83
- ensureDirectory(outputDirectory);
84
- const generated = [];
85
- const primitives = manifest.primitives.filter(
86
- (primitive) => includeNonCanonical ? true : primitive.canonical
87
- );
88
- for (const primitive of primitives) {
89
- const relBasePath = `${primitive.name}.base`;
90
- const absBasePath = path.join(outputDirectory, relBasePath);
91
- const content = renderBaseFile(primitive);
92
- fs.writeFileSync(absBasePath, content, "utf-8");
93
- generated.push(path.relative(workspacePath, absBasePath).replace(/\\/g, "/"));
94
- }
95
- return {
96
- outputDirectory: path.relative(workspacePath, outputDirectory).replace(/\\/g, "/"),
97
- generated: generated.sort()
98
- };
99
- }
100
- function renderBaseFile(primitive) {
101
- const columnFields = primitive.fields.map((field) => field.name).filter((name, idx, arr) => arr.indexOf(name) === idx);
102
- const baseDoc = {
103
- id: primitive.name,
104
- title: `${titleCase(primitive.name)} Base`,
105
- source: {
106
- type: "folder",
107
- path: primitive.directory,
108
- extension: "md"
109
- },
110
- views: [
111
- {
112
- id: "table",
113
- type: "table",
114
- name: "All",
115
- columns: ["file.name", ...columnFields]
116
- }
117
- ]
118
- };
119
- return YAML.stringify(baseDoc);
120
- }
121
- function ensureDirectory(dirPath) {
122
- if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true });
123
- }
124
- function titleCase(value) {
125
- return value.split(/[-_]/g).filter(Boolean).map((segment) => segment[0].toUpperCase() + segment.slice(1)).join(" ");
126
- }
127
-
128
- // src/workspace.ts
129
- var WORKGRAPH_CONFIG_FILE = ".workgraph.json";
130
- function workspaceConfigPath(workspacePath) {
131
- return path2.join(workspacePath, WORKGRAPH_CONFIG_FILE);
132
- }
133
- function isWorkgraphWorkspace(workspacePath) {
134
- return fs2.existsSync(workspaceConfigPath(workspacePath));
135
- }
136
- function initWorkspace(targetPath, options = {}) {
137
- const resolvedPath = path2.resolve(targetPath);
138
- const configPath = workspaceConfigPath(resolvedPath);
139
- if (fs2.existsSync(configPath)) {
140
- throw new Error(`Workgraph workspace already initialized at ${resolvedPath}`);
141
- }
142
- const createdDirectories = [];
143
- ensureDir(resolvedPath, createdDirectories);
144
- ensureDir(path2.join(resolvedPath, ".workgraph"), createdDirectories);
145
- const registry = loadRegistry(resolvedPath);
146
- saveRegistry(resolvedPath, registry);
147
- syncPrimitiveRegistryManifest(resolvedPath);
148
- if (options.createTypeDirs !== false) {
149
- const types = listTypes(resolvedPath);
150
- for (const typeDef of types) {
151
- ensureDir(path2.join(resolvedPath, typeDef.directory), createdDirectories);
152
- }
153
- }
154
- const now = (/* @__PURE__ */ new Date()).toISOString();
155
- const config = {
156
- name: options.name ?? path2.basename(resolvedPath),
157
- version: "1.0.0",
158
- mode: "workgraph",
159
- createdAt: now,
160
- updatedAt: now
161
- };
162
- fs2.writeFileSync(configPath, JSON.stringify(config, null, 2) + "\n", "utf-8");
163
- if (options.createReadme !== false) {
164
- writeReadmeIfMissing(resolvedPath, config.name);
165
- }
166
- const bases = options.createBases === false ? { generated: [] } : generateBasesFromPrimitiveRegistry(resolvedPath);
167
- loadPolicyRegistry(resolvedPath);
168
- refreshWikiLinkGraphIndex(resolvedPath);
169
- return {
170
- workspacePath: resolvedPath,
171
- configPath,
172
- config,
173
- createdDirectories,
174
- seededTypes: listTypes(resolvedPath).map((t) => t.name),
175
- generatedBases: bases.generated,
176
- primitiveRegistryManifestPath: ".workgraph/primitive-registry.yaml"
177
- };
178
- }
179
- function ensureDir(dirPath, createdDirectories) {
180
- if (fs2.existsSync(dirPath)) return;
181
- fs2.mkdirSync(dirPath, { recursive: true });
182
- createdDirectories.push(dirPath);
183
- }
184
- function writeReadmeIfMissing(workspacePath, name) {
185
- const readmePath = path2.join(workspacePath, "README.md");
186
- if (fs2.existsSync(readmePath)) return;
187
- const content = `# ${name}
188
-
189
- Agent-first workgraph workspace for multi-agent coordination.
190
-
191
- ## Quickstart
192
-
193
- \`\`\`bash
194
- workgraph thread list --json
195
- workgraph thread next --claim --actor agent-a --json
196
- workgraph ledger show --count 20 --json
197
- \`\`\`
198
- `;
199
- fs2.writeFileSync(readmePath, content, "utf-8");
200
- }
201
-
202
- // src/command-center.ts
203
- var command_center_exports = {};
204
- __export(command_center_exports, {
205
- generateCommandCenter: () => generateCommandCenter
206
- });
207
- import fs3 from "fs";
208
- import path3 from "path";
209
- function generateCommandCenter(workspacePath, options = {}) {
210
- const actor = options.actor ?? "system";
211
- const recentCount = options.recentCount ?? 15;
212
- const relOutputPath = options.outputPath ?? "Command Center.md";
213
- const absOutputPath = resolvePathWithinWorkspace(workspacePath, relOutputPath);
214
- const normalizedOutputPath = path3.relative(workspacePath, absOutputPath).replace(/\\/g, "/");
215
- const allThreads = list(workspacePath, "thread");
216
- const openThreads = allThreads.filter((thread) => thread.fields.status === "open");
217
- const activeThreads = allThreads.filter((thread) => thread.fields.status === "active");
218
- const blockedThreads = allThreads.filter((thread) => thread.fields.status === "blocked");
219
- const doneThreads = allThreads.filter((thread) => thread.fields.status === "done");
220
- const claims = allClaims(workspacePath);
221
- const recentEvents = recent(workspacePath, recentCount);
222
- const content = renderCommandCenter({
223
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
224
- openThreads,
225
- activeThreads,
226
- blockedThreads,
227
- doneThreads,
228
- claims: [...claims.entries()].map(([target, owner]) => ({ target, owner })),
229
- recentEvents
230
- });
231
- const parentDir = path3.dirname(absOutputPath);
232
- if (!fs3.existsSync(parentDir)) fs3.mkdirSync(parentDir, { recursive: true });
233
- const existed = fs3.existsSync(absOutputPath);
234
- fs3.writeFileSync(absOutputPath, content, "utf-8");
235
- append(
236
- workspacePath,
237
- actor,
238
- existed ? "update" : "create",
239
- normalizedOutputPath,
240
- "command-center",
241
- {
242
- generated: true,
243
- open_threads: openThreads.length,
244
- active_claims: claims.size,
245
- recent_events: recentEvents.length
246
- }
247
- );
248
- return {
249
- outputPath: normalizedOutputPath,
250
- stats: {
251
- totalThreads: allThreads.length,
252
- openThreads: openThreads.length,
253
- activeThreads: activeThreads.length,
254
- blockedThreads: blockedThreads.length,
255
- doneThreads: doneThreads.length,
256
- activeClaims: claims.size,
257
- recentEvents: recentEvents.length
258
- },
259
- content
260
- };
261
- }
262
- function resolvePathWithinWorkspace(workspacePath, outputPath) {
263
- const base = path3.resolve(workspacePath);
264
- const resolved = path3.resolve(base, outputPath);
265
- if (!resolved.startsWith(base + path3.sep) && resolved !== base) {
266
- throw new Error(`Invalid command-center output path: ${outputPath}`);
267
- }
268
- return resolved;
269
- }
270
- function renderCommandCenter(input) {
271
- const header = [
272
- "# Workgraph Command Center",
273
- "",
274
- `Generated: ${input.generatedAt}`,
275
- ""
276
- ];
277
- const statusBlock = [
278
- "## Thread Status",
279
- "",
280
- `- Open: ${input.openThreads.length}`,
281
- `- Active: ${input.activeThreads.length}`,
282
- `- Blocked: ${input.blockedThreads.length}`,
283
- `- Done: ${input.doneThreads.length}`,
284
- ""
285
- ];
286
- const openTable = [
287
- "## Open Threads",
288
- "",
289
- "| Priority | Title | Path |",
290
- "|---|---|---|",
291
- ...input.openThreads.length > 0 ? input.openThreads.map((thread) => `| ${String(thread.fields.priority ?? "medium")} | ${String(thread.fields.title ?? "Untitled")} | \`${thread.path}\` |`) : ["| - | None | - |"],
292
- ""
293
- ];
294
- const claimsSection = [
295
- "## Active Claims",
296
- "",
297
- ...input.claims.length > 0 ? input.claims.map((claim) => `- ${claim.owner} -> \`${claim.target}\``) : ["- None"],
298
- ""
299
- ];
300
- const blockedSection = [
301
- "## Blocked Threads",
302
- "",
303
- ...input.blockedThreads.length > 0 ? input.blockedThreads.map((thread) => {
304
- const deps = Array.isArray(thread.fields.deps) ? thread.fields.deps.join(", ") : "";
305
- return `- ${String(thread.fields.title ?? thread.path)} (\`${thread.path}\`)${deps ? ` blocked by: ${deps}` : ""}`;
306
- }) : ["- None"],
307
- ""
308
- ];
309
- const recentSection = [
310
- "## Recent Ledger Activity",
311
- "",
312
- ...input.recentEvents.length > 0 ? input.recentEvents.map((event) => `- ${event.ts} ${event.op} ${event.actor} -> \`${event.target}\``) : ["- No activity"],
313
- ""
314
- ];
315
- return [
316
- ...header,
317
- ...statusBlock,
318
- ...openTable,
319
- ...claimsSection,
320
- ...blockedSection,
321
- ...recentSection
322
- ].join("\n");
323
- }
324
-
325
- // src/skill.ts
326
- var skill_exports = {};
327
- __export(skill_exports, {
328
- listSkills: () => listSkills,
329
- loadSkill: () => loadSkill,
330
- promoteSkill: () => promoteSkill,
331
- proposeSkill: () => proposeSkill,
332
- skillDiff: () => skillDiff,
333
- skillHistory: () => skillHistory,
334
- writeSkill: () => writeSkill
335
- });
336
- import fs4 from "fs";
337
- import path4 from "path";
338
- function writeSkill(workspacePath, title, body, actor, options = {}) {
339
- const slug = skillSlug(title);
340
- const bundleSkillPath = folderSkillPath(slug);
341
- const legacyPath = legacySkillPath(slug);
342
- const existing = read(workspacePath, bundleSkillPath) ?? read(workspacePath, legacyPath);
343
- const status = options.status ?? existing?.fields.status ?? "draft";
344
- if (existing && options.expectedUpdatedAt) {
345
- const currentUpdatedAt = String(existing.fields.updated ?? "");
346
- if (currentUpdatedAt !== options.expectedUpdatedAt) {
347
- throw new Error(`Concurrent skill update detected for ${existing.path}. Expected updated="${options.expectedUpdatedAt}" but found "${currentUpdatedAt}".`);
348
- }
349
- }
350
- if (!existing) {
351
- ensureSkillBundleScaffold(workspacePath, slug);
352
- const created = create(workspacePath, "skill", {
353
- title,
354
- owner: options.owner ?? actor,
355
- version: options.version ?? "0.1.0",
356
- status,
357
- distribution: options.distribution ?? "tailscale-shared-vault",
358
- tailscale_path: options.tailscalePath,
359
- reviewers: options.reviewers ?? [],
360
- depends_on: options.dependsOn ?? [],
361
- tags: options.tags ?? []
362
- }, body, actor, {
363
- pathOverride: bundleSkillPath
364
- });
365
- writeSkillManifest(workspacePath, slug, created, actor);
366
- return created;
367
- }
368
- const updated = update(workspacePath, existing.path, {
369
- title,
370
- owner: options.owner ?? existing.fields.owner ?? actor,
371
- version: options.version ?? existing.fields.version ?? "0.1.0",
372
- status,
373
- distribution: options.distribution ?? existing.fields.distribution ?? "tailscale-shared-vault",
374
- tailscale_path: options.tailscalePath ?? existing.fields.tailscale_path,
375
- reviewers: options.reviewers ?? existing.fields.reviewers ?? [],
376
- depends_on: options.dependsOn ?? existing.fields.depends_on ?? [],
377
- tags: options.tags ?? existing.fields.tags ?? []
378
- }, body, actor);
379
- writeSkillManifest(workspacePath, slug, updated, actor);
380
- return updated;
381
- }
382
- function loadSkill(workspacePath, skillRef) {
383
- const normalizedCandidates = normalizeSkillRefCandidates(skillRef);
384
- const skill = normalizedCandidates.map((candidate) => read(workspacePath, candidate)).find((entry) => entry !== null);
385
- if (!skill) throw new Error(`Skill not found: ${skillRef}`);
386
- if (skill.type !== "skill") throw new Error(`Target is not a skill primitive: ${skillRef}`);
387
- return skill;
388
- }
389
- function listSkills(workspacePath, options = {}) {
390
- let skills = list(workspacePath, "skill");
391
- if (options.status) {
392
- skills = skills.filter((skill) => skill.fields.status === options.status);
393
- }
394
- if (options.updatedSince) {
395
- const threshold = Date.parse(options.updatedSince);
396
- if (Number.isFinite(threshold)) {
397
- skills = skills.filter((skill) => {
398
- const updatedAt = Date.parse(String(skill.fields.updated ?? ""));
399
- return Number.isFinite(updatedAt) && updatedAt >= threshold;
400
- });
401
- }
402
- }
403
- return skills;
404
- }
405
- function proposeSkill(workspacePath, skillRef, actor, options = {}) {
406
- const skill = loadSkill(workspacePath, skillRef);
407
- const slug = skillSlug(String(skill.fields.title ?? skillRef));
408
- let proposalThread = options.proposalThread;
409
- if (!proposalThread && options.createThreadIfMissing !== false) {
410
- const createdThread = createThread(
411
- workspacePath,
412
- `Review skill: ${String(skill.fields.title)}`,
413
- `Review and approve skill ${skill.path} for activation.`,
414
- actor,
415
- {
416
- priority: "medium",
417
- space: options.space,
418
- context_refs: [skill.path]
419
- }
420
- );
421
- proposalThread = createdThread.path;
422
- }
423
- const updated = update(workspacePath, skill.path, {
424
- status: "proposed",
425
- proposal_thread: proposalThread ?? skill.fields.proposal_thread,
426
- proposed_at: (/* @__PURE__ */ new Date()).toISOString(),
427
- reviewers: options.reviewers ?? skill.fields.reviewers ?? []
428
- }, void 0, actor);
429
- writeSkillManifest(workspacePath, slug, updated, actor);
430
- return updated;
431
- }
432
- function skillHistory(workspacePath, skillRef, options = {}) {
433
- const skill = loadSkill(workspacePath, skillRef);
434
- const entries = historyOf(workspacePath, skill.path);
435
- if (options.limit && options.limit > 0) {
436
- return entries.slice(-options.limit);
437
- }
438
- return entries;
439
- }
440
- function skillDiff(workspacePath, skillRef) {
441
- const skill = loadSkill(workspacePath, skillRef);
442
- const entries = historyOf(workspacePath, skill.path).filter((entry) => entry.op === "create" || entry.op === "update");
443
- const latest = entries.length > 0 ? entries[entries.length - 1] : null;
444
- const previous = entries.length > 1 ? entries[entries.length - 2] : null;
445
- const changedFields = Array.isArray(latest?.data?.changed) ? latest.data.changed.map((value) => String(value)) : latest?.op === "create" ? Object.keys(skill.fields) : [];
446
- return {
447
- path: skill.path,
448
- latestEntryTs: latest?.ts ?? null,
449
- previousEntryTs: previous?.ts ?? null,
450
- changedFields
451
- };
452
- }
453
- function promoteSkill(workspacePath, skillRef, actor, options = {}) {
454
- const skill = loadSkill(workspacePath, skillRef);
455
- const slug = skillSlug(String(skill.fields.title ?? skillRef));
456
- const currentVersion = String(skill.fields.version ?? "0.1.0");
457
- const nextVersion = options.version ?? bumpPatchVersion(currentVersion);
458
- const updated = update(workspacePath, skill.path, {
459
- status: "active",
460
- version: nextVersion,
461
- promoted_at: (/* @__PURE__ */ new Date()).toISOString()
462
- }, void 0, actor);
463
- writeSkillManifest(workspacePath, slug, updated, actor);
464
- return updated;
465
- }
466
- function skillSlug(title) {
467
- return title.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "").slice(0, 80);
468
- }
469
- function normalizeSkillRefCandidates(skillRef) {
470
- const raw = skillRef.trim();
471
- if (!raw) return [];
472
- if (raw.includes("/")) {
473
- const normalized = raw.endsWith(".md") ? raw : `${raw}.md`;
474
- if (normalized.endsWith("/SKILL.md")) return [normalized];
475
- if (normalized.endsWith("/SKILL")) return [`${normalized}.md`];
476
- if (normalized.endsWith(".md")) {
477
- const noExt = normalized.slice(0, -3);
478
- return [normalized, `${noExt}/SKILL.md`];
479
- }
480
- return [normalized, `${normalized}/SKILL.md`];
481
- }
482
- const slug = skillSlug(raw);
483
- return [folderSkillPath(slug), legacySkillPath(slug)];
484
- }
485
- function bumpPatchVersion(version) {
486
- const match = version.match(/^(\d+)\.(\d+)\.(\d+)$/);
487
- if (!match) return "0.1.0";
488
- const major = Number.parseInt(match[1], 10);
489
- const minor = Number.parseInt(match[2], 10);
490
- const patch = Number.parseInt(match[3], 10) + 1;
491
- return `${major}.${minor}.${patch}`;
492
- }
493
- function folderSkillPath(slug) {
494
- return `skills/${slug}/SKILL.md`;
495
- }
496
- function legacySkillPath(slug) {
497
- return `skills/${slug}.md`;
498
- }
499
- function ensureSkillBundleScaffold(workspacePath, slug) {
500
- const skillRoot = path4.join(workspacePath, "skills", slug);
501
- fs4.mkdirSync(skillRoot, { recursive: true });
502
- for (const subdir of ["scripts", "examples", "tests", "assets"]) {
503
- fs4.mkdirSync(path4.join(skillRoot, subdir), { recursive: true });
504
- }
505
- }
506
- function writeSkillManifest(workspacePath, slug, skill, actor) {
507
- const manifestPath = path4.join(workspacePath, "skills", slug, "skill-manifest.json");
508
- const dir = path4.dirname(manifestPath);
509
- fs4.mkdirSync(dir, { recursive: true });
510
- const manifest = {
511
- version: 1,
512
- slug,
513
- title: String(skill.fields.title ?? slug),
514
- primitivePath: skill.path,
515
- owner: String(skill.fields.owner ?? actor),
516
- skillVersion: String(skill.fields.version ?? "0.1.0"),
517
- status: String(skill.fields.status ?? "draft"),
518
- dependsOn: Array.isArray(skill.fields.depends_on) ? skill.fields.depends_on.map((value) => String(value)) : [],
519
- components: {
520
- skillDoc: "SKILL.md",
521
- scriptsDir: "scripts/",
522
- examplesDir: "examples/",
523
- testsDir: "tests/",
524
- assetsDir: "assets/"
525
- },
526
- updatedAt: (/* @__PURE__ */ new Date()).toISOString()
527
- };
528
- fs4.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
529
- }
530
-
531
- // src/board.ts
532
- var board_exports = {};
533
- __export(board_exports, {
534
- generateKanbanBoard: () => generateKanbanBoard,
535
- syncKanbanBoard: () => syncKanbanBoard
536
- });
537
- import fs5 from "fs";
538
- import path5 from "path";
539
- function generateKanbanBoard(workspacePath, options = {}) {
540
- const threads = list(workspacePath, "thread");
541
- const grouped = groupThreads(threads);
542
- const includeCancelled = options.includeCancelled === true;
543
- const lanes = [
544
- { title: "Backlog", items: grouped.open, checkChar: " " },
545
- { title: "In Progress", items: grouped.active, checkChar: " " },
546
- { title: "Blocked", items: grouped.blocked, checkChar: " " },
547
- { title: "Done", items: grouped.done, checkChar: "x" }
548
- ];
549
- if (includeCancelled) {
550
- lanes.push({ title: "Cancelled", items: grouped.cancelled, checkChar: "x" });
551
- }
552
- const content = renderKanbanMarkdown(lanes);
553
- const relOutputPath = options.outputPath ?? "ops/Workgraph Board.md";
554
- const absOutputPath = resolvePathWithinWorkspace2(workspacePath, relOutputPath);
555
- const parentDir = path5.dirname(absOutputPath);
556
- if (!fs5.existsSync(parentDir)) fs5.mkdirSync(parentDir, { recursive: true });
557
- fs5.writeFileSync(absOutputPath, content, "utf-8");
558
- return {
559
- outputPath: path5.relative(workspacePath, absOutputPath).replace(/\\/g, "/"),
560
- generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
561
- counts: {
562
- backlog: grouped.open.length,
563
- inProgress: grouped.active.length,
564
- blocked: grouped.blocked.length,
565
- done: grouped.done.length,
566
- cancelled: grouped.cancelled.length
567
- },
568
- content
569
- };
570
- }
571
- function syncKanbanBoard(workspacePath, options = {}) {
572
- return generateKanbanBoard(workspacePath, options);
573
- }
574
- function groupThreads(threads) {
575
- const groups = {
576
- open: [],
577
- active: [],
578
- blocked: [],
579
- done: [],
580
- cancelled: []
581
- };
582
- for (const thread of threads) {
583
- const status = String(thread.fields.status ?? "open");
584
- switch (status) {
585
- case "active":
586
- groups.active.push(thread);
587
- break;
588
- case "blocked":
589
- groups.blocked.push(thread);
590
- break;
591
- case "done":
592
- groups.done.push(thread);
593
- break;
594
- case "cancelled":
595
- groups.cancelled.push(thread);
596
- break;
597
- case "open":
598
- default:
599
- groups.open.push(thread);
600
- break;
601
- }
602
- }
603
- const byPriority = (a, b) => {
604
- const rank = (value) => {
605
- switch (String(value ?? "medium")) {
606
- case "urgent":
607
- return 0;
608
- case "high":
609
- return 1;
610
- case "medium":
611
- return 2;
612
- case "low":
613
- return 3;
614
- default:
615
- return 4;
616
- }
617
- };
618
- return rank(a.fields.priority) - rank(b.fields.priority) || String(a.fields.title).localeCompare(String(b.fields.title));
619
- };
620
- groups.open.sort(byPriority);
621
- groups.active.sort(byPriority);
622
- groups.blocked.sort(byPriority);
623
- groups.done.sort(byPriority);
624
- groups.cancelled.sort(byPriority);
625
- return groups;
626
- }
627
- function renderKanbanMarkdown(lanes) {
628
- const settings = {
629
- "kanban-plugin": "board"
630
- };
631
- const lines = [
632
- "---",
633
- "kanban-plugin: board",
634
- "---",
635
- ""
636
- ];
637
- for (const lane of lanes) {
638
- lines.push(`## ${lane.title}`);
639
- lines.push("");
640
- for (const thread of lane.items) {
641
- const title = String(thread.fields.title ?? thread.path);
642
- const priority = String(thread.fields.priority ?? "medium");
643
- lines.push(`- [${lane.checkChar}] [[${thread.path}|${title}]] (#${priority})`);
644
- }
645
- lines.push("");
646
- lines.push("");
647
- lines.push("");
648
- }
649
- lines.push("%% kanban:settings");
650
- lines.push("```");
651
- lines.push(JSON.stringify(settings));
652
- lines.push("```");
653
- lines.push("%%");
654
- lines.push("");
655
- return lines.join("\n");
656
- }
657
- function resolvePathWithinWorkspace2(workspacePath, outputPath) {
658
- const base = path5.resolve(workspacePath);
659
- const resolved = path5.resolve(base, outputPath);
660
- if (!resolved.startsWith(base + path5.sep) && resolved !== base) {
661
- throw new Error(`Invalid board output path: ${outputPath}`);
662
- }
663
- return resolved;
664
- }
665
-
666
- // src/onboard.ts
667
- var onboard_exports = {};
668
- __export(onboard_exports, {
669
- onboardWorkspace: () => onboardWorkspace,
670
- updateOnboardingStatus: () => updateOnboardingStatus
671
- });
672
- function onboardWorkspace(workspacePath, options) {
673
- const spaces = options.spaces && options.spaces.length > 0 ? options.spaces : ["platform", "product", "operations"];
674
- const spacesCreated = [];
675
- for (const space of spaces) {
676
- const title = titleCase2(space);
677
- const created = create(
678
- workspacePath,
679
- "space",
680
- {
681
- title,
682
- description: `${title} workspace lane`,
683
- members: [options.actor],
684
- tags: ["onboarded"]
685
- },
686
- `# ${title}
687
-
688
- Auto-created during onboarding.
689
- `,
690
- options.actor
691
- );
692
- spacesCreated.push(created.path);
693
- }
694
- const threadsCreated = [];
695
- if (options.createDemoThreads !== false) {
696
- const templates = [
697
- { title: "Review workspace policy gates", goal: "Validate sensitive transitions are governed.", space: spacesCreated[0] },
698
- { title: "Configure board sync cadence", goal: "Set board update expectations for all agents.", space: spacesCreated[1] ?? spacesCreated[0] },
699
- { title: "Establish daily checkpoint routine", goal: "Agents leave actionable hand-off notes.", space: spacesCreated[2] ?? spacesCreated[0] }
700
- ];
701
- for (const template of templates) {
702
- const created = create(
703
- workspacePath,
704
- "thread",
705
- {
706
- title: template.title,
707
- goal: template.goal,
708
- status: "open",
709
- priority: "medium",
710
- space: template.space,
711
- context_refs: [template.space],
712
- tags: ["onboarding"]
713
- },
714
- `## Goal
715
-
716
- ${template.goal}
717
- `,
718
- options.actor
719
- );
720
- threadsCreated.push(created.path);
721
- }
722
- }
723
- const boardResult = generateKanbanBoard(workspacePath, { outputPath: "ops/Onboarding Board.md" });
724
- const commandCenterResult = generateCommandCenter(workspacePath, {
725
- outputPath: "ops/Onboarding Command Center.md",
726
- actor: options.actor
727
- });
728
- const checkpointResult = checkpoint(
729
- workspacePath,
730
- options.actor,
731
- "Onboarding completed and workspace views initialized.",
732
- {
733
- next: ["Claim your next ready thread via `workgraph thread next --claim`"],
734
- blocked: [],
735
- tags: ["onboarding"]
736
- }
737
- );
738
- const onboarding = create(
739
- workspacePath,
740
- "onboarding",
741
- {
742
- title: `Onboarding for ${options.actor}`,
743
- actor: options.actor,
744
- status: "active",
745
- spaces: spacesCreated,
746
- thread_refs: threadsCreated,
747
- board: boardResult.outputPath,
748
- command_center: commandCenterResult.outputPath,
749
- tags: ["onboarding"]
750
- },
751
- [
752
- "# Onboarding",
753
- "",
754
- `Actor: ${options.actor}`,
755
- "",
756
- "## Spaces",
757
- "",
758
- ...spacesCreated.map((space) => `- [[${space}]]`),
759
- "",
760
- "## Starter Threads",
761
- "",
762
- ...threadsCreated.map((threadRef) => `- [[${threadRef}]]`),
763
- "",
764
- `Board: [[${boardResult.outputPath}]]`,
765
- `Command Center: [[${commandCenterResult.outputPath}]]`,
766
- ""
767
- ].join("\n"),
768
- options.actor
769
- );
770
- return {
771
- actor: options.actor,
772
- spacesCreated,
773
- threadsCreated,
774
- boardPath: boardResult.outputPath,
775
- commandCenterPath: commandCenterResult.outputPath,
776
- checkpointPath: checkpointResult.path,
777
- onboardingPath: onboarding.path
778
- };
779
- }
780
- function updateOnboardingStatus(workspacePath, onboardingPath, status, actor) {
781
- const onboarding = read(workspacePath, onboardingPath);
782
- if (!onboarding) throw new Error(`Onboarding primitive not found: ${onboardingPath}`);
783
- if (onboarding.type !== "onboarding") {
784
- throw new Error(`Target is not an onboarding primitive: ${onboardingPath}`);
785
- }
786
- const current = String(onboarding.fields.status ?? "active");
787
- const allowed = ONBOARDING_STATUS_TRANSITIONS[current] ?? [];
788
- if (!allowed.includes(status)) {
789
- throw new Error(`Invalid onboarding transition: ${current} -> ${status}. Allowed: ${allowed.join(", ") || "none"}`);
790
- }
791
- return update(
792
- workspacePath,
793
- onboardingPath,
794
- { status },
795
- void 0,
796
- actor
797
- );
798
- }
799
- var ONBOARDING_STATUS_TRANSITIONS = {
800
- active: ["paused", "completed"],
801
- paused: ["active", "completed"],
802
- completed: []
803
- };
804
- function titleCase2(value) {
805
- return value.split(/[-_\s]/g).filter(Boolean).map((part) => part[0].toUpperCase() + part.slice(1)).join(" ");
806
- }
807
-
808
- // src/search-qmd-adapter.ts
809
- var search_qmd_adapter_exports = {};
810
- __export(search_qmd_adapter_exports, {
811
- search: () => search
812
- });
813
- function search(workspacePath, text, options = {}) {
814
- const requestedMode = options.mode ?? "auto";
815
- const qmdEnabled = process.env.WORKGRAPH_QMD_ENDPOINT && process.env.WORKGRAPH_QMD_ENDPOINT.trim().length > 0;
816
- if (requestedMode === "qmd" && !qmdEnabled) {
817
- const results = keywordSearch(workspacePath, text, {
818
- type: options.type,
819
- limit: options.limit
820
- });
821
- return {
822
- mode: "core",
823
- query: text,
824
- results,
825
- fallbackReason: "QMD mode requested but WORKGRAPH_QMD_ENDPOINT is not configured."
826
- };
827
- }
828
- if (requestedMode === "qmd" && qmdEnabled) {
829
- const results = keywordSearch(workspacePath, text, {
830
- type: options.type,
831
- limit: options.limit
832
- });
833
- return {
834
- mode: "qmd",
835
- query: text,
836
- results,
837
- fallbackReason: "QMD endpoint configured; using core-compatible local ranking in MVP."
838
- };
839
- }
840
- if (requestedMode === "auto" && qmdEnabled) {
841
- const results = keywordSearch(workspacePath, text, {
842
- type: options.type,
843
- limit: options.limit
844
- });
845
- return {
846
- mode: "qmd",
847
- query: text,
848
- results,
849
- fallbackReason: "Auto mode selected; QMD endpoint detected; using core-compatible local ranking in MVP."
850
- };
851
- }
852
- return {
853
- mode: "core",
854
- query: text,
855
- results: keywordSearch(workspacePath, text, {
856
- type: options.type,
857
- limit: options.limit
858
- })
859
- };
860
- }
861
-
862
- // src/trigger.ts
863
- var trigger_exports = {};
864
- __export(trigger_exports, {
865
- fireTrigger: () => fireTrigger
866
- });
867
- import { createHash } from "crypto";
868
- function fireTrigger(workspacePath, triggerPath, options) {
869
- const trigger = read(workspacePath, triggerPath);
870
- if (!trigger) throw new Error(`Trigger not found: ${triggerPath}`);
871
- if (trigger.type !== "trigger") throw new Error(`Target is not a trigger primitive: ${triggerPath}`);
872
- const triggerStatus = String(trigger.fields.status ?? "draft");
873
- if (!["approved", "active"].includes(triggerStatus)) {
874
- throw new Error(`Trigger must be approved/active to fire. Current status: ${triggerStatus}`);
875
- }
876
- const objective = options.objective ?? `Trigger ${String(trigger.fields.title ?? triggerPath)} fired action ${String(trigger.fields.action ?? "run")}`;
877
- const eventSeed = options.eventKey ?? (/* @__PURE__ */ new Date()).toISOString();
878
- const idempotencyKey = buildIdempotencyKey(triggerPath, eventSeed, objective);
879
- const run = createRun(workspacePath, {
880
- actor: options.actor,
881
- objective,
882
- context: {
883
- trigger_path: triggerPath,
884
- trigger_event: String(trigger.fields.event ?? ""),
885
- ...options.context
886
- },
887
- idempotencyKey
888
- });
889
- append(workspacePath, options.actor, "create", triggerPath, "trigger", {
890
- fired: true,
891
- event_key: eventSeed,
892
- run_id: run.id,
893
- idempotency_key: idempotencyKey
894
- });
895
- return {
896
- triggerPath,
897
- run,
898
- idempotencyKey
899
- };
900
- }
901
- function buildIdempotencyKey(triggerPath, eventSeed, objective) {
902
- return createHash("sha256").update(`${triggerPath}:${eventSeed}:${objective}`).digest("hex").slice(0, 32);
903
- }
904
-
905
- // src/clawdapus.ts
906
- var clawdapus_exports = {};
907
- __export(clawdapus_exports, {
908
- CLAWDAPUS_INTEGRATION_PROVIDER: () => CLAWDAPUS_INTEGRATION_PROVIDER,
909
- DEFAULT_CLAWDAPUS_SKILL_URL: () => DEFAULT_CLAWDAPUS_SKILL_URL,
910
- fetchClawdapusSkillMarkdown: () => fetchClawdapusSkillMarkdown,
911
- installClawdapusSkill: () => installClawdapusSkill
912
- });
913
-
914
- // src/integration-core.ts
915
- async function installSkillIntegration(workspacePath, provider, options) {
916
- const actor = options.actor.trim();
917
- if (!actor) {
918
- throw new Error(`${provider.id} integration requires a non-empty actor.`);
919
- }
920
- const title = options.title?.trim() || provider.defaultTitle;
921
- const sourceUrl = options.sourceUrl?.trim() || provider.defaultSourceUrl;
922
- const existing = loadSkillIfExists(workspacePath, title);
923
- if (existing && !options.force) {
924
- throw new Error(
925
- `Skill "${title}" already exists at ${existing.path}. Use --force to refresh it from source.`
926
- );
927
- }
928
- const fetchSkillMarkdown = options.fetchSkillMarkdown ?? ((url) => fetchSkillMarkdownFromUrl(url, provider.userAgent));
929
- const markdown = await fetchSkillMarkdown(sourceUrl);
930
- if (!markdown.trim()) {
931
- throw new Error(`Downloaded ${provider.id} skill from ${sourceUrl} is empty.`);
932
- }
933
- const skill = writeSkill(workspacePath, title, markdown, actor, {
934
- owner: options.owner ?? actor,
935
- status: options.status,
936
- distribution: provider.distribution,
937
- tags: mergeTags(provider.defaultTags, options.tags)
938
- });
939
- return {
940
- provider: provider.id,
941
- skill,
942
- sourceUrl,
943
- importedAt: (/* @__PURE__ */ new Date()).toISOString(),
944
- replacedExisting: existing !== null
945
- };
946
- }
947
- async function fetchSkillMarkdownFromUrl(sourceUrl, userAgent = "@versatly/workgraph optional-integration") {
948
- let response;
949
- try {
950
- response = await fetch(sourceUrl, {
951
- headers: {
952
- "user-agent": userAgent
953
- }
954
- });
955
- } catch (error) {
956
- throw new Error(
957
- `Failed to download skill from ${sourceUrl}: ${errorMessage(error)}`
958
- );
959
- }
960
- if (!response.ok) {
961
- throw new Error(
962
- `Failed to download skill from ${sourceUrl}: HTTP ${response.status} ${response.statusText}`
963
- );
964
- }
965
- return response.text();
966
- }
967
- function loadSkillIfExists(workspacePath, skillRef) {
968
- try {
969
- return loadSkill(workspacePath, skillRef);
970
- } catch (error) {
971
- const message = errorMessage(error);
972
- if (message.startsWith("Skill not found:")) {
973
- return null;
974
- }
975
- throw error;
976
- }
977
- }
978
- function mergeTags(defaultTags, tags) {
979
- const merged = /* @__PURE__ */ new Set(["optional-integration"]);
980
- for (const tag of defaultTags) {
981
- const normalized = tag.trim();
982
- if (normalized) merged.add(normalized);
983
- }
984
- for (const tag of tags ?? []) {
985
- const normalized = tag.trim();
986
- if (normalized) merged.add(normalized);
987
- }
988
- return [...merged];
989
- }
990
- function errorMessage(error) {
991
- return error instanceof Error ? error.message : String(error);
992
- }
993
-
994
- // src/clawdapus.ts
995
- var DEFAULT_CLAWDAPUS_SKILL_URL = "https://raw.githubusercontent.com/mostlydev/clawdapus/master/skills/clawdapus/SKILL.md";
996
- var CLAWDAPUS_INTEGRATION_PROVIDER = {
997
- id: "clawdapus",
998
- defaultTitle: "clawdapus",
999
- defaultSourceUrl: DEFAULT_CLAWDAPUS_SKILL_URL,
1000
- distribution: "clawdapus-optional-integration",
1001
- defaultTags: ["clawdapus"],
1002
- userAgent: "@versatly/workgraph clawdapus-optional-integration"
1003
- };
1004
- async function installClawdapusSkill(workspacePath, options) {
1005
- return installSkillIntegration(
1006
- workspacePath,
1007
- CLAWDAPUS_INTEGRATION_PROVIDER,
1008
- options
1009
- );
1010
- }
1011
- async function fetchClawdapusSkillMarkdown(sourceUrl) {
1012
- return fetchSkillMarkdownFromUrl(sourceUrl, CLAWDAPUS_INTEGRATION_PROVIDER.userAgent);
1013
- }
1014
-
1015
- // src/integration.ts
1016
- var integration_exports = {};
1017
- __export(integration_exports, {
1018
- installIntegration: () => installIntegration,
1019
- listIntegrations: () => listIntegrations
1020
- });
1021
- var INTEGRATIONS = {
1022
- clawdapus: {
1023
- provider: CLAWDAPUS_INTEGRATION_PROVIDER,
1024
- description: "Infrastructure-layer governance skill import for AI agent containers.",
1025
- install: installClawdapusSkill
1026
- }
1027
- };
1028
- function listIntegrations() {
1029
- return Object.values(INTEGRATIONS).map((integration) => ({
1030
- id: integration.provider.id,
1031
- description: integration.description,
1032
- defaultTitle: integration.provider.defaultTitle,
1033
- defaultSourceUrl: integration.provider.defaultSourceUrl
1034
- }));
1035
- }
1036
- async function installIntegration(workspacePath, integrationId, options) {
1037
- const integration = INTEGRATIONS[integrationId.trim().toLowerCase()];
1038
- if (!integration) {
1039
- throw new Error(
1040
- `Unknown integration "${integrationId}". Supported integrations: ${supportedIntegrationList()}.`
1041
- );
1042
- }
1043
- return integration.install(workspacePath, options);
1044
- }
1045
- function supportedIntegrationList() {
1046
- return Object.keys(INTEGRATIONS).sort().join(", ");
1047
- }
1048
-
1049
- export {
1050
- bases_exports,
1051
- workspace_exports,
1052
- command_center_exports,
1053
- skill_exports,
1054
- board_exports,
1055
- onboard_exports,
1056
- search_qmd_adapter_exports,
1057
- trigger_exports,
1058
- installSkillIntegration,
1059
- fetchSkillMarkdownFromUrl,
1060
- clawdapus_exports,
1061
- integration_exports
1062
- };