@awebai/oats 0.27.2 → 0.29.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/bin/oats.mjs +445 -96
- package/capabilities/oats-okf/bin/oats-okf.mjs +55 -30
- package/capabilities/oats-okf/injects/okf.md +36 -28
- package/capabilities/oats-okf/lib/binding-wire.mjs +4 -1
- package/capabilities/oats-okf/lib/config.mjs +6 -1
- package/capabilities/oats-okf/lib/consult.mjs +496 -0
- package/capabilities/oats-okf/lib/harvest-status.mjs +88 -0
- package/capabilities/oats-okf/lib/harvest-switch.mjs +81 -0
- package/capabilities/oats-okf/lib/inspection.mjs +11 -3
- package/capabilities/oats-okf/lib/io.mjs +9 -2
- package/capabilities/oats-okf/lib/okf-validate.mjs +123 -0
- package/capabilities/oats-okf/lib/sources.mjs +42 -55
- package/capabilities/oats-okf/lib/stores.mjs +19 -11
- package/capabilities/oats-okf/lib/worker.mjs +90 -8
- package/capabilities/oats-okf/oats.json +24 -9
- package/capabilities/oats-okf/skills/okf-consultation/SKILL.md +144 -0
- package/capabilities/oats-okf/skills/okf-consultation/references/consult.md +86 -0
- package/capabilities/oats-okf/skills/okf-instance-knowledge/SKILL.md +104 -0
- package/capabilities/oats-okf-harvest/bin/okf-harvest.mjs +140 -0
- package/capabilities/oats-okf-harvest/injects/harvester.md +12 -0
- package/capabilities/oats-okf-harvest/oats.json +26 -0
- package/capabilities/oats-okf-harvest/skills/knowledge-harvest/SKILL.md +168 -0
- package/capabilities/oats-okf-harvest/skills/knowledge-theory/SKILL.md +192 -0
- package/capabilities/{oats-okf/skills/okf → oats-okf-harvest/skills/okf-authoring}/SKILL.md +15 -22
- package/capabilities/oats-okf-maintenance/bin/okf-maintenance.mjs +149 -0
- package/capabilities/oats-okf-maintenance/injects/maintainer.md +12 -0
- package/capabilities/oats-okf-maintenance/lib/provenance.mjs +45 -0
- package/capabilities/oats-okf-maintenance/oats.json +21 -0
- package/capabilities/oats-okf-maintenance/skills/knowledge-review/SKILL.md +144 -0
- package/capabilities/oats-okf-maintenance/skills/knowledge-theory/SKILL.md +192 -0
- package/capabilities/oats-okf-maintenance/skills/okf-authoring/SKILL.md +151 -0
- package/capabilities/oats-okf-maintenance/skills/okf-authoring/scripts/okf-validate.mjs +123 -0
- package/capabilities/oats-okf-maintenance/skills/okf-trigger-setup/SKILL.md +146 -0
- package/capabilities/oats-review/injects/review.md +3 -2
- package/capabilities/oats-review/oats.json +3 -4
- package/docs/capabilities.md +41 -9
- package/docs/capability-manifest.schema.json +0 -7
- package/docs/design/2026-09-24-phase-d-plan.md +11 -0
- package/docs/design/2026-09-26-desktop-design-brief-architecture.md +241 -0
- package/docs/design/2026-09-26-okf-knowledge-operations.md +389 -0
- package/docs/desktop-cli-api.md +342 -10
- package/docs/implementation.md +1 -1
- package/docs/knowledge-capability-authoring.md +8 -2
- package/docs/knowledge-reference/package-craft.md +8 -5
- package/docs/knowledge.md +101 -0
- package/docs/oats-local.schema.json +33 -2
- package/docs/oats-package.schema.json +39 -0
- package/docs/official-catalog.md +7 -4
- package/docs/packages.md +76 -6
- package/docs/release-lane.md +1 -1
- package/docs/release-notes/v0.28.0.md +144 -0
- package/docs/release-notes/v0.29.0.md +240 -0
- package/docs/schedules.md +230 -4
- package/docs/souls-and-instances.md +11 -9
- package/docs/workspaces.md +18 -3
- package/lib/automations.mjs +369 -0
- package/lib/core.mjs +87 -158
- package/lib/instance-inspect.mjs +16 -8
- package/lib/instance-resolution.mjs +90 -197
- package/lib/materialize.mjs +18 -7
- package/lib/operator-dispatch.mjs +1 -2
- package/lib/packages.mjs +107 -6
- package/lib/remote.mjs +21 -1
- package/lib/resolve.mjs +71 -9
- package/lib/schedule.mjs +228 -45
- package/lib/triggers.mjs +678 -0
- package/lib/workspace.mjs +81 -4
- package/package-catalog.json +6 -4
- package/package.json +1 -1
- package/capabilities/oats-okf/agents/memory-harvest/AGENTS.md +0 -21
- package/capabilities/oats-okf/agents/memory-harvest/soul.yaml +0 -5
- package/capabilities/oats-okf/skills/memory-harvest/SKILL.md +0 -285
- package/capabilities/oats-review/agents/reviewer/AGENTS.md +0 -53
- package/capabilities/oats-review/agents/reviewer/soul.yaml +0 -6
- /package/capabilities/{oats-okf/skills/okf → oats-okf-harvest/skills/okf-authoring}/scripts/okf-validate.mjs +0 -0
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: okf-authoring
|
|
3
|
+
description: >-
|
|
4
|
+
Open Knowledge Format (OKF) authoring craft for knowledge-operations souls:
|
|
5
|
+
how to write, edit, move and validate concepts in an OKF bundle (markdown
|
|
6
|
+
concepts with YAML frontmatter, per Google Cloud's OKF v0.1 spec), keep
|
|
7
|
+
index.md and log.md honest, supersede instead of silently rewriting, and run
|
|
8
|
+
the bundled validator. Use when staging or amending concepts in a knowledge
|
|
9
|
+
base, fixing index/log entries, reviewing a knowledge PR's Markdown, or when
|
|
10
|
+
asked to validate a bundle. Promotion judgment (what belongs in a base) is
|
|
11
|
+
the knowledge-theory skill.
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# OKF craft — author, maintain, consume
|
|
15
|
+
|
|
16
|
+
An OKF **bundle** is a directory tree of markdown files. Each non-reserved `.md`
|
|
17
|
+
file is **one concept**; links between files form the knowledge graph. No
|
|
18
|
+
database, no SDK — plain git-versionable text. Spec: OKF v0.1 (Google Cloud).
|
|
19
|
+
An external base is one bundle and link namespace. Owned nodes are
|
|
20
|
+
nonoverlapping subdirectories, not separate root-link namespaces. Instance
|
|
21
|
+
`notes/` files are task-local concepts; no knowledge lives in the soul.
|
|
22
|
+
|
|
23
|
+
## The format in one screen
|
|
24
|
+
|
|
25
|
+
- **Concept = one file.** Concept ID = path minus `.md`. Small and specific
|
|
26
|
+
beats long and general — split rather than grow.
|
|
27
|
+
- **Frontmatter** (`---` delimited): only **`type`** is required (short,
|
|
28
|
+
freeform — the spec ships no vocabulary. Fleet core: `Lesson`, `Decision`,
|
|
29
|
+
`Playbook`, `Reference`; souls also grow role-specific types like
|
|
30
|
+
`Area Guide` or `Roadmap` — see the knowledge-theory skill for routing).
|
|
31
|
+
Recommended,
|
|
32
|
+
in order: `title`, `description` (ONE sentence — it's what index listings
|
|
33
|
+
and skimming agents see), `resource` (URI, only if a real asset backs the
|
|
34
|
+
concept), `tags` (YAML list), `timestamp` (ISO date of last meaningful change).
|
|
35
|
+
- **Links** are ordinary markdown, keep the `.md`, prefer bundle-root-absolute:
|
|
36
|
+
`[clearing playbook](/node/playbooks/clearing-fields.md)`. Links are untyped
|
|
37
|
+
directed edges; the surrounding prose carries the relationship's meaning.
|
|
38
|
+
- **Reserved files** at any level: `index.md` (navigation) and `log.md`
|
|
39
|
+
(history). They carry **no `type`**; only the bundle-root `index.md` may
|
|
40
|
+
have frontmatter, and only `okf_version: "0.1"`.
|
|
41
|
+
- **Conventional headings** when applicable: `# Schema`, `# Examples`,
|
|
42
|
+
`# Citations` (numbered external sources backing claims).
|
|
43
|
+
|
|
44
|
+
## Honesty rules (non-negotiable)
|
|
45
|
+
|
|
46
|
+
- **Never invent** a `resource`, `timestamp`, or `description` — leave a field
|
|
47
|
+
out rather than guess it.
|
|
48
|
+
- Every claim you write down should be something you verified or observed;
|
|
49
|
+
cite sources under `# Citations` when the claim came from outside.
|
|
50
|
+
- Never create a link to a concept you didn't create or verify exists —
|
|
51
|
+
except deliberate not-yet-written knowledge, which is allowed by spec but
|
|
52
|
+
should be rare and intentional.
|
|
53
|
+
- **Supersede, don't silently rewrite.** When a concept's meaning changes,
|
|
54
|
+
update it AND log the change; when it's wrong, correct it and say so in
|
|
55
|
+
log.md (`**Fix**: …`). History must stay reconstructible.
|
|
56
|
+
|
|
57
|
+
## Maintaining a bundle
|
|
58
|
+
|
|
59
|
+
**Adding a concept:**
|
|
60
|
+
1. Write the file in the right section dir with valid frontmatter.
|
|
61
|
+
2. Link it to/from related concepts (edit those files' bodies).
|
|
62
|
+
3. Add a line to the section's `index.md`: `* [Title](file.md) - description`.
|
|
63
|
+
4. Append to the bundle's `log.md` (see conventions below).
|
|
64
|
+
|
|
65
|
+
**Renaming/moving a concept:** update **every inbound link** — search the
|
|
66
|
+
whole bundle for the old path (`grep -rn "old-name.md" <bundle>`) — EXCEPT
|
|
67
|
+
links inside historical `log.md` entries: never rewrite log history; dangling
|
|
68
|
+
links there are expected.
|
|
69
|
+
|
|
70
|
+
**Removing:** delete the file, remove its index.md line, fix inbound links,
|
|
71
|
+
log a `**Removal**` or `**Deprecation**` entry saying why.
|
|
72
|
+
|
|
73
|
+
**log.md conventions** (newest first, `## YYYY-MM-DD` headings):
|
|
74
|
+
`* **Creation|Update|Removal|Fix|Deprecation|Harvest|Triage**: prose with
|
|
75
|
+
[links](/path.md).` One line per event; the bold word makes logs greppable.
|
|
76
|
+
|
|
77
|
+
**index.md discipline:** every concept reachable from an index; descriptions
|
|
78
|
+
in listings match the concept's frontmatter `description`. Indexes are
|
|
79
|
+
navigation, not content — keep them to listings.
|
|
80
|
+
|
|
81
|
+
## Consuming a bundle (answering from knowledge)
|
|
82
|
+
|
|
83
|
+
1. **Index-first, always.** Start at the root `index.md`; follow only links
|
|
84
|
+
relevant to the question. Never bulk-read a bundle — progressive
|
|
85
|
+
disclosure is the point of the format.
|
|
86
|
+
2. Frontmatter (`type`, `tags`, `description`) is the quick filter layer;
|
|
87
|
+
open bodies only for concepts that survive the filter.
|
|
88
|
+
3. `log.md` answers "what changed recently" — check it when freshness matters.
|
|
89
|
+
4. Cite concepts by path when reporting answers.
|
|
90
|
+
5. Tolerate imperfect concepts: unknown types and stale links are
|
|
91
|
+
never a reason to reject or ignore a bundle — that permissiveness is spec.
|
|
92
|
+
|
|
93
|
+
## Validating
|
|
94
|
+
|
|
95
|
+
Run the bundled validator (node, no deps) after non-trivial maintenance:
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
node <skill-dir>/scripts/okf-validate.mjs <bundle-dir> # conformance
|
|
99
|
+
node <skill-dir>/scripts/okf-validate.mjs <bundle-dir> --strict # + producer lints
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- **Conformance errors** (must fix): unparseable/missing frontmatter, missing
|
|
103
|
+
or empty `type`, reserved files carrying a `type`.
|
|
104
|
+
- **Producer lints** (`--strict`, should fix in bundles you produce): broken
|
|
105
|
+
intra-bundle links (log.md exempt), links missing `.md`, concepts
|
|
106
|
+
unreachable from any index.md, missing `title`/`description`.
|
|
107
|
+
|
|
108
|
+
Lints in a bundle you're *consuming* are noise — read on regardless.
|
|
109
|
+
|
|
110
|
+
## Portable source and store declarations
|
|
111
|
+
|
|
112
|
+
When the portable binding interface is active (planned OATS >=0.24.0; not yet a
|
|
113
|
+
published provider baseline), treat the source declaration as policy and the
|
|
114
|
+
captured ProviderBinding as execution authority:
|
|
115
|
+
|
|
116
|
+
- In `oats.okf.locations@1`, `fixed` is source-owned, `default` is rebindable,
|
|
117
|
+
and `inherit` requires an external binding such as `write.default`.
|
|
118
|
+
- Qualify every read and owned node by its declared store. A read grants no
|
|
119
|
+
write authority. Never infer the sole readable store as a destination.
|
|
120
|
+
- Workspace store envelopes put concrete provider choices under
|
|
121
|
+
`payload.bindings`. Workspace, adoption, and operator values remain separate
|
|
122
|
+
inputs to the shared resolver; OKF does not select their precedence.
|
|
123
|
+
- Durable placement is explicit selected settings: physical absolute
|
|
124
|
+
`bindings-file` and `state-dir`, plus selected `harvest-runtime`, optional
|
|
125
|
+
`harvest-model` and optional `git-timeout` (seconds for remote Git
|
|
126
|
+
operations, default 600). Never derive state from an instance home.
|
|
127
|
+
- Provider codecs run only after exact retained executable approval. Their
|
|
128
|
+
populated binding is not proof of readiness, enrollment, credentials, privacy
|
|
129
|
+
or publication authority. Respect typed non-ready results.
|
|
130
|
+
- Captured source descriptors freeze binding/runtime/source identity. Later
|
|
131
|
+
reads and workers use those bytes after source/config deletion. Never replace
|
|
132
|
+
them with today's soul, workspace, settings or bindings file.
|
|
133
|
+
- `responsibleHuman: null` means messaging was explicitly disabled. Missing is
|
|
134
|
+
unknown, not disabled.
|
|
135
|
+
- New captured source schedules are definition v2 with `capture`, explicit
|
|
136
|
+
deployment/resolution selectors and saved `--json`; they do not use `--soul`.
|
|
137
|
+
- Captured `setup`, `init`, `migrate`, and `unlock` are deliberate refusals.
|
|
138
|
+
Provisioning/migration remains a separate explicit operator path.
|
|
139
|
+
|
|
140
|
+
The broker-owned `binding-normalize`, `binding-bind`, and `binding-check`
|
|
141
|
+
manifest commands are not manual recipes. Do not invoke them from a working
|
|
142
|
+
agent or copy transient `OATS_BINDING_FILE`/`OATS_SOURCE_RECEIPT_FILE` paths.
|
|
143
|
+
Those private mode-0600 files exist only for one synchronous captured invocation.
|
|
144
|
+
|
|
145
|
+
## External bases and native tools
|
|
146
|
+
|
|
147
|
+
A harvester stages writes with native file tools only under the roots listed
|
|
148
|
+
in work/staging.json. A maintainer amends a PR branch in its own checkout.
|
|
149
|
+
Either way, validate the WHOLE base, not an isolated node: absolute Markdown
|
|
150
|
+
links can cross node boundaries. The harvester's completion command validates
|
|
151
|
+
again and refuses any errors or producer warnings.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* okf-validate.mjs — OKF v0.1 bundle validator (no dependencies).
|
|
4
|
+
*
|
|
5
|
+
* Usage: node okf-validate.mjs <bundle-dir> [--strict] [--json]
|
|
6
|
+
*
|
|
7
|
+
* Conformance (errors): frontmatter parses; non-empty `type` on concepts;
|
|
8
|
+
* reserved files (index.md, log.md) carry no `type`.
|
|
9
|
+
* Producer lints (--strict, warnings): broken intra-bundle links (log.md exempt),
|
|
10
|
+
* links missing .md, concepts unreachable from any index.md, missing title/description.
|
|
11
|
+
* Exit: 0 conformant, 1 errors (or warnings with --strict), 2 usage.
|
|
12
|
+
*/
|
|
13
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
14
|
+
import { join, relative, resolve, dirname, posix } from "node:path";
|
|
15
|
+
|
|
16
|
+
const args = process.argv.slice(2);
|
|
17
|
+
const strict = args.includes("--strict");
|
|
18
|
+
const asJson = args.includes("--json");
|
|
19
|
+
const dir = args.find((a) => !a.startsWith("--"));
|
|
20
|
+
if (!dir || !existsSync(dir)) { console.error("usage: okf-validate.mjs <bundle-dir> [--strict] [--json]"); process.exit(2); }
|
|
21
|
+
const root = resolve(dir);
|
|
22
|
+
|
|
23
|
+
const files = [];
|
|
24
|
+
(function walk(d) {
|
|
25
|
+
for (const e of readdirSync(d, { withFileTypes: true })) {
|
|
26
|
+
if (e.name.startsWith(".")) continue;
|
|
27
|
+
const p = join(d, e.name);
|
|
28
|
+
if (e.isDirectory()) walk(p);
|
|
29
|
+
else if (e.name.endsWith(".md")) files.push(p);
|
|
30
|
+
}
|
|
31
|
+
})(root);
|
|
32
|
+
|
|
33
|
+
const errors = [], warnings = [];
|
|
34
|
+
const rel = (p) => relative(root, p).split("\\").join("/");
|
|
35
|
+
const isReserved = (p) => ["index.md", "log.md"].includes(posix.basename(rel(p)));
|
|
36
|
+
|
|
37
|
+
function parseFrontmatter(text) {
|
|
38
|
+
if (!text.startsWith("---")) return { present: false };
|
|
39
|
+
const m = text.match(/^---\r?\n([\s\S]*?)\r?\n---(\r?\n|$)/);
|
|
40
|
+
if (!m) return { present: true, parsed: false };
|
|
41
|
+
const meta = {};
|
|
42
|
+
for (const line of m[1].split("\n")) {
|
|
43
|
+
if (/^\s*#/.test(line) || !line.trim()) continue;
|
|
44
|
+
const kv = line.match(/^([A-Za-z_][A-Za-z0-9_-]*):\s*(.*)$/);
|
|
45
|
+
if (kv) meta[kv[1]] = kv[2].replace(/\s+#.*$/, "").replace(/^["']|["']$/g, "").trim();
|
|
46
|
+
else if (!/^\s+/.test(line)) return { present: true, parsed: false };
|
|
47
|
+
}
|
|
48
|
+
return { present: true, parsed: true, meta, body: text.slice(m[0].length) };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const concepts = new Map(); // rel path -> { meta, body }
|
|
52
|
+
for (const f of files) {
|
|
53
|
+
const r = rel(f);
|
|
54
|
+
const text = readFileSync(f, "utf8");
|
|
55
|
+
const fm = parseFrontmatter(text);
|
|
56
|
+
if (isReserved(f)) {
|
|
57
|
+
if (fm.present && fm.parsed && fm.meta.type) errors.push(`${r}: reserved file must not carry a 'type'`);
|
|
58
|
+
if (fm.present && fm.parsed && posix.basename(r) === "index.md" && r !== "index.md") {
|
|
59
|
+
const keys = Object.keys(fm.meta);
|
|
60
|
+
if (keys.some((k) => k !== "okf_version")) warnings.push(`${r}: only the bundle-root index.md may carry frontmatter`);
|
|
61
|
+
}
|
|
62
|
+
concepts.set(r, { reserved: true, body: fm.parsed ? fm.body : text });
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (!fm.present) { errors.push(`${r}: missing YAML frontmatter`); continue; }
|
|
66
|
+
if (!fm.parsed) { errors.push(`${r}: unparseable YAML frontmatter`); continue; }
|
|
67
|
+
if (!fm.meta.type) errors.push(`${r}: missing or empty required field 'type'`);
|
|
68
|
+
if (strict) {
|
|
69
|
+
if (!fm.meta.title) warnings.push(`${r}: missing recommended field 'title'`);
|
|
70
|
+
if (!fm.meta.description) warnings.push(`${r}: missing recommended field 'description'`);
|
|
71
|
+
}
|
|
72
|
+
concepts.set(r, { meta: fm.meta, body: fm.body });
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (strict) {
|
|
76
|
+
// Link checks (log.md bodies exempt) + reachability from index files.
|
|
77
|
+
const reachable = new Set();
|
|
78
|
+
const linkRe = /\[[^\]]*\]\(([^)\s]+)\)/g;
|
|
79
|
+
const resolveLink = (fromRel, target) => {
|
|
80
|
+
if (/^[a-z]+:\/\//i.test(target) || target.startsWith("mailto:")) return null; // external
|
|
81
|
+
const clean = target.split("#")[0];
|
|
82
|
+
if (!clean) return null;
|
|
83
|
+
const abs = clean.startsWith("/")
|
|
84
|
+
? posix.normalize(clean.slice(1))
|
|
85
|
+
: posix.normalize(posix.join(posix.dirname(fromRel), clean));
|
|
86
|
+
return abs;
|
|
87
|
+
};
|
|
88
|
+
for (const [r, c] of concepts) {
|
|
89
|
+
const body = c.body ?? "";
|
|
90
|
+
const fromLog = posix.basename(r) === "log.md";
|
|
91
|
+
for (const m of body.matchAll(linkRe)) {
|
|
92
|
+
const t = resolveLink(r, m[1]);
|
|
93
|
+
if (t === null) continue;
|
|
94
|
+
const isDir = t.endsWith("/") || concepts.has(posix.join(t, "index.md")) || existsSync(join(root, t)) && statSync(join(root, t)).isDirectory?.();
|
|
95
|
+
if (posix.basename(r) === "index.md" || !fromLog) {
|
|
96
|
+
if (!t.endsWith(".md") && !isDir) { if (!fromLog) warnings.push(`${r}: link missing .md extension: ${m[1]}`); continue; }
|
|
97
|
+
}
|
|
98
|
+
if (fromLog) continue; // history exempt from broken-link lint
|
|
99
|
+
if (t.endsWith(".md") && !concepts.has(t)) warnings.push(`${r}: broken link: ${m[1]}`);
|
|
100
|
+
if (posix.basename(r) === "index.md" && t.endsWith(".md") && concepts.has(t)) reachable.add(t);
|
|
101
|
+
if (posix.basename(r) === "index.md" && isDir) reachable.add(posix.join(t.replace(/\/$/, ""), "index.md"));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Reachability: walk index closure (an index that lists a subdir makes that subdir's index reachable).
|
|
105
|
+
for (const [r, c] of concepts) {
|
|
106
|
+
if (c.reserved || reachable.has(r)) continue;
|
|
107
|
+
// root-level concepts listed in root index handled above; report the rest
|
|
108
|
+
const anyIndex = [...concepts.keys()].some((k) => posix.basename(k) === "index.md");
|
|
109
|
+
if (anyIndex) warnings.push(`${r}: unreachable from any index.md`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const conceptCount = [...concepts.values()].filter((c) => !c.reserved).length;
|
|
114
|
+
if (asJson) {
|
|
115
|
+
console.log(JSON.stringify({ bundle: root, concepts: conceptCount, errors, warnings, conformant: errors.length === 0 }, null, 2));
|
|
116
|
+
} else {
|
|
117
|
+
console.log(`OKF validate — ${root}`);
|
|
118
|
+
console.log(` ${conceptCount} concept(s), ${errors.length} error(s), ${warnings.length} warning(s)`);
|
|
119
|
+
for (const e of errors) console.log(` ERROR ${e}`);
|
|
120
|
+
for (const w of warnings) console.log(` warn ${w}`);
|
|
121
|
+
console.log(errors.length === 0 ? (strict && warnings.length ? "PASS (with lints)" : "PASS — conformant") : "FAIL — nonconformant");
|
|
122
|
+
}
|
|
123
|
+
process.exit(errors.length > 0 ? 1 : strict && warnings.length > 0 && process.env.OKF_STRICT_EXIT ? 1 : 0);
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: okf-trigger-setup
|
|
3
|
+
description: >-
|
|
4
|
+
Set up and verify the OKF harvest-review trigger, which spawns a knowledge
|
|
5
|
+
maintainer for each harvest PR: declare it as a workspace automation
|
|
6
|
+
(`oats-triggers/okf-harvest-review.yaml` in a member repo, `from:
|
|
7
|
+
oats.okf:harvest-review`, with `runsOn` naming the one host and `owner` the
|
|
8
|
+
GitHub account that can merge on the knowledge-base repo), or locally with
|
|
9
|
+
`oats trigger add` for a machine-private setup. Covers the okf team, the
|
|
10
|
+
self-approval limit and `oats trigger test`. Use when setting up knowledge
|
|
11
|
+
operations, when harvest PRs are not being reviewed, when moving the
|
|
12
|
+
reviewer to another host, or when asked whether a host may run the
|
|
13
|
+
maintainer.
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Setting up the harvest-review trigger
|
|
17
|
+
|
|
18
|
+
The trigger makes a harvest PR get reviewed: when a PR labelled `okf-harvest`
|
|
19
|
+
opens on the knowledge-base (KB) repository, the host tick spawns a new
|
|
20
|
+
`oats.okf/knowledge-maintainer` in the `okf` team to review it. It runs on one
|
|
21
|
+
machine, acting as one GitHub account, and that account must be able to
|
|
22
|
+
**merge** on the KB repository.
|
|
23
|
+
|
|
24
|
+
## 1. Choose the host and the account
|
|
25
|
+
|
|
26
|
+
- **The account (`owner`)** must be able to merge on the KB repository (push,
|
|
27
|
+
maintain or admin). Check from the host:
|
|
28
|
+
`gh api repos/<owner>/<repo> --jq .permissions`.
|
|
29
|
+
- **The host (`runsOn`)** is the one machine that runs the trigger, named by
|
|
30
|
+
its `oats-local.yaml` `host: { name: <slug> }`, and logged in with `gh` as
|
|
31
|
+
that account.
|
|
32
|
+
- **The harvest switch is independent**: a review host need not harvest (its
|
|
33
|
+
`harvest` setting can stay `off`), and a harvesting host need not review.
|
|
34
|
+
|
|
35
|
+
## 2. The self-approval limit
|
|
36
|
+
|
|
37
|
+
GitHub forbids approving your own account's PR. If the harvester's host and
|
|
38
|
+
the reviewer's account are the same GitHub account, then either:
|
|
39
|
+
- the KB repository's `main` must not require approving reviews (merge
|
|
40
|
+
permission is enough; the maintainer records its verdict as a PR comment,
|
|
41
|
+
not an approval), or
|
|
42
|
+
- the trigger's `owner` is a separate reviewer account or machine user.
|
|
43
|
+
|
|
44
|
+
Say which one applies when you report the setup.
|
|
45
|
+
|
|
46
|
+
## 3. The okf team
|
|
47
|
+
|
|
48
|
+
The package souls carry `team: okf`. The workspace must declare it, with its
|
|
49
|
+
messaging mapping. Messaging is aweb (`oats.aweb`, the workspace default);
|
|
50
|
+
it needs oats.aweb 1.15.0 or later, which honours the `join=okf` the
|
|
51
|
+
harvester spawn and the trigger's `teams: [okf]` pass:
|
|
52
|
+
|
|
53
|
+
```yaml
|
|
54
|
+
# oats-workspace.yaml
|
|
55
|
+
teams:
|
|
56
|
+
okf: { description: Knowledge operations }
|
|
57
|
+
defaults:
|
|
58
|
+
messaging: { oats.aweb: { from: package } }
|
|
59
|
+
messaging:
|
|
60
|
+
byTeam:
|
|
61
|
+
okf: { team: aweb:<your-org>.okf }
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
Another messaging provider works the same way if it honours `join`.
|
|
65
|
+
|
|
66
|
+
Without it, the souls list with `E_TEAM_UNKNOWN`, and the harvester and the
|
|
67
|
+
maintainer cannot message each other.
|
|
68
|
+
|
|
69
|
+
## 4. Declare it in the workspace (the default)
|
|
70
|
+
|
|
71
|
+
A team relies on the review, so declare it in Git, in a member repository (the
|
|
72
|
+
workspace's host repo), in its `oats-triggers/` folder:
|
|
73
|
+
|
|
74
|
+
```yaml
|
|
75
|
+
# <member>/oats-triggers/okf-harvest-review.yaml
|
|
76
|
+
kind: oats-trigger
|
|
77
|
+
schemaVersion: 1
|
|
78
|
+
description: Review every OKF harvest PR on the knowledge base
|
|
79
|
+
from: oats.okf:harvest-review
|
|
80
|
+
set: { repo: github.com/<owner>/<kb-repo> } # optional: base, harness, model
|
|
81
|
+
runsOn: <host.name of the one machine>
|
|
82
|
+
owner: github.com/<the merge-capable account>
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
- `kind: oats-trigger` and `schemaVersion: 1` make the file self-describing; a
|
|
86
|
+
wrong kind is `E_AUTOMATION_SCHEMA`.
|
|
87
|
+
- The id is `id:` if present, else the filename stem (`okf-harvest-review`).
|
|
88
|
+
Two files with the same id in one member are `E_AUTOMATION_DUPLICATE`.
|
|
89
|
+
- A file named `*.oats-trigger.yaml` anywhere in the member works too (never
|
|
90
|
+
under `oats-package/`, `.git/` or `node_modules/`); `oats-triggers/` is the
|
|
91
|
+
canonical place.
|
|
92
|
+
|
|
93
|
+
Or let the CLI write it: `oats trigger add --from oats.okf:harvest-review
|
|
94
|
+
--set repo=github.com/<owner>/<kb-repo> --workspace <member>` (it prints the
|
|
95
|
+
file when that repository is not the current checkout). Commit and merge it
|
|
96
|
+
like any other change.
|
|
97
|
+
|
|
98
|
+
A host runs it only when **both** its `host.name` equals `runsOn` **and** its
|
|
99
|
+
`gh` account equals `owner`. Everywhere else it is listed with the reason:
|
|
100
|
+
`assigned-elsewhere`, `owner-mismatch` or `host-unnamed`. That keeps exactly
|
|
101
|
+
one machine on it, and the operator's consent explicit. A host can opt out
|
|
102
|
+
without a commit: `automations.disabled: [<member>/okf-harvest-review]` in its
|
|
103
|
+
`oats-local.yaml`. Changes reach the host within about ten minutes (after
|
|
104
|
+
`oats sync`, or the tick's refresh).
|
|
105
|
+
|
|
106
|
+
## 5. Or add it locally (machine-private)
|
|
107
|
+
|
|
108
|
+
For a personal or experimental setup, add it to this deployment only. It runs
|
|
109
|
+
on this host with this host's `gh`, with no `runsOn` or `owner`:
|
|
110
|
+
|
|
111
|
+
```sh
|
|
112
|
+
oats trigger add --from oats.okf:harvest-review --set repo=github.com/<owner>/<kb-repo>
|
|
113
|
+
# optional: --set base=main --set harness=claude --set model=opus --id okf-harvest-review
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
Never run the same review from two places: one workspace declaration, or one
|
|
117
|
+
local trigger on one host.
|
|
118
|
+
|
|
119
|
+
## 6. Test it on the host that runs it
|
|
120
|
+
|
|
121
|
+
```sh
|
|
122
|
+
oats trigger test <member>/okf-harvest-review # or: oats trigger test okf-harvest-review (local)
|
|
123
|
+
oats schedule host install # the host timer, if the test says it is missing
|
|
124
|
+
oats trigger status
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
- `oats trigger test` checks gh auth and where its credential comes from, the
|
|
128
|
+
repository and your merge permissions, the soul, its messaging capability,
|
|
129
|
+
the okf team, the host/owner match, and what would fire now. It spawns
|
|
130
|
+
nothing. It must pass before you report the setup done; fix what it names.
|
|
131
|
+
- **Credentials reach the tick through the host timer, not your shell.** A
|
|
132
|
+
`GH_TOKEN` exported in your shell does not reach it; `gh auth login` with the
|
|
133
|
+
keyring or config file does.
|
|
134
|
+
- Pause with `oats trigger disable <id>`; `remove` leaves running maintainers
|
|
135
|
+
alone.
|
|
136
|
+
|
|
137
|
+
## 7. Labels
|
|
138
|
+
|
|
139
|
+
Harvest PRs carry `okf-harvest` (the harvester's completion creates the label
|
|
140
|
+
if the repository lacks it). The maintainer adds `okf-needs-human` when a PR
|
|
141
|
+
would supersede a human-accepted decision. To pre-create both:
|
|
142
|
+
|
|
143
|
+
```sh
|
|
144
|
+
gh label create okf-harvest --repo <owner>/<repo> --force --color 0E8A16 --description "OKF harvest PR (oats.okf)"
|
|
145
|
+
gh label create okf-needs-human --repo <owner>/<repo> --force --color D93F0B --description "OKF: needs a human decision"
|
|
146
|
+
```
|
|
@@ -11,8 +11,9 @@ oats spawn reviewer --work attached --work-dir "$PWD/work" \
|
|
|
11
11
|
--task "Review commit <sha> on branch <branch>. Report to <your-instance> per your operating loop."
|
|
12
12
|
```
|
|
13
13
|
|
|
14
|
-
-
|
|
15
|
-
|
|
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
|
|
16
17
|
and automatically makes the reviewer your child (attached agents are always
|
|
17
18
|
children of the work-tree owner — no relation flags needed or allowed).
|
|
18
19
|
- The reviewer reviews **that commit's diff only** and reports its verdict
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"capability": "oats.review",
|
|
3
3
|
"private": true,
|
|
4
|
-
"version": "1.
|
|
5
|
-
"compatibility": { "oats": ">=0.
|
|
6
|
-
"description": "Post-commit review discipline: a fresh reviewer
|
|
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
7
|
"requires": [],
|
|
8
|
-
"agents": ["agents/reviewer"],
|
|
9
8
|
"skills": ["skills/code-review", "skills/security-review"],
|
|
10
9
|
"inject": "injects/review.md"
|
|
11
10
|
}
|
package/docs/capabilities.md
CHANGED
|
@@ -72,7 +72,7 @@ A self-contained package has an `oats.json`:
|
|
|
72
72
|
- `compatibility.oats` is the kernel range the capability runs on. The kernel
|
|
73
73
|
refuses to compose a capability whose range does not admit it
|
|
74
74
|
(`E_CAPABILITY_INCOMPATIBLE`, naming capability, range and kernel) wherever a
|
|
75
|
-
soul
|
|
75
|
+
soul resolves (spawn, `spawn --preview`, `inspect
|
|
76
76
|
--soul`, operator commands). `oats inspect` shows each module's
|
|
77
77
|
`compatibility: { ok, range, kernel }`; a home whose spawned module no longer
|
|
78
78
|
admits the running kernel reports a `capability-incompatible` problem.
|
|
@@ -302,6 +302,19 @@ workspace's decision to trust it. A soul names a package capability with
|
|
|
302
302
|
packages is in [packages.md](packages.md). There is no installed
|
|
303
303
|
copy at a deployment and no `oats install`/`trust`/`update`/`remove`.
|
|
304
304
|
|
|
305
|
+
One package can carry capabilities meant for **different souls**. oats.okf
|
|
306
|
+
4.0.0 ships three:
|
|
307
|
+
|
|
308
|
+
- `oats.okf` fills every working soul's knowledge slot;
|
|
309
|
+
- `oats.okf-harvest` is composed only into its harvester soul;
|
|
310
|
+
- `oats.okf-maintenance` is composed only into its maintainer soul.
|
|
311
|
+
|
|
312
|
+
Each is its own manifest with its own skills, inject and commands. One pin
|
|
313
|
+
versions all three, together with the package's souls
|
|
314
|
+
([knowledge.md](knowledge.md#who-gets-which-okf-skills)). Split a package this
|
|
315
|
+
way when roles need different instructions: a soul composes only the
|
|
316
|
+
capability it names, so no role carries another's procedure.
|
|
317
|
+
|
|
305
318
|
## Member capabilities
|
|
306
319
|
|
|
307
320
|
A capability at `<member repo>/capabilities/<name>/oats.json` is discoverable
|
|
@@ -314,13 +327,24 @@ listed (`private: true`, marked "(repo-owned)"), but usable only from its own
|
|
|
314
327
|
repo (`E_CAPABILITY_PRIVATE` elsewhere). A member's `oats-package/` is **not** a member capability: it is
|
|
315
328
|
reported as `publishes` and consumed only as a package.
|
|
316
329
|
|
|
317
|
-
##
|
|
330
|
+
## Agents a capability needs
|
|
331
|
+
|
|
332
|
+
A capability declares no agents: `agents:` in a manifest was **removed in
|
|
333
|
+
0.29.0**. Resolution refuses a module that still declares it with
|
|
334
|
+
`E_CAPABILITY_AGENTS_REMOVED { capability, agents }` (spawn, `spawn --preview`,
|
|
335
|
+
`inspect --soul`, operator commands). Ship the agent as a soul instead:
|
|
336
|
+
|
|
337
|
+
- a **package soul**: `souls/<name>/` beside the package's capabilities, listed
|
|
338
|
+
in `oats-package.json` `souls:`, spawned as `oats spawn <package>/<name>`
|
|
339
|
+
(or the bare name when unique), reading the package's capabilities with
|
|
340
|
+
`from: here` ([packages.md](packages.md#package-souls)). The post-commit
|
|
341
|
+
`reviewer` is one: oats.dev 1.1.0's `oats.dev/reviewer`, beside `oats.review`;
|
|
342
|
+
- or a **member soul**: `souls/<name>/` in a member repository, using a
|
|
343
|
+
member capability `from: here`.
|
|
318
344
|
|
|
319
|
-
A
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
expert soul is an ordinary `souls/<name>-expert/` in the member; the classic
|
|
323
|
-
lookup still exists for 0.24 layouts.)*
|
|
345
|
+
A home an earlier kernel spawned from a manifest `agents:` soul (its agent
|
|
346
|
+
directory holds only `instances/`) is still listed by `oats status`, may anchor
|
|
347
|
+
a `--parent`, and retires; nothing creates one any more.
|
|
324
348
|
|
|
325
349
|
## Commands and hooks
|
|
326
350
|
|
|
@@ -335,7 +359,14 @@ existing manifests load, and change nothing.
|
|
|
335
359
|
|
|
336
360
|
Hooks receive `OATS_EVENT`, `OATS_CAPABILITY`, `OATS_LAYER`, `OATS_INSTANCE`,
|
|
337
361
|
`OATS_HOME`, `OATS_AGENT`, `OATS_SOUL`, `OATS_CONTEXT`, `OATS_WORKSPACE`,
|
|
338
|
-
`OATS_ROOT`, `OATS_LEVEL`, `OATS_SETTINGS`,
|
|
362
|
+
`OATS_ROOT`, `OATS_LEVEL`, `OATS_SETTINGS`, `OATS_SETTINGS_ORIGINS`, and
|
|
363
|
+
`OATS_META`. `OATS_SETTINGS_ORIGINS` (0.29.0) says where each leaf of
|
|
364
|
+
`OATS_SETTINGS` came from: a JSON object from a JSON pointer to `{ kind, at }`,
|
|
365
|
+
`kind` being `manifest-default`, `workspace`, `soul`, `host`, `spawn` or
|
|
366
|
+
`anchor` (the last layer that set it), e.g.
|
|
367
|
+
`{"/harvest":{"kind":"soul","at":"soul.yaml#/knowledge"}}`. A provider tells a
|
|
368
|
+
soul-set value from a host-set one there, and never reads `soul.yaml` for it.
|
|
369
|
+
A home spawned before 0.29.0 recorded none: `{}`. A final JSON line may
|
|
339
370
|
return `meta`, `brief`, `warning`, or harness-specific `launch` arguments. A
|
|
340
371
|
**spawn hook only** may also return an `env` object for the launched process;
|
|
341
372
|
returning `env` from retire or soul-scaffold is an explicit contract error.
|
|
@@ -482,7 +513,8 @@ passed as arguments; no shell is involved.
|
|
|
482
513
|
- Every ambient `OATS_*`, `OAS_*` and `PI_*` variable is removed. Other
|
|
483
514
|
variables pass through.
|
|
484
515
|
- The kernel sets:
|
|
485
|
-
- `OATS_CAPABILITY
|
|
516
|
+
- `OATS_CAPABILITY`, `OATS_SETTINGS` (the payload as JSON) and
|
|
517
|
+
`OATS_SETTINGS_ORIGINS` (where each leaf came from, as hooks get it);
|
|
486
518
|
- `OATS_CLI_BIN`;
|
|
487
519
|
- `OATS_WORKSPACE` (the deployment);
|
|
488
520
|
- the team variables `OATS_TEAM_ID` (the messaging payload's `team`: the
|
|
@@ -262,13 +262,6 @@
|
|
|
262
262
|
},
|
|
263
263
|
"additionalProperties": false
|
|
264
264
|
},
|
|
265
|
-
"agents": {
|
|
266
|
-
"type": "array",
|
|
267
|
-
"items": {
|
|
268
|
-
"type": "string"
|
|
269
|
-
},
|
|
270
|
-
"description": "Package-relative soul directories (soul.yaml + AGENTS.md) \u2014 capability-defined agents; they resolve where the capability is active, souls stay read-only in the package, instances home under <deployment>/agents/<agent>/instances/."
|
|
271
|
-
},
|
|
272
265
|
"settings": {
|
|
273
266
|
"type": "object",
|
|
274
267
|
"description": "Declared capability settings: name to { default, values?, description }. Documentation for the values a soul, oats-local.yaml or `oats spawn --provider` supplies; undeclared settings are still accepted.",
|
|
@@ -35,6 +35,17 @@ pushes. Agreed by both on 2026-09-24:
|
|
|
35
35
|
published skills); merges into the other's lane; reverts, force-anything,
|
|
36
36
|
branch or tag deletion. A blocking intent unanswered for about 45 minutes goes
|
|
37
37
|
to the human, never to action.
|
|
38
|
+
- **Amendment (the human, 2026-09-26 ~14:00Z): "do the merge then fix if something broke".**
|
|
39
|
+
- A PR the lead has reviewed and approved is **merged immediately** by the lead (`gh pr merge --squash --match-head-commit <approved oid>`), without waiting for PR CI. CI runs on main after the merge.
|
|
40
|
+
- A red main is fixed forward at once, by the author or the lead, before anything else merges.
|
|
41
|
+
- **Tags still wait for main CI green on their exact SHA** (a tag never moves).
|
|
42
|
+
- Developers run only the affected suites locally (the nested globs included). The full glob and `smoke:tarball` run in CI, sharded ×6.
|
|
43
|
+
- The watcher no longer relays merges.
|
|
44
|
+
- **Amendment (the human, 2026-09-26 ~13:10Z): "lets approve and tag ourselves all of these PRs".**
|
|
45
|
+
- With the co-lead unresponsive since ~10:30Z, the lead's review + green CI is the full Class B gate, in BOTH lanes: merges, okf/aweb tags, catalog pins, releases.
|
|
46
|
+
- Every such act is logged, with its head/tag oid, in a running account mailed to the co-lead for after-the-fact review.
|
|
47
|
+
- The co-lead's objections then go to the human, and a revert follows only on the human's word.
|
|
48
|
+
- This ends when the co-lead resumes and the lead records that here.
|
|
38
49
|
- Every PR is reviewed by the co-lead who did not author it (a helper's PR is
|
|
39
50
|
reviewed by its own lead, inside that lead's lane). A disagreement not settled
|
|
40
51
|
in two mails goes to the human. Standing rules unchanged: PR CI is the full
|