pi-midcompact 0.5.3 → 0.7.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 +27 -10
- package/README.zh-CN.md +16 -1
- package/package.json +4 -2
- package/skills/midcompact/SKILL.md +53 -86
- package/skills/midcompact/references/tool-interface.md +41 -59
- package/src/SPEC.md +35 -7
- package/src/content-metrics.ts +75 -3
- package/src/index.ts +232 -118
- package/src/inventory.ts +2 -2
- package/src/plan.ts +10 -10
- package/src/projection.ts +12 -3
- package/src/review-ui.ts +2 -2
- package/src/review-webui.html +837 -481
- package/src/review-webui.ts +150 -57
- package/src/selection-ui.ts +1 -1
- package/src/start-ui.ts +2 -2
- package/src/telemetry.ts +1 -1
package/src/plan.ts
CHANGED
|
@@ -22,14 +22,14 @@ export function addDraftRange(
|
|
|
22
22
|
): DraftPlan {
|
|
23
23
|
const start = atoms.find((atom) => atom.ref === input.start);
|
|
24
24
|
const end = atoms.find((atom) => atom.ref === input.end);
|
|
25
|
-
if (!start || !end) throw new Error("Unknown atom ref; run
|
|
25
|
+
if (!start || !end) throw new Error("Unknown atom ref; run inspect or locate again against the current anchor snapshot.");
|
|
26
26
|
if (start.index > end.index) throw new Error("start must not occur after end.");
|
|
27
27
|
const selected = atoms.slice(start.index, end.index + 1);
|
|
28
28
|
if (selected.length === 0) throw new Error("Empty range.");
|
|
29
29
|
const unsafe = selected.find((atom) => isProtectedAtom(atom));
|
|
30
30
|
if (unsafe) throw new Error(`Range crosses protected atom ${unsafe.ref} (${unsafe.kind}). Split the plan around it.`);
|
|
31
31
|
const overlaps = draft.ranges.some((range) => !(end.index < range.startIndex || start.index > range.endIndex));
|
|
32
|
-
if (overlaps) throw new Error("Range overlaps an existing
|
|
32
|
+
if (overlaps) throw new Error("Range overlaps an existing plan range; remove or replace it first.");
|
|
33
33
|
|
|
34
34
|
const metrics = rangeMetricsForAtoms(selected);
|
|
35
35
|
const summary = input.summary ?? "";
|
|
@@ -76,7 +76,7 @@ export function updateDraftRange(
|
|
|
76
76
|
patch: { summary?: string; topic?: string },
|
|
77
77
|
): DraftPlan {
|
|
78
78
|
const target = draft.ranges.find((range) => range.id === draftId);
|
|
79
|
-
if (!target) throw new Error(`Unknown
|
|
79
|
+
if (!target) throw new Error(`Unknown plan range ${draftId}.`);
|
|
80
80
|
const summary = patch.summary ?? target.summary;
|
|
81
81
|
const topic = patch.topic ?? target.topic;
|
|
82
82
|
const replacement = replacementContentChars(summary, topic);
|
|
@@ -90,7 +90,7 @@ export function updateDraftRange(
|
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
export function removeDraftRange(draft: DraftPlan, draftId: string): DraftPlan {
|
|
93
|
-
if (!draft.ranges.some((range) => range.id === draftId)) throw new Error(`Unknown
|
|
93
|
+
if (!draft.ranges.some((range) => range.id === draftId)) throw new Error(`Unknown plan range ${draftId}.`);
|
|
94
94
|
return { ...draft, revision: draft.revision + 1, ranges: draft.ranges.filter((range) => range.id !== draftId) };
|
|
95
95
|
}
|
|
96
96
|
|
|
@@ -153,18 +153,18 @@ export interface DraftFormatOptions {
|
|
|
153
153
|
/** Agent-facing plan output with bounded semantic landmarks and summaries. */
|
|
154
154
|
export function formatDraft(draft: DraftPlan, telemetry?: DraftTelemetry, options: DraftFormatOptions = {}): string {
|
|
155
155
|
if (options.detail === "full" && !options.draftId) {
|
|
156
|
-
throw new Error("plan
|
|
156
|
+
throw new Error("Full plan output requires a known range id.");
|
|
157
157
|
}
|
|
158
158
|
const selected = options.draftId
|
|
159
159
|
? draft.ranges.filter((range) => range.id === options.draftId)
|
|
160
160
|
: draft.ranges;
|
|
161
161
|
const atomsByRef = options.atoms ? new Map(options.atoms.map((atom) => [atom.ref, atom])) : undefined;
|
|
162
|
-
if (options.draftId && selected.length === 0) throw new Error(`Unknown
|
|
162
|
+
if (options.draftId && selected.length === 0) throw new Error(`Unknown plan range ${options.draftId}.`);
|
|
163
163
|
|
|
164
164
|
const lines: string[] = [];
|
|
165
165
|
if (telemetry) lines.push(formatTelemetry(telemetry));
|
|
166
166
|
if (draft.ranges.length === 0) {
|
|
167
|
-
lines.push(`
|
|
167
|
+
lines.push(`Plan v${draft.revision}: no compression ranges.`);
|
|
168
168
|
return lines.join("\n\n");
|
|
169
169
|
}
|
|
170
170
|
lines.push(draftHeader(draft));
|
|
@@ -180,7 +180,7 @@ export function formatDraft(draft: DraftPlan, telemetry?: DraftTelemetry, option
|
|
|
180
180
|
const block = formatRangeBrief(range, atomsByRef);
|
|
181
181
|
const currentLength = lines.join("\n\n").length;
|
|
182
182
|
if (currentLength + 2 + block.length > DRAFT_OUTPUT_LIMIT) {
|
|
183
|
-
const notice = `Output budget reached: showed ${shown} of ${selected.length} range(s). Use
|
|
183
|
+
const notice = `Output budget reached: showed ${shown} of ${selected.length} range(s). Use plan_read to inspect one range.`;
|
|
184
184
|
if (currentLength + 2 + notice.length <= DRAFT_OUTPUT_LIMIT) lines.push(notice);
|
|
185
185
|
break;
|
|
186
186
|
}
|
|
@@ -199,7 +199,7 @@ export function formatPlanMutation(
|
|
|
199
199
|
): string {
|
|
200
200
|
const pendingCount = draft.ranges.filter((range) => range.summary.trim().length === 0).length;
|
|
201
201
|
const verb = op === "add" ? "added" : op === "update" ? "updated" : "removed";
|
|
202
|
-
const lines = [`
|
|
202
|
+
const lines = [`Plan v${draft.revision}: ${verb} ${changedId} · ${draft.ranges.length} range(s) · ${pendingCount} pending summary.`];
|
|
203
203
|
if (op !== "remove") {
|
|
204
204
|
const changed = draft.ranges.find((range) => range.id === changedId);
|
|
205
205
|
const atomsByRef = atoms ? new Map(atoms.map((atom) => [atom.ref, atom])) : undefined;
|
|
@@ -211,7 +211,7 @@ export function formatPlanMutation(
|
|
|
211
211
|
function draftHeader(draft: DraftPlan): string {
|
|
212
212
|
const pendingCount = draft.ranges.filter((range) => range.summary.trim().length === 0).length;
|
|
213
213
|
const reviewState = pendingCount === 0 ? "ready for review" : `${pendingCount} pending summary`;
|
|
214
|
-
return `
|
|
214
|
+
return `Plan v${draft.revision}: ${draft.ranges.length} range(s) (${reviewState}).`;
|
|
215
215
|
}
|
|
216
216
|
|
|
217
217
|
function formatRangeBrief(range: DraftRange, atomsByRef?: ReadonlyMap<string, Atom>): string {
|
package/src/projection.ts
CHANGED
|
@@ -74,8 +74,8 @@ export function replacementMetrics(block: CompressionBlock): ContentMetrics {
|
|
|
74
74
|
* Factual replacement content chars for a range, given its topic and summary.
|
|
75
75
|
* Computed from the actual summary message wrapper text that projection emits.
|
|
76
76
|
*/
|
|
77
|
-
|
|
78
|
-
|
|
77
|
+
function draftReplacementBlock(summary: string, topic?: string): CompressionBlock {
|
|
78
|
+
return {
|
|
79
79
|
id: "draft",
|
|
80
80
|
summary,
|
|
81
81
|
topic,
|
|
@@ -89,7 +89,16 @@ export function replacementContentChars(summary: string, topic?: string): number
|
|
|
89
89
|
originalApproxTokens: 0,
|
|
90
90
|
compressedApproxTokens: 0,
|
|
91
91
|
};
|
|
92
|
-
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Render the exact replacement content used for draft display estimates. */
|
|
95
|
+
export function replacementContentText(summary: string, topic?: string): string {
|
|
96
|
+
const content = summaryMessage(draftReplacementBlock(summary, topic)).content;
|
|
97
|
+
return typeof content === "string" ? content : "";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
export function replacementContentChars(summary: string, topic?: string): number {
|
|
101
|
+
return replacementMetrics(draftReplacementBlock(summary, topic)).contentChars;
|
|
93
102
|
}
|
|
94
103
|
|
|
95
104
|
/** @deprecated legacy heuristic; kept only for old approximate-token field compat. */
|
package/src/review-ui.ts
CHANGED
|
@@ -46,7 +46,7 @@ export async function showReviewUi(
|
|
|
46
46
|
|
|
47
47
|
const header: string[] = [
|
|
48
48
|
h("╭"),
|
|
49
|
-
framed(accent(theme.bold(`Midcompact Review ·
|
|
49
|
+
framed(accent(theme.bold(`Midcompact Review · Plan v${draft.revision} · ${draft.ranges.length} range(s)`))),
|
|
50
50
|
framed(usageLine(telemetry, theme)),
|
|
51
51
|
h("├"),
|
|
52
52
|
];
|
|
@@ -195,7 +195,7 @@ export async function showReviewUi(
|
|
|
195
195
|
|
|
196
196
|
export function buildReviewText(atoms: Atom[], draft: DraftPlan, telemetry: DraftTelemetry): string {
|
|
197
197
|
const lines = [
|
|
198
|
-
`Midcompact Review ·
|
|
198
|
+
`Midcompact Review · Plan v${draft.revision}`,
|
|
199
199
|
plainUsageLine(telemetry),
|
|
200
200
|
"This is awareness, not a target.",
|
|
201
201
|
"",
|