@yannelli/paseo-linear-plugin 1.1.0-beta.2 → 1.1.0-beta.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/OVERVIEW.md +8 -4
- package/README.md +24 -9
- package/client/graph-camera.tsx +28 -8
- package/client/graph-labels.ts +7 -4
- package/client/live-cursor.tsx +76 -49
- package/client/live-graph.tsx +59 -29
- package/client/live-header.tsx +26 -2
- package/client/live-map.tsx +7 -1
- package/client/live-panel.tsx +9 -2
- package/client/live-timeline.ts +36 -5
- package/client/plugin-icon.tsx +41 -9
- package/client/project-init.tsx +28 -4
- package/client/project-knowledge.tsx +87 -0
- package/client/settings-icon.tsx +57 -8
- package/client/settings-projects.tsx +16 -5
- package/client/web.ts +32 -0
- package/index.client.tsx +134 -79
- package/index.server.ts +5 -2
- package/package.json +1 -1
- package/server/init.ts +31 -7
- package/server/inspect.ts +392 -0
- package/server/knowledge.ts +157 -0
- package/server/live.ts +21 -3
- package/server/stores.ts +1 -1
- package/shared/custom-icon.ts +40 -0
- package/shared/graph-camera.ts +28 -8
- package/shared/graph-geometry.ts +30 -19
- package/shared/knowledge.ts +255 -0
- package/shared/live.ts +19 -3
- package/shared/map-model.ts +50 -2
- package/shared/project-setup.ts +3 -2
package/shared/map-model.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { DirTouch, FileTouch } from "./activity";
|
|
2
|
+
import { type AreaRef, areaLabel, areaOf, whereLabel } from "./knowledge";
|
|
2
3
|
import { dirOf, type MapFile } from "./live";
|
|
3
4
|
|
|
4
5
|
// The Linear Live map: predicted files and touched files grouped into folder zones, and the
|
|
@@ -31,12 +32,21 @@ export interface MapZone {
|
|
|
31
32
|
/** Touched files that are on the map. */
|
|
32
33
|
hits: number;
|
|
33
34
|
explored: ZoneExplored;
|
|
35
|
+
/** The service, package, or main folder the zone is in, from the project knowledge. */
|
|
36
|
+
area: AreaRef | null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** Touched files the map did not predict, grouped by where they are. */
|
|
40
|
+
export interface OffMapGroup {
|
|
41
|
+
where: string;
|
|
42
|
+
files: number;
|
|
34
43
|
}
|
|
35
44
|
|
|
36
45
|
export interface MapModel {
|
|
37
46
|
zones: MapZone[];
|
|
38
47
|
touched: number;
|
|
39
48
|
hits: number;
|
|
49
|
+
offMap: OffMapGroup[];
|
|
40
50
|
}
|
|
41
51
|
|
|
42
52
|
// Search hits alone do not count: one broad search would flood the map with off-map tiles.
|
|
@@ -98,6 +108,7 @@ export function buildMapModel(
|
|
|
98
108
|
touches: readonly FileTouch[],
|
|
99
109
|
listing: ReadonlyMap<string, readonly string[]>,
|
|
100
110
|
dirs: readonly DirTouch[] = [],
|
|
111
|
+
areas: readonly AreaRef[] = [],
|
|
101
112
|
): MapModel {
|
|
102
113
|
const predicted = predictions(files);
|
|
103
114
|
const touched = new Map(touches.filter(isTouched).map((file) => [file.path, file]));
|
|
@@ -134,11 +145,19 @@ export function buildMapModel(
|
|
|
134
145
|
touched: tiles.filter((tile) => tile.touch !== "none").length,
|
|
135
146
|
hits: tiles.filter((tile) => tile.touch !== "none" && tile.onMap).length,
|
|
136
147
|
explored: exploredState(dir, dirs),
|
|
148
|
+
area: areaOf(dir, areas),
|
|
137
149
|
};
|
|
138
150
|
});
|
|
139
151
|
let hits = 0;
|
|
140
|
-
|
|
141
|
-
|
|
152
|
+
const off = new Map<string, number>();
|
|
153
|
+
for (const path of touched.keys()) {
|
|
154
|
+
if (ownerOf(path, predicted)) hits += 1;
|
|
155
|
+
else off.set(whereLabel(path, areas), (off.get(whereLabel(path, areas)) ?? 0) + 1);
|
|
156
|
+
}
|
|
157
|
+
const offMap = [...off]
|
|
158
|
+
.map(([where, count]) => ({ where, files: count }))
|
|
159
|
+
.sort((a, b) => b.files - a.files || a.where.localeCompare(b.where));
|
|
160
|
+
return { zones, touched: touched.size, hits, offMap };
|
|
142
161
|
}
|
|
143
162
|
|
|
144
163
|
export function hitRateLine(model: MapModel): string {
|
|
@@ -147,6 +166,35 @@ export function hitRateLine(model: MapModel): string {
|
|
|
147
166
|
return `${model.touched} ${noun} touched, ${model.hits} on the map`;
|
|
148
167
|
}
|
|
149
168
|
|
|
169
|
+
const OFF_MAP_SHOWN = 4;
|
|
170
|
+
|
|
171
|
+
/** Such as "Off the map: billing-api · service (3), docs (1)"; null when all are on it. */
|
|
172
|
+
export function offMapLine(model: MapModel): string | null {
|
|
173
|
+
if (model.offMap.length === 0) return null;
|
|
174
|
+
const shown = model.offMap.slice(0, OFF_MAP_SHOWN).map((group) => `${group.where} (${group.files})`);
|
|
175
|
+
const more = model.offMap.length - OFF_MAP_SHOWN;
|
|
176
|
+
return `Off the map: ${shown.join(", ")}${more > 0 ? `, ${more} more` : ""}`;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/** Where the file is when the agent touched it off the map; null on the map or untouched. */
|
|
180
|
+
export function offMapWhere(model: MapModel, path: string | null, areas: readonly AreaRef[]): string | null {
|
|
181
|
+
if (!path) return null;
|
|
182
|
+
for (const zone of model.zones) {
|
|
183
|
+
const tile = zone.tiles.find((entry) => entry.path === path && !entry.ghost);
|
|
184
|
+
if (tile) return tile.touch !== "none" && !tile.onMap ? whereLabel(path, areas) : null;
|
|
185
|
+
}
|
|
186
|
+
return null;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
/** The area a zone header names; only the part its folder name does not already say. */
|
|
190
|
+
export function zoneAreaLabel(zone: MapZone): string | null {
|
|
191
|
+
const area = zone.area;
|
|
192
|
+
if (!area || area.path === "") return null;
|
|
193
|
+
const folder = area.path.replace(/\/$/, "").split("/").pop();
|
|
194
|
+
if (zone.dir !== area.path || area.name !== folder) return areaLabel(area);
|
|
195
|
+
return area.role || (area.kind === "folder" ? null : area.kind);
|
|
196
|
+
}
|
|
197
|
+
|
|
150
198
|
export const ZONE_GAP = 10;
|
|
151
199
|
|
|
152
200
|
// Rough zone height from its tiles, so zones can go to the shortest column. Tiles wrap, so
|
package/shared/project-setup.ts
CHANGED
|
@@ -3,6 +3,7 @@ import type { ProjectConfig } from "./settings";
|
|
|
3
3
|
|
|
4
4
|
// A setup proposal shows only fields that change; accepted fields append to the prompts.
|
|
5
5
|
export type ProposalField = "instructions" | "steps" | "implement" | "review";
|
|
6
|
+
type PromptProposal = Pick<InitProposal, ProposalField>;
|
|
6
7
|
|
|
7
8
|
export interface FieldDiff {
|
|
8
9
|
field: ProposalField;
|
|
@@ -11,7 +12,7 @@ export interface FieldDiff {
|
|
|
11
12
|
proposed: string;
|
|
12
13
|
}
|
|
13
14
|
|
|
14
|
-
export function proposalDiffs(config: ProjectConfig, proposal:
|
|
15
|
+
export function proposalDiffs(config: ProjectConfig, proposal: PromptProposal): FieldDiff[] {
|
|
15
16
|
const steps = proposal.steps.map((step) => step.trim()).filter(Boolean);
|
|
16
17
|
const rows: FieldDiff[] = [
|
|
17
18
|
{
|
|
@@ -43,7 +44,7 @@ export function proposalDiffs(config: ProjectConfig, proposal: InitProposal): Fi
|
|
|
43
44
|
}
|
|
44
45
|
|
|
45
46
|
export function applyProposal(
|
|
46
|
-
proposal:
|
|
47
|
+
proposal: PromptProposal,
|
|
47
48
|
fields: ReadonlySet<ProposalField>,
|
|
48
49
|
): Partial<ProjectConfig> {
|
|
49
50
|
const patch: Partial<ProjectConfig> = {};
|