pi-session-continuity 0.1.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.
Files changed (29) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.md +172 -0
  4. package/docs/deploys/README.md +18 -0
  5. package/docs/deploys/deploy-2026-07-07-SCOPE-0001-package-skeleton.md +53 -0
  6. package/docs/deploys/deploy-2026-07-07-SCOPE-0002-core-artifact-engine.md +39 -0
  7. package/docs/deploys/deploy-2026-07-07-SCOPE-0003-manual-checkpoint.md +38 -0
  8. package/docs/deploys/deploy-2026-07-07-SCOPE-0004-status-settings.md +38 -0
  9. package/docs/deploys/deploy-2026-07-07-SCOPE-0005-automatic-threshold.md +37 -0
  10. package/docs/deploys/deploy-2026-07-07-SCOPE-0006-compaction-hygiene.md +38 -0
  11. package/docs/deploys/deploy-2026-07-07-SCOPE-0007-tests-smoke.md +38 -0
  12. package/docs/deploys/deploy-2026-07-07-SCOPE-0008-public-docs-release-readiness.md +37 -0
  13. package/docs/deploys/deploy-2026-07-07-SCOPE-0009-dogfood-hardening-local-readiness.md +40 -0
  14. package/docs/deploys/deploy-2026-07-08-SCOPE-0010-ux-settings-menu.md +41 -0
  15. package/docs/deploys/deploy-2026-07-08-SCOPE-0011-compact-status-footer.md +26 -0
  16. package/docs/deploys/deploy-2026-07-08-SCOPE-0012-remove-settings-show.md +20 -0
  17. package/docs/deploys/deploy-2026-07-08-SCOPE-0014-status-and-effort.md +21 -0
  18. package/docs/deploys/deploy-2026-07-08-SCOPE-0016-codex-empty-synthesis.md +21 -0
  19. package/docs/product-spec.md +702 -0
  20. package/docs/release-readiness/local-v1-readiness-2026-07-07.md +76 -0
  21. package/extensions/session-continuity/index.ts +317 -0
  22. package/package.json +58 -0
  23. package/scripts/smoke/manual-checks.sh +42 -0
  24. package/src/artifact.ts +535 -0
  25. package/src/config.ts +360 -0
  26. package/src/constants.ts +68 -0
  27. package/src/handoff.ts +674 -0
  28. package/src/status.ts +130 -0
  29. package/src/trigger.ts +40 -0
@@ -0,0 +1,76 @@
1
+ # Local v1 Readiness Report — Pi Session Continuity
2
+
3
+ Date: 2026-07-07
4
+ Status: Local v1 readiness candidate
5
+ Scope: Local validation only; no external install, tag, release, publish, push, upload, or repository mutation performed.
6
+
7
+ ## Summary
8
+
9
+ Pi Session Continuity has local v0.1.0 readiness evidence across package shape, artifact/config behavior, manual checkpoint handoff, status/settings, automatic threshold triggering, compaction hygiene, tests/smoke collateral, and public documentation.
10
+
11
+ The product invariant remains intact:
12
+
13
+ ```text
14
+ Durable Continuity Brief first.
15
+ Resume prompt is injected from the disk artifact.
16
+ Compaction is token hygiene, not the source of continuity.
17
+ ```
18
+
19
+ ## Episode evidence
20
+
21
+ | Episode | Status | Primary evidence |
22
+ | --- | --- | --- |
23
+ | EPN-0001 package skeleton | Done | `specforge/runs/RUN-0001/autonomous-loop-result.yaml`; `docs/deploys/deploy-2026-07-07-SCOPE-0001-package-skeleton.md` |
24
+ | EPN-0002 core artifact engine | Done | `specforge/runs/RUN-0002/autonomous-loop-result.yaml`; `docs/deploys/deploy-2026-07-07-SCOPE-0002-core-artifact-engine.md` |
25
+ | EPN-0003 manual checkpoint | Done | `specforge/runs/RUN-0003/autonomous-loop-result.yaml`; `docs/deploys/deploy-2026-07-07-SCOPE-0003-manual-checkpoint.md` |
26
+ | EPN-0004 status/settings | Done | `specforge/runs/RUN-0004/autonomous-loop-result.yaml`; `docs/deploys/deploy-2026-07-07-SCOPE-0004-status-settings.md` |
27
+ | EPN-0005 automatic threshold | Done | `specforge/runs/RUN-0005/autonomous-loop-result.yaml`; `docs/deploys/deploy-2026-07-07-SCOPE-0005-automatic-threshold.md` |
28
+ | EPN-0006 compaction hygiene | Done | `specforge/runs/RUN-0006/autonomous-loop-result.yaml`; `docs/deploys/deploy-2026-07-07-SCOPE-0006-compaction-hygiene.md` |
29
+ | EPN-0007 tests + smoke scripts | Done | `specforge/runs/RUN-0007/autonomous-loop-result.yaml`; `docs/deploys/deploy-2026-07-07-SCOPE-0007-tests-smoke.md` |
30
+ | EPN-0008 public docs/release readiness | Done | `specforge/runs/RUN-0008/autonomous-loop-result.yaml`; `docs/deploys/deploy-2026-07-07-SCOPE-0008-public-docs-release-readiness.md` |
31
+ | EPN-0009 dogfood hardening/local readiness | Done | `specforge/runs/RUN-0009/autonomous-loop-result.yaml`; `docs/deploys/deploy-2026-07-07-SCOPE-0009-dogfood-hardening-local-readiness.md` |
32
+
33
+ ## Final local gates
34
+
35
+ Final RUN-0009 local gates:
36
+
37
+ - `npm test` — `CMD-0002`
38
+ - `npm run typecheck` — `CMD-0003`
39
+ - `npm run smoke:manual` — `CMD-0004`
40
+ - `npm pack --dry-run` — `CMD-0007`
41
+ - forbidden runtime terminology grep — `CMD-0008`
42
+ - final reviewer verdict — `specforge/runs/RUN-0009/subagents/JUDGE-0001.md` (PASS)
43
+
44
+ ## Local capability coverage
45
+
46
+ - Package manifest: `package.json` declares `pi-session-continuity` v0.1.0, `pi-package` keyword, Pi extension entrypoint, scripts, peer/dev dependency placement, and packaged docs/scripts.
47
+ - Runtime entrypoint: `extensions/session-continuity/index.ts` registers `/continuity` and lifecycle hooks.
48
+ - Config: `src/config.ts` covers trusted-project loading, validation, percentage math, and artifact directory resolution.
49
+ - Artifact contract: `src/artifact.ts` covers frontmatter, mandatory headings, status transitions, resume prompt rejection for non-pending artifacts, and directive-looking content boundaries.
50
+ - Handoff: `src/handoff.ts` writes/re-reads disk artifacts before queueing prompts, suppresses duplicates, archives after queue, and treats compaction as post-safe-path hygiene.
51
+ - Status/settings: `src/status.ts` reports complete status/settings fields and stale same-session pending artifacts.
52
+ - Automatic threshold: `src/trigger.ts` provides pure trigger decisions for disabled/untrusted/invalid/unavailable/below-threshold/threshold-reached states.
53
+ - Tests: `test/*.test.ts` cover local product gates, including package/docs/smoke collateral.
54
+ - Smoke template: `scripts/smoke/manual-checks.sh` lists all required manual Pi smoke assertions with PASS/FAIL fields.
55
+
56
+ ## Deferrals and residual risks
57
+
58
+ External validation is intentionally not performed in this local readiness run. Before public tag or announcement, Bernardo must explicitly approve the external validation step and then run/record at least:
59
+
60
+ ```bash
61
+ pi -e git:github.com/<owner>/pi-session-continuity@v0.1.0
62
+ pi install git:github.com/<owner>/pi-session-continuity@v0.1.0
63
+ ```
64
+
65
+ Required clean-install smoke evidence remains:
66
+
67
+ - package loads from GitHub ref without local workspace paths;
68
+ - `/continuity status` appears and reports config state;
69
+ - `/continuity checkpoint` writes a valid artifact;
70
+ - resume prompt is queued from the exact saved artifact;
71
+ - invalid config and untrusted project behavior are explicit and safe;
72
+ - package removal works without manual code cleanup.
73
+
74
+ ## Decision point
75
+
76
+ Local v1 readiness is accepted by RUN-0009 final gates and reviewer PASS. The next decision is whether Bernardo approves external clean-install/tag/release validation. No external action should happen without that separate approval.
@@ -0,0 +1,317 @@
1
+ import {
2
+ CONFIG_DIR_NAME,
3
+ type ExtensionAPI,
4
+ type ExtensionContext,
5
+ } from "@earendil-works/pi-coding-agent";
6
+ import { Text } from "@earendil-works/pi-tui";
7
+ import {
8
+ loadConfig,
9
+ publicConfigFromResolved,
10
+ validateConfigDraftForPath,
11
+ writeConfigToDisk,
12
+ type ContinuityConfig,
13
+ type ResolvedContinuityConfig,
14
+ type SynthesisEffort,
15
+ } from "../../src/config.js";
16
+ import {
17
+ AUTOMATIC_FAILURE_COOLDOWN_MS,
18
+ PRODUCT_NAME,
19
+ } from "../../src/constants.js";
20
+ import { createHandoffState, runContinuityHandoff } from "../../src/handoff.js";
21
+ import {
22
+ findStalePendingArtifacts,
23
+ footerStatusForMessage,
24
+ formatSettings,
25
+ formatStatus,
26
+ notifyStatus,
27
+ statusHeadlineForConfig,
28
+ } from "../../src/status.js";
29
+ import { decideAutomaticTrigger } from "../../src/trigger.js";
30
+
31
+ export default function sessionContinuityExtension(pi: ExtensionAPI) {
32
+ const state = createHandoffState();
33
+ let currentConfig: ResolvedContinuityConfig | undefined;
34
+ let staleNotificationSent = false;
35
+
36
+ pi.registerMessageRenderer(
37
+ "session-continuity-panel",
38
+ (message, _options, theme) => {
39
+ const content =
40
+ typeof message.content === "string" ? message.content : "";
41
+ const [headline = "", ...rest] = content.split("\n");
42
+ const text = [theme.bold(headline), ...rest].join("\n");
43
+ return new Text(text, 1, 1);
44
+ },
45
+ );
46
+
47
+ function showCommandOutput(ctx: ExtensionContext, message: string): void {
48
+ if (!ctx.hasUI) {
49
+ process.stdout.write(`${message}\n`);
50
+ return;
51
+ }
52
+ notifyStatus(ctx, message);
53
+ }
54
+
55
+ function showCommandPanel(ctx: ExtensionContext, message: string): void {
56
+ if (ctx.hasUI && ctx.mode === "tui") {
57
+ pi.sendMessage({
58
+ customType: "session-continuity-panel",
59
+ content: message,
60
+ display: true,
61
+ details: { product: PRODUCT_NAME },
62
+ });
63
+ ctx.ui.setStatus("session-continuity", footerStatusForMessage(message));
64
+ return;
65
+ }
66
+ showCommandOutput(ctx, message);
67
+ }
68
+
69
+ async function refreshConfig(
70
+ ctx: ExtensionContext,
71
+ ): Promise<ResolvedContinuityConfig> {
72
+ currentConfig = await loadConfig(ctx, CONFIG_DIR_NAME);
73
+ return currentConfig;
74
+ }
75
+
76
+ async function saveConfigDraft(
77
+ ctx: ExtensionContext,
78
+ draft: ContinuityConfig,
79
+ ): Promise<ResolvedContinuityConfig | undefined> {
80
+ const config = currentConfig ?? (await refreshConfig(ctx));
81
+ const errors = validateConfigDraftForPath(draft, config.configPath);
82
+ if (errors.length > 0) {
83
+ ctx.ui.notify(
84
+ `${PRODUCT_NAME}: settings not saved. ${errors.join("; ")}`,
85
+ "warning",
86
+ );
87
+ return undefined;
88
+ }
89
+ await writeConfigToDisk(config.configPath, draft);
90
+ const refreshed = await refreshConfig(ctx);
91
+ ctx.ui.notify(`${PRODUCT_NAME}: settings saved.`, "info");
92
+ ctx.ui.setStatus(
93
+ "session-continuity",
94
+ footerStatusForMessage(statusHeadlineForConfig(refreshed)),
95
+ );
96
+ return refreshed;
97
+ }
98
+
99
+ function triggerPercentChoices(predicate: (value: number) => boolean) {
100
+ return Array.from({ length: 10 }, (_, index) => 50 + index * 5)
101
+ .filter(predicate)
102
+ .map((value) => `${value}%`);
103
+ }
104
+
105
+ function keepPercentChoices(predicate: (value: number) => boolean) {
106
+ return [5, 10, 15, 20, 25].filter(predicate).map((value) => `${value}%`);
107
+ }
108
+
109
+ async function openSettingsMenu(
110
+ ctx: ExtensionContext,
111
+ initialConfig: ResolvedContinuityConfig,
112
+ ): Promise<void> {
113
+ if (!ctx.hasUI || ctx.mode !== "tui" || !initialConfig.trusted) {
114
+ showCommandPanel(ctx, formatSettings(initialConfig));
115
+ return;
116
+ }
117
+
118
+ let config = initialConfig;
119
+ for (;;) {
120
+ const choice = await ctx.ui.select("Pi Session Continuity settings", [
121
+ `Enabled: ${config.enabled ? "on" : "off"}`,
122
+ `Trigger threshold: ${config.triggerAtPercent}%`,
123
+ `Keep after handoff: ${config.keepRecentPercent}%`,
124
+ `Synthesis model: ${config.synthesisModel}`,
125
+ `Synthesis effort: ${config.synthesisEffort}`,
126
+ `Artifact directory: ${config.artifactDirectory}`,
127
+ "Done",
128
+ ]);
129
+ if (!choice || choice === "Done") return;
130
+
131
+ const draft = publicConfigFromResolved(config);
132
+ if (choice.startsWith("Enabled:")) {
133
+ const enabled = await ctx.ui.select("Enable automatic handoffs?", [
134
+ "on",
135
+ "off",
136
+ ]);
137
+ if (!enabled) continue;
138
+ draft.enabled = enabled === "on";
139
+ } else if (choice.startsWith("Trigger threshold:")) {
140
+ const trigger = await ctx.ui.select(
141
+ "Trigger when context reaches",
142
+ triggerPercentChoices((value) => value > draft.keepRecentPercent),
143
+ );
144
+ if (!trigger) continue;
145
+ draft.triggerAtPercent = Number.parseInt(trigger, 10);
146
+ } else if (choice.startsWith("Keep after handoff:")) {
147
+ const keep = await ctx.ui.select(
148
+ "Keep recent context after handoff",
149
+ keepPercentChoices((value) => value < draft.triggerAtPercent),
150
+ );
151
+ if (!keep) continue;
152
+ draft.keepRecentPercent = Number.parseInt(keep, 10);
153
+ } else if (choice.startsWith("Synthesis model:")) {
154
+ const model = await ctx.ui.input(
155
+ 'Synthesis model ("inherit" or provider/model)',
156
+ config.synthesisModel,
157
+ );
158
+ if (!model) continue;
159
+ draft.synthesisModel = model.trim();
160
+ } else if (choice.startsWith("Synthesis effort:")) {
161
+ const effort = await ctx.ui.select("Synthesis effort", [
162
+ "inherit",
163
+ "minimal",
164
+ "low",
165
+ "medium",
166
+ "high",
167
+ "xhigh",
168
+ ]);
169
+ if (!effort) continue;
170
+ draft.synthesisEffort = effort as SynthesisEffort;
171
+ } else if (choice.startsWith("Artifact directory:")) {
172
+ const artifactDirectory = await ctx.ui.input(
173
+ "Artifact directory (relative to Pi config dir unless absolute)",
174
+ config.artifactDirectory,
175
+ );
176
+ if (!artifactDirectory) continue;
177
+ draft.artifactDirectory = artifactDirectory.trim();
178
+ } else {
179
+ notifyStatus(ctx, formatSettings(config));
180
+ continue;
181
+ }
182
+
183
+ const refreshed = await saveConfigDraft(ctx, draft);
184
+ if (refreshed) config = refreshed;
185
+ }
186
+ }
187
+
188
+ pi.on("session_start", async (_event, ctx) => {
189
+ const config = await refreshConfig(ctx);
190
+ if (!config.trusted) {
191
+ notifyStatus(
192
+ ctx,
193
+ `${PRODUCT_NAME}: automatic behavior disabled because project is not trusted. Config path: ${config.configPath}.`,
194
+ );
195
+ return;
196
+ }
197
+ if (!config.valid) {
198
+ notifyStatus(
199
+ ctx,
200
+ `${PRODUCT_NAME} disabled: invalid configuration in ${config.configPath}.`,
201
+ );
202
+ return;
203
+ }
204
+
205
+ const stale = await findStalePendingArtifacts(
206
+ config.artifactDirectoryPath,
207
+ ctx.sessionManager.getSessionId(),
208
+ );
209
+ if (stale[0] && !staleNotificationSent) {
210
+ staleNotificationSent = true;
211
+ const lockDetail = stale[0].lockPath ? ` lock: ${stale[0].lockPath}` : "";
212
+ ctx.ui.notify(
213
+ `${PRODUCT_NAME}: stale pending Continuity Brief found for this session; leaving it inert. ${stale[0].path}${lockDetail}`,
214
+ "warning",
215
+ );
216
+ }
217
+ notifyStatus(ctx, statusHeadlineForConfig(config));
218
+ });
219
+
220
+ pi.on("turn_end", async (_event, ctx) => {
221
+ const config = currentConfig ?? (await refreshConfig(ctx));
222
+ const decision = decideAutomaticTrigger(config, ctx.getContextUsage());
223
+ if (!decision.shouldRun) {
224
+ if (decision.reason === "usage-unavailable") {
225
+ ctx.ui.notify(
226
+ `${PRODUCT_NAME}: automatic trigger skipped because context usage is unavailable.`,
227
+ "warning",
228
+ );
229
+ }
230
+ if (decision.reason === "window-unavailable") {
231
+ ctx.ui.notify(
232
+ `${PRODUCT_NAME}: automatic trigger skipped because context usage or model window is unavailable.`,
233
+ "warning",
234
+ );
235
+ }
236
+ return;
237
+ }
238
+ if (
239
+ state.lastAutomaticFailureAt &&
240
+ Date.now() - state.lastAutomaticFailureAt < AUTOMATIC_FAILURE_COOLDOWN_MS
241
+ ) {
242
+ return;
243
+ }
244
+ ctx.ui.notify(
245
+ `${PRODUCT_NAME}: context threshold reached; preparing Continuity Handoff.`,
246
+ "info",
247
+ );
248
+ await runContinuityHandoff(pi, ctx, config, state, {
249
+ reason: "threshold",
250
+ requestCompaction: true,
251
+ });
252
+ });
253
+
254
+ pi.registerCommand("continuity", {
255
+ description: "Pi Session Continuity: status, checkpoint, and settings",
256
+ getArgumentCompletions(prefix) {
257
+ return ["status", "checkpoint", "settings"]
258
+ .filter((item) => item.startsWith(prefix))
259
+ .map((value) => ({ value, label: value }));
260
+ },
261
+ handler: async (args, ctx) => {
262
+ const tokens = args.trim().split(/\s+/).filter(Boolean);
263
+ const subcommand = tokens[0] ?? "settings";
264
+ const config = await refreshConfig(ctx);
265
+
266
+ if (subcommand === "status") {
267
+ const stale = config.trusted
268
+ ? await findStalePendingArtifacts(
269
+ config.artifactDirectoryPath,
270
+ ctx.sessionManager.getSessionId(),
271
+ )
272
+ : [];
273
+ const stalePath = stale[0]
274
+ ? `${stale[0].path}${stale[0].lockPath ? ` (lock: ${stale[0].lockPath})` : ""}`
275
+ : undefined;
276
+ showCommandPanel(ctx, formatStatus(config, state, stalePath));
277
+ return;
278
+ }
279
+
280
+ if (subcommand === "settings") {
281
+ await openSettingsMenu(ctx, config);
282
+ return;
283
+ }
284
+
285
+ if (subcommand === "checkpoint") {
286
+ if (!config.trusted) {
287
+ showCommandOutput(
288
+ ctx,
289
+ `${PRODUCT_NAME}: checkpoint disabled because project is not trusted. Config path: ${config.configPath}.`,
290
+ );
291
+ return;
292
+ }
293
+ if (!config.valid) {
294
+ showCommandOutput(
295
+ ctx,
296
+ `${PRODUCT_NAME} disabled: invalid configuration in ${config.configPath}.`,
297
+ );
298
+ return;
299
+ }
300
+ if (!config.enabled) {
301
+ showCommandOutput(ctx, `${PRODUCT_NAME}: disabled by configuration.`);
302
+ return;
303
+ }
304
+ await runContinuityHandoff(pi, ctx, config, state, {
305
+ reason: "manual",
306
+ requestCompaction: false,
307
+ });
308
+ return;
309
+ }
310
+
311
+ showCommandOutput(
312
+ ctx,
313
+ `${PRODUCT_NAME}: unknown subcommand '${subcommand}'. Use /continuity status, /continuity checkpoint, or /continuity settings.`,
314
+ );
315
+ },
316
+ });
317
+ }
package/package.json ADDED
@@ -0,0 +1,58 @@
1
+ {
2
+ "name": "pi-session-continuity",
3
+ "version": "0.1.0",
4
+ "description": "Keep long-running Pi coding sessions recoverable with dull, durable handoffs.",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "keywords": [
8
+ "pi-package",
9
+ "pi-extension",
10
+ "session-continuity",
11
+ "continuity",
12
+ "checkpoint",
13
+ "long-running-sessions",
14
+ "coding-agents"
15
+ ],
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "https://github.com/bernardofortes/pi-session-continuity.git"
19
+ },
20
+ "homepage": "https://github.com/bernardofortes/pi-session-continuity#readme",
21
+ "files": [
22
+ "extensions",
23
+ "src",
24
+ "docs",
25
+ "scripts",
26
+ "README.md",
27
+ "LICENSE",
28
+ "CHANGELOG.md"
29
+ ],
30
+ "pi": {
31
+ "extensions": [
32
+ "./extensions/session-continuity/index.ts"
33
+ ]
34
+ },
35
+ "scripts": {
36
+ "test": "vitest run",
37
+ "typecheck": "tsc --noEmit",
38
+ "smoke:manual": "bash scripts/smoke/manual-checks.sh"
39
+ },
40
+ "peerDependencies": {
41
+ "@earendil-works/pi-agent-core": "*",
42
+ "@earendil-works/pi-ai": "*",
43
+ "@earendil-works/pi-coding-agent": "*",
44
+ "@earendil-works/pi-tui": "*"
45
+ },
46
+ "devDependencies": {
47
+ "@earendil-works/pi-agent-core": "*",
48
+ "@earendil-works/pi-ai": "*",
49
+ "@earendil-works/pi-coding-agent": "*",
50
+ "@earendil-works/pi-tui": "*",
51
+ "@types/node": "^22.10.2",
52
+ "typescript": "^5.9.3",
53
+ "vitest": "^3.2.4"
54
+ },
55
+ "engines": {
56
+ "node": ">=20"
57
+ }
58
+ }
@@ -0,0 +1,42 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ cat <<'SMOKE'
5
+ # Pi Session Continuity manual smoke checklist
6
+
7
+ Use this checklist in a clean Pi process after installing from a pinned GitHub ref.
8
+ Do not run external install/tag/release/publish steps without separate explicit approval.
9
+
10
+ Environment record:
11
+ - Pi version/commit:
12
+ - Node version:
13
+ - OS:
14
+ - Install command:
15
+ - Workspace:
16
+ - Config path:
17
+ - Artifact directory:
18
+
19
+ For each assertion, record PASS or FAIL, the command(s) run, and concrete evidence paths/output.
20
+
21
+ | ID | Assertion | Result | Evidence |
22
+ | --- | --- | --- | --- |
23
+ | manual-checkpoint-writes-artifact | `/continuity checkpoint` writes a valid Continuity Brief with required frontmatter and mandatory headings. | PASS/FAIL | |
24
+ | resume-uses-disk-artifact | The queued resume prompt contains the exact saved artifact body read from disk. | PASS/FAIL | |
25
+ | required-identity-present | Artifact includes `eventId`, `sessionId`, `sessionFile`, `createdAt`, and `updatedAt`. | PASS/FAIL | |
26
+ | reload-stale-is-inert | Reload does not inject a stale pending artifact; `/continuity status` reports it as inert. | PASS/FAIL | |
27
+ | duplicate-trigger-single-flight | Duplicate trigger while a handoff is active creates one artifact and one lock only. | PASS/FAIL | |
28
+ | threshold-percent-model-change | Threshold math preserves 65% trigger intent across at least two model context windows. | PASS/FAIL | |
29
+ | synthesis-failure-no-prompt | Forced synthesis failure queues no resume prompt and reports failure. | PASS/FAIL | |
30
+ | write-failure-no-prompt | Forced artifact write failure queues no resume prompt and reports failure. | PASS/FAIL | |
31
+ | cross-session-rejected | Artifact from another `sessionId` is not used automatically. | PASS/FAIL | |
32
+
33
+ Suggested transcript structure:
34
+
35
+ ```text
36
+ [ASSERTION-ID]
37
+ command(s):
38
+ observed:
39
+ evidence path(s):
40
+ result: PASS|FAIL
41
+ ```
42
+ SMOKE