blun-king-cli 9.1.597 → 9.1.600

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 (106) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/README.md +17 -1
  3. package/agent-spine-plugin/CHANGELOG.md +1 -1
  4. package/agent-spine-plugin/docs/host-integration.md +1 -15
  5. package/agent-spine-plugin/docs/preflight-recall.md +1 -1
  6. package/agent-spine-plugin/scripts/check-install.js +1 -6
  7. package/agent-spine-plugin/scripts/hermetic-process.js +8 -0
  8. package/agent-spine-plugin/scripts/release-check.js +2 -3
  9. package/agent-spine-plugin/scripts/run-tests-hermetic.js +8 -11
  10. package/agent-spine-plugin/src/hook.js +40 -40
  11. package/agent-spine-plugin/src/lib/briefing.js +6 -3
  12. package/agent-spine-plugin/src/lib/hook-context.js +63 -64
  13. package/agent-spine-plugin/src/lib/hook-output.js +115 -94
  14. package/agent-spine-plugin/src/lib/hook-timeline.js +20 -11
  15. package/agent-spine-plugin/src/lib/host-instruction-budget.js +8 -14
  16. package/agent-spine-plugin/src/lib/mcp-timeline-tools.js +0 -7
  17. package/agent-spine-plugin/src/lib/pre-answer-timeline-recall.js +104 -0
  18. package/agent-spine-plugin/src/lib/session-timeline-codex.js +0 -4
  19. package/agent-spine-plugin/src/lib/session-timeline-enrollment-source.js +0 -3
  20. package/agent-spine-plugin/src/lib/session-timeline-enrollment-storage.js +0 -4
  21. package/agent-spine-plugin/src/lib/session-timeline-enrollment-transport.js +0 -2
  22. package/agent-spine-plugin/src/lib/session-timeline-enrollment.js +0 -22
  23. package/agent-spine-plugin/src/lib/session-timeline-host-origin.js +0 -4
  24. package/agent-spine-plugin/src/lib/session-timeline-prior.js +42 -50
  25. package/agent-spine-plugin/src/lib/session-timeline-provider.js +0 -3
  26. package/agent-spine-plugin/src/lib/session-timeline-query.js +0 -1
  27. package/agent-spine-plugin/src/lib/session-timeline-sid-acl.js +0 -4
  28. package/agent-spine-plugin/src/lib/session-timeline-source.js +0 -3
  29. package/agent-spine-plugin/src/lib/session-timeline-transport.js +0 -8
  30. package/agent-spine-plugin/src/lib/session-timeline-windows-acl.js +0 -3
  31. package/agent-spine-plugin/src/lib/session-timeline.js +370 -408
  32. package/agent-spine-plugin/src/lib/source-roots.js +26 -16
  33. package/agent-spine-plugin/src/lib/timeline-user-feedback.js +0 -1
  34. package/bin/core-bootstrap.js +18 -10
  35. package/bin/launcher-mode.js +5 -0
  36. package/bin/launcher-runtime.js +27 -0
  37. package/bin/verify-bundled-agent-sources.cjs +28 -2
  38. package/blun.mjs +798 -143
  39. package/bundled-agent-sources.json +192 -155
  40. package/package.json +1 -1
  41. package/standard-skills/translate-native/README.md +1334 -1
  42. package/standard-skills/translate-native/SKILL.md +1 -1
  43. package/standard-skills/translate-native/VERSION +1 -1
  44. package/standard-skills/translate-native/docs/PREMORTEM.md +901 -0
  45. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION.md +208 -41
  46. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_API.md +1319 -77
  47. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_EVIDENCE_HTTP.md +66 -11
  48. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_HTTP_PROVIDER.md +19 -1
  49. package/standard-skills/translate-native/docs/WEBSITE_LOCALIZATION_RECEIPT_VERIFIER_HTTP.md +24 -6
  50. package/standard-skills/translate-native/integrations/commercial_localization_profile.py +23 -0
  51. package/standard-skills/translate-native/integrations/website_localization.py +234 -10
  52. package/standard-skills/translate-native/integrations/website_localization_benchmark.py +39 -3
  53. package/standard-skills/translate-native/integrations/website_localization_cms.py +512 -10
  54. package/standard-skills/translate-native/integrations/website_localization_cms_client.py +116 -0
  55. package/standard-skills/translate-native/integrations/website_localization_cms_receiver.py +101 -11
  56. package/standard-skills/translate-native/integrations/website_localization_cms_receiver_runtime.py +12 -1
  57. package/standard-skills/translate-native/integrations/website_localization_cms_receiver_store.py +186 -24
  58. package/standard-skills/translate-native/integrations/website_localization_cms_source_client.py +64 -1
  59. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery.py +528 -5
  60. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_auth.py +918 -0
  61. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_auth_runtime.py +609 -0
  62. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_client.py +885 -0
  63. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_http.py +1244 -0
  64. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_runtime.py +220 -0
  65. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_sidecar_adapter.py +331 -0
  66. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_capabilities_http.py +444 -0
  67. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_client.py +673 -0
  68. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch.py +1317 -0
  69. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_client.py +826 -0
  70. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_http.py +1152 -0
  71. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_openapi.py +896 -0
  72. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_dispatch_runtime.py +621 -0
  73. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_http.py +1180 -0
  74. package/standard-skills/translate-native/integrations/website_localization_cms_source_delivery_submission_runtime.py +1479 -0
  75. package/standard-skills/translate-native/integrations/website_localization_cms_source_http.py +133 -17
  76. package/standard-skills/translate-native/integrations/website_localization_cms_source_runtime.py +319 -4
  77. package/standard-skills/translate-native/integrations/website_localization_cms_source_service.py +72 -2
  78. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_http.py +13 -0
  79. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver.py +56 -13
  80. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_openapi.py +335 -0
  81. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_notification_receiver_runtime.py +66 -1
  82. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_processing_monitor.py +73 -7
  83. package/standard-skills/translate-native/integrations/website_localization_cms_terminal_receiver_client.py +93 -9
  84. package/standard-skills/translate-native/integrations/website_localization_evidence_http.py +221 -11
  85. package/standard-skills/translate-native/integrations/website_localization_health.py +6 -0
  86. package/standard-skills/translate-native/integrations/website_localization_http_provider.py +1 -1
  87. package/standard-skills/translate-native/integrations/website_localization_quality_profiles.py +350 -0
  88. package/standard-skills/translate-native/integrations/website_localization_queue.py +126 -56
  89. package/standard-skills/translate-native/integrations/website_localization_receipt_verifier_http.py +166 -13
  90. package/standard-skills/translate-native/integrations/website_localization_release.py +423 -17
  91. package/standard-skills/translate-native/integrations/website_localization_release_coordinator.py +168 -13
  92. package/standard-skills/translate-native/integrations/website_localization_runner.py +10 -0
  93. package/standard-skills/translate-native/integrations/website_localization_worker.py +213 -9
  94. package/standard-skills/translate-native/provenance.json +2 -2
  95. package/standard-skills/translate-native/references/commercial-localization.md +96 -17
  96. package/standard-skills/translate-native/scripts/blun_language_guard.py +3 -1
  97. package/standard-skills/translate-native/scripts/check_commercial_review.py +69 -9
  98. package/standard-skills/translate-native/scripts/commercial_localization_profile.py +829 -31
  99. package/standard-tools/language-guard/VERSION +1 -1
  100. package/standard-tools/language-guard/blun_language_guard.py +3 -1
  101. package/standard-tools/language-guard/check_commercial_review.py +69 -9
  102. package/standard-tools/language-guard/commercial_localization_profile.py +829 -31
  103. package/standard-tools/language-guard/provenance.json +2 -2
  104. package/standard-tools/manifest.json +1 -1
  105. package/worker-host.mjs +215 -82
  106. package/agent-spine-plugin/scripts/check-install-king.js +0 -37
package/CHANGELOG.md CHANGED
@@ -1,5 +1,44 @@
1
1
  # BLUN King Changelog
2
2
 
3
+ ## 9.1.600
4
+
5
+ - Bound each response-review attempt to 120 seconds while retaining the 30-second default for generic reviewers and the existing maximum of two outer attempts.
6
+ - Allow up to 90 seconds for first meaningful native-review text once per attempt, then renew a 30-second idle budget on meaningful text and ordered release and verification stages without extending the absolute deadline.
7
+ - Preserve cancellation, exact response binding, receipt expiry, and authorization; these changes do not establish a fix for live HTTP 502 errors.
8
+ - Pin AgentSpine 0.73.0 and Translate Native 6.155.0; keep the guard protocol at 6.20.0.
9
+
10
+ ## 9.1.599
11
+
12
+ Local candidate; not published or installed.
13
+
14
+ - Show localized personal-memory actions and complete previews of settings changes or remembered text, with terminal control characters escaped.
15
+ - Offer one-shot approval for valid memory changes and rejection only for invalid inputs.
16
+ - Prevent session-wide approval from automatically accepting queued requests that require individual approval.
17
+
18
+ ## 9.1.598
19
+
20
+ - Distinguish lower-bound input counts from exact counts in upstream context rejections.
21
+ - Reduce the output allowance once for a lower-bound rejection without discarding messages or tools; retain cancellation and bounded retry behavior.
22
+ - Verify moving tokenization thresholds and exact-count counterexamples in the compiled program.
23
+ - Include the previously missing package notes for 9.1.595 through 9.1.597.
24
+ - Restore the original 150 rotating activity words without additional model requests.
25
+ - Keep media payloads out of AgentSpine's text-only hook input while preserving complete model attachments and policy checks.
26
+
27
+ ## 9.1.597
28
+
29
+ - Classify explicit context-limit errors wrapped in HTTP 502 and allow one corrected request with a smaller output allowance.
30
+ - Preserve messages, tools, cancellation, and the rule against replaying an already-started response.
31
+ - Known limitation: this initial correction treated lower-bound input counts as exact. The additional correction is included in 9.1.598.
32
+
33
+ ## 9.1.596
34
+
35
+ - Distinguish owner cancellation from automatic turn interruption.
36
+
37
+ ## 9.1.595
38
+
39
+ - Defer tool and skill descriptions until they are needed, while keeping discovery available.
40
+ - Bound greeting context without deleting saved task history or weakening permissions.
41
+
3
42
  ## 9.1.594
4
43
 
5
44
  - Preserve private Telegram replies while group messages wait, and retain slash-command routing.
package/README.md CHANGED
@@ -9,7 +9,7 @@ Voraussetzung ist Node.js 24.15 oder neuer. Die geprüfte Version wird exakt
9
9
  installiert:
10
10
 
11
11
  ```powershell
12
- npm install -g blun-king-cli@9.1.36
12
+ npm install -g blun-king-cli@9.1.600
13
13
  ```
14
14
 
15
15
  ## Reproduzierbares Staging und Packen
@@ -40,6 +40,22 @@ eingerichtet. Die Anmeldung erfolgt anschließend in der
40
40
  Konsole mit `/login` über den BLUN-OAuth-Server. Das Paket erzeugt keine
41
41
  statische Anbieter- oder API-Key-Konfiguration.
42
42
 
43
+ ## Persönliches Gedächtnis
44
+
45
+ Seit Version 9.1.599 zeigt BLUN King Freigaben für das persönliche Gedächtnis mit übersetzten Aktionstiteln und vollständigen Vorschauen der tatsächlichen Einstellungsänderungen oder des zu speichernden Textes. Gültige Änderungen lassen sich nur einmalig freigeben, ungültige Eingaben nur ablehnen. Eine Sitzungsfreigabe genehmigt solche wartenden Anfragen nicht automatisch. Steuerzeichen werden sichtbar maskiert; der Originalinhalt bleibt unverändert.
46
+
47
+ ## Zeitgrenzen für die Antwortprüfung
48
+
49
+ Version 9.1.600 begrenzt die Antwortprüfung pro äußerem Prüfversuch auf insgesamt 120 Sekunden. Allgemeine Prüfer behalten die bisherige Frist von 30 Sekunden. Bei der nativen Sprachprüfung stehen nach dem bestätigten Start einmalig bis zu 90 Sekunden für den ersten inhaltlichen Text bereit; auch dabei gilt die Gesamtgrenze. Inhaltlicher Text innerhalb der zulässigen Ausgabelänge sowie die geordneten Übergänge zur Freigabe und zur Belegprüfung setzen die Wartefrist jeweils auf 30 Sekunden zurück, ohne die Gesamtgrenze zu verlängern. Es bleibt bei höchstens zwei äußeren Prüfversuchen.
50
+
51
+ Abbruch, exakte Bindung an den Antworttext, Belegablauf und Autorisierung bleiben unverändert. Damit ist keine Behebung von HTTP-502-Fehlern im Live-Betrieb nachgewiesen.
52
+
53
+ ## Festgelegte Komponentenstände
54
+
55
+ - AgentSpine 0.73.0, Commit `38cb94760b19e3a82e7efdf12538a1adf5324964`.
56
+ - Translate Native 6.155.0, Commit `21efc66da8f77992c8c63ac0f145a106c31e7eb2`.
57
+ - Guard-Protokoll unverändert auf 6.20.0.
58
+
43
59
  ## Nachweisbare Arbeitsabläufe
44
60
 
45
61
  Version 9.1.0 enthält sieben zusätzliche, getrennt nutzbare Befehlsgruppen:
@@ -58,7 +58,7 @@ All notable changes to AgentSpine will be documented here. The project follows [
58
58
 
59
59
  ### Fixed
60
60
 
61
- - King keeps complete instructions within the existing source-reader resource bounds, resolves its isolated profile first, and accounts for JSON escaping without adding an instruction-size gate. Routine hooks, lifecycle receipts and process advisories use quiet `additionalContext`; actual denials and exact source/receipt checks remain unchanged. Synthetic fresh/upgrade probes cover large and combined sources, escaping and byte preservation. Package allowance grows by 4 KiB unpacked for the helper and install checks; packed and file-count limits stay fixed. Live King/macOS acceptance remains separate.
61
+ - King reader overflow stays explicit and non-blocking; sources remain unchanged.
62
62
  - The portal/thread lifecycle probe that starts a fresh process now runs outside the shared Windows I/O pool. Its existing assertions and process deadlines are unchanged.
63
63
  - The hermetic runner isolates the real host-origin source-attestation probe from concurrent Windows filesystem pressure, preserving its existing fail-closed result and timing boundaries without increasing a timeout.
64
64
  - Reuse host-verified, assignment-bound briefing observations in MCP delivery preparation without a second content fetch; keep missing proof advisory and document provider-specific history limits.
@@ -10,7 +10,7 @@ Provider evidence is not interchangeable. Each adapter separately binds its life
10
10
  |---|---|---|---|
11
11
  | Project instructions | `CLAUDE.md` hierarchy | `AGENTS.md` hierarchy | `AGENTS.md` through the isolated BLUN profile |
12
12
  | Hook package | Native `hooks/hooks.json` | Explicit `hooks/codex.json` | Manifest lifecycle hooks |
13
- | Verified hook briefing → delivery knowledge | Native `additionalContext` handoff receipt | Native `additionalContext` handoff receipt | Bounded model-only `additionalContext` handoff receipt; live acceptance open |
13
+ | Verified hook briefing → delivery knowledge | Native `additionalContext` handoff receipt | Native `additionalContext` handoff receipt | Native bounded `message` handoff receipt; live acceptance open |
14
14
  | Historical transcript enrollment | Bounded registered Claude source | Bounded Codex `sessions` source with verified headers | Protected King agent-wire mapping; live mapping open |
15
15
  | Cross-provider evidence handoff | Explicit opt-in; Claude provenance retained | Explicit opt-in; Codex provenance retained | Same contract; live launcher unverified |
16
16
  | Native permissions/trust | Host-owned | Host-owned | Host-owned; enforcement of returned block decisions remains externally unverified |
@@ -19,20 +19,6 @@ Provider evidence is not interchangeable. Each adapter separately binds its life
19
19
 
20
20
  Adapter acceptance uses bounded synthetic A/B sessions and covers restart, source mutation, foreign scope, private exclusion and service failure. Cross-provider recall still needs local opt-in plus `includePriorProviders: true` with `includePriorSessions: true`. Report repository, installed-host and model observations separately.
21
21
 
22
- King preparation, lifecycle receipts and process advisories do not emit visible
23
- `message` prompts. Required instructions remain byte-exact; `UserPromptSubmit`
24
- uses model-only `additionalContext`. Other events also return that quiet field;
25
- whether the host consumes it is a separate lifecycle contract. Actual deny/block
26
- decisions retain their original error schema.
27
- King uses the existing source-reader safety bounds of 4 MiB per document and
28
- 8 MiB for the source set, not Codex's 32 KiB instruction cap. When both King
29
- environment bindings are present, `BLUN_HOME` owns the profile even if an unrelated
30
- `CODEX_HOME` is inherited. These are resource limits, not recommended file sizes.
31
- Codex/King internal hook envelopes account for the serialized verified briefing,
32
- including JSON escaping. King's emitted runtime context remains at most 1200
33
- bytes; its native instruction loader separately supplies full instructions.
34
- Claude's existing instruction and context limits remain unchanged.
35
-
36
22
  ## Claude Code
37
23
 
38
24
  | Component | Path | Purpose |
@@ -6,7 +6,7 @@ The receipt binds agent and optional persona, user, tenant, host, instruction ho
6
6
 
7
7
  ## Host instructions
8
8
 
9
- Claude Code uses its resolved user and project `CLAUDE.md` hierarchy. Codex uses the corresponding `AGENTS.override.md`/`AGENTS.md` hierarchy. A generic host must explicitly bind `instruction_host` to `claude` or `codex`; AgentSpine does not guess. The mandatory preflight section contains the complete bytes of every active instruction document. The standard budget is 8 KiB, with aggregate overflow up to 16 KiB for Claude or 32 KiB for Codex. King uses its own profile and the existing source-reader resource limits: 4 MiB per source and 8 MiB total, not a recommended instruction size (see [host integration](host-integration.md)). The mode, used bytes, overflow and hard limit are bound into the signed exact-turn receipt and revalidated before consumption. An unreadable, replaced, deleted, resource-oversized, out-of-scope, or symlinked mandatory file blocks immediately with a visible bounded diagnostic instead of degrading to a descriptor or waiting silently.
9
+ Claude Code uses its resolved user and project `CLAUDE.md` hierarchy. Codex uses the corresponding `AGENTS.override.md`/`AGENTS.md` hierarchy. A generic host must explicitly bind `instruction_host` to `claude` or `codex`; AgentSpine does not guess. The mandatory preflight section contains the complete bytes of every loaded instruction document. The standard budget is 8 KiB, with aggregate overflow up to 16 KiB for Claude or 32 KiB for Codex. King uses its own profile and bounded source-reader resources; a rule outside that resource budget remains explicitly unverified without blocking the native-host turn. Loaded bytes, overflow and hard limit are bound into the signed exact-turn receipt and revalidated before consumption. Unreadable, replaced, deleted, out-of-scope or symlinked mandatory files still fail closed.
10
10
 
11
11
  Claude Code's `InstructionsLoaded` lifecycle event is registered as an additional observability signal, while `UserPromptSubmit` remains the blocking and injection boundary. Codex uses its own manifest-selected hook set without that unsupported Claude-only event. The preflight does not rely on the model remembering to read a file or call a tool.
12
12
 
@@ -12,7 +12,6 @@ import {
12
12
  invokeInstalledBlockingProtocols, invokeInstalledBlunPostWriteDigest, invokeInstalledHook
13
13
  } from "./check-install-hook.js";
14
14
  import { invokeInstalledSelfstarter as runInstalledSelfstarter } from "./check-install-selfstarter.js";
15
- import { invokeInstalledKingInstructions } from "./check-install-king.js";
16
15
 
17
16
  function hash(value) {
18
17
  return createHash("sha256").update(value).digest("hex");
@@ -365,15 +364,13 @@ export async function checkInstall(root = process.cwd()) {
365
364
  await mkdir(userProject, { recursive: true });
366
365
  await writeFile(source, "# Existing soul\n\nNever modify me.\n", "utf8");
367
366
  await writeFile(join(userProject, "CLAUDE.md"), "# Installed Claude rules\n\nLoad this before every answer.\n", "utf8");
368
- const largeCodexRules = `# Installed Codex rules\n\n${"x".repeat(17_590 - 25)}`;
369
- await writeFile(join(userProject, "AGENTS.md"), largeCodexRules, "utf8");
367
+ await writeFile(join(userProject, "AGENTS.md"), `# Installed Codex rules\n\n${"x".repeat(17565)}`, "utf8");
370
368
  const protectedInstallSources = [source, join(userProject, "CLAUDE.md"), join(userProject, "AGENTS.md")];
371
369
  const sourceHashes = new Map(await Promise.all(protectedInstallSources.map(async (path) => [path, hash(await readFile(path))])));
372
370
 
373
371
  const fresh = join(workspace, "fresh", "agent-spine");
374
372
  await copyBundle(root, fresh);
375
373
  const freshResult = await checkHosts(fresh);
376
- const freshKing = await invokeInstalledKingInstructions(fresh, join(workspace, "king-fresh"));
377
374
  const aliasRoot = join(workspace, "fresh-alias");
378
375
  await symlink(join(workspace, "fresh"), aliasRoot, process.platform === "win32" ? "junction" : "dir");
379
376
  const aliasResult = await checkHosts(join(aliasRoot, "agent-spine"));
@@ -413,7 +410,6 @@ export async function checkInstall(root = process.cwd()) {
413
410
  await removeTree(installed);
414
411
  await rename(staging, installed);
415
412
  const upgraded = await checkHosts(installed);
416
- const upgradedKing = await invokeInstalledKingInstructions(installed, join(workspace, "king-upgrade"));
417
413
  const upgradeState = join(workspace, "state-upgrade");
418
414
  const upgradedHook = await invokeInstalledHook(installed, userProject, upgradeState, "codex");
419
415
  const upgradedBlockingProtocols = await invokeInstalledBlockingProtocols(
@@ -443,7 +439,6 @@ export async function checkInstall(root = process.cwd()) {
443
439
  automaticBriefing: { fresh: freshHook, upgrade: upgradedHook },
444
440
  blockingProtocols: { fresh: freshBlockingProtocols, upgrade: upgradedBlockingProtocols },
445
441
  blunPostWriteDigest: { fresh: freshBlunPost, upgrade: upgradedBlunPost },
446
- kingInstructions: { fresh: freshKing, upgrade: upgradedKing },
447
442
  automaticAttention: { fresh: freshAttention, upgrade: upgradedAttention },
448
443
  automaticSelfstarter: { fresh: freshSelfstarter, upgrade: upgradedSelfstarter },
449
444
  automaticChannelWake: { fresh: freshChannelWake, upgrade: upgradedChannelWake },
@@ -6,6 +6,14 @@ export const TIMEOUT_EXIT_CODE = 124;
6
6
  const MAX_CAPTURE_CHARS = 1_000_000;
7
7
  const TASKKILL_TIMEOUT_MS = 5_000;
8
8
 
9
+ export function selectTestShard(files, spec) {
10
+ if (!spec) return files;
11
+ const match = /^([1-9]\d*)\/([1-9]\d*)$/.exec(spec);
12
+ const index = +match?.[1]; const count = +match?.[2];
13
+ if (!match || count < 2 || index > count) throw new Error("SHARD");
14
+ return files.filter((_, i) => i % count === index - 1);
15
+ }
16
+
9
17
  function running(child) {
10
18
  return child?.pid && child.exitCode === null && child.signalCode === null;
11
19
  }
@@ -8,8 +8,7 @@ const SEMVER_RE = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-[0-9A-Za-z.-]+)?
8
8
  // Includes the bounded user-feedback candidate path; this is not semantic acceptance.
9
9
  // Measured code growth before documentation: 1874 packed / 7830 unpacked bytes.
10
10
  const MAX_PACKED_BYTES = 544 * 1024;
11
- // King instruction/handoff fix and copied-install probes add about 4 KiB unpacked.
12
- const MAX_UNPACKED_BYTES = 2434 * 1024;
11
+ const MAX_UNPACKED_BYTES = 2430 * 1024;
13
12
  const REQUIRED_PACKAGE_FILES = [
14
13
  "blun.plugin.json", ".claude-plugin/plugin.json", ".codex-plugin/plugin.json", ".mcp.json",
15
14
  "CHANGELOG.md", "LICENSE", "README.md", "bin/agentspine.js", "bin/agentspine-mcp.js",
@@ -79,7 +78,7 @@ function validatePackageReport(report, version) {
79
78
  assert(typeof item.integrity === "string" && item.integrity.startsWith("sha512-"), "npm pack did not report SHA-512 integrity");
80
79
  assert(Number.isInteger(item.entryCount) && item.entryCount > 0 && item.entryCount <= 500, "npm package file count is outside the release limit");
81
80
  assert(Number.isInteger(item.size) && item.size > 0 && item.size <= MAX_PACKED_BYTES, "npm package exceeds the 544 KiB packed release limit");
82
- assert(Number.isInteger(item.unpackedSize) && item.unpackedSize > 0 && item.unpackedSize <= MAX_UNPACKED_BYTES, "npm package exceeds the 2434 KiB unpacked release limit");
81
+ assert(Number.isInteger(item.unpackedSize) && item.unpackedSize > 0 && item.unpackedSize <= MAX_UNPACKED_BYTES, "npm package exceeds the 2430 KiB unpacked release limit");
83
82
  const paths = (item.files || []).map((file) => file.path);
84
83
  assert(paths.length === new Set(paths).size, "npm package contains duplicate paths");
85
84
  for (const path of REQUIRED_PACKAGE_FILES) assert(paths.includes(path), `npm package is missing required file: ${path}`);
@@ -4,16 +4,14 @@ import { mkdir, mkdtemp, readdir, rm, writeFile } from "node:fs/promises";
4
4
  import { join, resolve } from "node:path";
5
5
  import { fileURLToPath } from "node:url";
6
6
  import { githubErrorCommand } from "./github-actions.js";
7
- import { configuredTestTimeout, runBoundedProcess } from "./hermetic-process.js";
7
+ import { configuredTestTimeout, runBoundedProcess, selectTestShard } from "./hermetic-process.js";
8
8
 
9
9
  const root = resolve(fileURLToPath(new URL("..", import.meta.url)));
10
10
  const base = await mkdtemp(join(tmpdir(), "agentspine-hermetic-tests-"));
11
- const testFiles = (await readdir(join(root, "test"))).filter((name) => name.endsWith(".test.js")).sort();
12
- // Windows process creation and antivirus-backed temporary I/O contend sharply
13
- // when several hermetic files each spawn MCP children. Preserve the real child
14
- // deadlines by reducing runner load there, rather than relaxing any deadline.
15
- const concurrencyCeiling = process.platform === "win32" ? 2 : 4;
16
- const concurrency = Math.max(1, Math.min(concurrencyCeiling, cpus().length));
11
+ const testFiles = selectTestShard((await readdir(join(root, "test")))
12
+ .filter((name) => name.endsWith(".test.js")).sort(), process.env.AGENTSPINE_TEST_SHARD);
13
+ const win=process.platform==="win32";
14
+ const concurrency = Math.max(1, Math.min(win ? 3 : 4, cpus().length));
17
15
  const testTimeoutMs = configuredTestTimeout();
18
16
  const slowTestTimeouts = new Map([
19
17
  ["indexed-memory.test.js", 300_000]
@@ -55,10 +53,6 @@ async function runOne(file, mode, index) {
55
53
  async function runMode(mode) {
56
54
  const indexed = testFiles.map((file, index) => ({ file, index }));
57
55
  const results = [];
58
- // Installation copies complete bundles, while the MCP and route-lifecycle tests
59
- // start real children with fixed deadlines and host-origin enrollment attests a
60
- // live source plus signed state. Keep those probes off the shared Windows I/O pool.
61
- // Both profiles still run them once, with unchanged assertions and limits.
62
56
  const isolatedNames = new Set([
63
57
  "assignment-continuation-boundaries.test.js",
64
58
  "assignment-continuation.test.js",
@@ -80,6 +74,9 @@ async function runMode(mode) {
80
74
  "session-timeline-invocation.test.js",
81
75
  "timeline-natural-feedback.test.js"
82
76
  ]);
77
+ if (win) for (const { file } of indexed) {
78
+ if (file.includes("timeline") || file === "preflight-large-agents.test.js") isolatedNames.add(file);
79
+ }
83
80
  const isolated = indexed.filter(item => isolatedNames.has(item.file));
84
81
  const queue = indexed.filter(item => !isolatedNames.has(item.file));
85
82
  for (const item of isolated) results.push(await runOne(item.file, mode, item.index));
@@ -13,22 +13,18 @@ import { syncPersonaRosterFromEnvironment } from "./lib/persona-runtime.js";
13
13
  import { captureMustRememberPrompt, recordPreflightFailure, runPreflight, verifyPreflightReceipt } from "./lib/preflight.js";
14
14
  import { actionLessonRecall } from "./lib/action-lesson-recall.js";
15
15
  import { captureSessionTimelineLifecycle, finalizeUserPromptSessionTimeline } from "./lib/hook-timeline.js";
16
+ import { fitTimelineRecallToHostContext } from "./lib/pre-answer-timeline-recall.js";
16
17
  import { timelineToolKind } from "./lib/mcp-timeline-tools.js";
17
18
  import { emitTimelineToolGuard, runTimelineToolGuard } from "./lib/timeline-tool-guard.js";
18
19
  import { readHookInput, SILENT_OVERSIZE_POST_TOOL_USE, SILENT_OVERSIZE_POST_TOOL_USE_ARG } from "./lib/hook-input.js";
19
20
  import { isMainModule } from "./lib/runtime.js";
20
- import { deliveryActorSession, deliverySuccessEvidence, recordDeliveryToolUse,
21
- recordDeliveryWriteIntent } from "./lib/delivery-verification.js";
22
- import { blockPrompt, blockStop, blunRuntimeContext, blunRuntimeMessage, denyTool, hookOutput,
23
- lifecycleOutput } from "./lib/hook-output.js";
21
+ import { deliveryActorSession, deliverySuccessEvidence, recordDeliveryToolUse, recordDeliveryWriteIntent } from "./lib/delivery-verification.js";
22
+ import { blockPrompt, blockStop, blunRuntimeContext, blunRuntimeMessage, denyTool, hookOutput, lifecycleOutput } from "./lib/hook-output.js";
24
23
  import { ATTENTION_WRITE_EVENTS, boundedId, captureAttentionLifecycle, hookDeliveryId, hostContextLimit, hostFromInput,
25
- promptFromInput, renderContext, runtimeScope, selfstarterInput, selfstarterRootSkipped,
26
- selfstarterScope, sessionId, startChannelEvent, startSelfstarter, toolResult,
27
- toolSucceeded } from "./lib/hook-context.js";
28
- import { auditGuard, auditSkippedScans, candidatePaths, finishScanFailure, hookScanFailureFailsOpen,
29
- isMutationTool, isScanFailOpenTool, shellTargetsProtected } from "./lib/hook-protection.js";
30
- import { captureJavaScriptBeforeWrite, inspectWrittenJavaScript, verifyBaselineBeforeWrite,
31
- verifyDeliveredArtifacts } from "./lib/hook-artifact-guards.js";
24
+ promptFromInput, renderContext, runtimeScope, selfstarterInput, selfstarterRootSkipped, selfstarterScope, sessionId,
25
+ startChannelEvent, startSelfstarter, toolResult, toolSucceeded } from "./lib/hook-context.js";
26
+ import { auditGuard, auditSkippedScans, candidatePaths, finishScanFailure, hookScanFailureFailsOpen, isMutationTool, isScanFailOpenTool, shellTargetsProtected } from "./lib/hook-protection.js";
27
+ import { captureJavaScriptBeforeWrite, inspectWrittenJavaScript, verifyBaselineBeforeWrite, verifyDeliveredArtifacts } from "./lib/hook-artifact-guards.js";
32
28
  import { isPremortemWrite, prepareHookPremortem, recordHookPremortemWrite,
33
29
  recordHookPremortemWriteIntent, verifyHookPremortemWrite } from "./lib/hook-premortem.js";
34
30
  import { verifyHookStopContracts } from "./lib/hook-stop-verification.js";
@@ -38,7 +34,7 @@ export { blunRuntimeContext, blunRuntimeMessage } from "./lib/hook-output.js";
38
34
  const CONTEXT_EVENTS = new Set(["SessionStart", "UserPromptSubmit", "PreCompact", "PostCompact"]);
39
35
  const KNOWN_EVENTS = new Set([...CONTEXT_EVENTS, "InstructionsLoaded", "PreToolUse", "PostToolUse", "Stop", "SubagentStop"]);
40
36
  async function writeContextOutput(output, host) {
41
- const field = Object.hasOwn(output.hookSpecificOutput, "message") ? "message" : "additionalContext", content = output.hookSpecificOutput[field];
37
+ const field = Object.hasOwn(output.hookSpecificOutput, "additionalContext") ? "additionalContext" : "message", content = output.hookSpecificOutput[field];
42
38
  await new Promise((resolveWrite, rejectWrite) => process.stdout.write(`${JSON.stringify(output)}\n`, (error) => error ? rejectWrite(error) : resolveWrite()));
43
39
  return { schema: "agentspine.host-context-handoff/v1", host, field, bytes: Buffer.byteLength(content), digest: createHash("sha256").update(content).digest("hex") };
44
40
  }
@@ -99,17 +95,15 @@ async function runHookCore(input, payload, options) {
99
95
  }
100
96
  const root = resolvedSources.projectRoot;
101
97
  const catalog = resolvedSources.catalog;
102
- const catalogPath = await saveCatalog(catalog), sourceWarning = resolvedSources.diagnostics.warning || null;
103
- let scope = null;
104
- let selfstarter = null;
105
- let channelEvent = null;
106
- let learningDelivery = null;
107
- let deliveryVerification = null;
108
- let artifactGuard = null;
109
- let premortem = null;
110
- let lessonRecall = null;
111
- if (event === "PreToolUse" && isScanFailOpenTool(input.tool_name) && resolvedSources.diagnostics.skipped?.length) {
112
- await auditSkippedScans(input, "source-resolution", resolvedSources.diagnostics.skipped);
98
+ const diagnostics=resolvedSources.diagnostics,catalogPath=await saveCatalog(catalog),sourceWarning=[...new Set([diagnostics.warning,...resolvedSources.unverified.map((item)=>item.message),...(diagnostics.warnings||[]).map((item)=>item.message)].filter(Boolean))].join("\nAgentSpine source warning: ")||null;
99
+ if (CONTEXT_EVENTS.has(event) && resolvedSources.unverified.length && !catalog.summary.total) {
100
+ if (payload) return { blocked: false, sourceUnverified: true, sourceWarning };
101
+ process.stdout.write(`${JSON.stringify({ hookSpecificOutput: { hookEventName: event, message: sourceWarning } })}\n`); return;
102
+ }
103
+ let scope = null, selfstarter = null, channelEvent = null, learningDelivery = null;
104
+ let deliveryVerification = null, artifactGuard = null, premortem = null, lessonRecall = null;
105
+ if (event === "PreToolUse" && isScanFailOpenTool(input.tool_name) && diagnostics.skipped?.length) {
106
+ await auditSkippedScans(input, "source-resolution", diagnostics.skipped);
113
107
  }
114
108
  if (event === "PreToolUse" && isMutationTool(input.tool_name)) {
115
109
  const { graph } = await loadGraph(root, catalog);
@@ -129,7 +123,8 @@ async function runHookCore(input, payload, options) {
129
123
  }
130
124
  }
131
125
  }
132
- const protectedDocuments = catalog.documents.filter((doc) => protectedRelative.has(doc.relativePath));
126
+ const protectedDocuments = [...catalog.documents.filter((doc) => protectedRelative.has(doc.relativePath)),
127
+ ...resolvedSources.unverified.map((item) => ({ path: item.path, relativePath: item.id }))];
133
128
  const protectedPaths = new Set(protectedDocuments.map((doc) => resolve(doc.path)));
134
129
  const targets = await Promise.all(candidatePaths(input.tool_input || input.tool_args).map(async (path) => {
135
130
  const target = resolve(cwd, path);
@@ -141,7 +136,8 @@ async function runHookCore(input, payload, options) {
141
136
  const hit = targets.find((path) => protectedPaths.has(path));
142
137
  const shellHit = shellTargetsProtected(input, protectedDocuments, cwd, root);
143
138
  if (hit || shellHit) {
144
- const relativePath = shellHit?.relativePath || catalog.documents.find((doc) => resolve(doc.path) === hit)?.relativePath || hit;
139
+ const relativePath = shellHit?.relativePath
140
+ || protectedDocuments.find((doc) => resolve(doc.path) === hit)?.relativePath || hit;
145
141
  const reason = `AgentSpine protected source: ${relativePath}. Existing identity, rule, soul, and memory documents are read-only to agents.`;
146
142
  if (payload) return { blocked: true, reason };
147
143
  denyTool(reason);
@@ -170,7 +166,7 @@ async function runHookCore(input, payload, options) {
170
166
  });
171
167
  }
172
168
  }
173
- if (event === "PreToolUse" && !selfstarterRootSkipped(resolvedSources.diagnostics)) {
169
+ if (event === "PreToolUse" && !selfstarterRootSkipped(diagnostics)) {
174
170
  try {
175
171
  scope ||= await runtimeScope(input, root, resolvedSources.userStateRoot, catalog);
176
172
  const exact = await selfstarterScope(input, scope, root, "effect");
@@ -241,7 +237,7 @@ async function runHookCore(input, payload, options) {
241
237
  }
242
238
  }
243
239
  } else {
244
- const activeJob = selfstarterRootSkipped(resolvedSources.diagnostics)
240
+ const activeJob = selfstarterRootSkipped(diagnostics)
245
241
  ? null : await selfstarterScope(input, scope, root, "resume");
246
242
  const pauseRequested = Boolean(activeJob && selfstarterInput(input)?.status !== "completed");
247
243
  let contracts = await verifyHookStopContracts({
@@ -285,7 +281,7 @@ async function runHookCore(input, payload, options) {
285
281
  scope = await runtimeScope(input, root, resolvedSources.userStateRoot, catalog);
286
282
  attentionEvent = await captureAttentionLifecycle(input, event, root, scope, catalog);
287
283
  }
288
- if (event === "PostToolUse" && !selfstarterRootSkipped(resolvedSources.diagnostics)) {
284
+ if (event === "PostToolUse" && !selfstarterRootSkipped(diagnostics)) {
289
285
  scope ||= await runtimeScope(input, root, resolvedSources.userStateRoot, catalog);
290
286
  const exact = await selfstarterScope(input, scope, root, "effect");
291
287
  if (exact) {
@@ -297,7 +293,7 @@ async function runHookCore(input, payload, options) {
297
293
  }
298
294
  if (["Stop", "SubagentStop"].includes(event)) {
299
295
  scope ||= await runtimeScope(input, root, resolvedSources.userStateRoot, catalog);
300
- const exact = selfstarterRootSkipped(resolvedSources.diagnostics)
296
+ const exact = selfstarterRootSkipped(diagnostics)
301
297
  ? null : await selfstarterScope(input, scope, root, "resume");
302
298
  if (exact && !scope.currentTaskId) scope.currentTaskId = exact.taskId;
303
299
  try {
@@ -327,12 +323,12 @@ async function runHookCore(input, payload, options) {
327
323
  await syncPersonaRosterFromEnvironment({ root, env: process.env, now: input.timestamp || new Date(), catalog });
328
324
  scope ||= await runtimeScope(input, root, resolvedSources.userStateRoot, catalog);
329
325
  if (event !== "UserPromptSubmit") {
330
- try { resolvedSources.diagnostics.timeline = await captureSessionTimelineLifecycle({ root, event, input, scope,
326
+ try { diagnostics.timeline = await captureSessionTimelineLifecycle({ root, event, input, scope,
331
327
  hostHome: resolvedSources.hostHome, catalog }); }
332
- catch (error) { resolvedSources.diagnostics.timeline = { status: "degraded", reason: error.message, authority: "context-only" }; }
328
+ catch (error) { diagnostics.timeline = { status: "degraded", reason: error.message, authority: "context-only" }; }
333
329
  }
334
330
  channelEvent = await startChannelEvent(input, event, root, scope, catalog);
335
- selfstarter = await startSelfstarter(input, event, root, scope, resolvedSources.diagnostics);
331
+ selfstarter = await startSelfstarter(input, event, root, scope, diagnostics);
336
332
  if (selfstarter?.job && !scope.currentTaskId) scope.currentTaskId = selfstarter.job.taskId;
337
333
  if (event === "UserPromptSubmit") {
338
334
  const prompt = promptFromInput(input);
@@ -372,14 +368,14 @@ async function runHookCore(input, payload, options) {
372
368
  focusActive: true, includeSourceContent: event === "UserPromptSubmit" ? false : !scope.groupId,
373
369
  maxBytes: event === "UserPromptSubmit" ? 4096 : scope.config.maxBriefingBytes,
374
370
  now: input.timestamp || new Date(),
375
- catalog, userStateRoot: resolvedSources.userStateRoot, sourceDiagnostics: event === "UserPromptSubmit" ? null : resolvedSources.diagnostics,
371
+ catalog, userStateRoot: resolvedSources.userStateRoot, sourceDiagnostics: event === "UserPromptSubmit" ? null : diagnostics,
376
372
  prompt: event === "UserPromptSubmit" ? promptFromInput(input) : null, preAnswer: event === "UserPromptSubmit"
377
373
  });
378
374
  if (event === "PostCompact") {
379
375
  try { lessonRecall = await actionLessonRecall({ catalog, event, input, scope }); }
380
376
  catch (error) { lessonRecall = { status: "degraded", items: [], reason: error.message, authority: "context-only" }; }
381
377
  }
382
- let context = renderContext(event, catalog, briefing, signal, attentionEvent, selfstarter, channelEvent, resolvedSources.diagnostics, preflight, lessonRecall);
378
+ let context = renderContext(event, catalog, briefing, signal, attentionEvent, selfstarter, channelEvent, diagnostics, preflight, lessonRecall);
383
379
  if (event === "UserPromptSubmit" && Buffer.byteLength(context) > hostContextLimit(preflight)) {
384
380
  throw new Error("mandatory preflight context exceeds the host hook injection limit");
385
381
  }
@@ -388,7 +384,7 @@ async function runHookCore(input, payload, options) {
388
384
  prompt: promptFromInput(input), now: input.timestamp || new Date() });
389
385
  if (!finalized.preflightConsumed) throw new Error("preflight receipt could not be consumed atomically for this exact turn");
390
386
  briefingOrigin = finalized.briefingOrigin;
391
- resolvedSources.diagnostics.timeline = finalized.timeline;
387
+ diagnostics.timeline = finalized.timeline;
392
388
  }
393
389
  if (event === "UserPromptSubmit") {
394
390
  const activeCanaries = briefing.learning.filter((item) => ["active", "revalidating"].includes(item.outcomeStatus));
@@ -420,17 +416,21 @@ async function runHookCore(input, payload, options) {
420
416
  status: "degraded", receipts: [], reason: error.message, authority: "context-only"
421
417
  };
422
418
  }
423
- const enriched = renderContext(event, catalog, briefing, signal, attentionEvent, selfstarter, channelEvent,
424
- resolvedSources.diagnostics, preflight, lessonRecall);
419
+ const fitted = fitTimelineRecallToHostContext({ timeline: diagnostics.timeline,
420
+ maximumBytes: hostContextLimit(preflight), render: (timeline) => renderContext(event, catalog, briefing,
421
+ signal, attentionEvent, selfstarter, channelEvent, { ...diagnostics, timeline },
422
+ preflight, lessonRecall) });
423
+ diagnostics.timeline = fitted.timeline;
424
+ const enriched = fitted.context;
425
425
  if (Buffer.byteLength(enriched) <= hostContextLimit(preflight)) context = enriched;
426
426
  else if (preflight.learningApplications.status === "degraded") {
427
427
  preflight.learningApplications = null;
428
428
  context = renderContext(event, catalog, briefing, signal, attentionEvent, selfstarter, channelEvent,
429
- resolvedSources.diagnostics, preflight, lessonRecall);
429
+ diagnostics, preflight, lessonRecall);
430
430
  }
431
431
  }
432
432
  if (payload) return { blocked: false, context, briefing, preflight, signal, attentionEvent, channelEvent, lessonRecall, catalogPath };
433
- const output = hookOutput(event, context);
433
+ const output = hookOutput(event, context, process.env, sourceWarning);
434
434
  const handoff = await writeContextOutput(output, process.env.BLUN_PLUGIN_ROOT ? "king"
435
435
  : process.env.PLUGIN_ROOT ? "codex" : process.env.CLAUDE_PLUGIN_ROOT ? "claude" : scope.host);
436
436
  if (event === "UserPromptSubmit" && briefingOrigin) {
@@ -452,7 +452,7 @@ async function runHookCore(input, payload, options) {
452
452
  const context = JSON.stringify({
453
453
  schema: "agentspine.hook-context/v1", event, loaded: false, failedClosed: true,
454
454
  indexedSources: catalog.summary.total,
455
- sourceResolution: resolvedSources.diagnostics,
455
+ sourceResolution: diagnostics,
456
456
  error: error.message,
457
457
  instruction: "Do not claim AgentSpine recall succeeded. Continue with the current request under native host rules and run agentspine audit before using remembered context.",
458
458
  authority: "context-only"
@@ -106,9 +106,12 @@ function feedbackCandidates(values) {
106
106
  const fields = ["id", "text", "message", "at",
107
107
  ...["provider", "digest", "session"].filter((key) => !shared.includes(key))];
108
108
  const expected = { id: "assertion:user-feedback-", message: "timeline-event:" };
109
- const prefixes = fields.map((key) => expected[key] || (key === "at"
110
- && values.every((item) => /^\d{4}-\d{2}-\d{2}T\d{2}:/.test(item[key]))
111
- ? values[0][key].slice(0, 14) : ""));
109
+ const prefixes = fields.map((key) => {
110
+ const prefix = expected[key] || (key === "at"
111
+ && values.every((item) => /^\d{4}-\d{2}-\d{2}T\d{2}:/.test(item[key]))
112
+ ? values[0][key].slice(0, 14) : "");
113
+ return values.every((item) => item[key].startsWith(prefix)) ? prefix : "";
114
+ });
112
115
  return { fields, common: Object.fromEntries(shared.map((key) => [key, values[0][key]])), prefixes,
113
116
  rows: values.map((item) => fields.map((key, index) => item[key].slice(prefixes[index].length))) };
114
117
  }