@skillkit/core 1.18.0 → 1.19.1
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/index.d.ts +139 -1
- package/dist/index.js +674 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -8786,6 +8786,676 @@ var SessionExplainer = class {
|
|
|
8786
8786
|
}
|
|
8787
8787
|
};
|
|
8788
8788
|
|
|
8789
|
+
// src/session/timeline.ts
|
|
8790
|
+
function formatDuration2(ms) {
|
|
8791
|
+
const seconds = Math.floor(ms / 1e3);
|
|
8792
|
+
const minutes = Math.floor(seconds / 60);
|
|
8793
|
+
const hours = Math.floor(minutes / 60);
|
|
8794
|
+
if (hours > 0) return `${hours}h ${minutes % 60}m`;
|
|
8795
|
+
if (minutes > 0) return `${minutes}m`;
|
|
8796
|
+
return `${seconds}s`;
|
|
8797
|
+
}
|
|
8798
|
+
function formatTime(iso) {
|
|
8799
|
+
const d = new Date(iso);
|
|
8800
|
+
if (isNaN(d.getTime())) return iso;
|
|
8801
|
+
return d.toLocaleTimeString("en-US", { hour: "numeric", minute: "2-digit", hour12: true });
|
|
8802
|
+
}
|
|
8803
|
+
var SessionTimeline = class {
|
|
8804
|
+
projectPath;
|
|
8805
|
+
sessionManager;
|
|
8806
|
+
activityLog;
|
|
8807
|
+
snapshotManager;
|
|
8808
|
+
constructor(projectPath) {
|
|
8809
|
+
this.projectPath = projectPath;
|
|
8810
|
+
this.sessionManager = new SessionManager(projectPath);
|
|
8811
|
+
this.activityLog = new ActivityLog(projectPath);
|
|
8812
|
+
this.snapshotManager = new SnapshotManager(projectPath);
|
|
8813
|
+
}
|
|
8814
|
+
build(options) {
|
|
8815
|
+
const events = [];
|
|
8816
|
+
const state = this.sessionManager.get();
|
|
8817
|
+
const limit = options?.limit ?? 50;
|
|
8818
|
+
const includeGit = options?.includeGit !== false;
|
|
8819
|
+
const sinceDate = options?.since ? new Date(options.since) : void 0;
|
|
8820
|
+
if (sinceDate && isNaN(sinceDate.getTime())) {
|
|
8821
|
+
return { projectPath: this.projectPath, sessionDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0], events: [], totalCount: 0 };
|
|
8822
|
+
}
|
|
8823
|
+
if (state?.currentExecution) {
|
|
8824
|
+
const exec2 = state.currentExecution;
|
|
8825
|
+
events.push({
|
|
8826
|
+
timestamp: exec2.startedAt,
|
|
8827
|
+
type: "skill_start",
|
|
8828
|
+
source: exec2.skillName,
|
|
8829
|
+
summary: `${exec2.skillName} started`,
|
|
8830
|
+
details: { status: exec2.status, totalSteps: exec2.totalSteps }
|
|
8831
|
+
});
|
|
8832
|
+
const activeStatuses = /* @__PURE__ */ new Set(["in_progress", "completed", "failed"]);
|
|
8833
|
+
for (const task of exec2.tasks) {
|
|
8834
|
+
if (task.startedAt && activeStatuses.has(task.status)) {
|
|
8835
|
+
events.push({
|
|
8836
|
+
timestamp: task.completedAt ?? task.startedAt,
|
|
8837
|
+
type: "task_progress",
|
|
8838
|
+
source: exec2.skillName,
|
|
8839
|
+
summary: `${task.name} (${task.status})`,
|
|
8840
|
+
details: { taskId: task.id, error: task.error }
|
|
8841
|
+
});
|
|
8842
|
+
}
|
|
8843
|
+
}
|
|
8844
|
+
}
|
|
8845
|
+
for (const hist of state?.history ?? []) {
|
|
8846
|
+
events.push({
|
|
8847
|
+
timestamp: hist.completedAt,
|
|
8848
|
+
type: "skill_complete",
|
|
8849
|
+
source: hist.skillName,
|
|
8850
|
+
summary: `${hist.skillName} ${hist.status} (${formatDuration2(hist.durationMs)})`,
|
|
8851
|
+
details: {
|
|
8852
|
+
durationMs: hist.durationMs,
|
|
8853
|
+
commits: hist.commits,
|
|
8854
|
+
filesModified: hist.filesModified
|
|
8855
|
+
}
|
|
8856
|
+
});
|
|
8857
|
+
}
|
|
8858
|
+
if (includeGit) {
|
|
8859
|
+
try {
|
|
8860
|
+
const commits = getGitCommits(this.projectPath, {
|
|
8861
|
+
commits: 50,
|
|
8862
|
+
since: options?.since
|
|
8863
|
+
});
|
|
8864
|
+
for (const commit of commits) {
|
|
8865
|
+
const activity = this.activityLog.getByCommit(commit.shortHash);
|
|
8866
|
+
events.push({
|
|
8867
|
+
timestamp: commit.date,
|
|
8868
|
+
type: "git_commit",
|
|
8869
|
+
source: activity?.activeSkills?.join(", ") ?? "git",
|
|
8870
|
+
summary: `${commit.shortHash} \u2014 ${commit.message} (${commit.files.length} files)`,
|
|
8871
|
+
details: {
|
|
8872
|
+
sha: commit.hash,
|
|
8873
|
+
author: commit.author,
|
|
8874
|
+
skills: activity?.activeSkills
|
|
8875
|
+
}
|
|
8876
|
+
});
|
|
8877
|
+
}
|
|
8878
|
+
} catch {
|
|
8879
|
+
}
|
|
8880
|
+
}
|
|
8881
|
+
try {
|
|
8882
|
+
const observations = ObservationStore.readAll(this.projectPath);
|
|
8883
|
+
for (const obs of observations) {
|
|
8884
|
+
events.push({
|
|
8885
|
+
timestamp: obs.timestamp,
|
|
8886
|
+
type: "observation",
|
|
8887
|
+
source: String(obs.agent),
|
|
8888
|
+
summary: `${obs.type}: ${obs.content?.action ?? "unknown"}`,
|
|
8889
|
+
details: { observationId: obs.id, ...obs.content }
|
|
8890
|
+
});
|
|
8891
|
+
}
|
|
8892
|
+
} catch {
|
|
8893
|
+
}
|
|
8894
|
+
for (const decision of state?.decisions ?? []) {
|
|
8895
|
+
events.push({
|
|
8896
|
+
timestamp: decision.madeAt,
|
|
8897
|
+
type: "decision",
|
|
8898
|
+
source: decision.skillName ?? "user",
|
|
8899
|
+
summary: `${decision.key} \u2192 ${decision.value}`
|
|
8900
|
+
});
|
|
8901
|
+
}
|
|
8902
|
+
try {
|
|
8903
|
+
const snapshots = this.snapshotManager.list();
|
|
8904
|
+
for (const snap of snapshots) {
|
|
8905
|
+
events.push({
|
|
8906
|
+
timestamp: snap.createdAt,
|
|
8907
|
+
type: "snapshot",
|
|
8908
|
+
source: "snapshot",
|
|
8909
|
+
summary: `snapshot: ${snap.name}${snap.description ? ` \u2014 ${snap.description}` : ""}`,
|
|
8910
|
+
details: { skillCount: snap.skillCount }
|
|
8911
|
+
});
|
|
8912
|
+
}
|
|
8913
|
+
} catch {
|
|
8914
|
+
}
|
|
8915
|
+
let filtered = events;
|
|
8916
|
+
if (sinceDate) {
|
|
8917
|
+
filtered = filtered.filter((e) => new Date(e.timestamp) >= sinceDate);
|
|
8918
|
+
}
|
|
8919
|
+
if (options?.types && options.types.length > 0) {
|
|
8920
|
+
const typeSet = new Set(options.types);
|
|
8921
|
+
filtered = filtered.filter((e) => typeSet.has(e.type));
|
|
8922
|
+
}
|
|
8923
|
+
if (options?.skillFilter) {
|
|
8924
|
+
const skill = options.skillFilter;
|
|
8925
|
+
filtered = filtered.filter(
|
|
8926
|
+
(e) => e.source.includes(skill) || e.details?.skills?.includes(skill)
|
|
8927
|
+
);
|
|
8928
|
+
}
|
|
8929
|
+
filtered.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
8930
|
+
const totalCount = filtered.length;
|
|
8931
|
+
const limited = filtered.slice(-limit);
|
|
8932
|
+
return {
|
|
8933
|
+
projectPath: this.projectPath,
|
|
8934
|
+
sessionDate: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
|
|
8935
|
+
events: limited,
|
|
8936
|
+
totalCount
|
|
8937
|
+
};
|
|
8938
|
+
}
|
|
8939
|
+
formatText(data) {
|
|
8940
|
+
const lines = [];
|
|
8941
|
+
lines.push(" Session Timeline\n");
|
|
8942
|
+
if (data.events.length === 0) {
|
|
8943
|
+
lines.push(" No events found.\n");
|
|
8944
|
+
return lines.join("\n");
|
|
8945
|
+
}
|
|
8946
|
+
const icons = {
|
|
8947
|
+
skill_start: ">",
|
|
8948
|
+
skill_complete: "\u2713",
|
|
8949
|
+
task_progress: "\u2022",
|
|
8950
|
+
git_commit: "*",
|
|
8951
|
+
observation: "!",
|
|
8952
|
+
decision: "?",
|
|
8953
|
+
snapshot: "\u25C9"
|
|
8954
|
+
};
|
|
8955
|
+
let lastTimeBlock = "";
|
|
8956
|
+
for (const event of data.events) {
|
|
8957
|
+
const time = formatTime(event.timestamp);
|
|
8958
|
+
if (time !== lastTimeBlock) {
|
|
8959
|
+
if (lastTimeBlock) lines.push("");
|
|
8960
|
+
lines.push(` ${time}`);
|
|
8961
|
+
lastTimeBlock = time;
|
|
8962
|
+
}
|
|
8963
|
+
const icon = icons[event.type] ?? "\u2022";
|
|
8964
|
+
lines.push(` ${icon} ${event.summary}`);
|
|
8965
|
+
}
|
|
8966
|
+
lines.push("");
|
|
8967
|
+
if (data.totalCount > data.events.length) {
|
|
8968
|
+
lines.push(` Showing ${data.events.length} of ${data.totalCount} events`);
|
|
8969
|
+
} else {
|
|
8970
|
+
lines.push(` ${data.totalCount} ${data.totalCount === 1 ? "event" : "events"} total`);
|
|
8971
|
+
}
|
|
8972
|
+
return lines.join("\n");
|
|
8973
|
+
}
|
|
8974
|
+
formatJson(data) {
|
|
8975
|
+
return JSON.stringify(data, null, 2);
|
|
8976
|
+
}
|
|
8977
|
+
};
|
|
8978
|
+
|
|
8979
|
+
// src/session/handoff.ts
|
|
8980
|
+
function formatDuration3(ms) {
|
|
8981
|
+
const seconds = Math.floor(ms / 1e3);
|
|
8982
|
+
const minutes = Math.floor(seconds / 60);
|
|
8983
|
+
const hours = Math.floor(minutes / 60);
|
|
8984
|
+
if (hours > 0) return `${hours}h ${minutes % 60}m`;
|
|
8985
|
+
if (minutes > 0) return `${minutes}m`;
|
|
8986
|
+
return `${seconds}s`;
|
|
8987
|
+
}
|
|
8988
|
+
var SessionHandoff = class {
|
|
8989
|
+
projectPath;
|
|
8990
|
+
sessionManager;
|
|
8991
|
+
activityLog;
|
|
8992
|
+
constructor(projectPath) {
|
|
8993
|
+
this.projectPath = projectPath;
|
|
8994
|
+
this.sessionManager = new SessionManager(projectPath);
|
|
8995
|
+
this.activityLog = new ActivityLog(projectPath);
|
|
8996
|
+
}
|
|
8997
|
+
generate(options) {
|
|
8998
|
+
const state = this.sessionManager.get();
|
|
8999
|
+
const includeGit = options?.includeGit !== false;
|
|
9000
|
+
const includeObservations = options?.includeObservations !== false;
|
|
9001
|
+
const maxObservations = options?.maxObservations ?? 20;
|
|
9002
|
+
let agent = "unknown";
|
|
9003
|
+
try {
|
|
9004
|
+
const configAgent = loadConfig().agent;
|
|
9005
|
+
if (configAgent && configAgent !== "universal") {
|
|
9006
|
+
agent = configAgent;
|
|
9007
|
+
}
|
|
9008
|
+
} catch {
|
|
9009
|
+
}
|
|
9010
|
+
if (agent === "unknown" && state?.currentExecution?.skillSource) {
|
|
9011
|
+
agent = state.currentExecution.skillSource;
|
|
9012
|
+
}
|
|
9013
|
+
const accomplished = this.buildAccomplished(state?.currentExecution?.tasks, state?.history ?? [], includeGit);
|
|
9014
|
+
const pending = this.buildPending(state?.currentExecution?.tasks);
|
|
9015
|
+
const keyFiles = this.buildKeyFiles(state?.currentExecution?.tasks, state?.history ?? []);
|
|
9016
|
+
const observations = includeObservations ? this.buildObservations(maxObservations) : { errors: [], solutions: [], patterns: [] };
|
|
9017
|
+
const recommendations = this.buildRecommendations(pending, observations, includeGit);
|
|
9018
|
+
return {
|
|
9019
|
+
generatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
9020
|
+
fromAgent: agent,
|
|
9021
|
+
projectPath: this.projectPath,
|
|
9022
|
+
accomplished,
|
|
9023
|
+
pending,
|
|
9024
|
+
keyFiles,
|
|
9025
|
+
observations,
|
|
9026
|
+
recommendations
|
|
9027
|
+
};
|
|
9028
|
+
}
|
|
9029
|
+
buildAccomplished(tasks, history, includeGit) {
|
|
9030
|
+
const section = { tasks: [], commits: [] };
|
|
9031
|
+
for (const task of tasks ?? []) {
|
|
9032
|
+
if (task.status === "completed") {
|
|
9033
|
+
let duration;
|
|
9034
|
+
if (task.startedAt && task.completedAt) {
|
|
9035
|
+
duration = formatDuration3(
|
|
9036
|
+
new Date(task.completedAt).getTime() - new Date(task.startedAt).getTime()
|
|
9037
|
+
);
|
|
9038
|
+
}
|
|
9039
|
+
section.tasks.push({
|
|
9040
|
+
name: task.name,
|
|
9041
|
+
duration,
|
|
9042
|
+
commitSha: task.commitSha
|
|
9043
|
+
});
|
|
9044
|
+
}
|
|
9045
|
+
}
|
|
9046
|
+
for (const hist of history) {
|
|
9047
|
+
if (hist.status === "completed") {
|
|
9048
|
+
section.tasks.push({
|
|
9049
|
+
name: hist.skillName,
|
|
9050
|
+
duration: Number.isFinite(hist.durationMs) ? formatDuration3(hist.durationMs) : void 0,
|
|
9051
|
+
commitSha: hist.commits?.[0]
|
|
9052
|
+
});
|
|
9053
|
+
}
|
|
9054
|
+
}
|
|
9055
|
+
if (includeGit) {
|
|
9056
|
+
try {
|
|
9057
|
+
const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
|
|
9058
|
+
const commits = getGitCommits(this.projectPath, { commits: 50 });
|
|
9059
|
+
for (const commit of commits) {
|
|
9060
|
+
if (commit.date.startsWith(today)) {
|
|
9061
|
+
section.commits.push({
|
|
9062
|
+
sha: commit.shortHash,
|
|
9063
|
+
message: commit.message,
|
|
9064
|
+
filesCount: commit.files.length
|
|
9065
|
+
});
|
|
9066
|
+
}
|
|
9067
|
+
}
|
|
9068
|
+
} catch {
|
|
9069
|
+
}
|
|
9070
|
+
}
|
|
9071
|
+
return section;
|
|
9072
|
+
}
|
|
9073
|
+
buildPending(tasks) {
|
|
9074
|
+
const section = { tasks: [], commits: [] };
|
|
9075
|
+
const pendingStatuses = /* @__PURE__ */ new Set(["pending", "in_progress", "paused"]);
|
|
9076
|
+
for (const task of tasks ?? []) {
|
|
9077
|
+
if (pendingStatuses.has(task.status)) {
|
|
9078
|
+
section.tasks.push({ name: task.name });
|
|
9079
|
+
}
|
|
9080
|
+
}
|
|
9081
|
+
return section;
|
|
9082
|
+
}
|
|
9083
|
+
buildKeyFiles(tasks, history) {
|
|
9084
|
+
const fileMap = /* @__PURE__ */ new Map();
|
|
9085
|
+
for (const task of tasks ?? []) {
|
|
9086
|
+
for (const f of task.filesModified ?? []) {
|
|
9087
|
+
fileMap.set(f, task.status === "completed" ? "modified" : "in-progress");
|
|
9088
|
+
}
|
|
9089
|
+
}
|
|
9090
|
+
for (const hist of history) {
|
|
9091
|
+
for (const f of hist.filesModified ?? []) {
|
|
9092
|
+
if (!fileMap.has(f)) {
|
|
9093
|
+
fileMap.set(f, "modified");
|
|
9094
|
+
}
|
|
9095
|
+
}
|
|
9096
|
+
}
|
|
9097
|
+
return Array.from(fileMap.entries()).map(([path4, changeType]) => ({ path: path4, changeType })).sort((a, b) => a.path.localeCompare(b.path));
|
|
9098
|
+
}
|
|
9099
|
+
buildObservations(max) {
|
|
9100
|
+
const result = {
|
|
9101
|
+
errors: [],
|
|
9102
|
+
solutions: [],
|
|
9103
|
+
patterns: []
|
|
9104
|
+
};
|
|
9105
|
+
try {
|
|
9106
|
+
const observations = ObservationStore.readAll(this.projectPath);
|
|
9107
|
+
const sorted = [...observations].sort((a, b) => b.relevance - a.relevance);
|
|
9108
|
+
let count = 0;
|
|
9109
|
+
for (const obs of sorted) {
|
|
9110
|
+
if (count >= max) break;
|
|
9111
|
+
if (obs.type === "error" && obs.content.error) {
|
|
9112
|
+
result.errors.push({ action: obs.content.action, error: obs.content.error });
|
|
9113
|
+
count++;
|
|
9114
|
+
} else if (obs.type === "solution" && obs.content.solution) {
|
|
9115
|
+
result.solutions.push({ action: obs.content.action, solution: obs.content.solution });
|
|
9116
|
+
count++;
|
|
9117
|
+
} else if (obs.type === "pattern" && obs.content.context) {
|
|
9118
|
+
result.patterns.push({ action: obs.content.action, context: obs.content.context });
|
|
9119
|
+
count++;
|
|
9120
|
+
}
|
|
9121
|
+
}
|
|
9122
|
+
} catch {
|
|
9123
|
+
}
|
|
9124
|
+
return result;
|
|
9125
|
+
}
|
|
9126
|
+
buildRecommendations(pending, observations, includeGit) {
|
|
9127
|
+
const recs = [];
|
|
9128
|
+
if (pending.tasks.length > 0) {
|
|
9129
|
+
recs.push(`Complete pending tasks: ${pending.tasks.map((t) => t.name).join(", ")}`);
|
|
9130
|
+
}
|
|
9131
|
+
if (observations.errors.length > 0) {
|
|
9132
|
+
const unresolved = observations.errors.filter(
|
|
9133
|
+
(e) => !observations.solutions.some((s) => s.action === e.action)
|
|
9134
|
+
);
|
|
9135
|
+
if (unresolved.length > 0) {
|
|
9136
|
+
recs.push(`Resolve ${unresolved.length} unresolved error(s)`);
|
|
9137
|
+
}
|
|
9138
|
+
}
|
|
9139
|
+
if (includeGit) {
|
|
9140
|
+
try {
|
|
9141
|
+
const activities = this.activityLog.getRecent(50);
|
|
9142
|
+
const fileCounts = /* @__PURE__ */ new Map();
|
|
9143
|
+
for (const activity of activities) {
|
|
9144
|
+
for (const f of activity.filesChanged) {
|
|
9145
|
+
fileCounts.set(f, (fileCounts.get(f) ?? 0) + 1);
|
|
9146
|
+
}
|
|
9147
|
+
}
|
|
9148
|
+
const highChurn = Array.from(fileCounts.entries()).filter(([, count]) => count >= 3).sort((a, b) => b[1] - a[1]).slice(0, 3);
|
|
9149
|
+
for (const [file, count] of highChurn) {
|
|
9150
|
+
recs.push(`Review high-churn file: ${file} (${count} changes)`);
|
|
9151
|
+
}
|
|
9152
|
+
} catch {
|
|
9153
|
+
}
|
|
9154
|
+
}
|
|
9155
|
+
return recs;
|
|
9156
|
+
}
|
|
9157
|
+
toMarkdown(doc) {
|
|
9158
|
+
const lines = [];
|
|
9159
|
+
lines.push(`# Session Handoff`);
|
|
9160
|
+
lines.push(`Agent: ${doc.fromAgent} | ${doc.generatedAt.split("T")[0]}
|
|
9161
|
+
`);
|
|
9162
|
+
lines.push(`## Accomplished`);
|
|
9163
|
+
if (doc.accomplished.tasks.length === 0 && doc.accomplished.commits.length === 0) {
|
|
9164
|
+
lines.push("No completed tasks.\n");
|
|
9165
|
+
} else {
|
|
9166
|
+
for (const task of doc.accomplished.tasks) {
|
|
9167
|
+
const dur = task.duration ? ` (${task.duration})` : "";
|
|
9168
|
+
const sha = task.commitSha ? ` [${task.commitSha.slice(0, 7)}]` : "";
|
|
9169
|
+
lines.push(`- ${task.name}${dur}${sha}`);
|
|
9170
|
+
}
|
|
9171
|
+
for (const commit of doc.accomplished.commits) {
|
|
9172
|
+
lines.push(`- ${commit.sha} \u2014 ${commit.message} (${commit.filesCount} files)`);
|
|
9173
|
+
}
|
|
9174
|
+
lines.push("");
|
|
9175
|
+
}
|
|
9176
|
+
lines.push(`## Pending`);
|
|
9177
|
+
if (doc.pending.tasks.length === 0) {
|
|
9178
|
+
lines.push("No pending tasks.\n");
|
|
9179
|
+
} else {
|
|
9180
|
+
for (const task of doc.pending.tasks) {
|
|
9181
|
+
lines.push(`- ${task.name}`);
|
|
9182
|
+
}
|
|
9183
|
+
lines.push("");
|
|
9184
|
+
}
|
|
9185
|
+
if (doc.keyFiles.length > 0) {
|
|
9186
|
+
lines.push(`## Key Files`);
|
|
9187
|
+
for (const f of doc.keyFiles) {
|
|
9188
|
+
lines.push(`- \`${f.path}\` (${f.changeType})`);
|
|
9189
|
+
}
|
|
9190
|
+
lines.push("");
|
|
9191
|
+
}
|
|
9192
|
+
const hasObs = doc.observations.errors.length > 0 || doc.observations.solutions.length > 0 || doc.observations.patterns.length > 0;
|
|
9193
|
+
if (hasObs) {
|
|
9194
|
+
lines.push(`## Observations`);
|
|
9195
|
+
for (const e of doc.observations.errors) {
|
|
9196
|
+
lines.push(`- Error: ${e.error} (${e.action})`);
|
|
9197
|
+
}
|
|
9198
|
+
for (const s of doc.observations.solutions) {
|
|
9199
|
+
lines.push(`- Solution: ${s.solution} (${s.action})`);
|
|
9200
|
+
}
|
|
9201
|
+
for (const p of doc.observations.patterns) {
|
|
9202
|
+
lines.push(`- Pattern: ${p.action} \u2014 ${p.context}`);
|
|
9203
|
+
}
|
|
9204
|
+
lines.push("");
|
|
9205
|
+
}
|
|
9206
|
+
if (doc.recommendations.length > 0) {
|
|
9207
|
+
lines.push(`## Recommendations`);
|
|
9208
|
+
doc.recommendations.forEach((r, i) => {
|
|
9209
|
+
lines.push(`${i + 1}. ${r}`);
|
|
9210
|
+
});
|
|
9211
|
+
lines.push("");
|
|
9212
|
+
}
|
|
9213
|
+
return lines.join("\n");
|
|
9214
|
+
}
|
|
9215
|
+
toJson(doc) {
|
|
9216
|
+
return JSON.stringify(doc, null, 2);
|
|
9217
|
+
}
|
|
9218
|
+
};
|
|
9219
|
+
|
|
9220
|
+
// src/session/lineage.ts
|
|
9221
|
+
function formatDurationMs(ms) {
|
|
9222
|
+
const seconds = Math.floor(ms / 1e3);
|
|
9223
|
+
const minutes = Math.floor(seconds / 60);
|
|
9224
|
+
const hours = Math.floor(minutes / 60);
|
|
9225
|
+
if (hours > 0) return `${hours}h ${minutes % 60}m`;
|
|
9226
|
+
if (minutes > 0) return `${minutes}m`;
|
|
9227
|
+
return `${seconds}s`;
|
|
9228
|
+
}
|
|
9229
|
+
var SkillLineage = class {
|
|
9230
|
+
projectPath;
|
|
9231
|
+
sessionManager;
|
|
9232
|
+
activityLog;
|
|
9233
|
+
constructor(projectPath) {
|
|
9234
|
+
this.projectPath = projectPath;
|
|
9235
|
+
this.sessionManager = new SessionManager(projectPath);
|
|
9236
|
+
this.activityLog = new ActivityLog(projectPath);
|
|
9237
|
+
}
|
|
9238
|
+
build(options) {
|
|
9239
|
+
const state = this.sessionManager.get();
|
|
9240
|
+
const sinceDate = options?.since ? new Date(options.since) : void 0;
|
|
9241
|
+
const skillMap = /* @__PURE__ */ new Map();
|
|
9242
|
+
const fileSkillMap = /* @__PURE__ */ new Map();
|
|
9243
|
+
const fileCommitCount = /* @__PURE__ */ new Map();
|
|
9244
|
+
const fileLastModified = /* @__PURE__ */ new Map();
|
|
9245
|
+
for (const hist of state?.history ?? []) {
|
|
9246
|
+
if (sinceDate && new Date(hist.completedAt) < sinceDate) continue;
|
|
9247
|
+
const entry = skillMap.get(hist.skillName) ?? {
|
|
9248
|
+
skillName: hist.skillName,
|
|
9249
|
+
executions: 0,
|
|
9250
|
+
totalDurationMs: 0,
|
|
9251
|
+
commits: [],
|
|
9252
|
+
filesModified: [],
|
|
9253
|
+
observationIds: [],
|
|
9254
|
+
firstSeen: hist.completedAt,
|
|
9255
|
+
lastSeen: hist.completedAt
|
|
9256
|
+
};
|
|
9257
|
+
entry.executions++;
|
|
9258
|
+
entry.totalDurationMs += hist.durationMs;
|
|
9259
|
+
entry.commits.push(...hist.commits ?? []);
|
|
9260
|
+
entry.filesModified.push(...hist.filesModified ?? []);
|
|
9261
|
+
if (new Date(hist.completedAt) < new Date(entry.firstSeen)) {
|
|
9262
|
+
entry.firstSeen = hist.completedAt;
|
|
9263
|
+
}
|
|
9264
|
+
if (new Date(hist.completedAt) > new Date(entry.lastSeen)) {
|
|
9265
|
+
entry.lastSeen = hist.completedAt;
|
|
9266
|
+
}
|
|
9267
|
+
skillMap.set(hist.skillName, entry);
|
|
9268
|
+
for (const f of hist.filesModified ?? []) {
|
|
9269
|
+
const skills2 = fileSkillMap.get(f) ?? /* @__PURE__ */ new Set();
|
|
9270
|
+
skills2.add(hist.skillName);
|
|
9271
|
+
fileSkillMap.set(f, skills2);
|
|
9272
|
+
fileCommitCount.set(f, (fileCommitCount.get(f) ?? 0) + (hist.commits?.length ?? 0));
|
|
9273
|
+
const existing = fileLastModified.get(f);
|
|
9274
|
+
if (!existing || new Date(hist.completedAt) > new Date(existing)) {
|
|
9275
|
+
fileLastModified.set(f, hist.completedAt);
|
|
9276
|
+
}
|
|
9277
|
+
}
|
|
9278
|
+
}
|
|
9279
|
+
if (state?.currentExecution) {
|
|
9280
|
+
const exec2 = state.currentExecution;
|
|
9281
|
+
const startTime = new Date(exec2.startedAt);
|
|
9282
|
+
if (!sinceDate || startTime >= sinceDate) {
|
|
9283
|
+
const entry = skillMap.get(exec2.skillName) ?? {
|
|
9284
|
+
skillName: exec2.skillName,
|
|
9285
|
+
executions: 0,
|
|
9286
|
+
totalDurationMs: 0,
|
|
9287
|
+
commits: [],
|
|
9288
|
+
filesModified: [],
|
|
9289
|
+
observationIds: [],
|
|
9290
|
+
firstSeen: exec2.startedAt,
|
|
9291
|
+
lastSeen: exec2.startedAt
|
|
9292
|
+
};
|
|
9293
|
+
entry.executions++;
|
|
9294
|
+
entry.totalDurationMs += Date.now() - startTime.getTime();
|
|
9295
|
+
for (const task of exec2.tasks) {
|
|
9296
|
+
if (task.commitSha) entry.commits.push(task.commitSha);
|
|
9297
|
+
entry.filesModified.push(...task.filesModified ?? []);
|
|
9298
|
+
for (const f of task.filesModified ?? []) {
|
|
9299
|
+
const skills2 = fileSkillMap.get(f) ?? /* @__PURE__ */ new Set();
|
|
9300
|
+
skills2.add(exec2.skillName);
|
|
9301
|
+
fileSkillMap.set(f, skills2);
|
|
9302
|
+
if (task.commitSha) {
|
|
9303
|
+
fileCommitCount.set(f, (fileCommitCount.get(f) ?? 0) + 1);
|
|
9304
|
+
}
|
|
9305
|
+
const existing = fileLastModified.get(f);
|
|
9306
|
+
if (!existing || new Date(exec2.startedAt) > new Date(existing)) {
|
|
9307
|
+
fileLastModified.set(f, exec2.startedAt);
|
|
9308
|
+
}
|
|
9309
|
+
}
|
|
9310
|
+
}
|
|
9311
|
+
if (new Date(exec2.startedAt) < new Date(entry.firstSeen)) {
|
|
9312
|
+
entry.firstSeen = exec2.startedAt;
|
|
9313
|
+
}
|
|
9314
|
+
entry.lastSeen = (/* @__PURE__ */ new Date()).toISOString();
|
|
9315
|
+
skillMap.set(exec2.skillName, entry);
|
|
9316
|
+
}
|
|
9317
|
+
}
|
|
9318
|
+
try {
|
|
9319
|
+
const activities = this.activityLog.getRecent(500);
|
|
9320
|
+
for (const activity of activities) {
|
|
9321
|
+
if (sinceDate && new Date(activity.committedAt) < sinceDate) continue;
|
|
9322
|
+
const countedFiles = /* @__PURE__ */ new Set();
|
|
9323
|
+
for (const skill of activity.activeSkills) {
|
|
9324
|
+
const entry = skillMap.get(skill);
|
|
9325
|
+
if (entry && !entry.commits.includes(activity.commitSha)) {
|
|
9326
|
+
entry.commits.push(activity.commitSha);
|
|
9327
|
+
}
|
|
9328
|
+
for (const f of activity.filesChanged) {
|
|
9329
|
+
const skills2 = fileSkillMap.get(f) ?? /* @__PURE__ */ new Set();
|
|
9330
|
+
skills2.add(skill);
|
|
9331
|
+
fileSkillMap.set(f, skills2);
|
|
9332
|
+
if (!countedFiles.has(f)) {
|
|
9333
|
+
fileCommitCount.set(f, (fileCommitCount.get(f) ?? 0) + 1);
|
|
9334
|
+
countedFiles.add(f);
|
|
9335
|
+
}
|
|
9336
|
+
const existing = fileLastModified.get(f);
|
|
9337
|
+
if (!existing || new Date(activity.committedAt) > new Date(existing)) {
|
|
9338
|
+
fileLastModified.set(f, activity.committedAt);
|
|
9339
|
+
}
|
|
9340
|
+
}
|
|
9341
|
+
}
|
|
9342
|
+
}
|
|
9343
|
+
} catch {
|
|
9344
|
+
}
|
|
9345
|
+
const errorProneFiles = [];
|
|
9346
|
+
try {
|
|
9347
|
+
const observations = ObservationStore.readAll(this.projectPath);
|
|
9348
|
+
for (const obs of observations) {
|
|
9349
|
+
if (sinceDate && new Date(obs.timestamp) < sinceDate) continue;
|
|
9350
|
+
for (const [, entry] of skillMap) {
|
|
9351
|
+
const execStart = new Date(entry.firstSeen).getTime();
|
|
9352
|
+
const execEnd = new Date(entry.lastSeen).getTime();
|
|
9353
|
+
const obsTime = new Date(obs.timestamp).getTime();
|
|
9354
|
+
if (obsTime >= execStart && obsTime <= execEnd) {
|
|
9355
|
+
if (!entry.observationIds.includes(obs.id)) {
|
|
9356
|
+
entry.observationIds.push(obs.id);
|
|
9357
|
+
}
|
|
9358
|
+
}
|
|
9359
|
+
}
|
|
9360
|
+
if (obs.type === "error" && Array.isArray(obs.content?.files)) {
|
|
9361
|
+
errorProneFiles.push(...obs.content.files);
|
|
9362
|
+
}
|
|
9363
|
+
}
|
|
9364
|
+
} catch {
|
|
9365
|
+
}
|
|
9366
|
+
for (const entry of skillMap.values()) {
|
|
9367
|
+
entry.commits = [...new Set(entry.commits)];
|
|
9368
|
+
entry.filesModified = [...new Set(entry.filesModified)];
|
|
9369
|
+
}
|
|
9370
|
+
let skills = Array.from(skillMap.values());
|
|
9371
|
+
let files = Array.from(fileSkillMap.entries()).map(([path4, skillSet]) => ({
|
|
9372
|
+
path: path4,
|
|
9373
|
+
skills: Array.from(skillSet),
|
|
9374
|
+
commitCount: fileCommitCount.get(path4) ?? 0,
|
|
9375
|
+
lastModified: fileLastModified.get(path4) ?? ""
|
|
9376
|
+
}));
|
|
9377
|
+
if (options?.skill) {
|
|
9378
|
+
skills = skills.filter((s) => s.skillName === options.skill);
|
|
9379
|
+
const skillFiles = new Set(skills.flatMap((s) => s.filesModified));
|
|
9380
|
+
files = files.filter((f) => skillFiles.has(f.path));
|
|
9381
|
+
}
|
|
9382
|
+
if (options?.file) {
|
|
9383
|
+
files = files.filter((f) => f.path === options.file || f.path.includes(options.file));
|
|
9384
|
+
const fileSkills = new Set(files.flatMap((f) => f.skills));
|
|
9385
|
+
skills = skills.filter((s) => fileSkills.has(s.skillName));
|
|
9386
|
+
}
|
|
9387
|
+
skills.sort((a, b) => b.filesModified.length - a.filesModified.length);
|
|
9388
|
+
files.sort((a, b) => b.skills.length - a.skills.length || b.commitCount - a.commitCount);
|
|
9389
|
+
if (options?.limit) {
|
|
9390
|
+
skills = skills.slice(0, options.limit);
|
|
9391
|
+
files = files.slice(0, options.limit);
|
|
9392
|
+
}
|
|
9393
|
+
const uniqueErrorFiles = [...new Set(errorProneFiles)];
|
|
9394
|
+
const allCommits = new Set(skills.flatMap((s) => s.commits));
|
|
9395
|
+
const allFiles = new Set(skills.flatMap((s) => s.filesModified));
|
|
9396
|
+
const mostImpactful = skills.length > 0 ? skills[0].skillName : null;
|
|
9397
|
+
const mostChanged = files.length > 0 ? files[0].path : null;
|
|
9398
|
+
return {
|
|
9399
|
+
projectPath: this.projectPath,
|
|
9400
|
+
skills,
|
|
9401
|
+
files,
|
|
9402
|
+
stats: {
|
|
9403
|
+
totalSkillExecutions: skills.reduce((sum, s) => sum + s.executions, 0),
|
|
9404
|
+
totalCommits: allCommits.size,
|
|
9405
|
+
totalFilesChanged: allFiles.size,
|
|
9406
|
+
mostImpactfulSkill: mostImpactful,
|
|
9407
|
+
mostChangedFile: mostChanged,
|
|
9408
|
+
errorProneFiles: uniqueErrorFiles.slice(0, 5)
|
|
9409
|
+
}
|
|
9410
|
+
};
|
|
9411
|
+
}
|
|
9412
|
+
getSkillLineage(skillName) {
|
|
9413
|
+
const data = this.build({ skill: skillName });
|
|
9414
|
+
return data.skills.find((s) => s.skillName === skillName);
|
|
9415
|
+
}
|
|
9416
|
+
getFileLineage(filePath) {
|
|
9417
|
+
const data = this.build({ file: filePath });
|
|
9418
|
+
return data.files.find((f) => f.path === filePath || f.path.includes(filePath));
|
|
9419
|
+
}
|
|
9420
|
+
formatText(data) {
|
|
9421
|
+
const lines = [];
|
|
9422
|
+
lines.push(" Skill Lineage\n");
|
|
9423
|
+
if (data.skills.length === 0) {
|
|
9424
|
+
lines.push(" No skill executions found.\n");
|
|
9425
|
+
return lines.join("\n");
|
|
9426
|
+
}
|
|
9427
|
+
lines.push(" Skills");
|
|
9428
|
+
for (const skill of data.skills) {
|
|
9429
|
+
const duration = formatDurationMs(skill.totalDurationMs);
|
|
9430
|
+
lines.push(
|
|
9431
|
+
` ${skill.skillName.padEnd(24)} ${skill.executions} runs ${skill.commits.length} commits ${skill.filesModified.length} files ${duration} total`
|
|
9432
|
+
);
|
|
9433
|
+
}
|
|
9434
|
+
lines.push("");
|
|
9435
|
+
const hotspots = data.files.filter((f) => f.skills.length >= 2);
|
|
9436
|
+
if (hotspots.length > 0) {
|
|
9437
|
+
lines.push(" File Hotspots (touched by 2+ skills)");
|
|
9438
|
+
for (const f of hotspots.slice(0, 10)) {
|
|
9439
|
+
lines.push(` ${f.path.padEnd(40)} ${f.skills.join(", ")}`);
|
|
9440
|
+
}
|
|
9441
|
+
lines.push("");
|
|
9442
|
+
}
|
|
9443
|
+
lines.push(" Stats");
|
|
9444
|
+
lines.push(` Total executions: ${data.stats.totalSkillExecutions} | Commits: ${data.stats.totalCommits}`);
|
|
9445
|
+
if (data.stats.mostImpactfulSkill) {
|
|
9446
|
+
const skill = data.skills.find((s) => s.skillName === data.stats.mostImpactfulSkill);
|
|
9447
|
+
lines.push(` Most impactful: ${data.stats.mostImpactfulSkill} (${skill?.filesModified.length ?? 0} files)`);
|
|
9448
|
+
}
|
|
9449
|
+
if (data.stats.errorProneFiles.length > 0) {
|
|
9450
|
+
lines.push(` Error-prone: ${data.stats.errorProneFiles.join(", ")}`);
|
|
9451
|
+
}
|
|
9452
|
+
return lines.join("\n");
|
|
9453
|
+
}
|
|
9454
|
+
formatJson(data) {
|
|
9455
|
+
return JSON.stringify(data, null, 2);
|
|
9456
|
+
}
|
|
9457
|
+
};
|
|
9458
|
+
|
|
8789
9459
|
// src/workflow/types.ts
|
|
8790
9460
|
var WORKFLOWS_DIR = "workflows";
|
|
8791
9461
|
var WORKFLOW_EXTENSION = ".yaml";
|
|
@@ -33843,7 +34513,7 @@ var SkillScanner = class {
|
|
|
33843
34513
|
|
|
33844
34514
|
// src/scanner/reporter.ts
|
|
33845
34515
|
import { basename as basename21 } from "path";
|
|
33846
|
-
var SCANNER_VERSION = true ? "1.
|
|
34516
|
+
var SCANNER_VERSION = true ? "1.19.1" : "0.0.0";
|
|
33847
34517
|
var SEVERITY_COLORS = {
|
|
33848
34518
|
["critical" /* CRITICAL */]: "\x1B[91m",
|
|
33849
34519
|
["high" /* HIGH */]: "\x1B[31m",
|
|
@@ -35014,8 +35684,10 @@ export {
|
|
|
35014
35684
|
SecretsAnalyzer,
|
|
35015
35685
|
SessionEndHook,
|
|
35016
35686
|
SessionExplainer,
|
|
35687
|
+
SessionHandoff,
|
|
35017
35688
|
SessionManager,
|
|
35018
35689
|
SessionStartHook,
|
|
35690
|
+
SessionTimeline,
|
|
35019
35691
|
Severity,
|
|
35020
35692
|
Skill,
|
|
35021
35693
|
SkillAnalyzer,
|
|
@@ -35026,6 +35698,7 @@ export {
|
|
|
35026
35698
|
SkillFrontmatter,
|
|
35027
35699
|
SkillGenerator,
|
|
35028
35700
|
SkillInjector,
|
|
35701
|
+
SkillLineage,
|
|
35029
35702
|
SkillLocation,
|
|
35030
35703
|
SkillMdTranslator,
|
|
35031
35704
|
SkillMerger,
|