@awebai/oats 0.29.0 → 0.29.1
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/bin/oats.mjs +2 -1
- package/docs/desktop-cli-api.md +20 -4
- package/docs/release-notes/v0.29.1.md +79 -0
- package/lib/instance-git.mjs +30 -1
- package/package.json +1 -1
package/bin/oats.mjs
CHANGED
|
@@ -958,7 +958,8 @@ function instanceCmd() {
|
|
|
958
958
|
console.log(` upstream: ${observed.upstream.ref ? `${observed.upstream.ref} +${observed.upstream.ahead} -${observed.upstream.behind}` : "none (ahead/behind unknown)"}`);
|
|
959
959
|
console.log(` base: ${observed.base.ref ? `${observed.base.ref} +${observed.base.ahead} -${observed.base.behind} (merge-base ${observed.base.mergeBase?.slice(0, 12)})` : "unknown"}`);
|
|
960
960
|
console.log(` files: ${observed.files.length} (${Object.entries(observed.summary).filter(([, n]) => n).map(([k, n]) => `${n} ${k}`).join(", ") || "clean"})`);
|
|
961
|
-
|
|
961
|
+
const lines = (f) => (f.binary ? " binary" : Number.isInteger(f.additions) ? ` +${f.additions} -${f.deletions}` : "");
|
|
962
|
+
for (const f of observed.files) console.log(` ${f.xy} ${f.origPath ? `${f.origPath} -> ` : ""}${f.path}${lines(f)} [${f.id}]`);
|
|
962
963
|
for (const n of observed.notes) console.log(` note: ${n}`);
|
|
963
964
|
return;
|
|
964
965
|
}
|
package/docs/desktop-cli-api.md
CHANGED
|
@@ -19,9 +19,12 @@ prints exactly one JSON object on stdout:
|
|
|
19
19
|
|
|
20
20
|
`version` is the installed package's exact semver (e.g. `0.20.0`).
|
|
21
21
|
The Desktop accepts `desktopApi === 1` and gates on the kernel feature
|
|
22
|
-
`packages-no-approval` (semver range `>=0.25.8 <0.
|
|
23
|
-
|
|
24
|
-
|
|
22
|
+
`packages-no-approval` (semver range `>=0.25.8 <0.30.0`, spelled once in
|
|
23
|
+
`packages/desktop/cli-locator.mjs` `ACCEPT_RANGE`: the floor admits the
|
|
24
|
+
main-branch kernel before 0.26.0 was tagged; the feature fences are the real gate,
|
|
25
|
+
and 0.29's reads are gated on `automations` and `desktop-facts`).
|
|
26
|
+
Earlier bands were `>=0.25.8 <0.29.0` (Desktop 0.28), `<0.28.0` (Desktop 0.27),
|
|
27
|
+
`<0.27.0` (Desktop 0.26), `>=0.22.0 <0.26.0` (Desktop 0.25) and `>=0.22.0 <0.24.0`
|
|
25
28
|
(Desktop 0.23). It does not establish complete
|
|
26
29
|
UI, backend, plugin, retirement or recovery parity; capability checks and explicit
|
|
27
30
|
refusals below remain authoritative.
|
|
@@ -483,7 +486,7 @@ un-materialized tree → `E_NO_WORKTREE`.
|
|
|
483
486
|
"base":{"ref":"origin/main","source":"origin/HEAD","mergeBase":"<oid>","ahead":2,"behind":0},
|
|
484
487
|
"remote":{"name":"origin","url":"git@github.com:acme/one.git","host":"github.com","path":"acme/one","source":"branch-upstream|origin"},
|
|
485
488
|
"summary":{"changed":1,"renamed":1,"copied":0,"unmerged":0,"untracked":1},
|
|
486
|
-
"files":[{"id":"<24 hex>","kind":"renamed","xy":"R.","submodule":false,"score":"R100","path":"src/new.txt","origPath":"src/old.txt"}],
|
|
489
|
+
"files":[{"id":"<24 hex>","kind":"renamed","xy":"R.","submodule":false,"score":"R100","path":"src/new.txt","origPath":"src/old.txt","additions":84,"deletions":3,"binary":false}],
|
|
487
490
|
"notes":[]}
|
|
488
491
|
```
|
|
489
492
|
|
|
@@ -496,6 +499,19 @@ un-materialized tree → `E_NO_WORKTREE`.
|
|
|
496
499
|
unmerged | untracked. Ignored files are not listed.
|
|
497
500
|
- `files[].id` is **opaque**, minted under (`revision`, `indexRevision`). It is
|
|
498
501
|
the only way to ask for a diff.
|
|
502
|
+
- `files[].additions`, `deletions` and `binary` (0.29.1, additive;
|
|
503
|
+
`instanceGitApi` stays 1) are each file's line counts.
|
|
504
|
+
- They come from one `git diff <revision> --numstat -z -M` per observation:
|
|
505
|
+
the working tree against the observed commit, **staged and unstaged
|
|
506
|
+
combined**. That is the baseline of the status letters and of
|
|
507
|
+
`oats instance diff`.
|
|
508
|
+
- An unborn tree counts against the empty tree. A rename counts on its new
|
|
509
|
+
`path`.
|
|
510
|
+
- A binary file is `additions: null, deletions: null, binary: true`. An
|
|
511
|
+
untracked file (no baseline; its contents are not read) and a submodule
|
|
512
|
+
are all `null`.
|
|
513
|
+
- If the count itself fails, every entry is `null` and `notes` says line
|
|
514
|
+
counts are unavailable. `null` means unknown, never zero.
|
|
499
515
|
- `remote` (0.24.8+): the branch's configured remote (`source: branch-upstream`),
|
|
500
516
|
else `origin`, else `null` — never invented. `host`/`path` are **parsed** from
|
|
501
517
|
the URL (ssh/https forms; `.git` stripped) so an ADE can choose a forge backend
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# OATS 0.29.1
|
|
2
|
+
|
|
3
|
+
## Added
|
|
4
|
+
|
|
5
|
+
- **Per-file line counts in `oats instance git`.** Each tracked file entry has
|
|
6
|
+
`additions` and `deletions` (integers) and `binary`, so the Desktop can show
|
|
7
|
+
`+84 −3` beside a changed file. They come from one `git diff --numstat`
|
|
8
|
+
against the observed commit, counting staged and unstaged changes together,
|
|
9
|
+
which is the same baseline as the status letters and `oats instance diff`.
|
|
10
|
+
- A binary file has `additions: null, deletions: null, binary: true`.
|
|
11
|
+
- An untracked file or a submodule has all three `null`.
|
|
12
|
+
- A rename is counted on its new path.
|
|
13
|
+
- `instanceGitApi` stays 1: the fields are additive. The text output shows
|
|
14
|
+
`+N -M` after each path. See
|
|
15
|
+
[desktop-cli-api.md](../desktop-cli-api.md#instance-git-state-oats-instance-gitdiff-instancegitapi-1-oats-0247).
|
|
16
|
+
|
|
17
|
+
## Changed
|
|
18
|
+
|
|
19
|
+
- **The `oats-setup-admin` soul is never harvested.** Its `soul.yaml` opts out
|
|
20
|
+
of OKF harvest with `knowledge: { harvest: off }`: its sessions work on the
|
|
21
|
+
deployment's own config and carry deployment specifics that must not reach
|
|
22
|
+
the shared knowledge base. oats.okf 4.0.0 makes a soul's opt-out absolute, so
|
|
23
|
+
a deployment that switches harvest on (`settings.oats.okf.harvest: on`)
|
|
24
|
+
still registers no source, captures nothing and takes no custody for its
|
|
25
|
+
instances. `oats okf harvest-status` in one of its homes reports
|
|
26
|
+
`harvest: off` with the soul's reason.
|
|
27
|
+
|
|
28
|
+
## Desktop
|
|
29
|
+
|
|
30
|
+
The Desktop now shows the kernel's 0.29 Desktop facts and the Workspace v4
|
|
31
|
+
design's Git & GitHub section. On an older kernel, each fact is absent and its
|
|
32
|
+
view shows nothing new.
|
|
33
|
+
|
|
34
|
+
- **Souls say when a spawn here would refuse** (#228, #231). A soul the kernel
|
|
35
|
+
reports as not spawnable shows "Can't spawn here" with the kernel's reason on
|
|
36
|
+
its card and page, and its Spawn and Schedule… are disabled with that reason.
|
|
37
|
+
A soul page offers Open file for a hosted soul file, and shows the file's path
|
|
38
|
+
when it has no web address.
|
|
39
|
+
- **The server passes the kernel's Desktop facts through** (#230). Its souls,
|
|
40
|
+
capabilities, workspace status and instance projections keep those fields,
|
|
41
|
+
bounded, instead of dropping them. A soul's spawn default is nested as
|
|
42
|
+
`spawnDefault`, so it is never read as the soul's own harness or model.
|
|
43
|
+
- **Capability pages** (#232) show the description, what the capability
|
|
44
|
+
provides (skills, commands and hooks by name, or "Not listable"), its file and
|
|
45
|
+
its fingerprint.
|
|
46
|
+
- **Setup names the files and this computer's state** (#233). It names the
|
|
47
|
+
workspace and membership files, and links them when they are hosted. A member
|
|
48
|
+
panel has Open repository and its clone on this computer. This computer lists
|
|
49
|
+
the lock file, disabled souls and each member's clone, or "not cloned here".
|
|
50
|
+
Packages with a newer version say "X available".
|
|
51
|
+
- **Setup shows the workspace defaults** (#237): each slot and its source ("not
|
|
52
|
+
set" or "none" when empty), the default capabilities, and what each team adds
|
|
53
|
+
or turns off.
|
|
54
|
+
- **The instance page** (#235) says when the session started, where its model
|
|
55
|
+
came from (the soul, the spawn, the start, the launch configuration or the
|
|
56
|
+
harness default), and its messaging address.
|
|
57
|
+
- **Each local instance's pull request on its roster row** (#234, #236). A new
|
|
58
|
+
`POST /api/forge-roster` maps each instance to its member's clone from the
|
|
59
|
+
kernel's workspace status and reads the branch's PR with `gh`, cached for a
|
|
60
|
+
minute. The row shows `#N`, with draft, merged or closed in words. Remote
|
|
61
|
+
instances, and hosts without a signed-in `gh`, show no badge.
|
|
62
|
+
- **W6 Git & GitHub** (#239, #240, #241, #242, #243). The instance's Git panel
|
|
63
|
+
follows the design:
|
|
64
|
+
- **Branch** shows the work mode, the distance from the default branch and
|
|
65
|
+
whether the tree is clean, with the rest behind Details.
|
|
66
|
+
- **Changes** is a list of status letters and paths, with Open diff.
|
|
67
|
+
- **Pull request** shows the title and state, the issues it closes and the
|
|
68
|
+
checks: failing first, with how long each took. It also shows the review
|
|
69
|
+
decision with the count of unresolved review threads, and has Open on
|
|
70
|
+
GitHub.
|
|
71
|
+
- Check marks are icons. The thread count is read only for the open
|
|
72
|
+
instance's PR, never for the roster.
|
|
73
|
+
- **Automations through the app proxy** (#224). `/api/automations` has its own
|
|
74
|
+
proxy route, pinned to the verified workspace like the other workspace
|
|
75
|
+
endpoints. It has a 90 s deadline, so `trigger test` (which polls the forge)
|
|
76
|
+
is no longer cut off in the packaged app. The automations docs describe the
|
|
77
|
+
API and the proxy.
|
|
78
|
+
- **Docs** (#227): the documented Desktop band is `>=0.25.8 <0.30.0`.
|
|
79
|
+
- The Desktop still accepts OATS CLIs `>=0.25.8 <0.30.0`.
|
package/lib/instance-git.mjs
CHANGED
|
@@ -138,6 +138,33 @@ function remoteOf(work, branch) {
|
|
|
138
138
|
if (!url) return null;
|
|
139
139
|
return { name, url, ...parseRemoteUrl(url), source: configured && configured !== "." ? "branch-upstream" : "origin" };
|
|
140
140
|
}
|
|
141
|
+
/** Per-file line counts (0.29.1), from ONE `git diff <captured HEAD> --numstat -z -M`: the working tree
|
|
142
|
+
* against the observed commit, staged and unstaged combined. That is the same baseline as the status letters
|
|
143
|
+
* and `oats instance diff`. An unborn tree counts against the empty tree (`hash-object` without -w writes nothing).
|
|
144
|
+
* → Map path → { additions, deletions, binary }, keyed by the NEW path for a rename; null when git failed.
|
|
145
|
+
* -z records: "A\tD\tpath\0", or "A\tD\t\0old\0new\0" for a rename; "-\t-" is a binary file. */
|
|
146
|
+
function lineCounts(work, headOid) {
|
|
147
|
+
const against = headOid ?? trim(git(work, ["hash-object", "-t", "tree", "/dev/null"], { allowFail: true }));
|
|
148
|
+
if (!against) return null;
|
|
149
|
+
const raw = git(work, ["diff", against, "--numstat", "-z", "-M", "--"], { allowFail: true });
|
|
150
|
+
if (raw === null) return null;
|
|
151
|
+
const counts = new Map(), recs = raw.split("\0");
|
|
152
|
+
for (let i = 0; i < recs.length;) {
|
|
153
|
+
const m = /^(-|\d+)\t(-|\d+)\t([\s\S]*)$/.exec(recs[i++]);
|
|
154
|
+
if (!m) continue;
|
|
155
|
+
let path = m[3];
|
|
156
|
+
if (path === "") { i++; path = recs[i++]; } // a rename: skip the old path, key the new one
|
|
157
|
+
if (typeof path !== "string" || !path) continue;
|
|
158
|
+
counts.set(path, m[1] === "-" ? { additions: null, deletions: null, binary: true } : { additions: Number(m[1]), deletions: Number(m[2]), binary: false });
|
|
159
|
+
}
|
|
160
|
+
return counts;
|
|
161
|
+
}
|
|
162
|
+
/** An entry's counts: untracked (no baseline; contents are not read) and submodules have none (all null). */
|
|
163
|
+
function countsOf(counts, entry) {
|
|
164
|
+
if (entry.kind === "untracked" || entry.submodule) return { additions: null, deletions: null, binary: null };
|
|
165
|
+
return counts?.get(entry.path) ?? { additions: null, deletions: null, binary: null };
|
|
166
|
+
}
|
|
167
|
+
|
|
141
168
|
function fileId(revision, indexOid, entry) {
|
|
142
169
|
return createHash("sha256").update(`${revision}\0${indexOid}\0${entry.kind}\0${entry.path}\0${entry.origPath ?? ""}`).digest("hex").slice(0, 24);
|
|
143
170
|
}
|
|
@@ -164,7 +191,8 @@ export function observeInstanceGit(home) {
|
|
|
164
191
|
const counts = mergeBase ? countRange(work, `${base.ref}...HEAD`) : null;
|
|
165
192
|
baseComparison = { ref: base.ref, source: base.source, mergeBase, ahead: counts ? counts.right : null, behind: counts ? counts.left : null };
|
|
166
193
|
}
|
|
167
|
-
const
|
|
194
|
+
const counts = lineCounts(work, headOid);
|
|
195
|
+
const files = entries.filter((e) => e.kind !== "ignored").map((e) => ({ id: fileId(revision, indexOid, e), ...e, ...countsOf(counts, e) }));
|
|
168
196
|
const summary = { changed: 0, renamed: 0, copied: 0, unmerged: 0, untracked: 0 };
|
|
169
197
|
for (const f of files) summary[f.kind]++;
|
|
170
198
|
return {
|
|
@@ -178,6 +206,7 @@ export function observeInstanceGit(home) {
|
|
|
178
206
|
notes: [
|
|
179
207
|
...(upstream.ref === null ? ["no upstream configured: upstream ahead/behind are unknown, not zero"] : []),
|
|
180
208
|
...(baseComparison.ref === null ? ["no default branch found (origin/HEAD, origin/main, origin/master, main, master): base comparison unknown"] : []),
|
|
209
|
+
...(counts === null ? ["line counts unavailable (git diff --numstat failed): additions/deletions are unknown, not zero"] : []),
|
|
181
210
|
],
|
|
182
211
|
};
|
|
183
212
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awebai/oats",
|
|
3
|
-
"version": "0.29.
|
|
3
|
+
"version": "0.29.1",
|
|
4
4
|
"description": "OATS (Open Agent Team Specification) — durable souls, disposable instances, targetable capability packages, and the runtime-neutral oats CLI/kernel.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"agents",
|