gsd-pi 2.37.1-dev.d3ace49 → 2.38.0-dev.29edcdc
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/app-paths.js +1 -1
- package/dist/cli.js +9 -0
- package/dist/extension-discovery.d.ts +5 -3
- package/dist/extension-discovery.js +14 -9
- package/dist/extension-registry.js +2 -2
- package/dist/remote-questions-config.js +2 -2
- package/dist/resource-loader.js +34 -1
- package/dist/resources/extensions/browser-tools/package.json +3 -1
- package/dist/resources/extensions/cmux/index.js +55 -1
- package/dist/resources/extensions/context7/package.json +1 -1
- package/dist/resources/extensions/env-utils.js +29 -0
- package/dist/resources/extensions/get-secrets-from-user.js +5 -24
- package/dist/resources/extensions/github-sync/cli.js +284 -0
- package/dist/resources/extensions/github-sync/index.js +73 -0
- package/dist/resources/extensions/github-sync/mapping.js +67 -0
- package/dist/resources/extensions/github-sync/sync.js +424 -0
- package/dist/resources/extensions/github-sync/templates.js +118 -0
- package/dist/resources/extensions/github-sync/types.js +7 -0
- package/dist/resources/extensions/google-search/package.json +3 -1
- package/dist/resources/extensions/gsd/auto/session.js +6 -23
- package/dist/resources/extensions/gsd/auto-dispatch.js +8 -9
- package/dist/resources/extensions/gsd/auto-loop.js +597 -588
- package/dist/resources/extensions/gsd/auto-post-unit.js +99 -70
- package/dist/resources/extensions/gsd/auto-prompts.js +23 -43
- package/dist/resources/extensions/gsd/auto-start.js +13 -2
- package/dist/resources/extensions/gsd/auto-worktree-sync.js +13 -5
- package/dist/resources/extensions/gsd/auto-worktree.js +3 -3
- package/dist/resources/extensions/gsd/auto.js +143 -96
- package/dist/resources/extensions/gsd/captures.js +9 -1
- package/dist/resources/extensions/gsd/commands-extensions.js +3 -2
- package/dist/resources/extensions/gsd/commands-handlers.js +16 -3
- package/dist/resources/extensions/gsd/commands-prefs-wizard.js +1 -1
- package/dist/resources/extensions/gsd/commands.js +24 -3
- package/dist/resources/extensions/gsd/context-budget.js +2 -10
- package/dist/resources/extensions/gsd/detection.js +1 -2
- package/dist/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/dist/resources/extensions/gsd/doctor-checks.js +82 -0
- package/dist/resources/extensions/gsd/doctor-environment.js +78 -0
- package/dist/resources/extensions/gsd/doctor-format.js +15 -0
- package/dist/resources/extensions/gsd/doctor-providers.js +27 -11
- package/dist/resources/extensions/gsd/doctor.js +204 -12
- package/dist/resources/extensions/gsd/exit-command.js +2 -1
- package/dist/resources/extensions/gsd/export.js +1 -1
- package/dist/resources/extensions/gsd/files.js +6 -2
- package/dist/resources/extensions/gsd/forensics.js +1 -1
- package/dist/resources/extensions/gsd/git-service.js +15 -12
- package/dist/resources/extensions/gsd/guided-flow.js +82 -32
- package/dist/resources/extensions/gsd/index.js +24 -20
- package/dist/resources/extensions/gsd/migrate/parsers.js +1 -1
- package/dist/resources/extensions/gsd/native-git-bridge.js +37 -0
- package/dist/resources/extensions/gsd/package.json +1 -1
- package/dist/resources/extensions/gsd/preferences-models.js +0 -12
- package/dist/resources/extensions/gsd/preferences-types.js +1 -1
- package/dist/resources/extensions/gsd/preferences-validation.js +59 -11
- package/dist/resources/extensions/gsd/preferences.js +8 -5
- package/dist/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/dist/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/dist/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/dist/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/dist/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/dist/resources/extensions/gsd/prompts/queue.md +4 -8
- package/dist/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/dist/resources/extensions/gsd/prompts/run-uat.md +27 -10
- package/dist/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/dist/resources/extensions/gsd/repo-identity.js +21 -4
- package/dist/resources/extensions/gsd/resource-version.js +2 -1
- package/dist/resources/extensions/gsd/roadmap-mutations.js +24 -0
- package/dist/resources/extensions/gsd/state.js +1 -1
- package/dist/resources/extensions/gsd/visualizer-data.js +1 -1
- package/dist/resources/extensions/gsd/worktree.js +35 -16
- package/dist/resources/extensions/mcp-client/index.js +14 -1
- package/dist/resources/extensions/remote-questions/status.js +2 -1
- package/dist/resources/extensions/remote-questions/store.js +2 -1
- package/dist/resources/extensions/search-the-web/provider.js +2 -1
- package/dist/resources/extensions/subagent/index.js +12 -3
- package/dist/resources/extensions/subagent/isolation.js +2 -1
- package/dist/resources/extensions/ttsr/rule-loader.js +2 -1
- package/dist/resources/extensions/universal-config/package.json +1 -1
- package/dist/welcome-screen.d.ts +12 -0
- package/dist/welcome-screen.js +53 -0
- package/package.json +1 -1
- package/packages/pi-ai/dist/utils/oauth/anthropic.js +2 -2
- package/packages/pi-ai/dist/utils/oauth/anthropic.js.map +1 -1
- package/packages/pi-ai/src/utils/oauth/anthropic.ts +2 -2
- package/packages/pi-coding-agent/dist/core/extensions/loader.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/extensions/loader.js +205 -7
- package/packages/pi-coding-agent/dist/core/extensions/loader.js.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.d.ts.map +1 -1
- package/packages/pi-coding-agent/dist/core/package-manager.js +8 -4
- package/packages/pi-coding-agent/dist/core/package-manager.js.map +1 -1
- package/packages/pi-coding-agent/package.json +1 -1
- package/packages/pi-coding-agent/src/core/extensions/loader.ts +223 -7
- package/packages/pi-coding-agent/src/core/package-manager.ts +8 -4
- package/pkg/package.json +1 -1
- package/src/resources/extensions/cmux/index.ts +57 -1
- package/src/resources/extensions/env-utils.ts +31 -0
- package/src/resources/extensions/get-secrets-from-user.ts +5 -24
- package/src/resources/extensions/github-sync/cli.ts +364 -0
- package/src/resources/extensions/github-sync/index.ts +93 -0
- package/src/resources/extensions/github-sync/mapping.ts +81 -0
- package/src/resources/extensions/github-sync/sync.ts +556 -0
- package/src/resources/extensions/github-sync/templates.ts +183 -0
- package/src/resources/extensions/github-sync/tests/cli.test.ts +20 -0
- package/src/resources/extensions/github-sync/tests/commit-linking.test.ts +39 -0
- package/src/resources/extensions/github-sync/tests/mapping.test.ts +104 -0
- package/src/resources/extensions/github-sync/tests/templates.test.ts +110 -0
- package/src/resources/extensions/github-sync/types.ts +47 -0
- package/src/resources/extensions/gsd/auto/session.ts +7 -25
- package/src/resources/extensions/gsd/auto-dispatch.ts +7 -9
- package/src/resources/extensions/gsd/auto-loop.ts +484 -546
- package/src/resources/extensions/gsd/auto-post-unit.ts +80 -44
- package/src/resources/extensions/gsd/auto-prompts.ts +25 -45
- package/src/resources/extensions/gsd/auto-start.ts +18 -2
- package/src/resources/extensions/gsd/auto-worktree-sync.ts +15 -4
- package/src/resources/extensions/gsd/auto-worktree.ts +3 -3
- package/src/resources/extensions/gsd/auto.ts +139 -101
- package/src/resources/extensions/gsd/captures.ts +10 -1
- package/src/resources/extensions/gsd/commands-extensions.ts +4 -2
- package/src/resources/extensions/gsd/commands-handlers.ts +17 -2
- package/src/resources/extensions/gsd/commands-prefs-wizard.ts +1 -1
- package/src/resources/extensions/gsd/commands.ts +26 -4
- package/src/resources/extensions/gsd/context-budget.ts +2 -12
- package/src/resources/extensions/gsd/detection.ts +2 -2
- package/src/resources/extensions/gsd/docs/preferences-reference.md +0 -2
- package/src/resources/extensions/gsd/doctor-checks.ts +75 -0
- package/src/resources/extensions/gsd/doctor-environment.ts +82 -1
- package/src/resources/extensions/gsd/doctor-format.ts +20 -0
- package/src/resources/extensions/gsd/doctor-providers.ts +26 -9
- package/src/resources/extensions/gsd/doctor-types.ts +16 -1
- package/src/resources/extensions/gsd/doctor.ts +199 -14
- package/src/resources/extensions/gsd/exit-command.ts +2 -2
- package/src/resources/extensions/gsd/export.ts +1 -1
- package/src/resources/extensions/gsd/files.ts +5 -3
- package/src/resources/extensions/gsd/forensics.ts +1 -1
- package/src/resources/extensions/gsd/git-service.ts +20 -10
- package/src/resources/extensions/gsd/guided-flow.ts +110 -38
- package/src/resources/extensions/gsd/index.ts +24 -17
- package/src/resources/extensions/gsd/migrate/parsers.ts +1 -1
- package/src/resources/extensions/gsd/native-git-bridge.ts +37 -0
- package/src/resources/extensions/gsd/preferences-models.ts +0 -12
- package/src/resources/extensions/gsd/preferences-types.ts +4 -4
- package/src/resources/extensions/gsd/preferences-validation.ts +51 -11
- package/src/resources/extensions/gsd/preferences.ts +8 -5
- package/src/resources/extensions/gsd/prompts/discuss.md +11 -14
- package/src/resources/extensions/gsd/prompts/execute-task.md +2 -2
- package/src/resources/extensions/gsd/prompts/guided-discuss-milestone.md +11 -12
- package/src/resources/extensions/gsd/prompts/guided-discuss-slice.md +8 -10
- package/src/resources/extensions/gsd/prompts/guided-resume-task.md +1 -1
- package/src/resources/extensions/gsd/prompts/queue.md +4 -8
- package/src/resources/extensions/gsd/prompts/reactive-execute.md +11 -8
- package/src/resources/extensions/gsd/prompts/run-uat.md +27 -10
- package/src/resources/extensions/gsd/prompts/workflow-start.md +2 -2
- package/src/resources/extensions/gsd/repo-identity.ts +23 -4
- package/src/resources/extensions/gsd/resource-version.ts +3 -1
- package/src/resources/extensions/gsd/roadmap-mutations.ts +29 -0
- package/src/resources/extensions/gsd/state.ts +1 -1
- package/src/resources/extensions/gsd/tests/agent-end-retry.test.ts +21 -18
- package/src/resources/extensions/gsd/tests/auto-loop.test.ts +122 -68
- package/src/resources/extensions/gsd/tests/cmux.test.ts +93 -0
- package/src/resources/extensions/gsd/tests/doctor-enhancements.test.ts +266 -0
- package/src/resources/extensions/gsd/tests/doctor-providers.test.ts +86 -3
- package/src/resources/extensions/gsd/tests/preferences.test.ts +2 -7
- package/src/resources/extensions/gsd/tests/prompt-contracts.test.ts +59 -0
- package/src/resources/extensions/gsd/tests/repo-identity-worktree.test.ts +21 -1
- package/src/resources/extensions/gsd/tests/run-uat.test.ts +11 -3
- package/src/resources/extensions/gsd/tests/worktree.test.ts +47 -0
- package/src/resources/extensions/gsd/types.ts +0 -1
- package/src/resources/extensions/gsd/visualizer-data.ts +1 -1
- package/src/resources/extensions/gsd/worktree.ts +35 -15
- package/src/resources/extensions/mcp-client/index.ts +17 -1
- package/src/resources/extensions/remote-questions/status.ts +3 -1
- package/src/resources/extensions/remote-questions/store.ts +3 -1
- package/src/resources/extensions/search-the-web/provider.ts +2 -1
- package/src/resources/extensions/subagent/index.ts +12 -3
- package/src/resources/extensions/subagent/isolation.ts +3 -1
- package/src/resources/extensions/ttsr/rule-loader.ts +3 -1
- package/dist/resources/extensions/gsd/prompt-compressor.js +0 -393
- package/dist/resources/extensions/gsd/semantic-chunker.js +0 -254
- package/dist/resources/extensions/gsd/summary-distiller.js +0 -212
- package/src/resources/extensions/gsd/prompt-compressor.ts +0 -508
- package/src/resources/extensions/gsd/semantic-chunker.ts +0 -336
- package/src/resources/extensions/gsd/summary-distiller.ts +0 -258
- package/src/resources/extensions/gsd/tests/context-compression.test.ts +0 -193
- package/src/resources/extensions/gsd/tests/prompt-compressor.test.ts +0 -529
- package/src/resources/extensions/gsd/tests/semantic-chunker.test.ts +0 -426
- package/src/resources/extensions/gsd/tests/summary-distiller.test.ts +0 -323
- package/src/resources/extensions/gsd/tests/token-optimization-benchmark.test.ts +0 -1272
- package/src/resources/extensions/gsd/tests/token-optimization-prefs.test.ts +0 -164
|
@@ -297,116 +297,163 @@ export async function stopAuto(ctx, pi, reason) {
|
|
|
297
297
|
return;
|
|
298
298
|
const loadedPreferences = loadEffectiveGSDPreferences()?.preferences;
|
|
299
299
|
const reasonSuffix = reason ? ` — ${reason}` : "";
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
clearLock(lockBase());
|
|
303
|
-
if (lockBase())
|
|
304
|
-
releaseSessionLock(lockBase());
|
|
305
|
-
clearSkillSnapshot();
|
|
306
|
-
resetSkillTelemetry();
|
|
307
|
-
// Remove SIGTERM handler registered at auto-mode start
|
|
308
|
-
deregisterSigtermHandler();
|
|
309
|
-
// ── Auto-worktree: exit worktree and reset s.basePath on stop ──
|
|
310
|
-
if (s.currentMilestoneId) {
|
|
311
|
-
const notifyCtx = ctx
|
|
312
|
-
? { notify: ctx.ui.notify.bind(ctx.ui) }
|
|
313
|
-
: { notify: () => { } };
|
|
314
|
-
buildResolver().exitMilestone(s.currentMilestoneId, notifyCtx, {
|
|
315
|
-
preserveBranch: true,
|
|
316
|
-
});
|
|
317
|
-
}
|
|
318
|
-
// ── DB cleanup: close the SQLite connection ──
|
|
319
|
-
if (isDbAvailable()) {
|
|
300
|
+
try {
|
|
301
|
+
// ── Step 1: Timers and locks ──
|
|
320
302
|
try {
|
|
321
|
-
|
|
322
|
-
|
|
303
|
+
clearUnitTimeout();
|
|
304
|
+
if (lockBase())
|
|
305
|
+
clearLock(lockBase());
|
|
306
|
+
if (lockBase())
|
|
307
|
+
releaseSessionLock(lockBase());
|
|
323
308
|
}
|
|
324
309
|
catch (e) {
|
|
325
|
-
debugLog("
|
|
326
|
-
error: e instanceof Error ? e.message : String(e),
|
|
327
|
-
});
|
|
310
|
+
debugLog("stop-cleanup-locks", { error: e instanceof Error ? e.message : String(e) });
|
|
328
311
|
}
|
|
329
|
-
|
|
330
|
-
if (s.originalBasePath) {
|
|
331
|
-
s.basePath = s.originalBasePath;
|
|
312
|
+
// ── Step 2: Skill state ──
|
|
332
313
|
try {
|
|
333
|
-
|
|
314
|
+
clearSkillSnapshot();
|
|
315
|
+
resetSkillTelemetry();
|
|
334
316
|
}
|
|
335
|
-
catch {
|
|
336
|
-
|
|
317
|
+
catch (e) {
|
|
318
|
+
debugLog("stop-cleanup-skills", { error: e instanceof Error ? e.message : String(e) });
|
|
337
319
|
}
|
|
338
|
-
|
|
339
|
-
const ledger = getLedger();
|
|
340
|
-
if (ledger && ledger.units.length > 0) {
|
|
341
|
-
const totals = getProjectTotals(ledger.units);
|
|
342
|
-
ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}. Session: ${formatCost(totals.cost)} · ${formatTokenCount(totals.tokens.total)} tokens · ${ledger.units.length} units`, "info");
|
|
343
|
-
}
|
|
344
|
-
else {
|
|
345
|
-
ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}.`, "info");
|
|
346
|
-
}
|
|
347
|
-
if (s.basePath) {
|
|
320
|
+
// ── Step 3: SIGTERM handler ──
|
|
348
321
|
try {
|
|
349
|
-
|
|
322
|
+
deregisterSigtermHandler();
|
|
350
323
|
}
|
|
351
324
|
catch (e) {
|
|
352
|
-
debugLog("stop-
|
|
353
|
-
error: e instanceof Error ? e.message : String(e),
|
|
354
|
-
});
|
|
325
|
+
debugLog("stop-cleanup-sigterm", { error: e instanceof Error ? e.message : String(e) });
|
|
355
326
|
}
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
327
|
+
// ── Step 4: Auto-worktree exit ──
|
|
328
|
+
try {
|
|
329
|
+
if (s.currentMilestoneId) {
|
|
330
|
+
const notifyCtx = ctx
|
|
331
|
+
? { notify: ctx.ui.notify.bind(ctx.ui) }
|
|
332
|
+
: { notify: () => { } };
|
|
333
|
+
buildResolver().exitMilestone(s.currentMilestoneId, notifyCtx, {
|
|
334
|
+
preserveBranch: true,
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
catch (e) {
|
|
339
|
+
debugLog("stop-cleanup-worktree", { error: e instanceof Error ? e.message : String(e) });
|
|
340
|
+
}
|
|
341
|
+
// ── Step 5: DB cleanup ──
|
|
342
|
+
if (isDbAvailable()) {
|
|
343
|
+
try {
|
|
344
|
+
const { closeDatabase } = await import("./gsd-db.js");
|
|
345
|
+
closeDatabase();
|
|
346
|
+
}
|
|
347
|
+
catch (e) {
|
|
348
|
+
debugLog("db-close-failed", {
|
|
349
|
+
error: e instanceof Error ? e.message : String(e),
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
// ── Step 6: Restore basePath and chdir ──
|
|
354
|
+
try {
|
|
355
|
+
if (s.originalBasePath) {
|
|
356
|
+
s.basePath = s.originalBasePath;
|
|
357
|
+
try {
|
|
358
|
+
process.chdir(s.basePath);
|
|
359
|
+
}
|
|
360
|
+
catch {
|
|
361
|
+
/* best-effort */
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
catch (e) {
|
|
366
|
+
debugLog("stop-cleanup-basepath", { error: e instanceof Error ? e.message : String(e) });
|
|
367
|
+
}
|
|
368
|
+
// ── Step 7: Ledger notification ──
|
|
369
|
+
try {
|
|
370
|
+
const ledger = getLedger();
|
|
371
|
+
if (ledger && ledger.units.length > 0) {
|
|
372
|
+
const totals = getProjectTotals(ledger.units);
|
|
373
|
+
ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}. Session: ${formatCost(totals.cost)} · ${formatTokenCount(totals.tokens.total)} tokens · ${ledger.units.length} units`, "info");
|
|
374
|
+
}
|
|
375
|
+
else {
|
|
376
|
+
ctx?.ui.notify(`Auto-mode stopped${reasonSuffix}.`, "info");
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
catch (e) {
|
|
380
|
+
debugLog("stop-cleanup-ledger", { error: e instanceof Error ? e.message : String(e) });
|
|
381
|
+
}
|
|
382
|
+
// ── Step 8: Rebuild state ──
|
|
383
|
+
if (s.basePath) {
|
|
384
|
+
try {
|
|
385
|
+
await rebuildState(s.basePath);
|
|
386
|
+
}
|
|
387
|
+
catch (e) {
|
|
388
|
+
debugLog("stop-rebuild-state-failed", {
|
|
389
|
+
error: e instanceof Error ? e.message : String(e),
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
// ── Step 9: Cmux sidebar / event log ──
|
|
394
|
+
try {
|
|
395
|
+
clearCmuxSidebar(loadedPreferences);
|
|
396
|
+
logCmuxEvent(loadedPreferences, `Auto-mode stopped${reasonSuffix || ""}.`, reason?.startsWith("Blocked:") ? "warning" : "info");
|
|
397
|
+
}
|
|
398
|
+
catch (e) {
|
|
399
|
+
debugLog("stop-cleanup-cmux", { error: e instanceof Error ? e.message : String(e) });
|
|
400
|
+
}
|
|
401
|
+
// ── Step 10: Debug summary ──
|
|
402
|
+
try {
|
|
403
|
+
if (isDebugEnabled()) {
|
|
404
|
+
const logPath = writeDebugSummary();
|
|
405
|
+
if (logPath) {
|
|
406
|
+
ctx?.ui.notify(`Debug log written → ${logPath}`, "info");
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
catch (e) {
|
|
411
|
+
debugLog("stop-cleanup-debug", { error: e instanceof Error ? e.message : String(e) });
|
|
412
|
+
}
|
|
413
|
+
// ── Step 11: Reset metrics, routing, hooks ──
|
|
414
|
+
try {
|
|
415
|
+
resetMetrics();
|
|
416
|
+
resetRoutingHistory();
|
|
417
|
+
resetHookState();
|
|
418
|
+
if (s.basePath)
|
|
419
|
+
clearPersistedHookState(s.basePath);
|
|
420
|
+
}
|
|
421
|
+
catch (e) {
|
|
422
|
+
debugLog("stop-cleanup-metrics", { error: e instanceof Error ? e.message : String(e) });
|
|
423
|
+
}
|
|
424
|
+
// ── Step 12: Remove paused-session metadata (#1383) ──
|
|
425
|
+
try {
|
|
426
|
+
const pausedPath = join(gsdRoot(s.originalBasePath || s.basePath), "runtime", "paused-session.json");
|
|
427
|
+
if (existsSync(pausedPath))
|
|
428
|
+
unlinkSync(pausedPath);
|
|
429
|
+
}
|
|
430
|
+
catch { /* non-fatal */ }
|
|
431
|
+
// ── Step 13: Restore original model (before reset clears IDs) ──
|
|
432
|
+
try {
|
|
433
|
+
if (pi && ctx && s.originalModelId && s.originalModelProvider) {
|
|
434
|
+
const original = ctx.modelRegistry.find(s.originalModelProvider, s.originalModelId);
|
|
435
|
+
if (original)
|
|
436
|
+
await pi.setModel(original);
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
catch (e) {
|
|
440
|
+
debugLog("stop-cleanup-model", { error: e instanceof Error ? e.message : String(e) });
|
|
363
441
|
}
|
|
364
442
|
}
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
s.paused = false;
|
|
379
|
-
s.stepMode = false;
|
|
380
|
-
s.unitDispatchCount.clear();
|
|
381
|
-
s.unitRecoveryCount.clear();
|
|
382
|
-
clearInFlightTools();
|
|
383
|
-
s.lastBudgetAlertLevel = 0;
|
|
384
|
-
s.lastStateRebuildAt = 0;
|
|
385
|
-
s.unitLifetimeDispatches.clear();
|
|
386
|
-
s.currentUnit = null;
|
|
387
|
-
s.autoModeStartModel = null;
|
|
388
|
-
s.currentMilestoneId = null;
|
|
389
|
-
s.originalBasePath = "";
|
|
390
|
-
s.completedUnits = [];
|
|
391
|
-
s.pendingQuickTasks = [];
|
|
392
|
-
clearSliceProgressCache();
|
|
393
|
-
clearActivityLogState();
|
|
394
|
-
resetProactiveHealing();
|
|
395
|
-
s.pendingCrashRecovery = null;
|
|
396
|
-
s.pendingVerificationRetry = null;
|
|
397
|
-
s.verificationRetryCount.clear();
|
|
398
|
-
s.pausedSessionFile = null;
|
|
399
|
-
ctx?.ui.setStatus("gsd-auto", undefined);
|
|
400
|
-
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
401
|
-
ctx?.ui.setFooter(undefined);
|
|
402
|
-
if (pi && ctx && s.originalModelId && s.originalModelProvider) {
|
|
403
|
-
const original = ctx.modelRegistry.find(s.originalModelProvider, s.originalModelId);
|
|
404
|
-
if (original)
|
|
405
|
-
await pi.setModel(original);
|
|
406
|
-
s.originalModelId = null;
|
|
407
|
-
s.originalModelProvider = null;
|
|
443
|
+
finally {
|
|
444
|
+
// ── Critical invariants: these MUST execute regardless of errors ──
|
|
445
|
+
// External cleanup (not covered by session reset)
|
|
446
|
+
clearInFlightTools();
|
|
447
|
+
clearSliceProgressCache();
|
|
448
|
+
clearActivityLogState();
|
|
449
|
+
resetProactiveHealing();
|
|
450
|
+
// UI cleanup
|
|
451
|
+
ctx?.ui.setStatus("gsd-auto", undefined);
|
|
452
|
+
ctx?.ui.setWidget("gsd-progress", undefined);
|
|
453
|
+
ctx?.ui.setFooter(undefined);
|
|
454
|
+
// Reset all session state in one call
|
|
455
|
+
s.reset();
|
|
408
456
|
}
|
|
409
|
-
s.cmdCtx = null;
|
|
410
457
|
}
|
|
411
458
|
/**
|
|
412
459
|
* Pause auto-mode without destroying state. Context is preserved.
|
|
@@ -30,8 +30,16 @@ const VALID_CLASSIFICATIONS = [
|
|
|
30
30
|
*/
|
|
31
31
|
export function resolveCapturesPath(basePath) {
|
|
32
32
|
const resolved = resolve(basePath);
|
|
33
|
+
// Direct layout: /.gsd/worktrees/
|
|
33
34
|
const worktreeMarker = `${sep}.gsd${sep}worktrees${sep}`;
|
|
34
|
-
|
|
35
|
+
let idx = resolved.indexOf(worktreeMarker);
|
|
36
|
+
if (idx === -1) {
|
|
37
|
+
// Symlink-resolved layout: /.gsd/projects/<hash>/worktrees/
|
|
38
|
+
const symlinkRe = new RegExp(`\\${sep}\\.gsd\\${sep}projects\\${sep}[a-f0-9]+\\${sep}worktrees\\${sep}`);
|
|
39
|
+
const match = resolved.match(symlinkRe);
|
|
40
|
+
if (match && match.index !== undefined)
|
|
41
|
+
idx = match.index;
|
|
42
|
+
}
|
|
35
43
|
if (idx !== -1) {
|
|
36
44
|
// basePath is inside a worktree — resolve to project root
|
|
37
45
|
const projectRoot = resolved.slice(0, idx);
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, readdirSync, renameSync, writeFileSync } from "node:fs";
|
|
9
9
|
import { dirname, join } from "node:path";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
11
12
|
// ─── Registry I/O ───────────────────────────────────────────────────────────
|
|
12
13
|
function getRegistryPath() {
|
|
13
|
-
return join(
|
|
14
|
+
return join(gsdHome, "extensions", "registry.json");
|
|
14
15
|
}
|
|
15
16
|
function getAgentExtensionsDir() {
|
|
16
|
-
return join(
|
|
17
|
+
return join(gsdHome, "agent", "extensions");
|
|
17
18
|
}
|
|
18
19
|
function loadRegistry() {
|
|
19
20
|
const filePath = getRegistryPath();
|
|
@@ -10,7 +10,7 @@ import { deriveState } from "./state.js";
|
|
|
10
10
|
import { gsdRoot } from "./paths.js";
|
|
11
11
|
import { appendCapture, hasPendingCaptures, loadPendingCaptures } from "./captures.js";
|
|
12
12
|
import { appendOverride, appendKnowledge } from "./files.js";
|
|
13
|
-
import { formatDoctorIssuesForPrompt, formatDoctorReport, runGSDDoctor, selectDoctorScope, filterDoctorIssues, } from "./doctor.js";
|
|
13
|
+
import { formatDoctorIssuesForPrompt, formatDoctorReport, formatDoctorReportJson, runGSDDoctor, selectDoctorScope, filterDoctorIssues, } from "./doctor.js";
|
|
14
14
|
import { isAutoActive } from "./auto.js";
|
|
15
15
|
import { projectRoot } from "./commands.js";
|
|
16
16
|
import { loadPrompt } from "./prompt-loader.js";
|
|
@@ -28,15 +28,28 @@ export function dispatchDoctorHeal(pi, scope, reportText, structuredIssues) {
|
|
|
28
28
|
}
|
|
29
29
|
export async function handleDoctor(args, ctx, pi) {
|
|
30
30
|
const trimmed = args.trim();
|
|
31
|
-
|
|
31
|
+
// Extract flags before positional parsing
|
|
32
|
+
const jsonMode = trimmed.includes("--json");
|
|
33
|
+
const dryRun = trimmed.includes("--dry-run");
|
|
34
|
+
const includeBuild = trimmed.includes("--build");
|
|
35
|
+
const includeTests = trimmed.includes("--test");
|
|
36
|
+
const stripped = trimmed.replace(/--json|--dry-run|--build|--test/g, "").trim();
|
|
37
|
+
const parts = stripped ? stripped.split(/\s+/) : [];
|
|
32
38
|
const mode = parts[0] === "fix" || parts[0] === "heal" || parts[0] === "audit" ? parts[0] : "doctor";
|
|
33
39
|
const requestedScope = mode === "doctor" ? parts[0] : parts[1];
|
|
34
40
|
const scope = await selectDoctorScope(projectRoot(), requestedScope);
|
|
35
41
|
const effectiveScope = mode === "audit" ? requestedScope : scope;
|
|
36
42
|
const report = await runGSDDoctor(projectRoot(), {
|
|
37
|
-
fix: mode === "fix" || mode === "heal",
|
|
43
|
+
fix: mode === "fix" || mode === "heal" || dryRun,
|
|
44
|
+
dryRun,
|
|
38
45
|
scope: effectiveScope,
|
|
46
|
+
includeBuild,
|
|
47
|
+
includeTests,
|
|
39
48
|
});
|
|
49
|
+
if (jsonMode) {
|
|
50
|
+
ctx.ui.notify(formatDoctorReportJson(report), "info");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
40
53
|
const reportText = formatDoctorReport(report, {
|
|
41
54
|
scope: effectiveScope,
|
|
42
55
|
includeWarnings: mode === "audit",
|
|
@@ -631,7 +631,7 @@ export function serializePreferencesToFrontmatter(prefs) {
|
|
|
631
631
|
"dynamic_routing", "token_profile", "phases", "parallel",
|
|
632
632
|
"auto_visualize", "auto_report",
|
|
633
633
|
"verification_commands", "verification_auto_fix", "verification_max_retries",
|
|
634
|
-
"search_provider", "
|
|
634
|
+
"search_provider", "context_selection",
|
|
635
635
|
];
|
|
636
636
|
const seen = new Set();
|
|
637
637
|
for (const key of orderedKeys) {
|
|
@@ -3,10 +3,12 @@
|
|
|
3
3
|
*
|
|
4
4
|
* One command, one wizard. Routes to smart entry or status.
|
|
5
5
|
*/
|
|
6
|
+
import { importExtensionModule } from "@gsd/pi-coding-agent";
|
|
6
7
|
import { existsSync, readFileSync, readdirSync, unlinkSync } from "node:fs";
|
|
7
8
|
import { homedir } from "node:os";
|
|
8
9
|
import { join } from "node:path";
|
|
9
10
|
import { gsdRoot } from "./paths.js";
|
|
11
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
10
12
|
import { enableDebug } from "./debug-logger.js";
|
|
11
13
|
import { deriveState } from "./state.js";
|
|
12
14
|
import { GSDDashboardOverlay } from "./dashboard-overlay.js";
|
|
@@ -135,7 +137,7 @@ async function guardRemoteSession(ctx, pi) {
|
|
|
135
137
|
}
|
|
136
138
|
export function registerGSDCommand(pi) {
|
|
137
139
|
pi.registerCommand("gsd", {
|
|
138
|
-
description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|visualize|queue|quick|capture|triage|dispatch|history|undo|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|update",
|
|
140
|
+
description: "GSD — Get Shit Done: /gsd help|start|templates|next|auto|stop|pause|status|widget|visualize|queue|quick|capture|triage|dispatch|history|undo|rate|skip|export|cleanup|mode|prefs|config|keys|hooks|run-hook|skill-health|doctor|logs|forensics|changelog|migrate|remote|steer|knowledge|new-milestone|parallel|cmux|park|unpark|init|setup|inspect|extensions|update",
|
|
139
141
|
getArgumentCompletions: (prefix) => {
|
|
140
142
|
const subcommands = [
|
|
141
143
|
{ cmd: "help", desc: "Categorized command reference with descriptions" },
|
|
@@ -186,7 +188,11 @@ export function registerGSDCommand(pi) {
|
|
|
186
188
|
{ cmd: "templates", desc: "List available workflow templates" },
|
|
187
189
|
{ cmd: "extensions", desc: "Manage extensions (list, enable, disable, info)" },
|
|
188
190
|
];
|
|
191
|
+
const hasTrailingSpace = prefix.endsWith(" ");
|
|
189
192
|
const parts = prefix.trim().split(/\s+/);
|
|
193
|
+
if (hasTrailingSpace && parts.length >= 1) {
|
|
194
|
+
parts.push("");
|
|
195
|
+
}
|
|
190
196
|
if (parts.length <= 1) {
|
|
191
197
|
return subcommands
|
|
192
198
|
.filter((item) => item.cmd.startsWith(parts[0] ?? ""))
|
|
@@ -433,7 +439,7 @@ export function registerGSDCommand(pi) {
|
|
|
433
439
|
if (parts.length === 3 && ["enable", "disable", "info"].includes(parts[1])) {
|
|
434
440
|
const idPrefix = parts[2] ?? "";
|
|
435
441
|
try {
|
|
436
|
-
const extDir = join(
|
|
442
|
+
const extDir = join(gsdHome, "agent", "extensions");
|
|
437
443
|
const ids = [];
|
|
438
444
|
for (const entry of readdirSync(extDir, { withFileTypes: true })) {
|
|
439
445
|
if (!entry.isDirectory())
|
|
@@ -468,6 +474,10 @@ export function registerGSDCommand(pi) {
|
|
|
468
474
|
{ cmd: "fix", desc: "Auto-fix detected issues" },
|
|
469
475
|
{ cmd: "heal", desc: "AI-driven deep healing" },
|
|
470
476
|
{ cmd: "audit", desc: "Run health audit without fixing" },
|
|
477
|
+
{ cmd: "--dry-run", desc: "Show what --fix would change without applying" },
|
|
478
|
+
{ cmd: "--json", desc: "Output report as JSON (CI/tooling friendly)" },
|
|
479
|
+
{ cmd: "--build", desc: "Include slow build health check (npm run build)" },
|
|
480
|
+
{ cmd: "--test", desc: "Include slow test health check (npm test)" },
|
|
471
481
|
];
|
|
472
482
|
if (parts.length <= 2) {
|
|
473
483
|
return modes
|
|
@@ -491,6 +501,17 @@ export function registerGSDCommand(pi) {
|
|
|
491
501
|
.filter((p) => p.cmd.startsWith(phasePrefix))
|
|
492
502
|
.map((p) => ({ value: `dispatch ${p.cmd}`, label: p.cmd, description: p.desc }));
|
|
493
503
|
}
|
|
504
|
+
if (parts[0] === "rate" && parts.length <= 2) {
|
|
505
|
+
const tierPrefix = parts[1] ?? "";
|
|
506
|
+
const tiers = [
|
|
507
|
+
{ cmd: "over", desc: "Model was overqualified for this task" },
|
|
508
|
+
{ cmd: "ok", desc: "Model was appropriate for this task" },
|
|
509
|
+
{ cmd: "under", desc: "Model was underqualified for this task" },
|
|
510
|
+
];
|
|
511
|
+
return tiers
|
|
512
|
+
.filter((t) => t.cmd.startsWith(tierPrefix))
|
|
513
|
+
.map((t) => ({ value: `rate ${t.cmd}`, label: t.cmd, description: t.desc }));
|
|
514
|
+
}
|
|
494
515
|
return [];
|
|
495
516
|
},
|
|
496
517
|
async handler(args, ctx) {
|
|
@@ -509,7 +530,7 @@ export async function handleGSDCommand(args, ctx, pi) {
|
|
|
509
530
|
return;
|
|
510
531
|
}
|
|
511
532
|
if (trimmed === "widget" || trimmed.startsWith("widget ")) {
|
|
512
|
-
const { cycleWidgetMode, setWidgetMode, getWidgetMode } = await import
|
|
533
|
+
const { cycleWidgetMode, setWidgetMode, getWidgetMode } = await importExtensionModule(import.meta.url, "./auto-dashboard.js");
|
|
513
534
|
const arg = trimmed.replace(/^widget\s*/, "").trim();
|
|
514
535
|
if (arg === "full" || arg === "small" || arg === "min" || arg === "off") {
|
|
515
536
|
setWidgetMode(arg);
|
|
@@ -8,7 +8,6 @@
|
|
|
8
8
|
* @see D001 (module location), D002 (200K fallback), D003 (section-boundary truncation)
|
|
9
9
|
*/
|
|
10
10
|
import { getCharsPerToken } from "./token-counter.js";
|
|
11
|
-
import { compressToTarget } from "./prompt-compressor.js";
|
|
12
11
|
// ─── Budget ratio constants ──────────────────────────────────────────────────
|
|
13
12
|
// Percentages of total context window allocated to each budget category.
|
|
14
13
|
// These are applied after tokens→chars conversion.
|
|
@@ -132,20 +131,13 @@ export function resolveExecutorContextWindow(registry, preferences, sessionConte
|
|
|
132
131
|
return DEFAULT_CONTEXT_WINDOW;
|
|
133
132
|
}
|
|
134
133
|
/**
|
|
135
|
-
*
|
|
136
|
-
* Returns the content within budget with maximum information preservation.
|
|
134
|
+
* Reduce content to fit within budget using section-boundary truncation.
|
|
137
135
|
*/
|
|
138
136
|
export function reduceToFit(content, budgetChars) {
|
|
139
137
|
if (!content || content.length <= budgetChars) {
|
|
140
138
|
return { content, droppedSections: 0 };
|
|
141
139
|
}
|
|
142
|
-
|
|
143
|
-
const compressed = compressToTarget(content, budgetChars);
|
|
144
|
-
if (compressed.compressedChars <= budgetChars) {
|
|
145
|
-
return { content: compressed.content, droppedSections: 0 };
|
|
146
|
-
}
|
|
147
|
-
// Step 2: Truncate the compressed content at section boundaries
|
|
148
|
-
return truncateAtSectionBoundary(compressed.content, budgetChars);
|
|
140
|
+
return truncateAtSectionBoundary(content, budgetChars);
|
|
149
141
|
}
|
|
150
142
|
// ─── Internal helpers ────────────────────────────────────────────────────────
|
|
151
143
|
/**
|
|
@@ -9,6 +9,7 @@ import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
|
9
9
|
import { join } from "node:path";
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { gsdRoot } from "./paths.js";
|
|
12
|
+
const gsdHome = process.env.GSD_HOME || join(homedir(), ".gsd");
|
|
12
13
|
// ─── Project File Markers ───────────────────────────────────────────────────────
|
|
13
14
|
const PROJECT_FILES = [
|
|
14
15
|
"package.json",
|
|
@@ -309,7 +310,6 @@ function detectVerificationCommands(basePath, detectedFiles, packageManager) {
|
|
|
309
310
|
* Check if global GSD setup exists (has ~/.gsd/ with preferences).
|
|
310
311
|
*/
|
|
311
312
|
export function hasGlobalSetup() {
|
|
312
|
-
const gsdHome = join(homedir(), ".gsd");
|
|
313
313
|
return (existsSync(join(gsdHome, "preferences.md")) ||
|
|
314
314
|
existsSync(join(gsdHome, "PREFERENCES.md")));
|
|
315
315
|
}
|
|
@@ -318,7 +318,6 @@ export function hasGlobalSetup() {
|
|
|
318
318
|
* Returns true if ~/.gsd/ doesn't exist or has no preferences or auth.
|
|
319
319
|
*/
|
|
320
320
|
export function isFirstEverLaunch() {
|
|
321
|
-
const gsdHome = join(homedir(), ".gsd");
|
|
322
321
|
if (!existsSync(gsdHome))
|
|
323
322
|
return true;
|
|
324
323
|
// If we have preferences, not first launch
|
|
@@ -194,8 +194,6 @@ Setting `prefer_skills: []` does **not** disable skill discovery — it just mea
|
|
|
194
194
|
|
|
195
195
|
- `search_provider`: `"brave"`, `"tavily"`, `"ollama"`, `"native"`, or `"auto"` — selects the search backend for research phases. `"native"` forces Anthropic's built-in web search only; provider values force that backend and disable native search; `"auto"` uses the default heuristic. Default: `"auto"`.
|
|
196
196
|
|
|
197
|
-
- `compression_strategy`: `"truncate"` or `"compress"` — controls how context that exceeds the budget is reduced. `"truncate"` (default) drops sections from the end. `"compress"` applies heuristic compression before truncating, preserving more content at the cost of some fidelity. Default: `"truncate"`.
|
|
198
|
-
|
|
199
197
|
- `context_selection`: `"full"` or `"smart"` — controls how files are inlined into context. `"full"` inlines entire files; `"smart"` uses semantic chunking to include only the most relevant sections. Default is derived from `token_profile`.
|
|
200
198
|
|
|
201
199
|
- `parallel`: configures parallel orchestration for running multiple slices concurrently. Keys:
|
|
@@ -618,6 +618,88 @@ export async function checkRuntimeHealth(basePath, issues, fixesApplied, shouldF
|
|
|
618
618
|
catch {
|
|
619
619
|
// Non-fatal — external state check failed
|
|
620
620
|
}
|
|
621
|
+
// ── Metrics ledger integrity ───────────────────────────────────────────
|
|
622
|
+
try {
|
|
623
|
+
const metricsPath = join(root, "metrics.json");
|
|
624
|
+
if (existsSync(metricsPath)) {
|
|
625
|
+
try {
|
|
626
|
+
const raw = readFileSync(metricsPath, "utf-8");
|
|
627
|
+
const ledger = JSON.parse(raw);
|
|
628
|
+
if (ledger.version !== 1 || !Array.isArray(ledger.units)) {
|
|
629
|
+
issues.push({
|
|
630
|
+
severity: "warning",
|
|
631
|
+
code: "metrics_ledger_corrupt",
|
|
632
|
+
scope: "project",
|
|
633
|
+
unitId: "project",
|
|
634
|
+
message: "metrics.json has an unexpected structure (version !== 1 or units is not an array) — metrics data may be unreliable",
|
|
635
|
+
file: ".gsd/metrics.json",
|
|
636
|
+
fixable: false,
|
|
637
|
+
});
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
catch {
|
|
641
|
+
issues.push({
|
|
642
|
+
severity: "warning",
|
|
643
|
+
code: "metrics_ledger_corrupt",
|
|
644
|
+
scope: "project",
|
|
645
|
+
unitId: "project",
|
|
646
|
+
message: "metrics.json is not valid JSON — metrics data may be corrupt",
|
|
647
|
+
file: ".gsd/metrics.json",
|
|
648
|
+
fixable: false,
|
|
649
|
+
});
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
catch {
|
|
654
|
+
// Non-fatal — metrics check failed
|
|
655
|
+
}
|
|
656
|
+
// ── Large planning file detection ──────────────────────────────────────
|
|
657
|
+
// Files over 100KB can cause LLM context pressure. Report the worst offenders.
|
|
658
|
+
try {
|
|
659
|
+
const MAX_FILE_BYTES = 100 * 1024; // 100KB
|
|
660
|
+
const milestonesPath = milestonesDir(basePath);
|
|
661
|
+
if (existsSync(milestonesPath)) {
|
|
662
|
+
const largeFiles = [];
|
|
663
|
+
function scanForLargeFiles(dir, depth = 0) {
|
|
664
|
+
if (depth > 6)
|
|
665
|
+
return;
|
|
666
|
+
try {
|
|
667
|
+
for (const entry of readdirSync(dir)) {
|
|
668
|
+
const full = join(dir, entry);
|
|
669
|
+
try {
|
|
670
|
+
const s = statSync(full);
|
|
671
|
+
if (s.isDirectory()) {
|
|
672
|
+
scanForLargeFiles(full, depth + 1);
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
if (entry.endsWith(".md") && s.size > MAX_FILE_BYTES) {
|
|
676
|
+
largeFiles.push({ path: full.replace(basePath + "/", ""), sizeKB: Math.round(s.size / 1024) });
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
catch { /* skip entry */ }
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
catch { /* skip dir */ }
|
|
683
|
+
}
|
|
684
|
+
scanForLargeFiles(milestonesPath);
|
|
685
|
+
if (largeFiles.length > 0) {
|
|
686
|
+
largeFiles.sort((a, b) => b.sizeKB - a.sizeKB);
|
|
687
|
+
const worst = largeFiles[0];
|
|
688
|
+
issues.push({
|
|
689
|
+
severity: "warning",
|
|
690
|
+
code: "large_planning_file",
|
|
691
|
+
scope: "project",
|
|
692
|
+
unitId: "project",
|
|
693
|
+
message: `${largeFiles.length} planning file(s) exceed 100KB — largest: ${worst.path} (${worst.sizeKB}KB). Large files cause LLM context pressure.`,
|
|
694
|
+
file: worst.path,
|
|
695
|
+
fixable: false,
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
catch {
|
|
701
|
+
// Non-fatal — large file scan failed
|
|
702
|
+
}
|
|
621
703
|
}
|
|
622
704
|
/**
|
|
623
705
|
* Build STATE.md markdown content from derived state.
|