pi-graft 0.1.0 → 0.1.2
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/extensions/graft.ts +36 -5
- package/package.json +3 -2
package/extensions/graft.ts
CHANGED
|
@@ -138,6 +138,10 @@ interface Stats {
|
|
|
138
138
|
totalCount: number; readyCount: number;
|
|
139
139
|
staleCount: number; dirty: boolean; syncing: boolean;
|
|
140
140
|
syncedAt: string | null; lastFile: string | null;
|
|
141
|
+
/** When the in-flight background sync started (null when idle). A syncing
|
|
142
|
+
* flag older than BUILD_TIMEOUT_MS is orphaned (pi quit / crash / sleep
|
|
143
|
+
* killed the detached build) and must be treated as not-syncing. */
|
|
144
|
+
syncStartedAt?: string | null;
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
interface SessionState {
|
|
@@ -157,6 +161,7 @@ function emptyStats(): Stats {
|
|
|
157
161
|
return {
|
|
158
162
|
nodeCount: 0, edgeCount: 0, languages: [], totalCount: 0, readyCount: 0,
|
|
159
163
|
staleCount: 0, dirty: false, syncing: false, syncedAt: null, lastFile: null,
|
|
164
|
+
syncStartedAt: null,
|
|
160
165
|
};
|
|
161
166
|
}
|
|
162
167
|
|
|
@@ -193,6 +198,23 @@ function patchStats(d: string, patch: Partial<Stats>): Stats {
|
|
|
193
198
|
return next;
|
|
194
199
|
}
|
|
195
200
|
|
|
201
|
+
/** True when the syncing flag is orphaned: set, but its build can no longer
|
|
202
|
+
* be alive (older than the rebuild budget, or timestamp missing from an
|
|
203
|
+
* older version). Callers should clear it instead of showing `syncing…`. */
|
|
204
|
+
function isSyncStale(s: Stats | null): boolean {
|
|
205
|
+
if (!s?.syncing) return false;
|
|
206
|
+
if (!s.syncStartedAt) return true;
|
|
207
|
+
const age = Date.now() - Date.parse(s.syncStartedAt);
|
|
208
|
+
return Number.isNaN(age) || age > BUILD_TIMEOUT_MS;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
/** Clear an orphaned syncing flag (persisted so every session sees it). */
|
|
212
|
+
function healStaleSync(d: string): Stats | null {
|
|
213
|
+
const s = readStats(d);
|
|
214
|
+
if (!isSyncStale(s)) return s;
|
|
215
|
+
return patchStats(d, { syncing: false, syncStartedAt: null });
|
|
216
|
+
}
|
|
217
|
+
|
|
196
218
|
function readSession(d: string, id: string): SessionState {
|
|
197
219
|
return readJson<SessionState>(sessionPath(d, id)) ?? emptySession();
|
|
198
220
|
}
|
|
@@ -233,7 +255,7 @@ function resolveStats(d: string): Stats | null {
|
|
|
233
255
|
// ── formatting (mirror claude/format) ───────────────────────────────────────
|
|
234
256
|
|
|
235
257
|
function freshnessSegment(s: Stats): string {
|
|
236
|
-
if (s.syncing) return "syncing…";
|
|
258
|
+
if (s.syncing && !isSyncStale(s)) return "syncing…";
|
|
237
259
|
if (s.dirty && s.staleCount > 0) return `⚠ ${s.staleCount} stale`;
|
|
238
260
|
if (s.dirty) return "⚠ stale";
|
|
239
261
|
return "✓ synced";
|
|
@@ -500,6 +522,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
500
522
|
ctx.ui.setStatus("graft", "graft: not installed (npm i -g @nanonets/graft)");
|
|
501
523
|
return;
|
|
502
524
|
}
|
|
525
|
+
// Self-heal here (not just at settle): a restarted session must not
|
|
526
|
+
// display another process's orphaned `syncing…` for even one turn.
|
|
527
|
+
healStaleSync(cwd);
|
|
503
528
|
const stats = resolveStats(cwd);
|
|
504
529
|
if (!stats) {
|
|
505
530
|
ctx.ui.setStatus("graft", `graft ${version}: no graph — run graft build`);
|
|
@@ -622,6 +647,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
622
647
|
const staleCount =
|
|
623
648
|
(g.changed?.length ?? 0) + (g.added?.length ?? 0) + (g.removed?.length ?? 0);
|
|
624
649
|
patchStats(cwd, { dirty: true, staleCount, lastFile: basename(abs) });
|
|
650
|
+
// A stale syncing flag from a killed build must not survive an edit:
|
|
651
|
+
// the footer should show the real ⚠ stale state, not phantom syncing.
|
|
652
|
+
healStaleSync(cwd);
|
|
625
653
|
await refreshStatus(ctx, cwd);
|
|
626
654
|
} catch { /* post-edit work is advisory */ }
|
|
627
655
|
|
|
@@ -660,11 +688,14 @@ export default function (pi: ExtensionAPI) {
|
|
|
660
688
|
pi.on("agent_settled", async (_event, ctx) => {
|
|
661
689
|
const cwd = ctx.cwd;
|
|
662
690
|
if (!hasGraph(cwd) || syncing.has(cwd)) return;
|
|
691
|
+
// Orphaned flag from a previous process that died mid-build: clear it so
|
|
692
|
+
// the footer is truthful even when this turn makes no edits (!dirty).
|
|
693
|
+
healStaleSync(cwd);
|
|
663
694
|
let stats: Stats | null = null;
|
|
664
695
|
try { stats = readStats(cwd); } catch { return; }
|
|
665
696
|
if (!stats?.dirty) return;
|
|
666
697
|
syncing.add(cwd);
|
|
667
|
-
try { patchStats(cwd, { syncing: true }); } catch { /* ignore */ }
|
|
698
|
+
try { patchStats(cwd, { syncing: true, syncStartedAt: new Date().toISOString() }); } catch { /* ignore */ }
|
|
668
699
|
await refreshStatus(ctx, cwd);
|
|
669
700
|
const dir = cwd;
|
|
670
701
|
const bin = GRAFT_BIN;
|
|
@@ -688,11 +719,11 @@ const writeAtomic = (p, v) => {
|
|
|
688
719
|
try {
|
|
689
720
|
execFileSync(bin, ["build", "."], { cwd: dir, stdio: "ignore", timeout: ${BUILD_TIMEOUT_MS} });
|
|
690
721
|
const w = readJ(wiringP);
|
|
691
|
-
if (!w) { writeAtomic(statsP, { ...(readJ(statsP) ?? {}), syncing: false }); process.exit(0); }
|
|
722
|
+
if (!w) { writeAtomic(statsP, { ...(readJ(statsP) ?? {}), syncing: false, syncStartedAt: null }); process.exit(0); }
|
|
692
723
|
const nodes = w.nodes ?? [], edges = w.edges ?? [];
|
|
693
724
|
writeAtomic(statsP, {
|
|
694
725
|
...(readJ(statsP) ?? {}),
|
|
695
|
-
dirty: false, staleCount: 0, syncing: false, syncedAt: new Date().toISOString(),
|
|
726
|
+
dirty: false, staleCount: 0, syncing: false, syncStartedAt: null, syncedAt: new Date().toISOString(),
|
|
696
727
|
nodeCount: (w.meta && w.meta.nodeCount) || nodes.length,
|
|
697
728
|
edgeCount: (w.meta && w.meta.edgeCount) || edges.length,
|
|
698
729
|
languages: (w.meta && w.meta.languages) || [],
|
|
@@ -700,7 +731,7 @@ try {
|
|
|
700
731
|
readyCount: nodes.filter((n) => n.summary_state === "ready").length,
|
|
701
732
|
});
|
|
702
733
|
} catch {
|
|
703
|
-
try { writeAtomic(statsP, { ...(readJ(statsP) ?? {}), syncing: false }); } catch {}
|
|
734
|
+
try { writeAtomic(statsP, { ...(readJ(statsP) ?? {}), syncing: false, syncStartedAt: null }); } catch {}
|
|
704
735
|
}
|
|
705
736
|
`;
|
|
706
737
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-graft",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Graft context-graph support for the pi coding harness — Claude-parity hooks, CLI-backed tools, skill, statusline and commands",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"keywords": ["pi-package", "graft", "context-graph"],
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"skills": ["./skills/graft"]
|
|
17
17
|
},
|
|
18
18
|
"peerDependencies": {
|
|
19
|
-
"@earendil-works/pi-coding-agent": "*"
|
|
19
|
+
"@earendil-works/pi-coding-agent": "*",
|
|
20
|
+
"typebox": "*"
|
|
20
21
|
}
|
|
21
22
|
}
|