agent-trajectories 0.5.8 → 0.6.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 +18 -10
- package/dist/{chunk-WMJRBQB4.js → chunk-ENWKFNUD.js} +522 -267
- package/dist/chunk-ENWKFNUD.js.map +1 -0
- package/dist/cli/index.js +588 -384
- package/dist/cli/index.js.map +1 -1
- package/dist/{index-C9IcYSNQ.d.ts → index-DEr3Rs32.d.ts} +67 -41
- package/dist/index.d.ts +2 -2
- package/dist/index.js +7 -1
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js +1 -1
- package/package.json +3 -1
- package/dist/chunk-WMJRBQB4.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -404,17 +404,23 @@ function abandonTrajectory(trajectory, reason) {
|
|
|
404
404
|
}
|
|
405
405
|
|
|
406
406
|
// src/storage/file.ts
|
|
407
|
-
import { randomUUID } from "crypto";
|
|
408
407
|
import { existsSync } from "fs";
|
|
409
408
|
import {
|
|
410
409
|
mkdir,
|
|
411
410
|
readFile,
|
|
412
411
|
readdir,
|
|
413
412
|
rename,
|
|
414
|
-
|
|
413
|
+
rm,
|
|
415
414
|
writeFile
|
|
416
415
|
} from "fs/promises";
|
|
417
|
-
import {
|
|
416
|
+
import {
|
|
417
|
+
basename,
|
|
418
|
+
dirname,
|
|
419
|
+
isAbsolute,
|
|
420
|
+
join,
|
|
421
|
+
relative,
|
|
422
|
+
resolve
|
|
423
|
+
} from "path";
|
|
418
424
|
|
|
419
425
|
// src/export/markdown.ts
|
|
420
426
|
function exportToMarkdown(trajectory) {
|
|
@@ -577,6 +583,18 @@ function extractDecisions(trajectory) {
|
|
|
577
583
|
}
|
|
578
584
|
|
|
579
585
|
// src/storage/file.ts
|
|
586
|
+
var TRAJECTORY_FILE = "trajectory.json";
|
|
587
|
+
var SUMMARY_FILE = "summary.md";
|
|
588
|
+
var COMPACTION_FILE = "compaction.json";
|
|
589
|
+
var LEGACY_COMPACTION_SUFFIX = ".compaction.json";
|
|
590
|
+
var DEFAULT_TRAJECTORY_DATA_DIR = join(
|
|
591
|
+
".agentworkforce",
|
|
592
|
+
"trajectories"
|
|
593
|
+
);
|
|
594
|
+
var LEGACY_TRAJECTORY_DATA_DIR = ".trajectories";
|
|
595
|
+
function getDefaultTrajectoryDataDir(baseDir = process.cwd()) {
|
|
596
|
+
return join(baseDir, DEFAULT_TRAJECTORY_DATA_DIR);
|
|
597
|
+
}
|
|
580
598
|
function expandPath(path2) {
|
|
581
599
|
if (path2.startsWith("~")) {
|
|
582
600
|
return join(process.env.HOME ?? "", path2.slice(1));
|
|
@@ -592,7 +610,7 @@ function getSearchPaths() {
|
|
|
592
610
|
if (dataDir) {
|
|
593
611
|
return [expandPath(dataDir)];
|
|
594
612
|
}
|
|
595
|
-
return [
|
|
613
|
+
return [getDefaultTrajectoryDataDir()];
|
|
596
614
|
}
|
|
597
615
|
function describeReadFailure(reason, error) {
|
|
598
616
|
if (reason === "schema_violation" && error && typeof error === "object" && "issues" in error) {
|
|
@@ -608,64 +626,60 @@ function describeReadFailure(reason, error) {
|
|
|
608
626
|
if (error instanceof Error) return error.message;
|
|
609
627
|
return String(error);
|
|
610
628
|
}
|
|
611
|
-
var indexLocks = /* @__PURE__ */ new Map();
|
|
612
|
-
function withIndexLock(path2, task) {
|
|
613
|
-
const prev = indexLocks.get(path2) ?? Promise.resolve();
|
|
614
|
-
const next = prev.then(task, task);
|
|
615
|
-
indexLocks.set(
|
|
616
|
-
path2,
|
|
617
|
-
next.catch(() => void 0)
|
|
618
|
-
);
|
|
619
|
-
return next;
|
|
620
|
-
}
|
|
621
629
|
var FileStorage = class {
|
|
622
630
|
baseDir;
|
|
623
631
|
trajectoriesDir;
|
|
624
632
|
activeDir;
|
|
625
633
|
completedDir;
|
|
626
|
-
indexPath;
|
|
627
634
|
lastReconcileSummary;
|
|
635
|
+
shouldMigrateLegacyDefault = false;
|
|
628
636
|
constructor(baseDir) {
|
|
629
637
|
this.baseDir = baseDir ?? process.cwd();
|
|
630
638
|
const dataDir = process.env.TRAJECTORIES_DATA_DIR;
|
|
631
639
|
if (dataDir) {
|
|
632
640
|
this.trajectoriesDir = expandPath(dataDir);
|
|
633
641
|
} else {
|
|
634
|
-
this.trajectoriesDir =
|
|
642
|
+
this.trajectoriesDir = getDefaultTrajectoryDataDir(this.baseDir);
|
|
643
|
+
this.shouldMigrateLegacyDefault = true;
|
|
635
644
|
}
|
|
636
645
|
this.activeDir = join(this.trajectoriesDir, "active");
|
|
637
646
|
this.completedDir = join(this.trajectoriesDir, "completed");
|
|
638
|
-
this.indexPath = join(this.trajectoriesDir, "index.json");
|
|
639
647
|
}
|
|
640
648
|
/**
|
|
641
649
|
* Initialize storage directories
|
|
642
650
|
*/
|
|
643
651
|
async initialize() {
|
|
652
|
+
await this.migrateLegacyDefaultDir();
|
|
644
653
|
await mkdir(this.trajectoriesDir, { recursive: true });
|
|
645
654
|
await mkdir(this.activeDir, { recursive: true });
|
|
646
655
|
await mkdir(this.completedDir, { recursive: true });
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
if (!existsSync(this.indexPath)) {
|
|
650
|
-
await this.saveIndex(this.emptyIndex());
|
|
651
|
-
}
|
|
652
|
-
});
|
|
653
|
-
}
|
|
656
|
+
await this.migrateLegacyIndexCompactionMarkers();
|
|
657
|
+
await rm(join(this.trajectoriesDir, "index.json"), { force: true });
|
|
654
658
|
await this.reconcileIndex();
|
|
655
659
|
}
|
|
660
|
+
async migrateLegacyDefaultDir() {
|
|
661
|
+
if (!this.shouldMigrateLegacyDefault) {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
const legacyDir = join(this.baseDir, LEGACY_TRAJECTORY_DATA_DIR);
|
|
665
|
+
if (!existsSync(legacyDir) || existsSync(this.trajectoriesDir)) {
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
await mkdir(dirname(this.trajectoriesDir), { recursive: true });
|
|
669
|
+
await rename(legacyDir, this.trajectoriesDir);
|
|
670
|
+
}
|
|
656
671
|
/**
|
|
657
|
-
* Scan active/ and completed/ recursively and
|
|
658
|
-
*
|
|
659
|
-
* only adds, never removes.
|
|
672
|
+
* Scan active/ and completed/ recursively and report trajectory files
|
|
673
|
+
* that can be loaded plus files that should be surfaced by doctor.
|
|
660
674
|
*
|
|
661
675
|
* Handles three on-disk layouts in completed/:
|
|
662
676
|
* - flat: completed/{id}.json (legacy workforce data)
|
|
663
|
-
* - monthly: completed/YYYY-MM/{id}.json (
|
|
677
|
+
* - monthly: completed/YYYY-MM/{id}.json (legacy monthly layout)
|
|
678
|
+
* - directory: completed/YYYY-MM/{id}/trajectory.json (current layout)
|
|
664
679
|
* - nested: completed/.../{id}.json (defensive — any depth)
|
|
665
680
|
*
|
|
666
|
-
*
|
|
667
|
-
*
|
|
668
|
-
* added.
|
|
681
|
+
* The method name is kept for callers such as `trail doctor`, but no
|
|
682
|
+
* shared index file is written.
|
|
669
683
|
*/
|
|
670
684
|
async reconcileIndex() {
|
|
671
685
|
const summary = {
|
|
@@ -677,58 +691,29 @@ var FileStorage = class {
|
|
|
677
691
|
skippedIoError: 0,
|
|
678
692
|
failures: []
|
|
679
693
|
};
|
|
680
|
-
await
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
const
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
}
|
|
690
|
-
|
|
691
|
-
if (error.code !== "ENOENT") throw error;
|
|
692
|
-
}
|
|
693
|
-
await this.walkJsonFilesInto(this.completedDir, discovered);
|
|
694
|
-
for (const filePath of discovered) {
|
|
695
|
-
summary.scanned += 1;
|
|
696
|
-
const result = await this.readTrajectoryFile(filePath);
|
|
697
|
-
if (!result.ok) {
|
|
698
|
-
if (result.reason === "malformed_json") {
|
|
699
|
-
summary.skippedMalformedJson += 1;
|
|
700
|
-
} else if (result.reason === "schema_violation") {
|
|
701
|
-
summary.skippedSchemaViolation += 1;
|
|
702
|
-
} else {
|
|
703
|
-
summary.skippedIoError += 1;
|
|
704
|
-
}
|
|
705
|
-
summary.failures.push({
|
|
706
|
-
path: result.path,
|
|
707
|
-
reason: result.reason,
|
|
708
|
-
message: describeReadFailure(result.reason, result.error)
|
|
709
|
-
});
|
|
710
|
-
continue;
|
|
711
|
-
}
|
|
712
|
-
const trajectory = result.trajectory;
|
|
713
|
-
if (index.trajectories[trajectory.id]) {
|
|
714
|
-
summary.alreadyIndexed += 1;
|
|
715
|
-
continue;
|
|
694
|
+
const discovered = await this.listTrajectoryFiles();
|
|
695
|
+
for (const filePath of discovered) {
|
|
696
|
+
summary.scanned += 1;
|
|
697
|
+
const result = await this.readTrajectoryFile(filePath);
|
|
698
|
+
if (!result.ok) {
|
|
699
|
+
if (result.reason === "malformed_json") {
|
|
700
|
+
summary.skippedMalformedJson += 1;
|
|
701
|
+
} else if (result.reason === "schema_violation") {
|
|
702
|
+
summary.skippedSchemaViolation += 1;
|
|
703
|
+
} else {
|
|
704
|
+
summary.skippedIoError += 1;
|
|
716
705
|
}
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
};
|
|
724
|
-
summary.added += 1;
|
|
725
|
-
}
|
|
726
|
-
if (Object.keys(index.trajectories).length !== before) {
|
|
727
|
-
await this.saveIndex(index);
|
|
706
|
+
summary.failures.push({
|
|
707
|
+
path: result.path,
|
|
708
|
+
reason: result.reason,
|
|
709
|
+
message: describeReadFailure(result.reason, result.error)
|
|
710
|
+
});
|
|
711
|
+
continue;
|
|
728
712
|
}
|
|
729
|
-
|
|
713
|
+
summary.added += 1;
|
|
714
|
+
}
|
|
730
715
|
const hadSkips = summary.skippedMalformedJson + summary.skippedSchemaViolation + summary.skippedIoError > 0;
|
|
731
|
-
if (
|
|
716
|
+
if (hadSkips) {
|
|
732
717
|
const parts = [`reconciled ${summary.added}/${summary.scanned}`];
|
|
733
718
|
if (summary.skippedMalformedJson > 0) {
|
|
734
719
|
parts.push(`malformed: ${summary.skippedMalformedJson}`);
|
|
@@ -753,7 +738,7 @@ var FileStorage = class {
|
|
|
753
738
|
return this.lastReconcileSummary;
|
|
754
739
|
}
|
|
755
740
|
/**
|
|
756
|
-
* Move trajectory files that fail to load into `.trajectories/invalid/`
|
|
741
|
+
* Move trajectory files that fail to load into `.agentworkforce/trajectories/invalid/`
|
|
757
742
|
* so reconcile no longer scans them. Only quarantines parse and schema
|
|
758
743
|
* failures — transient io_error failures are left in place because the
|
|
759
744
|
* file may load fine on the next attempt.
|
|
@@ -810,7 +795,7 @@ var FileStorage = class {
|
|
|
810
795
|
return dest;
|
|
811
796
|
}
|
|
812
797
|
/**
|
|
813
|
-
* Recursively collect
|
|
798
|
+
* Recursively collect trajectory JSON file paths under `dir` into `out`.
|
|
814
799
|
* Silently treats a missing directory as empty.
|
|
815
800
|
*/
|
|
816
801
|
async walkJsonFilesInto(dir, out) {
|
|
@@ -825,7 +810,7 @@ var FileStorage = class {
|
|
|
825
810
|
const entryPath = join(dir, entry.name);
|
|
826
811
|
if (entry.isDirectory()) {
|
|
827
812
|
await this.walkJsonFilesInto(entryPath, out);
|
|
828
|
-
} else if (entry.isFile() && entry.name
|
|
813
|
+
} else if (entry.isFile() && isTrajectoryJsonFile(entry.name)) {
|
|
829
814
|
out.push(entryPath);
|
|
830
815
|
}
|
|
831
816
|
}
|
|
@@ -849,56 +834,43 @@ var FileStorage = class {
|
|
|
849
834
|
}
|
|
850
835
|
const trajectory = validation.data;
|
|
851
836
|
const isCompleted = trajectory.status === "completed" || trajectory.status === "abandoned";
|
|
852
|
-
|
|
837
|
+
const existingPaths = await this.findTrajectoryFilePaths(trajectory.id);
|
|
838
|
+
let trajectoryDir;
|
|
853
839
|
if (isCompleted) {
|
|
854
840
|
const date = new Date(trajectory.completedAt ?? trajectory.startedAt);
|
|
855
841
|
const monthDir = join(
|
|
856
842
|
this.completedDir,
|
|
857
843
|
`${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, "0")}`
|
|
858
844
|
);
|
|
859
|
-
|
|
860
|
-
filePath = join(monthDir, `${trajectory.id}.json`);
|
|
861
|
-
const activePath = join(this.activeDir, `${trajectory.id}.json`);
|
|
862
|
-
if (existsSync(activePath)) {
|
|
863
|
-
await unlink(activePath);
|
|
864
|
-
}
|
|
865
|
-
const mdPath = join(monthDir, `${trajectory.id}.md`);
|
|
866
|
-
const markdown = exportToMarkdown(trajectory);
|
|
867
|
-
await writeFile(mdPath, markdown, "utf-8");
|
|
845
|
+
trajectoryDir = join(monthDir, trajectory.id);
|
|
868
846
|
} else {
|
|
869
|
-
|
|
847
|
+
trajectoryDir = join(this.activeDir, trajectory.id);
|
|
848
|
+
}
|
|
849
|
+
const filePath = join(trajectoryDir, TRAJECTORY_FILE);
|
|
850
|
+
await this.removeTrajectoryFiles(existingPaths, filePath);
|
|
851
|
+
await mkdir(trajectoryDir, { recursive: true });
|
|
852
|
+
if (isCompleted) {
|
|
853
|
+
const markdown = exportToMarkdown(trajectory);
|
|
854
|
+
await writeFile(join(trajectoryDir, SUMMARY_FILE), markdown, "utf-8");
|
|
870
855
|
}
|
|
871
856
|
await writeFile(filePath, JSON.stringify(trajectory, null, 2), "utf-8");
|
|
872
|
-
await this.updateIndex(trajectory, filePath);
|
|
873
857
|
}
|
|
874
858
|
/**
|
|
875
859
|
* Get a trajectory by ID
|
|
876
860
|
*/
|
|
877
861
|
async get(id) {
|
|
878
|
-
const
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
const entry = index.trajectories[id];
|
|
884
|
-
if (entry?.path && existsSync(entry.path)) {
|
|
885
|
-
return this.readTrajectoryOrNull(entry.path);
|
|
886
|
-
}
|
|
887
|
-
try {
|
|
888
|
-
const flatPath = join(this.completedDir, `${id}.json`);
|
|
889
|
-
if (existsSync(flatPath)) {
|
|
890
|
-
return this.readTrajectoryOrNull(flatPath);
|
|
891
|
-
}
|
|
892
|
-
const months = await readdir(this.completedDir);
|
|
893
|
-
for (const month of months) {
|
|
894
|
-
const filePath = join(this.completedDir, month, `${id}.json`);
|
|
895
|
-
if (existsSync(filePath)) {
|
|
896
|
-
return this.readTrajectoryOrNull(filePath);
|
|
897
|
-
}
|
|
862
|
+
for (const filePath of this.getActiveCandidatePaths(id)) {
|
|
863
|
+
if (!existsSync(filePath)) continue;
|
|
864
|
+
const trajectory = await this.readTrajectoryOrNull(filePath);
|
|
865
|
+
if (trajectory?.id === id) {
|
|
866
|
+
return trajectory;
|
|
898
867
|
}
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
|
|
868
|
+
}
|
|
869
|
+
const paths = await this.findTrajectoryFilePaths(id);
|
|
870
|
+
for (const filePath of paths) {
|
|
871
|
+
const trajectory = await this.readTrajectoryOrNull(filePath);
|
|
872
|
+
if (trajectory?.id === id) {
|
|
873
|
+
return trajectory;
|
|
902
874
|
}
|
|
903
875
|
}
|
|
904
876
|
return null;
|
|
@@ -907,107 +879,61 @@ var FileStorage = class {
|
|
|
907
879
|
* Get the currently active trajectory
|
|
908
880
|
*/
|
|
909
881
|
async getActive() {
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
913
|
-
if (jsonFiles.length === 0) {
|
|
914
|
-
return null;
|
|
915
|
-
}
|
|
916
|
-
let mostRecent = null;
|
|
917
|
-
let mostRecentTime = 0;
|
|
918
|
-
for (const file of jsonFiles) {
|
|
919
|
-
const trajectory = await this.readTrajectoryOrNull(
|
|
920
|
-
join(this.activeDir, file)
|
|
921
|
-
);
|
|
922
|
-
if (trajectory) {
|
|
923
|
-
const startTime = new Date(trajectory.startedAt).getTime();
|
|
924
|
-
if (startTime > mostRecentTime) {
|
|
925
|
-
mostRecentTime = startTime;
|
|
926
|
-
mostRecent = trajectory;
|
|
927
|
-
}
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
return mostRecent;
|
|
931
|
-
} catch (error) {
|
|
932
|
-
if (error.code === "ENOENT") {
|
|
933
|
-
return null;
|
|
934
|
-
}
|
|
935
|
-
console.error("Error reading active trajectories:", error);
|
|
882
|
+
const activeFiles = await this.collectTrajectoryFiles(this.activeDir);
|
|
883
|
+
if (activeFiles.length === 0) {
|
|
936
884
|
return null;
|
|
937
885
|
}
|
|
886
|
+
let mostRecent = null;
|
|
887
|
+
let mostRecentTime = 0;
|
|
888
|
+
for (const filePath of activeFiles) {
|
|
889
|
+
const trajectory = await this.readTrajectoryOrNull(filePath);
|
|
890
|
+
if (trajectory?.status !== "active") continue;
|
|
891
|
+
const startTime = new Date(trajectory.startedAt).getTime();
|
|
892
|
+
if (startTime > mostRecentTime) {
|
|
893
|
+
mostRecentTime = startTime;
|
|
894
|
+
mostRecent = trajectory;
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
return mostRecent;
|
|
938
898
|
}
|
|
939
899
|
/**
|
|
940
900
|
* List trajectories with optional filtering
|
|
941
901
|
*/
|
|
942
902
|
async list(query) {
|
|
943
|
-
|
|
944
|
-
let entries = Object.entries(index.trajectories);
|
|
903
|
+
let trajectories = await this.loadAllTrajectories();
|
|
945
904
|
if (query.status) {
|
|
946
|
-
|
|
905
|
+
trajectories = trajectories.filter((t) => t.status === query.status);
|
|
947
906
|
}
|
|
948
907
|
if (query.since) {
|
|
949
908
|
const sinceTime = new Date(query.since).getTime();
|
|
950
|
-
|
|
951
|
-
(
|
|
909
|
+
trajectories = trajectories.filter(
|
|
910
|
+
(trajectory) => new Date(trajectory.startedAt).getTime() >= sinceTime
|
|
952
911
|
);
|
|
953
912
|
}
|
|
954
913
|
if (query.until) {
|
|
955
914
|
const untilTime = new Date(query.until).getTime();
|
|
956
|
-
|
|
957
|
-
(
|
|
915
|
+
trajectories = trajectories.filter(
|
|
916
|
+
(trajectory) => new Date(trajectory.startedAt).getTime() <= untilTime
|
|
958
917
|
);
|
|
959
918
|
}
|
|
960
919
|
const sortBy = query.sortBy ?? "startedAt";
|
|
961
920
|
const sortOrder = query.sortOrder ?? "desc";
|
|
962
|
-
|
|
963
|
-
const aVal = a
|
|
964
|
-
const bVal = b
|
|
921
|
+
trajectories.sort((a, b) => {
|
|
922
|
+
const aVal = this.getSortValue(a, sortBy);
|
|
923
|
+
const bVal = this.getSortValue(b, sortBy);
|
|
965
924
|
const cmp = String(aVal).localeCompare(String(bVal));
|
|
966
925
|
return sortOrder === "asc" ? cmp : -cmp;
|
|
967
926
|
});
|
|
968
927
|
const offset = query.offset ?? 0;
|
|
969
928
|
const limit = query.limit ?? 500;
|
|
970
|
-
|
|
971
|
-
return
|
|
972
|
-
entries.map(async ([id, entry]) => {
|
|
973
|
-
const trajectory = await this.get(id);
|
|
974
|
-
return {
|
|
975
|
-
id,
|
|
976
|
-
title: entry.title,
|
|
977
|
-
status: entry.status,
|
|
978
|
-
startedAt: entry.startedAt,
|
|
979
|
-
completedAt: entry.completedAt,
|
|
980
|
-
confidence: trajectory?.retrospective?.confidence,
|
|
981
|
-
chapterCount: trajectory?.chapters.length ?? 0,
|
|
982
|
-
decisionCount: trajectory?.chapters.reduce(
|
|
983
|
-
(count, chapter) => count + chapter.events.filter((e) => e.type === "decision").length,
|
|
984
|
-
0
|
|
985
|
-
) ?? 0
|
|
986
|
-
};
|
|
987
|
-
})
|
|
988
|
-
);
|
|
929
|
+
trajectories = trajectories.slice(offset, offset + limit);
|
|
930
|
+
return trajectories.map((trajectory) => this.toSummary(trajectory));
|
|
989
931
|
}
|
|
990
932
|
/**
|
|
991
933
|
* Delete a trajectory
|
|
992
934
|
*/
|
|
993
935
|
async delete(id) {
|
|
994
|
-
|
|
995
|
-
if (existsSync(activePath)) {
|
|
996
|
-
await unlink(activePath);
|
|
997
|
-
}
|
|
998
|
-
await withIndexLock(this.indexPath, async () => {
|
|
999
|
-
const index = await this.loadIndex();
|
|
1000
|
-
const entry = index.trajectories[id];
|
|
1001
|
-
if (entry?.path && existsSync(entry.path)) {
|
|
1002
|
-
await unlink(entry.path);
|
|
1003
|
-
const mdPath = entry.path.replace(".json", ".md");
|
|
1004
|
-
if (existsSync(mdPath)) {
|
|
1005
|
-
await unlink(mdPath);
|
|
1006
|
-
}
|
|
1007
|
-
}
|
|
1008
|
-
delete index.trajectories[id];
|
|
1009
|
-
await this.saveIndex(index);
|
|
1010
|
-
});
|
|
936
|
+
await this.deleteWithSummary(id);
|
|
1011
937
|
}
|
|
1012
938
|
/**
|
|
1013
939
|
* Search trajectories by text
|
|
@@ -1040,12 +966,395 @@ var FileStorage = class {
|
|
|
1040
966
|
}
|
|
1041
967
|
return matches;
|
|
1042
968
|
}
|
|
969
|
+
/**
|
|
970
|
+
* Mark a trajectory as compacted without writing to a shared index.
|
|
971
|
+
*/
|
|
972
|
+
async markCompacted(id, compactedInto) {
|
|
973
|
+
const markedIds = await this.markCompactedMany([id], compactedInto);
|
|
974
|
+
return markedIds.has(id);
|
|
975
|
+
}
|
|
976
|
+
/**
|
|
977
|
+
* Mark multiple trajectories as compacted with one filesystem scan.
|
|
978
|
+
*/
|
|
979
|
+
async markCompactedMany(ids, compactedInto) {
|
|
980
|
+
const pathsById = await this.findTrajectoryFilePathsForIds(ids);
|
|
981
|
+
const markedIds = /* @__PURE__ */ new Set();
|
|
982
|
+
const compactedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
983
|
+
const writes = [];
|
|
984
|
+
for (const [id, paths] of pathsById.entries()) {
|
|
985
|
+
if (paths.length === 0) {
|
|
986
|
+
continue;
|
|
987
|
+
}
|
|
988
|
+
markedIds.add(id);
|
|
989
|
+
const marker = {
|
|
990
|
+
trajectoryId: id,
|
|
991
|
+
compactedInto,
|
|
992
|
+
compactedAt
|
|
993
|
+
};
|
|
994
|
+
for (const filePath of paths) {
|
|
995
|
+
writes.push(
|
|
996
|
+
writeFile(
|
|
997
|
+
this.getCompactionMarkerPath(filePath, id),
|
|
998
|
+
JSON.stringify(marker, null, 2),
|
|
999
|
+
"utf-8"
|
|
1000
|
+
)
|
|
1001
|
+
);
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
await Promise.all(writes);
|
|
1005
|
+
return markedIds;
|
|
1006
|
+
}
|
|
1007
|
+
/**
|
|
1008
|
+
* Return trajectory IDs that have a per-trajectory compaction marker.
|
|
1009
|
+
*/
|
|
1010
|
+
async getCompactedTrajectoryIds() {
|
|
1011
|
+
const markerPaths = await this.listCompactionMarkerFiles();
|
|
1012
|
+
const compactedIds = /* @__PURE__ */ new Set();
|
|
1013
|
+
for (const markerPath of markerPaths) {
|
|
1014
|
+
try {
|
|
1015
|
+
const marker = JSON.parse(
|
|
1016
|
+
await readFile(markerPath, "utf-8")
|
|
1017
|
+
);
|
|
1018
|
+
const trajectoryId = typeof marker.trajectoryId === "string" ? marker.trajectoryId : this.getTrajectoryIdFromCompactionMarkerPath(markerPath);
|
|
1019
|
+
if (trajectoryId && typeof marker.compactedInto === "string") {
|
|
1020
|
+
compactedIds.add(trajectoryId);
|
|
1021
|
+
}
|
|
1022
|
+
} catch {
|
|
1023
|
+
}
|
|
1024
|
+
}
|
|
1025
|
+
return compactedIds;
|
|
1026
|
+
}
|
|
1027
|
+
/**
|
|
1028
|
+
* Delete a trajectory and return file counts for CLI reporting.
|
|
1029
|
+
*/
|
|
1030
|
+
async deleteWithSummary(id) {
|
|
1031
|
+
return this.deleteManyWithSummary([id]);
|
|
1032
|
+
}
|
|
1033
|
+
/**
|
|
1034
|
+
* Delete multiple trajectories with one filesystem scan.
|
|
1035
|
+
*/
|
|
1036
|
+
async deleteManyWithSummary(ids) {
|
|
1037
|
+
const summary = {
|
|
1038
|
+
removedTrajectories: 0,
|
|
1039
|
+
deletedJsonFiles: 0,
|
|
1040
|
+
deletedMarkdownFiles: 0,
|
|
1041
|
+
deletedTraceFiles: 0,
|
|
1042
|
+
deletedCompactionFiles: 0
|
|
1043
|
+
};
|
|
1044
|
+
const pathsById = await this.findTrajectoryFilePathsForIds(ids);
|
|
1045
|
+
const deletedPaths = /* @__PURE__ */ new Set();
|
|
1046
|
+
for (const paths of pathsById.values()) {
|
|
1047
|
+
for (const filePath of paths) {
|
|
1048
|
+
if (deletedPaths.has(filePath)) {
|
|
1049
|
+
continue;
|
|
1050
|
+
}
|
|
1051
|
+
deletedPaths.add(filePath);
|
|
1052
|
+
await this.removeTrajectoryFile(filePath, summary);
|
|
1053
|
+
}
|
|
1054
|
+
}
|
|
1055
|
+
return summary;
|
|
1056
|
+
}
|
|
1043
1057
|
/**
|
|
1044
1058
|
* Close storage (no-op for file storage)
|
|
1045
1059
|
*/
|
|
1046
1060
|
async close() {
|
|
1047
1061
|
}
|
|
1048
1062
|
// Private helpers
|
|
1063
|
+
getActiveCandidatePaths(id) {
|
|
1064
|
+
if (!isSafeTrajectoryId(id)) {
|
|
1065
|
+
return [];
|
|
1066
|
+
}
|
|
1067
|
+
return [
|
|
1068
|
+
join(this.activeDir, id, TRAJECTORY_FILE),
|
|
1069
|
+
// Legacy layout from v0.5.x and earlier.
|
|
1070
|
+
join(this.activeDir, `${id}.json`)
|
|
1071
|
+
];
|
|
1072
|
+
}
|
|
1073
|
+
async loadAllTrajectories() {
|
|
1074
|
+
const files = await this.listTrajectoryFiles();
|
|
1075
|
+
const trajectories = /* @__PURE__ */ new Map();
|
|
1076
|
+
for (const filePath of files) {
|
|
1077
|
+
const trajectory = await this.readTrajectoryOrNull(filePath);
|
|
1078
|
+
if (!trajectory) {
|
|
1079
|
+
continue;
|
|
1080
|
+
}
|
|
1081
|
+
const current = trajectories.get(trajectory.id);
|
|
1082
|
+
if (!current || this.isNewerTrajectory(trajectory, current)) {
|
|
1083
|
+
trajectories.set(trajectory.id, trajectory);
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
return Array.from(trajectories.values());
|
|
1087
|
+
}
|
|
1088
|
+
async listTrajectoryFiles() {
|
|
1089
|
+
const [activeFiles, completedFiles] = await Promise.all([
|
|
1090
|
+
this.collectTrajectoryFiles(this.activeDir),
|
|
1091
|
+
this.collectTrajectoryFiles(this.completedDir)
|
|
1092
|
+
]);
|
|
1093
|
+
return [...activeFiles, ...completedFiles];
|
|
1094
|
+
}
|
|
1095
|
+
async collectTrajectoryFiles(dir) {
|
|
1096
|
+
const files = [];
|
|
1097
|
+
await this.walkJsonFilesInto(dir, files);
|
|
1098
|
+
return files;
|
|
1099
|
+
}
|
|
1100
|
+
async findTrajectoryFilePaths(id) {
|
|
1101
|
+
const pathsById = await this.findTrajectoryFilePathsForIds([id]);
|
|
1102
|
+
return pathsById.get(id) ?? [];
|
|
1103
|
+
}
|
|
1104
|
+
async findTrajectoryFilePathsForIds(ids) {
|
|
1105
|
+
const targetIds = new Set(Array.from(ids).filter(isSafeTrajectoryId));
|
|
1106
|
+
const pathsById = new Map(
|
|
1107
|
+
Array.from(targetIds).map((id) => [id, []])
|
|
1108
|
+
);
|
|
1109
|
+
if (targetIds.size === 0) {
|
|
1110
|
+
return pathsById;
|
|
1111
|
+
}
|
|
1112
|
+
const allFiles = await this.listTrajectoryFiles();
|
|
1113
|
+
for (const filePath of allFiles) {
|
|
1114
|
+
const trajectoryId = this.getTrajectoryIdFromPath(filePath);
|
|
1115
|
+
if (!trajectoryId || !targetIds.has(trajectoryId)) {
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
pathsById.get(trajectoryId)?.push(filePath);
|
|
1119
|
+
}
|
|
1120
|
+
return pathsById;
|
|
1121
|
+
}
|
|
1122
|
+
getTrajectoryIdFromPath(filePath) {
|
|
1123
|
+
if (basename(filePath) === TRAJECTORY_FILE) {
|
|
1124
|
+
const id = basename(dirname(filePath));
|
|
1125
|
+
return isSafeTrajectoryId(id) ? id : void 0;
|
|
1126
|
+
}
|
|
1127
|
+
const name = basename(filePath);
|
|
1128
|
+
if (name.endsWith(".json")) {
|
|
1129
|
+
const id = name.slice(0, -".json".length);
|
|
1130
|
+
return isSafeTrajectoryId(id) ? id : void 0;
|
|
1131
|
+
}
|
|
1132
|
+
return void 0;
|
|
1133
|
+
}
|
|
1134
|
+
async removeTrajectoryFiles(paths, exceptPath) {
|
|
1135
|
+
const summary = this.emptyDeleteSummary();
|
|
1136
|
+
for (const filePath of paths) {
|
|
1137
|
+
if (filePath === exceptPath) {
|
|
1138
|
+
continue;
|
|
1139
|
+
}
|
|
1140
|
+
await this.removeTrajectoryFile(filePath, summary);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
async removeTrajectoryFile(filePath, summary) {
|
|
1144
|
+
if (basename(filePath) === TRAJECTORY_FILE) {
|
|
1145
|
+
const trajectoryDir = dirname(filePath);
|
|
1146
|
+
await this.countDirectoryTrajectoryFiles(trajectoryDir, summary);
|
|
1147
|
+
await this.removeFileIfExists(
|
|
1148
|
+
join(dirname(trajectoryDir), `${basename(trajectoryDir)}.trace.json`),
|
|
1149
|
+
"trace",
|
|
1150
|
+
summary
|
|
1151
|
+
);
|
|
1152
|
+
await rm(trajectoryDir, { recursive: true, force: true });
|
|
1153
|
+
return;
|
|
1154
|
+
}
|
|
1155
|
+
await this.removeFileIfExists(filePath, "json", summary);
|
|
1156
|
+
await this.removeFileIfExists(
|
|
1157
|
+
getMarkdownOutputPath(filePath),
|
|
1158
|
+
"markdown",
|
|
1159
|
+
summary
|
|
1160
|
+
);
|
|
1161
|
+
await this.removeFileIfExists(
|
|
1162
|
+
getTraceOutputPath(filePath),
|
|
1163
|
+
"trace",
|
|
1164
|
+
summary
|
|
1165
|
+
);
|
|
1166
|
+
await this.removeFileIfExists(
|
|
1167
|
+
getLegacyCompactionMarkerPath(filePath),
|
|
1168
|
+
"compaction",
|
|
1169
|
+
summary
|
|
1170
|
+
);
|
|
1171
|
+
}
|
|
1172
|
+
async countDirectoryTrajectoryFiles(trajectoryDir, summary) {
|
|
1173
|
+
await this.countFileIfExists(
|
|
1174
|
+
join(trajectoryDir, TRAJECTORY_FILE),
|
|
1175
|
+
"json",
|
|
1176
|
+
summary
|
|
1177
|
+
);
|
|
1178
|
+
await this.countFileIfExists(
|
|
1179
|
+
join(trajectoryDir, SUMMARY_FILE),
|
|
1180
|
+
"markdown",
|
|
1181
|
+
summary
|
|
1182
|
+
);
|
|
1183
|
+
await this.countFileIfExists(
|
|
1184
|
+
join(trajectoryDir, `${basename(trajectoryDir)}.trace.json`),
|
|
1185
|
+
"trace",
|
|
1186
|
+
summary
|
|
1187
|
+
);
|
|
1188
|
+
await this.countFileIfExists(
|
|
1189
|
+
join(trajectoryDir, "trace.json"),
|
|
1190
|
+
"trace",
|
|
1191
|
+
summary
|
|
1192
|
+
);
|
|
1193
|
+
await this.countFileIfExists(
|
|
1194
|
+
join(trajectoryDir, COMPACTION_FILE),
|
|
1195
|
+
"compaction",
|
|
1196
|
+
summary
|
|
1197
|
+
);
|
|
1198
|
+
}
|
|
1199
|
+
async removeFileIfExists(path2, kind, summary) {
|
|
1200
|
+
if (!existsSync(path2)) {
|
|
1201
|
+
return;
|
|
1202
|
+
}
|
|
1203
|
+
await rm(path2, { force: true });
|
|
1204
|
+
this.incrementDeleteSummary(kind, summary);
|
|
1205
|
+
}
|
|
1206
|
+
async countFileIfExists(path2, kind, summary) {
|
|
1207
|
+
if (existsSync(path2)) {
|
|
1208
|
+
this.incrementDeleteSummary(kind, summary);
|
|
1209
|
+
}
|
|
1210
|
+
}
|
|
1211
|
+
incrementDeleteSummary(kind, summary) {
|
|
1212
|
+
if (kind === "json") {
|
|
1213
|
+
summary.deletedJsonFiles += 1;
|
|
1214
|
+
summary.removedTrajectories += 1;
|
|
1215
|
+
} else if (kind === "markdown") {
|
|
1216
|
+
summary.deletedMarkdownFiles += 1;
|
|
1217
|
+
} else if (kind === "trace") {
|
|
1218
|
+
summary.deletedTraceFiles += 1;
|
|
1219
|
+
} else {
|
|
1220
|
+
summary.deletedCompactionFiles += 1;
|
|
1221
|
+
}
|
|
1222
|
+
}
|
|
1223
|
+
emptyDeleteSummary() {
|
|
1224
|
+
return {
|
|
1225
|
+
removedTrajectories: 0,
|
|
1226
|
+
deletedJsonFiles: 0,
|
|
1227
|
+
deletedMarkdownFiles: 0,
|
|
1228
|
+
deletedTraceFiles: 0,
|
|
1229
|
+
deletedCompactionFiles: 0
|
|
1230
|
+
};
|
|
1231
|
+
}
|
|
1232
|
+
async listCompactionMarkerFiles() {
|
|
1233
|
+
const markerPaths = [];
|
|
1234
|
+
await this.walkFilesInto(
|
|
1235
|
+
this.activeDir,
|
|
1236
|
+
markerPaths,
|
|
1237
|
+
isCompactionMarkerFile
|
|
1238
|
+
);
|
|
1239
|
+
await this.walkFilesInto(
|
|
1240
|
+
this.completedDir,
|
|
1241
|
+
markerPaths,
|
|
1242
|
+
isCompactionMarkerFile
|
|
1243
|
+
);
|
|
1244
|
+
return markerPaths;
|
|
1245
|
+
}
|
|
1246
|
+
getCompactionMarkerPath(filePath, id) {
|
|
1247
|
+
if (basename(filePath) === TRAJECTORY_FILE) {
|
|
1248
|
+
return join(dirname(filePath), COMPACTION_FILE);
|
|
1249
|
+
}
|
|
1250
|
+
return join(dirname(filePath), `${id}${LEGACY_COMPACTION_SUFFIX}`);
|
|
1251
|
+
}
|
|
1252
|
+
getTrajectoryIdFromCompactionMarkerPath(markerPath) {
|
|
1253
|
+
if (basename(markerPath) === COMPACTION_FILE) {
|
|
1254
|
+
const id = basename(dirname(markerPath));
|
|
1255
|
+
return id.startsWith("traj_") ? id : void 0;
|
|
1256
|
+
}
|
|
1257
|
+
const markerName = basename(markerPath);
|
|
1258
|
+
return markerName.endsWith(LEGACY_COMPACTION_SUFFIX) ? markerName.slice(0, -LEGACY_COMPACTION_SUFFIX.length) : void 0;
|
|
1259
|
+
}
|
|
1260
|
+
async migrateLegacyIndexCompactionMarkers() {
|
|
1261
|
+
const indexPath = join(this.trajectoriesDir, "index.json");
|
|
1262
|
+
if (!existsSync(indexPath)) {
|
|
1263
|
+
return;
|
|
1264
|
+
}
|
|
1265
|
+
let parsed;
|
|
1266
|
+
try {
|
|
1267
|
+
parsed = JSON.parse(await readFile(indexPath, "utf-8"));
|
|
1268
|
+
} catch {
|
|
1269
|
+
return;
|
|
1270
|
+
}
|
|
1271
|
+
if (parsed === null || typeof parsed !== "object") {
|
|
1272
|
+
return;
|
|
1273
|
+
}
|
|
1274
|
+
const trajectories = parsed.trajectories;
|
|
1275
|
+
if (trajectories === null || typeof trajectories !== "object" || Array.isArray(trajectories)) {
|
|
1276
|
+
return;
|
|
1277
|
+
}
|
|
1278
|
+
await Promise.all(
|
|
1279
|
+
Object.entries(trajectories).map(async ([id, entry]) => {
|
|
1280
|
+
if (entry === null || typeof entry !== "object" || !isSafeTrajectoryId(id)) {
|
|
1281
|
+
return;
|
|
1282
|
+
}
|
|
1283
|
+
const compactedInto = entry.compactedInto;
|
|
1284
|
+
const path2 = entry.path;
|
|
1285
|
+
if (typeof compactedInto !== "string") {
|
|
1286
|
+
return;
|
|
1287
|
+
}
|
|
1288
|
+
const paths = typeof path2 === "string" && existsSync(path2) && this.isPathInsideTrajectoriesDir(path2) ? [path2] : await this.findTrajectoryFilePaths(id);
|
|
1289
|
+
if (paths.length === 0) return;
|
|
1290
|
+
const marker = {
|
|
1291
|
+
trajectoryId: id,
|
|
1292
|
+
compactedInto,
|
|
1293
|
+
compactedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1294
|
+
};
|
|
1295
|
+
await Promise.all(
|
|
1296
|
+
paths.map(
|
|
1297
|
+
(filePath) => writeFile(
|
|
1298
|
+
this.getCompactionMarkerPath(filePath, id),
|
|
1299
|
+
JSON.stringify(marker, null, 2),
|
|
1300
|
+
"utf-8"
|
|
1301
|
+
)
|
|
1302
|
+
)
|
|
1303
|
+
);
|
|
1304
|
+
})
|
|
1305
|
+
);
|
|
1306
|
+
}
|
|
1307
|
+
isPathInsideTrajectoriesDir(path2) {
|
|
1308
|
+
const rel = relative(resolve(this.trajectoriesDir), resolve(path2));
|
|
1309
|
+
return Boolean(rel && !rel.startsWith("..") && !isAbsolute(rel));
|
|
1310
|
+
}
|
|
1311
|
+
async walkFilesInto(dir, out, predicate) {
|
|
1312
|
+
let entries;
|
|
1313
|
+
try {
|
|
1314
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
1315
|
+
} catch (error) {
|
|
1316
|
+
if (error.code === "ENOENT") return;
|
|
1317
|
+
throw error;
|
|
1318
|
+
}
|
|
1319
|
+
for (const entry of entries) {
|
|
1320
|
+
const entryPath = join(dir, entry.name);
|
|
1321
|
+
if (entry.isDirectory()) {
|
|
1322
|
+
await this.walkFilesInto(entryPath, out, predicate);
|
|
1323
|
+
} else if (entry.isFile() && predicate(entry.name)) {
|
|
1324
|
+
out.push(entryPath);
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
}
|
|
1328
|
+
getSortValue(trajectory, sortBy) {
|
|
1329
|
+
if (sortBy === "title") {
|
|
1330
|
+
return trajectory.task.title;
|
|
1331
|
+
}
|
|
1332
|
+
return trajectory[sortBy] ?? "";
|
|
1333
|
+
}
|
|
1334
|
+
toSummary(trajectory) {
|
|
1335
|
+
return {
|
|
1336
|
+
id: trajectory.id,
|
|
1337
|
+
title: trajectory.task.title,
|
|
1338
|
+
status: trajectory.status,
|
|
1339
|
+
startedAt: trajectory.startedAt,
|
|
1340
|
+
completedAt: trajectory.completedAt,
|
|
1341
|
+
confidence: trajectory.retrospective?.confidence,
|
|
1342
|
+
chapterCount: trajectory.chapters.length,
|
|
1343
|
+
decisionCount: trajectory.chapters.reduce(
|
|
1344
|
+
(count, chapter) => count + chapter.events.filter((event) => event.type === "decision").length,
|
|
1345
|
+
0
|
|
1346
|
+
)
|
|
1347
|
+
};
|
|
1348
|
+
}
|
|
1349
|
+
isNewerTrajectory(candidate, current) {
|
|
1350
|
+
const candidateTime = new Date(
|
|
1351
|
+
candidate.completedAt ?? candidate.startedAt
|
|
1352
|
+
).getTime();
|
|
1353
|
+
const currentTime = new Date(
|
|
1354
|
+
current.completedAt ?? current.startedAt
|
|
1355
|
+
).getTime();
|
|
1356
|
+
return candidateTime > currentTime;
|
|
1357
|
+
}
|
|
1049
1358
|
/**
|
|
1050
1359
|
* Read a trajectory file and return a tagged result so callers can
|
|
1051
1360
|
* distinguish missing files, malformed JSON, and schema violations.
|
|
@@ -1085,82 +1394,25 @@ var FileStorage = class {
|
|
|
1085
1394
|
const result = await this.readTrajectoryFile(path2);
|
|
1086
1395
|
return result.ok ? result.trajectory : null;
|
|
1087
1396
|
}
|
|
1088
|
-
/**
|
|
1089
|
-
* Read and parse the on-disk index.
|
|
1090
|
-
*
|
|
1091
|
-
* Tolerances (belt-and-braces against the read/write race):
|
|
1092
|
-
* - ENOENT: first-run, return an empty index silently.
|
|
1093
|
-
* - Empty file: a concurrent writer truncated index.json in "w" mode
|
|
1094
|
-
* right before we read. Return an empty index silently — this is
|
|
1095
|
-
* not a real corruption, just an interleaving the mutex + atomic
|
|
1096
|
-
* rename should already prevent. Logging here would be noise.
|
|
1097
|
-
* - Non-empty but malformed JSON: genuinely corrupted on disk (hand
|
|
1098
|
-
* edit, disk error, etc). Log it and return an empty index so the
|
|
1099
|
-
* caller can recover, but keep the log so the problem is visible.
|
|
1100
|
-
*/
|
|
1101
|
-
async loadIndex() {
|
|
1102
|
-
let content;
|
|
1103
|
-
try {
|
|
1104
|
-
content = await readFile(this.indexPath, "utf-8");
|
|
1105
|
-
} catch (error) {
|
|
1106
|
-
if (error.code !== "ENOENT") {
|
|
1107
|
-
console.error(
|
|
1108
|
-
"Error loading trajectory index, using empty index:",
|
|
1109
|
-
error
|
|
1110
|
-
);
|
|
1111
|
-
}
|
|
1112
|
-
return this.emptyIndex();
|
|
1113
|
-
}
|
|
1114
|
-
if (content.length === 0) {
|
|
1115
|
-
return this.emptyIndex();
|
|
1116
|
-
}
|
|
1117
|
-
try {
|
|
1118
|
-
return JSON.parse(content);
|
|
1119
|
-
} catch (error) {
|
|
1120
|
-
console.error(
|
|
1121
|
-
"Error loading trajectory index, using empty index:",
|
|
1122
|
-
error
|
|
1123
|
-
);
|
|
1124
|
-
return this.emptyIndex();
|
|
1125
|
-
}
|
|
1126
|
-
}
|
|
1127
|
-
emptyIndex() {
|
|
1128
|
-
return {
|
|
1129
|
-
version: 1,
|
|
1130
|
-
lastUpdated: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1131
|
-
trajectories: {}
|
|
1132
|
-
};
|
|
1133
|
-
}
|
|
1134
|
-
/**
|
|
1135
|
-
* Atomic write: stage into a process-unique temp path in the same directory
|
|
1136
|
-
* and then rename over the live file. `rename` is atomic on POSIX, so
|
|
1137
|
-
* concurrent readers in any process either see the old complete file or
|
|
1138
|
-
* the new complete file — never a half-written / zero-byte state.
|
|
1139
|
-
*
|
|
1140
|
-
* Callers MUST hold `withIndexLock(this.indexPath, ...)` so the in-process
|
|
1141
|
-
* read-modify-write cycle stays serialized; the unique temp name also keeps
|
|
1142
|
-
* parallel writers in other processes from colliding on a shared tmp path.
|
|
1143
|
-
*/
|
|
1144
|
-
async saveIndex(index) {
|
|
1145
|
-
index.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
1146
|
-
const tmpPath = `${this.indexPath}.${process.pid}.${randomUUID()}.tmp`;
|
|
1147
|
-
await writeFile(tmpPath, JSON.stringify(index, null, 2), "utf-8");
|
|
1148
|
-
await rename(tmpPath, this.indexPath);
|
|
1149
|
-
}
|
|
1150
|
-
async updateIndex(trajectory, filePath) {
|
|
1151
|
-
await withIndexLock(this.indexPath, async () => {
|
|
1152
|
-
const index = await this.loadIndex();
|
|
1153
|
-
index.trajectories[trajectory.id] = {
|
|
1154
|
-
title: trajectory.task.title,
|
|
1155
|
-
status: trajectory.status,
|
|
1156
|
-
startedAt: trajectory.startedAt,
|
|
1157
|
-
completedAt: trajectory.completedAt,
|
|
1158
|
-
path: filePath
|
|
1159
|
-
};
|
|
1160
|
-
await this.saveIndex(index);
|
|
1161
|
-
});
|
|
1162
|
-
}
|
|
1163
1397
|
};
|
|
1398
|
+
function isTrajectoryJsonFile(name) {
|
|
1399
|
+
return name === TRAJECTORY_FILE || name.endsWith(".json") && name !== "index.json" && !name.endsWith(".trace.json") && !name.endsWith(LEGACY_COMPACTION_SUFFIX) && name !== COMPACTION_FILE;
|
|
1400
|
+
}
|
|
1401
|
+
function isSafeTrajectoryId(id) {
|
|
1402
|
+
return id.length > 0 && !id.includes("..") && !id.includes("/") && !id.includes("\\");
|
|
1403
|
+
}
|
|
1404
|
+
function isCompactionMarkerFile(name) {
|
|
1405
|
+
return name === COMPACTION_FILE || name.endsWith(LEGACY_COMPACTION_SUFFIX);
|
|
1406
|
+
}
|
|
1407
|
+
function getMarkdownOutputPath(outputPath) {
|
|
1408
|
+
return outputPath.endsWith(".json") ? outputPath.slice(0, -".json".length).concat(".md") : `${outputPath}.md`;
|
|
1409
|
+
}
|
|
1410
|
+
function getTraceOutputPath(outputPath) {
|
|
1411
|
+
return outputPath.endsWith(".json") ? outputPath.slice(0, -".json".length).concat(".trace.json") : `${outputPath}.trace.json`;
|
|
1412
|
+
}
|
|
1413
|
+
function getLegacyCompactionMarkerPath(outputPath) {
|
|
1414
|
+
return outputPath.endsWith(".json") ? outputPath.slice(0, -".json".length).concat(LEGACY_COMPACTION_SUFFIX) : `${outputPath}${LEGACY_COMPACTION_SUFFIX}`;
|
|
1415
|
+
}
|
|
1164
1416
|
|
|
1165
1417
|
// src/cli/commands/abandon.ts
|
|
1166
1418
|
function registerAbandonCommand(program2) {
|
|
@@ -1183,13 +1435,7 @@ function registerAbandonCommand(program2) {
|
|
|
1183
1435
|
|
|
1184
1436
|
// src/cli/commands/compact.ts
|
|
1185
1437
|
import { execFileSync } from "child_process";
|
|
1186
|
-
import {
|
|
1187
|
-
existsSync as existsSync3,
|
|
1188
|
-
mkdirSync,
|
|
1189
|
-
readFileSync as readFileSync2,
|
|
1190
|
-
unlinkSync,
|
|
1191
|
-
writeFileSync
|
|
1192
|
-
} from "fs";
|
|
1438
|
+
import { existsSync as existsSync3, mkdirSync, writeFileSync } from "fs";
|
|
1193
1439
|
import { dirname as dirname2, join as join4 } from "path";
|
|
1194
1440
|
|
|
1195
1441
|
// src/compact/config.ts
|
|
@@ -1247,7 +1493,7 @@ function loadFileConfig() {
|
|
|
1247
1493
|
}
|
|
1248
1494
|
function getPrimaryConfigDir() {
|
|
1249
1495
|
const searchPaths = getSearchPaths();
|
|
1250
|
-
return searchPaths[0] ??
|
|
1496
|
+
return searchPaths[0] ?? getDefaultTrajectoryDataDir();
|
|
1251
1497
|
}
|
|
1252
1498
|
function readStringEnv(name) {
|
|
1253
1499
|
return readString(process.env[name]);
|
|
@@ -1894,7 +2140,7 @@ function buildCliArgs(cli) {
|
|
|
1894
2140
|
}
|
|
1895
2141
|
}
|
|
1896
2142
|
function spawnWithStdin(command, args, input) {
|
|
1897
|
-
return new Promise((
|
|
2143
|
+
return new Promise((resolve2, reject) => {
|
|
1898
2144
|
const child = spawn(command, args, {
|
|
1899
2145
|
timeout: 3e5,
|
|
1900
2146
|
stdio: ["pipe", "pipe", "pipe"]
|
|
@@ -1912,7 +2158,7 @@ function spawnWithStdin(command, args, input) {
|
|
|
1912
2158
|
new Error(`CLI exited with code ${code}: ${stderr.slice(0, 200)}`)
|
|
1913
2159
|
);
|
|
1914
2160
|
} else {
|
|
1915
|
-
|
|
2161
|
+
resolve2(Buffer.concat(chunks).toString().trim());
|
|
1916
2162
|
}
|
|
1917
2163
|
});
|
|
1918
2164
|
child.stdin.write(input);
|
|
@@ -2337,7 +2583,7 @@ function registerCompactCommand(program2) {
|
|
|
2337
2583
|
"Comma-separated focus areas to emphasize in LLM compaction"
|
|
2338
2584
|
).option("--markdown", "Also write a Markdown companion file").option("--no-markdown", "Skip writing a Markdown companion file").option(
|
|
2339
2585
|
"--discard-sources",
|
|
2340
|
-
"After saving the compaction, delete source trajectory JSON/MD/trace files
|
|
2586
|
+
"After saving the compaction, delete source trajectory JSON/MD/trace files"
|
|
2341
2587
|
).option("--dry-run", "Preview what would be compacted without saving").option("--output <path>", "Output path for compacted trajectory").action(async (options) => {
|
|
2342
2588
|
const trajectories = await loadTrajectories(options);
|
|
2343
2589
|
if (trajectories.length === 0) {
|
|
@@ -2378,7 +2624,7 @@ function registerCompactCommand(program2) {
|
|
|
2378
2624
|
markdownEnabled
|
|
2379
2625
|
);
|
|
2380
2626
|
if (options.discardSources) {
|
|
2381
|
-
const discardSummary = discardSourceTrajectories(trajectories);
|
|
2627
|
+
const discardSummary = await discardSourceTrajectories(trajectories);
|
|
2382
2628
|
printDiscardSummary(discardSummary);
|
|
2383
2629
|
} else {
|
|
2384
2630
|
await markTrajectoriesAsCompacted(
|
|
@@ -2390,7 +2636,7 @@ function registerCompactCommand(program2) {
|
|
|
2390
2636
|
Compacted trajectory saved to: ${outputPath2}`);
|
|
2391
2637
|
if (markdownEnabled) {
|
|
2392
2638
|
console.log(
|
|
2393
|
-
`Markdown summary saved to: ${
|
|
2639
|
+
`Markdown summary saved to: ${getMarkdownOutputPath2(outputPath2)}`
|
|
2394
2640
|
);
|
|
2395
2641
|
}
|
|
2396
2642
|
printCompactedSummary(mechanicalCompacted);
|
|
@@ -2439,7 +2685,7 @@ Compacted trajectory saved to: ${outputPath2}`);
|
|
|
2439
2685
|
const outputPath = options.output || getDefaultOutputPath(compacted, options.workflow);
|
|
2440
2686
|
saveCompactionArtifacts(compacted, outputPath, markdownEnabled);
|
|
2441
2687
|
if (options.discardSources) {
|
|
2442
|
-
const discardSummary = discardSourceTrajectories(trajectories);
|
|
2688
|
+
const discardSummary = await discardSourceTrajectories(trajectories);
|
|
2443
2689
|
printDiscardSummary(discardSummary);
|
|
2444
2690
|
} else {
|
|
2445
2691
|
await markTrajectoriesAsCompacted(trajectories, compacted.id);
|
|
@@ -2448,7 +2694,7 @@ Compacted trajectory saved to: ${outputPath2}`);
|
|
|
2448
2694
|
Compacted trajectory saved to: ${outputPath}`);
|
|
2449
2695
|
if (markdownEnabled) {
|
|
2450
2696
|
console.log(
|
|
2451
|
-
`Markdown summary saved to: ${
|
|
2697
|
+
`Markdown summary saved to: ${getMarkdownOutputPath2(outputPath)}`
|
|
2452
2698
|
);
|
|
2453
2699
|
}
|
|
2454
2700
|
printCompactedSummary(compacted);
|
|
@@ -2467,19 +2713,12 @@ async function loadTrajectories(options) {
|
|
|
2467
2713
|
return [trimmed, trimmed.slice(0, 7)];
|
|
2468
2714
|
})
|
|
2469
2715
|
) : null;
|
|
2470
|
-
const compactedIds = options.all ? /* @__PURE__ */ new Set() : getCompactedTrajectoryIds();
|
|
2716
|
+
const compactedIds = options.all ? /* @__PURE__ */ new Set() : await getCompactedTrajectoryIds();
|
|
2471
2717
|
const searchPaths = getSearchPaths();
|
|
2472
2718
|
const seenIds = /* @__PURE__ */ new Set();
|
|
2473
2719
|
for (const searchPath of searchPaths) {
|
|
2474
2720
|
if (!existsSync3(searchPath)) continue;
|
|
2475
|
-
const
|
|
2476
|
-
process.env.TRAJECTORIES_DATA_DIR = searchPath;
|
|
2477
|
-
const storage = new FileStorage();
|
|
2478
|
-
if (originalDataDir !== void 0) {
|
|
2479
|
-
process.env.TRAJECTORIES_DATA_DIR = originalDataDir;
|
|
2480
|
-
} else {
|
|
2481
|
-
delete process.env.TRAJECTORIES_DATA_DIR;
|
|
2482
|
-
}
|
|
2721
|
+
const storage = createStorageForSearchPath(searchPath);
|
|
2483
2722
|
await storage.initialize();
|
|
2484
2723
|
const summaries = await storage.list({
|
|
2485
2724
|
status: "completed",
|
|
@@ -2525,6 +2764,19 @@ async function loadTrajectories(options) {
|
|
|
2525
2764
|
}
|
|
2526
2765
|
return trajectories;
|
|
2527
2766
|
}
|
|
2767
|
+
function createStorageForSearchPath(searchPath) {
|
|
2768
|
+
const originalDataDir = process.env.TRAJECTORIES_DATA_DIR;
|
|
2769
|
+
process.env.TRAJECTORIES_DATA_DIR = searchPath;
|
|
2770
|
+
try {
|
|
2771
|
+
return new FileStorage();
|
|
2772
|
+
} finally {
|
|
2773
|
+
if (originalDataDir !== void 0) {
|
|
2774
|
+
process.env.TRAJECTORIES_DATA_DIR = originalDataDir;
|
|
2775
|
+
} else {
|
|
2776
|
+
delete process.env.TRAJECTORIES_DATA_DIR;
|
|
2777
|
+
}
|
|
2778
|
+
}
|
|
2779
|
+
}
|
|
2528
2780
|
function getBranchCommits(targetBranch) {
|
|
2529
2781
|
const commits = /* @__PURE__ */ new Set();
|
|
2530
2782
|
try {
|
|
@@ -2549,21 +2801,17 @@ function getBranchCommits(targetBranch) {
|
|
|
2549
2801
|
}
|
|
2550
2802
|
return commits;
|
|
2551
2803
|
}
|
|
2552
|
-
function getCompactedTrajectoryIds() {
|
|
2804
|
+
async function getCompactedTrajectoryIds() {
|
|
2553
2805
|
const compacted = /* @__PURE__ */ new Set();
|
|
2554
2806
|
const searchPaths = getSearchPaths();
|
|
2555
2807
|
for (const searchPath of searchPaths) {
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
compacted.add(id);
|
|
2564
|
-
}
|
|
2565
|
-
}
|
|
2566
|
-
} catch {
|
|
2808
|
+
if (!existsSync3(searchPath)) {
|
|
2809
|
+
continue;
|
|
2810
|
+
}
|
|
2811
|
+
const storage = createStorageForSearchPath(searchPath);
|
|
2812
|
+
await storage.initialize();
|
|
2813
|
+
for (const id of await storage.getCompactedTrajectoryIds()) {
|
|
2814
|
+
compacted.add(id);
|
|
2567
2815
|
}
|
|
2568
2816
|
}
|
|
2569
2817
|
return compacted;
|
|
@@ -2571,92 +2819,45 @@ function getCompactedTrajectoryIds() {
|
|
|
2571
2819
|
async function markTrajectoriesAsCompacted(trajectories, compactedIntoId) {
|
|
2572
2820
|
const searchPaths = getSearchPaths();
|
|
2573
2821
|
for (const searchPath of searchPaths) {
|
|
2574
|
-
|
|
2575
|
-
|
|
2576
|
-
try {
|
|
2577
|
-
const indexContent = readFileSync2(indexPath, "utf-8");
|
|
2578
|
-
const index = JSON.parse(indexContent);
|
|
2579
|
-
let updated = false;
|
|
2580
|
-
for (const traj of trajectories) {
|
|
2581
|
-
if (index.trajectories[traj.id]) {
|
|
2582
|
-
index.trajectories[traj.id].compactedInto = compactedIntoId;
|
|
2583
|
-
updated = true;
|
|
2584
|
-
}
|
|
2585
|
-
}
|
|
2586
|
-
if (updated) {
|
|
2587
|
-
index.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
2588
|
-
writeFileSync(indexPath, JSON.stringify(index, null, 2));
|
|
2589
|
-
}
|
|
2590
|
-
} catch {
|
|
2822
|
+
if (!existsSync3(searchPath)) {
|
|
2823
|
+
continue;
|
|
2591
2824
|
}
|
|
2825
|
+
const storage = createStorageForSearchPath(searchPath);
|
|
2826
|
+
await storage.initialize();
|
|
2827
|
+
await storage.markCompactedMany(
|
|
2828
|
+
trajectories.map((trajectory) => trajectory.id),
|
|
2829
|
+
compactedIntoId
|
|
2830
|
+
);
|
|
2592
2831
|
}
|
|
2593
2832
|
}
|
|
2594
|
-
function discardSourceTrajectories(trajectories) {
|
|
2595
|
-
const sourceIds = new Set(trajectories.map((trajectory) => trajectory.id));
|
|
2833
|
+
async function discardSourceTrajectories(trajectories) {
|
|
2596
2834
|
const summary = {
|
|
2597
|
-
|
|
2835
|
+
removedTrajectories: 0,
|
|
2598
2836
|
deletedJsonFiles: 0,
|
|
2599
2837
|
deletedMarkdownFiles: 0,
|
|
2600
|
-
deletedTraceFiles: 0
|
|
2838
|
+
deletedTraceFiles: 0,
|
|
2839
|
+
deletedCompactionFiles: 0
|
|
2601
2840
|
};
|
|
2602
2841
|
for (const searchPath of getSearchPaths()) {
|
|
2603
|
-
|
|
2604
|
-
if (!existsSync3(indexPath)) continue;
|
|
2605
|
-
let index;
|
|
2606
|
-
try {
|
|
2607
|
-
const indexContent = readFileSync2(indexPath, "utf-8");
|
|
2608
|
-
const parsedIndex = JSON.parse(indexContent);
|
|
2609
|
-
if (!isTrajectoryIndex(parsedIndex)) {
|
|
2610
|
-
continue;
|
|
2611
|
-
}
|
|
2612
|
-
index = parsedIndex;
|
|
2613
|
-
} catch {
|
|
2842
|
+
if (!existsSync3(searchPath)) {
|
|
2614
2843
|
continue;
|
|
2615
2844
|
}
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
|
|
2624
|
-
|
|
2625
|
-
|
|
2626
|
-
if (deleteFileIfExists(getTraceOutputPath(entry.path))) {
|
|
2627
|
-
summary.deletedTraceFiles += 1;
|
|
2628
|
-
}
|
|
2629
|
-
delete index.trajectories[id];
|
|
2630
|
-
summary.removedIndexEntries += 1;
|
|
2631
|
-
updated = true;
|
|
2632
|
-
}
|
|
2633
|
-
if (updated) {
|
|
2634
|
-
index.lastUpdated = (/* @__PURE__ */ new Date()).toISOString();
|
|
2635
|
-
writeFileSync(indexPath, JSON.stringify(index, null, 2));
|
|
2636
|
-
}
|
|
2845
|
+
const storage = createStorageForSearchPath(searchPath);
|
|
2846
|
+
await storage.initialize();
|
|
2847
|
+
const deleteSummary = await storage.deleteManyWithSummary(
|
|
2848
|
+
trajectories.map((trajectory) => trajectory.id)
|
|
2849
|
+
);
|
|
2850
|
+
summary.removedTrajectories += deleteSummary.removedTrajectories;
|
|
2851
|
+
summary.deletedJsonFiles += deleteSummary.deletedJsonFiles;
|
|
2852
|
+
summary.deletedMarkdownFiles += deleteSummary.deletedMarkdownFiles;
|
|
2853
|
+
summary.deletedTraceFiles += deleteSummary.deletedTraceFiles;
|
|
2854
|
+
summary.deletedCompactionFiles += deleteSummary.deletedCompactionFiles;
|
|
2637
2855
|
}
|
|
2638
2856
|
return summary;
|
|
2639
2857
|
}
|
|
2640
|
-
function deleteFileIfExists(path2) {
|
|
2641
|
-
if (!existsSync3(path2)) {
|
|
2642
|
-
return false;
|
|
2643
|
-
}
|
|
2644
|
-
unlinkSync(path2);
|
|
2645
|
-
return true;
|
|
2646
|
-
}
|
|
2647
|
-
function isTrajectoryIndex(value) {
|
|
2648
|
-
if (value === null || typeof value !== "object") {
|
|
2649
|
-
return false;
|
|
2650
|
-
}
|
|
2651
|
-
const candidate = value;
|
|
2652
|
-
return candidate.trajectories !== null && typeof candidate.trajectories === "object" && !Array.isArray(candidate.trajectories);
|
|
2653
|
-
}
|
|
2654
|
-
function getTraceOutputPath(outputPath) {
|
|
2655
|
-
return outputPath.endsWith(".json") ? outputPath.slice(0, -".json".length).concat(".trace.json") : `${outputPath}.trace.json`;
|
|
2656
|
-
}
|
|
2657
2858
|
function printDiscardSummary(summary) {
|
|
2658
2859
|
console.log(
|
|
2659
|
-
`Discarded source trajectories: ${summary.
|
|
2860
|
+
`Discarded source trajectories: ${summary.removedTrajectories} trajectories, ${summary.deletedJsonFiles} JSON files, ${summary.deletedMarkdownFiles} Markdown files, ${summary.deletedTraceFiles} trace files, ${summary.deletedCompactionFiles} compaction markers`
|
|
2660
2861
|
);
|
|
2661
2862
|
}
|
|
2662
2863
|
function parseRelativeDate(input) {
|
|
@@ -2882,7 +3083,7 @@ function getProviderLabel(provider) {
|
|
|
2882
3083
|
return "LLM";
|
|
2883
3084
|
}
|
|
2884
3085
|
function getDefaultOutputPath(compacted, workflowId) {
|
|
2885
|
-
const trajDir = process.env.TRAJECTORIES_DATA_DIR ||
|
|
3086
|
+
const trajDir = process.env.TRAJECTORIES_DATA_DIR || getDefaultTrajectoryDataDir();
|
|
2886
3087
|
const compactedDir = join4(trajDir, "compacted");
|
|
2887
3088
|
if (!existsSync3(compactedDir)) {
|
|
2888
3089
|
mkdirSync(compactedDir, { recursive: true });
|
|
@@ -2901,12 +3102,12 @@ function saveCompactionArtifacts(compacted, outputPath, markdownEnabled) {
|
|
|
2901
3102
|
writeFileSync(outputPath, JSON.stringify(compacted, null, 2));
|
|
2902
3103
|
if (markdownEnabled) {
|
|
2903
3104
|
writeFileSync(
|
|
2904
|
-
|
|
3105
|
+
getMarkdownOutputPath2(outputPath),
|
|
2905
3106
|
renderCompactionMarkdown(compacted)
|
|
2906
3107
|
);
|
|
2907
3108
|
}
|
|
2908
3109
|
}
|
|
2909
|
-
function
|
|
3110
|
+
function getMarkdownOutputPath2(outputPath) {
|
|
2910
3111
|
return outputPath.endsWith(".json") ? outputPath.slice(0, -".json".length).concat(".md") : `${outputPath}.md`;
|
|
2911
3112
|
}
|
|
2912
3113
|
function renderCompactionMarkdown(compacted) {
|
|
@@ -3229,7 +3430,7 @@ function createTraceRef(startRef, traceId) {
|
|
|
3229
3430
|
|
|
3230
3431
|
// src/core/trailers.ts
|
|
3231
3432
|
import { execSync as execSync2 } from "child_process";
|
|
3232
|
-
import { readFileSync as
|
|
3433
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
3233
3434
|
function getCommitsBetween(startRef, endRef = "HEAD") {
|
|
3234
3435
|
if (!isGitRepo()) {
|
|
3235
3436
|
return [];
|
|
@@ -3298,7 +3499,7 @@ if [ "$COMMIT_SOURCE" = "merge" ] || [ "$COMMIT_SOURCE" = "squash" ] || [ "$COMM
|
|
|
3298
3499
|
fi
|
|
3299
3500
|
|
|
3300
3501
|
# Find the trajectories data directory
|
|
3301
|
-
TRAJ_DIR="\${TRAJECTORIES_DATA_DIR:-$(git rev-parse --show-toplevel)/.trajectories}"
|
|
3502
|
+
TRAJ_DIR="\${TRAJECTORIES_DATA_DIR:-$(git rev-parse --show-toplevel)/.agentworkforce/trajectories}"
|
|
3302
3503
|
ACTIVE_DIR="$TRAJ_DIR/active"
|
|
3303
3504
|
|
|
3304
3505
|
# Check if there's an active trajectory
|
|
@@ -3342,7 +3543,7 @@ function detectExistingHook() {
|
|
|
3342
3543
|
}).trim();
|
|
3343
3544
|
const hookPath = `${hooksDir}/hooks/prepare-commit-msg`;
|
|
3344
3545
|
try {
|
|
3345
|
-
const content =
|
|
3546
|
+
const content = readFileSync2(hookPath, "utf-8");
|
|
3346
3547
|
if (content.includes("agent-trajectories")) {
|
|
3347
3548
|
return "ours";
|
|
3348
3549
|
}
|
|
@@ -3358,7 +3559,7 @@ function detectExistingHook() {
|
|
|
3358
3559
|
// src/cli/commands/complete.ts
|
|
3359
3560
|
async function saveTraceFile(trajectory, trace) {
|
|
3360
3561
|
const dataDir = process.env.TRAJECTORIES_DATA_DIR;
|
|
3361
|
-
const baseDir = dataDir ? dataDir :
|
|
3562
|
+
const baseDir = dataDir ? dataDir : getDefaultTrajectoryDataDir();
|
|
3362
3563
|
const completedDir = join5(baseDir, "completed");
|
|
3363
3564
|
const date = new Date(trajectory.completedAt ?? trajectory.startedAt);
|
|
3364
3565
|
const monthDir = join5(
|
|
@@ -3486,7 +3687,7 @@ function registerDoctorCommand(program2) {
|
|
|
3486
3687
|
"List trajectory files that fail to load; optionally quarantine them"
|
|
3487
3688
|
).option(
|
|
3488
3689
|
"--quarantine",
|
|
3489
|
-
"Move invalid files to .trajectories/invalid/ so reconcile stops scanning them"
|
|
3690
|
+
"Move invalid files to .agentworkforce/trajectories/invalid/ so reconcile stops scanning them"
|
|
3490
3691
|
).action(async (opts) => {
|
|
3491
3692
|
const storage = new FileStorage();
|
|
3492
3693
|
await storage.initialize();
|
|
@@ -3504,7 +3705,7 @@ function registerDoctorCommand(program2) {
|
|
|
3504
3705
|
}
|
|
3505
3706
|
if (!opts.quarantine) {
|
|
3506
3707
|
console.log(
|
|
3507
|
-
"\nRun `trail doctor --quarantine` to move these files into .trajectories/invalid/."
|
|
3708
|
+
"\nRun `trail doctor --quarantine` to move these files into .agentworkforce/trajectories/invalid/."
|
|
3508
3709
|
);
|
|
3509
3710
|
return;
|
|
3510
3711
|
}
|
|
@@ -3602,8 +3803,8 @@ function registerEnableCommand(program2) {
|
|
|
3602
3803
|
console.error("Remove it manually if needed");
|
|
3603
3804
|
throw new Error("Hook not ours");
|
|
3604
3805
|
}
|
|
3605
|
-
const { unlink
|
|
3606
|
-
await
|
|
3806
|
+
const { unlink } = await import("fs/promises");
|
|
3807
|
+
await unlink(hookPath);
|
|
3607
3808
|
console.log("Trajectory hook removed");
|
|
3608
3809
|
});
|
|
3609
3810
|
}
|
|
@@ -4308,7 +4509,10 @@ function registerExportCommand(program2) {
|
|
|
4308
4509
|
openInBrowser(options.output);
|
|
4309
4510
|
}
|
|
4310
4511
|
} else if (options.open && options.format === "html") {
|
|
4311
|
-
const outputDir = join7(
|
|
4512
|
+
const outputDir = join7(
|
|
4513
|
+
process.env.TRAJECTORIES_DATA_DIR ?? getDefaultTrajectoryDataDir(),
|
|
4514
|
+
"html"
|
|
4515
|
+
);
|
|
4312
4516
|
await mkdir4(outputDir, { recursive: true });
|
|
4313
4517
|
const filePath = join7(outputDir, `${trajectory.id}.html`);
|
|
4314
4518
|
await writeFile4(filePath, output, "utf-8");
|