agent-trajectories 0.5.3 → 0.5.4
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/dist/{chunk-YI27BR5O.js → chunk-W222QB6V.js} +171 -25
- package/dist/chunk-W222QB6V.js.map +1 -0
- package/dist/cli/index.js +220 -42
- package/dist/cli/index.js.map +1 -1
- package/dist/{index-Bw0Jk4zO.d.ts → index-7tzw_CMS.d.ts} +76 -21
- package/dist/index.d.ts +2 -2
- package/dist/index.js +1 -1
- package/dist/sdk/index.d.ts +1 -1
- package/dist/sdk/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-YI27BR5O.js.map +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -92,7 +92,7 @@ var DecisionSchema = z.object({
|
|
|
92
92
|
});
|
|
93
93
|
var AgentParticipationSchema = z.object({
|
|
94
94
|
name: z.string().min(1, "Agent name is required"),
|
|
95
|
-
role: z.
|
|
95
|
+
role: z.string().min(1, "Agent role is required"),
|
|
96
96
|
joinedAt: z.string().datetime(),
|
|
97
97
|
leftAt: z.string().datetime().optional()
|
|
98
98
|
});
|
|
@@ -152,7 +152,7 @@ var TrajectoryTraceRefSchema = z.object({
|
|
|
152
152
|
traceId: z.string().optional()
|
|
153
153
|
});
|
|
154
154
|
var TrajectorySchema = z.object({
|
|
155
|
-
id: z.string().regex(/^traj_[a-z0-
|
|
155
|
+
id: z.string().regex(/^traj_[a-z0-9_]+$/, "Invalid trajectory ID format"),
|
|
156
156
|
version: z.literal(1),
|
|
157
157
|
task: TaskReferenceSchema,
|
|
158
158
|
status: TrajectoryStatusSchema,
|
|
@@ -161,11 +161,11 @@ var TrajectorySchema = z.object({
|
|
|
161
161
|
agents: z.array(AgentParticipationSchema),
|
|
162
162
|
chapters: z.array(ChapterSchema),
|
|
163
163
|
retrospective: RetrospectiveSchema.optional(),
|
|
164
|
-
commits: z.array(z.string()),
|
|
165
|
-
filesChanged: z.array(z.string()),
|
|
166
|
-
projectId: z.string(),
|
|
164
|
+
commits: z.array(z.string()).default([]),
|
|
165
|
+
filesChanged: z.array(z.string()).default([]),
|
|
166
|
+
projectId: z.string().optional(),
|
|
167
167
|
workflowId: z.string().optional(),
|
|
168
|
-
tags: z.array(z.string()),
|
|
168
|
+
tags: z.array(z.string()).default([]),
|
|
169
169
|
_trace: TrajectoryTraceRefSchema.optional()
|
|
170
170
|
});
|
|
171
171
|
var CreateTrajectoryInputSchema = z.object({
|
|
@@ -567,11 +567,11 @@ function extractDecisions(trajectory) {
|
|
|
567
567
|
}
|
|
568
568
|
|
|
569
569
|
// src/storage/file.ts
|
|
570
|
-
function expandPath(
|
|
571
|
-
if (
|
|
572
|
-
return join(process.env.HOME ?? "",
|
|
570
|
+
function expandPath(path2) {
|
|
571
|
+
if (path2.startsWith("~")) {
|
|
572
|
+
return join(process.env.HOME ?? "", path2.slice(1));
|
|
573
573
|
}
|
|
574
|
-
return
|
|
574
|
+
return path2;
|
|
575
575
|
}
|
|
576
576
|
function getSearchPaths() {
|
|
577
577
|
const searchPathsEnv = process.env.TRAJECTORIES_SEARCH_PATHS;
|
|
@@ -616,11 +616,129 @@ var FileStorage = class {
|
|
|
616
616
|
trajectories: {}
|
|
617
617
|
});
|
|
618
618
|
}
|
|
619
|
+
await this.reconcileIndex();
|
|
620
|
+
}
|
|
621
|
+
/**
|
|
622
|
+
* Scan active/ and completed/ recursively and add any trajectory files
|
|
623
|
+
* missing from the index. Existing entries are preserved — reconcile
|
|
624
|
+
* only adds, never removes.
|
|
625
|
+
*
|
|
626
|
+
* Handles three on-disk layouts in completed/:
|
|
627
|
+
* - flat: completed/{id}.json (legacy workforce data)
|
|
628
|
+
* - monthly: completed/YYYY-MM/{id}.json (current save() writes)
|
|
629
|
+
* - nested: completed/.../{id}.json (defensive — any depth)
|
|
630
|
+
*
|
|
631
|
+
* Returns a ReconcileSummary so tests and CLI wrappers can observe
|
|
632
|
+
* outcomes without parsing logs. Only writes the index if anything was
|
|
633
|
+
* added.
|
|
634
|
+
*/
|
|
635
|
+
async reconcileIndex() {
|
|
636
|
+
const summary = {
|
|
637
|
+
scanned: 0,
|
|
638
|
+
added: 0,
|
|
639
|
+
alreadyIndexed: 0,
|
|
640
|
+
skippedMalformedJson: 0,
|
|
641
|
+
skippedSchemaViolation: 0,
|
|
642
|
+
skippedIoError: 0
|
|
643
|
+
};
|
|
644
|
+
const index = await this.loadIndex();
|
|
645
|
+
const before = Object.keys(index.trajectories).length;
|
|
646
|
+
const discovered = [];
|
|
647
|
+
try {
|
|
648
|
+
const activeFiles = await readdir(this.activeDir);
|
|
649
|
+
for (const file of activeFiles) {
|
|
650
|
+
if (!file.endsWith(".json")) continue;
|
|
651
|
+
discovered.push(join(this.activeDir, file));
|
|
652
|
+
}
|
|
653
|
+
} catch (error) {
|
|
654
|
+
if (error.code !== "ENOENT") throw error;
|
|
655
|
+
}
|
|
656
|
+
await this.walkJsonFilesInto(this.completedDir, discovered);
|
|
657
|
+
for (const filePath of discovered) {
|
|
658
|
+
summary.scanned += 1;
|
|
659
|
+
const result = await this.readTrajectoryFile(filePath);
|
|
660
|
+
if (!result.ok) {
|
|
661
|
+
if (result.reason === "malformed_json") {
|
|
662
|
+
summary.skippedMalformedJson += 1;
|
|
663
|
+
} else if (result.reason === "schema_violation") {
|
|
664
|
+
summary.skippedSchemaViolation += 1;
|
|
665
|
+
} else {
|
|
666
|
+
summary.skippedIoError += 1;
|
|
667
|
+
}
|
|
668
|
+
continue;
|
|
669
|
+
}
|
|
670
|
+
const trajectory = result.trajectory;
|
|
671
|
+
if (index.trajectories[trajectory.id]) {
|
|
672
|
+
summary.alreadyIndexed += 1;
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
index.trajectories[trajectory.id] = {
|
|
676
|
+
title: trajectory.task.title,
|
|
677
|
+
status: trajectory.status,
|
|
678
|
+
startedAt: trajectory.startedAt,
|
|
679
|
+
completedAt: trajectory.completedAt,
|
|
680
|
+
path: filePath
|
|
681
|
+
};
|
|
682
|
+
summary.added += 1;
|
|
683
|
+
}
|
|
684
|
+
if (Object.keys(index.trajectories).length !== before) {
|
|
685
|
+
await this.saveIndex(index);
|
|
686
|
+
}
|
|
687
|
+
const hadSkips = summary.skippedMalformedJson + summary.skippedSchemaViolation + summary.skippedIoError > 0;
|
|
688
|
+
if (summary.added > 0 || hadSkips) {
|
|
689
|
+
const parts = [`reconciled ${summary.added}/${summary.scanned}`];
|
|
690
|
+
if (summary.skippedMalformedJson > 0) {
|
|
691
|
+
parts.push(`malformed: ${summary.skippedMalformedJson}`);
|
|
692
|
+
}
|
|
693
|
+
if (summary.skippedSchemaViolation > 0) {
|
|
694
|
+
parts.push(`invalid: ${summary.skippedSchemaViolation}`);
|
|
695
|
+
}
|
|
696
|
+
if (summary.skippedIoError > 0) {
|
|
697
|
+
parts.push(`io: ${summary.skippedIoError}`);
|
|
698
|
+
}
|
|
699
|
+
console.warn(`[trajectories] ${parts.join(", ")}`);
|
|
700
|
+
}
|
|
701
|
+
return summary;
|
|
702
|
+
}
|
|
703
|
+
/**
|
|
704
|
+
* Recursively collect all .json file paths under `dir` into `out`.
|
|
705
|
+
* Silently treats a missing directory as empty.
|
|
706
|
+
*/
|
|
707
|
+
async walkJsonFilesInto(dir, out) {
|
|
708
|
+
let entries;
|
|
709
|
+
try {
|
|
710
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
711
|
+
} catch (error) {
|
|
712
|
+
if (error.code === "ENOENT") return;
|
|
713
|
+
throw error;
|
|
714
|
+
}
|
|
715
|
+
for (const entry of entries) {
|
|
716
|
+
const entryPath = join(dir, entry.name);
|
|
717
|
+
if (entry.isDirectory()) {
|
|
718
|
+
await this.walkJsonFilesInto(entryPath, out);
|
|
719
|
+
} else if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
720
|
+
out.push(entryPath);
|
|
721
|
+
}
|
|
722
|
+
}
|
|
619
723
|
}
|
|
620
724
|
/**
|
|
621
|
-
* Save a trajectory
|
|
725
|
+
* Save a trajectory.
|
|
726
|
+
*
|
|
727
|
+
* Validates the input against the trajectory schema before touching
|
|
728
|
+
* disk. Closes the historical read/write asymmetry where save() would
|
|
729
|
+
* happily write data that the reader then rejected, producing files
|
|
730
|
+
* that could never be loaded back.
|
|
622
731
|
*/
|
|
623
|
-
async save(
|
|
732
|
+
async save(input) {
|
|
733
|
+
const validation = validateTrajectory(input);
|
|
734
|
+
if (!validation.success) {
|
|
735
|
+
const issues = validation.errors?.issues.map((issue) => {
|
|
736
|
+
const path2 = issue.path.length > 0 ? issue.path.join(".") : "root";
|
|
737
|
+
return `${path2}: ${issue.message}`;
|
|
738
|
+
}).join("; ") ?? "unknown validation error";
|
|
739
|
+
throw new Error(`Cannot save invalid trajectory: ${issues}`);
|
|
740
|
+
}
|
|
741
|
+
const trajectory = validation.data;
|
|
624
742
|
const isCompleted = trajectory.status === "completed" || trajectory.status === "abandoned";
|
|
625
743
|
let filePath;
|
|
626
744
|
if (isCompleted) {
|
|
@@ -650,19 +768,23 @@ var FileStorage = class {
|
|
|
650
768
|
async get(id) {
|
|
651
769
|
const activePath = join(this.activeDir, `${id}.json`);
|
|
652
770
|
if (existsSync(activePath)) {
|
|
653
|
-
return this.
|
|
771
|
+
return this.readTrajectoryOrNull(activePath);
|
|
654
772
|
}
|
|
655
773
|
const index = await this.loadIndex();
|
|
656
774
|
const entry = index.trajectories[id];
|
|
657
775
|
if (entry?.path && existsSync(entry.path)) {
|
|
658
|
-
return this.
|
|
776
|
+
return this.readTrajectoryOrNull(entry.path);
|
|
659
777
|
}
|
|
660
778
|
try {
|
|
779
|
+
const flatPath = join(this.completedDir, `${id}.json`);
|
|
780
|
+
if (existsSync(flatPath)) {
|
|
781
|
+
return this.readTrajectoryOrNull(flatPath);
|
|
782
|
+
}
|
|
661
783
|
const months = await readdir(this.completedDir);
|
|
662
784
|
for (const month of months) {
|
|
663
785
|
const filePath = join(this.completedDir, month, `${id}.json`);
|
|
664
786
|
if (existsSync(filePath)) {
|
|
665
|
-
return this.
|
|
787
|
+
return this.readTrajectoryOrNull(filePath);
|
|
666
788
|
}
|
|
667
789
|
}
|
|
668
790
|
} catch (error) {
|
|
@@ -685,7 +807,7 @@ var FileStorage = class {
|
|
|
685
807
|
let mostRecent = null;
|
|
686
808
|
let mostRecentTime = 0;
|
|
687
809
|
for (const file of jsonFiles) {
|
|
688
|
-
const trajectory = await this.
|
|
810
|
+
const trajectory = await this.readTrajectoryOrNull(
|
|
689
811
|
join(this.activeDir, file)
|
|
690
812
|
);
|
|
691
813
|
if (trajectory) {
|
|
@@ -813,20 +935,44 @@ var FileStorage = class {
|
|
|
813
935
|
async close() {
|
|
814
936
|
}
|
|
815
937
|
// Private helpers
|
|
816
|
-
|
|
938
|
+
/**
|
|
939
|
+
* Read a trajectory file and return a tagged result so callers can
|
|
940
|
+
* distinguish missing files, malformed JSON, and schema violations.
|
|
941
|
+
*
|
|
942
|
+
* Does NOT log. Callers choose whether to warn, swallow, or throw.
|
|
943
|
+
*/
|
|
944
|
+
async readTrajectoryFile(path2) {
|
|
945
|
+
let content;
|
|
817
946
|
try {
|
|
818
|
-
|
|
819
|
-
const data = JSON.parse(content);
|
|
820
|
-
const validation = validateTrajectory(data);
|
|
821
|
-
if (validation.success) {
|
|
822
|
-
return validation.data;
|
|
823
|
-
}
|
|
824
|
-
console.error(`Invalid trajectory at ${path}:`, validation.errors);
|
|
825
|
-
return null;
|
|
947
|
+
content = await readFile(path2, "utf-8");
|
|
826
948
|
} catch (error) {
|
|
827
|
-
|
|
828
|
-
return null;
|
|
949
|
+
return { ok: false, reason: "io_error", path: path2, error };
|
|
829
950
|
}
|
|
951
|
+
let data;
|
|
952
|
+
try {
|
|
953
|
+
data = JSON.parse(content);
|
|
954
|
+
} catch (error) {
|
|
955
|
+
return { ok: false, reason: "malformed_json", path: path2, error };
|
|
956
|
+
}
|
|
957
|
+
const validation = validateTrajectory(data);
|
|
958
|
+
if (validation.success) {
|
|
959
|
+
return { ok: true, trajectory: validation.data };
|
|
960
|
+
}
|
|
961
|
+
return {
|
|
962
|
+
ok: false,
|
|
963
|
+
reason: "schema_violation",
|
|
964
|
+
path: path2,
|
|
965
|
+
error: validation.errors
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
/**
|
|
969
|
+
* Convenience wrapper for callers that only care whether they got a
|
|
970
|
+
* trajectory. Returns null for any failure and writes nothing to the
|
|
971
|
+
* console — so nothing leaks into test output or the CLI spinner.
|
|
972
|
+
*/
|
|
973
|
+
async readTrajectoryOrNull(path2) {
|
|
974
|
+
const result = await this.readTrajectoryFile(path2);
|
|
975
|
+
return result.ok ? result.trajectory : null;
|
|
830
976
|
}
|
|
831
977
|
async loadIndex() {
|
|
832
978
|
try {
|
|
@@ -1662,9 +1808,9 @@ async function resolveCLIProvider() {
|
|
|
1662
1808
|
return SUPPORTED_CLIS;
|
|
1663
1809
|
})();
|
|
1664
1810
|
for (const cli of clisToTry) {
|
|
1665
|
-
const
|
|
1666
|
-
if (
|
|
1667
|
-
return new CLIProvider(cli,
|
|
1811
|
+
const path2 = await findBinary(cli);
|
|
1812
|
+
if (path2) {
|
|
1813
|
+
return new CLIProvider(cli, path2);
|
|
1668
1814
|
}
|
|
1669
1815
|
}
|
|
1670
1816
|
return null;
|
|
@@ -1672,8 +1818,8 @@ async function resolveCLIProvider() {
|
|
|
1672
1818
|
async function findBinary(name) {
|
|
1673
1819
|
try {
|
|
1674
1820
|
const { stdout } = await execFileAsync("which", [name]);
|
|
1675
|
-
const
|
|
1676
|
-
if (
|
|
1821
|
+
const path2 = stdout.trim();
|
|
1822
|
+
if (path2) return path2;
|
|
1677
1823
|
} catch {
|
|
1678
1824
|
}
|
|
1679
1825
|
const home = homedir();
|
|
@@ -2775,8 +2921,8 @@ function generateTrace(trajectory, startRef) {
|
|
|
2775
2921
|
return null;
|
|
2776
2922
|
}
|
|
2777
2923
|
const model = detectModel();
|
|
2778
|
-
const traceFiles = changedFiles.map(({ path, ranges }) => ({
|
|
2779
|
-
path,
|
|
2924
|
+
const traceFiles = changedFiles.map(({ path: path2, ranges }) => ({
|
|
2925
|
+
path: path2,
|
|
2780
2926
|
conversations: [
|
|
2781
2927
|
{
|
|
2782
2928
|
contributor: {
|
|
@@ -2923,8 +3069,11 @@ if [ -z "$ACTIVE_FILE" ]; then
|
|
|
2923
3069
|
exit 0
|
|
2924
3070
|
fi
|
|
2925
3071
|
|
|
2926
|
-
# Extract trajectory ID (grep for the "id" field)
|
|
2927
|
-
|
|
3072
|
+
# Extract trajectory ID (grep for the "id" field). Character class must
|
|
3073
|
+
# include underscore to match legacy traj_<timestamp>_<hex> ids -- without
|
|
3074
|
+
# it, grep -o silently truncates at the first internal underscore and
|
|
3075
|
+
# emits a wrong (shorter) id into the commit trailer.
|
|
3076
|
+
TRAJ_ID=$(grep -o '"id"[[:space:]]*:[[:space:]]*"traj_[a-z0-9_]*"' "$ACTIVE_FILE" | head -1 | grep -o 'traj_[a-z0-9_]*')
|
|
2928
3077
|
if [ -z "$TRAJ_ID" ]; then
|
|
2929
3078
|
exit 0
|
|
2930
3079
|
fi
|
|
@@ -3882,19 +4031,19 @@ function registerExportCommand(program2) {
|
|
|
3882
4031
|
}
|
|
3883
4032
|
});
|
|
3884
4033
|
}
|
|
3885
|
-
function openInBrowser(
|
|
4034
|
+
function openInBrowser(path2) {
|
|
3886
4035
|
const platform = process.platform;
|
|
3887
4036
|
let command;
|
|
3888
4037
|
if (platform === "darwin") {
|
|
3889
|
-
command = `open "${
|
|
4038
|
+
command = `open "${path2}"`;
|
|
3890
4039
|
} else if (platform === "win32") {
|
|
3891
|
-
command = `start "" "${
|
|
4040
|
+
command = `start "" "${path2}"`;
|
|
3892
4041
|
} else {
|
|
3893
|
-
command = `xdg-open "${
|
|
4042
|
+
command = `xdg-open "${path2}"`;
|
|
3894
4043
|
}
|
|
3895
4044
|
exec(command, (error) => {
|
|
3896
4045
|
if (error) {
|
|
3897
|
-
console.log(`Open manually: file://${
|
|
4046
|
+
console.log(`Open manually: file://${path2}`);
|
|
3898
4047
|
}
|
|
3899
4048
|
});
|
|
3900
4049
|
}
|
|
@@ -4399,8 +4548,37 @@ function registerCommands(program2) {
|
|
|
4399
4548
|
registerCompactCommand(program2);
|
|
4400
4549
|
}
|
|
4401
4550
|
|
|
4551
|
+
// src/cli/version.ts
|
|
4552
|
+
import fs from "fs";
|
|
4553
|
+
import path from "path";
|
|
4554
|
+
import { fileURLToPath } from "url";
|
|
4555
|
+
function findPackageJson(startDir) {
|
|
4556
|
+
let dir = startDir;
|
|
4557
|
+
while (dir !== path.dirname(dir)) {
|
|
4558
|
+
const candidate = path.join(dir, "package.json");
|
|
4559
|
+
if (fs.existsSync(candidate)) {
|
|
4560
|
+
return candidate;
|
|
4561
|
+
}
|
|
4562
|
+
dir = path.dirname(dir);
|
|
4563
|
+
}
|
|
4564
|
+
throw new Error("Could not find package.json");
|
|
4565
|
+
}
|
|
4566
|
+
function resolveCliVersion() {
|
|
4567
|
+
try {
|
|
4568
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
4569
|
+
const packageJsonPath = findPackageJson(here);
|
|
4570
|
+
const packageJson = JSON.parse(
|
|
4571
|
+
fs.readFileSync(packageJsonPath, "utf-8")
|
|
4572
|
+
);
|
|
4573
|
+
return packageJson.version ?? "unknown";
|
|
4574
|
+
} catch {
|
|
4575
|
+
return "unknown";
|
|
4576
|
+
}
|
|
4577
|
+
}
|
|
4578
|
+
var VERSION = resolveCliVersion();
|
|
4579
|
+
|
|
4402
4580
|
// src/cli/index.ts
|
|
4403
|
-
program.name("trail").description("Leave a trail of your work for others to follow").version(
|
|
4581
|
+
program.name("trail").description("Leave a trail of your work for others to follow").version(VERSION).option(
|
|
4404
4582
|
"--data-dir <path>",
|
|
4405
4583
|
"Override trajectory storage directory (or set TRAJECTORIES_DATA_DIR)"
|
|
4406
4584
|
).hook("preAction", (thisCommand) => {
|