@versatly/workgraph 1.0.0 → 1.2.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 +3 -0
- package/dist/{chunk-R2MLGBHB.js → chunk-G6B47IBD.js} +5 -0
- package/dist/{chunk-OJ6KOGB2.js → chunk-MCHTUXG2.js} +846 -68
- package/dist/cli.js +151 -2
- package/dist/index.d.ts +199 -1
- package/dist/index.js +6 -2
- package/dist/mcp-server.js +1 -1
- package/package.json +1 -1
|
@@ -3,12 +3,17 @@ import {
|
|
|
3
3
|
allClaims,
|
|
4
4
|
append,
|
|
5
5
|
checkpoint,
|
|
6
|
+
claim,
|
|
6
7
|
create,
|
|
7
8
|
createRun,
|
|
8
9
|
createThread,
|
|
10
|
+
done,
|
|
9
11
|
historyOf,
|
|
10
12
|
keywordSearch,
|
|
11
13
|
list,
|
|
14
|
+
listReadyThreads,
|
|
15
|
+
listReadyThreadsInSpace,
|
|
16
|
+
listRuns,
|
|
12
17
|
listTypes,
|
|
13
18
|
loadPolicyRegistry,
|
|
14
19
|
loadRegistry,
|
|
@@ -21,7 +26,7 @@ import {
|
|
|
21
26
|
saveRegistry,
|
|
22
27
|
stop,
|
|
23
28
|
update
|
|
24
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-G6B47IBD.js";
|
|
25
30
|
|
|
26
31
|
// src/workspace.ts
|
|
27
32
|
var workspace_exports = {};
|
|
@@ -298,7 +303,7 @@ function renderCommandCenter(input) {
|
|
|
298
303
|
const claimsSection = [
|
|
299
304
|
"## Active Claims",
|
|
300
305
|
"",
|
|
301
|
-
...input.claims.length > 0 ? input.claims.map((
|
|
306
|
+
...input.claims.length > 0 ? input.claims.map((claim2) => `- ${claim2.owner} -> \`${claim2.target}\``) : ["- None"],
|
|
302
307
|
""
|
|
303
308
|
];
|
|
304
309
|
const blockedSection = [
|
|
@@ -532,14 +537,480 @@ function writeSkillManifest(workspacePath, slug, skill, actor) {
|
|
|
532
537
|
fs4.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
533
538
|
}
|
|
534
539
|
|
|
540
|
+
// src/lens.ts
|
|
541
|
+
var lens_exports = {};
|
|
542
|
+
__export(lens_exports, {
|
|
543
|
+
generateContextLens: () => generateContextLens,
|
|
544
|
+
listContextLenses: () => listContextLenses,
|
|
545
|
+
materializeContextLens: () => materializeContextLens
|
|
546
|
+
});
|
|
547
|
+
import fs5 from "fs";
|
|
548
|
+
import path5 from "path";
|
|
549
|
+
var DEFAULT_LOOKBACK_HOURS = 24;
|
|
550
|
+
var DEFAULT_STALE_HOURS = 24;
|
|
551
|
+
var DEFAULT_LIMIT = 10;
|
|
552
|
+
var PRIORITY_ORDER = {
|
|
553
|
+
urgent: 0,
|
|
554
|
+
high: 1,
|
|
555
|
+
medium: 2,
|
|
556
|
+
low: 3
|
|
557
|
+
};
|
|
558
|
+
var HIGH_RISK_PRIORITIES = /* @__PURE__ */ new Set(["urgent", "high"]);
|
|
559
|
+
var HIGH_RISK_SEVERITIES = /* @__PURE__ */ new Set(["sev0", "sev1", "sev2"]);
|
|
560
|
+
var BUILT_IN_LENSES = [
|
|
561
|
+
{
|
|
562
|
+
id: "my-work",
|
|
563
|
+
description: "Actor workload, blockers, stale claims, and ready-next queue"
|
|
564
|
+
},
|
|
565
|
+
{
|
|
566
|
+
id: "team-risk",
|
|
567
|
+
description: "High-risk blockers, stale active claims, failed runs, and incidents"
|
|
568
|
+
},
|
|
569
|
+
{
|
|
570
|
+
id: "customer-health",
|
|
571
|
+
description: "Customer-tagged delivery health, blockers, and related incidents"
|
|
572
|
+
},
|
|
573
|
+
{
|
|
574
|
+
id: "exec-brief",
|
|
575
|
+
description: "Top priorities, momentum, risks, and recent decisions"
|
|
576
|
+
}
|
|
577
|
+
];
|
|
578
|
+
function listContextLenses() {
|
|
579
|
+
return BUILT_IN_LENSES.map((lens) => ({ ...lens }));
|
|
580
|
+
}
|
|
581
|
+
function generateContextLens(workspacePath, lensId, options = {}) {
|
|
582
|
+
const normalizedLensId = normalizeLensId(lensId);
|
|
583
|
+
const normalizedOptions = normalizeLensOptions(options);
|
|
584
|
+
switch (normalizedLensId) {
|
|
585
|
+
case "my-work":
|
|
586
|
+
return buildMyWorkLens(workspacePath, normalizedOptions);
|
|
587
|
+
case "team-risk":
|
|
588
|
+
return buildTeamRiskLens(workspacePath, normalizedOptions);
|
|
589
|
+
case "customer-health":
|
|
590
|
+
return buildCustomerHealthLens(workspacePath, normalizedOptions);
|
|
591
|
+
case "exec-brief":
|
|
592
|
+
return buildExecBriefLens(workspacePath, normalizedOptions);
|
|
593
|
+
default:
|
|
594
|
+
return assertNever(normalizedLensId);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
function materializeContextLens(workspacePath, lensId, options) {
|
|
598
|
+
const result = generateContextLens(workspacePath, lensId, options);
|
|
599
|
+
const absOutputPath = resolvePathWithinWorkspace2(workspacePath, options.outputPath);
|
|
600
|
+
const relOutputPath = path5.relative(workspacePath, absOutputPath).replace(/\\/g, "/");
|
|
601
|
+
const parentDir = path5.dirname(absOutputPath);
|
|
602
|
+
if (!fs5.existsSync(parentDir)) fs5.mkdirSync(parentDir, { recursive: true });
|
|
603
|
+
const existed = fs5.existsSync(absOutputPath);
|
|
604
|
+
fs5.writeFileSync(absOutputPath, result.markdown, "utf-8");
|
|
605
|
+
append(
|
|
606
|
+
workspacePath,
|
|
607
|
+
options.actor ?? result.actor ?? "system",
|
|
608
|
+
existed ? "update" : "create",
|
|
609
|
+
relOutputPath,
|
|
610
|
+
"lens",
|
|
611
|
+
{
|
|
612
|
+
lens: result.lens,
|
|
613
|
+
sections: result.sections.length
|
|
614
|
+
}
|
|
615
|
+
);
|
|
616
|
+
return {
|
|
617
|
+
...result,
|
|
618
|
+
outputPath: relOutputPath,
|
|
619
|
+
created: !existed
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
function buildMyWorkLens(workspacePath, options) {
|
|
623
|
+
const actor = options.actor;
|
|
624
|
+
const nowMs = Date.now();
|
|
625
|
+
const staleCutoffMs = nowMs - options.staleHours * 60 * 60 * 1e3;
|
|
626
|
+
const claims = [...allClaims(workspacePath).entries()];
|
|
627
|
+
const myClaimedThreads = claims.filter(([, owner]) => owner === actor).map(([target]) => read(workspacePath, target)).filter((instance) => !!instance && instance.type === "thread").sort(compareThreadsByPriorityThenUpdated);
|
|
628
|
+
const myBlockedThreads = myClaimedThreads.filter((instance) => String(instance.fields.status ?? "") === "blocked").slice(0, options.limit);
|
|
629
|
+
const staleClaims = myClaimedThreads.filter((instance) => isStale(instance, staleCutoffMs)).slice(0, options.limit);
|
|
630
|
+
const nextReady = listReadyThreads(workspacePath).filter((instance) => !instance.fields.owner).sort(compareThreadsByPriorityThenUpdated).slice(0, options.limit);
|
|
631
|
+
const sections = [
|
|
632
|
+
{
|
|
633
|
+
id: "my_claims",
|
|
634
|
+
title: `Claimed Threads (${actor})`,
|
|
635
|
+
items: myClaimedThreads.slice(0, options.limit).map((instance) => toThreadItem(instance, nowMs))
|
|
636
|
+
},
|
|
637
|
+
{
|
|
638
|
+
id: "my_blockers",
|
|
639
|
+
title: `Blocked Threads (${actor})`,
|
|
640
|
+
items: myBlockedThreads.map((instance) => toThreadItem(instance, nowMs))
|
|
641
|
+
},
|
|
642
|
+
{
|
|
643
|
+
id: "stale_claims",
|
|
644
|
+
title: `Stale Claims (${options.staleHours}h+)`,
|
|
645
|
+
items: staleClaims.map((instance) => toThreadItem(instance, nowMs))
|
|
646
|
+
},
|
|
647
|
+
{
|
|
648
|
+
id: "next_ready",
|
|
649
|
+
title: "Next Ready Threads",
|
|
650
|
+
items: nextReady.map((instance) => toThreadItem(instance, nowMs))
|
|
651
|
+
}
|
|
652
|
+
];
|
|
653
|
+
return finalizeLensResult("my-work", {
|
|
654
|
+
actor,
|
|
655
|
+
options,
|
|
656
|
+
metrics: {
|
|
657
|
+
myClaims: myClaimedThreads.length,
|
|
658
|
+
blocked: myBlockedThreads.length,
|
|
659
|
+
staleClaims: staleClaims.length,
|
|
660
|
+
nextReady: nextReady.length
|
|
661
|
+
},
|
|
662
|
+
sections
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
function buildTeamRiskLens(workspacePath, options) {
|
|
666
|
+
const nowMs = Date.now();
|
|
667
|
+
const staleCutoffMs = nowMs - options.staleHours * 60 * 60 * 1e3;
|
|
668
|
+
const lookbackCutoffMs = nowMs - options.lookbackHours * 60 * 60 * 1e3;
|
|
669
|
+
const threads = list(workspacePath, "thread");
|
|
670
|
+
const blockedHighPriority = threads.filter((instance) => String(instance.fields.status ?? "") === "blocked").filter((instance) => HIGH_RISK_PRIORITIES.has(normalizePriority(instance.fields.priority))).sort(compareThreadsByPriorityThenUpdated).slice(0, options.limit);
|
|
671
|
+
const staleActiveClaims = [...allClaims(workspacePath).entries()].map(([target, owner]) => ({ owner, instance: read(workspacePath, target) })).filter((entry) => !!entry.instance && entry.instance.type === "thread").filter((entry) => String(entry.instance.fields.status ?? "") === "active").filter((entry) => isStale(entry.instance, staleCutoffMs)).slice(0, options.limit);
|
|
672
|
+
const failedRuns = listRuns(workspacePath, { status: "failed" }).filter((run) => parseTimestamp(run.updatedAt) >= lookbackCutoffMs).slice(0, options.limit);
|
|
673
|
+
const highSeverityIncidents = list(workspacePath, "incident").filter((incident) => String(incident.fields.status ?? "") === "active").filter((incident) => HIGH_RISK_SEVERITIES.has(normalizeSeverity(incident.fields.severity))).slice(0, options.limit);
|
|
674
|
+
const sections = [
|
|
675
|
+
{
|
|
676
|
+
id: "blocked_high_priority_threads",
|
|
677
|
+
title: "High Priority Blocked Threads",
|
|
678
|
+
items: blockedHighPriority.map((instance) => toThreadItem(instance, nowMs))
|
|
679
|
+
},
|
|
680
|
+
{
|
|
681
|
+
id: "stale_active_claims",
|
|
682
|
+
title: `Stale Active Claims (${options.staleHours}h+)`,
|
|
683
|
+
items: staleActiveClaims.map((entry) => ({
|
|
684
|
+
...toThreadItem(entry.instance, nowMs),
|
|
685
|
+
owner: entry.owner
|
|
686
|
+
}))
|
|
687
|
+
},
|
|
688
|
+
{
|
|
689
|
+
id: "failed_runs",
|
|
690
|
+
title: `Failed Runs (${options.lookbackHours}h window)`,
|
|
691
|
+
items: failedRuns.map(toRunItem)
|
|
692
|
+
},
|
|
693
|
+
{
|
|
694
|
+
id: "active_high_severity_incidents",
|
|
695
|
+
title: "Active High-Severity Incidents",
|
|
696
|
+
items: highSeverityIncidents.map((incident) => toIncidentItem(incident, nowMs))
|
|
697
|
+
}
|
|
698
|
+
];
|
|
699
|
+
return finalizeLensResult("team-risk", {
|
|
700
|
+
actor: options.actor,
|
|
701
|
+
options,
|
|
702
|
+
metrics: {
|
|
703
|
+
blockedHighPriority: blockedHighPriority.length,
|
|
704
|
+
staleActiveClaims: staleActiveClaims.length,
|
|
705
|
+
failedRuns: failedRuns.length,
|
|
706
|
+
activeHighSeverityIncidents: highSeverityIncidents.length
|
|
707
|
+
},
|
|
708
|
+
sections
|
|
709
|
+
});
|
|
710
|
+
}
|
|
711
|
+
function buildCustomerHealthLens(workspacePath, options) {
|
|
712
|
+
const nowMs = Date.now();
|
|
713
|
+
const customerThreads = list(workspacePath, "thread").filter(isCustomerLinked).sort(compareThreadsByPriorityThenUpdated);
|
|
714
|
+
const activeCustomerThreads = customerThreads.filter((instance) => ["open", "active"].includes(String(instance.fields.status ?? ""))).slice(0, options.limit);
|
|
715
|
+
const blockedCustomerThreads = customerThreads.filter((instance) => String(instance.fields.status ?? "") === "blocked").slice(0, options.limit);
|
|
716
|
+
const customerIncidents = list(workspacePath, "incident").filter((incident) => String(incident.fields.status ?? "") === "active").filter(isCustomerLinked).slice(0, options.limit);
|
|
717
|
+
const clients = list(workspacePath, "client").slice(0, options.limit);
|
|
718
|
+
const sections = [
|
|
719
|
+
{
|
|
720
|
+
id: "active_customer_threads",
|
|
721
|
+
title: "Active Customer Threads",
|
|
722
|
+
items: activeCustomerThreads.map((instance) => toThreadItem(instance, nowMs))
|
|
723
|
+
},
|
|
724
|
+
{
|
|
725
|
+
id: "blocked_customer_threads",
|
|
726
|
+
title: "Blocked Customer Threads",
|
|
727
|
+
items: blockedCustomerThreads.map((instance) => toThreadItem(instance, nowMs))
|
|
728
|
+
},
|
|
729
|
+
{
|
|
730
|
+
id: "customer_incidents",
|
|
731
|
+
title: "Customer Incidents",
|
|
732
|
+
items: customerIncidents.map((incident) => toIncidentItem(incident, nowMs))
|
|
733
|
+
},
|
|
734
|
+
{
|
|
735
|
+
id: "client_records",
|
|
736
|
+
title: "Client Records",
|
|
737
|
+
items: clients.map((instance) => ({
|
|
738
|
+
title: String(instance.fields.title ?? instance.path),
|
|
739
|
+
path: instance.path,
|
|
740
|
+
status: stringOrUndefined(instance.fields.status),
|
|
741
|
+
detail: stringOrUndefined(instance.fields.health ?? instance.fields.risk),
|
|
742
|
+
ageHours: ageHours(instance, nowMs)
|
|
743
|
+
}))
|
|
744
|
+
}
|
|
745
|
+
];
|
|
746
|
+
return finalizeLensResult("customer-health", {
|
|
747
|
+
actor: options.actor,
|
|
748
|
+
options,
|
|
749
|
+
metrics: {
|
|
750
|
+
activeCustomerThreads: activeCustomerThreads.length,
|
|
751
|
+
blockedCustomerThreads: blockedCustomerThreads.length,
|
|
752
|
+
customerIncidents: customerIncidents.length,
|
|
753
|
+
clients: clients.length
|
|
754
|
+
},
|
|
755
|
+
sections
|
|
756
|
+
});
|
|
757
|
+
}
|
|
758
|
+
function buildExecBriefLens(workspacePath, options) {
|
|
759
|
+
const nowMs = Date.now();
|
|
760
|
+
const lookbackCutoffMs = nowMs - options.lookbackHours * 60 * 60 * 1e3;
|
|
761
|
+
const threads = list(workspacePath, "thread");
|
|
762
|
+
const topPriorities = threads.filter((instance) => ["open", "active"].includes(String(instance.fields.status ?? ""))).sort(compareThreadsByPriorityThenUpdated).slice(0, options.limit);
|
|
763
|
+
const momentum = threads.filter((instance) => String(instance.fields.status ?? "") === "done").filter((instance) => parseTimestamp(instance.fields.updated) >= lookbackCutoffMs).sort(compareThreadsByPriorityThenUpdated).slice(0, options.limit);
|
|
764
|
+
const blockedHighPriority = threads.filter((instance) => String(instance.fields.status ?? "") === "blocked").filter((instance) => HIGH_RISK_PRIORITIES.has(normalizePriority(instance.fields.priority))).sort(compareThreadsByPriorityThenUpdated).slice(0, options.limit);
|
|
765
|
+
const failedRuns = listRuns(workspacePath, { status: "failed" }).filter((run) => parseTimestamp(run.updatedAt) >= lookbackCutoffMs).slice(0, options.limit);
|
|
766
|
+
const decisions = list(workspacePath, "decision").filter((instance) => ["proposed", "approved", "active"].includes(String(instance.fields.status ?? ""))).filter((instance) => parseTimestamp(instance.fields.updated ?? instance.fields.date) >= lookbackCutoffMs).slice(0, options.limit);
|
|
767
|
+
const sections = [
|
|
768
|
+
{
|
|
769
|
+
id: "top_priorities",
|
|
770
|
+
title: "Top Priorities",
|
|
771
|
+
items: topPriorities.map((instance) => toThreadItem(instance, nowMs))
|
|
772
|
+
},
|
|
773
|
+
{
|
|
774
|
+
id: "momentum",
|
|
775
|
+
title: `Momentum (${options.lookbackHours}h completed)`,
|
|
776
|
+
items: momentum.map((instance) => toThreadItem(instance, nowMs))
|
|
777
|
+
},
|
|
778
|
+
{
|
|
779
|
+
id: "key_risks",
|
|
780
|
+
title: "Key Risks",
|
|
781
|
+
items: [
|
|
782
|
+
...blockedHighPriority.map((instance) => toThreadItem(instance, nowMs)),
|
|
783
|
+
...failedRuns.map(toRunItem)
|
|
784
|
+
].slice(0, options.limit)
|
|
785
|
+
},
|
|
786
|
+
{
|
|
787
|
+
id: "recent_decisions",
|
|
788
|
+
title: `Decisions (${options.lookbackHours}h window)`,
|
|
789
|
+
items: decisions.map((instance) => ({
|
|
790
|
+
title: String(instance.fields.title ?? instance.path),
|
|
791
|
+
path: instance.path,
|
|
792
|
+
status: stringOrUndefined(instance.fields.status),
|
|
793
|
+
detail: stringOrUndefined(instance.fields.date),
|
|
794
|
+
ageHours: ageHours(instance, nowMs)
|
|
795
|
+
}))
|
|
796
|
+
}
|
|
797
|
+
];
|
|
798
|
+
return finalizeLensResult("exec-brief", {
|
|
799
|
+
actor: options.actor,
|
|
800
|
+
options,
|
|
801
|
+
metrics: {
|
|
802
|
+
topPriorities: topPriorities.length,
|
|
803
|
+
momentumDone: momentum.length,
|
|
804
|
+
risks: blockedHighPriority.length + failedRuns.length,
|
|
805
|
+
decisions: decisions.length
|
|
806
|
+
},
|
|
807
|
+
sections
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
function finalizeLensResult(lens, input) {
|
|
811
|
+
const base = {
|
|
812
|
+
lens,
|
|
813
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
814
|
+
actor: input.actor,
|
|
815
|
+
options: {
|
|
816
|
+
lookbackHours: input.options.lookbackHours,
|
|
817
|
+
staleHours: input.options.staleHours,
|
|
818
|
+
limit: input.options.limit
|
|
819
|
+
},
|
|
820
|
+
metrics: input.metrics,
|
|
821
|
+
sections: input.sections
|
|
822
|
+
};
|
|
823
|
+
return {
|
|
824
|
+
...base,
|
|
825
|
+
markdown: renderLensMarkdown(base)
|
|
826
|
+
};
|
|
827
|
+
}
|
|
828
|
+
function toThreadItem(instance, nowMs) {
|
|
829
|
+
return {
|
|
830
|
+
title: String(instance.fields.title ?? instance.path),
|
|
831
|
+
path: instance.path,
|
|
832
|
+
status: stringOrUndefined(instance.fields.status),
|
|
833
|
+
priority: stringOrUndefined(instance.fields.priority),
|
|
834
|
+
owner: stringOrUndefined(instance.fields.owner),
|
|
835
|
+
detail: renderThreadDependencies(instance),
|
|
836
|
+
ageHours: ageHours(instance, nowMs)
|
|
837
|
+
};
|
|
838
|
+
}
|
|
839
|
+
function toIncidentItem(instance, nowMs) {
|
|
840
|
+
return {
|
|
841
|
+
title: String(instance.fields.title ?? instance.path),
|
|
842
|
+
path: instance.path,
|
|
843
|
+
status: stringOrUndefined(instance.fields.status),
|
|
844
|
+
priority: stringOrUndefined(instance.fields.severity),
|
|
845
|
+
owner: stringOrUndefined(instance.fields.owner),
|
|
846
|
+
ageHours: ageHours(instance, nowMs)
|
|
847
|
+
};
|
|
848
|
+
}
|
|
849
|
+
function toRunItem(run) {
|
|
850
|
+
return {
|
|
851
|
+
title: run.objective,
|
|
852
|
+
path: `runs/${run.id}.md`,
|
|
853
|
+
status: run.status,
|
|
854
|
+
owner: run.actor,
|
|
855
|
+
detail: run.error ?? run.output,
|
|
856
|
+
ageHours: ageHoursFromIso(run.updatedAt)
|
|
857
|
+
};
|
|
858
|
+
}
|
|
859
|
+
function renderLensMarkdown(input) {
|
|
860
|
+
const lines = [
|
|
861
|
+
`# Workgraph Context Lens: ${input.lens}`,
|
|
862
|
+
"",
|
|
863
|
+
`Generated: ${input.generatedAt}`,
|
|
864
|
+
...input.actor ? [`Actor: ${input.actor}`] : [],
|
|
865
|
+
`Lookback: ${input.options.lookbackHours}h`,
|
|
866
|
+
`Stale threshold: ${input.options.staleHours}h`,
|
|
867
|
+
`Section limit: ${input.options.limit}`,
|
|
868
|
+
"",
|
|
869
|
+
"## Metrics",
|
|
870
|
+
"",
|
|
871
|
+
...Object.entries(input.metrics).map(([metric, value]) => `- ${metric}: ${value}`),
|
|
872
|
+
""
|
|
873
|
+
];
|
|
874
|
+
for (const section of input.sections) {
|
|
875
|
+
lines.push(`## ${section.title}`);
|
|
876
|
+
lines.push("");
|
|
877
|
+
if (section.items.length === 0) {
|
|
878
|
+
lines.push("- None");
|
|
879
|
+
lines.push("");
|
|
880
|
+
continue;
|
|
881
|
+
}
|
|
882
|
+
for (const item of section.items) {
|
|
883
|
+
lines.push(`- ${renderLensItem(item)}`);
|
|
884
|
+
}
|
|
885
|
+
lines.push("");
|
|
886
|
+
}
|
|
887
|
+
return lines.join("\n");
|
|
888
|
+
}
|
|
889
|
+
function renderLensItem(item) {
|
|
890
|
+
const components = [item.title];
|
|
891
|
+
if (item.path) components.push(`(\`${item.path}\`)`);
|
|
892
|
+
const metadata = [];
|
|
893
|
+
if (item.status) metadata.push(`status=${item.status}`);
|
|
894
|
+
if (item.priority) metadata.push(`priority=${item.priority}`);
|
|
895
|
+
if (item.owner) metadata.push(`owner=${item.owner}`);
|
|
896
|
+
if (typeof item.ageHours === "number") metadata.push(`age=${item.ageHours.toFixed(1)}h`);
|
|
897
|
+
if (metadata.length > 0) components.push(`[${metadata.join(", ")}]`);
|
|
898
|
+
if (item.detail) components.push(`- ${item.detail}`);
|
|
899
|
+
return components.join(" ");
|
|
900
|
+
}
|
|
901
|
+
function resolvePathWithinWorkspace2(workspacePath, outputPath) {
|
|
902
|
+
const base = path5.resolve(workspacePath);
|
|
903
|
+
const resolved = path5.resolve(base, outputPath);
|
|
904
|
+
if (!resolved.startsWith(base + path5.sep) && resolved !== base) {
|
|
905
|
+
throw new Error(`Invalid lens output path: ${outputPath}`);
|
|
906
|
+
}
|
|
907
|
+
return resolved;
|
|
908
|
+
}
|
|
909
|
+
function normalizeLensId(value) {
|
|
910
|
+
const normalized = String(value).trim().toLowerCase().replace(/^lens:\/\//, "");
|
|
911
|
+
if (normalized === "my-work") return "my-work";
|
|
912
|
+
if (normalized === "team-risk") return "team-risk";
|
|
913
|
+
if (normalized === "customer-health") return "customer-health";
|
|
914
|
+
if (normalized === "exec-brief") return "exec-brief";
|
|
915
|
+
const valid = BUILT_IN_LENSES.map((item) => item.id).join(", ");
|
|
916
|
+
throw new Error(`Unknown context lens "${value}". Valid lenses: ${valid}`);
|
|
917
|
+
}
|
|
918
|
+
function normalizeLensOptions(options) {
|
|
919
|
+
return {
|
|
920
|
+
actor: String(options.actor ?? "anonymous").trim() || "anonymous",
|
|
921
|
+
lookbackHours: parsePositiveNumber(options.lookbackHours, "lookbackHours", DEFAULT_LOOKBACK_HOURS),
|
|
922
|
+
staleHours: parsePositiveNumber(options.staleHours, "staleHours", DEFAULT_STALE_HOURS),
|
|
923
|
+
limit: parsePositiveInteger(options.limit, "limit", DEFAULT_LIMIT)
|
|
924
|
+
};
|
|
925
|
+
}
|
|
926
|
+
function parsePositiveNumber(value, fieldName, defaultValue) {
|
|
927
|
+
if (value === void 0 || value === null) return defaultValue;
|
|
928
|
+
const parsed = Number(value);
|
|
929
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
930
|
+
throw new Error(`Invalid ${fieldName}: expected a positive number.`);
|
|
931
|
+
}
|
|
932
|
+
return parsed;
|
|
933
|
+
}
|
|
934
|
+
function parsePositiveInteger(value, fieldName, defaultValue) {
|
|
935
|
+
if (value === void 0 || value === null) return defaultValue;
|
|
936
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
937
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
938
|
+
throw new Error(`Invalid ${fieldName}: expected a positive integer.`);
|
|
939
|
+
}
|
|
940
|
+
return parsed;
|
|
941
|
+
}
|
|
942
|
+
function compareThreadsByPriorityThenUpdated(a, b) {
|
|
943
|
+
const priorityDelta = rankPriority(a) - rankPriority(b);
|
|
944
|
+
if (priorityDelta !== 0) return priorityDelta;
|
|
945
|
+
return parseTimestamp(b.fields.updated) - parseTimestamp(a.fields.updated);
|
|
946
|
+
}
|
|
947
|
+
function rankPriority(instance) {
|
|
948
|
+
const priority = normalizePriority(instance.fields.priority);
|
|
949
|
+
return PRIORITY_ORDER[priority] ?? PRIORITY_ORDER.medium;
|
|
950
|
+
}
|
|
951
|
+
function normalizePriority(value) {
|
|
952
|
+
return String(value ?? "medium").trim().toLowerCase();
|
|
953
|
+
}
|
|
954
|
+
function normalizeSeverity(value) {
|
|
955
|
+
return String(value ?? "sev4").trim().toLowerCase();
|
|
956
|
+
}
|
|
957
|
+
function isStale(instance, staleCutoffMs) {
|
|
958
|
+
const updatedAt = parseTimestamp(instance.fields.updated ?? instance.fields.created);
|
|
959
|
+
if (!Number.isFinite(updatedAt)) return false;
|
|
960
|
+
return updatedAt <= staleCutoffMs;
|
|
961
|
+
}
|
|
962
|
+
function parseTimestamp(value) {
|
|
963
|
+
const parsed = Date.parse(String(value ?? ""));
|
|
964
|
+
return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;
|
|
965
|
+
}
|
|
966
|
+
function ageHours(instance, nowMs) {
|
|
967
|
+
const updatedAt = parseTimestamp(instance.fields.updated ?? instance.fields.created);
|
|
968
|
+
if (!Number.isFinite(updatedAt)) return void 0;
|
|
969
|
+
return Math.max(0, (nowMs - updatedAt) / (60 * 60 * 1e3));
|
|
970
|
+
}
|
|
971
|
+
function ageHoursFromIso(value) {
|
|
972
|
+
const nowMs = Date.now();
|
|
973
|
+
const ts = parseTimestamp(value);
|
|
974
|
+
if (!Number.isFinite(ts)) return void 0;
|
|
975
|
+
return Math.max(0, (nowMs - ts) / (60 * 60 * 1e3));
|
|
976
|
+
}
|
|
977
|
+
function renderThreadDependencies(instance) {
|
|
978
|
+
const deps = instance.fields.deps;
|
|
979
|
+
if (!Array.isArray(deps) || deps.length === 0) return void 0;
|
|
980
|
+
const visible = deps.slice(0, 3).map((value) => String(value));
|
|
981
|
+
const suffix = deps.length > visible.length ? ` +${deps.length - visible.length} more` : "";
|
|
982
|
+
return `deps: ${visible.join(", ")}${suffix}`;
|
|
983
|
+
}
|
|
984
|
+
function isCustomerLinked(instance) {
|
|
985
|
+
const tags = normalizeTags(instance.fields.tags);
|
|
986
|
+
if (tags.includes("customer") || tags.includes("client")) return true;
|
|
987
|
+
const candidateFields = ["client", "client_ref", "customer", "customer_ref", "account", "account_ref"];
|
|
988
|
+
return candidateFields.some((key) => {
|
|
989
|
+
const value = instance.fields[key];
|
|
990
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
991
|
+
});
|
|
992
|
+
}
|
|
993
|
+
function normalizeTags(value) {
|
|
994
|
+
if (!Array.isArray(value)) return [];
|
|
995
|
+
return value.map((item) => String(item).trim().toLowerCase()).filter(Boolean);
|
|
996
|
+
}
|
|
997
|
+
function stringOrUndefined(value) {
|
|
998
|
+
if (value === void 0 || value === null) return void 0;
|
|
999
|
+
const normalized = String(value).trim();
|
|
1000
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
1001
|
+
}
|
|
1002
|
+
function assertNever(value) {
|
|
1003
|
+
throw new Error(`Unhandled lens variant: ${String(value)}`);
|
|
1004
|
+
}
|
|
1005
|
+
|
|
535
1006
|
// src/board.ts
|
|
536
1007
|
var board_exports = {};
|
|
537
1008
|
__export(board_exports, {
|
|
538
1009
|
generateKanbanBoard: () => generateKanbanBoard,
|
|
539
1010
|
syncKanbanBoard: () => syncKanbanBoard
|
|
540
1011
|
});
|
|
541
|
-
import
|
|
542
|
-
import
|
|
1012
|
+
import fs6 from "fs";
|
|
1013
|
+
import path6 from "path";
|
|
543
1014
|
function generateKanbanBoard(workspacePath, options = {}) {
|
|
544
1015
|
const threads = list(workspacePath, "thread");
|
|
545
1016
|
const grouped = groupThreads(threads);
|
|
@@ -555,12 +1026,12 @@ function generateKanbanBoard(workspacePath, options = {}) {
|
|
|
555
1026
|
}
|
|
556
1027
|
const content = renderKanbanMarkdown(lanes);
|
|
557
1028
|
const relOutputPath = options.outputPath ?? "ops/Workgraph Board.md";
|
|
558
|
-
const absOutputPath =
|
|
559
|
-
const parentDir =
|
|
560
|
-
if (!
|
|
561
|
-
|
|
1029
|
+
const absOutputPath = resolvePathWithinWorkspace3(workspacePath, relOutputPath);
|
|
1030
|
+
const parentDir = path6.dirname(absOutputPath);
|
|
1031
|
+
if (!fs6.existsSync(parentDir)) fs6.mkdirSync(parentDir, { recursive: true });
|
|
1032
|
+
fs6.writeFileSync(absOutputPath, content, "utf-8");
|
|
562
1033
|
return {
|
|
563
|
-
outputPath:
|
|
1034
|
+
outputPath: path6.relative(workspacePath, absOutputPath).replace(/\\/g, "/"),
|
|
564
1035
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
565
1036
|
counts: {
|
|
566
1037
|
backlog: grouped.open.length,
|
|
@@ -658,10 +1129,10 @@ function renderKanbanMarkdown(lanes) {
|
|
|
658
1129
|
lines.push("");
|
|
659
1130
|
return lines.join("\n");
|
|
660
1131
|
}
|
|
661
|
-
function
|
|
662
|
-
const base =
|
|
663
|
-
const resolved =
|
|
664
|
-
if (!resolved.startsWith(base +
|
|
1132
|
+
function resolvePathWithinWorkspace3(workspacePath, outputPath) {
|
|
1133
|
+
const base = path6.resolve(workspacePath);
|
|
1134
|
+
const resolved = path6.resolve(base, outputPath);
|
|
1135
|
+
if (!resolved.startsWith(base + path6.sep) && resolved !== base) {
|
|
665
1136
|
throw new Error(`Invalid board output path: ${outputPath}`);
|
|
666
1137
|
}
|
|
667
1138
|
return resolved;
|
|
@@ -1142,6 +1613,311 @@ function supportedIntegrationList() {
|
|
|
1142
1613
|
return Object.keys(INTEGRATIONS).sort().join(", ");
|
|
1143
1614
|
}
|
|
1144
1615
|
|
|
1616
|
+
// src/swarm.ts
|
|
1617
|
+
var swarm_exports = {};
|
|
1618
|
+
__export(swarm_exports, {
|
|
1619
|
+
createPlanTemplate: () => createPlanTemplate,
|
|
1620
|
+
deployPlan: () => deployPlan,
|
|
1621
|
+
getSwarmStatus: () => getSwarmStatus,
|
|
1622
|
+
synthesize: () => synthesize,
|
|
1623
|
+
validatePlan: () => validatePlan,
|
|
1624
|
+
workerClaim: () => workerClaim,
|
|
1625
|
+
workerComplete: () => workerComplete,
|
|
1626
|
+
workerLoop: () => workerLoop
|
|
1627
|
+
});
|
|
1628
|
+
import * as fs7 from "fs";
|
|
1629
|
+
import * as path7 from "path";
|
|
1630
|
+
function createPlanTemplate(goal) {
|
|
1631
|
+
return {
|
|
1632
|
+
goal,
|
|
1633
|
+
tasks: [],
|
|
1634
|
+
phases: [],
|
|
1635
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1636
|
+
estimatedTotalMinutes: 0
|
|
1637
|
+
};
|
|
1638
|
+
}
|
|
1639
|
+
function validatePlan(plan) {
|
|
1640
|
+
const errors = [];
|
|
1641
|
+
if (!plan.goal.title) errors.push("Goal title is required");
|
|
1642
|
+
if (plan.tasks.length === 0) errors.push("Plan has no tasks");
|
|
1643
|
+
if (plan.tasks.length > (plan.goal.maxTasks ?? 1e3)) {
|
|
1644
|
+
errors.push(`Plan has ${plan.tasks.length} tasks, exceeds max ${plan.goal.maxTasks ?? 1e3}`);
|
|
1645
|
+
}
|
|
1646
|
+
const taskTitles = new Set(plan.tasks.map((t) => t.title));
|
|
1647
|
+
for (const task of plan.tasks) {
|
|
1648
|
+
for (const dep of task.dependsOn ?? []) {
|
|
1649
|
+
if (!taskTitles.has(dep)) {
|
|
1650
|
+
errors.push(`Task "${task.title}" depends on unknown task "${dep}"`);
|
|
1651
|
+
}
|
|
1652
|
+
}
|
|
1653
|
+
}
|
|
1654
|
+
const visited = /* @__PURE__ */ new Set();
|
|
1655
|
+
const stack = /* @__PURE__ */ new Set();
|
|
1656
|
+
const depMap = /* @__PURE__ */ new Map();
|
|
1657
|
+
for (const task of plan.tasks) {
|
|
1658
|
+
depMap.set(task.title, task.dependsOn ?? []);
|
|
1659
|
+
}
|
|
1660
|
+
function hasCycle(node) {
|
|
1661
|
+
if (stack.has(node)) return true;
|
|
1662
|
+
if (visited.has(node)) return false;
|
|
1663
|
+
visited.add(node);
|
|
1664
|
+
stack.add(node);
|
|
1665
|
+
for (const dep of depMap.get(node) ?? []) {
|
|
1666
|
+
if (hasCycle(dep)) return true;
|
|
1667
|
+
}
|
|
1668
|
+
stack.delete(node);
|
|
1669
|
+
return false;
|
|
1670
|
+
}
|
|
1671
|
+
for (const task of plan.tasks) {
|
|
1672
|
+
visited.clear();
|
|
1673
|
+
stack.clear();
|
|
1674
|
+
if (hasCycle(task.title)) {
|
|
1675
|
+
errors.push(`Circular dependency detected involving "${task.title}"`);
|
|
1676
|
+
break;
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
for (const phase of plan.phases) {
|
|
1680
|
+
for (const idx of phase.taskIndices) {
|
|
1681
|
+
if (idx < 0 || idx >= plan.tasks.length) {
|
|
1682
|
+
errors.push(`Phase "${phase.name}" references invalid task index ${idx}`);
|
|
1683
|
+
}
|
|
1684
|
+
}
|
|
1685
|
+
}
|
|
1686
|
+
return { valid: errors.length === 0, errors };
|
|
1687
|
+
}
|
|
1688
|
+
function deployPlan(workspacePath, plan, actor) {
|
|
1689
|
+
const validation = validatePlan(plan);
|
|
1690
|
+
if (!validation.valid) {
|
|
1691
|
+
throw new Error(`Invalid plan: ${validation.errors.join("; ")}`);
|
|
1692
|
+
}
|
|
1693
|
+
const spaceSlug = slugify(`swarm-${plan.goal.title}`);
|
|
1694
|
+
const spacePath = path7.join("spaces", `${spaceSlug}.md`);
|
|
1695
|
+
const spaceFullPath = path7.join(workspacePath, spacePath);
|
|
1696
|
+
if (!fs7.existsSync(spaceFullPath)) {
|
|
1697
|
+
const spaceDir = path7.join(workspacePath, "spaces");
|
|
1698
|
+
fs7.mkdirSync(spaceDir, { recursive: true });
|
|
1699
|
+
const spaceFrontmatter = [
|
|
1700
|
+
"---",
|
|
1701
|
+
`title: "Swarm: ${plan.goal.title}"`,
|
|
1702
|
+
`status: active`,
|
|
1703
|
+
`created: '${(/* @__PURE__ */ new Date()).toISOString()}'`,
|
|
1704
|
+
`updated: '${(/* @__PURE__ */ new Date()).toISOString()}'`,
|
|
1705
|
+
"---",
|
|
1706
|
+
"",
|
|
1707
|
+
`# Swarm Space: ${plan.goal.title}`,
|
|
1708
|
+
"",
|
|
1709
|
+
plan.goal.description,
|
|
1710
|
+
"",
|
|
1711
|
+
`Total tasks: ${plan.tasks.length}`
|
|
1712
|
+
].join("\n");
|
|
1713
|
+
fs7.writeFileSync(spaceFullPath, spaceFrontmatter);
|
|
1714
|
+
}
|
|
1715
|
+
const threadPaths = [];
|
|
1716
|
+
const slugMap = /* @__PURE__ */ new Map();
|
|
1717
|
+
for (const task of plan.tasks) {
|
|
1718
|
+
const taskSlug = slugify(task.title);
|
|
1719
|
+
slugMap.set(task.title, taskSlug);
|
|
1720
|
+
}
|
|
1721
|
+
for (const task of plan.tasks) {
|
|
1722
|
+
const taskSlug = slugMap.get(task.title);
|
|
1723
|
+
let body = `# ${task.title}
|
|
1724
|
+
|
|
1725
|
+
${task.description}
|
|
1726
|
+
`;
|
|
1727
|
+
if (task.dependsOn && task.dependsOn.length > 0) {
|
|
1728
|
+
body += `
|
|
1729
|
+
## Dependencies
|
|
1730
|
+
`;
|
|
1731
|
+
for (const dep of task.dependsOn) {
|
|
1732
|
+
const depSlug = slugMap.get(dep);
|
|
1733
|
+
if (depSlug) {
|
|
1734
|
+
body += `- [[${depSlug}]]
|
|
1735
|
+
`;
|
|
1736
|
+
}
|
|
1737
|
+
}
|
|
1738
|
+
}
|
|
1739
|
+
body += `
|
|
1740
|
+
## Output
|
|
1741
|
+
|
|
1742
|
+
_Agent writes result here._
|
|
1743
|
+
`;
|
|
1744
|
+
if (task.tags && task.tags.length > 0) {
|
|
1745
|
+
body += `
|
|
1746
|
+
Tags: ${task.tags.join(", ")}
|
|
1747
|
+
`;
|
|
1748
|
+
}
|
|
1749
|
+
const created = createThread(workspacePath, task.title, body, actor, {
|
|
1750
|
+
priority: task.priority,
|
|
1751
|
+
space: `spaces/${spaceSlug}`
|
|
1752
|
+
});
|
|
1753
|
+
threadPaths.push(created.path);
|
|
1754
|
+
}
|
|
1755
|
+
const deployment = {
|
|
1756
|
+
planPath: path7.join(".workgraph", `swarm-${spaceSlug}.json`),
|
|
1757
|
+
workspacePath,
|
|
1758
|
+
threadPaths,
|
|
1759
|
+
spaceSlug,
|
|
1760
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1761
|
+
status: "deployed"
|
|
1762
|
+
};
|
|
1763
|
+
const manifestPath = path7.join(workspacePath, deployment.planPath);
|
|
1764
|
+
fs7.mkdirSync(path7.dirname(manifestPath), { recursive: true });
|
|
1765
|
+
fs7.writeFileSync(manifestPath, JSON.stringify({ plan, deployment }, null, 2));
|
|
1766
|
+
append(workspacePath, actor, "create", deployment.planPath, "swarm");
|
|
1767
|
+
return deployment;
|
|
1768
|
+
}
|
|
1769
|
+
function getSwarmStatus(workspacePath, spaceSlug) {
|
|
1770
|
+
const manifestPath = path7.join(workspacePath, ".workgraph", `swarm-${spaceSlug}.json`);
|
|
1771
|
+
if (!fs7.existsSync(manifestPath)) {
|
|
1772
|
+
throw new Error(`No swarm deployment found for space "${spaceSlug}"`);
|
|
1773
|
+
}
|
|
1774
|
+
const manifest = JSON.parse(fs7.readFileSync(manifestPath, "utf-8"));
|
|
1775
|
+
const deployment = manifest.deployment;
|
|
1776
|
+
const threads = [];
|
|
1777
|
+
let claimed = 0;
|
|
1778
|
+
let done2 = 0;
|
|
1779
|
+
let blocked = 0;
|
|
1780
|
+
let open = 0;
|
|
1781
|
+
for (const threadPath of deployment.threadPaths) {
|
|
1782
|
+
const t = read(workspacePath, threadPath);
|
|
1783
|
+
if (!t) continue;
|
|
1784
|
+
const status = String(t.fields.status ?? "open");
|
|
1785
|
+
const threadInfo = {
|
|
1786
|
+
path: threadPath,
|
|
1787
|
+
title: String(t.fields.title ?? ""),
|
|
1788
|
+
status,
|
|
1789
|
+
owner: t.fields.owner ? String(t.fields.owner) : void 0,
|
|
1790
|
+
priority: String(t.fields.priority ?? "medium")
|
|
1791
|
+
};
|
|
1792
|
+
threads.push(threadInfo);
|
|
1793
|
+
if (status === "done") done2++;
|
|
1794
|
+
else if (status === "active") claimed++;
|
|
1795
|
+
else if (status === "blocked") blocked++;
|
|
1796
|
+
else open++;
|
|
1797
|
+
}
|
|
1798
|
+
const total = deployment.threadPaths.length;
|
|
1799
|
+
const readyToClaim = open;
|
|
1800
|
+
const percentComplete = total > 0 ? Math.round(done2 / total * 100) : 0;
|
|
1801
|
+
if (done2 === total) deployment.status = "done";
|
|
1802
|
+
else if (claimed > 0 || done2 > 0) deployment.status = "running";
|
|
1803
|
+
return {
|
|
1804
|
+
deployment,
|
|
1805
|
+
total,
|
|
1806
|
+
claimed,
|
|
1807
|
+
done: done2,
|
|
1808
|
+
blocked,
|
|
1809
|
+
open,
|
|
1810
|
+
readyToClaim,
|
|
1811
|
+
percentComplete,
|
|
1812
|
+
threads
|
|
1813
|
+
};
|
|
1814
|
+
}
|
|
1815
|
+
function workerClaim(workspacePath, spaceSlug, agent) {
|
|
1816
|
+
const ready = listReadyThreadsInSpace(workspacePath, `spaces/${spaceSlug}`);
|
|
1817
|
+
if (ready.length === 0) return null;
|
|
1818
|
+
const priorityOrder = {
|
|
1819
|
+
critical: 0,
|
|
1820
|
+
high: 1,
|
|
1821
|
+
medium: 2,
|
|
1822
|
+
low: 3
|
|
1823
|
+
};
|
|
1824
|
+
ready.sort((a, b) => {
|
|
1825
|
+
const aPri = priorityOrder[String(a.fields.priority)] ?? 2;
|
|
1826
|
+
const bPri = priorityOrder[String(b.fields.priority)] ?? 2;
|
|
1827
|
+
return aPri - bPri;
|
|
1828
|
+
});
|
|
1829
|
+
const target = ready[0];
|
|
1830
|
+
return claim(workspacePath, target.path, agent);
|
|
1831
|
+
}
|
|
1832
|
+
function workerComplete(workspacePath, threadPath, agent, result) {
|
|
1833
|
+
const t = read(workspacePath, threadPath);
|
|
1834
|
+
if (!t) throw new Error(`Thread not found: ${threadPath}`);
|
|
1835
|
+
const currentBody = t.body ?? "";
|
|
1836
|
+
const updatedBody = currentBody.replace(
|
|
1837
|
+
"_Agent writes result here._",
|
|
1838
|
+
result
|
|
1839
|
+
);
|
|
1840
|
+
return done(workspacePath, threadPath, agent, updatedBody);
|
|
1841
|
+
}
|
|
1842
|
+
async function workerLoop(workspacePath, spaceSlug, agent, workFn, options) {
|
|
1843
|
+
let completed = 0;
|
|
1844
|
+
let errors = 0;
|
|
1845
|
+
const maxTasks = options?.maxTasks ?? Infinity;
|
|
1846
|
+
const delayMs = options?.delayMs ?? 1e3;
|
|
1847
|
+
while (completed + errors < maxTasks) {
|
|
1848
|
+
const claimed = workerClaim(workspacePath, spaceSlug, agent);
|
|
1849
|
+
if (!claimed) break;
|
|
1850
|
+
try {
|
|
1851
|
+
const result = await workFn(claimed);
|
|
1852
|
+
workerComplete(workspacePath, claimed.path, agent, result);
|
|
1853
|
+
completed++;
|
|
1854
|
+
} catch (err) {
|
|
1855
|
+
errors++;
|
|
1856
|
+
const errorMsg = err instanceof Error ? err.message : String(err);
|
|
1857
|
+
try {
|
|
1858
|
+
update(workspacePath, claimed.path, {
|
|
1859
|
+
status: "blocked"
|
|
1860
|
+
}, `Error: ${errorMsg}`, agent);
|
|
1861
|
+
} catch {
|
|
1862
|
+
}
|
|
1863
|
+
}
|
|
1864
|
+
if (delayMs > 0) {
|
|
1865
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
return { completed, errors };
|
|
1869
|
+
}
|
|
1870
|
+
function synthesize(workspacePath, spaceSlug) {
|
|
1871
|
+
const status = getSwarmStatus(workspacePath, spaceSlug);
|
|
1872
|
+
const sections = [];
|
|
1873
|
+
const manifestPath = path7.join(workspacePath, ".workgraph", `swarm-${spaceSlug}.json`);
|
|
1874
|
+
const manifest = JSON.parse(fs7.readFileSync(manifestPath, "utf-8"));
|
|
1875
|
+
const plan = manifest.plan;
|
|
1876
|
+
sections.push(`# ${plan.goal.title}
|
|
1877
|
+
`);
|
|
1878
|
+
sections.push(`${plan.goal.description}
|
|
1879
|
+
`);
|
|
1880
|
+
sections.push(`---
|
|
1881
|
+
`);
|
|
1882
|
+
for (const threadInfo of status.threads) {
|
|
1883
|
+
const t = read(workspacePath, threadInfo.path);
|
|
1884
|
+
if (!t) continue;
|
|
1885
|
+
if (threadInfo.status !== "done") {
|
|
1886
|
+
sections.push(`## [PENDING] ${threadInfo.title}
|
|
1887
|
+
|
|
1888
|
+
_Not yet completed._
|
|
1889
|
+
`);
|
|
1890
|
+
continue;
|
|
1891
|
+
}
|
|
1892
|
+
const body = t.body ?? "";
|
|
1893
|
+
const result = body.replace(/^#\s+.*\n/, "").trim();
|
|
1894
|
+
if (result && result !== "_Agent writes result here._") {
|
|
1895
|
+
sections.push(`## ${threadInfo.title}
|
|
1896
|
+
|
|
1897
|
+
${result}
|
|
1898
|
+
`);
|
|
1899
|
+
} else {
|
|
1900
|
+
sections.push(`## ${threadInfo.title}
|
|
1901
|
+
|
|
1902
|
+
_Completed but no output found._
|
|
1903
|
+
`);
|
|
1904
|
+
}
|
|
1905
|
+
}
|
|
1906
|
+
sections.push(`
|
|
1907
|
+
---
|
|
1908
|
+
`);
|
|
1909
|
+
sections.push(`*Generated from swarm "${plan.goal.title}" \u2014 ${status.done}/${status.total} tasks completed.*
|
|
1910
|
+
`);
|
|
1911
|
+
return {
|
|
1912
|
+
markdown: sections.join("\n"),
|
|
1913
|
+
completedCount: status.done,
|
|
1914
|
+
totalCount: status.total
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
function slugify(text) {
|
|
1918
|
+
return text.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").substring(0, 80);
|
|
1919
|
+
}
|
|
1920
|
+
|
|
1145
1921
|
// src/diagnostics/index.ts
|
|
1146
1922
|
var diagnostics_exports = {};
|
|
1147
1923
|
__export(diagnostics_exports, {
|
|
@@ -1157,8 +1933,8 @@ __export(diagnostics_exports, {
|
|
|
1157
1933
|
});
|
|
1158
1934
|
|
|
1159
1935
|
// src/diagnostics/doctor.ts
|
|
1160
|
-
import
|
|
1161
|
-
import
|
|
1936
|
+
import fs8 from "fs";
|
|
1937
|
+
import path9 from "path";
|
|
1162
1938
|
import YAML2 from "yaml";
|
|
1163
1939
|
|
|
1164
1940
|
// src/diagnostics/format.ts
|
|
@@ -1222,7 +1998,7 @@ function inferPrimitiveTypeFromPath(targetPath) {
|
|
|
1222
1998
|
}
|
|
1223
1999
|
|
|
1224
2000
|
// src/diagnostics/primitives.ts
|
|
1225
|
-
import
|
|
2001
|
+
import path8 from "path";
|
|
1226
2002
|
function loadPrimitiveInventory(workspacePath) {
|
|
1227
2003
|
const registry = loadRegistry(workspacePath);
|
|
1228
2004
|
const allPrimitives = queryPrimitives(workspacePath);
|
|
@@ -1240,7 +2016,7 @@ function loadPrimitiveInventory(workspacePath) {
|
|
|
1240
2016
|
const requiredFields = Object.entries(typeDef?.fields ?? {}).filter(([, fieldDef]) => fieldDef.required === true).map(([fieldName]) => fieldName);
|
|
1241
2017
|
const presentCount = requiredFields.filter((fieldName) => hasRequiredValue(instance.fields[fieldName])).length;
|
|
1242
2018
|
const frontmatterCompleteness = requiredFields.length === 0 ? 1 : presentCount / requiredFields.length;
|
|
1243
|
-
const slug =
|
|
2019
|
+
const slug = path8.basename(instance.path, ".md");
|
|
1244
2020
|
return {
|
|
1245
2021
|
...instance,
|
|
1246
2022
|
slug,
|
|
@@ -1582,8 +2358,8 @@ function collectStaleRuns(workspacePath, staleAfterMs, now) {
|
|
|
1582
2358
|
}
|
|
1583
2359
|
function collectPrimitiveRegistryReferenceIssues(workspacePath, inventory) {
|
|
1584
2360
|
const issues = [];
|
|
1585
|
-
const manifestPath =
|
|
1586
|
-
if (!
|
|
2361
|
+
const manifestPath = path9.join(workspacePath, ".workgraph", "primitive-registry.yaml");
|
|
2362
|
+
if (!fs8.existsSync(manifestPath)) {
|
|
1587
2363
|
issues.push({
|
|
1588
2364
|
code: "broken-primitive-registry-reference",
|
|
1589
2365
|
severity: "error",
|
|
@@ -1594,7 +2370,7 @@ function collectPrimitiveRegistryReferenceIssues(workspacePath, inventory) {
|
|
|
1594
2370
|
}
|
|
1595
2371
|
let parsed;
|
|
1596
2372
|
try {
|
|
1597
|
-
parsed = YAML2.parse(
|
|
2373
|
+
parsed = YAML2.parse(fs8.readFileSync(manifestPath, "utf-8"));
|
|
1598
2374
|
} catch (error) {
|
|
1599
2375
|
issues.push({
|
|
1600
2376
|
code: "broken-primitive-registry-reference",
|
|
@@ -1646,7 +2422,7 @@ function collectPrimitiveRegistryReferenceIssues(workspacePath, inventory) {
|
|
|
1646
2422
|
path: ".workgraph/primitive-registry.yaml"
|
|
1647
2423
|
});
|
|
1648
2424
|
}
|
|
1649
|
-
if (!
|
|
2425
|
+
if (!fs8.existsSync(path9.join(workspacePath, directory))) {
|
|
1650
2426
|
issues.push({
|
|
1651
2427
|
code: "broken-primitive-registry-reference",
|
|
1652
2428
|
severity: "error",
|
|
@@ -1681,8 +2457,8 @@ function collectPrimitiveRegistryReferenceIssues(workspacePath, inventory) {
|
|
|
1681
2457
|
function collectEmptyPrimitiveDirectoryIssues(workspacePath, inventory) {
|
|
1682
2458
|
const issues = [];
|
|
1683
2459
|
for (const typeDef of inventory.typeDefs.values()) {
|
|
1684
|
-
const directoryPath =
|
|
1685
|
-
if (!
|
|
2460
|
+
const directoryPath = path9.join(workspacePath, typeDef.directory);
|
|
2461
|
+
if (!fs8.existsSync(directoryPath)) continue;
|
|
1686
2462
|
const markdownCount = listMarkdownFilesRecursive(directoryPath).length;
|
|
1687
2463
|
if (markdownCount > 0) continue;
|
|
1688
2464
|
issues.push({
|
|
@@ -1711,10 +2487,10 @@ function removeOrphanLinks(workspacePath, orphanLinks) {
|
|
|
1711
2487
|
}
|
|
1712
2488
|
let removedLinks = 0;
|
|
1713
2489
|
for (const [sourcePath, tokenSet] of tokensBySource.entries()) {
|
|
1714
|
-
const absPath =
|
|
1715
|
-
if (!
|
|
2490
|
+
const absPath = path9.join(workspacePath, sourcePath);
|
|
2491
|
+
if (!fs8.existsSync(absPath)) continue;
|
|
1716
2492
|
try {
|
|
1717
|
-
const raw =
|
|
2493
|
+
const raw = fs8.readFileSync(absPath, "utf-8");
|
|
1718
2494
|
let fileRemoved = 0;
|
|
1719
2495
|
const updated = raw.replace(/\[\[([^[\]]+)\]\]/g, (token) => {
|
|
1720
2496
|
if (!tokenSet.has(token)) return token;
|
|
@@ -1722,7 +2498,7 @@ function removeOrphanLinks(workspacePath, orphanLinks) {
|
|
|
1722
2498
|
return "";
|
|
1723
2499
|
});
|
|
1724
2500
|
if (fileRemoved === 0) continue;
|
|
1725
|
-
|
|
2501
|
+
fs8.writeFileSync(absPath, updated, "utf-8");
|
|
1726
2502
|
removedLinks += fileRemoved;
|
|
1727
2503
|
filesUpdated.push(sourcePath);
|
|
1728
2504
|
} catch (error) {
|
|
@@ -1787,10 +2563,10 @@ function cancelStaleRuns(workspacePath, staleRuns, actor) {
|
|
|
1787
2563
|
return { cancelled, errors };
|
|
1788
2564
|
}
|
|
1789
2565
|
function readDispatchRunsSnapshot(workspacePath) {
|
|
1790
|
-
const runsPath =
|
|
1791
|
-
if (!
|
|
2566
|
+
const runsPath = path9.join(workspacePath, ".workgraph", "dispatch-runs.json");
|
|
2567
|
+
if (!fs8.existsSync(runsPath)) return [];
|
|
1792
2568
|
try {
|
|
1793
|
-
const parsed = JSON.parse(
|
|
2569
|
+
const parsed = JSON.parse(fs8.readFileSync(runsPath, "utf-8"));
|
|
1794
2570
|
return Array.isArray(parsed.runs) ? parsed.runs : [];
|
|
1795
2571
|
} catch {
|
|
1796
2572
|
return [];
|
|
@@ -1801,9 +2577,9 @@ function listMarkdownFilesRecursive(rootDirectory) {
|
|
|
1801
2577
|
const stack = [rootDirectory];
|
|
1802
2578
|
while (stack.length > 0) {
|
|
1803
2579
|
const current = stack.pop();
|
|
1804
|
-
const entries =
|
|
2580
|
+
const entries = fs8.readdirSync(current, { withFileTypes: true });
|
|
1805
2581
|
for (const entry of entries) {
|
|
1806
|
-
const absPath =
|
|
2582
|
+
const absPath = path9.join(current, entry.name);
|
|
1807
2583
|
if (entry.isDirectory()) {
|
|
1808
2584
|
stack.push(absPath);
|
|
1809
2585
|
continue;
|
|
@@ -1947,7 +2723,7 @@ function toNullableString(value) {
|
|
|
1947
2723
|
}
|
|
1948
2724
|
|
|
1949
2725
|
// src/diagnostics/viz.ts
|
|
1950
|
-
import
|
|
2726
|
+
import path10 from "path";
|
|
1951
2727
|
var TYPE_COLORS = ["cyan", "magenta", "yellow", "green", "blue", "red"];
|
|
1952
2728
|
function visualizeVaultGraph(workspacePath, options = {}) {
|
|
1953
2729
|
const inventory = loadPrimitiveInventory(workspacePath);
|
|
@@ -2087,7 +2863,7 @@ function resolveFocusPath(focusInput, inventory) {
|
|
|
2087
2863
|
const directCandidate = normalized.endsWith(".md") ? normalized : `${normalized}.md`;
|
|
2088
2864
|
if (inventory.byPath.has(normalized)) return normalized;
|
|
2089
2865
|
if (inventory.byPath.has(directCandidate)) return directCandidate;
|
|
2090
|
-
const slug =
|
|
2866
|
+
const slug = path10.basename(normalized, ".md");
|
|
2091
2867
|
const candidates = inventory.slugToPaths.get(slug) ?? [];
|
|
2092
2868
|
if (candidates.length === 1) return candidates[0];
|
|
2093
2869
|
if (candidates.length > 1) {
|
|
@@ -2395,8 +3171,8 @@ __export(autonomy_daemon_exports, {
|
|
|
2395
3171
|
startAutonomyDaemon: () => startAutonomyDaemon,
|
|
2396
3172
|
stopAutonomyDaemon: () => stopAutonomyDaemon
|
|
2397
3173
|
});
|
|
2398
|
-
import
|
|
2399
|
-
import
|
|
3174
|
+
import fs9 from "fs";
|
|
3175
|
+
import path11 from "path";
|
|
2400
3176
|
import { spawn } from "child_process";
|
|
2401
3177
|
var DAEMON_DIR = ".workgraph/daemon";
|
|
2402
3178
|
var AUTONOMY_PID_FILE = "autonomy.pid";
|
|
@@ -2405,29 +3181,29 @@ var AUTONOMY_LOG_FILE = "autonomy.log";
|
|
|
2405
3181
|
var AUTONOMY_META_FILE = "autonomy-process.json";
|
|
2406
3182
|
function startAutonomyDaemon(workspacePath, input) {
|
|
2407
3183
|
const daemonDir = ensureDaemonDir(workspacePath);
|
|
2408
|
-
const pidPath =
|
|
2409
|
-
const heartbeatPath = input.heartbeatPath ?
|
|
2410
|
-
const logPath = input.logPath ?
|
|
2411
|
-
const metaPath =
|
|
3184
|
+
const pidPath = path11.join(daemonDir, AUTONOMY_PID_FILE);
|
|
3185
|
+
const heartbeatPath = input.heartbeatPath ? resolvePathWithinWorkspace4(workspacePath, input.heartbeatPath) : path11.join(daemonDir, AUTONOMY_HEARTBEAT_FILE);
|
|
3186
|
+
const logPath = input.logPath ? resolvePathWithinWorkspace4(workspacePath, input.logPath) : path11.join(daemonDir, AUTONOMY_LOG_FILE);
|
|
3187
|
+
const metaPath = path11.join(daemonDir, AUTONOMY_META_FILE);
|
|
2412
3188
|
const existing = readAutonomyDaemonStatus(workspacePath, { cleanupStalePidFile: true });
|
|
2413
3189
|
if (existing.running) {
|
|
2414
3190
|
throw new Error(`Autonomy daemon already running (pid=${existing.pid}). Stop it before starting a new one.`);
|
|
2415
3191
|
}
|
|
2416
|
-
const logFd =
|
|
3192
|
+
const logFd = fs9.openSync(logPath, "a");
|
|
2417
3193
|
const args = buildAutonomyDaemonArgs(workspacePath, input, heartbeatPath);
|
|
2418
3194
|
const child = spawn(process.execPath, args, {
|
|
2419
3195
|
detached: true,
|
|
2420
3196
|
stdio: ["ignore", logFd, logFd],
|
|
2421
3197
|
env: process.env
|
|
2422
3198
|
});
|
|
2423
|
-
|
|
3199
|
+
fs9.closeSync(logFd);
|
|
2424
3200
|
child.unref();
|
|
2425
3201
|
if (!child.pid) {
|
|
2426
3202
|
throw new Error("Failed to start autonomy daemon: missing child process pid.");
|
|
2427
3203
|
}
|
|
2428
|
-
|
|
3204
|
+
fs9.writeFileSync(pidPath, `${child.pid}
|
|
2429
3205
|
`, "utf-8");
|
|
2430
|
-
|
|
3206
|
+
fs9.writeFileSync(metaPath, JSON.stringify({
|
|
2431
3207
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2432
3208
|
pid: child.pid,
|
|
2433
3209
|
args,
|
|
@@ -2461,9 +3237,9 @@ async function stopAutonomyDaemon(workspacePath, input = {}) {
|
|
|
2461
3237
|
await waitForProcessExit(pid, 1500);
|
|
2462
3238
|
stopped = !isProcessAlive(pid);
|
|
2463
3239
|
}
|
|
2464
|
-
const pidPath =
|
|
2465
|
-
if (stopped &&
|
|
2466
|
-
|
|
3240
|
+
const pidPath = path11.join(ensureDaemonDir(workspacePath), AUTONOMY_PID_FILE);
|
|
3241
|
+
if (stopped && fs9.existsSync(pidPath)) {
|
|
3242
|
+
fs9.rmSync(pidPath, { force: true });
|
|
2467
3243
|
}
|
|
2468
3244
|
return {
|
|
2469
3245
|
stopped,
|
|
@@ -2474,14 +3250,14 @@ async function stopAutonomyDaemon(workspacePath, input = {}) {
|
|
|
2474
3250
|
}
|
|
2475
3251
|
function readAutonomyDaemonStatus(workspacePath, options = {}) {
|
|
2476
3252
|
const daemonDir = ensureDaemonDir(workspacePath);
|
|
2477
|
-
const pidPath =
|
|
2478
|
-
const meta = readDaemonMeta(
|
|
2479
|
-
const logPath = meta?.logPath ? String(meta.logPath) :
|
|
2480
|
-
const heartbeatPath = meta?.heartbeatPath ? String(meta.heartbeatPath) :
|
|
3253
|
+
const pidPath = path11.join(daemonDir, AUTONOMY_PID_FILE);
|
|
3254
|
+
const meta = readDaemonMeta(path11.join(daemonDir, AUTONOMY_META_FILE));
|
|
3255
|
+
const logPath = meta?.logPath ? String(meta.logPath) : path11.join(daemonDir, AUTONOMY_LOG_FILE);
|
|
3256
|
+
const heartbeatPath = meta?.heartbeatPath ? String(meta.heartbeatPath) : path11.join(daemonDir, AUTONOMY_HEARTBEAT_FILE);
|
|
2481
3257
|
const pid = readPid(pidPath);
|
|
2482
3258
|
const running = pid ? isProcessAlive(pid) : false;
|
|
2483
|
-
if (!running && pid && options.cleanupStalePidFile !== false &&
|
|
2484
|
-
|
|
3259
|
+
if (!running && pid && options.cleanupStalePidFile !== false && fs9.existsSync(pidPath)) {
|
|
3260
|
+
fs9.rmSync(pidPath, { force: true });
|
|
2485
3261
|
}
|
|
2486
3262
|
return {
|
|
2487
3263
|
running,
|
|
@@ -2494,7 +3270,7 @@ function readAutonomyDaemonStatus(workspacePath, options = {}) {
|
|
|
2494
3270
|
}
|
|
2495
3271
|
function buildAutonomyDaemonArgs(workspacePath, input, heartbeatPath) {
|
|
2496
3272
|
const args = [
|
|
2497
|
-
|
|
3273
|
+
path11.resolve(input.cliEntrypointPath),
|
|
2498
3274
|
"autonomy",
|
|
2499
3275
|
"run",
|
|
2500
3276
|
"-w",
|
|
@@ -2552,22 +3328,22 @@ function waitForProcessExit(pid, timeoutMs) {
|
|
|
2552
3328
|
});
|
|
2553
3329
|
}
|
|
2554
3330
|
function ensureDaemonDir(workspacePath) {
|
|
2555
|
-
const daemonDir =
|
|
2556
|
-
if (!
|
|
3331
|
+
const daemonDir = path11.join(workspacePath, DAEMON_DIR);
|
|
3332
|
+
if (!fs9.existsSync(daemonDir)) fs9.mkdirSync(daemonDir, { recursive: true });
|
|
2557
3333
|
return daemonDir;
|
|
2558
3334
|
}
|
|
2559
3335
|
function readPid(pidPath) {
|
|
2560
|
-
if (!
|
|
2561
|
-
const raw =
|
|
3336
|
+
if (!fs9.existsSync(pidPath)) return void 0;
|
|
3337
|
+
const raw = fs9.readFileSync(pidPath, "utf-8").trim();
|
|
2562
3338
|
if (!raw) return void 0;
|
|
2563
3339
|
const parsed = Number(raw);
|
|
2564
3340
|
if (!Number.isInteger(parsed) || parsed <= 0) return void 0;
|
|
2565
3341
|
return parsed;
|
|
2566
3342
|
}
|
|
2567
3343
|
function readHeartbeat(heartbeatPath) {
|
|
2568
|
-
if (!
|
|
3344
|
+
if (!fs9.existsSync(heartbeatPath)) return void 0;
|
|
2569
3345
|
try {
|
|
2570
|
-
const parsed = JSON.parse(
|
|
3346
|
+
const parsed = JSON.parse(fs9.readFileSync(heartbeatPath, "utf-8"));
|
|
2571
3347
|
if (!parsed || typeof parsed !== "object") return void 0;
|
|
2572
3348
|
return parsed;
|
|
2573
3349
|
} catch {
|
|
@@ -2575,9 +3351,9 @@ function readHeartbeat(heartbeatPath) {
|
|
|
2575
3351
|
}
|
|
2576
3352
|
}
|
|
2577
3353
|
function readDaemonMeta(metaPath) {
|
|
2578
|
-
if (!
|
|
3354
|
+
if (!fs9.existsSync(metaPath)) return void 0;
|
|
2579
3355
|
try {
|
|
2580
|
-
const parsed = JSON.parse(
|
|
3356
|
+
const parsed = JSON.parse(fs9.readFileSync(metaPath, "utf-8"));
|
|
2581
3357
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
2582
3358
|
return parsed;
|
|
2583
3359
|
} catch {
|
|
@@ -2595,9 +3371,9 @@ function isProcessAlive(pid) {
|
|
|
2595
3371
|
}
|
|
2596
3372
|
function isZombieProcess(pid) {
|
|
2597
3373
|
const statPath = `/proc/${pid}/stat`;
|
|
2598
|
-
if (!
|
|
3374
|
+
if (!fs9.existsSync(statPath)) return false;
|
|
2599
3375
|
try {
|
|
2600
|
-
const stat =
|
|
3376
|
+
const stat = fs9.readFileSync(statPath, "utf-8");
|
|
2601
3377
|
const closingIdx = stat.indexOf(")");
|
|
2602
3378
|
if (closingIdx === -1 || closingIdx + 2 >= stat.length) return false;
|
|
2603
3379
|
const state = stat.slice(closingIdx + 2, closingIdx + 3);
|
|
@@ -2606,10 +3382,10 @@ function isZombieProcess(pid) {
|
|
|
2606
3382
|
return false;
|
|
2607
3383
|
}
|
|
2608
3384
|
}
|
|
2609
|
-
function
|
|
2610
|
-
const base =
|
|
2611
|
-
const resolved =
|
|
2612
|
-
if (!resolved.startsWith(base +
|
|
3385
|
+
function resolvePathWithinWorkspace4(workspacePath, filePath) {
|
|
3386
|
+
const base = path11.resolve(workspacePath);
|
|
3387
|
+
const resolved = path11.resolve(base, filePath);
|
|
3388
|
+
if (!resolved.startsWith(base + path11.sep) && resolved !== base) {
|
|
2613
3389
|
throw new Error(`Invalid path outside workspace: ${filePath}`);
|
|
2614
3390
|
}
|
|
2615
3391
|
return resolved;
|
|
@@ -2624,6 +3400,7 @@ export {
|
|
|
2624
3400
|
workspace_exports,
|
|
2625
3401
|
command_center_exports,
|
|
2626
3402
|
skill_exports,
|
|
3403
|
+
lens_exports,
|
|
2627
3404
|
board_exports,
|
|
2628
3405
|
agent_exports,
|
|
2629
3406
|
onboard_exports,
|
|
@@ -2633,6 +3410,7 @@ export {
|
|
|
2633
3410
|
fetchSkillMarkdownFromUrl,
|
|
2634
3411
|
clawdapus_exports,
|
|
2635
3412
|
integration_exports,
|
|
3413
|
+
swarm_exports,
|
|
2636
3414
|
diagnostics_exports,
|
|
2637
3415
|
autonomy_daemon_exports
|
|
2638
3416
|
};
|