@awebai/oats 0.22.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/LICENSE +21 -0
- package/README.md +380 -0
- package/bin/oats.mjs +3294 -0
- package/capabilities/oats-authoring/oats.json +12 -0
- package/capabilities/oats-aweb/bin/oats-aweb.mjs +297 -0
- package/capabilities/oats-aweb/injects/aweb.md +55 -0
- package/capabilities/oats-aweb/oats.json +47 -0
- package/capabilities/oats-aweb/package.json +20 -0
- package/capabilities/oats-jira/bin/oats-jira.mjs +40 -0
- package/capabilities/oats-jira/injects/jira.md +10 -0
- package/capabilities/oats-jira/oats.json +22 -0
- package/capabilities/oats-jira/package.json +25 -0
- package/capabilities/oats-jira/skills/jira-tasks/SKILL.md +176 -0
- package/capabilities/oats-linear/README.md +234 -0
- package/capabilities/oats-linear/bin/oats-linear-hook.mjs +32 -0
- package/capabilities/oats-linear/bin/oats-linear.mjs +344 -0
- package/capabilities/oats-linear/injects/linear.md +8 -0
- package/capabilities/oats-linear/oats.json +24 -0
- package/capabilities/oats-linear/package.json +29 -0
- package/capabilities/oats-linear/skills/linear-tasks/SKILL.md +217 -0
- package/capabilities/oats-linear/test/oats-linear.test.mjs +168 -0
- package/capabilities/oats-okf/agents/memory-harvest.md +25 -0
- package/capabilities/oats-okf/bin/oats-okf.mjs +283 -0
- package/capabilities/oats-okf/injects/okf.md +62 -0
- package/capabilities/oats-okf/oats.json +20 -0
- package/capabilities/oats-okf/package.json +22 -0
- package/capabilities/oats-okf/skills/memory-harvest/SKILL.md +100 -0
- package/capabilities/oats-okf/skills/okf/SKILL.md +107 -0
- package/capabilities/oats-okf/skills/okf/scripts/okf-validate.mjs +123 -0
- package/capabilities/oats-review/agents/reviewer/AGENTS.md +53 -0
- package/capabilities/oats-review/agents/reviewer/soul.yaml +6 -0
- package/capabilities/oats-review/injects/review.md +68 -0
- package/capabilities/oats-review/oats.json +10 -0
- package/capabilities/oats-review/skills/code-review/SKILL.md +44 -0
- package/capabilities/oats-review/skills/security-review/SKILL.md +59 -0
- package/docs/capabilities.md +505 -0
- package/docs/capability-manifest.schema.json +223 -0
- package/docs/configuration.md +482 -0
- package/docs/conventions.md +73 -0
- package/docs/design/desktop-ux-plan.md +362 -0
- package/docs/design/package-engine-contract.md +813 -0
- package/docs/design/package-runtime-api.md +414 -0
- package/docs/desktop-cli-api.md +89 -0
- package/docs/desktop-succession.md +51 -0
- package/docs/desktop.md +187 -0
- package/docs/implementation.md +282 -0
- package/docs/integrations.md +123 -0
- package/docs/knowledge-theory.md +97 -0
- package/docs/knowledge.md +139 -0
- package/docs/layers.md +110 -0
- package/docs/migration-from-oas.md +122 -0
- package/docs/oats-config.schema.json +140 -0
- package/docs/oats-lock.schema.json +175 -0
- package/docs/oats-package.schema.json +129 -0
- package/docs/packages.md +442 -0
- package/docs/release-lane.md +132 -0
- package/docs/release-notes/v0.18.2.md +79 -0
- package/docs/release-notes/v0.18.3.md +63 -0
- package/docs/release-notes/v0.18.4.md +65 -0
- package/docs/release-notes/v0.18.5.md +66 -0
- package/docs/release-notes/v0.18.6.md +87 -0
- package/docs/release-notes/v0.19.0.md +186 -0
- package/docs/release-notes/v0.19.1.md +24 -0
- package/docs/release-notes/v0.19.2.md +32 -0
- package/docs/release-notes/v0.19.3.md +16 -0
- package/docs/release-notes/v0.19.4.md +12 -0
- package/docs/release-notes/v0.20.0.md +89 -0
- package/docs/release-notes/v0.22.0.md +180 -0
- package/docs/souls-and-instances.md +344 -0
- package/injects/framework-workspace.md +7 -0
- package/injects/instance-boundary.md +43 -0
- package/injects/local-soul.md +19 -0
- package/injects/oats.md +11 -0
- package/injects/work-attached.md +16 -0
- package/injects/work-checkout.md +12 -0
- package/injects/work-workspace.md +22 -0
- package/injects/work-worktree.md +13 -0
- package/lib/core.mjs +6792 -0
- package/lib/packages.mjs +1375 -0
- package/lib/tmux-config.mjs +35 -0
- package/package-catalog.json +44 -0
- package/package.json +55 -0
- package/packages/record/README.md +151 -0
- package/packages/record/bin/capture.mjs +256 -0
- package/packages/record/bin/recall.mjs +102 -0
- package/packages/record/bin/setup.mjs +281 -0
- package/packages/record/bin/turn-record.mjs +47 -0
- package/packages/record/docs/turn-record-sot.md +350 -0
- package/packages/record/lib/canonical.mjs +151 -0
- package/packages/record/lib/capture-aw.mjs +179 -0
- package/packages/record/lib/capture-cc.mjs +322 -0
- package/packages/record/lib/formats.mjs +296 -0
- package/packages/record/lib/ignore.mjs +155 -0
- package/packages/record/lib/index-db.mjs +524 -0
- package/packages/record/lib/project-aweb.mjs +251 -0
- package/packages/record/lib/segments.mjs +186 -0
- package/packages/record/lib/store.mjs +447 -0
- package/packages/record/lib/tags.mjs +114 -0
- package/packages/record/package.json +45 -0
- package/packages/record/test/vectors/README.md +34 -0
- package/packages/record/test/vectors/aweb-projection-v1.json +231 -0
- package/packages/record/test/vectors/journal-merge-v1.json +261 -0
- package/packages/record/test/vectors/turn-id-v1.json +152 -0
- package/packages/record/test/vectors/validate.mjs +391 -0
- package/skills/integration-authoring/SKILL.md +76 -0
- package/skills/oats/SKILL.md +151 -0
- package/skills/oats-config/SKILL.md +162 -0
- package/skills/oats-getting-started/SKILL.md +159 -0
- package/skills/oats-packages/SKILL.md +180 -0
- package/skills/oats-support/SKILL.md +79 -0
- package/skills/skill-craft/SKILL.md +109 -0
- package/skills/soul-craft/SKILL.md +109 -0
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { spawn } from "node:child_process";
|
|
3
|
+
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { createServer } from "node:http";
|
|
5
|
+
import { tmpdir } from "node:os";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
7
|
+
import test from "node:test";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
const DIR = resolve(fileURLToPath(new URL("..", import.meta.url)));
|
|
11
|
+
const CLI = join(DIR, "bin", "oats-linear.mjs");
|
|
12
|
+
const HOOK = join(DIR, "bin", "oats-linear-hook.mjs");
|
|
13
|
+
|
|
14
|
+
function run(script, args = [], env = {}) {
|
|
15
|
+
return new Promise((done) => {
|
|
16
|
+
const child = spawn(process.execPath, [script, ...args], {
|
|
17
|
+
env: { ...process.env, ...env },
|
|
18
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
19
|
+
});
|
|
20
|
+
let stdout = "";
|
|
21
|
+
let stderr = "";
|
|
22
|
+
child.stdout.on("data", (chunk) => { stdout += chunk; });
|
|
23
|
+
child.stderr.on("data", (chunk) => { stderr += chunk; });
|
|
24
|
+
child.on("close", (code) => done({ code, stdout, stderr }));
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function mockApi(responder, fn) {
|
|
29
|
+
const requests = [];
|
|
30
|
+
const server = createServer(async (request, response) => {
|
|
31
|
+
let body = "";
|
|
32
|
+
for await (const chunk of request) body += chunk;
|
|
33
|
+
const parsed = JSON.parse(body);
|
|
34
|
+
requests.push({ headers: request.headers, ...parsed });
|
|
35
|
+
const payload = await responder(parsed, requests);
|
|
36
|
+
response.writeHead(payload.status || 200, { "content-type": "application/json" });
|
|
37
|
+
response.end(JSON.stringify(payload.body || payload));
|
|
38
|
+
});
|
|
39
|
+
await new Promise((ready) => server.listen(0, "127.0.0.1", ready));
|
|
40
|
+
const url = `http://127.0.0.1:${server.address().port}/graphql`;
|
|
41
|
+
try { await fn({ url, requests }); }
|
|
42
|
+
finally { await new Promise((closed) => server.close(closed)); }
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const team = { id: "team-1", key: "ENG", name: "Engineering" };
|
|
46
|
+
const issue = {
|
|
47
|
+
id: "issue-1", identifier: "ENG-1", title: "Test issue", description: "body",
|
|
48
|
+
url: "https://linear.app/issue/ENG-1", priority: 0, priorityLabel: "No priority",
|
|
49
|
+
team, state: { id: "started-1", name: "In Progress", type: "started" },
|
|
50
|
+
project: null, parent: null, assignee: { id: "human-1", name: "Human", email: "human@example.com" },
|
|
51
|
+
labels: { nodes: [] },
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function operation(query) { return query.match(/(?:query|mutation)\s+(\w+)/)?.[1]; }
|
|
55
|
+
|
|
56
|
+
test("missing API key fails once with setup guidance", async () => {
|
|
57
|
+
const result = await run(CLI, ["auth"], { LINEAR_API_KEY: "" });
|
|
58
|
+
assert.equal(result.code, 1);
|
|
59
|
+
assert.match(result.stderr, /LINEAR_API_KEY is not set/);
|
|
60
|
+
assert.match(result.stderr, /Security & access/);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("auth uses the personal-key Authorization header", async () => {
|
|
64
|
+
await mockApi(() => ({ data: {
|
|
65
|
+
viewer: { id: "user-1", name: "Human", email: "human@example.com" },
|
|
66
|
+
organization: { id: "org-1", name: "Acme", urlKey: "acme" },
|
|
67
|
+
} }), async ({ url, requests }) => {
|
|
68
|
+
const result = await run(CLI, ["auth"], { LINEAR_API_KEY: "secret-key", LINEAR_API_URL: url });
|
|
69
|
+
assert.equal(result.code, 0, result.stderr);
|
|
70
|
+
assert.equal(JSON.parse(result.stdout).authenticated, true);
|
|
71
|
+
assert.equal(requests[0].headers.authorization, "secret-key");
|
|
72
|
+
assert.equal(operation(requests[0].query), "OatsLinearAuth");
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
test("issue list builds an open agent/project filter", async () => {
|
|
77
|
+
await mockApi(({ query }) => {
|
|
78
|
+
if (operation(query) === "OatsLinearTeam") return { data: { teams: { nodes: [team] } } };
|
|
79
|
+
if (operation(query) === "OatsLinearProjects") return { data: { projects: { nodes: [
|
|
80
|
+
{ id: "project-1", name: "Agent Platform", slugId: "agent-platform", status: { id: "ps-1", name: "Started", type: "started" }, teams: { nodes: [team] } },
|
|
81
|
+
] } } };
|
|
82
|
+
if (operation(query) === "OatsLinearIssues") return { data: { issues: { nodes: [issue], pageInfo: { hasNextPage: false, endCursor: null } } } };
|
|
83
|
+
throw new Error(`unexpected operation ${operation(query)}`);
|
|
84
|
+
}, async ({ url, requests }) => {
|
|
85
|
+
const result = await run(CLI, ["issue", "list", "--team", "ENG", "--agent", "worker-1", "--project", "Agent Platform"], {
|
|
86
|
+
LINEAR_API_KEY: "key", LINEAR_API_URL: url,
|
|
87
|
+
});
|
|
88
|
+
assert.equal(result.code, 0, result.stderr);
|
|
89
|
+
const listRequest = requests.find((request) => operation(request.query) === "OatsLinearIssues");
|
|
90
|
+
assert.deepEqual(listRequest.variables.filter, {
|
|
91
|
+
team: { id: { eq: "team-1" } },
|
|
92
|
+
state: { type: { nin: ["completed", "canceled", "duplicate"] } },
|
|
93
|
+
labels: { some: { name: { eqIgnoreCase: "agent-worker-1" } } },
|
|
94
|
+
project: { id: { eq: "project-1" } },
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
test("issue create provisions the agent label and records identity", async () => {
|
|
100
|
+
await mockApi(({ query, variables }) => {
|
|
101
|
+
const op = operation(query);
|
|
102
|
+
if (op === "OatsLinearTeam") return { data: { teams: { nodes: [team] } } };
|
|
103
|
+
if (op === "OatsLinearLabels") return { data: { issueLabels: { nodes: [] } } };
|
|
104
|
+
if (op === "OatsLinearCreateLabel") return { data: { issueLabelCreate: {
|
|
105
|
+
success: true, issueLabel: { id: "label-1", name: variables.input.name, color: "#5E6AD2", team },
|
|
106
|
+
} } };
|
|
107
|
+
if (op === "OatsLinearIssueCreate") return { data: { issueCreate: { success: true, issue } } };
|
|
108
|
+
throw new Error(`unexpected operation ${op}`);
|
|
109
|
+
}, async ({ url, requests }) => {
|
|
110
|
+
const result = await run(CLI, ["issue", "create", "--team", "ENG", "--title", "Bounded work", "--description", "Acceptance", "--agent", "worker-1"], {
|
|
111
|
+
LINEAR_API_KEY: "key", LINEAR_API_URL: url,
|
|
112
|
+
});
|
|
113
|
+
assert.equal(result.code, 0, result.stderr);
|
|
114
|
+
const create = requests.find((request) => operation(request.query) === "OatsLinearIssueCreate");
|
|
115
|
+
assert.equal(create.variables.input.teamId, "team-1");
|
|
116
|
+
assert.deepEqual(create.variables.input.labelIds, ["label-1"]);
|
|
117
|
+
assert.match(create.variables.input.description, /Acceptance\n\n---\nAgent: worker-1/);
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("terminal transitions require explicit authorization", async () => {
|
|
122
|
+
await mockApi(({ query }) => {
|
|
123
|
+
const op = operation(query);
|
|
124
|
+
if (op === "OatsLinearIssue") return { data: { issue } };
|
|
125
|
+
if (op === "OatsLinearStates") return { data: { team: { states: { nodes: [
|
|
126
|
+
{ id: "done-1", name: "Done", type: "completed", position: 1 },
|
|
127
|
+
] } } } };
|
|
128
|
+
throw new Error(`unexpected operation ${op}`);
|
|
129
|
+
}, async ({ url, requests }) => {
|
|
130
|
+
const result = await run(CLI, ["issue", "update", "ENG-1", "--state", "Done"], {
|
|
131
|
+
LINEAR_API_KEY: "key", LINEAR_API_URL: url,
|
|
132
|
+
});
|
|
133
|
+
assert.equal(result.code, 1);
|
|
134
|
+
assert.match(result.stderr, /refusing terminal state/);
|
|
135
|
+
assert.equal(requests.some((request) => operation(request.query) === "OatsLinearIssueUpdate"), false);
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("comment accepts multiline markdown from a file", async () => {
|
|
140
|
+
const temp = mkdtempSync(join(tmpdir(), "oats-linear-test-"));
|
|
141
|
+
const bodyFile = join(temp, "comment.md");
|
|
142
|
+
writeFileSync(bodyFile, "[worker-1] handoff: details\n\n- tests pass\n");
|
|
143
|
+
try {
|
|
144
|
+
await mockApi(({ query }) => {
|
|
145
|
+
assert.equal(operation(query), "OatsLinearComment");
|
|
146
|
+
return { data: { commentCreate: { success: true, comment: { id: "comment-1", body: "ok", createdAt: "2026-07-10", url: "https://linear.app/c/1", user: { id: "user-1", name: "Human" } } } } };
|
|
147
|
+
}, async ({ url, requests }) => {
|
|
148
|
+
const result = await run(CLI, ["issue", "comment", "ENG-1", "--body-file", bodyFile], {
|
|
149
|
+
LINEAR_API_KEY: "key", LINEAR_API_URL: url,
|
|
150
|
+
});
|
|
151
|
+
assert.equal(result.code, 0, result.stderr);
|
|
152
|
+
assert.equal(requests[0].variables.input.body, "[worker-1] handoff: details\n\n- tests pass");
|
|
153
|
+
});
|
|
154
|
+
} finally { rmSync(temp, { recursive: true, force: true }); }
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
test("spawn hook briefs settings and warns without auth", async () => {
|
|
158
|
+
const result = await run(HOOK, ["spawn"], {
|
|
159
|
+
OATS_EVENT: "spawn", OATS_INSTANCE: "worker-1",
|
|
160
|
+
OATS_SETTINGS: JSON.stringify({ team: "ENG", project: "Agent Platform" }),
|
|
161
|
+
LINEAR_API_KEY: "",
|
|
162
|
+
});
|
|
163
|
+
assert.equal(result.code, 0, result.stderr);
|
|
164
|
+
const payload = JSON.parse(result.stdout);
|
|
165
|
+
assert.deepEqual(payload.meta, { label: "agent-worker-1", team: "ENG", project: "Agent Platform" });
|
|
166
|
+
assert.match(payload.brief, /team ENG, default project Agent Platform/);
|
|
167
|
+
assert.match(payload.warning, /LINEAR_API_KEY/);
|
|
168
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# memory-harvest — soul promotion from live instances
|
|
2
|
+
|
|
3
|
+
You are a memory-harvest instance. You were spawned because a live agent
|
|
4
|
+
instance committed work while holding pending notes.
|
|
5
|
+
|
|
6
|
+
**Your briefing (TASK.md) is the authority on your situation**: the source
|
|
7
|
+
notes dir, the soul to update, the work tree you were given, and how your
|
|
8
|
+
promotion is delivered. That last part depends on the source soul's custody —
|
|
9
|
+
a commit on the shared tree, a commit plus a PR from your own worktree, or a
|
|
10
|
+
direct edit with nothing to commit at all. Read it before you plan anything.
|
|
11
|
+
|
|
12
|
+
**You are ephemeral.** Skip all episodic-state upkeep of your own: do not
|
|
13
|
+
maintain STATE.md/log.md, do not write notes/, and never harvest yourself.
|
|
14
|
+
Any memory instructions injected below do not apply to you.
|
|
15
|
+
|
|
16
|
+
Follow the **memory-harvest** skill — **load it before touching any
|
|
17
|
+
note**; it is your entire protocol: judge each
|
|
18
|
+
note (promote / merge / drop), route knowledge vs skills, keep index and log
|
|
19
|
+
discipline, validate, delete processed notes, deliver the way your briefing
|
|
20
|
+
says, then `oats retire <your-instance> --self`.
|
|
21
|
+
|
|
22
|
+
Boundaries: only the soul dirs named in your briefing and the source notes
|
|
23
|
+
files, and nothing else. When your briefing attached you to another instance's
|
|
24
|
+
work tree, that tree belongs to an agent still working in it — one focused
|
|
25
|
+
commit, no other changes, never switch branches.
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* oats-okf — OATS knowledge-integration hooks for OKF (Open Knowledge Format).
|
|
4
|
+
*
|
|
5
|
+
* THE KNOWLEDGE INTEGRATION OWNS ALL MEMORY CONVENTIONS. The kernel knows
|
|
6
|
+
* nothing about STATE.md, log.md, notes/, knowledge bundles, or harvest —
|
|
7
|
+
* agents without a knowledge integration simply have none of this.
|
|
8
|
+
*
|
|
9
|
+
* Events (hook contract):
|
|
10
|
+
* soul-scaffold scaffold the soul's OKF knowledge bundle (idempotent)
|
|
11
|
+
* spawn scaffold instance memory (STATE.md, log.md, notes/) + brief
|
|
12
|
+
* retire no-op (promotion is continuous — see harvest)
|
|
13
|
+
* harvest AGENT-INITIATED (not a kernel hook): run from an instance
|
|
14
|
+
* home (`node <pkg>/capabilities/oats-okf/bin/oats-okf.mjs harvest`)
|
|
15
|
+
* after committing with pending notes — spawns the memory-harvest
|
|
16
|
+
* agent attached to this instance's work tree.
|
|
17
|
+
*
|
|
18
|
+
* Env: OATS_EVENT, OATS_INSTANCE, OATS_HOME, OATS_AGENT, OATS_SOUL (soul dir),
|
|
19
|
+
* OATS_CONTEXT, OATS_WORKSPACE, OATS_SETTINGS ({ "sections-file"? }),
|
|
20
|
+
* OATS_TASK (spawn), OATS_REPO/OATS_BRANCH/OATS_WORK (spawn), OATS_META (retire).
|
|
21
|
+
* Output: JSON { meta, brief, warning } on stdout. Failures warn, never block.
|
|
22
|
+
*/
|
|
23
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, copyFileSync, realpathSync } from "node:fs";
|
|
24
|
+
import { join, isAbsolute, dirname } from "node:path";
|
|
25
|
+
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
26
|
+
import { execSync } from "node:child_process";
|
|
27
|
+
|
|
28
|
+
/** The kernel install root. When this package runs from inside the kernel
|
|
29
|
+
* (marketplace source tree), ../../.. works; when it runs as a copied
|
|
30
|
+
* marketplace install (.agents/capabilities/installed/oats-okf), resolve the
|
|
31
|
+
* kernel through `oats root` — the same mechanism adapters use. */
|
|
32
|
+
const FRAMEWORK_ROOT = (() => {
|
|
33
|
+
const rel = join(dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
|
|
34
|
+
if (existsSync(join(rel, "lib", "core.mjs"))) return rel;
|
|
35
|
+
try {
|
|
36
|
+
const root = execSync("oats root", { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 15000 }).trim();
|
|
37
|
+
if (root && existsSync(join(root, "lib", "core.mjs"))) return root;
|
|
38
|
+
} catch { /* fall through */ }
|
|
39
|
+
return rel; // callers report the missing module with a clear path
|
|
40
|
+
})();
|
|
41
|
+
|
|
42
|
+
const out = (o) => { process.stdout.write(JSON.stringify(o) + "\n"); process.exit(0); };
|
|
43
|
+
const warn = (m) => out({ warning: `oats-okf: ${String(m).slice(0, 300)}` });
|
|
44
|
+
|
|
45
|
+
// Desktop CLI API v1: `oats okf harvest --json` emits EXACTLY ONE envelope
|
|
46
|
+
// object on stdout — {schemaVersion:1,ok,result|error} — and a nonzero exit
|
|
47
|
+
// on failure. Ordinary (non---json) output keeps the hook JSON shape above.
|
|
48
|
+
const JSON_MODE = process.argv.includes("--json");
|
|
49
|
+
const jsonOk = (result) => { process.stdout.write(JSON.stringify({ schemaVersion: 1, ok: true, result }) + "\n"); process.exit(0); };
|
|
50
|
+
const jsonFail = (code, message) => { process.stdout.write(JSON.stringify({ schemaVersion: 1, ok: false, error: { code, message: String(message).slice(0, 300) } }) + "\n"); process.exit(1); };
|
|
51
|
+
|
|
52
|
+
const event = process.env.OATS_EVENT || process.argv[2];
|
|
53
|
+
const instance = process.env.OATS_INSTANCE;
|
|
54
|
+
const home = process.env.OATS_HOME || process.cwd();
|
|
55
|
+
const soulDir = process.env.OATS_SOUL;
|
|
56
|
+
const agentName = process.env.OATS_AGENT || "agent";
|
|
57
|
+
// Fallible init stays inside an error boundary: malformed inherited env must
|
|
58
|
+
// never produce a bare stack trace — in --json mode Desktop expects one
|
|
59
|
+
// envelope object on stdout even for init failures.
|
|
60
|
+
let settings = {};
|
|
61
|
+
try { settings = JSON.parse(process.env.OATS_SETTINGS || "{}"); }
|
|
62
|
+
catch (e) {
|
|
63
|
+
if (JSON_MODE) jsonFail("E_HARVEST_FAILED", `malformed OATS_SETTINGS: ${e.message || e}`);
|
|
64
|
+
process.stderr.write(`oats-okf: malformed OATS_SETTINGS (ignoring): ${e.message || e}\n`);
|
|
65
|
+
}
|
|
66
|
+
/** Model for the memory-harvest agent — promotion judgment is cheap-but-good
|
|
67
|
+
* work; default gpt-5.5, overridable via okf settings { "harvest-model": ... }. */
|
|
68
|
+
const DEFAULT_HARVEST_MODEL = "github-copilot/gpt-5.5";
|
|
69
|
+
|
|
70
|
+
/** Append a one-line entry to an OKF log.md (newest-first, date-grouped per spec §7). */
|
|
71
|
+
function appendLogEntry(logPath, entry, title) {
|
|
72
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
73
|
+
let text = existsSync(logPath) ? readFileSync(logPath, "utf8") : `# ${title}\n\n`;
|
|
74
|
+
const heading = `## ${today}`;
|
|
75
|
+
if (text.includes(heading)) text = text.replace(`${heading}\n`, `${heading}\n* ${entry}\n`);
|
|
76
|
+
else text = text.replace(/^(# [^\n]*\n\n?)/, `$1${heading}\n* ${entry}\n\n`);
|
|
77
|
+
writeFileSync(logPath, text);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** Scaffold the soul's OKF knowledge bundle (idempotent). */
|
|
81
|
+
function scaffoldSoul() {
|
|
82
|
+
if (!soulDir) return false;
|
|
83
|
+
const kb = join(soulDir, "knowledge");
|
|
84
|
+
mkdirSync(kb, { recursive: true });
|
|
85
|
+
const index = join(kb, "index.md");
|
|
86
|
+
if (!existsSync(index)) {
|
|
87
|
+
let seeded = "";
|
|
88
|
+
const sf = settings["sections-file"];
|
|
89
|
+
if (sf) {
|
|
90
|
+
const abs = isAbsolute(sf) ? sf : join(process.env.OATS_CONTEXT || home, sf);
|
|
91
|
+
if (existsSync(abs)) seeded = readFileSync(abs, "utf8").trim() + "\n";
|
|
92
|
+
}
|
|
93
|
+
writeFileSync(index, `---
|
|
94
|
+
okf_version: "0.1"
|
|
95
|
+
---
|
|
96
|
+
|
|
97
|
+
# ${agentName} knowledge base
|
|
98
|
+
|
|
99
|
+
Curated long-term knowledge for the ${agentName} agent (OKF bundle). Follow links
|
|
100
|
+
selectively — read what the current task needs, not everything.
|
|
101
|
+
|
|
102
|
+
# Sections
|
|
103
|
+
|
|
104
|
+
* [lessons/](lessons/) - durable lessons learned (type: Lesson).
|
|
105
|
+
* [decisions/](decisions/) - decisions and their rationale (type: Decision).
|
|
106
|
+
* [playbooks/](playbooks/) - step-by-step procedures kept as knowledge (type: Playbook).
|
|
107
|
+
* [references/](references/) - internal/external reference material (type: Reference).
|
|
108
|
+
${seeded}
|
|
109
|
+
Grow role-specific sections beyond these as the agent's role demands (e.g.
|
|
110
|
+
architecture/, codebase/) — list them here and log the growth in log.md.
|
|
111
|
+
`);
|
|
112
|
+
}
|
|
113
|
+
const log = join(kb, "log.md");
|
|
114
|
+
if (!existsSync(log)) appendLogEntry(log, "**Initialization**: knowledge bundle scaffolded by oats-okf.", "Knowledge Log");
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
if (event === "soul-scaffold") {
|
|
119
|
+
try { out({ meta: { scaffolded: scaffoldSoul() } }); } catch (e) { warn(e.message || e); }
|
|
120
|
+
} else if (event === "spawn") {
|
|
121
|
+
// Ephemeral CAPABILITY agents (reviewer, memory-harvest) carry no episodic
|
|
122
|
+
// state of their own — no STATE.md/log.md/notes scaffolding, and no
|
|
123
|
+
// session-protocol brief. LOCAL souls are full souls: they get everything.
|
|
124
|
+
// ("tmp" is the legacy spelling of local — treat it as local, with memory.)
|
|
125
|
+
if ((process.env.OATS_KIND || "") === "capability") {
|
|
126
|
+
out({ meta: { memory: "none" }, brief: "Memory: none — you are ephemeral; no STATE.md/log.md/notes upkeep, no harvest." });
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
const task = (process.env.OATS_TASK || "").trim();
|
|
130
|
+
writeFileSync(join(home, "STATE.md"), `---
|
|
131
|
+
type: Instance State
|
|
132
|
+
title: ${instance} working state
|
|
133
|
+
description: Live working state for instance ${instance} — rewritten as work progresses.
|
|
134
|
+
timestamp: ${new Date().toISOString()}
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
# Task
|
|
138
|
+
|
|
139
|
+
${task || "_No task assigned yet — await instructions._"}
|
|
140
|
+
|
|
141
|
+
# Plan
|
|
142
|
+
|
|
143
|
+
_(numbered steps once you have a plan)_
|
|
144
|
+
|
|
145
|
+
# Progress
|
|
146
|
+
|
|
147
|
+
_(what is done — commits, files touched, verified results)_
|
|
148
|
+
|
|
149
|
+
# Next
|
|
150
|
+
|
|
151
|
+
_(the single next action — keep this current; a fresh session on any model resumes from here)_
|
|
152
|
+
|
|
153
|
+
# Context
|
|
154
|
+
|
|
155
|
+
- repo: ${process.env.OATS_REPO || "?"} (branch ${process.env.OATS_BRANCH || "?"}, mode ${process.env.OATS_WORK || "?"})
|
|
156
|
+
- key files/paths: _(fill in as you learn them)_
|
|
157
|
+
`);
|
|
158
|
+
appendLogEntry(join(home, "log.md"),
|
|
159
|
+
`**Creation**: instance ${instance} spawned from soul ${agentName}${task ? ` — task: ${task.split("\n")[0].slice(0, 120)}` : ""}.`,
|
|
160
|
+
"Instance Log");
|
|
161
|
+
mkdirSync(join(home, "notes"), { recursive: true });
|
|
162
|
+
out({
|
|
163
|
+
meta: { memory: "okf" },
|
|
164
|
+
brief: "Memory: your STATE.md/log.md/notes/ are scaffolded — your AGENTS.md's 'Knowledge: OKF' section has the session protocol.",
|
|
165
|
+
});
|
|
166
|
+
} catch (e) { warn(`instance memory scaffold failed: ${e.message || e}`); }
|
|
167
|
+
} else if (event === "harvest") {
|
|
168
|
+
// AGENT-INITIATED HARVEST. An instance that committed with pending notes
|
|
169
|
+
// runs `harvest` from its home: spawn the
|
|
170
|
+
// memory-harvest agent ATTACHED to the same work tree — sibling home, shared
|
|
171
|
+
// tree — to promote notes into the soul, commit, and retire itself.
|
|
172
|
+
// Long-lived sessions thus feed the soul continuously, on the agent's call.
|
|
173
|
+
try {
|
|
174
|
+
// Derive context from the instance home (cwd) when hook env is absent.
|
|
175
|
+
const metaFile = join(home, "instance.json");
|
|
176
|
+
const meta = existsSync(metaFile) ? JSON.parse(readFileSync(metaFile, "utf8")) : {};
|
|
177
|
+
const inst = instance || meta.instance;
|
|
178
|
+
const agName = process.env.OATS_AGENT || meta.agent || "agent";
|
|
179
|
+
const sDir = soulDir || join(home, "soul");
|
|
180
|
+
const context = process.env.OATS_CONTEXT || meta.repo;
|
|
181
|
+
let root = process.env.OATS_ROOT;
|
|
182
|
+
if (!root) { // walk up from home to the agents/ dir
|
|
183
|
+
let d = home;
|
|
184
|
+
while (d !== dirname(d)) { if (d.endsWith("/instances")) { root = join(d, "..", ".."); break; } d = dirname(d); }
|
|
185
|
+
root = root ? realpathSync(join(root)) : undefined;
|
|
186
|
+
// instances live at <root>/<agent>/instances/<inst>, at the legacy nested
|
|
187
|
+
// <root>/{local,tmp}-agents/<agent>/instances/<inst>, or at the sibling
|
|
188
|
+
// <scope>/local-agents/<agent>/instances/<inst> — canonical root is <scope>/agents.
|
|
189
|
+
if (root && ["local-agents", "tmp-agents"].includes(root.split("/").pop())) {
|
|
190
|
+
const parent = dirname(root);
|
|
191
|
+
root = parent.split("/").pop() === "agents" ? parent : join(parent, "agents");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const notesDir = join(home, "notes");
|
|
195
|
+
const skip = (why) => (JSON_MODE ? jsonOk({ harvest: "skipped", reason: why }) : out({ meta: { harvestSpawn: "skipped", why } }));
|
|
196
|
+
if (String(agName).startsWith("memory-harvest")) skip("self (loop guard)");
|
|
197
|
+
const notes = existsSync(notesDir) ? readdirSync(notesDir).filter((f) => f.endsWith(".md")) : [];
|
|
198
|
+
if (notes.length === 0) skip("no pending notes");
|
|
199
|
+
if (!root || (!existsSync(root) && !existsSync(join(dirname(root), "local-agents")))) skip("no agents root found above this home");
|
|
200
|
+
if (!inst) skip("no instance identity (run from an instance home)");
|
|
201
|
+
const core = await import(pathToFileURL(join(FRAMEWORK_ROOT, "lib", "core.mjs")).href);
|
|
202
|
+
const slug = String(inst).toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "").slice(0, 30);
|
|
203
|
+
// Debounce: one harvester per source instance at a time (canonical sibling
|
|
204
|
+
// local-agents/ plus legacy nested locations).
|
|
205
|
+
const harvesterHomes = [
|
|
206
|
+
join(dirname(root), "local-agents", "memory-harvest", "instances", `memory-harvest-${slug}`),
|
|
207
|
+
join(root, "local-agents", "memory-harvest", "instances", `memory-harvest-${slug}`),
|
|
208
|
+
join(root, "tmp-agents", "memory-harvest", "instances", `memory-harvest-${slug}`),
|
|
209
|
+
];
|
|
210
|
+
if (harvesterHomes.some((h) => existsSync(h))) skip("harvester already running for this instance");
|
|
211
|
+
let agentDef = core.findAgent(root, "memory-harvest");
|
|
212
|
+
if (!agentDef) {
|
|
213
|
+
const upsert = core.upsertLocalAgent || core.upsertTmpAgent; // kernel ≥0.18 / older
|
|
214
|
+
upsert(root, { name: "memory-harvest", instructions: readFileSync(join(dirname(fileURLToPath(import.meta.url)), "..", "agents", "memory-harvest.md"), "utf8") });
|
|
215
|
+
agentDef = core.findAgent(root, "memory-harvest");
|
|
216
|
+
}
|
|
217
|
+
// The harvester is service infrastructure: ALWAYS ephemeral, regardless of
|
|
218
|
+
// its on-disk kind (it homes as a local soul so it is uncommitted).
|
|
219
|
+
agentDef = { ...agentDef, kind: "capability" };
|
|
220
|
+
// Harvest model: explicit okf settings win (hook env, or resolved from config
|
|
221
|
+
// when agent-initiated); else the integration's default.
|
|
222
|
+
let harvestModel = settings["harvest-model"];
|
|
223
|
+
if (!harvestModel && context) {
|
|
224
|
+
try { harvestModel = core.resolveOatsConfig(context).layers?.knowledge?.settings?.["harvest-model"]; } catch { /* config unreadable: use default */ }
|
|
225
|
+
}
|
|
226
|
+
harvestModel = harvestModel || DEFAULT_HARVEST_MODEL;
|
|
227
|
+
const workDir = realpathSync(join(home, "work"));
|
|
228
|
+
const realSoul = realpathSync(sDir);
|
|
229
|
+
const harvName = `memory-harvest-${slug}`;
|
|
230
|
+
const gitRootOf = (start) => { let d = start; while (d !== dirname(d)) { if (existsSync(join(d, ".git"))) return d; d = dirname(d); } return undefined; };
|
|
231
|
+
let r;
|
|
232
|
+
const srcKind = process.env.OATS_KIND || meta.kind || "";
|
|
233
|
+
if (["local", "tmp"].includes(srcKind)) {
|
|
234
|
+
// LOCAL soul: uncommitted by contract (local-agents/, gitignored). The
|
|
235
|
+
// harvester judges notes exactly as usual, but the deliverable is DIRECT
|
|
236
|
+
// edits to the canonical soul — no commit, no PR: there is nothing to
|
|
237
|
+
// version. It must not touch the owner's work tree.
|
|
238
|
+
r = core.spawnInstance(root, agentDef, {
|
|
239
|
+
instance: harvName, parent: inst,
|
|
240
|
+
repo: context, work: "attached", workDir, model: harvestModel,
|
|
241
|
+
task: `Harvest the pending notes of live LOCAL-SOUL instance "${inst}" (agent "${agName}") into its soul — by direct edits, no commit.\n\n- Source notes: ${notesDir} (${notes.join(", ")})\n- Soul knowledge bundle to update: ${join(realSoul, "knowledge")}\n- Soul skills dir (for procedure-shaped notes): ${join(realSoul, "skills")}\n- This soul is LOCAL (uncommitted, gitignored): edit those soul files IN PLACE. Do NOT run git commit — not for the soul, and not in ./work (the shared tree belongs to the working instance; leave it untouched).\n- Follow your memory-harvest skill for everything else: promote/merge/drop each note, knowledge vs skill routing, index + log discipline, validate the bundle, DELETE processed notes from the source notes/ dir.\n- Then run \`oats retire ${harvName} --self\`.`,
|
|
242
|
+
});
|
|
243
|
+
} else if ((process.env.OATS_WORK || meta.work) === "workspace") {
|
|
244
|
+
// WORKSPACE-MODE instance: ./work is the whole workspace, not a git repo —
|
|
245
|
+
// the harvester may NOT commit there. The soul lives in its own home repo
|
|
246
|
+
// (committed to the workspace): harvest in a WORKTREE of that repo and
|
|
247
|
+
// deliver the promotion as a PR, never a direct push to its main branch.
|
|
248
|
+
const soulRepo = gitRootOf(realSoul);
|
|
249
|
+
if (!soulRepo) skip("workspace-mode soul is not inside a git repo — nowhere to deliver a PR");
|
|
250
|
+
const relSoul = realSoul.slice(soulRepo.length + 1);
|
|
251
|
+
r = core.spawnInstance(root, agentDef, {
|
|
252
|
+
instance: harvName, parent: inst,
|
|
253
|
+
repo: soulRepo, work: "worktree", branch: `memory-harvest/${slug}`, model: harvestModel,
|
|
254
|
+
task: `Harvest the pending notes of live WORKSPACE-MODE instance "${inst}" (agent "${agName}") into its soul — delivered as a PR.\n\n- Source notes: ${notesDir} (${notes.join(", ")})\n- Your ./work is a dedicated worktree of the soul's home repo (${soulRepo}), branch memory-harvest/${slug}.\n- Soul knowledge bundle to update: ./work/${join(relSoul, "knowledge")}\n- Soul skills dir (for procedure-shaped notes): ./work/${join(relSoul, "skills")}\n- Follow your memory-harvest skill: promote/merge/drop each note, knowledge vs skill routing, index + log discipline, validate the bundle, DELETE processed notes from the source notes/ dir, commit once (prefixed "memory-harvest:").\n- Then push the branch and open a PR (\`git push -u origin memory-harvest/${slug}\` then \`gh pr create --fill\`). Do NOT merge it; the humans/owners of ${soulRepo} review soul changes. If gh is unavailable, push the branch and report the compare URL.\n- Finally run \`oats retire ${harvName} --self\` (keep the branch: --self only).`,
|
|
255
|
+
});
|
|
256
|
+
} else {
|
|
257
|
+
// Repo-resident souls: write to the soul AS SEEN FROM THE WORK TREE, so the
|
|
258
|
+
// promotion commits onto the instance's own branch. Otherwise the canonical soul.
|
|
259
|
+
const realRepo = realpathSync(context || workDir);
|
|
260
|
+
const soulTarget = realSoul.startsWith(realRepo + "/")
|
|
261
|
+
? join(workDir, realSoul.slice(realRepo.length + 1))
|
|
262
|
+
: realSoul;
|
|
263
|
+
r = core.spawnInstance(root, agentDef, {
|
|
264
|
+
instance: harvName, parent: inst,
|
|
265
|
+
repo: context, work: "attached", workDir, model: harvestModel,
|
|
266
|
+
task: `Harvest the pending notes of live instance "${inst}" (agent "${agName}") into its soul.\n\n- Source notes: ${notesDir} (${notes.join(", ")})\n- Soul knowledge bundle to update: ${join(soulTarget, "knowledge")}\n- Soul skills dir (for procedure-shaped notes): ${join(soulTarget, "skills")}\n- You are ATTACHED to the instance's work tree (./work) — commit your promotions there as a single commit, prefixed "memory-harvest:".\n- Follow your memory-harvest skill: promote/merge/drop each note, knowledge vs skill routing, index + log discipline, validate the bundle, DELETE processed notes from the source notes/ dir (so they are not re-harvested), commit, then run \`oats retire ${harvName} --self\`.`,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
if (JSON_MODE) jsonOk({ harvest: "spawned", instance: r.instance, window: r.tmux?.window || null });
|
|
270
|
+
out({ meta: { harvestSpawn: r.instance, window: r.tmux?.window } });
|
|
271
|
+
} catch (e) {
|
|
272
|
+
if (JSON_MODE) jsonFail("E_HARVEST_FAILED", `harvest spawn failed (notes are safe on disk): ${e.message || e}`);
|
|
273
|
+
warn(`harvest spawn failed (notes are safe on disk): ${e.message || e}`);
|
|
274
|
+
}
|
|
275
|
+
} else if (event === "retire") {
|
|
276
|
+
// Retirement is intentionally a no-op for knowledge (for now): promotion happens
|
|
277
|
+
// continuously via agent-initiated harvest. Uncommitted notes die with the home —
|
|
278
|
+
// the injection tells instances to bring memory up to date, commit, and harvest
|
|
279
|
+
// before finishing.
|
|
280
|
+
out({ meta: {} });
|
|
281
|
+
} else {
|
|
282
|
+
warn(`unknown event "${event}" (expected soul-scaffold|spawn|retire)`);
|
|
283
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
## Knowledge: OKF
|
|
2
|
+
|
|
3
|
+
Your knowledge layer is **OKF** (Open Knowledge Format). Long-term knowledge
|
|
4
|
+
lives in your soul's OKF bundle (`./soul/knowledge/`, index-first); episodic
|
|
5
|
+
state lives in `STATE.md`/`log.md`/`notes/`.
|
|
6
|
+
|
|
7
|
+
**Before working — every session, no exceptions:**
|
|
8
|
+
|
|
9
|
+
1. **Load the okf skill.** It is the protocol for both reading and writing
|
|
10
|
+
your knowledge — do not work your bundle from memory.
|
|
11
|
+
2. Read `./STATE.md` and recent `./log.md` — if STATE.md has a plan/progress
|
|
12
|
+
you are resuming, continue from its `# Next`.
|
|
13
|
+
3. **Check your knowledge for the task at hand**: open
|
|
14
|
+
`./soul/knowledge/index.md` and follow the links relevant to what you are
|
|
15
|
+
about to do — index-first and selective (frontmatter `type`/`tags`/
|
|
16
|
+
`description` filters what to open; never bulk-read). Prior decisions,
|
|
17
|
+
lessons, and playbooks are binding context — re-deriving what the soul
|
|
18
|
+
already knows is a bug. Repeat this check before each new non-trivial
|
|
19
|
+
task, not just at session start.
|
|
20
|
+
|
|
21
|
+
Keep STATE.md current as you work (the test: could a fresh session resume
|
|
22
|
+
from files alone? its `# Next` names the single next action). Append dated
|
|
23
|
+
milestones to `./log.md` (newest first).
|
|
24
|
+
|
|
25
|
+
**Write down what you learn.** Anything you figured out that was not obvious
|
|
26
|
+
— a gotcha, a decision and its why, a procedure that worked — goes in
|
|
27
|
+
`./notes/`, one OKF concept per insight, as you go. Do not judge whether it
|
|
28
|
+
is "important enough"; that is someone else's job. Just capture it
|
|
29
|
+
faithfully.
|
|
30
|
+
|
|
31
|
+
**Before every commit, bring memory up to date**: STATE.md current, log.md
|
|
32
|
+
milestone appended, fresh insights in `./notes/`.
|
|
33
|
+
|
|
34
|
+
**After committing with pending notes, launch the harvester yourself**: run
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
oats okf harvest
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
from your instance home. It spawns the memory-harvest agent attached to your
|
|
41
|
+
work tree to promote your notes into the soul (it skips cleanly when there
|
|
42
|
+
are no notes or a harvester is already running — calling it "too often" is
|
|
43
|
+
safe; not calling it means your insights never reach the soul, and unwritten
|
|
44
|
+
or unharvested notes are lost when your home is retired).
|
|
45
|
+
|
|
46
|
+
**Workspace-mode instances**: your soul lives in its own home repo, and your
|
|
47
|
+
`./work` (the workspace) is not where it commits. `oats okf harvest` handles
|
|
48
|
+
this — it promotes your notes in a worktree of the soul's home repo and
|
|
49
|
+
delivers the update **as a PR to that repo**, never a direct push and never
|
|
50
|
+
a commit into member repos. Your job is unchanged: write notes, commit
|
|
51
|
+
nothing yourself, call the harvester.
|
|
52
|
+
|
|
53
|
+
**Local-soul instances**: your soul is uncommitted by design (it lives in
|
|
54
|
+
`local-agents/`, gitignored). The harvester edits your soul directly — no
|
|
55
|
+
commit, no PR. Your job is still unchanged: write notes, commit your WORK
|
|
56
|
+
normally, call the harvester.
|
|
57
|
+
|
|
58
|
+
The okf skill you loaded at session start also governs writing: notes,
|
|
59
|
+
concepts, index.md, and log.md follow its format craft (concepts,
|
|
60
|
+
frontmatter, index/log discipline, validation). Re-read the relevant section
|
|
61
|
+
before authoring if you have not written OKF this session — notes written
|
|
62
|
+
from memory tend to fail validation and stall the harvest.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"capability": "oats.okf",
|
|
3
|
+
"command": "okf",
|
|
4
|
+
"version": "1.4.1",
|
|
5
|
+
"compatibility": { "oats": ">=0.6.2" },
|
|
6
|
+
"layer": "knowledge",
|
|
7
|
+
"description": "Knowledge layer via OKF: soul bundles, instance memory (STATE.md/log.md/notes/), continuous post-commit harvest into the soul (commit, PR, or direct-edit for local souls), craft + memory skills, validator.",
|
|
8
|
+
"requires": [],
|
|
9
|
+
"skills": [
|
|
10
|
+
"skills"
|
|
11
|
+
],
|
|
12
|
+
"commands": {
|
|
13
|
+
"harvest": "bin/oats-okf.mjs harvest"
|
|
14
|
+
},
|
|
15
|
+
"inject": "injects/okf.md",
|
|
16
|
+
"hooks": {
|
|
17
|
+
"soul-scaffold": "bin/oats-okf.mjs soul-scaffold",
|
|
18
|
+
"spawn": "bin/oats-okf.mjs spawn"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@awebai/oats-okf",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "OATS knowledge provider: Open Knowledge Format (OKF) craft skill, bundle validator, and agents-md injection",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"oats",
|
|
8
|
+
"okf"
|
|
9
|
+
],
|
|
10
|
+
"repository": {
|
|
11
|
+
"type": "git",
|
|
12
|
+
"url": "https://github.com/awebai/oats",
|
|
13
|
+
"directory": "capabilities/oats-okf"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"pi": {
|
|
18
|
+
"skills": [
|
|
19
|
+
"./skills"
|
|
20
|
+
]
|
|
21
|
+
}
|
|
22
|
+
}
|