@skillkit/core 1.19.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.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.19.0" : "0.0.0";
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,