@awebai/oats 0.29.3 → 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.
Files changed (232) hide show
  1. package/README.md +12 -6
  2. package/bin/oats.mjs +203 -54
  3. package/capabilities/oats-aweb/bin/oats-aweb.mjs +538 -204
  4. package/capabilities/oats-aweb/injects/aweb.md +1 -1
  5. package/capabilities/oats-aweb/lib/binding-wire.mjs +31 -22
  6. package/capabilities/oats-aweb/oats.json +5 -12
  7. package/capabilities/oats-aweb/skills/VENDORED.md +4 -4
  8. package/capabilities/oats-aweb/skills/aweb-team-membership/SKILL.md +1 -1
  9. package/capabilities/oats-aweb/skills/oats-aweb/SKILL.md +83 -13
  10. package/capabilities/oats-code-review/injects/reviewer.md +26 -0
  11. package/capabilities/oats-code-review/oats.json +16 -0
  12. package/capabilities/oats-code-review/skills/adversarial-review/SKILL.md +66 -0
  13. package/capabilities/oats-code-review/skills/review-dev-docs/SKILL.md +30 -0
  14. package/capabilities/oats-code-review/skills/security-review/SKILL.md +56 -0
  15. package/capabilities/oats-code-review/skills/simplification-review/SKILL.md +34 -0
  16. package/capabilities/oats-developer/injects/developer.md +38 -0
  17. package/capabilities/oats-developer/oats.json +17 -0
  18. package/capabilities/oats-developer/skills/execution-strategy/SKILL.md +43 -0
  19. package/capabilities/oats-developer/skills/maintain-dev-docs/SKILL.md +47 -0
  20. package/capabilities/oats-developer/skills/run-the-review-loop/SKILL.md +65 -0
  21. package/capabilities/oats-developer/skills/understand-the-spec/SKILL.md +37 -0
  22. package/capabilities/oats-developer/skills/worktrees/SKILL.md +36 -0
  23. package/capabilities/oats-engineering-expert/injects/expert.md +37 -0
  24. package/capabilities/oats-engineering-expert/oats.json +17 -0
  25. package/capabilities/oats-engineering-expert/skills/coordinate-developers/SKILL.md +37 -0
  26. package/capabilities/oats-engineering-expert/skills/coordinate-experts/SKILL.md +52 -0
  27. package/capabilities/oats-engineering-expert/skills/land-your-prs/SKILL.md +50 -0
  28. package/capabilities/oats-engineering-expert/skills/plan-and-spec/SKILL.md +53 -0
  29. package/capabilities/oats-engineering-expert/skills/verify-developer-work/SKILL.md +49 -0
  30. package/capabilities/oats-okf/bin/oats-okf.mjs +16 -9
  31. package/capabilities/oats-okf/lib/binding-wire.mjs +47 -15
  32. package/capabilities/oats-okf/lib/config.mjs +2 -1
  33. package/capabilities/oats-okf/lib/consult.mjs +26 -4
  34. package/capabilities/oats-okf/lib/harvest-switch.mjs +16 -3
  35. package/capabilities/oats-okf/lib/inspection.mjs +26 -7
  36. package/capabilities/oats-okf/lib/io.mjs +9 -1
  37. package/capabilities/oats-okf/lib/sources.mjs +34 -3
  38. package/capabilities/oats-okf/lib/stores.mjs +8 -6
  39. package/capabilities/oats-okf/lib/worker.mjs +7 -17
  40. package/capabilities/oats-okf/oats.json +6 -3
  41. package/capabilities/oats-okf-harvest/bin/okf-harvest.mjs +2 -2
  42. package/capabilities/oats-okf-harvest/oats.json +3 -3
  43. package/capabilities/oats-okf-harvest/skills/knowledge-harvest/SKILL.md +6 -6
  44. package/capabilities/oats-okf-maintenance/bin/okf-maintenance.mjs +37 -16
  45. package/capabilities/oats-okf-maintenance/injects/maintainer.md +1 -1
  46. package/capabilities/oats-okf-maintenance/lib/provenance.mjs +6 -1
  47. package/capabilities/oats-okf-maintenance/oats.json +2 -2
  48. package/capabilities/oats-okf-maintenance/skills/knowledge-review/SKILL.md +17 -2
  49. package/capabilities/oats-okf-maintenance/skills/okf-trigger-setup/SKILL.md +11 -23
  50. package/capabilities/oats-workspace-experts/injects/oats-experts.md +26 -0
  51. package/capabilities/oats-workspace-experts/oats.json +9 -0
  52. package/docs/capabilities.md +160 -171
  53. package/docs/capability-manifest.schema.json +7 -10
  54. package/docs/configuration.md +213 -64
  55. package/docs/design/2026-09-16-knowledge-capability-contract.md +36 -50
  56. package/docs/design/2026-09-23-workspace-module-contracts.md +377 -544
  57. package/docs/design/2026-09-26-okf-knowledge-operations.md +132 -357
  58. package/docs/design/2026-09-27-team-model-v2.md +116 -0
  59. package/docs/design/2026-09-28-automations-trust.md +38 -0
  60. package/docs/design/2026-09-28-soul-launch-preference.md +63 -0
  61. package/docs/design/HISTORY.md +65 -0
  62. package/docs/design/README.md +23 -54
  63. package/docs/desktop-cli-api.md +1787 -1777
  64. package/docs/desktop.md +30 -91
  65. package/docs/execution-targets.md +146 -292
  66. package/docs/first-team.md +31 -17
  67. package/docs/implementation.md +76 -288
  68. package/docs/integrations.md +118 -320
  69. package/docs/knowledge-capability-authoring.md +25 -52
  70. package/docs/knowledge-reference/acceptance.md +3 -3
  71. package/docs/knowledge-reference/adoption.md +1 -1
  72. package/docs/knowledge-reference/harvester.md +2 -2
  73. package/docs/knowledge-reference/package-craft.md +3 -3
  74. package/docs/knowledge-reference/provider-mapping.md +3 -6
  75. package/docs/knowledge-reference/reader-capture.md +3 -3
  76. package/docs/knowledge-theory.md +62 -166
  77. package/docs/knowledge.md +225 -404
  78. package/docs/layers.md +42 -97
  79. package/docs/oats-local.schema.json +58 -5
  80. package/docs/oats-membership.schema.json +1 -8
  81. package/docs/oats-package.schema.json +5 -5
  82. package/docs/oats-workspace.schema.json +8 -22
  83. package/docs/official-catalog.md +25 -28
  84. package/docs/packages.md +45 -63
  85. package/docs/plans/0.30-close-out.md +61 -0
  86. package/docs/release-lane.md +77 -0
  87. package/docs/release-notes/oats-framework-v1.1.3.md +10 -8
  88. package/docs/release-notes/v0.19.0.md +48 -147
  89. package/docs/release-notes/v0.19.1.md +2 -3
  90. package/docs/release-notes/v0.19.3.md +2 -15
  91. package/docs/release-notes/v0.20.0.md +0 -15
  92. package/docs/release-notes/v0.22.0.md +71 -138
  93. package/docs/release-notes/v0.22.1.md +42 -90
  94. package/docs/release-notes/v0.22.10.md +1 -1
  95. package/docs/release-notes/v0.22.11.md +1 -47
  96. package/docs/release-notes/v0.22.12.md +4 -13
  97. package/docs/release-notes/v0.22.13.md +1 -42
  98. package/docs/release-notes/v0.22.14.md +3 -11
  99. package/docs/release-notes/v0.22.15.md +1 -46
  100. package/docs/release-notes/v0.22.16.md +6 -8
  101. package/docs/release-notes/v0.22.18.md +1 -99
  102. package/docs/release-notes/v0.22.19.md +3 -14
  103. package/docs/release-notes/v0.22.2.md +6 -15
  104. package/docs/release-notes/v0.22.3.md +0 -1
  105. package/docs/release-notes/v0.22.4.md +1 -14
  106. package/docs/release-notes/v0.22.5.md +2 -12
  107. package/docs/release-notes/v0.22.6.md +0 -3
  108. package/docs/release-notes/v0.23.0.md +9 -25
  109. package/docs/release-notes/v0.23.1.md +9 -25
  110. package/docs/release-notes/v0.23.2.md +2 -4
  111. package/docs/release-notes/v0.24.0.md +56 -97
  112. package/docs/release-notes/v0.24.1.md +7 -11
  113. package/docs/release-notes/v0.24.10.md +34 -45
  114. package/docs/release-notes/v0.24.11.md +12 -20
  115. package/docs/release-notes/v0.24.12.md +35 -48
  116. package/docs/release-notes/v0.24.13.md +34 -41
  117. package/docs/release-notes/v0.24.2.md +9 -13
  118. package/docs/release-notes/v0.24.3.md +7 -11
  119. package/docs/release-notes/v0.24.4.md +6 -6
  120. package/docs/release-notes/v0.24.5.md +6 -10
  121. package/docs/release-notes/v0.24.6.md +2 -5
  122. package/docs/release-notes/v0.24.7.md +46 -75
  123. package/docs/release-notes/v0.24.8.md +58 -96
  124. package/docs/release-notes/v0.24.9.md +38 -54
  125. package/docs/release-notes/v0.25.0.md +59 -76
  126. package/docs/release-notes/v0.25.1.md +57 -81
  127. package/docs/release-notes/v0.25.2.md +51 -70
  128. package/docs/release-notes/v0.25.3.md +11 -13
  129. package/docs/release-notes/v0.25.4.md +9 -13
  130. package/docs/release-notes/v0.25.5.md +3 -5
  131. package/docs/release-notes/v0.25.6.md +20 -29
  132. package/docs/release-notes/v0.25.7.md +5 -7
  133. package/docs/release-notes/v0.25.8.md +26 -39
  134. package/docs/release-notes/v0.26.0.md +175 -646
  135. package/docs/release-notes/v0.27.0.md +4 -5
  136. package/docs/release-notes/v0.27.1.md +4 -6
  137. package/docs/release-notes/v0.27.2.md +1 -1
  138. package/docs/release-notes/v0.28.0.md +57 -124
  139. package/docs/release-notes/v0.29.0.md +89 -208
  140. package/docs/release-notes/v0.29.1.md +1 -1
  141. package/docs/release-notes/v0.29.2.md +3 -4
  142. package/docs/release-notes/v0.29.4.md +90 -0
  143. package/docs/release-notes/v0.30.0.md +205 -0
  144. package/docs/schedules.md +280 -349
  145. package/docs/servers.md +99 -117
  146. package/docs/soul.schema.json +2 -9
  147. package/docs/souls-and-instances.md +145 -158
  148. package/docs/workspaces.md +132 -215
  149. package/lib/automations.mjs +28 -6
  150. package/lib/core.mjs +226 -74
  151. package/lib/instance-events.mjs +1 -1
  152. package/lib/instance-inspect.mjs +109 -34
  153. package/lib/instance-lifecycle.mjs +14 -1
  154. package/lib/instance-resolution.mjs +26 -27
  155. package/lib/launch-preference.mjs +87 -0
  156. package/lib/materialize.mjs +3 -3
  157. package/lib/packages.mjs +2 -5
  158. package/lib/resolve.mjs +29 -87
  159. package/lib/schedule.mjs +32 -18
  160. package/lib/teams-verbs.mjs +195 -0
  161. package/lib/teams.mjs +190 -0
  162. package/lib/triggers.mjs +53 -17
  163. package/lib/workspace.mjs +54 -147
  164. package/package-catalog.json +9 -15
  165. package/package.json +1 -1
  166. package/skills/oats-getting-started/SKILL.md +25 -13
  167. package/capabilities/oats-review/injects/review.md +0 -69
  168. package/capabilities/oats-review/oats.json +0 -10
  169. package/capabilities/oats-review/skills/code-review/SKILL.md +0 -44
  170. package/capabilities/oats-review/skills/security-review/SKILL.md +0 -59
  171. package/docs/conventions.md +0 -90
  172. package/docs/design/2026-09-07-architecture-reassessment.md +0 -131
  173. package/docs/design/2026-09-07-desktop-souls-capabilities.md +0 -50
  174. package/docs/design/2026-09-07-mobile-agent-management-proposal.md +0 -228
  175. package/docs/design/2026-09-08-expert-assisted-deployment-proposal.md +0 -558
  176. package/docs/design/2026-09-13-knowledge-and-memory-direction.md +0 -744
  177. package/docs/design/2026-09-13-knowledge-implementation.md +0 -127
  178. package/docs/design/2026-09-13-knowledge-location-contract.md +0 -340
  179. package/docs/design/2026-09-14-artifact-retention-contract.md +0 -190
  180. package/docs/design/2026-09-14-portable-souls-and-git-workspaces.md +0 -708
  181. package/docs/design/2026-09-14-portable-souls-contract-amendments.md +0 -85
  182. package/docs/design/2026-09-14-portable-souls-explainer.md +0 -750
  183. package/docs/design/2026-09-15-captured-dispatch.md +0 -127
  184. package/docs/design/2026-09-15-captured-resolution-records.md +0 -143
  185. package/docs/design/2026-09-15-package-preparation.md +0 -100
  186. package/docs/design/2026-09-15-portable-data-contract.md +0 -121
  187. package/docs/design/2026-09-15-portable-declarations.md +0 -189
  188. package/docs/design/2026-09-15-portable-souls-handoff.md +0 -150
  189. package/docs/design/2026-09-15-portable-souls-implementation.md +0 -417
  190. package/docs/design/2026-09-15-selection-lock-and-approval.md +0 -122
  191. package/docs/design/2026-09-15-source-observation.md +0 -119
  192. package/docs/design/2026-09-16-captured-admission.md +0 -77
  193. package/docs/design/2026-09-16-captured-helper-dispatch.md +0 -105
  194. package/docs/design/2026-09-16-captured-launch-inputs.md +0 -42
  195. package/docs/design/2026-09-16-command-profile-preparation.md +0 -86
  196. package/docs/design/2026-09-16-fresh-install-first-rollout.md +0 -47
  197. package/docs/design/2026-09-16-fresh-operator-walkthrough.md +0 -282
  198. package/docs/design/2026-09-16-messaging-capability-contract.md +0 -59
  199. package/docs/design/2026-09-16-portable-migration-evidence.md +0 -158
  200. package/docs/design/2026-09-16-portable-onboarding.md +0 -179
  201. package/docs/design/2026-09-16-prepare-request-transport.md +0 -26
  202. package/docs/design/2026-09-16-provider-binding-codecs.md +0 -98
  203. package/docs/design/2026-09-16-provider-binding-wire.md +0 -274
  204. package/docs/design/2026-09-17-capability-helper-input-contract.md +0 -95
  205. package/docs/design/2026-09-17-captured-backend-parity.md +0 -53
  206. package/docs/design/2026-09-17-captured-native-start.md +0 -58
  207. package/docs/design/2026-09-17-portable-boundary-hookup.md +0 -19
  208. package/docs/design/2026-09-17-portable-boundary-resources.md +0 -52
  209. package/docs/design/2026-09-17-public-captured-start.md +0 -108
  210. package/docs/design/2026-09-17-public-prepare-request.md +0 -90
  211. package/docs/design/2026-09-18-captured-pi-host.md +0 -205
  212. package/docs/design/2026-09-18-first-cut-release-checklist.md +0 -131
  213. package/docs/design/2026-09-18-herdr-protocol-compatibility.md +0 -60
  214. package/docs/design/2026-09-20-redesign-program-board.md +0 -142
  215. package/docs/design/2026-09-20-workspace-and-portable-adoption-plan.md +0 -289
  216. package/docs/design/2026-09-20-workspace-onboarding-public.md +0 -207
  217. package/docs/design/2026-09-22-desktop-parity-seams.md +0 -58
  218. package/docs/design/2026-09-23-simplified-workspace-model.md +0 -711
  219. package/docs/design/2026-09-23-workspace-v2-implementation-plan.md +0 -65
  220. package/docs/design/2026-09-24-desktop-phase-f-boundary.md +0 -242
  221. package/docs/design/2026-09-24-phase-d-plan.md +0 -305
  222. package/docs/design/2026-09-25-teams-contract.md +0 -258
  223. package/docs/design/2026-09-26-desktop-design-brief-architecture.md +0 -241
  224. package/docs/design/desktop-ux-plan.md +0 -362
  225. package/docs/design/launch-configurations.md +0 -168
  226. package/docs/design/okf-mirror-provenance.md +0 -105
  227. package/docs/design/operations-contract.md +0 -141
  228. package/docs/oats-member.schema.json +0 -38
  229. package/skills/integration-authoring/SKILL.md +0 -84
  230. package/skills/oats-support/SKILL.md +0 -79
  231. package/skills/skill-craft/SKILL.md +0 -109
  232. package/skills/soul-craft/SKILL.md +0 -116
@@ -5,7 +5,7 @@ and it lives in the workspace's default team (the `Comms:` line of your TASK.md
5
5
  names it). Wider workspace teams are joined explicitly, each with its own
6
6
  identity home.
7
7
 
8
- **Load the `oats-aweb` skill before your first `aw mail`/`aw chat` of a session,
8
+ **Run /oats-aweb before your first `aw mail`/`aw chat` of a session,
9
9
  whenever an aweb wake or channel event arrives, and whenever messaging or a
10
10
  team command looks wrong.** It covers your teams, the roster, sending and
11
11
  replying, how wakes work, etiquette and troubleshooting. Do not work from
@@ -1,6 +1,6 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import { existsSync, readdirSync, readFileSync, statSync } from 'node:fs';
3
- import { isAbsolute, join, resolve } from 'node:path';
3
+ import { delimiter, isAbsolute, join, resolve } from 'node:path';
4
4
  import { TextDecoder } from 'node:util';
5
5
  import { assessCapturedSessionReadiness } from './session-readiness.mjs';
6
6
  import { custodyPreflight } from './grant-custody.mjs';
@@ -19,8 +19,10 @@ const CAPABILITY='oats.aweb',SLOT='messaging';
19
19
  const phases=new Set(['normalize','bind','check']);
20
20
  const declarationKinds=new Set(['soul','workspace','adoption','operator']);
21
21
  const errorCodes=new Set(['needs-configuration','requirement-conflict','invalid-binding','authorization-required','host-requirement-missing','provider-unavailable','provider-not-qualified']);
22
+ const TEAM_SETTING_MESSAGE='teams are not a setting since oats.aweb 1.17 / OATS 0.30: use oats teams / oats soul teams';
23
+ const AWEB_TEAM_ID_MESSAGE='aweb team ids must have shape <name>:<namespace> (name matches ^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$; namespace is a hostname)';
22
24
  const obj=value=>value!==null && typeof value==='object' && !Array.isArray(value);
23
- const wireError=code=>{throw Object.assign(new Error(code),{wireCode:code});};
25
+ const wireError=(code,message=code)=>{throw Object.assign(new Error(message),{wireCode:code});};
24
26
  const canonical=value=>value===null || typeof value!=='object'?JSON.stringify(value):Array.isArray(value)?`[${value.map(canonical).join(',')}]`:`{${Object.keys(value).sort().map(key=>`${JSON.stringify(key)}:${canonical(value[key])}`).join(',')}}`;
25
27
  const same=(a,b)=>canonical(a)===canonical(b);
26
28
  function keys(value,allowed,required) {
@@ -83,10 +85,10 @@ export function parseBindingJson(bytes,limits=BINDING_WIRE_LIMITS) {
83
85
  }
84
86
  function settings(value,{phase}={}) {
85
87
  if(!obj(value)) wireError('invalid-binding');
88
+ if(Object.hasOwn(value,'team')) wireError('needs-configuration',TEAM_SETTING_MESSAGE);
86
89
  if(phase!=='check' && Object.hasOwn(value,'identity')) wireError('provider-not-qualified');
87
- keys(value,phase==='check'?['delivery','team','root','roots','identity','residents','join']:['delivery','team','root','roots','join'],[]);
90
+ keys(value,phase==='check'?['delivery','root','roots','identity','residents','join']:['delivery','root','roots','join'],[]);
88
91
  if(value.delivery!==undefined && !['channel','session'].includes(value.delivery)) wireError('needs-configuration');
89
- if(value.team!==undefined && (typeof value.team!=='string' || !value.team.trim())) wireError('needs-configuration');
90
92
  if(value.root!==undefined && (typeof value.root!=='string' || !value.root.trim())) wireError('needs-configuration');
91
93
  if(value.join!==undefined && typeof value.join!=='string') wireError('needs-configuration');
92
94
  if(value.roots!==undefined && !obj(value.roots)) wireError('needs-configuration');
@@ -165,10 +167,9 @@ function workspaceReadinessContext(value) {
165
167
  return value;
166
168
  }
167
169
  function yamlScalar(text,key){const m=String(text).match(new RegExp(`^${key}:\\s*["']?([^"'\\n#]+)["']?\\s*$`,'m'));return m?m[1].trim():undefined;}
168
- export const CUSTODY_ATTACH_MIN = '1.36.3';
169
- export const WAKE_STREAM_MIN = '1.36.5';
170
+ export const AW_MIN = '1.36.13';
170
171
  const CLASSIC_REFUSAL = 'oats.aweb 1.14 needs OATS 0.26.0 or newer (workspace model); on an older kernel pin oats.aweb v1.13.x';
171
- function classicEnv(env=process.env) {return !!env.OATS_TEAM_SCOPE && !(env.OATS_WORKSPACE_KEY || env.OATS_WORKSPACE_NAME || env.OATS_TEAM_LABEL);}
172
+ function classicEnv(env=process.env) {return !!env.OATS_TEAM_SCOPE && !(env.OATS_WORKSPACE_KEY || env.OATS_WORKSPACE_NAME || env.OATS_DEFAULT_TEAM);}
172
173
  export function grantYamlCustodySocket(text) {
173
174
  const lines=String(text??'').split(/\r?\n/);let inCustody=false,baseIndent=0;
174
175
  for(const line of lines) {
@@ -192,15 +193,12 @@ function grantAttachmentProblem(home) {
192
193
  let text;try{text=readFileSync(grantYaml,'utf8');}catch{return null;}
193
194
  if(grantYamlCustodySocket(text)) return null;
194
195
  const id=yamlScalar(text,'grant_id')||'<unknown>';
195
- return {code:'custody',message:`grant ${id} is not attached to custody; retire and respawn on aw >= ${CUSTODY_ATTACH_MIN}`};
196
+ return {code:'custody',message:`grant ${id} is not attached to custody; retire and respawn on aw >= ${AW_MIN}`};
196
197
  }
197
198
  function activeTeamAt(root){try{const text=readFileSync(join(resolve(root),'.aw','teams.yaml'),'utf8');return yamlScalar(text,'active_team')||yamlScalar(text,'active');}catch{return undefined;}}
198
199
  function residentCustodyRoot(settings){const identity=obj(settings.identity)?settings.identity:{},residents=obj(settings.residents)?settings.residents:{};const name=typeof identity.resident==='string'?identity.resident:'';const root=name&&typeof residents[name]==='string'?residents[name]:undefined;return identity.mode==='global'&&root&&isAbsolute(root)?root:undefined;}
199
200
  function teamFromSettings(settings,candidate,{env=process.env}={}) {
200
- const configured=typeof settings.team==='string' && settings.team.trim()?settings.team.trim():undefined;
201
- if(configured) return configured;
202
- const custody=residentCustodyRoot(settings);if(custody)return activeTeamAt(custody);
203
- return candidate?.root && isAbsolute(candidate.root) ? activeTeamAt(candidate.root) : undefined;
201
+ return typeof env.OATS_DEFAULT_TEAM_ID==='string' && env.OATS_DEFAULT_TEAM_ID.trim()?env.OATS_DEFAULT_TEAM_ID.trim():undefined;
204
202
  }
205
203
  function rootCandidate(settings,team,{deployment,env=process.env}={}) {
206
204
  const roots=obj(settings.roots)?settings.roots:{};
@@ -210,17 +208,23 @@ function rootCandidate(settings,team,{deployment,env=process.env}={}) {
210
208
  for(const root of candidates) if(isAbsolute(root) && existsSync(join(resolve(root),'.aw'))) return {root,key:'settings.oats.aweb.root',declared:false};
211
209
  return {root:candidates[0] || process.cwd(),key:'settings.oats.aweb.root',declared:false};
212
210
  }
213
- function parseOatsTeams(env=process.env){try{const rows=JSON.parse(env.OATS_TEAMS||'[]');return Array.isArray(rows)?rows.filter(r=>r&&typeof r==='object').map(r=>({label:String(r.label||''),team:typeof r.team==='string'?r.team:null,mapped:r.mapped===true,payload:obj(r.payload)?r.payload:{}})):[];}catch{return [];}}
214
- function primaryTeamLabel(env=process.env){return env.OATS_TEAM_LABEL || String(env.OATS_TEAM_LABELS||'').split(',').map(s=>s.trim()).filter(Boolean)[0] || null;}
215
- function unmappedPrimary(env=process.env){const primary=primaryTeamLabel(env);return primary?parseOatsTeams(env).find(t=>t.label===primary&&!t.mapped):undefined;}
211
+ function validHostname(value){const s=String(value||'');return s.length<=253&&s.split('.').every(label=>/^[A-Za-z0-9](?:[A-Za-z0-9-]{0,61}[A-Za-z0-9])?$/.test(label));}
212
+ function validAwebTeamId(value){const m=/^([A-Za-z0-9][A-Za-z0-9._-]{0,127}):([^:]+)$/.exec(String(value||''));return !!m&&validHostname(m[2]);}
213
+ function invalidAwebTeamId(env=process.env){const id=typeof env.OATS_DEFAULT_TEAM_ID==='string'&&env.OATS_DEFAULT_TEAM_ID.trim()?env.OATS_DEFAULT_TEAM_ID.trim():null;if(id&&!validAwebTeamId(id))return id;try{const rows=JSON.parse(env.OATS_TEAMS||'[]');if(Array.isArray(rows))for(const r of rows)if(r&&typeof r.team==='string'&&r.team.trim()&&!validAwebTeamId(r.team.trim()))return r.team.trim();}catch{}return null;}
214
+ function parseOatsTeams(env=process.env){try{const rows=JSON.parse(env.OATS_TEAMS||'[]');return Array.isArray(rows)?rows.filter(r=>r&&typeof r==='object').map(r=>({label:String(r.label||''),team:typeof r.team==='string'?r.team:null,default:r.default===true,from:r.from==='shared'?'shared':'local'})):[];}catch{return [];}}
215
+ function primaryTeamLabel(env=process.env){return typeof env.OATS_DEFAULT_TEAM==='string'&&env.OATS_DEFAULT_TEAM.trim()?env.OATS_DEFAULT_TEAM.trim():null;}
216
+ function unmappedPrimary(env=process.env){return undefined;}
216
217
  function joinedTeams(home){if(!home)return[];try{const doc=JSON.parse(readFileSync(join(home,'.oats-aweb','teams.json'),'utf8'));return Array.isArray(doc.joinedTeams)?doc.joinedTeams.filter(j=>j&&typeof j==='object'&&j.label&&j.team&&j.identityHome):[];}catch{return[];}}
217
218
  function readinessDetails(settings,{deployment,env=process.env}={}) {
218
219
  if(classicEnv(env)) return {team:undefined,candidate:null,warnings:[],result:{status:'needs-configuration',problems:[{code:'needs-configuration',message:CLASSIC_REFUSAL}]}};
219
- const initialTeam=typeof settings.team==='string' && settings.team.trim()?settings.team.trim():undefined;
220
- const candidate=rootCandidate(settings,initialTeam,{deployment,env}),team=teamFromSettings(settings,candidate,{env}),problems=[],warnings=[];
220
+ const invalidTeam=invalidAwebTeamId(env);if(invalidTeam)return {team:undefined,candidate:null,warnings:[],result:{status:'needs-configuration',problems:[{code:'needs-configuration',message:AWEB_TEAM_ID_MESSAGE}]}};
221
+ const team=teamFromSettings(settings,null,{env}),candidate=rootCandidate(settings,team,{deployment,env}),problems=[],warnings=[];
222
+ const awProblem=awFloorProblem();if(awProblem) problems.push(awProblem);
223
+ if(!team) {
224
+ const label=typeof env.OATS_DEFAULT_TEAM==='string'&&env.OATS_DEFAULT_TEAM.trim()?env.OATS_DEFAULT_TEAM.trim():'';
225
+ problems.push({code:'needs-configuration',message:label?`the default team ${label} has no provider id yet: its owner runs oats aweb setup, then commits the id, or choose another default with oats teams default`:'no teams configured: run `oats aweb setup`'});
226
+ }
221
227
  if(!candidate.root || !isAbsolute(candidate.root) || !existsSync(join(resolve(candidate.root),'.aw'))) problems.push({code:'needs-configuration',message:`no messaging root at ${candidate.root?resolve(candidate.root):process.cwd()}: run oats aweb setup there or set ${candidate.key}`});
222
- const unmapped=unmappedPrimary(env);if(unmapped&&team)warnings.push({code:'team-unmapped',message:`workspace label ${unmapped.label} is not mapped; using the default team ${team}`});
223
- if(!team) problems.push({code:'needs-configuration',message:'no team: set settings.oats.aweb.team or keep an active team at the aweb root'});
224
228
  return {team,candidate,warnings,result:checkProblems(problems) || {status:'ready',problems:[]}};
225
229
  }
226
230
  function readinessFromSettings(settings,options) {return readinessDetails(settings,options).result;}
@@ -231,6 +235,9 @@ function runAw(argv,cwd,{unsetEnv=[],timeout=60000}={}) {
231
235
  catch(e) {throw new Error(`${argv.slice(0,3).join(' ')} failed${e.status===undefined?'':` (exit ${e.status})`}`);}
232
236
  }
233
237
  function semverLt(a,b) {const A=String(a||'0.0.0').split('.').map(n=>Number(n)||0),B=String(b).split('.').map(n=>Number(n)||0);for(let i=0;i<3;i++){if((A[i]||0)!==(B[i]||0)) return (A[i]||0)<(B[i]||0);}return false;}
238
+ function onPath(cmd,env=process.env){for(const dir of String(env.PATH||'').split(delimiter)){if(!dir)continue;try{const st=statSync(join(dir,cmd));if(st.isFile()&&(st.mode&0o111))return true;}catch{}}return false;}
239
+ function awVersionLabel(){try{const text=runAw(['aw','version'],process.cwd(),{timeout:10000});const m=/aw\s+v?(\d+\.\d+\.\d+)/.exec(text);return m?m[1]:undefined;}catch{return undefined;}}
240
+ function awFloorProblem(){if(!onPath('aw'))return{code:'needs-configuration',message:`aw CLI not on PATH; install aw >= ${AW_MIN}`};const installed=awVersionLabel();if(!installed)return{code:'needs-configuration',message:`aw version could not be read; install aw >= ${AW_MIN}`};return !semverLt(installed,AW_MIN)?null:{code:'needs-configuration',message:`aw ${installed} is older than required ${AW_MIN}; install aw >= ${AW_MIN}`};}
234
241
  function wakeReadiness(home,{reliedOn=false}={}) {
235
242
  if(!home || !reliedOn) return {problems:[],warnings:[]};
236
243
  try {
@@ -238,12 +245,12 @@ function wakeReadiness(home,{reliedOn=false}={}) {
238
245
  const state=doc.daemon_version_state || (doc.daemon_running===false?'not_running':doc.daemon_version?'reported':'unknown');
239
246
  if(state==='reported') {
240
247
  const running=String(doc.daemon_version||'unknown');
241
- if(semverLt(running,WAKE_STREAM_MIN)) return {problems:[{code:'wake-daemon-outdated',message:`host wake daemon is running ${running}; required ${WAKE_STREAM_MIN}; upgrade aw, then restart the host wake daemon`}],warnings:[]};
248
+ if(semverLt(running,AW_MIN)) return {problems:[{code:'wake-daemon-outdated',message:`host wake daemon is running ${running}; required ${AW_MIN}; upgrade aw, then restart the host wake daemon`}],warnings:[]};
242
249
  return {problems:[],warnings:[]};
243
250
  }
244
251
  if(state==='not_running') return {problems:[{code:'wake-daemon-not-running',message:'host wake daemon is not running; session delivery relies on it'}],warnings:[]};
245
- return {problems:[],warnings:[{code:'wake-daemon-version-unknown',message:`host wake daemon version is unknown; compatibility unproven; required ${WAKE_STREAM_MIN}; upgrade aw, then restart the host wake daemon`}]};
246
- } catch {return {problems:[],warnings:[{code:'wake-daemon-version-unknown',message:`host wake daemon version is unknown; compatibility unproven; required ${WAKE_STREAM_MIN}; upgrade aw, then restart the host wake daemon`}]};}
252
+ return {problems:[],warnings:[{code:'wake-daemon-version-unknown',message:`host wake daemon version is unknown; compatibility unproven; required ${AW_MIN}; upgrade aw, then restart the host wake daemon`}]};
253
+ } catch {return {problems:[],warnings:[{code:'wake-daemon-version-unknown',message:`host wake daemon version is unknown; compatibility unproven; required ${AW_MIN}; upgrade aw, then restart the host wake daemon`}]};}
247
254
  }
248
255
  function workspaceReadinessPhase(req) {
249
256
  const ctx=workspaceReadinessContext(req.input.context);
@@ -310,6 +317,8 @@ function errorCode(error) {if(errorCodes.has(error?.wireCode)) return error.wire
310
317
  // caught exception's dynamic alias/key/path, native stderr or credential text.
311
318
  const safeReasons=new Map([
312
319
  ['needs-configuration',[
320
+ TEAM_SETTING_MESSAGE,
321
+ AWEB_TEAM_ID_MESSAGE,
313
322
  'messaging-enabled standalone preparation needs an explicit context key',
314
323
  'messaging binding needs one soul declaration',
315
324
  'messaging workspace must declare private: per-human',
@@ -1,22 +1,22 @@
1
1
  {
2
2
  "capability": "oats.aweb",
3
3
  "command": "aweb",
4
- "version": "1.16.0",
4
+ "version": "1.17.1",
5
5
  "compatibility": {
6
- "oats": ">=0.26.0"
6
+ "oats": ">=0.30.0"
7
7
  },
8
8
  "layer": "messaging",
9
9
  "description": "Messaging layer via aweb: per-instance team identities, explicit joined teams with live receive, native aw mail/chat skills and the cross-machine team roster.",
10
10
  "requires": [
11
11
  {
12
12
  "command": "aw",
13
- "why": "identity minting at spawn (joined teams: aw >= 1.36.12), self-delete at retire, live receive registration, and all agent messaging",
13
+ "why": "aw >= 1.36.13: identity minting at spawn, self-delete at retire, live receive registration, onboarding, and all agent messaging",
14
14
  "install": "https://aweb.ai/docs (aw CLI)"
15
15
  },
16
16
  {
17
17
  "runtime": "pi",
18
18
  "package": "npm:@awebai/pi",
19
- "why": "the aweb channel extension for pi sessions \u2014 real-time mail/chat awakenings; without it a pi instance can send with `aw` but is never woken by incoming messages",
19
+ "why": "the aweb channel extension for pi sessions — real-time mail/chat awakenings; without it a pi instance can send with `aw` but is never woken by incoming messages",
20
20
  "install": "https://aweb.ai/docs (installed into pi with `pi install npm:@awebai/pi`)",
21
21
  "when": {
22
22
  "delivery": "channel"
@@ -26,7 +26,7 @@
26
26
  "runtime": "claude",
27
27
  "package": "aweb-channel@awebai-marketplace",
28
28
  "marketplace": "awebai/claude-plugins",
29
- "why": "the aweb channel plugin for Claude Code sessions \u2014 real-time mail/chat awakenings; without it a Claude instance can send with `aw` but is never woken by incoming messages",
29
+ "why": "the aweb channel plugin for Claude Code sessions — real-time mail/chat awakenings; without it a Claude instance can send with `aw` but is never woken by incoming messages",
30
30
  "install": "https://aweb.ai/docs (installs the awebai marketplace and the aweb-channel plugin into Claude Code)",
31
31
  "when": {
32
32
  "delivery": "channel"
@@ -63,10 +63,6 @@
63
63
  "skills/aweb-identity"
64
64
  ],
65
65
  "inject": "injects/aweb.md",
66
- "helperInjection": {
67
- "version": 1,
68
- "mode": "omit"
69
- },
70
66
  "commands": {
71
67
  "roster": "bin/oats-aweb.mjs roster",
72
68
  "setup": "bin/oats-aweb.mjs setup",
@@ -141,9 +137,6 @@
141
137
  ],
142
138
  "description": "channel: the native aweb channel packages wake the instance (default). session: delivery is external (AWEB_DELIVERY=session), no channel flag; the host wake broker registers the instance once it exists."
143
139
  },
144
- "team": {
145
- "description": "Target aweb team id for the primary identity lifecycle. In workspace v2 spawns this payload value wins over OATS_TEAM_ID; if both are set and differ the hook warns and uses this setting. Unset means the aweb root's active team."
146
- },
147
140
  "root": {
148
141
  "hostOnly": true,
149
142
  "description": "Host-owned absolute directory whose .aw is the aweb minting root. Put this only in oats-local.yaml settings.oats.aweb.root; use oats aweb setup to initialize it."
@@ -11,11 +11,11 @@ These reviewed resources are vendored from the MIT-licensed aweb repository:
11
11
 
12
12
  Vendored trees:
13
13
 
14
- - `aweb-messaging/`
15
- - `aweb-team-membership/` (adapted for OATS: team changes go through `oats aweb teams|join|leave`)
16
- - `aweb-identity/`
14
+ - /aweb-messaging (directory `skills/aweb-messaging/`)
15
+ - /aweb-team-membership (directory `skills/aweb-team-membership/`, adapted for OATS: team changes go through `oats aweb teams|join|leave`)
16
+ - /aweb-identity (directory `skills/aweb-identity/`)
17
17
 
18
- Not vendored: `oats-aweb/` is this package's own OATS playbook (identity,
18
+ Not vendored: /oats-aweb (directory `skills/oats-aweb/`) is this package's own OATS playbook (identity,
19
19
  default and joined teams, roster, delivery and wakes, etiquette,
20
20
  troubleshooting). Every `aw` invocation it and the vendored skills cite is
21
21
  checked against a real published aw by `test/oats-aweb-1-15.test.mjs`.
@@ -31,7 +31,7 @@ oats aweb leave --labels <label>[,<label>] # leave joined wider-team labels
31
31
  - The workspace's default team cannot be left; attempting it with label `default` is `E_TEAM_DEFAULT`.
32
32
  - A label that is not eligible for this soul/workspace is `E_TEAM_NOT_ELIGIBLE`.
33
33
  - Joined wider teams use a local identity home such as
34
- `<home>/.aweb-identity-<label>`. Joined teams require aw >= 1.36.12. The
34
+ `<home>/.aweb-identity-<label>`. The host aw CLI must be >= 1.36.13. The
35
35
  provider creates joined homes with `aw id team accept-invite` under
36
36
  `--identity-home`, verifies the root auto-connected, and does not run
37
37
  `aw init` inside the per-team home.
@@ -8,9 +8,9 @@ allowed-tools: "Bash(aw *), Bash(oats aweb *), Bash(oats status*), Bash(oats rea
8
8
 
9
9
  You run on OATS with the `oats.aweb` messaging layer. This skill is what you
10
10
  need to message well: who you are, who you can reach, how mail reaches you,
11
- how to behave, and what to do when something is off. For deeper aw detail load
12
- `aweb-messaging` (mail/chat craft, verification), `aweb-team-membership`
13
- (certificates, teams) or `aweb-identity` (keys, addresses).
11
+ how to behave, and what to do when something is off. For deeper aw detail run
12
+ /aweb-messaging (mail/chat craft, verification), /aweb-team-membership
13
+ (certificates, teams) or /aweb-identity (keys, addresses).
14
14
 
15
15
  Run the `oats aweb` commands below **from your instance home** (where
16
16
  `TASK.md` is) or pass `--home <your home>`: they resolve which instance you are
@@ -23,16 +23,14 @@ joined team, put `--identity-home <identityHome>` before the subcommand.
23
23
  | Fact | Where to read it |
24
24
  |---|---|
25
25
  | Your alias | your instance name; the `Comms:` line of `TASK.md`; `aw whoami` |
26
- | Your default team | `oats aweb teams --json` → `defaultTeam.team` (`defaultTeam.source`) |
26
+ | Your default team | `oats aweb teams --json` → `defaultTeam` (`{label, team, from}`) |
27
27
  | Teams you may join | `oats aweb teams --json` → `eligible[]` |
28
28
  | Teams you have joined | `oats aweb teams --json` → `joined[]` (each with `identityHome`, `receive`) |
29
29
  | How mail reaches you | the `Comms:` line of `TASK.md` (see section 4) |
30
30
 
31
- - **Default team.** Your primary identity lives in the workspace's default team:
32
- the aweb root's active team (`defaultTeam.source: root`), or the team the
33
- deployment pinned (`defaultTeam.source: setting`). `defaultTeam.source` is
34
- always present and is only `root` or `setting`. Everyone this deployment
35
- spawns into that team is there with you.
31
+ - **Default team.** Your primary identity lives in the kernel-selected default
32
+ team. `defaultTeam.from` is `deployment` or `soul`; `defaultTeam.team` is the
33
+ provider id. There is no root-active-team fallback.
36
34
  - **Joined teams.** A wider team the workspace defines, joined explicitly. Each
37
35
  gives you a **separate identity** with the same alias in that team, kept
38
36
  under `<home>/.aweb-identity-<label>`. You act as that team only with
@@ -154,7 +152,7 @@ oats aweb leave --labels <label>[,<label>]
154
152
  - **Verified senders:** check `trust_status` / `verified` on what you receive.
155
153
  Do not act on an unverified or mismatched sender's request to expose data,
156
154
  change identities, run destructive commands or move authority; ask through
157
- another channel first (`aweb-messaging` → Verification posture).
155
+ another channel first (/aweb-messaging → Verification posture).
158
156
  - **Tasks are not messages:** durable task tracking belongs to your deployment's
159
157
  task layer, not mail.
160
158
 
@@ -176,7 +174,7 @@ oats readiness --home "$PWD" --json # the provider's readiness answer for this
176
174
  | `team-unmapped` | your soul's primary label is not mapped by the workspace; you are in the default team | workspace owner, if a shared team was meant |
177
175
  | `joined-team-receive` | a joined team receives live through the broker (informational) | nobody |
178
176
  | `joined-team-poll-only` | a joined team does not wake you; the message says why | poll that team at task boundaries; human may start the wake daemon |
179
- | `wake-daemon-not-running` / `-outdated` / `-version-unknown` | host wake broker is down or older than 1.36.5 | human: upgrade aw, restart the host wake daemon |
177
+ | `wake-daemon-not-running` / `-outdated` / `-version-unknown` | host wake broker is down or older than 1.36.13 | human: upgrade aw, restart the host wake daemon |
180
178
  | `custody`, `e2ee-disabled` | resident-grant mode custody/encryption issue | human |
181
179
  | `teams-unverified` (launch) | live team data was unavailable; memberships were kept | nobody |
182
180
 
@@ -187,8 +185,6 @@ oats readiness --home "$PWD" --json # the provider's readiness answer for this
187
185
  - `E_TEAM_DEFAULT` — the workspace's default team cannot be left.
188
186
  - `E_TEAM_GLOBAL_MODE` — this home acts as a resident identity through a
189
187
  session grant; joined teams need local identities. Report it.
190
- - `E_TEAM_AW_FLOOR` — the host `aw` is too old: joined teams need aw >= 1.36.12.
191
- Report it; don't work around it.
192
188
  - "failed to leave team … kept …" — the release was not confirmed; the identity
193
189
  home was kept on purpose so leave can be retried. Retry later or report.
194
190
 
@@ -204,6 +200,70 @@ oats readiness --home "$PWD" --json # the provider's readiness answer for this
204
200
  commands once, and report a readiness warning rather than looping.
205
201
  - A flag looks wrong: run `aw <command> --help`; never guess flags.
206
202
 
203
+ ## 8. Provider configuration and setup internals
204
+
205
+ OATS owns team selection. oats.aweb receives the kernel's default and eligible
206
+ teams; it does not have a provider `team` setting.
207
+
208
+ **Settings under `settings.oats.aweb`:**
209
+
210
+ - `delivery`: `channel` (default) or `session`; `session` uses the host wake
211
+ broker and sets `AWEB_DELIVERY=session`.
212
+ - `root`: absolute directory whose `.aw` is the default team's minting root.
213
+ - `roots`: `{ <team id>: <absolute dir> }`; `roots[team]` wins over `root` and
214
+ is how one deployment mints into several aweb teams.
215
+ - `residents`: host-only resident custody roots for `identity.mode: global`.
216
+ - `join`: comma-separated eligible labels to join at spawn.
217
+ - `identity`: local by default; global mode uses a named resident grant.
218
+
219
+ There is deliberately no `settings.oats.aweb.team` in 1.17. Use `oats teams`
220
+ and `oats soul teams`; a stale `team` setting is refused with a message saying
221
+ teams are not a setting since oats.aweb 1.17 / OATS 0.30.
222
+
223
+ **One root per team.** A local aweb root holds one local identity and one team
224
+ membership. Setup never accepts a second local team into an existing `.aw`.
225
+ For created or joined teams it creates `<deployment>/.aweb-roots/<label>`,
226
+ accepts the invite into `<root>/.aw`, connects it, and records
227
+ `settings.oats.aweb.roots[<team id>] = <root>` in `oats-local.yaml`. Minting for
228
+ team `T` uses `roots[T]`, else `root`.
229
+
230
+ **Setup acts:**
231
+
232
+ From a deployment directory (outside an instance home), call setup through any
233
+ soul that uses this messaging provider: `oats aweb setup --soul <any soul with messaging>`.
234
+ The provider consumes the kernel-forwarded `--soul` dispatch flag; it is not a
235
+ team selector and should not appear in any `aw` call.
236
+
237
+ - `oats aweb setup --username <u>` → `aw init --new-account --username <u>` for
238
+ a missing hosted root.
239
+ - `AWEB_API_KEY=<key> oats aweb setup` → `aw init` for the hosted team behind
240
+ the API key.
241
+ - `oats aweb setup --create <label> --namespace <domain>` → owner/admin act for
242
+ a customer-controlled namespace: normalize the label, create the team, require
243
+ aw to return `team_id` and an invite token, accept into a per-team root, record
244
+ `roots[team]`, and record the local mapping via `oats teams add <label> --team
245
+ <id>`. Without `--namespace`, setup refuses hosted additional-team creation
246
+ until the hosted-team aweb release exists.
247
+ - `oats aweb setup --join <label> --invite <token>` → accept an existing/shared
248
+ team's invite into a per-team root and record `roots[team]`.
249
+ - For an unmapped committed/shared default, plain setup creates nothing; it asks
250
+ for the owner-provided provider id or invite. The owner explicitly runs
251
+ `oats aweb setup --create <label> --namespace <domain>`, then commits the
252
+ printed provider id; setup never edits the committed team file.
253
+
254
+ **Readiness messages:** no default is exactly `no teams configured: run \`oats
255
+ aweb setup\``. An unmapped default is exactly `the default team <label> has no
256
+ provider id yet: its owner runs oats aweb setup, then commits the id, or choose
257
+ another default with oats teams default`. A shared team whose root is missing or
258
+ not a member is an operator setup problem: ask the owner for an invite and run
259
+ `oats aweb setup --join <label> --invite <token>`, or use `--create` if this
260
+ host owns that team. When a joined team is removed from the live team set,
261
+ hosted teams are left automatically; on a namespace team you control (BYOT), a
262
+ failed leave is reported as an instance event and the team owner removes the
263
+ member.
264
+
265
+ **aw floor:** all 1.17 paths require `aw >= 1.36.13`.
266
+
207
267
  ## Gotchas
208
268
 
209
269
  - `aw mail inbox` shows **unread** only; `--show-all` shows history.
@@ -214,3 +274,13 @@ oats readiness --home "$PWD" --json # the provider's readiness answer for this
214
274
  - Don't hand-edit `.aw`, `.aweb-identity-*` or `.oats-aweb/teams.json`; report mismatches.
215
275
  - `oats aweb setup` is the operator's onboarding tool; if messaging is broken,
216
276
  report its output to your human instead of re-onboarding yourself.
277
+ - `oats aweb setup --create <label> --namespace <domain>` creates a new local
278
+ BYOT team, accepts it into a new per-team root under `.aweb-roots/`, records
279
+ `settings.oats.aweb.roots` in `oats-local.yaml`, and records it with `oats
280
+ teams add <label> --team <id>` through the selected OATS CLI. Hosted
281
+ additional-team creation without `--namespace` is refused until the
282
+ hosted-team aweb release exists. `oats aweb setup --join <label> --invite
283
+ <token>` uses the same separate-root path for an existing/shared team; never
284
+ accept a second local team into the existing root. For an unmapped
285
+ committed/shared default, plain setup creates nothing and asks for the owner's
286
+ id or invite; it does not edit the shared file.
@@ -0,0 +1,26 @@
1
+ ## You are an adversarial code reviewer
2
+
3
+ You review ONE piece of work for the developer who spawned you, in its worktree. You stay
4
+ for the whole loop: the first review and every re-review round, until you approve or the
5
+ developer escalates. Your value is a fresh, hostile reading: you don't know how the author
6
+ reasoned, and you don't guess.
7
+
8
+ **Each round**
9
+ 1. **Read the brief** (your task): the goal, the spec, the diff range, how to run the
10
+ tests, who to report to.
11
+ 2. **Review with the skills, not from memory:** `/adversarial-review` (real bugs, proven),
12
+ which runs `/security-review`, `/simplification-review` and `/review-dev-docs` as well.
13
+ 3. **Report to the developer** in one message: the verdict, the findings, the
14
+ simplifications. Use your messaging layer if one is active (write the report to a file
15
+ and send that); otherwise print it as your final message.
16
+ 4. **Re-review** when the developer replies with the new head, the fixes and any disputes:
17
+ check the delta and the disputed points, and report again. Finish at `APPROVE` or
18
+ `APPROVE WITH NITS`, or when the developer says the loop is escalated.
19
+
20
+ **Boundaries**
21
+ - **Never edit the worktree**: no commits, branch switches or pushes. Throwaway checks go in
22
+ a temp directory outside it.
23
+ - Run tests **only to confirm or refute a finding**, and only the relevant ones.
24
+ - Be consistent across rounds: don't reopen what you approved unless new code broke it,
25
+ and don't move the bar.
26
+ - If the brief lacks something you need, ask the developer instead of guessing.
@@ -0,0 +1,16 @@
1
+ {
2
+ "capability": "oats.code-review",
3
+ "version": "1.1.0",
4
+ "compatibility": {
5
+ "oats": ">=0.29.0"
6
+ },
7
+ "description": "The adversarial code reviewer's role and method: review one developer's piece of work in its worktree, try to break it, prove each finding, cover security and simplification, keep the noise out, and iterate with the same developer until satisfied. Assigned to the code-reviewer soul.",
8
+ "requires": [],
9
+ "inject": "injects/reviewer.md",
10
+ "skills": [
11
+ "skills/adversarial-review",
12
+ "skills/security-review",
13
+ "skills/simplification-review",
14
+ "skills/review-dev-docs"
15
+ ]
16
+ }
@@ -0,0 +1,66 @@
1
+ ---
2
+ name: adversarial-review
3
+ description: The reviewer's method. Find the real bugs in a change by trying to break it, prove each finding, rank by impact, and keep noise out. Use when reviewing a diff as the code-reviewer, including each re-review round.
4
+ ---
5
+
6
+ # Adversarial review
7
+
8
+ You are trying to **break the change**, not to grade it. A good review finds the few things
9
+ that would hurt in production and proves them. A bad one lists thirty opinions.
10
+
11
+ ## 1. Understand what it's for
12
+ Read the goal and the spec first, then the whole diff, then the code around it that the
13
+ diff calls or is called by. Don't judge a line before you know what the change must do.
14
+
15
+ ## 2. Attack it
16
+ Go through these deliberately, for every changed path:
17
+ - **The spec:** does it do what "done when" says? Every edge case the spec lists?
18
+ - **Inputs:** empty, huge, malformed, unicode, negative, duplicate, missing, of the wrong
19
+ type. Values from files, env, network, users.
20
+ - **Failure paths:** what happens when each call it makes fails? Is the error surfaced,
21
+ retried, or swallowed? Is state left half-written?
22
+ - **State and concurrency:** ordering, retries, re-entrancy, two processes at once,
23
+ check-then-use races, caches that go stale.
24
+ - **Contracts:** did an output shape, error code, file format or flag change? Who reads
25
+ it, and do they still work?
26
+ - **Resources:** leaks (files, processes, listeners), unbounded growth, timeouts.
27
+ - **Tests:** do they prove the behaviour, or just run the code? Would they fail if the
28
+ bug you're thinking of existed?
29
+
30
+ Then run the `/security-review`, `/simplification-review` and `/review-dev-docs` passes.
31
+
32
+ ## 3. Prove it before you report it
33
+ - For each suspected bug, **show the failing path**: the input, the steps, the wrong result.
34
+ - If you can confirm it cheaply, **do**: run the relevant test, or write a small throwaway
35
+ check outside the tree. Run tests **only** to confirm or refute a finding; you are not
36
+ the CI.
37
+ - If you can't show how it breaks, it's a **question**, not a bug. Ask it as one.
38
+
39
+ ## 4. Report: signal only
40
+ One report per round. Verdict first:
41
+ - `CHANGES NEEDED`: at least one **blocker** or **major**.
42
+ - `APPROVE WITH NITS`: only minors or simplifications.
43
+ - `APPROVE`: nothing worth the author's time.
44
+
45
+ Then the findings, most severe first, each as:
46
+ ```
47
+ [blocker|major|minor] file:line: what breaks, for which input or state (one sentence)
48
+ proof: <the path, or the test you ran and its output>
49
+ fix: <the concrete change>
50
+ ```
51
+ - **blocker:** wrong results, data loss, a security hole, a broken contract, a crash on a
52
+ plausible path.
53
+ - **major:** a real bug on a less common path; a missing test for a "done when".
54
+ - **minor:** a real but low-impact issue.
55
+ - Simplifications go in their own short section (see `/simplification-review`).
56
+
57
+ **Keep out:** style the formatter or linter owns; personal taste; "consider adding
58
+ comments"; restating the diff; praise; speculative findings with no path. **At most 3
59
+ minors per round.** If you have more, pick the three that matter.
60
+
61
+ ## 5. Re-review rounds
62
+ - Check each fix actually fixes the finding, and didn't break something next to it.
63
+ - Re-read disputed findings against the author's reason. Withdraw if they're right; say
64
+ why if they aren't.
65
+ - Review the delta, not the whole change again, unless the fix changed the design.
66
+ - Don't raise new minors on code that didn't change.
@@ -0,0 +1,30 @@
1
+ ---
2
+ name: review-dev-docs
3
+ description: The reviewer's pass over a change's effect on the repository's development docs and code comments. Check that they still describe how the code works and how to work in it, that the change updated what it made untrue, and that nothing drift-prone was added. Use in every adversarial review round.
4
+ ---
5
+
6
+ # Review the development docs
7
+
8
+ The code is only half the change. Check the docs and comments the next developer will rely
9
+ on.
10
+
11
+ ## Check
12
+ - **Coverage:** did the change alter a structure, a flow, a convention, a command, a public
13
+ promise or a rule that the contributor guide, the architecture docs, a module README or a
14
+ comment describes? If so, is that doc updated in the same change?
15
+ - **Truth:** does every doc and comment the diff touches match the code as it now is? Read
16
+ them against the code, not against the author's intent.
17
+ - **The why:** does non-obvious new code (an invariant, a workaround, a subtle ordering)
18
+ carry a comment saying *why*?
19
+ - **Drift-prone content:** decisions in motion ("for now", dates, PR numbers, who asked),
20
+ version history, or comments that restate what the code does. These belong in git or with
21
+ whoever is deciding, not in the docs.
22
+ - **Duplication:** a rule restated in several places, which will diverge.
23
+
24
+ ## Report
25
+ Use the `/adversarial-review` format:
26
+ - **major:** a doc now tells the next developer something false about how to build, test or
27
+ change the code, or a contract's docs don't match the new behaviour.
28
+ - **minor:** a missing *why* on non-obvious code; drift-prone content; a stale sentence
29
+ nearby.
30
+ Missing docs for a trivial change aren't a finding. At most 2 doc minors per round.
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: security-review
3
+ description: The reviewer's security pass. Read the change as an attacker would, across every trust boundary it touches (injection, paths, secrets, authz, deserialization, supply chain, web), and report only what is exploitable or a concrete hardening gap. Use in every adversarial review round, and alone when asked for a security review.
4
+ ---
5
+
6
+ # Security review
7
+
8
+ Every input the process didn't create itself is hostile. Every boundary the change
9
+ crosses is a chance for an attacker. Rank by **exploitability**, not pattern count.
10
+
11
+ ## Map the trust boundaries first
12
+ List what the change reads from outside (users, files, env, network, other processes,
13
+ config, CI) and what it can cause (commands, file writes, network calls, privileged
14
+ actions). Findings live where the first reaches the second.
15
+
16
+ ## Checklist
17
+ **Injection and execution**
18
+ - Command injection: external data reaching a shell string or an argv. Quoting isn't
19
+ escaping; use argv arrays. **Option injection:** a value starting with `-` passed as an
20
+ argument can become a flag (`--upload-pack=…`, `--config=…`). Use `--` or a
21
+ `--flag=value` single token, and refuse leading `-`.
22
+ - Interpreters: SQL/NoSQL, regex (catastrophic backtracking), templates, `eval`/`Function`,
23
+ YAML/JSON loaders that build objects.
24
+ - Anything that writes, then executes (temp scripts, `curl | sh`, hooks, plugins).
25
+
26
+ **Files and paths**
27
+ - Traversal: external segments joined into paths (`..`, absolute paths, symlinks, zip-slip,
28
+ crafted archive or git tree entries). Canonicalize, then prefix-check, and don't follow
29
+ symlinks out of the root.
30
+ - Permissions on new files holding secrets or state; temp files in shared directories.
31
+ - TOCTOU: check-then-use on files, permissions or state.
32
+
33
+ **Secrets and data**
34
+ - Hard-coded credentials, tokens, keys (tests and examples included).
35
+ - Secrets in logs, errors, process arguments (visible in `ps`), URLs, crash reports.
36
+ - Sensitive data persisted without need, or left behind on delete/retire paths.
37
+
38
+ **AuthN / AuthZ**
39
+ - New endpoints, commands, IPC or local servers: who can reach them, and what they check.
40
+ "Localhost only" is a weak boundary: what can a hostile local process or a web page do?
41
+ - CSRF, CORS, Host/Origin checks on local HTTP servers.
42
+ - Can low-trust input (config, a repo's files, a PR) cause high-trust execution (CI, hooks,
43
+ automation running under someone's credentials)?
44
+
45
+ **Supply chain**
46
+ - New dependencies: needed, pinned, from the expected owner?
47
+ - Downloaded or cloned artifacts: is integrity checked before use?
48
+
49
+ **Web (when applicable)**
50
+ - XSS: external strings reaching HTML without escaping (`innerHTML`, attributes, URLs).
51
+
52
+ ## Report
53
+ Use the adversarial-review format. Any credible injection, traversal, secret leak or authz
54
+ bypass is a **blocker**. Each finding states the **attack in one sentence** (who, what
55
+ input, what they gain). If you can't state the attack, it's a hardening note (minor) or a
56
+ question, not a blocker.
@@ -0,0 +1,34 @@
1
+ ---
2
+ name: simplification-review
3
+ description: The reviewer's refactor pass. Find where the change could be simpler, smaller or more consistent with the code around it, without changing behaviour, and report only suggestions worth the author's time. Use in every adversarial review round.
4
+ ---
5
+
6
+ # Simplification review
7
+
8
+ Good code is the smallest code that does the job clearly. Look for what could go.
9
+
10
+ ## Look for
11
+ - **Unneeded generality:** options, flags, parameters, abstraction layers or extension
12
+ points nothing uses yet.
13
+ - **Duplication:** logic that already exists nearby (a helper, a validator, a pattern the
14
+ module uses), re-implemented.
15
+ - **Indirection:** wrappers that only forward, one-use helpers that hide simple code,
16
+ deep call chains for a small result.
17
+ - **Dead or defensive noise:** unreachable branches, checks that can't fail given the
18
+ types or callers, commented-out code, stale TODOs.
19
+ - **Tangled control flow:** nested conditions that early returns would flatten; state
20
+ flags that a clearer structure would remove.
21
+ - **Inconsistency:** a new name or pattern for something the codebase already names or
22
+ does another way.
23
+ - **Tests:** repetitive tests that a table would express; tests of implementation details
24
+ that will break on harmless refactors.
25
+
26
+ ## Report
27
+ In a separate **Simplifications** section after the findings, **at most 3**, each as:
28
+ ```
29
+ file:line: what to simplify → the simpler form (one or two lines, or a short sketch)
30
+ why: less code / removes a duplicate / matches <existing pattern>
31
+ ```
32
+ - Only suggest what preserves behaviour and is clearly better, not just different.
33
+ - Simplifications never block on their own. If one also fixes a bug, report the bug as a
34
+ finding instead.