@visual-z/xeno 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -17,7 +17,7 @@ is ever summarized by a model, and nothing is ever rewritten in place.
17
17
  messages[] frozen system prefix + handoff + this node's events
18
18
  ```
19
19
 
20
- Version **0.1.2**. The on-disk session format will still change; `0.x` is that
20
+ Version **0.1.3**. The on-disk session format will still change; `0.x` is that
21
21
  statement. Published as `@visual-z/xeno`; the license is `UNLICENSED` (readable and installable, no reuse granted).
22
22
 
23
23
  ## Install
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visual-z/xeno",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "license": "UNLICENSED",
5
5
  "type": "module",
6
6
  "description": "Segmented append-only agent log with pure context and handoff projections",
@@ -27,6 +27,12 @@ function hasUnseenLines(requested: readonly LineRange[], observed: readonly Line
27
27
  });
28
28
  }
29
29
 
30
+ /**
31
+ * A file version's fingerprint in the observation set. Keyed by digest alone:
32
+ * the same bytes under another path are not new work either.
33
+ */
34
+ const versionKey = (_path: string, sha256: string): string => `version:${sha256}`;
35
+
30
36
  export function deriveProgress(
31
37
  previous: readonly ProgressEvidence[], events: readonly LogEvent[], node: number,
32
38
  eligibleChecks: ReadonlySet<string>,
@@ -45,6 +51,8 @@ export function deriveProgress(
45
51
  seen.push(item);
46
52
  reads.set(item.sha256, seen);
47
53
  if (item.observation_sha256 !== undefined) observations.add(item.observation_sha256);
54
+ // A version read before a cut is as known as one read in this segment.
55
+ observations.add(versionKey("", item.sha256));
48
56
  } else if (item.kind === "observation") observations.add(item.sha256);
49
57
  else checks.add(checkKey(item.command, item.gate, item.workspace_sha256));
50
58
  }
@@ -61,6 +69,7 @@ export function deriveProgress(
61
69
  const source = { node, event_id: event.id };
62
70
  const effect = event.effect;
63
71
  const observation = event.observation_sha256 ?? event.content_ref?.sha256 ?? event.content_sha256;
72
+ if (effect?.kind === "read" && effect.sha256 !== "") observations.add(versionKey(effect.path, effect.sha256));
64
73
  if (effect?.kind === "read" && effect.sha256 !== "") {
65
74
  const seen = reads.get(effect.sha256) ?? [];
66
75
  const novel = !seen.some((r) => r.ranges === undefined) &&
@@ -89,6 +98,22 @@ export function deriveProgress(
89
98
  // rerun of old evidence into a fresh completion by giving it a new name.
90
99
  if (eligibleChecks.has(event.id)) progressed = true;
91
100
  }
101
+ } else if (effect?.kind === "edit") {
102
+ // D9 revision (2026-09-29, user-approved): a file version no one has
103
+ // seen before is new evidence -- it is how implementation work shows up.
104
+ // Versions already seen anywhere (read, or either side of an edit) do
105
+ // not count, so an A->B->A rewrite loop still stalls.
106
+ // Both sides become durable evidence: the ledger is all a later
107
+ // derivation sees, and a version seen only as "before" must still be
108
+ // recognised when the file is edited back to it.
109
+ for (const [sha, isAfter] of [[effect.sha256_before, false], [effect.sha256_after, true]] as const) {
110
+ if (sha === null) continue;
111
+ const key = versionKey(effect.path, sha);
112
+ if (observations.has(key)) continue;
113
+ observations.add(key);
114
+ evidence.push({ kind: "observation", sha256: key, source });
115
+ if (isAfter) progressed = true;
116
+ }
92
117
  } else if ((name === "grep" || name === "find" || name === "ls" || name === "query") &&
93
118
  observation !== undefined && (event.content_ref?.bytes ?? event.content.length) > 0 &&
94
119
  !observations.has(observation)) {
@@ -17,7 +17,7 @@ import { createServer, type Server } from "node:http";
17
17
  import { pollDeviceCode } from "./oauth-device.ts";
18
18
 
19
19
  /** Matches package.json. Sent as User-Agent so the server can see who we are (D18). */
20
- export const XENO_VERSION = "0.1.2";
20
+ export const XENO_VERSION = "0.1.3";
21
21
 
22
22
  /**
23
23
  * Minutes subtracted from server expiry so a request issued at the edge
@@ -135,7 +135,7 @@ export type ApprovalChoice = "once" | "command" | "tool" | "deny";
135
135
  export const DEFAULT_TOOL_CONTEXT: Omit<ToolContext, "root"> = {
136
136
  // Runner prefixes are resolved in command position so `python3 -m pytest`
137
137
  // works without letting `echo pytest` forge a gate (D11 / M.2).
138
- gates: { pytest: "pytest", tsc: "typecheck", cargo: "cargo", jest: "test", vitest: "test" },
138
+ gates: { pytest: "pytest", tsc: "typecheck", cargo: "cargo", jest: "test", vitest: "test", go: "go" },
139
139
  // Five minutes covers typical compiles and installs; longer work asks for
140
140
  // its own `timeout` up to the hour ceiling (omp uses the same 300 / 3600).
141
141
  // Approved change from 120s (2026-09-28): 120s hard-killed real builds.