@awebai/oats 0.29.4 → 0.30.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.
- package/README.md +12 -6
- package/bin/oats.mjs +194 -50
- package/capabilities/oats-aweb/bin/oats-aweb.mjs +538 -204
- package/capabilities/oats-aweb/injects/aweb.md +1 -1
- package/capabilities/oats-aweb/lib/binding-wire.mjs +31 -22
- package/capabilities/oats-aweb/oats.json +5 -12
- package/capabilities/oats-aweb/skills/VENDORED.md +4 -4
- package/capabilities/oats-aweb/skills/aweb-team-membership/SKILL.md +1 -1
- package/capabilities/oats-aweb/skills/oats-aweb/SKILL.md +83 -13
- package/capabilities/oats-code-review/injects/reviewer.md +26 -0
- package/capabilities/oats-code-review/oats.json +16 -0
- package/capabilities/oats-code-review/skills/adversarial-review/SKILL.md +66 -0
- package/capabilities/oats-code-review/skills/review-dev-docs/SKILL.md +30 -0
- package/capabilities/oats-code-review/skills/security-review/SKILL.md +56 -0
- package/capabilities/oats-code-review/skills/simplification-review/SKILL.md +34 -0
- package/capabilities/oats-developer/injects/developer.md +38 -0
- package/capabilities/oats-developer/oats.json +17 -0
- package/capabilities/oats-developer/skills/execution-strategy/SKILL.md +43 -0
- package/capabilities/oats-developer/skills/maintain-dev-docs/SKILL.md +47 -0
- package/capabilities/oats-developer/skills/run-the-review-loop/SKILL.md +65 -0
- package/capabilities/oats-developer/skills/understand-the-spec/SKILL.md +37 -0
- package/capabilities/oats-developer/skills/worktrees/SKILL.md +36 -0
- package/capabilities/oats-engineering-expert/injects/expert.md +37 -0
- package/capabilities/oats-engineering-expert/oats.json +17 -0
- package/capabilities/oats-engineering-expert/skills/coordinate-developers/SKILL.md +37 -0
- package/capabilities/oats-engineering-expert/skills/coordinate-experts/SKILL.md +52 -0
- package/capabilities/oats-engineering-expert/skills/land-your-prs/SKILL.md +50 -0
- package/capabilities/oats-engineering-expert/skills/plan-and-spec/SKILL.md +53 -0
- package/capabilities/oats-engineering-expert/skills/verify-developer-work/SKILL.md +49 -0
- package/capabilities/oats-okf/bin/oats-okf.mjs +8 -4
- package/capabilities/oats-okf/lib/binding-wire.mjs +47 -15
- package/capabilities/oats-okf/lib/inspection.mjs +26 -7
- package/capabilities/oats-okf/lib/sources.mjs +16 -2
- package/capabilities/oats-okf/lib/worker.mjs +5 -16
- package/capabilities/oats-okf/oats.json +6 -3
- package/capabilities/oats-okf-harvest/bin/okf-harvest.mjs +2 -2
- package/capabilities/oats-okf-harvest/oats.json +3 -3
- package/capabilities/oats-okf-harvest/skills/knowledge-harvest/SKILL.md +6 -6
- package/capabilities/oats-okf-maintenance/bin/okf-maintenance.mjs +2 -2
- package/capabilities/oats-okf-maintenance/injects/maintainer.md +1 -1
- package/capabilities/oats-okf-maintenance/oats.json +2 -2
- package/capabilities/oats-okf-maintenance/skills/knowledge-review/SKILL.md +1 -1
- package/capabilities/oats-okf-maintenance/skills/okf-trigger-setup/SKILL.md +11 -23
- package/capabilities/oats-workspace-experts/injects/oats-experts.md +26 -0
- package/capabilities/oats-workspace-experts/oats.json +9 -0
- package/docs/capabilities.md +160 -171
- package/docs/capability-manifest.schema.json +6 -11
- package/docs/configuration.md +213 -64
- package/docs/design/2026-09-16-knowledge-capability-contract.md +36 -50
- package/docs/design/2026-09-23-workspace-module-contracts.md +377 -544
- package/docs/design/2026-09-26-okf-knowledge-operations.md +132 -357
- package/docs/design/2026-09-27-team-model-v2.md +97 -117
- package/docs/design/2026-09-28-automations-trust.md +38 -0
- package/docs/design/2026-09-28-soul-launch-preference.md +63 -0
- package/docs/design/HISTORY.md +65 -0
- package/docs/design/README.md +23 -54
- package/docs/desktop-cli-api.md +1787 -1777
- package/docs/desktop.md +30 -91
- package/docs/execution-targets.md +146 -292
- package/docs/first-team.md +31 -17
- package/docs/implementation.md +76 -288
- package/docs/integrations.md +118 -320
- package/docs/knowledge-capability-authoring.md +25 -52
- package/docs/knowledge-reference/acceptance.md +3 -3
- package/docs/knowledge-reference/adoption.md +1 -1
- package/docs/knowledge-reference/harvester.md +2 -2
- package/docs/knowledge-reference/package-craft.md +3 -3
- package/docs/knowledge-reference/provider-mapping.md +3 -6
- package/docs/knowledge-reference/reader-capture.md +3 -3
- package/docs/knowledge-theory.md +62 -166
- package/docs/knowledge.md +225 -404
- package/docs/layers.md +42 -97
- package/docs/oats-local.schema.json +58 -5
- package/docs/oats-membership.schema.json +1 -8
- package/docs/oats-package.schema.json +5 -5
- package/docs/oats-workspace.schema.json +8 -22
- package/docs/official-catalog.md +25 -28
- package/docs/packages.md +45 -63
- package/docs/plans/0.30-close-out.md +61 -0
- package/docs/release-lane.md +77 -0
- package/docs/release-notes/oats-framework-v1.1.3.md +10 -8
- package/docs/release-notes/v0.19.0.md +48 -147
- package/docs/release-notes/v0.19.1.md +2 -3
- package/docs/release-notes/v0.19.3.md +2 -15
- package/docs/release-notes/v0.20.0.md +0 -15
- package/docs/release-notes/v0.22.0.md +71 -138
- package/docs/release-notes/v0.22.1.md +42 -90
- package/docs/release-notes/v0.22.10.md +1 -1
- package/docs/release-notes/v0.22.11.md +1 -47
- package/docs/release-notes/v0.22.12.md +4 -13
- package/docs/release-notes/v0.22.13.md +1 -42
- package/docs/release-notes/v0.22.14.md +3 -11
- package/docs/release-notes/v0.22.15.md +1 -46
- package/docs/release-notes/v0.22.16.md +6 -8
- package/docs/release-notes/v0.22.18.md +1 -99
- package/docs/release-notes/v0.22.19.md +3 -14
- package/docs/release-notes/v0.22.2.md +6 -15
- package/docs/release-notes/v0.22.3.md +0 -1
- package/docs/release-notes/v0.22.4.md +1 -14
- package/docs/release-notes/v0.22.5.md +2 -12
- package/docs/release-notes/v0.22.6.md +0 -3
- package/docs/release-notes/v0.23.0.md +9 -25
- package/docs/release-notes/v0.23.1.md +9 -25
- package/docs/release-notes/v0.23.2.md +2 -4
- package/docs/release-notes/v0.24.0.md +56 -97
- package/docs/release-notes/v0.24.1.md +7 -11
- package/docs/release-notes/v0.24.10.md +34 -45
- package/docs/release-notes/v0.24.11.md +12 -20
- package/docs/release-notes/v0.24.12.md +35 -48
- package/docs/release-notes/v0.24.13.md +34 -41
- package/docs/release-notes/v0.24.2.md +9 -13
- package/docs/release-notes/v0.24.3.md +7 -11
- package/docs/release-notes/v0.24.4.md +6 -6
- package/docs/release-notes/v0.24.5.md +6 -10
- package/docs/release-notes/v0.24.6.md +2 -5
- package/docs/release-notes/v0.24.7.md +46 -75
- package/docs/release-notes/v0.24.8.md +58 -96
- package/docs/release-notes/v0.24.9.md +38 -54
- package/docs/release-notes/v0.25.0.md +59 -76
- package/docs/release-notes/v0.25.1.md +57 -81
- package/docs/release-notes/v0.25.2.md +51 -70
- package/docs/release-notes/v0.25.3.md +11 -13
- package/docs/release-notes/v0.25.4.md +9 -13
- package/docs/release-notes/v0.25.5.md +3 -5
- package/docs/release-notes/v0.25.6.md +20 -29
- package/docs/release-notes/v0.25.7.md +5 -7
- package/docs/release-notes/v0.25.8.md +26 -39
- package/docs/release-notes/v0.26.0.md +175 -646
- package/docs/release-notes/v0.27.0.md +4 -5
- package/docs/release-notes/v0.27.1.md +4 -6
- package/docs/release-notes/v0.27.2.md +1 -1
- package/docs/release-notes/v0.28.0.md +57 -124
- package/docs/release-notes/v0.29.0.md +89 -208
- package/docs/release-notes/v0.29.1.md +1 -1
- package/docs/release-notes/v0.29.2.md +3 -4
- package/docs/release-notes/v0.30.0.md +205 -0
- package/docs/schedules.md +280 -363
- package/docs/servers.md +99 -117
- package/docs/soul.schema.json +2 -9
- package/docs/souls-and-instances.md +145 -158
- package/docs/workspaces.md +132 -215
- package/lib/automations.mjs +21 -6
- package/lib/core.mjs +226 -74
- package/lib/instance-events.mjs +1 -1
- package/lib/instance-inspect.mjs +109 -34
- package/lib/instance-lifecycle.mjs +14 -1
- package/lib/instance-resolution.mjs +26 -27
- package/lib/launch-preference.mjs +87 -0
- package/lib/materialize.mjs +3 -3
- package/lib/resolve.mjs +29 -87
- package/lib/schedule.mjs +1 -1
- package/lib/teams-verbs.mjs +195 -0
- package/lib/teams.mjs +190 -0
- package/lib/triggers.mjs +2 -2
- package/lib/workspace.mjs +54 -147
- package/package-catalog.json +9 -15
- package/package.json +1 -1
- package/skills/oats-getting-started/SKILL.md +25 -13
- package/capabilities/oats-review/injects/review.md +0 -69
- package/capabilities/oats-review/oats.json +0 -10
- package/capabilities/oats-review/skills/code-review/SKILL.md +0 -44
- package/capabilities/oats-review/skills/security-review/SKILL.md +0 -59
- package/docs/conventions.md +0 -90
- package/docs/design/2026-09-07-architecture-reassessment.md +0 -131
- package/docs/design/2026-09-07-desktop-souls-capabilities.md +0 -50
- package/docs/design/2026-09-07-mobile-agent-management-proposal.md +0 -228
- package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +0 -558
- package/docs/design/2026-09-13-knowledge-and-memory-direction.md +0 -744
- package/docs/design/2026-09-13-knowledge-implementation.md +0 -127
- package/docs/design/2026-09-13-knowledge-location-contract.md +0 -340
- package/docs/design/2026-09-14-artifact-retention-contract.md +0 -190
- package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +0 -708
- package/docs/design/2026-09-14-portable-souls-contract-amendments.md +0 -85
- package/docs/design/2026-09-14-portable-souls-explainer.md +0 -750
- package/docs/design/2026-09-15-captured-dispatch.md +0 -127
- package/docs/design/2026-09-15-captured-resolution-records.md +0 -143
- package/docs/design/2026-09-15-package-preparation.md +0 -100
- package/docs/design/2026-09-15-portable-data-contract.md +0 -121
- package/docs/design/2026-09-15-portable-declarations.md +0 -189
- package/docs/design/2026-09-15-portable-souls-handoff.md +0 -150
- package/docs/design/2026-09-15-portable-souls-implementation.md +0 -417
- package/docs/design/2026-09-15-selection-lock-and-approval.md +0 -122
- package/docs/design/2026-09-15-source-observation.md +0 -119
- package/docs/design/2026-09-16-captured-admission.md +0 -77
- package/docs/design/2026-09-16-captured-helper-dispatch.md +0 -105
- package/docs/design/2026-09-16-captured-launch-inputs.md +0 -42
- package/docs/design/2026-09-16-command-profile-preparation.md +0 -86
- package/docs/design/2026-09-16-fresh-install-first-rollout.md +0 -47
- package/docs/design/2026-09-16-fresh-operator-walkthrough.md +0 -282
- package/docs/design/2026-09-16-messaging-capability-contract.md +0 -59
- package/docs/design/2026-09-16-portable-migration-evidence.md +0 -158
- package/docs/design/2026-09-16-portable-onboarding.md +0 -179
- package/docs/design/2026-09-16-prepare-request-transport.md +0 -26
- package/docs/design/2026-09-16-provider-binding-codecs.md +0 -98
- package/docs/design/2026-09-16-provider-binding-wire.md +0 -274
- package/docs/design/2026-09-17-capability-helper-input-contract.md +0 -95
- package/docs/design/2026-09-17-captured-backend-parity.md +0 -53
- package/docs/design/2026-09-17-captured-native-start.md +0 -58
- package/docs/design/2026-09-17-portable-boundary-hookup.md +0 -19
- package/docs/design/2026-09-17-portable-boundary-resources.md +0 -52
- package/docs/design/2026-09-17-public-captured-start.md +0 -108
- package/docs/design/2026-09-17-public-prepare-request.md +0 -90
- package/docs/design/2026-09-18-captured-pi-host.md +0 -205
- package/docs/design/2026-09-18-first-cut-release-checklist.md +0 -131
- package/docs/design/2026-09-18-herdr-protocol-compatibility.md +0 -60
- package/docs/design/2026-09-20-redesign-program-board.md +0 -142
- package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +0 -289
- package/docs/design/2026-09-20-workspace-onboarding-public.md +0 -207
- package/docs/design/2026-09-22-desktop-parity-seams.md +0 -58
- package/docs/design/2026-09-23-simplified-workspace-model.md +0 -711
- package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +0 -65
- package/docs/design/2026-09-24-desktop-phase-f-boundary.md +0 -242
- package/docs/design/2026-09-24-phase-d-plan.md +0 -305
- package/docs/design/2026-09-25-teams-contract.md +0 -258
- package/docs/design/2026-09-26-desktop-design-brief-architecture.md +0 -241
- package/docs/design/desktop-ux-plan.md +0 -362
- package/docs/design/launch-configurations.md +0 -168
- package/docs/design/okf-mirror-provenance.md +0 -105
- package/docs/design/operations-contract.md +0 -141
- package/docs/oats-member.schema.json +0 -38
- package/skills/integration-authoring/SKILL.md +0 -84
- package/skills/oats-support/SKILL.md +0 -79
- package/skills/skill-craft/SKILL.md +0 -109
- package/skills/soul-craft/SKILL.md +0 -116
package/lib/workspace.mjs
CHANGED
|
@@ -17,6 +17,7 @@ import { oatsError } from "./errors.mjs";
|
|
|
17
17
|
import * as defaultRemote from "./remote.mjs";
|
|
18
18
|
import { bindRemote, classifyPackageValue, lockedPackageRef } from "./packages.mjs";
|
|
19
19
|
import { manifestContractProblems } from "./capability-contract.mjs";
|
|
20
|
+
import { BY_TEAM_REMOVED, TEAM_MEMBERSHIP_MOVED } from "./teams.mjs";
|
|
20
21
|
|
|
21
22
|
/* ───────────────────────────── errors ─────────────────────────────────── */
|
|
22
23
|
|
|
@@ -163,10 +164,10 @@ const ABSOLUTE_PATH = /^(?:\/|\\\\|[A-Za-z]:[\\/])/;
|
|
|
163
164
|
/** The workspace file's REF/PATH fields — the only values the absolute-path refusal applies to (contract §2,
|
|
164
165
|
* Phase B: "absolute paths" means bare filesystem paths as VALUES that stand for a repo or a path inside one;
|
|
165
166
|
* host state belongs in oats-local.yaml). Free text (`teams.*.description`) and the opaque provider payload
|
|
166
|
-
* (`messaging
|
|
167
|
+
* (`messaging`) are never scanned: a description may mention `/srv`, a provider may
|
|
167
168
|
* legitimately carry a socket path or a URL, and neither is a place the kernel resolves.
|
|
168
|
-
* → [[path, value]] for members[], packages.*, stores.*, external[].source|soul, defaults.<slot|capabilities>.*.from
|
|
169
|
-
|
|
169
|
+
* → [[path, value]] for members[], packages.*, stores.*, external[].source|soul, defaults.<slot|capabilities>.*.from
|
|
170
|
+
*/
|
|
170
171
|
function* refFields(value) {
|
|
171
172
|
if (Array.isArray(value.members)) for (let i = 0; i < value.members.length; i++) if (typeof value.members[i] === "string") yield [`/members/${i}`, value.members[i]];
|
|
172
173
|
if (isObject(value.packages)) for (const [id, v] of Object.entries(value.packages)) if (typeof v === "string") yield [`/packages/${pointerKey(id)}`, v];
|
|
@@ -180,7 +181,6 @@ function* refFields(value) {
|
|
|
180
181
|
const d = value.defaults;
|
|
181
182
|
const froms = function* (caps, path) { if (isObject(caps)) for (const [name, choice] of Object.entries(caps)) if (isObject(choice) && typeof choice.from === "string") yield [`${path}/${pointerKey(name)}/from`, choice.from]; };
|
|
182
183
|
for (const slot of ["knowledge", "messaging", "tasks", "capabilities"]) yield* froms(d[slot], `/defaults/${slot}`);
|
|
183
|
-
if (isObject(d.byTeam)) for (const [label, team] of Object.entries(d.byTeam)) yield* froms(team?.capabilities, `/defaults/byTeam/${pointerKey(label)}/capabilities`);
|
|
184
184
|
}
|
|
185
185
|
}
|
|
186
186
|
function refKey(remote, ref) {
|
|
@@ -211,7 +211,6 @@ export function validateWorkspace(value, { remote = defaultRemote } = {}) {
|
|
|
211
211
|
if (c.problem) { problems.push({ path: `/packages/${pointerKey(id)}`, message: `${c.problem} (a package is a bare version like v2.1.3 or git:<repo>@<ref>)` }); continue; }
|
|
212
212
|
if (c.kind === "git") keyOrProblem(remote, c.repo, `/packages/${pointerKey(id)}`, problems);
|
|
213
213
|
}
|
|
214
|
-
const teams = isObject(value.teams) ? Object.keys(value.teams) : [];
|
|
215
214
|
if (Array.isArray(value.members)) {
|
|
216
215
|
const seen = new Map();
|
|
217
216
|
value.members.forEach((ref, i) => {
|
|
@@ -223,15 +222,8 @@ export function validateWorkspace(value, { remote = defaultRemote } = {}) {
|
|
|
223
222
|
});
|
|
224
223
|
}
|
|
225
224
|
if (isObject(value.stores)) for (const [name, ref] of Object.entries(value.stores)) if (typeof ref === "string") keyOrProblem(remote, ref, `/stores/${pointerKey(name)}`, problems);
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
if (isObject(value.messaging) && value.messaging.byTeam !== undefined) {
|
|
229
|
-
if (!isObject(value.messaging.byTeam)) problems.push({ path: "/messaging/byTeam", message: "must be an object of { <team label>: <payload> }" });
|
|
230
|
-
else for (const label of Object.keys(value.messaging.byTeam)) {
|
|
231
|
-
if (!teams.includes(label)) problems.push({ path: `/messaging/byTeam/${pointerKey(label)}`, message: `team ${show(label)} is not declared in teams` });
|
|
232
|
-
if (!isObject(value.messaging.byTeam[label])) problems.push({ path: `/messaging/byTeam/${pointerKey(label)}`, message: "must be an object (the payload for that team)" });
|
|
233
|
-
else reservedKeyProblems(value.messaging.byTeam[label], `/messaging/byTeam/${pointerKey(label)}`, problems);
|
|
234
|
-
}
|
|
225
|
+
if (isObject(value.messaging) && Object.hasOwn(value.messaging, "byTeam")) {
|
|
226
|
+
problems.push({ path: "/messaging/byTeam", reason: "removed-key", message: "messaging.byTeam was removed in 0.30: a team's provider id is teams.<label>.team (committed, shared) or oats-local.yaml teams.<label>.team (local)" });
|
|
235
227
|
}
|
|
236
228
|
if (Array.isArray(value.external)) value.external.forEach((entry, i) => {
|
|
237
229
|
if (!isObject(entry)) return;
|
|
@@ -239,23 +231,35 @@ export function validateWorkspace(value, { remote = defaultRemote } = {}) {
|
|
|
239
231
|
const at = entry.source.lastIndexOf("@");
|
|
240
232
|
if (at > 0) keyOrProblem(remote, entry.source.slice(0, at), `/external/${i}/source`, problems);
|
|
241
233
|
}
|
|
242
|
-
if (typeof entry.team === "string" && !teams.includes(entry.team)) problems.push({ path: `/external/${i}/team`, message: `team ${show(entry.team)} is not declared in teams` });
|
|
243
234
|
});
|
|
244
235
|
if (isObject(value.defaults)) {
|
|
245
236
|
const d = value.defaults;
|
|
246
237
|
for (const slot of ["knowledge", "messaging", "tasks", "capabilities"]) fromProblems(remote, d[slot], `/defaults/${slot}`, problems, { here: false });
|
|
247
|
-
if (isObject(d.byTeam)) for (const label of Object.keys(d.byTeam)) {
|
|
248
|
-
if (!teams.includes(label)) problems.push({ path: `/defaults/byTeam/${pointerKey(label)}`, message: `team ${show(label)} is not declared in teams` });
|
|
249
|
-
fromProblems(remote, d.byTeam[label]?.capabilities, `/defaults/byTeam/${pointerKey(label)}/capabilities`, problems, { here: false });
|
|
250
|
-
}
|
|
251
238
|
}
|
|
252
|
-
return problems;
|
|
239
|
+
return withRemovedKeys(problems, value, REMOVED_KEYS.workspace);
|
|
240
|
+
}
|
|
241
|
+
export function validateMembership(value) { return withRemovedKeys(validateAgainst(schemaFor("membership"), value), value, REMOVED_KEYS.membership); }
|
|
242
|
+
|
|
243
|
+
/** Keys 0.30 removed (team model v2): each is a schema problem naming its replacement, in place of
|
|
244
|
+
* the schema's generic "unexpected property". `at(value)` → the JSON pointers where it appears. */
|
|
245
|
+
const REMOVED_KEYS = {
|
|
246
|
+
workspace: [
|
|
247
|
+
{ at: (v) => (isObject(v?.defaults) && Object.hasOwn(v.defaults, "byTeam") ? ["/defaults/byTeam"] : []), message: "defaults.byTeam was removed in 0.30: capabilities compose from defaults.capabilities and the soul only" },
|
|
248
|
+
{ at: (v) => (Array.isArray(v?.external) ? v.external.flatMap((e, i) => (isObject(e) && Object.hasOwn(e, "team") ? [`/external/${i}/team`] : [])) : []), message: TEAM_MEMBERSHIP_MOVED },
|
|
249
|
+
],
|
|
250
|
+
membership: [{ at: (v) => (isObject(v) && Object.hasOwn(v, "team") ? ["/team"] : []), message: TEAM_MEMBERSHIP_MOVED }],
|
|
251
|
+
soul: [{ at: (v) => (isObject(v) && Object.hasOwn(v, "team") ? ["/team"] : []), message: TEAM_MEMBERSHIP_MOVED }],
|
|
252
|
+
};
|
|
253
|
+
function withRemovedKeys(problems, value, removed) {
|
|
254
|
+
const found = removed.flatMap((r) => r.at(value).map((path) => ({ path, reason: "removed-key", message: r.message })));
|
|
255
|
+
if (!found.length) return problems;
|
|
256
|
+
const paths = new Set(found.map((p) => p.path));
|
|
257
|
+
return [...problems.filter((p) => !paths.has(p.path)), ...found];
|
|
253
258
|
}
|
|
254
|
-
export function validateMembership(value) { return validateAgainst(schemaFor("membership"), value); }
|
|
255
259
|
/** Schema + domain problems for a soul.yaml value: `from:` values must be canonical (see fromProblems);
|
|
256
|
-
* slot payloads may not carry the
|
|
260
|
+
* slot payloads may not carry the removed `byTeam` key (see reservedKeyProblems). */
|
|
257
261
|
export function validateSoul(value, { remote = defaultRemote } = {}) {
|
|
258
|
-
const problems = validateAgainst(schemaFor("soul"), value);
|
|
262
|
+
const problems = withRemovedKeys(validateAgainst(schemaFor("soul"), value), value, REMOVED_KEYS.soul);
|
|
259
263
|
if (isObject(value) && value.schemaVersion === 2) {
|
|
260
264
|
fromProblems(remote, value.capabilities, "/capabilities", problems, { here: true });
|
|
261
265
|
for (const slot of ["knowledge", "messaging", "tasks"]) if (isObject(value[slot])) reservedKeyProblems(value[slot], `/${slot}`, problems);
|
|
@@ -263,15 +267,14 @@ export function validateSoul(value, { remote = defaultRemote } = {}) {
|
|
|
263
267
|
return problems;
|
|
264
268
|
}
|
|
265
269
|
|
|
266
|
-
/** `byTeam`
|
|
267
|
-
*
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
|
|
271
|
-
const RESERVED_PAYLOAD_KEY = "byTeam";
|
|
270
|
+
/** `byTeam` was removed in 0.30 (team model v2): a per-team payload no longer exists — a team's
|
|
271
|
+
* provider id is teams.<label>.team. At the top level of any payload layer (a soul's slot payload,
|
|
272
|
+
* oats-local.yaml settings[cap], a spawn provider) it would reach the provider verbatim as a dead key;
|
|
273
|
+
* refused as a schema problem with `reason: "removed-key"`. Deeper keys are the provider's. */
|
|
274
|
+
const REMOVED_PAYLOAD_KEY = "byTeam";
|
|
272
275
|
function reservedKeyProblems(payload, path, problems) {
|
|
273
276
|
if (!isObject(payload)) return;
|
|
274
|
-
if (Object.hasOwn(payload,
|
|
277
|
+
if (Object.hasOwn(payload, REMOVED_PAYLOAD_KEY)) problems.push({ path: `${path}/${REMOVED_PAYLOAD_KEY}`, reason: "removed-key", message: BY_TEAM_REMOVED });
|
|
275
278
|
}
|
|
276
279
|
|
|
277
280
|
/** `from:` must be exactly what the resolver compares against — "package", "here" (souls only) or a CANONICAL
|
|
@@ -290,7 +293,7 @@ function fromProblems(remote, capabilities, path, problems, { here }) {
|
|
|
290
293
|
if (key !== from) problems.push({ path: at, message: `${show(from)} is not a canonical repo key (expected <host>/<path> as parseRepoRef(...).key${key ? `, e.g. ${show(key)}` : ""}); \`here\`, \`package\` or a key are the only forms` });
|
|
291
294
|
}
|
|
292
295
|
}
|
|
293
|
-
/** Schema + domain problems for an oats-local.yaml value: settings[<cap>] may not carry the
|
|
296
|
+
/** Schema + domain problems for an oats-local.yaml value: settings[<cap>] may not carry the removed `byTeam` key. */
|
|
294
297
|
export function validateLocal(value) {
|
|
295
298
|
const problems = validateAgainst(schemaFor("local"), value);
|
|
296
299
|
if (isObject(value) && isObject(value.settings)) for (const [cap, payload] of Object.entries(value.settings)) reservedKeyProblems(payload, `/settings/${pointerKey(cap)}`, problems);
|
|
@@ -345,7 +348,7 @@ function schemaError(kind, origin, problems, value) {
|
|
|
345
348
|
|
|
346
349
|
/** What a 0.25 `oats-config.yaml` held, and where each piece lives now (0.26.0 removed the file). */
|
|
347
350
|
export const LEGACY_CONFIG_MIGRATION = "oats-config.yaml is no longer read (removed in 0.26.0): launch-configs moved to the deployment's oats-local.yaml; "
|
|
348
|
-
+ "capability bindings
|
|
351
|
+
+ "capability bindings and layers moved to oats-workspace.yaml (defaults) and each soul's soul.yaml in its member repository, and team membership to the deployment's oats-local.yaml (`oats teams`, `oats soul teams`); "
|
|
349
352
|
+ "agents-md-injection, skill-overrides, work-modes and yolo are removed (yolo is --yolo or a launch configuration's yolo). Move what you still need, then delete the file";
|
|
350
353
|
|
|
351
354
|
/** Walk up from dir to find oats-local.yaml → { path, local } | E_LOCAL_MISSING.
|
|
@@ -392,52 +395,11 @@ export async function observeWorkspace(ref, { at, remote: injected, remoteOption
|
|
|
392
395
|
return { key: obs.key, url: obs.url, ref, commit: obs.commit, workspace: read.value, observedAt: obs.observedAt };
|
|
393
396
|
}
|
|
394
397
|
|
|
395
|
-
/**
|
|
396
|
-
* A soul's team labels as its repository declares them NOW, without a workspace discovery (teams
|
|
397
|
-
* contract decision 6, the live teams of an existing home): the workspace host has already been read
|
|
398
|
-
* (`wsObs`, observeWorkspace); this reads the soul's own repo once — a member at its current commit
|
|
399
|
-
* (`souls/<name>/soul.yaml`, and `oats-membership.yaml` only when the soul declares no `team`), or an
|
|
400
|
-
* `external[]` soul at its pinned commit (its entry's `team` wins, as in discovery).
|
|
401
|
-
* → labels (primary first), or null when the workspace no longer lists the soul's repo.
|
|
402
|
-
* Membership is not re-confirmed here: the home's soul was confirmed at spawn; this reads labels only.
|
|
403
|
-
*/
|
|
404
|
-
export async function observeSoulLabels(wsObs, { name, repoKey }, { remote: injected, remoteOptions } = {}) {
|
|
405
|
-
const remote = remoteOf({ remote: injected, remoteOptions });
|
|
406
|
-
const workspace = wsObs.workspace;
|
|
407
|
-
const readSoul = async (ref, commit, path) => {
|
|
408
|
-
const file = `${path.replace(/\/+$/, "")}/soul.yaml`;
|
|
409
|
-
const { bytes } = await remote.readRemoteFile(ref, commit, file);
|
|
410
|
-
const read = readDeclaration("soul", bytes, { kind: "soul", repoKey, commit, path: file });
|
|
411
|
-
if (read.problems) throw schemaError("soul", { kind: "soul", repoKey, commit, path: file }, read.problems, read.value);
|
|
412
|
-
return read.value;
|
|
413
|
-
};
|
|
414
|
-
for (const entry of workspace.external || []) {
|
|
415
|
-
const pin = entry.source.lastIndexOf("@");
|
|
416
|
-
let key; try { key = refKey(remote, entry.source.slice(0, pin)); } catch { continue; }
|
|
417
|
-
if (key !== repoKey) continue;
|
|
418
|
-
if (typeof entry.team === "string") return [entry.team];
|
|
419
|
-
const soul = await readSoul(entry.source.slice(0, pin), entry.source.slice(pin + 1), entry.soul);
|
|
420
|
-
if (soul.name !== name) continue;
|
|
421
|
-
return labelList(soul.team) ?? [];
|
|
422
|
-
}
|
|
423
|
-
const memberRef = (workspace.members || []).find((ref) => { try { return refKey(remote, ref) === repoKey; } catch { return false; } });
|
|
424
|
-
if (!memberRef) return null;
|
|
425
|
-
const obs = await remote.observeRemote(memberRef);
|
|
426
|
-
const soul = await readSoul(memberRef, obs.commit, `souls/${name}`);
|
|
427
|
-
const own = labelList(soul.team);
|
|
428
|
-
if (own) return own;
|
|
429
|
-
try {
|
|
430
|
-
const { bytes } = await remote.readRemoteFile(memberRef, obs.commit, "oats-membership.yaml");
|
|
431
|
-
const read = readDeclaration("membership", bytes, { kind: "membership", repoKey, commit: obs.commit, path: "oats-membership.yaml" });
|
|
432
|
-
return read.problems ? [] : labelList(read.value.team) ?? [];
|
|
433
|
-
} catch (e) { if (e?.code === "E_REMOTE_PATH_MISSING") return []; throw e; }
|
|
434
|
-
}
|
|
435
|
-
|
|
436
398
|
const unconfirmed = (key, reason, detail, extra = {}) => ({ key, confirmed: false, reason, detail, ...extra });
|
|
437
399
|
|
|
438
400
|
/**
|
|
439
401
|
* Read the member's oats-membership.yaml at its default branch in the same access context.
|
|
440
|
-
* → { key, commit, confirmed: true
|
|
402
|
+
* → { key, commit, confirmed: true } | { key, confirmed: false, reason, detail }
|
|
441
403
|
*/
|
|
442
404
|
export async function confirmMembership(workspaceObs, memberRef, { remote: injected, remoteOptions } = {}) {
|
|
443
405
|
const remote = remoteOf({ remote: injected, remoteOptions });
|
|
@@ -481,35 +443,24 @@ export async function confirmMembership(workspaceObs, memberRef, { remote: injec
|
|
|
481
443
|
const caseOnly = backlinkKey.toLowerCase() === workspaceObs.key.toLowerCase();
|
|
482
444
|
return unconfirmed(key, "backlink-elsewhere", `${key}@${obs.commit.slice(0, 12)} names workspace ${backlinkKey}, not ${workspaceObs.key}${caseOnly ? " (the keys differ only by letter case: repo paths are case-sensitive identities; spell the workspace ref exactly as the workspace lists itself)" : ""}`, { commit: obs.commit, backlink: backlinkKey, ...(caseOnly ? { caseOnly: true } : {}) });
|
|
483
445
|
}
|
|
484
|
-
|
|
485
|
-
return { key, commit: obs.commit, confirmed: true, team: labels[0] ?? null, labels };
|
|
446
|
+
return { key, commit: obs.commit, confirmed: true };
|
|
486
447
|
}
|
|
487
448
|
|
|
488
449
|
/* ───────────────────────────── enumeration ────────────────────────────── */
|
|
489
450
|
|
|
490
451
|
const SOUL_FILE = /^([^/]+)\/soul\.yaml$/;
|
|
491
452
|
const CAP_FILE = /^([^/]+)\/oats\.json$/;
|
|
492
|
-
/** A declared `team` as a label list (teams contract 2026-09-25: a label or a non-empty list of distinct
|
|
493
|
-
* labels, the first the primary); null when the item declares none. */
|
|
494
|
-
const labelList = (team) => (typeof team === "string" ? [team] : Array.isArray(team) ? team.filter((l) => typeof l === "string") : null);
|
|
495
|
-
/** The item's labels, else the repo default's (oats-membership.yaml). */
|
|
496
|
-
const labelsOf = (item, fallback) => labelList(item?.team) ?? [...(fallback || [])];
|
|
497
|
-
/** Each label with the path it is declared at: `/team` for one label, `/team/<i>` in a list. */
|
|
498
|
-
const labelPaths = (item, labels, file) => labels.map((label, i) => [label, Array.isArray(item?.team) ? `${file}#/team/${i}` : `${file}#/team`]);
|
|
499
453
|
|
|
500
454
|
/**
|
|
501
455
|
* Enumerate souls/*\/soul.yaml and capabilities/*\/oats.json of one repo at one commit.
|
|
502
456
|
* Validates each item; collects problems instead of aborting.
|
|
503
457
|
* → { souls: [SoulEntry], capabilities: [CapEntry], problems: [{ code, path, message, repoKey }] }
|
|
504
458
|
*/
|
|
505
|
-
async function enumerateRepo(remote, ref, key, commit
|
|
459
|
+
async function enumerateRepo(remote, ref, key, commit) {
|
|
506
460
|
const souls = [];
|
|
507
461
|
const capabilities = [];
|
|
508
462
|
const problems = [];
|
|
509
463
|
const problem = (code, path, message) => problems.push({ code, repoKey: key, path, message });
|
|
510
|
-
const checkTeam = (team, path) => {
|
|
511
|
-
if (team !== null && teams && !teams.includes(team)) problem("E_TEAM_UNKNOWN", path, `team ${show(team)} is not declared in the workspace's teams (${teams.join(", ") || "none"})`);
|
|
512
|
-
};
|
|
513
464
|
// A listing failure on one directory is a problem of THIS repo, never an abort of the whole picture.
|
|
514
465
|
const list = async (dir) => {
|
|
515
466
|
try { return await remote.listRemoteTree(ref, commit, dir, { depth: 2 }); }
|
|
@@ -533,10 +484,8 @@ async function enumerateRepo(remote, ref, key, commit, { defaultLabels = [], tea
|
|
|
533
484
|
if (definition.name !== m[1]) problem("E_WORKSPACE_SCHEMA", `${file}#/name`, `soul name ${show(definition.name)} does not match its directory ${show(m[1])}`);
|
|
534
485
|
if (soulNames.has(definition.name)) { problem("E_WORKSPACE_SCHEMA", `${file}#/name`, `soul name ${show(definition.name)} is already declared by ${soulNames.get(definition.name)}; the second declaration is not listed`); continue; }
|
|
535
486
|
soulNames.set(definition.name, file);
|
|
536
|
-
const labels = labelsOf(definition, defaultLabels);
|
|
537
|
-
for (const [label, at] of labelPaths(definition, labels, file)) checkTeam(label, at);
|
|
538
487
|
// Souls have no private mode since 0.26.0: `private` is accepted, warned (soul-private-ignored) and never hides a soul.
|
|
539
|
-
souls.push({ name: definition.name, path, repoKey: key, commit,
|
|
488
|
+
souls.push({ name: definition.name, path, repoKey: key, commit, private: false, definition });
|
|
540
489
|
}
|
|
541
490
|
const capNames = new Map();
|
|
542
491
|
for (const entry of await list("capabilities")) {
|
|
@@ -559,7 +508,6 @@ async function enumerateRepo(remote, ref, key, commit, { defaultLabels = [], tea
|
|
|
559
508
|
capability: { type: "string", pattern: "^[a-z0-9][a-z0-9._-]*$" },
|
|
560
509
|
version: { type: "string", minLength: 1 },
|
|
561
510
|
private: { type: "boolean" },
|
|
562
|
-
team: { type: "string", pattern: "^[a-z0-9][a-z0-9._-]*$" },
|
|
563
511
|
layer: { enum: ["knowledge", "messaging", "tasks"] },
|
|
564
512
|
},
|
|
565
513
|
}, manifest);
|
|
@@ -569,10 +517,7 @@ async function enumerateRepo(remote, ref, key, commit, { defaultLabels = [], tea
|
|
|
569
517
|
if (contract.length) { for (const p of contract) problem("E_WORKSPACE_SCHEMA", `${file}#${p.pointer}`, p.message); continue; }
|
|
570
518
|
if (capNames.has(manifest.capability)) { problem("E_WORKSPACE_SCHEMA", `${file}#/capability`, `capability ${show(manifest.capability)} is already declared by ${capNames.get(manifest.capability)}; the second declaration is not listed`); continue; }
|
|
571
519
|
capNames.set(manifest.capability, file);
|
|
572
|
-
|
|
573
|
-
const team = typeof manifest.team === "string" ? manifest.team : defaultLabels[0] ?? null;
|
|
574
|
-
checkTeam(team, `${file}#/team`);
|
|
575
|
-
capabilities.push({ name: manifest.capability, path, repoKey: key, commit, team, private: manifest.private === true, manifest });
|
|
520
|
+
capabilities.push({ name: manifest.capability, path, repoKey: key, commit, private: manifest.private === true, manifest });
|
|
576
521
|
}
|
|
577
522
|
// Non-collapse rule: a member's oats-package/ is REPORTED (publishes), never enumerated as member capabilities.
|
|
578
523
|
let publishes = null;
|
|
@@ -608,7 +553,7 @@ export async function discoverRepo(ref, { at, remote: injected, remoteOptions }
|
|
|
608
553
|
} catch (e) {
|
|
609
554
|
if (e?.code !== "E_REMOTE_PATH_MISSING") throw e;
|
|
610
555
|
}
|
|
611
|
-
const items = await enumerateRepo(remote, ref, obs.key, obs.commit
|
|
556
|
+
const items = await enumerateRepo(remote, ref, obs.key, obs.commit);
|
|
612
557
|
return { key: obs.key, commit: obs.commit, membership, ...items, problems: [...problems, ...items.problems] };
|
|
613
558
|
}
|
|
614
559
|
|
|
@@ -622,8 +567,8 @@ export function packageSoulAgentName(id, soul) { return `${String(id).replace(/[
|
|
|
622
567
|
/**
|
|
623
568
|
* Package souls (0.28.0): the souls the lock records for each package the workspace STILL declares,
|
|
624
569
|
* read at the locked commit. A package soul is listed like a member soul, with its package origin
|
|
625
|
-
* (`package`, `version`) and its qualified name `<package>/<soul
|
|
626
|
-
*
|
|
570
|
+
* (`package`, `version`) and its qualified name `<package>/<soul>`. A package soul.yaml still carrying
|
|
571
|
+
* `team:` (removed in 0.30: team membership is local) is invalid and not listed.
|
|
627
572
|
* Nothing here trusts the lock's digests — a spawn verifies the fetched soul against them.
|
|
628
573
|
* `lock` null (or no workspace) → nothing. → { souls: [SoulEntry], problems }
|
|
629
574
|
*/
|
|
@@ -632,7 +577,6 @@ export async function discoverPackageSouls(workspace, lock, { remote: injected,
|
|
|
632
577
|
if (!isObject(lock?.packages) || !isObject(workspace)) return { souls, problems };
|
|
633
578
|
const remote = remoteOf({ remote: injected, remoteOptions });
|
|
634
579
|
const declared = isObject(workspace.packages) ? workspace.packages : {};
|
|
635
|
-
const teams = isObject(workspace.teams) ? Object.keys(workspace.teams) : [];
|
|
636
580
|
for (const id of Object.keys(lock.packages).sort()) {
|
|
637
581
|
const entry = lock.packages[id];
|
|
638
582
|
if (!Array.isArray(entry?.souls) || entry.souls.length === 0 || !Object.hasOwn(declared, id)) continue;
|
|
@@ -656,12 +600,8 @@ export async function discoverPackageSouls(workspace, lock, { remote: injected,
|
|
|
656
600
|
if (read.problems) { for (const p of read.problems) problem("E_WORKSPACE_SCHEMA", `${file}#${p.path}`, `${p.message}${schemaHint("soul", read.value)}`); continue; }
|
|
657
601
|
const definition = read.value;
|
|
658
602
|
if (definition.name !== locked.name) { problem("E_WORKSPACE_SCHEMA", `${file}#/name`, `soul name ${show(definition.name)} does not match its directory ${show(locked.name)}`); continue; }
|
|
659
|
-
const labels = labelList(definition.team) ?? [];
|
|
660
|
-
for (const [label, where] of labelPaths(definition, labels, file)) {
|
|
661
|
-
if (!teams.includes(label)) problems.push({ code: "E_TEAM_UNKNOWN", repoKey: null, package: id, path: at(where), message: `team ${show(label)} is not declared in the workspace's teams (${teams.join(", ") || "none"}) — declare it in oats-workspace.yaml teams: to spawn ${id}/${locked.name}` });
|
|
662
|
-
}
|
|
663
603
|
souls.push({
|
|
664
|
-
name: locked.name, path, repoKey: key, ref, commit: entry.commit,
|
|
604
|
+
name: locked.name, path, repoKey: key, ref, commit: entry.commit, private: false, definition,
|
|
665
605
|
package: id, version: entry.version, qualifiedName: `${id}/${locked.name}`, agentName: packageSoulAgentName(id, locked.name), digest: locked.digest,
|
|
666
606
|
});
|
|
667
607
|
}
|
|
@@ -683,7 +623,7 @@ export async function discoverPackageSouls(workspace, lock, { remote: injected,
|
|
|
683
623
|
|
|
684
624
|
/**
|
|
685
625
|
* The whole picture in one access context.
|
|
686
|
-
* → { workspace, members: [{ key, commit, confirmed, reason?,
|
|
626
|
+
* → { workspace, members: [{ key, commit, confirmed, reason?, souls, capabilities, publishes }], external: [...],
|
|
687
627
|
* problems: [{ code, path, message, repoKey? }] }
|
|
688
628
|
* `publishes` = { package, version } when the member carries oats-package/oats-package.json (informational:
|
|
689
629
|
* the package's capabilities are NOT member capabilities — non-collapse rule), else null.
|
|
@@ -692,12 +632,11 @@ export async function discoverWorkspace(ref, { at, local, lock = null, remote: i
|
|
|
692
632
|
const remote = remoteOf({ remote: injected, remoteOptions });
|
|
693
633
|
const wsObs = await observeWorkspace(ref, { at, remote });
|
|
694
634
|
const workspace = wsObs.workspace;
|
|
695
|
-
const teams = isObject(workspace.teams) ? Object.keys(workspace.teams) : [];
|
|
696
635
|
const problems = [];
|
|
697
636
|
const members = [];
|
|
698
637
|
for (const memberRef of workspace.members || []) {
|
|
699
638
|
const confirmation = await confirmMembership(wsObs, memberRef, { remote });
|
|
700
|
-
const row = { key: confirmation.key, ref: memberRef, commit: confirmation.commit ?? null, confirmed: confirmation.confirmed,
|
|
639
|
+
const row = { key: confirmation.key, ref: memberRef, commit: confirmation.commit ?? null, confirmed: confirmation.confirmed, souls: [], capabilities: [], publishes: null };
|
|
701
640
|
if (!confirmation.confirmed) {
|
|
702
641
|
// Contract: an unconfirmed member contributes nothing but its row.
|
|
703
642
|
row.reason = confirmation.reason;
|
|
@@ -705,10 +644,7 @@ export async function discoverWorkspace(ref, { at, local, lock = null, remote: i
|
|
|
705
644
|
members.push(row);
|
|
706
645
|
continue;
|
|
707
646
|
}
|
|
708
|
-
|
|
709
|
-
if (!teams.includes(label)) problems.push({ code: "E_TEAM_UNKNOWN", repoKey: row.key, path: row.labels.length > 1 ? `oats-membership.yaml#/team/${i}` : "oats-membership.yaml#/team", message: `team ${show(label)} is not declared in the workspace's teams (${teams.join(", ") || "none"})` });
|
|
710
|
-
}
|
|
711
|
-
const items = await enumerateRepo(remote, memberRef, row.key, row.commit, { defaultLabels: row.labels, teams });
|
|
647
|
+
const items = await enumerateRepo(remote, memberRef, row.key, row.commit);
|
|
712
648
|
row.souls = items.souls;
|
|
713
649
|
row.capabilities = items.capabilities;
|
|
714
650
|
row.publishes = items.publishes;
|
|
@@ -735,47 +671,18 @@ export async function discoverWorkspace(ref, { at, local, lock = null, remote: i
|
|
|
735
671
|
}
|
|
736
672
|
const read = readDeclaration("soul", bytes, { kind: "soul", repoKey: key, commit, path: file });
|
|
737
673
|
if (read.problems) { for (const p of read.problems) pushProblem("E_WORKSPACE_SCHEMA", `${file}#${p.path}`, `${p.message}${schemaHint("soul", read.value)}`); continue; }
|
|
738
|
-
|
|
739
|
-
const labels = labelList(entry.team) ?? labelList(read.value.team) ?? [];
|
|
740
|
-
for (const label of labels) if (!teams.includes(label)) pushProblem("E_TEAM_UNKNOWN", `${file}#/team`, `team ${show(label)} is not declared in the workspace's teams`);
|
|
741
|
-
externalRows.push({ source: entry.source, key, commit, soul: { name: read.value.name, path: entry.soul, repoKey: key, commit, team: labels[0] ?? null, labels, private: false, definition: read.value } });
|
|
674
|
+
externalRows.push({ source: entry.source, key, commit, soul: { name: read.value.name, path: entry.soul, repoKey: key, commit, private: false, definition: read.value } });
|
|
742
675
|
}
|
|
743
676
|
const pkg = await discoverPackageSouls(workspace, lock, { remote });
|
|
744
677
|
problems.push(...pkg.problems);
|
|
745
|
-
return { workspace, key: wsObs.key, url: wsObs.url, commit: wsObs.commit, observedAt: wsObs.observedAt, local: local ?? null, members, external: externalRows, packageSouls: pkg.souls, problems, warnings: workspaceWarnings(workspace, members, externalRows, pkg.souls
|
|
746
|
-
}
|
|
747
|
-
|
|
748
|
-
/** The discovery's warnings: unmapped team labels and ignored `private:`, over every listed soul. */
|
|
749
|
-
export function workspaceWarnings(workspace, members, externalRows, packageSouls, teams = isObject(workspace?.teams) ? Object.keys(workspace.teams) : []) {
|
|
750
|
-
const extra = (packageSouls || []).map((soul) => ({ soul }));
|
|
751
|
-
return [...unmappedLabelWarnings(workspace, members, [...externalRows, ...extra], teams), ...privateSoulWarnings([...members.filter((m) => m.confirmed).flatMap((m) => m.souls), ...externalRows.map((e) => e.soul), ...(packageSouls || [])])];
|
|
678
|
+
return { workspace, key: wsObs.key, url: wsObs.url, commit: wsObs.commit, observedAt: wsObs.observedAt, local: local ?? null, members, external: externalRows, packageSouls: pkg.souls, problems, warnings: workspaceWarnings(workspace, members, externalRows, pkg.souls) };
|
|
752
679
|
}
|
|
753
680
|
|
|
754
|
-
/**
|
|
755
|
-
* `
|
|
756
|
-
* messaging provider falls back to the workspace's default team for it. (An undeclared label is E_TEAM_UNKNOWN.)
|
|
757
|
-
* ONE warning per unmapped label, naming its souls (a workspace on its default team alone would otherwise print
|
|
758
|
-
* a line per soul): { code, label, souls, paths, message }, sorted by label. */
|
|
681
|
+
/** The discovery's warnings: ignored `private:`, over every listed soul. (0.30 removed the
|
|
682
|
+
* `unmapped-team-label` warning: an unmapped shared team is the readiness item `team-unmapped`.) */
|
|
759
683
|
const byCodepointOrder = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
|
|
760
|
-
function
|
|
761
|
-
|
|
762
|
-
const byLabel = new Map();
|
|
763
|
-
const souls = [...members.filter((m) => m.confirmed).flatMap((m) => m.souls), ...externalRows.map((e) => e.soul)];
|
|
764
|
-
for (const soul of souls) {
|
|
765
|
-
for (const label of soul.labels || []) {
|
|
766
|
-
if (!teams.includes(label) || Object.hasOwn(byTeam, label)) continue;
|
|
767
|
-
const row = byLabel.get(label) || { souls: [], paths: [] };
|
|
768
|
-
row.souls.push(soul.name); row.paths.push(`${soul.repoKey}:${soul.path}/soul.yaml#/team`);
|
|
769
|
-
byLabel.set(label, row);
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
return [...byLabel.keys()].sort(byCodepointOrder).map((label) => {
|
|
773
|
-
const { souls: names, paths } = byLabel.get(label);
|
|
774
|
-
const order = names.map((n, i) => i).sort((x, y) => byCodepointOrder(names[x], names[y]) || byCodepointOrder(paths[x], paths[y]));
|
|
775
|
-
const sorted = order.map((i) => names[i]);
|
|
776
|
-
return { code: "unmapped-team-label", label, souls: sorted, paths: order.map((i) => paths[i]),
|
|
777
|
-
message: `team ${show(label)} has no messaging.byTeam entry; its souls (${sorted.join(", ")}) fall back to the workspace's default team for it` };
|
|
778
|
-
});
|
|
684
|
+
export function workspaceWarnings(workspace, members, externalRows, packageSouls) {
|
|
685
|
+
return privateSoulWarnings([...members.filter((m) => m.confirmed).flatMap((m) => m.souls), ...externalRows.map((e) => e.soul), ...(packageSouls || [])]);
|
|
779
686
|
}
|
|
780
687
|
|
|
781
688
|
/** `private:` in a soul.yaml has no effect since 0.26.0 (human decision 2026-09-25): every soul of a
|
|
@@ -837,7 +744,7 @@ export function standaloneRepo(ref, commit, discovery, { remote: injected } = {}
|
|
|
837
744
|
});
|
|
838
745
|
return {
|
|
839
746
|
standalone: true, key, commit: source.commit ?? commit ?? null, workspace: null,
|
|
840
|
-
members: [{ key, commit: source.commit ?? commit ?? null, confirmed: false, reason: "cannot-read", detail: `workspace of ${key} cannot be read; standalone view`,
|
|
747
|
+
members: [{ key, commit: source.commit ?? commit ?? null, confirmed: false, reason: "cannot-read", detail: `workspace of ${key} cannot be read; standalone view`, souls, capabilities, publishes: source.publishes ?? null }],
|
|
841
748
|
external: [], problems, warnings: privateSoulWarnings(souls),
|
|
842
749
|
};
|
|
843
750
|
}
|
package/package-catalog.json
CHANGED
|
@@ -3,12 +3,12 @@
|
|
|
3
3
|
"packages": {
|
|
4
4
|
"oats.okf": {
|
|
5
5
|
"url": "https://github.com/awebai/oats-okf.git",
|
|
6
|
-
"ref": "v4.0.
|
|
6
|
+
"ref": "v4.0.4",
|
|
7
7
|
"path": "oats-package"
|
|
8
8
|
},
|
|
9
9
|
"oats.aweb": {
|
|
10
10
|
"url": "https://github.com/awebai/oats-aweb.git",
|
|
11
|
-
"ref": "v1.
|
|
11
|
+
"ref": "v1.17.1",
|
|
12
12
|
"path": "oats-package"
|
|
13
13
|
},
|
|
14
14
|
"oats.jira": {
|
|
@@ -26,19 +26,18 @@
|
|
|
26
26
|
"ref": "v1.0.3",
|
|
27
27
|
"path": "oats-package"
|
|
28
28
|
},
|
|
29
|
-
"oats.
|
|
30
|
-
"url": "https://github.com/awebai/oats-
|
|
29
|
+
"oats.engineering": {
|
|
30
|
+
"url": "https://github.com/awebai/oats-engineering.git",
|
|
31
31
|
"ref": "v1.1.0",
|
|
32
32
|
"path": "oats-package"
|
|
33
33
|
},
|
|
34
34
|
"oats.framework": {
|
|
35
35
|
"url": "https://github.com/awebai/oats.git",
|
|
36
|
-
"ref": "oats-framework/v1.
|
|
36
|
+
"ref": "oats-framework/v1.4.0",
|
|
37
37
|
"path": "oats-package"
|
|
38
38
|
}
|
|
39
39
|
},
|
|
40
40
|
"capabilities": {
|
|
41
|
-
"oats.review": "oats.dev",
|
|
42
41
|
"oas.okf": {
|
|
43
42
|
"package": "oats.okf",
|
|
44
43
|
"capability": "oats.okf"
|
|
@@ -59,18 +58,13 @@
|
|
|
59
58
|
"package": "oats.authoring",
|
|
60
59
|
"capability": "oats.authoring"
|
|
61
60
|
},
|
|
62
|
-
"oas.dev": {
|
|
63
|
-
"package": "oats.dev",
|
|
64
|
-
"capability": "oats.dev"
|
|
65
|
-
},
|
|
66
|
-
"oas.review": {
|
|
67
|
-
"package": "oats.dev",
|
|
68
|
-
"capability": "oats.review"
|
|
69
|
-
},
|
|
70
61
|
"oats.knowledge-theory": "oats.framework",
|
|
71
62
|
"oats.core": "oats.framework",
|
|
72
63
|
"oats.setup": "oats.framework",
|
|
73
64
|
"oats.okf-harvest": "oats.okf",
|
|
74
|
-
"oats.okf-maintenance": "oats.okf"
|
|
65
|
+
"oats.okf-maintenance": "oats.okf",
|
|
66
|
+
"oats.engineering-expert": "oats.engineering",
|
|
67
|
+
"oats.developer": "oats.engineering",
|
|
68
|
+
"oats.code-review": "oats.engineering"
|
|
75
69
|
}
|
|
76
70
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@awebai/oats",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.30.0",
|
|
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",
|
|
@@ -60,10 +60,8 @@ members:
|
|
|
60
60
|
- git:github.com/acme/agents # the host is a member too
|
|
61
61
|
- git:github.com/acme/platform
|
|
62
62
|
packages:
|
|
63
|
-
oats.framework: v1.
|
|
64
|
-
oats.okf:
|
|
65
|
-
teams:
|
|
66
|
-
global: { description: Org-wide souls }
|
|
63
|
+
oats.framework: v1.4.0 # bare versions resolve through the official catalog
|
|
64
|
+
oats.okf: v4.0.4
|
|
67
65
|
defaults:
|
|
68
66
|
capabilities: { oats.core: { from: package } }
|
|
69
67
|
knowledge: { oats.okf: { from: package } }
|
|
@@ -72,7 +70,7 @@ defaults:
|
|
|
72
70
|
Take the current package versions from the official catalog
|
|
73
71
|
(`package-catalog.json` in the OATS repository); ask which slots the user wants
|
|
74
72
|
filled (knowledge, messaging, tasks) instead of copying the example. No absolute
|
|
75
|
-
paths
|
|
73
|
+
paths or accounts go in this file. Teams come with messaging (step 6).
|
|
76
74
|
|
|
77
75
|
Declaring a package in `packages:` is the decision to trust it: its commands
|
|
78
76
|
and hooks run on every machine that spawns a soul using it. Show the user what
|
|
@@ -84,7 +82,6 @@ In **every** member repository, including the host, `oats-membership.yaml`:
|
|
|
84
82
|
```yaml
|
|
85
83
|
schemaVersion: 2
|
|
86
84
|
workspace: git:github.com/acme/agents
|
|
87
|
-
team: global
|
|
88
85
|
```
|
|
89
86
|
|
|
90
87
|
Membership is reciprocal: the workspace lists the repository and the
|
|
@@ -135,20 +132,35 @@ each package to an exact commit and integrity, and content that no longer
|
|
|
135
132
|
matches is refused (`E_PACKAGE_INTEGRITY`). Member capabilities come from
|
|
136
133
|
membership.
|
|
137
134
|
|
|
138
|
-
## 6.
|
|
135
|
+
## 6. Give the deployment a team (with messaging)
|
|
136
|
+
|
|
137
|
+
If a messaging capability fills the messaging slot, every instance lives in a
|
|
138
|
+
team, and readiness fails with `E_TEAM_UNCONFIGURED` until this deployment has
|
|
139
|
+
a default team. Create the team with the messaging provider (its own skills say
|
|
140
|
+
how), then record it:
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
oats teams add research --team <provider team id> # a local team; the first one becomes the default
|
|
144
|
+
oats teams
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
A team the whole organisation uses is committed in `oats-workspace.yaml` as
|
|
148
|
+
`teams.<label>` with its provider id; which souls join which team on this
|
|
149
|
+
machine is `oats soul teams` (`/oats-teams`, in `oats.setup`).
|
|
150
|
+
|
|
151
|
+
## 7. Spawn the first soul
|
|
139
152
|
|
|
140
153
|
A soul with `work: worktree | checkout` needs a clone of its repository at
|
|
141
154
|
`<dir>/<repo name>` (or named in `oats-local.yaml` `clones:`).
|
|
142
155
|
|
|
143
156
|
```bash
|
|
144
|
-
oats souls --dir <dir> # what the workspace offers, with origin and
|
|
157
|
+
oats souls --dir <dir> # what the workspace offers, with origin and teams
|
|
145
158
|
oats spawn backend-expert --preview # modules, commits, merged provider settings — nothing created
|
|
146
159
|
oats spawn backend-expert --task "First concrete task"
|
|
147
160
|
oats status
|
|
148
161
|
```
|
|
149
162
|
|
|
150
|
-
Create and spawn only when asked. After the first spawn,
|
|
151
|
-
skills
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
`docs/workspaces.md` in the OATS repository.
|
|
163
|
+
Create and spawn only when asked. After the first spawn, the `oats.setup`
|
|
164
|
+
skills carry the rest of the deployment (messaging, more souls, rebuilds); the
|
|
165
|
+
`oats.authoring` package teaches writing capabilities, skills and souls. The
|
|
166
|
+
model in full is `docs/workspaces.md` in the OATS repository.
|
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
## Review discipline: oats.review
|
|
2
|
-
|
|
3
|
-
**After every substantive commit, launch the reviewer** — and, when a knowledge
|
|
4
|
-
layer is active, run its promotion step alongside (whatever command that layer
|
|
5
|
-
documents; omit the line entirely if you have no knowledge layer):
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
<your knowledge layer's promotion command> # omit when no knowledge layer is active
|
|
9
|
-
oats spawn reviewer --work attached --work-dir "$PWD/work" \
|
|
10
|
-
--purpose "<short-sha>" \
|
|
11
|
-
--task "Review commit <sha> on branch <branch>. Report to <your-instance> per your operating loop."
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
- `reviewer` is the oats.dev package's soul (`oats.dev/reviewer`).
|
|
15
|
-
`--purpose "<short-sha>"` gives it a unique, commit-relevant instance name
|
|
16
|
-
(`oats-dev-reviewer-<short-sha>`); attached mode shares your work tree
|
|
17
|
-
and automatically makes the reviewer your child (attached agents are always
|
|
18
|
-
children of the work-tree owner — no relation flags needed or allowed).
|
|
19
|
-
- The reviewer reviews **that commit's diff only** and reports its verdict
|
|
20
|
-
(`APPROVE` / `APPROVE WITH NITS` / `NEEDS CHANGES`) back to you the way your
|
|
21
|
-
deployment delivers messages: over your messaging layer when one is active,
|
|
22
|
-
and otherwise in its own session transcript, which is where you read it.
|
|
23
|
-
When a messaging layer IS active, do not wait actively — finish your turn and
|
|
24
|
-
go idle; the layer wakes you when the verdict arrives. `NEEDS CHANGES` means
|
|
25
|
-
fix, commit, and re-review before the work is ready.
|
|
26
|
-
- Do not review your own commits in its place — the point is eyes that are
|
|
27
|
-
not yours.
|
|
28
|
-
- Skip only for trivial mechanical commits (typo, lockfile refresh) — when
|
|
29
|
-
in doubt, review.
|
|
30
|
-
|
|
31
|
-
## Delivery discipline (all OATS developers)
|
|
32
|
-
|
|
33
|
-
- You work in a dedicated worktree on your own branch. **Main only moves
|
|
34
|
-
through PRs** — never push to main.
|
|
35
|
-
- **Single-developer features**: branch from main (`agents/<instance>` or as
|
|
36
|
-
tasked), open the PR yourself (`gh pr create`) when review-clean, then
|
|
37
|
-
**spawn a fresh maintainer instance for it** — one per PR, always, even if
|
|
38
|
-
another oats-expert instance is live:
|
|
39
|
-
|
|
40
|
-
```bash
|
|
41
|
-
oats spawn oats-expert --purpose "pr<n>" --relation parent --relative-to "$OATS_INSTANCE" \
|
|
42
|
-
--task "Maintainer review of PR #<n>: run your pr-review gates. You own this PR to its terminal outcome — on RETURN stay alive and idle for my fix notice, re-review, repeat; on merge/close record the delivery in your stewardship knowledge and retire yourself. Report verdicts to <your-instance>."
|
|
43
|
-
```
|
|
44
|
-
|
|
45
|
-
Go idle for the verdict. **The maintainer instance stays alive across
|
|
46
|
-
RETURN rounds** — when you push fixes, notify the SAME maintainer, continuing
|
|
47
|
-
the existing exchange rather than starting a new one; do not spawn another for
|
|
48
|
-
this PR. You never merge to main
|
|
49
|
-
yourself.
|
|
50
|
-
|
|
51
|
-
**Reviewers are the opposite: one per commit, then gone.** The post-commit
|
|
52
|
-
reviewer reviews its one diff, reports its verdict, and retires — it no
|
|
53
|
-
longer exists when you fix its findings. To re-review a fix, spawn a NEW
|
|
54
|
-
reviewer on the fix commit (`--purpose <new-short-sha>`); never message a
|
|
55
|
-
retired reviewer or expect it to follow up.
|
|
56
|
-
- **Multi-developer features**: the coordinator owns the feature branch
|
|
57
|
-
(`feature/<name>`) and the PR. Branch `<you>/<name>` **from the feature
|
|
58
|
-
branch**, push your branch, and tell the coordinator when it is ready —
|
|
59
|
-
the coordinator merges, validates, and reviews the integrated state. Never
|
|
60
|
-
merge into the feature branch yourself.
|
|
61
|
-
- **If you need another developer's unmerged code, ask the coordinator** —
|
|
62
|
-
never fetch or merge a peer's branch yourself. The coordinator
|
|
63
|
-
lands the dependency on the feature branch and tells you to merge the
|
|
64
|
-
feature branch into yours.
|
|
65
|
-
- Quality bar before handing off or opening a PR: the repo's full test/check
|
|
66
|
-
gate green; docs updated with behavior changes.
|
|
67
|
-
- While waiting on the reviewer, the coordinator, or a peer: **do not sleep,
|
|
68
|
-
poll, or busy-wait** — go idle. An active messaging layer wakes you when the
|
|
69
|
-
answer arrives; with none, read the reviewer's own session when it finishes.
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"capability": "oats.review",
|
|
3
|
-
"private": true,
|
|
4
|
-
"version": "1.3.0",
|
|
5
|
-
"compatibility": { "oats": ">=0.28.0" },
|
|
6
|
-
"description": "Post-commit review discipline: a fresh reviewer (this package's soul `reviewer`) reviews each new commit's diff, reports its verdict to its spawner over the deployment's messaging layer (or in its transcript when there is none), and retires — plus the shared developer delivery discipline and the reviewer's code-review and security-review skills.",
|
|
7
|
-
"requires": [],
|
|
8
|
-
"skills": ["skills/code-review", "skills/security-review"],
|
|
9
|
-
"inject": "injects/review.md"
|
|
10
|
-
}
|