@versatly/workgraph 1.0.0 → 1.1.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-OTSECVE5.js} +2 -0
- package/dist/{chunk-OJ6KOGB2.js → chunk-TOFSHG5S.js} +536 -67
- package/dist/cli.js +71 -2
- package/dist/index.d.ts +61 -1
- package/dist/index.js +4 -2
- package/dist/mcp-server.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -15,6 +15,7 @@ Agent-first workgraph workspace for multi-agent collaboration.
|
|
|
15
15
|
- Native skill primitive lifecycle (`workgraph skill write/load/propose/promote`)
|
|
16
16
|
- Primitive-registry manifest + auto-generated `.base` files
|
|
17
17
|
- Orientation loop commands (`workgraph status/brief/checkpoint/intake`)
|
|
18
|
+
- Deterministic context lenses (`workgraph lens list/show`) for real-time situational awareness
|
|
18
19
|
- Multi-filter primitive query (`workgraph query ...`)
|
|
19
20
|
- Core + QMD-compatible keyword search (`workgraph search ...`)
|
|
20
21
|
- Obsidian Kanban board generation/sync (`workgraph board generate|sync`)
|
|
@@ -61,6 +62,8 @@ workgraph thread create "Ship command center" \
|
|
|
61
62
|
workgraph thread next --claim --actor agent-worker --json
|
|
62
63
|
workgraph status --json
|
|
63
64
|
workgraph brief --actor agent-worker --json
|
|
65
|
+
workgraph lens list --json
|
|
66
|
+
workgraph lens show my-work --actor agent-worker --json
|
|
64
67
|
workgraph query --type thread --status open --limit 10 --json
|
|
65
68
|
workgraph search "auth" --mode auto --json
|
|
66
69
|
workgraph checkpoint "Completed API layer" --next "implement tests" --actor agent-worker --json
|
|
@@ -6027,12 +6027,14 @@ export {
|
|
|
6027
6027
|
CursorCloudAdapter,
|
|
6028
6028
|
createRun,
|
|
6029
6029
|
stop,
|
|
6030
|
+
listRuns,
|
|
6030
6031
|
dispatch_exports,
|
|
6031
6032
|
parseCronExpression,
|
|
6032
6033
|
matchesCronSchedule,
|
|
6033
6034
|
nextCronMatch,
|
|
6034
6035
|
trigger_engine_exports,
|
|
6035
6036
|
createThread,
|
|
6037
|
+
listReadyThreads,
|
|
6036
6038
|
release,
|
|
6037
6039
|
thread_exports,
|
|
6038
6040
|
thread_audit_exports,
|
|
@@ -9,6 +9,8 @@ import {
|
|
|
9
9
|
historyOf,
|
|
10
10
|
keywordSearch,
|
|
11
11
|
list,
|
|
12
|
+
listReadyThreads,
|
|
13
|
+
listRuns,
|
|
12
14
|
listTypes,
|
|
13
15
|
loadPolicyRegistry,
|
|
14
16
|
loadRegistry,
|
|
@@ -21,7 +23,7 @@ import {
|
|
|
21
23
|
saveRegistry,
|
|
22
24
|
stop,
|
|
23
25
|
update
|
|
24
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-OTSECVE5.js";
|
|
25
27
|
|
|
26
28
|
// src/workspace.ts
|
|
27
29
|
var workspace_exports = {};
|
|
@@ -532,14 +534,480 @@ function writeSkillManifest(workspacePath, slug, skill, actor) {
|
|
|
532
534
|
fs4.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + "\n", "utf-8");
|
|
533
535
|
}
|
|
534
536
|
|
|
537
|
+
// src/lens.ts
|
|
538
|
+
var lens_exports = {};
|
|
539
|
+
__export(lens_exports, {
|
|
540
|
+
generateContextLens: () => generateContextLens,
|
|
541
|
+
listContextLenses: () => listContextLenses,
|
|
542
|
+
materializeContextLens: () => materializeContextLens
|
|
543
|
+
});
|
|
544
|
+
import fs5 from "fs";
|
|
545
|
+
import path5 from "path";
|
|
546
|
+
var DEFAULT_LOOKBACK_HOURS = 24;
|
|
547
|
+
var DEFAULT_STALE_HOURS = 24;
|
|
548
|
+
var DEFAULT_LIMIT = 10;
|
|
549
|
+
var PRIORITY_ORDER = {
|
|
550
|
+
urgent: 0,
|
|
551
|
+
high: 1,
|
|
552
|
+
medium: 2,
|
|
553
|
+
low: 3
|
|
554
|
+
};
|
|
555
|
+
var HIGH_RISK_PRIORITIES = /* @__PURE__ */ new Set(["urgent", "high"]);
|
|
556
|
+
var HIGH_RISK_SEVERITIES = /* @__PURE__ */ new Set(["sev0", "sev1", "sev2"]);
|
|
557
|
+
var BUILT_IN_LENSES = [
|
|
558
|
+
{
|
|
559
|
+
id: "my-work",
|
|
560
|
+
description: "Actor workload, blockers, stale claims, and ready-next queue"
|
|
561
|
+
},
|
|
562
|
+
{
|
|
563
|
+
id: "team-risk",
|
|
564
|
+
description: "High-risk blockers, stale active claims, failed runs, and incidents"
|
|
565
|
+
},
|
|
566
|
+
{
|
|
567
|
+
id: "customer-health",
|
|
568
|
+
description: "Customer-tagged delivery health, blockers, and related incidents"
|
|
569
|
+
},
|
|
570
|
+
{
|
|
571
|
+
id: "exec-brief",
|
|
572
|
+
description: "Top priorities, momentum, risks, and recent decisions"
|
|
573
|
+
}
|
|
574
|
+
];
|
|
575
|
+
function listContextLenses() {
|
|
576
|
+
return BUILT_IN_LENSES.map((lens) => ({ ...lens }));
|
|
577
|
+
}
|
|
578
|
+
function generateContextLens(workspacePath, lensId, options = {}) {
|
|
579
|
+
const normalizedLensId = normalizeLensId(lensId);
|
|
580
|
+
const normalizedOptions = normalizeLensOptions(options);
|
|
581
|
+
switch (normalizedLensId) {
|
|
582
|
+
case "my-work":
|
|
583
|
+
return buildMyWorkLens(workspacePath, normalizedOptions);
|
|
584
|
+
case "team-risk":
|
|
585
|
+
return buildTeamRiskLens(workspacePath, normalizedOptions);
|
|
586
|
+
case "customer-health":
|
|
587
|
+
return buildCustomerHealthLens(workspacePath, normalizedOptions);
|
|
588
|
+
case "exec-brief":
|
|
589
|
+
return buildExecBriefLens(workspacePath, normalizedOptions);
|
|
590
|
+
default:
|
|
591
|
+
return assertNever(normalizedLensId);
|
|
592
|
+
}
|
|
593
|
+
}
|
|
594
|
+
function materializeContextLens(workspacePath, lensId, options) {
|
|
595
|
+
const result = generateContextLens(workspacePath, lensId, options);
|
|
596
|
+
const absOutputPath = resolvePathWithinWorkspace2(workspacePath, options.outputPath);
|
|
597
|
+
const relOutputPath = path5.relative(workspacePath, absOutputPath).replace(/\\/g, "/");
|
|
598
|
+
const parentDir = path5.dirname(absOutputPath);
|
|
599
|
+
if (!fs5.existsSync(parentDir)) fs5.mkdirSync(parentDir, { recursive: true });
|
|
600
|
+
const existed = fs5.existsSync(absOutputPath);
|
|
601
|
+
fs5.writeFileSync(absOutputPath, result.markdown, "utf-8");
|
|
602
|
+
append(
|
|
603
|
+
workspacePath,
|
|
604
|
+
options.actor ?? result.actor ?? "system",
|
|
605
|
+
existed ? "update" : "create",
|
|
606
|
+
relOutputPath,
|
|
607
|
+
"lens",
|
|
608
|
+
{
|
|
609
|
+
lens: result.lens,
|
|
610
|
+
sections: result.sections.length
|
|
611
|
+
}
|
|
612
|
+
);
|
|
613
|
+
return {
|
|
614
|
+
...result,
|
|
615
|
+
outputPath: relOutputPath,
|
|
616
|
+
created: !existed
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
function buildMyWorkLens(workspacePath, options) {
|
|
620
|
+
const actor = options.actor;
|
|
621
|
+
const nowMs = Date.now();
|
|
622
|
+
const staleCutoffMs = nowMs - options.staleHours * 60 * 60 * 1e3;
|
|
623
|
+
const claims = [...allClaims(workspacePath).entries()];
|
|
624
|
+
const myClaimedThreads = claims.filter(([, owner]) => owner === actor).map(([target]) => read(workspacePath, target)).filter((instance) => !!instance && instance.type === "thread").sort(compareThreadsByPriorityThenUpdated);
|
|
625
|
+
const myBlockedThreads = myClaimedThreads.filter((instance) => String(instance.fields.status ?? "") === "blocked").slice(0, options.limit);
|
|
626
|
+
const staleClaims = myClaimedThreads.filter((instance) => isStale(instance, staleCutoffMs)).slice(0, options.limit);
|
|
627
|
+
const nextReady = listReadyThreads(workspacePath).filter((instance) => !instance.fields.owner).sort(compareThreadsByPriorityThenUpdated).slice(0, options.limit);
|
|
628
|
+
const sections = [
|
|
629
|
+
{
|
|
630
|
+
id: "my_claims",
|
|
631
|
+
title: `Claimed Threads (${actor})`,
|
|
632
|
+
items: myClaimedThreads.slice(0, options.limit).map((instance) => toThreadItem(instance, nowMs))
|
|
633
|
+
},
|
|
634
|
+
{
|
|
635
|
+
id: "my_blockers",
|
|
636
|
+
title: `Blocked Threads (${actor})`,
|
|
637
|
+
items: myBlockedThreads.map((instance) => toThreadItem(instance, nowMs))
|
|
638
|
+
},
|
|
639
|
+
{
|
|
640
|
+
id: "stale_claims",
|
|
641
|
+
title: `Stale Claims (${options.staleHours}h+)`,
|
|
642
|
+
items: staleClaims.map((instance) => toThreadItem(instance, nowMs))
|
|
643
|
+
},
|
|
644
|
+
{
|
|
645
|
+
id: "next_ready",
|
|
646
|
+
title: "Next Ready Threads",
|
|
647
|
+
items: nextReady.map((instance) => toThreadItem(instance, nowMs))
|
|
648
|
+
}
|
|
649
|
+
];
|
|
650
|
+
return finalizeLensResult("my-work", {
|
|
651
|
+
actor,
|
|
652
|
+
options,
|
|
653
|
+
metrics: {
|
|
654
|
+
myClaims: myClaimedThreads.length,
|
|
655
|
+
blocked: myBlockedThreads.length,
|
|
656
|
+
staleClaims: staleClaims.length,
|
|
657
|
+
nextReady: nextReady.length
|
|
658
|
+
},
|
|
659
|
+
sections
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
function buildTeamRiskLens(workspacePath, options) {
|
|
663
|
+
const nowMs = Date.now();
|
|
664
|
+
const staleCutoffMs = nowMs - options.staleHours * 60 * 60 * 1e3;
|
|
665
|
+
const lookbackCutoffMs = nowMs - options.lookbackHours * 60 * 60 * 1e3;
|
|
666
|
+
const threads = list(workspacePath, "thread");
|
|
667
|
+
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);
|
|
668
|
+
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);
|
|
669
|
+
const failedRuns = listRuns(workspacePath, { status: "failed" }).filter((run) => parseTimestamp(run.updatedAt) >= lookbackCutoffMs).slice(0, options.limit);
|
|
670
|
+
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);
|
|
671
|
+
const sections = [
|
|
672
|
+
{
|
|
673
|
+
id: "blocked_high_priority_threads",
|
|
674
|
+
title: "High Priority Blocked Threads",
|
|
675
|
+
items: blockedHighPriority.map((instance) => toThreadItem(instance, nowMs))
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
id: "stale_active_claims",
|
|
679
|
+
title: `Stale Active Claims (${options.staleHours}h+)`,
|
|
680
|
+
items: staleActiveClaims.map((entry) => ({
|
|
681
|
+
...toThreadItem(entry.instance, nowMs),
|
|
682
|
+
owner: entry.owner
|
|
683
|
+
}))
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
id: "failed_runs",
|
|
687
|
+
title: `Failed Runs (${options.lookbackHours}h window)`,
|
|
688
|
+
items: failedRuns.map(toRunItem)
|
|
689
|
+
},
|
|
690
|
+
{
|
|
691
|
+
id: "active_high_severity_incidents",
|
|
692
|
+
title: "Active High-Severity Incidents",
|
|
693
|
+
items: highSeverityIncidents.map((incident) => toIncidentItem(incident, nowMs))
|
|
694
|
+
}
|
|
695
|
+
];
|
|
696
|
+
return finalizeLensResult("team-risk", {
|
|
697
|
+
actor: options.actor,
|
|
698
|
+
options,
|
|
699
|
+
metrics: {
|
|
700
|
+
blockedHighPriority: blockedHighPriority.length,
|
|
701
|
+
staleActiveClaims: staleActiveClaims.length,
|
|
702
|
+
failedRuns: failedRuns.length,
|
|
703
|
+
activeHighSeverityIncidents: highSeverityIncidents.length
|
|
704
|
+
},
|
|
705
|
+
sections
|
|
706
|
+
});
|
|
707
|
+
}
|
|
708
|
+
function buildCustomerHealthLens(workspacePath, options) {
|
|
709
|
+
const nowMs = Date.now();
|
|
710
|
+
const customerThreads = list(workspacePath, "thread").filter(isCustomerLinked).sort(compareThreadsByPriorityThenUpdated);
|
|
711
|
+
const activeCustomerThreads = customerThreads.filter((instance) => ["open", "active"].includes(String(instance.fields.status ?? ""))).slice(0, options.limit);
|
|
712
|
+
const blockedCustomerThreads = customerThreads.filter((instance) => String(instance.fields.status ?? "") === "blocked").slice(0, options.limit);
|
|
713
|
+
const customerIncidents = list(workspacePath, "incident").filter((incident) => String(incident.fields.status ?? "") === "active").filter(isCustomerLinked).slice(0, options.limit);
|
|
714
|
+
const clients = list(workspacePath, "client").slice(0, options.limit);
|
|
715
|
+
const sections = [
|
|
716
|
+
{
|
|
717
|
+
id: "active_customer_threads",
|
|
718
|
+
title: "Active Customer Threads",
|
|
719
|
+
items: activeCustomerThreads.map((instance) => toThreadItem(instance, nowMs))
|
|
720
|
+
},
|
|
721
|
+
{
|
|
722
|
+
id: "blocked_customer_threads",
|
|
723
|
+
title: "Blocked Customer Threads",
|
|
724
|
+
items: blockedCustomerThreads.map((instance) => toThreadItem(instance, nowMs))
|
|
725
|
+
},
|
|
726
|
+
{
|
|
727
|
+
id: "customer_incidents",
|
|
728
|
+
title: "Customer Incidents",
|
|
729
|
+
items: customerIncidents.map((incident) => toIncidentItem(incident, nowMs))
|
|
730
|
+
},
|
|
731
|
+
{
|
|
732
|
+
id: "client_records",
|
|
733
|
+
title: "Client Records",
|
|
734
|
+
items: clients.map((instance) => ({
|
|
735
|
+
title: String(instance.fields.title ?? instance.path),
|
|
736
|
+
path: instance.path,
|
|
737
|
+
status: stringOrUndefined(instance.fields.status),
|
|
738
|
+
detail: stringOrUndefined(instance.fields.health ?? instance.fields.risk),
|
|
739
|
+
ageHours: ageHours(instance, nowMs)
|
|
740
|
+
}))
|
|
741
|
+
}
|
|
742
|
+
];
|
|
743
|
+
return finalizeLensResult("customer-health", {
|
|
744
|
+
actor: options.actor,
|
|
745
|
+
options,
|
|
746
|
+
metrics: {
|
|
747
|
+
activeCustomerThreads: activeCustomerThreads.length,
|
|
748
|
+
blockedCustomerThreads: blockedCustomerThreads.length,
|
|
749
|
+
customerIncidents: customerIncidents.length,
|
|
750
|
+
clients: clients.length
|
|
751
|
+
},
|
|
752
|
+
sections
|
|
753
|
+
});
|
|
754
|
+
}
|
|
755
|
+
function buildExecBriefLens(workspacePath, options) {
|
|
756
|
+
const nowMs = Date.now();
|
|
757
|
+
const lookbackCutoffMs = nowMs - options.lookbackHours * 60 * 60 * 1e3;
|
|
758
|
+
const threads = list(workspacePath, "thread");
|
|
759
|
+
const topPriorities = threads.filter((instance) => ["open", "active"].includes(String(instance.fields.status ?? ""))).sort(compareThreadsByPriorityThenUpdated).slice(0, options.limit);
|
|
760
|
+
const momentum = threads.filter((instance) => String(instance.fields.status ?? "") === "done").filter((instance) => parseTimestamp(instance.fields.updated) >= lookbackCutoffMs).sort(compareThreadsByPriorityThenUpdated).slice(0, options.limit);
|
|
761
|
+
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);
|
|
762
|
+
const failedRuns = listRuns(workspacePath, { status: "failed" }).filter((run) => parseTimestamp(run.updatedAt) >= lookbackCutoffMs).slice(0, options.limit);
|
|
763
|
+
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);
|
|
764
|
+
const sections = [
|
|
765
|
+
{
|
|
766
|
+
id: "top_priorities",
|
|
767
|
+
title: "Top Priorities",
|
|
768
|
+
items: topPriorities.map((instance) => toThreadItem(instance, nowMs))
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
id: "momentum",
|
|
772
|
+
title: `Momentum (${options.lookbackHours}h completed)`,
|
|
773
|
+
items: momentum.map((instance) => toThreadItem(instance, nowMs))
|
|
774
|
+
},
|
|
775
|
+
{
|
|
776
|
+
id: "key_risks",
|
|
777
|
+
title: "Key Risks",
|
|
778
|
+
items: [
|
|
779
|
+
...blockedHighPriority.map((instance) => toThreadItem(instance, nowMs)),
|
|
780
|
+
...failedRuns.map(toRunItem)
|
|
781
|
+
].slice(0, options.limit)
|
|
782
|
+
},
|
|
783
|
+
{
|
|
784
|
+
id: "recent_decisions",
|
|
785
|
+
title: `Decisions (${options.lookbackHours}h window)`,
|
|
786
|
+
items: decisions.map((instance) => ({
|
|
787
|
+
title: String(instance.fields.title ?? instance.path),
|
|
788
|
+
path: instance.path,
|
|
789
|
+
status: stringOrUndefined(instance.fields.status),
|
|
790
|
+
detail: stringOrUndefined(instance.fields.date),
|
|
791
|
+
ageHours: ageHours(instance, nowMs)
|
|
792
|
+
}))
|
|
793
|
+
}
|
|
794
|
+
];
|
|
795
|
+
return finalizeLensResult("exec-brief", {
|
|
796
|
+
actor: options.actor,
|
|
797
|
+
options,
|
|
798
|
+
metrics: {
|
|
799
|
+
topPriorities: topPriorities.length,
|
|
800
|
+
momentumDone: momentum.length,
|
|
801
|
+
risks: blockedHighPriority.length + failedRuns.length,
|
|
802
|
+
decisions: decisions.length
|
|
803
|
+
},
|
|
804
|
+
sections
|
|
805
|
+
});
|
|
806
|
+
}
|
|
807
|
+
function finalizeLensResult(lens, input) {
|
|
808
|
+
const base = {
|
|
809
|
+
lens,
|
|
810
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
811
|
+
actor: input.actor,
|
|
812
|
+
options: {
|
|
813
|
+
lookbackHours: input.options.lookbackHours,
|
|
814
|
+
staleHours: input.options.staleHours,
|
|
815
|
+
limit: input.options.limit
|
|
816
|
+
},
|
|
817
|
+
metrics: input.metrics,
|
|
818
|
+
sections: input.sections
|
|
819
|
+
};
|
|
820
|
+
return {
|
|
821
|
+
...base,
|
|
822
|
+
markdown: renderLensMarkdown(base)
|
|
823
|
+
};
|
|
824
|
+
}
|
|
825
|
+
function toThreadItem(instance, nowMs) {
|
|
826
|
+
return {
|
|
827
|
+
title: String(instance.fields.title ?? instance.path),
|
|
828
|
+
path: instance.path,
|
|
829
|
+
status: stringOrUndefined(instance.fields.status),
|
|
830
|
+
priority: stringOrUndefined(instance.fields.priority),
|
|
831
|
+
owner: stringOrUndefined(instance.fields.owner),
|
|
832
|
+
detail: renderThreadDependencies(instance),
|
|
833
|
+
ageHours: ageHours(instance, nowMs)
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
function toIncidentItem(instance, nowMs) {
|
|
837
|
+
return {
|
|
838
|
+
title: String(instance.fields.title ?? instance.path),
|
|
839
|
+
path: instance.path,
|
|
840
|
+
status: stringOrUndefined(instance.fields.status),
|
|
841
|
+
priority: stringOrUndefined(instance.fields.severity),
|
|
842
|
+
owner: stringOrUndefined(instance.fields.owner),
|
|
843
|
+
ageHours: ageHours(instance, nowMs)
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
function toRunItem(run) {
|
|
847
|
+
return {
|
|
848
|
+
title: run.objective,
|
|
849
|
+
path: `runs/${run.id}.md`,
|
|
850
|
+
status: run.status,
|
|
851
|
+
owner: run.actor,
|
|
852
|
+
detail: run.error ?? run.output,
|
|
853
|
+
ageHours: ageHoursFromIso(run.updatedAt)
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
function renderLensMarkdown(input) {
|
|
857
|
+
const lines = [
|
|
858
|
+
`# Workgraph Context Lens: ${input.lens}`,
|
|
859
|
+
"",
|
|
860
|
+
`Generated: ${input.generatedAt}`,
|
|
861
|
+
...input.actor ? [`Actor: ${input.actor}`] : [],
|
|
862
|
+
`Lookback: ${input.options.lookbackHours}h`,
|
|
863
|
+
`Stale threshold: ${input.options.staleHours}h`,
|
|
864
|
+
`Section limit: ${input.options.limit}`,
|
|
865
|
+
"",
|
|
866
|
+
"## Metrics",
|
|
867
|
+
"",
|
|
868
|
+
...Object.entries(input.metrics).map(([metric, value]) => `- ${metric}: ${value}`),
|
|
869
|
+
""
|
|
870
|
+
];
|
|
871
|
+
for (const section of input.sections) {
|
|
872
|
+
lines.push(`## ${section.title}`);
|
|
873
|
+
lines.push("");
|
|
874
|
+
if (section.items.length === 0) {
|
|
875
|
+
lines.push("- None");
|
|
876
|
+
lines.push("");
|
|
877
|
+
continue;
|
|
878
|
+
}
|
|
879
|
+
for (const item of section.items) {
|
|
880
|
+
lines.push(`- ${renderLensItem(item)}`);
|
|
881
|
+
}
|
|
882
|
+
lines.push("");
|
|
883
|
+
}
|
|
884
|
+
return lines.join("\n");
|
|
885
|
+
}
|
|
886
|
+
function renderLensItem(item) {
|
|
887
|
+
const components = [item.title];
|
|
888
|
+
if (item.path) components.push(`(\`${item.path}\`)`);
|
|
889
|
+
const metadata = [];
|
|
890
|
+
if (item.status) metadata.push(`status=${item.status}`);
|
|
891
|
+
if (item.priority) metadata.push(`priority=${item.priority}`);
|
|
892
|
+
if (item.owner) metadata.push(`owner=${item.owner}`);
|
|
893
|
+
if (typeof item.ageHours === "number") metadata.push(`age=${item.ageHours.toFixed(1)}h`);
|
|
894
|
+
if (metadata.length > 0) components.push(`[${metadata.join(", ")}]`);
|
|
895
|
+
if (item.detail) components.push(`- ${item.detail}`);
|
|
896
|
+
return components.join(" ");
|
|
897
|
+
}
|
|
898
|
+
function resolvePathWithinWorkspace2(workspacePath, outputPath) {
|
|
899
|
+
const base = path5.resolve(workspacePath);
|
|
900
|
+
const resolved = path5.resolve(base, outputPath);
|
|
901
|
+
if (!resolved.startsWith(base + path5.sep) && resolved !== base) {
|
|
902
|
+
throw new Error(`Invalid lens output path: ${outputPath}`);
|
|
903
|
+
}
|
|
904
|
+
return resolved;
|
|
905
|
+
}
|
|
906
|
+
function normalizeLensId(value) {
|
|
907
|
+
const normalized = String(value).trim().toLowerCase().replace(/^lens:\/\//, "");
|
|
908
|
+
if (normalized === "my-work") return "my-work";
|
|
909
|
+
if (normalized === "team-risk") return "team-risk";
|
|
910
|
+
if (normalized === "customer-health") return "customer-health";
|
|
911
|
+
if (normalized === "exec-brief") return "exec-brief";
|
|
912
|
+
const valid = BUILT_IN_LENSES.map((item) => item.id).join(", ");
|
|
913
|
+
throw new Error(`Unknown context lens "${value}". Valid lenses: ${valid}`);
|
|
914
|
+
}
|
|
915
|
+
function normalizeLensOptions(options) {
|
|
916
|
+
return {
|
|
917
|
+
actor: String(options.actor ?? "anonymous").trim() || "anonymous",
|
|
918
|
+
lookbackHours: parsePositiveNumber(options.lookbackHours, "lookbackHours", DEFAULT_LOOKBACK_HOURS),
|
|
919
|
+
staleHours: parsePositiveNumber(options.staleHours, "staleHours", DEFAULT_STALE_HOURS),
|
|
920
|
+
limit: parsePositiveInteger(options.limit, "limit", DEFAULT_LIMIT)
|
|
921
|
+
};
|
|
922
|
+
}
|
|
923
|
+
function parsePositiveNumber(value, fieldName, defaultValue) {
|
|
924
|
+
if (value === void 0 || value === null) return defaultValue;
|
|
925
|
+
const parsed = Number(value);
|
|
926
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
927
|
+
throw new Error(`Invalid ${fieldName}: expected a positive number.`);
|
|
928
|
+
}
|
|
929
|
+
return parsed;
|
|
930
|
+
}
|
|
931
|
+
function parsePositiveInteger(value, fieldName, defaultValue) {
|
|
932
|
+
if (value === void 0 || value === null) return defaultValue;
|
|
933
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
934
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
935
|
+
throw new Error(`Invalid ${fieldName}: expected a positive integer.`);
|
|
936
|
+
}
|
|
937
|
+
return parsed;
|
|
938
|
+
}
|
|
939
|
+
function compareThreadsByPriorityThenUpdated(a, b) {
|
|
940
|
+
const priorityDelta = rankPriority(a) - rankPriority(b);
|
|
941
|
+
if (priorityDelta !== 0) return priorityDelta;
|
|
942
|
+
return parseTimestamp(b.fields.updated) - parseTimestamp(a.fields.updated);
|
|
943
|
+
}
|
|
944
|
+
function rankPriority(instance) {
|
|
945
|
+
const priority = normalizePriority(instance.fields.priority);
|
|
946
|
+
return PRIORITY_ORDER[priority] ?? PRIORITY_ORDER.medium;
|
|
947
|
+
}
|
|
948
|
+
function normalizePriority(value) {
|
|
949
|
+
return String(value ?? "medium").trim().toLowerCase();
|
|
950
|
+
}
|
|
951
|
+
function normalizeSeverity(value) {
|
|
952
|
+
return String(value ?? "sev4").trim().toLowerCase();
|
|
953
|
+
}
|
|
954
|
+
function isStale(instance, staleCutoffMs) {
|
|
955
|
+
const updatedAt = parseTimestamp(instance.fields.updated ?? instance.fields.created);
|
|
956
|
+
if (!Number.isFinite(updatedAt)) return false;
|
|
957
|
+
return updatedAt <= staleCutoffMs;
|
|
958
|
+
}
|
|
959
|
+
function parseTimestamp(value) {
|
|
960
|
+
const parsed = Date.parse(String(value ?? ""));
|
|
961
|
+
return Number.isFinite(parsed) ? parsed : Number.NEGATIVE_INFINITY;
|
|
962
|
+
}
|
|
963
|
+
function ageHours(instance, nowMs) {
|
|
964
|
+
const updatedAt = parseTimestamp(instance.fields.updated ?? instance.fields.created);
|
|
965
|
+
if (!Number.isFinite(updatedAt)) return void 0;
|
|
966
|
+
return Math.max(0, (nowMs - updatedAt) / (60 * 60 * 1e3));
|
|
967
|
+
}
|
|
968
|
+
function ageHoursFromIso(value) {
|
|
969
|
+
const nowMs = Date.now();
|
|
970
|
+
const ts = parseTimestamp(value);
|
|
971
|
+
if (!Number.isFinite(ts)) return void 0;
|
|
972
|
+
return Math.max(0, (nowMs - ts) / (60 * 60 * 1e3));
|
|
973
|
+
}
|
|
974
|
+
function renderThreadDependencies(instance) {
|
|
975
|
+
const deps = instance.fields.deps;
|
|
976
|
+
if (!Array.isArray(deps) || deps.length === 0) return void 0;
|
|
977
|
+
const visible = deps.slice(0, 3).map((value) => String(value));
|
|
978
|
+
const suffix = deps.length > visible.length ? ` +${deps.length - visible.length} more` : "";
|
|
979
|
+
return `deps: ${visible.join(", ")}${suffix}`;
|
|
980
|
+
}
|
|
981
|
+
function isCustomerLinked(instance) {
|
|
982
|
+
const tags = normalizeTags(instance.fields.tags);
|
|
983
|
+
if (tags.includes("customer") || tags.includes("client")) return true;
|
|
984
|
+
const candidateFields = ["client", "client_ref", "customer", "customer_ref", "account", "account_ref"];
|
|
985
|
+
return candidateFields.some((key) => {
|
|
986
|
+
const value = instance.fields[key];
|
|
987
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
function normalizeTags(value) {
|
|
991
|
+
if (!Array.isArray(value)) return [];
|
|
992
|
+
return value.map((item) => String(item).trim().toLowerCase()).filter(Boolean);
|
|
993
|
+
}
|
|
994
|
+
function stringOrUndefined(value) {
|
|
995
|
+
if (value === void 0 || value === null) return void 0;
|
|
996
|
+
const normalized = String(value).trim();
|
|
997
|
+
return normalized.length > 0 ? normalized : void 0;
|
|
998
|
+
}
|
|
999
|
+
function assertNever(value) {
|
|
1000
|
+
throw new Error(`Unhandled lens variant: ${String(value)}`);
|
|
1001
|
+
}
|
|
1002
|
+
|
|
535
1003
|
// src/board.ts
|
|
536
1004
|
var board_exports = {};
|
|
537
1005
|
__export(board_exports, {
|
|
538
1006
|
generateKanbanBoard: () => generateKanbanBoard,
|
|
539
1007
|
syncKanbanBoard: () => syncKanbanBoard
|
|
540
1008
|
});
|
|
541
|
-
import
|
|
542
|
-
import
|
|
1009
|
+
import fs6 from "fs";
|
|
1010
|
+
import path6 from "path";
|
|
543
1011
|
function generateKanbanBoard(workspacePath, options = {}) {
|
|
544
1012
|
const threads = list(workspacePath, "thread");
|
|
545
1013
|
const grouped = groupThreads(threads);
|
|
@@ -555,12 +1023,12 @@ function generateKanbanBoard(workspacePath, options = {}) {
|
|
|
555
1023
|
}
|
|
556
1024
|
const content = renderKanbanMarkdown(lanes);
|
|
557
1025
|
const relOutputPath = options.outputPath ?? "ops/Workgraph Board.md";
|
|
558
|
-
const absOutputPath =
|
|
559
|
-
const parentDir =
|
|
560
|
-
if (!
|
|
561
|
-
|
|
1026
|
+
const absOutputPath = resolvePathWithinWorkspace3(workspacePath, relOutputPath);
|
|
1027
|
+
const parentDir = path6.dirname(absOutputPath);
|
|
1028
|
+
if (!fs6.existsSync(parentDir)) fs6.mkdirSync(parentDir, { recursive: true });
|
|
1029
|
+
fs6.writeFileSync(absOutputPath, content, "utf-8");
|
|
562
1030
|
return {
|
|
563
|
-
outputPath:
|
|
1031
|
+
outputPath: path6.relative(workspacePath, absOutputPath).replace(/\\/g, "/"),
|
|
564
1032
|
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
565
1033
|
counts: {
|
|
566
1034
|
backlog: grouped.open.length,
|
|
@@ -658,10 +1126,10 @@ function renderKanbanMarkdown(lanes) {
|
|
|
658
1126
|
lines.push("");
|
|
659
1127
|
return lines.join("\n");
|
|
660
1128
|
}
|
|
661
|
-
function
|
|
662
|
-
const base =
|
|
663
|
-
const resolved =
|
|
664
|
-
if (!resolved.startsWith(base +
|
|
1129
|
+
function resolvePathWithinWorkspace3(workspacePath, outputPath) {
|
|
1130
|
+
const base = path6.resolve(workspacePath);
|
|
1131
|
+
const resolved = path6.resolve(base, outputPath);
|
|
1132
|
+
if (!resolved.startsWith(base + path6.sep) && resolved !== base) {
|
|
665
1133
|
throw new Error(`Invalid board output path: ${outputPath}`);
|
|
666
1134
|
}
|
|
667
1135
|
return resolved;
|
|
@@ -1157,8 +1625,8 @@ __export(diagnostics_exports, {
|
|
|
1157
1625
|
});
|
|
1158
1626
|
|
|
1159
1627
|
// src/diagnostics/doctor.ts
|
|
1160
|
-
import
|
|
1161
|
-
import
|
|
1628
|
+
import fs7 from "fs";
|
|
1629
|
+
import path8 from "path";
|
|
1162
1630
|
import YAML2 from "yaml";
|
|
1163
1631
|
|
|
1164
1632
|
// src/diagnostics/format.ts
|
|
@@ -1222,7 +1690,7 @@ function inferPrimitiveTypeFromPath(targetPath) {
|
|
|
1222
1690
|
}
|
|
1223
1691
|
|
|
1224
1692
|
// src/diagnostics/primitives.ts
|
|
1225
|
-
import
|
|
1693
|
+
import path7 from "path";
|
|
1226
1694
|
function loadPrimitiveInventory(workspacePath) {
|
|
1227
1695
|
const registry = loadRegistry(workspacePath);
|
|
1228
1696
|
const allPrimitives = queryPrimitives(workspacePath);
|
|
@@ -1240,7 +1708,7 @@ function loadPrimitiveInventory(workspacePath) {
|
|
|
1240
1708
|
const requiredFields = Object.entries(typeDef?.fields ?? {}).filter(([, fieldDef]) => fieldDef.required === true).map(([fieldName]) => fieldName);
|
|
1241
1709
|
const presentCount = requiredFields.filter((fieldName) => hasRequiredValue(instance.fields[fieldName])).length;
|
|
1242
1710
|
const frontmatterCompleteness = requiredFields.length === 0 ? 1 : presentCount / requiredFields.length;
|
|
1243
|
-
const slug =
|
|
1711
|
+
const slug = path7.basename(instance.path, ".md");
|
|
1244
1712
|
return {
|
|
1245
1713
|
...instance,
|
|
1246
1714
|
slug,
|
|
@@ -1582,8 +2050,8 @@ function collectStaleRuns(workspacePath, staleAfterMs, now) {
|
|
|
1582
2050
|
}
|
|
1583
2051
|
function collectPrimitiveRegistryReferenceIssues(workspacePath, inventory) {
|
|
1584
2052
|
const issues = [];
|
|
1585
|
-
const manifestPath =
|
|
1586
|
-
if (!
|
|
2053
|
+
const manifestPath = path8.join(workspacePath, ".workgraph", "primitive-registry.yaml");
|
|
2054
|
+
if (!fs7.existsSync(manifestPath)) {
|
|
1587
2055
|
issues.push({
|
|
1588
2056
|
code: "broken-primitive-registry-reference",
|
|
1589
2057
|
severity: "error",
|
|
@@ -1594,7 +2062,7 @@ function collectPrimitiveRegistryReferenceIssues(workspacePath, inventory) {
|
|
|
1594
2062
|
}
|
|
1595
2063
|
let parsed;
|
|
1596
2064
|
try {
|
|
1597
|
-
parsed = YAML2.parse(
|
|
2065
|
+
parsed = YAML2.parse(fs7.readFileSync(manifestPath, "utf-8"));
|
|
1598
2066
|
} catch (error) {
|
|
1599
2067
|
issues.push({
|
|
1600
2068
|
code: "broken-primitive-registry-reference",
|
|
@@ -1646,7 +2114,7 @@ function collectPrimitiveRegistryReferenceIssues(workspacePath, inventory) {
|
|
|
1646
2114
|
path: ".workgraph/primitive-registry.yaml"
|
|
1647
2115
|
});
|
|
1648
2116
|
}
|
|
1649
|
-
if (!
|
|
2117
|
+
if (!fs7.existsSync(path8.join(workspacePath, directory))) {
|
|
1650
2118
|
issues.push({
|
|
1651
2119
|
code: "broken-primitive-registry-reference",
|
|
1652
2120
|
severity: "error",
|
|
@@ -1681,8 +2149,8 @@ function collectPrimitiveRegistryReferenceIssues(workspacePath, inventory) {
|
|
|
1681
2149
|
function collectEmptyPrimitiveDirectoryIssues(workspacePath, inventory) {
|
|
1682
2150
|
const issues = [];
|
|
1683
2151
|
for (const typeDef of inventory.typeDefs.values()) {
|
|
1684
|
-
const directoryPath =
|
|
1685
|
-
if (!
|
|
2152
|
+
const directoryPath = path8.join(workspacePath, typeDef.directory);
|
|
2153
|
+
if (!fs7.existsSync(directoryPath)) continue;
|
|
1686
2154
|
const markdownCount = listMarkdownFilesRecursive(directoryPath).length;
|
|
1687
2155
|
if (markdownCount > 0) continue;
|
|
1688
2156
|
issues.push({
|
|
@@ -1711,10 +2179,10 @@ function removeOrphanLinks(workspacePath, orphanLinks) {
|
|
|
1711
2179
|
}
|
|
1712
2180
|
let removedLinks = 0;
|
|
1713
2181
|
for (const [sourcePath, tokenSet] of tokensBySource.entries()) {
|
|
1714
|
-
const absPath =
|
|
1715
|
-
if (!
|
|
2182
|
+
const absPath = path8.join(workspacePath, sourcePath);
|
|
2183
|
+
if (!fs7.existsSync(absPath)) continue;
|
|
1716
2184
|
try {
|
|
1717
|
-
const raw =
|
|
2185
|
+
const raw = fs7.readFileSync(absPath, "utf-8");
|
|
1718
2186
|
let fileRemoved = 0;
|
|
1719
2187
|
const updated = raw.replace(/\[\[([^[\]]+)\]\]/g, (token) => {
|
|
1720
2188
|
if (!tokenSet.has(token)) return token;
|
|
@@ -1722,7 +2190,7 @@ function removeOrphanLinks(workspacePath, orphanLinks) {
|
|
|
1722
2190
|
return "";
|
|
1723
2191
|
});
|
|
1724
2192
|
if (fileRemoved === 0) continue;
|
|
1725
|
-
|
|
2193
|
+
fs7.writeFileSync(absPath, updated, "utf-8");
|
|
1726
2194
|
removedLinks += fileRemoved;
|
|
1727
2195
|
filesUpdated.push(sourcePath);
|
|
1728
2196
|
} catch (error) {
|
|
@@ -1787,10 +2255,10 @@ function cancelStaleRuns(workspacePath, staleRuns, actor) {
|
|
|
1787
2255
|
return { cancelled, errors };
|
|
1788
2256
|
}
|
|
1789
2257
|
function readDispatchRunsSnapshot(workspacePath) {
|
|
1790
|
-
const runsPath =
|
|
1791
|
-
if (!
|
|
2258
|
+
const runsPath = path8.join(workspacePath, ".workgraph", "dispatch-runs.json");
|
|
2259
|
+
if (!fs7.existsSync(runsPath)) return [];
|
|
1792
2260
|
try {
|
|
1793
|
-
const parsed = JSON.parse(
|
|
2261
|
+
const parsed = JSON.parse(fs7.readFileSync(runsPath, "utf-8"));
|
|
1794
2262
|
return Array.isArray(parsed.runs) ? parsed.runs : [];
|
|
1795
2263
|
} catch {
|
|
1796
2264
|
return [];
|
|
@@ -1801,9 +2269,9 @@ function listMarkdownFilesRecursive(rootDirectory) {
|
|
|
1801
2269
|
const stack = [rootDirectory];
|
|
1802
2270
|
while (stack.length > 0) {
|
|
1803
2271
|
const current = stack.pop();
|
|
1804
|
-
const entries =
|
|
2272
|
+
const entries = fs7.readdirSync(current, { withFileTypes: true });
|
|
1805
2273
|
for (const entry of entries) {
|
|
1806
|
-
const absPath =
|
|
2274
|
+
const absPath = path8.join(current, entry.name);
|
|
1807
2275
|
if (entry.isDirectory()) {
|
|
1808
2276
|
stack.push(absPath);
|
|
1809
2277
|
continue;
|
|
@@ -1947,7 +2415,7 @@ function toNullableString(value) {
|
|
|
1947
2415
|
}
|
|
1948
2416
|
|
|
1949
2417
|
// src/diagnostics/viz.ts
|
|
1950
|
-
import
|
|
2418
|
+
import path9 from "path";
|
|
1951
2419
|
var TYPE_COLORS = ["cyan", "magenta", "yellow", "green", "blue", "red"];
|
|
1952
2420
|
function visualizeVaultGraph(workspacePath, options = {}) {
|
|
1953
2421
|
const inventory = loadPrimitiveInventory(workspacePath);
|
|
@@ -2087,7 +2555,7 @@ function resolveFocusPath(focusInput, inventory) {
|
|
|
2087
2555
|
const directCandidate = normalized.endsWith(".md") ? normalized : `${normalized}.md`;
|
|
2088
2556
|
if (inventory.byPath.has(normalized)) return normalized;
|
|
2089
2557
|
if (inventory.byPath.has(directCandidate)) return directCandidate;
|
|
2090
|
-
const slug =
|
|
2558
|
+
const slug = path9.basename(normalized, ".md");
|
|
2091
2559
|
const candidates = inventory.slugToPaths.get(slug) ?? [];
|
|
2092
2560
|
if (candidates.length === 1) return candidates[0];
|
|
2093
2561
|
if (candidates.length > 1) {
|
|
@@ -2395,8 +2863,8 @@ __export(autonomy_daemon_exports, {
|
|
|
2395
2863
|
startAutonomyDaemon: () => startAutonomyDaemon,
|
|
2396
2864
|
stopAutonomyDaemon: () => stopAutonomyDaemon
|
|
2397
2865
|
});
|
|
2398
|
-
import
|
|
2399
|
-
import
|
|
2866
|
+
import fs8 from "fs";
|
|
2867
|
+
import path10 from "path";
|
|
2400
2868
|
import { spawn } from "child_process";
|
|
2401
2869
|
var DAEMON_DIR = ".workgraph/daemon";
|
|
2402
2870
|
var AUTONOMY_PID_FILE = "autonomy.pid";
|
|
@@ -2405,29 +2873,29 @@ var AUTONOMY_LOG_FILE = "autonomy.log";
|
|
|
2405
2873
|
var AUTONOMY_META_FILE = "autonomy-process.json";
|
|
2406
2874
|
function startAutonomyDaemon(workspacePath, input) {
|
|
2407
2875
|
const daemonDir = ensureDaemonDir(workspacePath);
|
|
2408
|
-
const pidPath =
|
|
2409
|
-
const heartbeatPath = input.heartbeatPath ?
|
|
2410
|
-
const logPath = input.logPath ?
|
|
2411
|
-
const metaPath =
|
|
2876
|
+
const pidPath = path10.join(daemonDir, AUTONOMY_PID_FILE);
|
|
2877
|
+
const heartbeatPath = input.heartbeatPath ? resolvePathWithinWorkspace4(workspacePath, input.heartbeatPath) : path10.join(daemonDir, AUTONOMY_HEARTBEAT_FILE);
|
|
2878
|
+
const logPath = input.logPath ? resolvePathWithinWorkspace4(workspacePath, input.logPath) : path10.join(daemonDir, AUTONOMY_LOG_FILE);
|
|
2879
|
+
const metaPath = path10.join(daemonDir, AUTONOMY_META_FILE);
|
|
2412
2880
|
const existing = readAutonomyDaemonStatus(workspacePath, { cleanupStalePidFile: true });
|
|
2413
2881
|
if (existing.running) {
|
|
2414
2882
|
throw new Error(`Autonomy daemon already running (pid=${existing.pid}). Stop it before starting a new one.`);
|
|
2415
2883
|
}
|
|
2416
|
-
const logFd =
|
|
2884
|
+
const logFd = fs8.openSync(logPath, "a");
|
|
2417
2885
|
const args = buildAutonomyDaemonArgs(workspacePath, input, heartbeatPath);
|
|
2418
2886
|
const child = spawn(process.execPath, args, {
|
|
2419
2887
|
detached: true,
|
|
2420
2888
|
stdio: ["ignore", logFd, logFd],
|
|
2421
2889
|
env: process.env
|
|
2422
2890
|
});
|
|
2423
|
-
|
|
2891
|
+
fs8.closeSync(logFd);
|
|
2424
2892
|
child.unref();
|
|
2425
2893
|
if (!child.pid) {
|
|
2426
2894
|
throw new Error("Failed to start autonomy daemon: missing child process pid.");
|
|
2427
2895
|
}
|
|
2428
|
-
|
|
2896
|
+
fs8.writeFileSync(pidPath, `${child.pid}
|
|
2429
2897
|
`, "utf-8");
|
|
2430
|
-
|
|
2898
|
+
fs8.writeFileSync(metaPath, JSON.stringify({
|
|
2431
2899
|
startedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2432
2900
|
pid: child.pid,
|
|
2433
2901
|
args,
|
|
@@ -2461,9 +2929,9 @@ async function stopAutonomyDaemon(workspacePath, input = {}) {
|
|
|
2461
2929
|
await waitForProcessExit(pid, 1500);
|
|
2462
2930
|
stopped = !isProcessAlive(pid);
|
|
2463
2931
|
}
|
|
2464
|
-
const pidPath =
|
|
2465
|
-
if (stopped &&
|
|
2466
|
-
|
|
2932
|
+
const pidPath = path10.join(ensureDaemonDir(workspacePath), AUTONOMY_PID_FILE);
|
|
2933
|
+
if (stopped && fs8.existsSync(pidPath)) {
|
|
2934
|
+
fs8.rmSync(pidPath, { force: true });
|
|
2467
2935
|
}
|
|
2468
2936
|
return {
|
|
2469
2937
|
stopped,
|
|
@@ -2474,14 +2942,14 @@ async function stopAutonomyDaemon(workspacePath, input = {}) {
|
|
|
2474
2942
|
}
|
|
2475
2943
|
function readAutonomyDaemonStatus(workspacePath, options = {}) {
|
|
2476
2944
|
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) :
|
|
2945
|
+
const pidPath = path10.join(daemonDir, AUTONOMY_PID_FILE);
|
|
2946
|
+
const meta = readDaemonMeta(path10.join(daemonDir, AUTONOMY_META_FILE));
|
|
2947
|
+
const logPath = meta?.logPath ? String(meta.logPath) : path10.join(daemonDir, AUTONOMY_LOG_FILE);
|
|
2948
|
+
const heartbeatPath = meta?.heartbeatPath ? String(meta.heartbeatPath) : path10.join(daemonDir, AUTONOMY_HEARTBEAT_FILE);
|
|
2481
2949
|
const pid = readPid(pidPath);
|
|
2482
2950
|
const running = pid ? isProcessAlive(pid) : false;
|
|
2483
|
-
if (!running && pid && options.cleanupStalePidFile !== false &&
|
|
2484
|
-
|
|
2951
|
+
if (!running && pid && options.cleanupStalePidFile !== false && fs8.existsSync(pidPath)) {
|
|
2952
|
+
fs8.rmSync(pidPath, { force: true });
|
|
2485
2953
|
}
|
|
2486
2954
|
return {
|
|
2487
2955
|
running,
|
|
@@ -2494,7 +2962,7 @@ function readAutonomyDaemonStatus(workspacePath, options = {}) {
|
|
|
2494
2962
|
}
|
|
2495
2963
|
function buildAutonomyDaemonArgs(workspacePath, input, heartbeatPath) {
|
|
2496
2964
|
const args = [
|
|
2497
|
-
|
|
2965
|
+
path10.resolve(input.cliEntrypointPath),
|
|
2498
2966
|
"autonomy",
|
|
2499
2967
|
"run",
|
|
2500
2968
|
"-w",
|
|
@@ -2552,22 +3020,22 @@ function waitForProcessExit(pid, timeoutMs) {
|
|
|
2552
3020
|
});
|
|
2553
3021
|
}
|
|
2554
3022
|
function ensureDaemonDir(workspacePath) {
|
|
2555
|
-
const daemonDir =
|
|
2556
|
-
if (!
|
|
3023
|
+
const daemonDir = path10.join(workspacePath, DAEMON_DIR);
|
|
3024
|
+
if (!fs8.existsSync(daemonDir)) fs8.mkdirSync(daemonDir, { recursive: true });
|
|
2557
3025
|
return daemonDir;
|
|
2558
3026
|
}
|
|
2559
3027
|
function readPid(pidPath) {
|
|
2560
|
-
if (!
|
|
2561
|
-
const raw =
|
|
3028
|
+
if (!fs8.existsSync(pidPath)) return void 0;
|
|
3029
|
+
const raw = fs8.readFileSync(pidPath, "utf-8").trim();
|
|
2562
3030
|
if (!raw) return void 0;
|
|
2563
3031
|
const parsed = Number(raw);
|
|
2564
3032
|
if (!Number.isInteger(parsed) || parsed <= 0) return void 0;
|
|
2565
3033
|
return parsed;
|
|
2566
3034
|
}
|
|
2567
3035
|
function readHeartbeat(heartbeatPath) {
|
|
2568
|
-
if (!
|
|
3036
|
+
if (!fs8.existsSync(heartbeatPath)) return void 0;
|
|
2569
3037
|
try {
|
|
2570
|
-
const parsed = JSON.parse(
|
|
3038
|
+
const parsed = JSON.parse(fs8.readFileSync(heartbeatPath, "utf-8"));
|
|
2571
3039
|
if (!parsed || typeof parsed !== "object") return void 0;
|
|
2572
3040
|
return parsed;
|
|
2573
3041
|
} catch {
|
|
@@ -2575,9 +3043,9 @@ function readHeartbeat(heartbeatPath) {
|
|
|
2575
3043
|
}
|
|
2576
3044
|
}
|
|
2577
3045
|
function readDaemonMeta(metaPath) {
|
|
2578
|
-
if (!
|
|
3046
|
+
if (!fs8.existsSync(metaPath)) return void 0;
|
|
2579
3047
|
try {
|
|
2580
|
-
const parsed = JSON.parse(
|
|
3048
|
+
const parsed = JSON.parse(fs8.readFileSync(metaPath, "utf-8"));
|
|
2581
3049
|
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return void 0;
|
|
2582
3050
|
return parsed;
|
|
2583
3051
|
} catch {
|
|
@@ -2595,9 +3063,9 @@ function isProcessAlive(pid) {
|
|
|
2595
3063
|
}
|
|
2596
3064
|
function isZombieProcess(pid) {
|
|
2597
3065
|
const statPath = `/proc/${pid}/stat`;
|
|
2598
|
-
if (!
|
|
3066
|
+
if (!fs8.existsSync(statPath)) return false;
|
|
2599
3067
|
try {
|
|
2600
|
-
const stat =
|
|
3068
|
+
const stat = fs8.readFileSync(statPath, "utf-8");
|
|
2601
3069
|
const closingIdx = stat.indexOf(")");
|
|
2602
3070
|
if (closingIdx === -1 || closingIdx + 2 >= stat.length) return false;
|
|
2603
3071
|
const state = stat.slice(closingIdx + 2, closingIdx + 3);
|
|
@@ -2606,10 +3074,10 @@ function isZombieProcess(pid) {
|
|
|
2606
3074
|
return false;
|
|
2607
3075
|
}
|
|
2608
3076
|
}
|
|
2609
|
-
function
|
|
2610
|
-
const base =
|
|
2611
|
-
const resolved =
|
|
2612
|
-
if (!resolved.startsWith(base +
|
|
3077
|
+
function resolvePathWithinWorkspace4(workspacePath, filePath) {
|
|
3078
|
+
const base = path10.resolve(workspacePath);
|
|
3079
|
+
const resolved = path10.resolve(base, filePath);
|
|
3080
|
+
if (!resolved.startsWith(base + path10.sep) && resolved !== base) {
|
|
2613
3081
|
throw new Error(`Invalid path outside workspace: ${filePath}`);
|
|
2614
3082
|
}
|
|
2615
3083
|
return resolved;
|
|
@@ -2624,6 +3092,7 @@ export {
|
|
|
2624
3092
|
workspace_exports,
|
|
2625
3093
|
command_center_exports,
|
|
2626
3094
|
skill_exports,
|
|
3095
|
+
lens_exports,
|
|
2627
3096
|
board_exports,
|
|
2628
3097
|
agent_exports,
|
|
2629
3098
|
onboard_exports,
|
package/dist/cli.js
CHANGED
|
@@ -7,12 +7,13 @@ import {
|
|
|
7
7
|
command_center_exports,
|
|
8
8
|
diagnostics_exports,
|
|
9
9
|
integration_exports,
|
|
10
|
+
lens_exports,
|
|
10
11
|
onboard_exports,
|
|
11
12
|
search_qmd_adapter_exports,
|
|
12
13
|
skill_exports,
|
|
13
14
|
trigger_exports,
|
|
14
15
|
workspace_exports
|
|
15
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-TOFSHG5S.js";
|
|
16
17
|
import {
|
|
17
18
|
autonomy_exports,
|
|
18
19
|
dispatch_exports,
|
|
@@ -28,7 +29,7 @@ import {
|
|
|
28
29
|
thread_audit_exports,
|
|
29
30
|
thread_exports,
|
|
30
31
|
trigger_engine_exports
|
|
31
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-OTSECVE5.js";
|
|
32
33
|
|
|
33
34
|
// src/cli.ts
|
|
34
35
|
import fs from "fs";
|
|
@@ -1053,6 +1054,57 @@ addWorkspaceOption(
|
|
|
1053
1054
|
(result) => [`Captured intake: ${result.intake.path}`]
|
|
1054
1055
|
)
|
|
1055
1056
|
);
|
|
1057
|
+
var lensCmd = program.command("lens").description("Generate deterministic context lenses for situational awareness");
|
|
1058
|
+
addWorkspaceOption(
|
|
1059
|
+
lensCmd.command("list").description("List built-in context lenses").option("--json", "Emit structured JSON output")
|
|
1060
|
+
).action(
|
|
1061
|
+
(opts) => runCommand(
|
|
1062
|
+
opts,
|
|
1063
|
+
() => ({
|
|
1064
|
+
lenses: lens_exports.listContextLenses()
|
|
1065
|
+
}),
|
|
1066
|
+
(result) => result.lenses.map((lens) => `lens://${lens.id} - ${lens.description}`)
|
|
1067
|
+
)
|
|
1068
|
+
);
|
|
1069
|
+
addWorkspaceOption(
|
|
1070
|
+
lensCmd.command("show <lensId>").description("Generate one context lens snapshot").option("-a, --actor <name>", "Actor identity for actor-scoped lenses", DEFAULT_ACTOR).option("--lookback-hours <hours>", "Lookback window in hours", "24").option("--stale-hours <hours>", "Stale threshold in hours", "24").option("--limit <n>", "Maximum items per section", "10").option("-o, --output <path>", "Write lens markdown to workspace-relative output path").option("--json", "Emit structured JSON output")
|
|
1071
|
+
).action(
|
|
1072
|
+
(lensId, opts) => runCommand(
|
|
1073
|
+
opts,
|
|
1074
|
+
() => {
|
|
1075
|
+
const workspacePath = resolveWorkspacePath(opts);
|
|
1076
|
+
const lensOptions = {
|
|
1077
|
+
actor: opts.actor,
|
|
1078
|
+
lookbackHours: parsePositiveNumberOption(opts.lookbackHours, "lookback-hours"),
|
|
1079
|
+
staleHours: parsePositiveNumberOption(opts.staleHours, "stale-hours"),
|
|
1080
|
+
limit: parsePositiveIntegerOption(opts.limit, "limit")
|
|
1081
|
+
};
|
|
1082
|
+
if (opts.output) {
|
|
1083
|
+
return lens_exports.materializeContextLens(workspacePath, lensId, {
|
|
1084
|
+
...lensOptions,
|
|
1085
|
+
outputPath: opts.output
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
return lens_exports.generateContextLens(workspacePath, lensId, lensOptions);
|
|
1089
|
+
},
|
|
1090
|
+
(result) => {
|
|
1091
|
+
const metricSummary = Object.entries(result.metrics).map(([metric, value]) => `${metric}=${value}`).join(" ");
|
|
1092
|
+
const sectionSummary = result.sections.map((section) => `${section.id}:${section.items.length}`).join(" ");
|
|
1093
|
+
const lines = [
|
|
1094
|
+
`Lens: ${result.lens}`,
|
|
1095
|
+
`Generated: ${result.generatedAt}`,
|
|
1096
|
+
...result.actor ? [`Actor: ${result.actor}`] : [],
|
|
1097
|
+
`Metrics: ${metricSummary || "none"}`,
|
|
1098
|
+
`Sections: ${sectionSummary || "none"}`
|
|
1099
|
+
];
|
|
1100
|
+
if (isMaterializedLensResult(result)) {
|
|
1101
|
+
lines.push(`Saved markdown: ${result.outputPath}`);
|
|
1102
|
+
return lines;
|
|
1103
|
+
}
|
|
1104
|
+
return [...lines, "", ...result.markdown.split("\n")];
|
|
1105
|
+
}
|
|
1106
|
+
)
|
|
1107
|
+
);
|
|
1056
1108
|
addWorkspaceOption(
|
|
1057
1109
|
program.command("query").description("Query primitive instances with multi-field filters").option("--type <type>", "Primitive type").option("--status <status>", "Status value").option("--owner <owner>", "Owner/actor value").option("--tag <tag>", "Tag filter").option("--text <text>", "Full-text contains filter").option("--path-includes <text>", "Path substring filter").option("--updated-after <iso>", "Updated at or after").option("--updated-before <iso>", "Updated at or before").option("--created-after <iso>", "Created at or after").option("--created-before <iso>", "Created at or before").option("--limit <n>", "Result limit").option("--offset <n>", "Result offset").option("--json", "Emit structured JSON output")
|
|
1058
1110
|
).action(
|
|
@@ -1952,6 +2004,13 @@ function parsePositiveIntOption(value, name) {
|
|
|
1952
2004
|
}
|
|
1953
2005
|
return parsed;
|
|
1954
2006
|
}
|
|
2007
|
+
function parsePositiveNumberOption(value, optionName) {
|
|
2008
|
+
const parsed = Number(value);
|
|
2009
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
2010
|
+
throw new Error(`Invalid --${optionName}. Expected a positive number.`);
|
|
2011
|
+
}
|
|
2012
|
+
return parsed;
|
|
2013
|
+
}
|
|
1955
2014
|
function parseNonNegativeIntOption(value, name) {
|
|
1956
2015
|
const parsed = Number.parseInt(String(value ?? ""), 10);
|
|
1957
2016
|
if (!Number.isFinite(parsed) || parsed < 0) {
|
|
@@ -1959,6 +2018,16 @@ function parseNonNegativeIntOption(value, name) {
|
|
|
1959
2018
|
}
|
|
1960
2019
|
return parsed;
|
|
1961
2020
|
}
|
|
2021
|
+
function parsePositiveIntegerOption(value, optionName) {
|
|
2022
|
+
const parsed = Number.parseInt(String(value), 10);
|
|
2023
|
+
if (!Number.isInteger(parsed) || parsed <= 0) {
|
|
2024
|
+
throw new Error(`Invalid --${optionName}. Expected a positive integer.`);
|
|
2025
|
+
}
|
|
2026
|
+
return parsed;
|
|
2027
|
+
}
|
|
2028
|
+
function isMaterializedLensResult(value) {
|
|
2029
|
+
return typeof value.outputPath === "string";
|
|
2030
|
+
}
|
|
1962
2031
|
function normalizeRunStatus(status) {
|
|
1963
2032
|
const normalized = String(status).toLowerCase();
|
|
1964
2033
|
if (normalized === "running" || normalized === "succeeded" || normalized === "failed" || normalized === "cancelled") {
|
package/dist/index.d.ts
CHANGED
|
@@ -132,6 +132,51 @@ interface WorkgraphBrief {
|
|
|
132
132
|
nextReadyThreads: PrimitiveInstance[];
|
|
133
133
|
recentActivity: LedgerEntry[];
|
|
134
134
|
}
|
|
135
|
+
type WorkgraphLensId = 'my-work' | 'team-risk' | 'customer-health' | 'exec-brief';
|
|
136
|
+
interface WorkgraphLensDescriptor {
|
|
137
|
+
id: WorkgraphLensId;
|
|
138
|
+
description: string;
|
|
139
|
+
}
|
|
140
|
+
interface WorkgraphLensOptions {
|
|
141
|
+
actor?: string;
|
|
142
|
+
lookbackHours?: number;
|
|
143
|
+
staleHours?: number;
|
|
144
|
+
limit?: number;
|
|
145
|
+
}
|
|
146
|
+
interface WorkgraphLensItem {
|
|
147
|
+
title: string;
|
|
148
|
+
path?: string;
|
|
149
|
+
status?: string;
|
|
150
|
+
priority?: string;
|
|
151
|
+
owner?: string;
|
|
152
|
+
detail?: string;
|
|
153
|
+
ageHours?: number;
|
|
154
|
+
}
|
|
155
|
+
interface WorkgraphLensSection {
|
|
156
|
+
id: string;
|
|
157
|
+
title: string;
|
|
158
|
+
items: WorkgraphLensItem[];
|
|
159
|
+
}
|
|
160
|
+
interface WorkgraphLensResult {
|
|
161
|
+
lens: WorkgraphLensId;
|
|
162
|
+
generatedAt: string;
|
|
163
|
+
actor?: string;
|
|
164
|
+
options: {
|
|
165
|
+
lookbackHours: number;
|
|
166
|
+
staleHours: number;
|
|
167
|
+
limit: number;
|
|
168
|
+
};
|
|
169
|
+
metrics: Record<string, number>;
|
|
170
|
+
sections: WorkgraphLensSection[];
|
|
171
|
+
markdown: string;
|
|
172
|
+
}
|
|
173
|
+
interface WorkgraphMaterializeLensOptions extends WorkgraphLensOptions {
|
|
174
|
+
outputPath: string;
|
|
175
|
+
}
|
|
176
|
+
interface WorkgraphMaterializedLensResult extends WorkgraphLensResult {
|
|
177
|
+
outputPath: string;
|
|
178
|
+
created: boolean;
|
|
179
|
+
}
|
|
135
180
|
interface PolicyParty {
|
|
136
181
|
id: string;
|
|
137
182
|
roles: string[];
|
|
@@ -655,6 +700,21 @@ declare namespace orientation {
|
|
|
655
700
|
export { orientation_brief as brief, orientation_checkpoint as checkpoint, orientation_intake as intake, orientation_statusSnapshot as statusSnapshot };
|
|
656
701
|
}
|
|
657
702
|
|
|
703
|
+
/**
|
|
704
|
+
* Deterministic context lenses for fast, runtime-agnostic orientation.
|
|
705
|
+
*/
|
|
706
|
+
|
|
707
|
+
declare function listContextLenses(): WorkgraphLensDescriptor[];
|
|
708
|
+
declare function generateContextLens(workspacePath: string, lensId: WorkgraphLensId | string, options?: WorkgraphLensOptions): WorkgraphLensResult;
|
|
709
|
+
declare function materializeContextLens(workspacePath: string, lensId: WorkgraphLensId | string, options: WorkgraphMaterializeLensOptions): WorkgraphMaterializedLensResult;
|
|
710
|
+
|
|
711
|
+
declare const lens_generateContextLens: typeof generateContextLens;
|
|
712
|
+
declare const lens_listContextLenses: typeof listContextLenses;
|
|
713
|
+
declare const lens_materializeContextLens: typeof materializeContextLens;
|
|
714
|
+
declare namespace lens {
|
|
715
|
+
export { lens_generateContextLens as generateContextLens, lens_listContextLenses as listContextLenses, lens_materializeContextLens as materializeContextLens };
|
|
716
|
+
}
|
|
717
|
+
|
|
658
718
|
/**
|
|
659
719
|
* Wiki-link graph indexing and graph-intelligence queries.
|
|
660
720
|
*/
|
|
@@ -1720,4 +1780,4 @@ declare class CursorCloudAdapter implements DispatchAdapter {
|
|
|
1720
1780
|
execute(input: DispatchAdapterExecutionInput): Promise<DispatchAdapterExecutionResult>;
|
|
1721
1781
|
}
|
|
1722
1782
|
|
|
1723
|
-
export { type CronField, type CronSchedule, CursorCloudAdapter, type DispatchAdapter, type DispatchAdapterCreateInput, type DispatchAdapterExecutionInput, type DispatchAdapterExecutionResult, type DispatchAdapterLogEntry, type DispatchAdapterRunStatus, type DispatchRun, type FieldDefinition, type InstallSkillIntegrationOptions, type InstallSkillIntegrationResult, type LedgerChainState, type LedgerEntry, type LedgerIndex, type LedgerOp, type PolicyParty, type PolicyRegistry, type PrimitiveInstance, type PrimitiveQueryFilters, type PrimitiveTypeDefinition, type Registry, type RunStatus, type SkillIntegrationProvider, THREAD_STATUS_TRANSITIONS, type ThreadStatus, type WorkgraphBrief, type WorkgraphStatusSnapshot, type WorkgraphWorkspaceConfig, agent, autonomy, autonomyDaemon, bases, board, claimLease, clawdapus, commandCenter, index as diagnostics, dispatch, fetchSkillMarkdownFromUrl, gate, graph, installSkillIntegration, integration, ledger, matchesCronSchedule, nextCronMatch, onboard, orientation, parseCronExpression, policy, query, registry, searchQmdAdapter, skill, store, thread, threadAudit, trigger, triggerEngine, workspace };
|
|
1783
|
+
export { type CronField, type CronSchedule, CursorCloudAdapter, type DispatchAdapter, type DispatchAdapterCreateInput, type DispatchAdapterExecutionInput, type DispatchAdapterExecutionResult, type DispatchAdapterLogEntry, type DispatchAdapterRunStatus, type DispatchRun, type FieldDefinition, type InstallSkillIntegrationOptions, type InstallSkillIntegrationResult, type LedgerChainState, type LedgerEntry, type LedgerIndex, type LedgerOp, type PolicyParty, type PolicyRegistry, type PrimitiveInstance, type PrimitiveQueryFilters, type PrimitiveTypeDefinition, type Registry, type RunStatus, type SkillIntegrationProvider, THREAD_STATUS_TRANSITIONS, type ThreadStatus, type WorkgraphBrief, type WorkgraphLensDescriptor, type WorkgraphLensId, type WorkgraphLensItem, type WorkgraphLensOptions, type WorkgraphLensResult, type WorkgraphLensSection, type WorkgraphMaterializeLensOptions, type WorkgraphMaterializedLensResult, type WorkgraphStatusSnapshot, type WorkgraphWorkspaceConfig, agent, autonomy, autonomyDaemon, bases, board, claimLease, clawdapus, commandCenter, index as diagnostics, dispatch, fetchSkillMarkdownFromUrl, gate, graph, installSkillIntegration, integration, ledger, lens, matchesCronSchedule, nextCronMatch, onboard, orientation, parseCronExpression, policy, query, registry, searchQmdAdapter, skill, store, thread, threadAudit, trigger, triggerEngine, workspace };
|
package/dist/index.js
CHANGED
|
@@ -9,12 +9,13 @@ import {
|
|
|
9
9
|
fetchSkillMarkdownFromUrl,
|
|
10
10
|
installSkillIntegration,
|
|
11
11
|
integration_exports,
|
|
12
|
+
lens_exports,
|
|
12
13
|
onboard_exports,
|
|
13
14
|
search_qmd_adapter_exports,
|
|
14
15
|
skill_exports,
|
|
15
16
|
trigger_exports,
|
|
16
17
|
workspace_exports
|
|
17
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-TOFSHG5S.js";
|
|
18
19
|
import {
|
|
19
20
|
CursorCloudAdapter,
|
|
20
21
|
THREAD_STATUS_TRANSITIONS,
|
|
@@ -36,7 +37,7 @@ import {
|
|
|
36
37
|
thread_audit_exports,
|
|
37
38
|
thread_exports,
|
|
38
39
|
trigger_engine_exports
|
|
39
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-OTSECVE5.js";
|
|
40
41
|
export {
|
|
41
42
|
CursorCloudAdapter,
|
|
42
43
|
THREAD_STATUS_TRANSITIONS,
|
|
@@ -56,6 +57,7 @@ export {
|
|
|
56
57
|
installSkillIntegration,
|
|
57
58
|
integration_exports as integration,
|
|
58
59
|
ledger_exports as ledger,
|
|
60
|
+
lens_exports as lens,
|
|
59
61
|
matchesCronSchedule,
|
|
60
62
|
mcp_server_exports as mcpServer,
|
|
61
63
|
nextCronMatch,
|
package/dist/mcp-server.js
CHANGED
package/package.json
CHANGED