owfadev 0.0.0-stage → 0.3.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/.agents/skills/asset-read/SKILL.md +39 -0
- package/.agents/skills/check/SKILL.md +130 -0
- package/.agents/skills/docs/SKILL.md +24 -0
- package/.agents/skills/map/SKILL.md +72 -0
- package/.agents/skills/plugin/SKILL.md +74 -0
- package/.agents/skills/profile/SKILL.md +75 -0
- package/.agents/skills/query/SKILL.md +48 -0
- package/.agents/skills/save/SKILL.md +94 -0
- package/.agents/skills/wiki_plugin_kernel/SKILL.md +104 -0
- package/.meta/VERSION +1 -0
- package/.meta/command/asset-read/SKILL.md +39 -0
- package/.meta/command/check/SKILL.md +130 -0
- package/.meta/command/docs/SKILL.md +24 -0
- package/.meta/command/map/SKILL.md +72 -0
- package/.meta/command/plugin/SKILL.md +74 -0
- package/.meta/command/profile/SKILL.md +75 -0
- package/.meta/command/query/SKILL.md +48 -0
- package/.meta/command/save/SKILL.md +94 -0
- package/.meta/command/wiki_plugin_kernel/SKILL.md +104 -0
- package/.meta/docs/agent-behavior.md +68 -0
- package/.meta/docs/agents-example.md +34 -0
- package/.meta/docs/conversion.md +24 -0
- package/.meta/docs/deployment.md +311 -0
- package/.meta/docs/developer.md +33 -0
- package/.meta/docs/distribution.md +132 -0
- package/.meta/docs/intro.md +56 -0
- package/.meta/docs/log-example.md +19 -0
- package/.meta/docs/map.md +23 -0
- package/.meta/docs/mechanics.md +180 -0
- package/.meta/docs/naming.md +113 -0
- package/.meta/docs/packages.md +130 -0
- package/.meta/docs/quickstart.md +79 -0
- package/.meta/docs/research-landscape.md +132 -0
- package/.meta/docs/research-user-profile.md +27 -0
- package/.meta/docs/sandbox.md +40 -0
- package/.meta/docs/usage.md +45 -0
- package/.meta/plugins/core/calendar/PLUGIN.md +35 -0
- package/.meta/plugins/core/calendar/PLUGIN.yaml +21 -0
- package/.meta/plugins/core/cron/PLUGIN.md +38 -0
- package/.meta/plugins/core/cron/PLUGIN.yaml +24 -0
- package/.meta/plugins/core/device/PLUGIN.md +38 -0
- package/.meta/plugins/core/device/PLUGIN.yaml +21 -0
- package/.meta/plugins/core/device/scripts/check.py +29 -0
- package/.meta/plugins/core/domain/PLUGIN.md +43 -0
- package/.meta/plugins/core/domain/PLUGIN.yaml +8 -0
- package/.meta/plugins/core/hot/PLUGIN.md +40 -0
- package/.meta/plugins/core/hot/PLUGIN.yaml +15 -0
- package/.meta/plugins/core/index/PLUGIN.md +39 -0
- package/.meta/plugins/core/index/PLUGIN.yaml +18 -0
- package/.meta/plugins/core/instance-git/PLUGIN.md +39 -0
- package/.meta/plugins/core/instance-git/PLUGIN.yaml +15 -0
- package/.meta/plugins/core/instance-git/scripts/check.py +90 -0
- package/.meta/plugins/core/language/PLUGIN.md +38 -0
- package/.meta/plugins/core/language/PLUGIN.yaml +23 -0
- package/.meta/plugins/core/language/scripts/check.py +23 -0
- package/.meta/plugins/core/link/PLUGIN.md +39 -0
- package/.meta/plugins/core/link/PLUGIN.yaml +16 -0
- package/.meta/plugins/core/link/scripts/check.py +122 -0
- package/.meta/plugins/core/log/PLUGIN.md +58 -0
- package/.meta/plugins/core/log/PLUGIN.yaml +18 -0
- package/.meta/plugins/core/log/scripts/check.py +21 -0
- package/.meta/plugins/core/mapping/PLUGIN.md +32 -0
- package/.meta/plugins/core/mapping/PLUGIN.yaml +24 -0
- package/.meta/plugins/core/mapping/scripts/check.py +90 -0
- package/.meta/plugins/core/notes/PLUGIN.md +42 -0
- package/.meta/plugins/core/notes/PLUGIN.yaml +19 -0
- package/.meta/plugins/core/sessions/PLUGIN.md +63 -0
- package/.meta/plugins/core/sessions/PLUGIN.yaml +20 -0
- package/.meta/plugins/core/sessions/scripts/check.py +33 -0
- package/.meta/plugins/core/structure/PLUGIN.md +29 -0
- package/.meta/plugins/core/structure/PLUGIN.yaml +18 -0
- package/.meta/plugins/core/structure/scripts/check.py +32 -0
- package/.meta/plugins/core/tag/PLUGIN.md +37 -0
- package/.meta/plugins/core/tag/PLUGIN.yaml +18 -0
- package/.meta/plugins/core/tag/scripts/check.py +28 -0
- package/.meta/plugins/core/tmp/PLUGIN.md +26 -0
- package/.meta/plugins/core/tmp/PLUGIN.yaml +16 -0
- package/.meta/plugins/core/todo/PLUGIN.md +33 -0
- package/.meta/plugins/core/todo/PLUGIN.yaml +22 -0
- package/.meta/plugins/core/trust/PLUGIN.md +70 -0
- package/.meta/plugins/core/trust/PLUGIN.yaml +23 -0
- package/.meta/plugins/core/trust/scripts/check.py +77 -0
- package/.meta/plugins/core/user-profile/PLUGIN.md +39 -0
- package/.meta/plugins/core/user-profile/PLUGIN.yaml +30 -0
- package/.meta/plugins/core/vault/PLUGIN.md +28 -0
- package/.meta/plugins/core/vault/PLUGIN.yaml +13 -0
- package/.meta/plugins/core/wiki/PLUGIN.md +37 -0
- package/.meta/plugins/core/wiki/PLUGIN.yaml +8 -0
- package/.meta/protocol/actions.md +42 -0
- package/.meta/protocol/experiments.md +40 -0
- package/.meta/protocol/registry.yaml +75 -0
- package/.meta/scripts/pipeline.py +428 -0
- package/.meta/scripts/wiki_plugin_kernel.py +1846 -0
- package/.meta/scripts/wikilib.py +79 -0
- package/LICENSE +21 -0
- package/README.md +105 -2
- package/bin/owfa.js +540 -0
- package/package.json +45 -4
- package/templates/AGENTS.template.md +19 -0
package/bin/owfa.js
ADDED
|
@@ -0,0 +1,540 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* OWFA — One Workspace For All (agents).
|
|
4
|
+
*
|
|
5
|
+
* The npm channel for the owfa framework. This CLI owns exactly three
|
|
6
|
+
* things the framework itself cannot own: fetching a complete framework tree,
|
|
7
|
+
* initializing a directory without hand-copying it, and replacing that tree
|
|
8
|
+
* on upgrade. Everything semantic stays in the Python kernel
|
|
9
|
+
* (.meta/scripts/wiki_plugin_kernel.py) — this file only orchestrates.
|
|
10
|
+
*
|
|
11
|
+
* Design rules:
|
|
12
|
+
* - zero runtime dependencies; Node is needed to install, not to use
|
|
13
|
+
* - no install/postinstall scripts for consumers (nothing executes on npm install)
|
|
14
|
+
* - additive on init: existing content is never modified outside AGENTS.md's
|
|
15
|
+
* injection region
|
|
16
|
+
* - upgrade is a tree replacement plus kernel convergence; instance-local
|
|
17
|
+
* kernel state (migration ledger, journal, registry cache) is preserved
|
|
18
|
+
*/
|
|
19
|
+
"use strict";
|
|
20
|
+
|
|
21
|
+
const fs = require("node:fs");
|
|
22
|
+
const os = require("node:os");
|
|
23
|
+
const path = require("node:path");
|
|
24
|
+
const { spawnSync } = require("node:child_process");
|
|
25
|
+
|
|
26
|
+
const CLI_ROOT = path.resolve(__dirname, "..");
|
|
27
|
+
const PKG = JSON.parse(fs.readFileSync(path.join(CLI_ROOT, "package.json"), "utf8"));
|
|
28
|
+
const FRAMEWORK_VERSION = readVersion(CLI_ROOT) || "?";
|
|
29
|
+
const INJECT_START = "<!-- wiki-inject:start -->";
|
|
30
|
+
const INJECT_END = "<!-- wiki-inject:end -->";
|
|
31
|
+
const PAYLOAD = [".meta", ".agents"];
|
|
32
|
+
const INSTANCE_DIRS = ["vault", path.join("wiki", "notes"), path.join("wiki", "sessions"), path.join("wiki", "vault")];
|
|
33
|
+
// Kernel state that belongs to the instance, not to the framework tree.
|
|
34
|
+
const PRESERVED = [
|
|
35
|
+
path.join(".meta", "migrations", "pending.json"),
|
|
36
|
+
path.join(".meta", "kernel-journal.log"),
|
|
37
|
+
path.join(".meta", "registry-cache.yaml"),
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
// ---------------------------------------------------------------------------
|
|
41
|
+
// small utilities
|
|
42
|
+
// ---------------------------------------------------------------------------
|
|
43
|
+
|
|
44
|
+
function readVersion(root) {
|
|
45
|
+
try {
|
|
46
|
+
return fs.readFileSync(path.join(root, ".meta", "VERSION"), "utf8").trim();
|
|
47
|
+
} catch {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function out(line = "") {
|
|
53
|
+
process.stdout.write(line + "\n");
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function fail(message, code = 1) {
|
|
57
|
+
process.stderr.write("owfa: " + message + "\n");
|
|
58
|
+
process.exit(code);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function rel(root, p) {
|
|
62
|
+
return path.relative(root, p) || ".";
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseArgs(argv) {
|
|
66
|
+
const args = { _: [], flags: new Set() };
|
|
67
|
+
for (let i = 0; i < argv.length; i++) {
|
|
68
|
+
const a = argv[i];
|
|
69
|
+
if (a === "--") {
|
|
70
|
+
args._.push(...argv.slice(i + 1));
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
if (a.startsWith("--")) {
|
|
74
|
+
const [key, inline] = a.slice(2).split("=", 2);
|
|
75
|
+
const takesValue = ["name", "packages", "without", "from"].includes(key);
|
|
76
|
+
if (takesValue && inline === undefined) {
|
|
77
|
+
args[key] = argv[++i];
|
|
78
|
+
} else if (takesValue) {
|
|
79
|
+
args[key] = inline;
|
|
80
|
+
} else {
|
|
81
|
+
args.flags.add(key);
|
|
82
|
+
}
|
|
83
|
+
} else {
|
|
84
|
+
args._.push(a);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
return args;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function copyPayload(target, { replace = false } = {}) {
|
|
91
|
+
const skip = (src) => {
|
|
92
|
+
const base = path.basename(src);
|
|
93
|
+
if (base === "__pycache__" || base.endsWith(".pyc") || base === ".DS_Store") return false;
|
|
94
|
+
if (base === "smoke-tmp") return false;
|
|
95
|
+
return true;
|
|
96
|
+
};
|
|
97
|
+
for (const entry of PAYLOAD) {
|
|
98
|
+
const src = path.join(CLI_ROOT, entry);
|
|
99
|
+
const dst = path.join(target, entry);
|
|
100
|
+
if (!fs.existsSync(src)) fail(`package is missing ${entry}/ — the npm tarball is incomplete`);
|
|
101
|
+
if (replace) {
|
|
102
|
+
if (fs.existsSync(dst)) replacePayloadTree(target, entry, skip);
|
|
103
|
+
} else {
|
|
104
|
+
fs.mkdirSync(path.dirname(dst), { recursive: true });
|
|
105
|
+
fs.cpSync(src, dst, { recursive: true, force: true, filter: skip });
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// Instance-owned state inside the payload trees, distinguished from framework-owned
|
|
111
|
+
// files (review P1, PR #53): installed package masters (kernel install), migration
|
|
112
|
+
// state (ledger / pending / dormant teardown), and skill copies the incoming tree
|
|
113
|
+
// does not ship (package connector skills, user standalone skills). Everything else
|
|
114
|
+
// under .meta/ and .agents/ is framework-owned and replaced by the channel.
|
|
115
|
+
const META_MERGE_DIRS = [path.join("plugins", "domains"), "migrations"];
|
|
116
|
+
|
|
117
|
+
// .meta/command/ is mixed-ownership: framework commands ship in the fresh tree,
|
|
118
|
+
// package commands (kernel install) and anything the channel no longer ships are
|
|
119
|
+
// carried over by difference — the fresh copy wins for shared names.
|
|
120
|
+
function carryOverCommands(target, metaAttic) {
|
|
121
|
+
const oldDir = path.join(metaAttic, "command");
|
|
122
|
+
if (!fs.existsSync(oldDir)) return;
|
|
123
|
+
const freshDir = path.join(target, ".meta", "command");
|
|
124
|
+
const fresh = new Set(fs.existsSync(freshDir) ? fs.readdirSync(freshDir) : []);
|
|
125
|
+
for (const d of fs.readdirSync(oldDir)) {
|
|
126
|
+
if (fresh.has(d)) continue;
|
|
127
|
+
fs.cpSync(path.join(oldDir, d), path.join(freshDir, d), { recursive: true, force: true });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function mergeTree(src, dst) {
|
|
132
|
+
if (!fs.existsSync(src)) return;
|
|
133
|
+
fs.cpSync(src, dst, { recursive: true, force: false, errorOnExist: false });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function carryOverEntry(target, entry, attic) {
|
|
137
|
+
if (entry === ".meta") {
|
|
138
|
+
for (const d of META_MERGE_DIRS) mergeTree(path.join(attic, d), path.join(target, ".meta", d));
|
|
139
|
+
carryOverCommands(target, attic);
|
|
140
|
+
for (const p of PRESERVED) {
|
|
141
|
+
const s = path.join(attic, path.relative(".meta", p)); // PRESERVED is target-relative; attic IS the old .meta
|
|
142
|
+
if (fs.existsSync(s)) {
|
|
143
|
+
const d = path.join(target, p);
|
|
144
|
+
fs.mkdirSync(path.dirname(d), { recursive: true });
|
|
145
|
+
fs.copyFileSync(s, d);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
} else {
|
|
149
|
+
const oldSkills = path.join(attic, "skills");
|
|
150
|
+
if (!fs.existsSync(oldSkills)) return;
|
|
151
|
+
const freshDir = path.join(target, ".agents", "skills");
|
|
152
|
+
const fresh = new Set(fs.existsSync(freshDir) ? fs.readdirSync(freshDir) : []);
|
|
153
|
+
for (const d of fs.readdirSync(oldSkills)) {
|
|
154
|
+
if (fresh.has(d)) continue; // framework-shipped skill — the fresh copy wins
|
|
155
|
+
fs.cpSync(path.join(oldSkills, d), path.join(freshDir, d), { recursive: true, force: true });
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Review round 2 (PR #53): the parked tree under .owfa/replace/ holds the only
|
|
161
|
+
// copy of the instance state between set-aside and carry-over. An interrupted
|
|
162
|
+
// swap (Windows: rename of a tree with an open file) must therefore never be
|
|
163
|
+
// implicitly cleaned by a retry, and a failure must roll the parked tree back.
|
|
164
|
+
function replacePayloadTree(target, entry, skip) {
|
|
165
|
+
const dst = path.join(target, entry);
|
|
166
|
+
const attic = path.join(target, ".owfa", "replace", entry === ".meta" ? "meta" : "agents");
|
|
167
|
+
if (fs.existsSync(attic)) {
|
|
168
|
+
fail(
|
|
169
|
+
`interrupted replace detected: instance state is parked at ${attic} — ` +
|
|
170
|
+
`do NOT simply rerun. Restore it manually (delete the fresh ${entry}/, ` +
|
|
171
|
+
`rename the parked tree back to ${entry}/), then rerun.`
|
|
172
|
+
);
|
|
173
|
+
}
|
|
174
|
+
fs.mkdirSync(path.dirname(attic), { recursive: true });
|
|
175
|
+
try {
|
|
176
|
+
fs.renameSync(dst, attic); // same volume — atomic enough for a directory swap
|
|
177
|
+
fs.cpSync(path.join(CLI_ROOT, entry), dst, { recursive: true, force: true, filter: skip });
|
|
178
|
+
carryOverEntry(target, entry, attic);
|
|
179
|
+
} catch (e) {
|
|
180
|
+
try {
|
|
181
|
+
fs.rmSync(dst, { recursive: true, force: true }); // partial fresh copy
|
|
182
|
+
fs.renameSync(attic, dst); // parked tree back in place
|
|
183
|
+
fail(`replace failed (${e.message}) — rolled back: ${entry}/ restored from the parked tree; fix the underlying error before rerunning`);
|
|
184
|
+
} catch (e2) {
|
|
185
|
+
fail(
|
|
186
|
+
`replace failed (${e.message}) AND rollback failed (${e2.message}) — the previous tree ` +
|
|
187
|
+
`is parked at ${attic}; do NOT rerun until it is restored to ${entry}/`
|
|
188
|
+
);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
fs.rmSync(path.join(target, ".owfa", "replace"), { recursive: true, force: true });
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function ensureDirs(target) {
|
|
195
|
+
for (const d of INSTANCE_DIRS) fs.mkdirSync(path.join(target, d), { recursive: true });
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
function ensureAgents(target, name) {
|
|
199
|
+
const file = path.join(target, "AGENTS.md");
|
|
200
|
+
const seed = fs
|
|
201
|
+
.readFileSync(path.join(CLI_ROOT, "templates", "AGENTS.template.md"), "utf8")
|
|
202
|
+
.replaceAll("{{name}}", name);
|
|
203
|
+
if (!fs.existsSync(file)) {
|
|
204
|
+
fs.writeFileSync(file, seed, "utf8");
|
|
205
|
+
return "written";
|
|
206
|
+
}
|
|
207
|
+
const text = fs.readFileSync(file, "utf8");
|
|
208
|
+
const hasStart = text.includes(INJECT_START);
|
|
209
|
+
const hasEnd = text.includes(INJECT_END);
|
|
210
|
+
if (hasStart && hasEnd) return "kept (injection region already present)";
|
|
211
|
+
if (hasStart || hasEnd) fail("AGENTS.md has only one injection marker — restore both markers (or remove the broken one) and rerun");
|
|
212
|
+
const region = seed.slice(seed.indexOf(INJECT_START));
|
|
213
|
+
const glue = text.endsWith("\n") ? "\n" : "\n\n";
|
|
214
|
+
fs.writeFileSync(file, text + glue + region + "\n", "utf8");
|
|
215
|
+
return "appended (existing AGENTS.md preserved)";
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function findPython() {
|
|
219
|
+
if (process.env.OWFA_PYTHON) {
|
|
220
|
+
return { cmd: process.env.OWFA_PYTHON, args: [] };
|
|
221
|
+
}
|
|
222
|
+
const candidates =
|
|
223
|
+
process.platform === "win32"
|
|
224
|
+
? [{ cmd: "py", args: ["-3"] }, { cmd: "python", args: [] }, { cmd: "python3", args: [] }]
|
|
225
|
+
: [{ cmd: "python3", args: [] }, { cmd: "python", args: [] }];
|
|
226
|
+
for (const cand of candidates) {
|
|
227
|
+
const probe = spawnSync(cand.cmd, [...cand.args, "-c", "import sys; sys.exit(0 if sys.version_info >= (3, 9) else 1)"], {
|
|
228
|
+
stdio: "ignore",
|
|
229
|
+
});
|
|
230
|
+
if (!probe.error && probe.status === 0) {
|
|
231
|
+
const v = spawnSync(cand.cmd, [...cand.args, "--version"], { encoding: "utf8" });
|
|
232
|
+
return { ...cand, version: (v.stdout || v.stderr || "").trim() };
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
return null;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
function pythonOrFail() {
|
|
239
|
+
const py = findPython();
|
|
240
|
+
if (!py) {
|
|
241
|
+
fail(
|
|
242
|
+
"Python 3.9+ not found. The framework itself is pure-stdlib Python; install Python 3 and rerun " +
|
|
243
|
+
"(or set OWFA_PYTHON=/path/to/python3)."
|
|
244
|
+
);
|
|
245
|
+
}
|
|
246
|
+
return py;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function kernel(target, kernelArgs) {
|
|
250
|
+
const py = pythonOrFail();
|
|
251
|
+
const script = path.join(target, ".meta", "scripts", "wiki_plugin_kernel.py");
|
|
252
|
+
if (!fs.existsSync(script)) fail(`no kernel at ${rel(target, script)} — is this an OWFA instance?`);
|
|
253
|
+
const res = spawnSync(py.cmd, [...py.args, script, ...kernelArgs], {
|
|
254
|
+
cwd: target,
|
|
255
|
+
stdio: "inherit",
|
|
256
|
+
// Instances should not accumulate bytecode; the framework tree stays clean.
|
|
257
|
+
env: { ...process.env, PYTHONDONTWRITEBYTECODE: "1" },
|
|
258
|
+
});
|
|
259
|
+
return res.status === null ? 1 : res.status;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function versionTuple(v) {
|
|
263
|
+
return String(v || "")
|
|
264
|
+
.split(".")
|
|
265
|
+
.map((x) => parseInt(x, 10) || 0);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
function cmpVersion(a, b) {
|
|
269
|
+
const x = versionTuple(a);
|
|
270
|
+
const y = versionTuple(b);
|
|
271
|
+
for (let i = 0; i < Math.max(x.length, y.length); i++) {
|
|
272
|
+
const d = (x[i] || 0) - (y[i] || 0);
|
|
273
|
+
if (d !== 0) return d > 0 ? 1 : -1;
|
|
274
|
+
}
|
|
275
|
+
return 0;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function timestamp() {
|
|
279
|
+
const d = new Date();
|
|
280
|
+
const p = (n) => String(n).padStart(2, "0");
|
|
281
|
+
return `${d.getFullYear()}${p(d.getMonth() + 1)}${p(d.getDate())}-${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
function pendingPackages(target) {
|
|
286
|
+
try {
|
|
287
|
+
return Object.keys(JSON.parse(fs.readFileSync(path.join(target, ".meta", "migrations", "pending.json"), "utf8")));
|
|
288
|
+
} catch {
|
|
289
|
+
return [];
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
function reportPending(target) {
|
|
294
|
+
const pending = pendingPackages(target);
|
|
295
|
+
if (!pending.length) return false;
|
|
296
|
+
out("");
|
|
297
|
+
out(`semantic migration pending: ${pending.join(", ")} — the package is installed, but its first-run step is an agent act, not a mechanical one.`);
|
|
298
|
+
out(` run: python .meta/scripts/wiki_plugin_kernel.py migrate ${pending[0]}`);
|
|
299
|
+
out(" (read the printed page, perform the step, then rerun with --clear)");
|
|
300
|
+
return true;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// ---------------------------------------------------------------------------
|
|
304
|
+
// commands
|
|
305
|
+
// ---------------------------------------------------------------------------
|
|
306
|
+
|
|
307
|
+
function cmdInit(args) {
|
|
308
|
+
const target = path.resolve(args._[0] || ".");
|
|
309
|
+
const force = args.flags.has("force");
|
|
310
|
+
const hasMeta = fs.existsSync(path.join(target, ".meta"));
|
|
311
|
+
if (hasMeta && !force) {
|
|
312
|
+
fail(
|
|
313
|
+
`${target} already contains .meta/ — refusing to overwrite. Use \`owfa upgrade\` to update the framework, ` +
|
|
314
|
+
`or pass --force to re-copy the framework trees (instance data is never touched either way).`
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
const name = args.name || path.basename(target) || "workspace";
|
|
318
|
+
fs.mkdirSync(target, { recursive: true });
|
|
319
|
+
|
|
320
|
+
out(`OWFA init — framework ${FRAMEWORK_VERSION} → ${target}`);
|
|
321
|
+
if (!hasMeta && fs.existsSync(path.join(target, ".agents", "skills"))) {
|
|
322
|
+
out("note: .agents/skills/ exists — framework skill copies are updated in place, other skills stay untouched");
|
|
323
|
+
}
|
|
324
|
+
if (hasMeta) out("warning: --force replaces the framework trees; instance vault/ and wiki/ are untouched");
|
|
325
|
+
copyPayload(target, { replace: hasMeta });
|
|
326
|
+
ensureDirs(target);
|
|
327
|
+
out(`agents.md: ${ensureAgents(target, name)}`);
|
|
328
|
+
|
|
329
|
+
out("");
|
|
330
|
+
out("converging projections (python .meta/scripts/wiki_plugin_kernel.py all)");
|
|
331
|
+
if (kernel(target, ["all"]) !== 0) {
|
|
332
|
+
const pending = pendingPackages(target);
|
|
333
|
+
if (pending.length) {
|
|
334
|
+
fail(`convergence blocked by pending semantic migrations (${pending.join(", ")}) — clear them with \`python .meta/scripts/wiki_plugin_kernel.py migrate <pkg>\``);
|
|
335
|
+
}
|
|
336
|
+
fail("kernel convergence failed — the framework tree is incomplete; nothing was deleted, fix and rerun");
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
let installed = 0;
|
|
340
|
+
if (args.packages) {
|
|
341
|
+
for (const pkg of String(args.packages)
|
|
342
|
+
.split(",")
|
|
343
|
+
.map((s) => s.trim())
|
|
344
|
+
.filter(Boolean)) {
|
|
345
|
+
out("");
|
|
346
|
+
out(`installing package ${pkg} from the registry`);
|
|
347
|
+
if (kernel(target, ["install", pkg, "--yes"]) !== 0) fail(`package ${pkg} failed to install (earlier packages stay installed)`);
|
|
348
|
+
installed++;
|
|
349
|
+
if (reportPending(target)) fail(`framework and ${pkg} are installed, but the workspace is not converged until the semantic migration above is cleared`);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
out("");
|
|
354
|
+
out(`done — framework ${FRAMEWORK_VERSION}${installed ? ` + ${installed} package(s)` : ""} in ${target}`);
|
|
355
|
+
out("");
|
|
356
|
+
out("next:");
|
|
357
|
+
out(` cd ${target}`);
|
|
358
|
+
out(" git init && git add -A && git commit -m \"init: OWFA workspace\" # recommended: instance snapshots");
|
|
359
|
+
out(" put any file into vault/, open your agent here, say \"map\"");
|
|
360
|
+
out(" owfa doctor # anytime: python, framework, kernel validate");
|
|
361
|
+
return 0;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function cmdUpgrade(args) {
|
|
365
|
+
const target = path.resolve(args._[0] || ".");
|
|
366
|
+
const local = readVersion(target);
|
|
367
|
+
if (!local) fail(`${target} is not an OWFA instance (no .meta/VERSION) — run \`owfa init\` first`);
|
|
368
|
+
const incoming = FRAMEWORK_VERSION;
|
|
369
|
+
const cmp = cmpVersion(local, incoming);
|
|
370
|
+
const frozen = args.flags.has("check") || args.flags.has("dry-run");
|
|
371
|
+
const force = args.flags.has("force");
|
|
372
|
+
|
|
373
|
+
out(`OWFA upgrade — instance ${local} → channel ${incoming} (CLI ${PKG.version})`);
|
|
374
|
+
if (cmp > 0) {
|
|
375
|
+
if (!force) fail(`instance framework ${local} is newer than this CLI's ${incoming} — run \`npx owfa@latest upgrade\``);
|
|
376
|
+
out(`warning: downgrading framework ${local} → ${incoming} (--force)`);
|
|
377
|
+
}
|
|
378
|
+
if (cmp === 0 && !force) {
|
|
379
|
+
out("already at this framework version — nothing to do (pass --force to re-sync the trees)");
|
|
380
|
+
return 0;
|
|
381
|
+
}
|
|
382
|
+
if (frozen) {
|
|
383
|
+
out(`would replace ${PAYLOAD.join(" + ")} and rerun kernel all (dry run)`);
|
|
384
|
+
return 0;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
if (args.flags.has("backup")) {
|
|
388
|
+
const dir = path.join(target, ".owfa", "backup", `${local}-${timestamp()}`);
|
|
389
|
+
for (const entry of PAYLOAD) {
|
|
390
|
+
const src = path.join(target, entry);
|
|
391
|
+
if (fs.existsSync(src)) fs.cpSync(src, path.join(dir, entry), { recursive: true, force: true });
|
|
392
|
+
}
|
|
393
|
+
out(`backup: ${rel(target, dir)} (add .owfa/ to the instance .gitignore if you do not want it tracked)`);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
out(`warning: local edits under ${PAYLOAD.join("/, ")}/ are replaced by the channel's tree — keep instance hotfixes in git`);
|
|
397
|
+
copyPayload(target, { replace: true });
|
|
398
|
+
|
|
399
|
+
out("");
|
|
400
|
+
out("converging projections (python .meta/scripts/wiki_plugin_kernel.py all)");
|
|
401
|
+
if (kernel(target, ["all"]) !== 0) {
|
|
402
|
+
const pending = pendingPackages(target);
|
|
403
|
+
if (pending.length) {
|
|
404
|
+
fail(`trees replaced, but convergence is blocked by pending semantic migrations (${pending.join(", ")}) — run \`python .meta/scripts/wiki_plugin_kernel.py migrate <pkg>\``);
|
|
405
|
+
}
|
|
406
|
+
fail("kernel convergence failed after the tree replacement — the instance git snapshot is your rollback");
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
const pending = pendingPackages(target);
|
|
410
|
+
if (pending.length) {
|
|
411
|
+
out("");
|
|
412
|
+
out(`pending semantic migrations: ${pending.join(", ")} — run \`python .meta/scripts/wiki_plugin_kernel.py migrate <pkg>\` and follow the page`);
|
|
413
|
+
}
|
|
414
|
+
out("");
|
|
415
|
+
out(`done — framework ${local} → ${incoming}`);
|
|
416
|
+
return 0;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
function cmdDoctor(args) {
|
|
420
|
+
const target = path.resolve(args._[0] || ".");
|
|
421
|
+
const local = readVersion(target);
|
|
422
|
+
const py = findPython();
|
|
423
|
+
out(`owfa ${PKG.version} (channel framework ${FRAMEWORK_VERSION})`);
|
|
424
|
+
out(`node ${process.version}`);
|
|
425
|
+
out(`python ${py ? py.version || `${py.cmd} ${py.args.join(" ")}` : "NOT FOUND"}`);
|
|
426
|
+
out(`target ${target}`);
|
|
427
|
+
if (!local) fail("not an OWFA instance (no .meta/VERSION) — run `owfa init` first");
|
|
428
|
+
out(`instance framework ${local}`);
|
|
429
|
+
const problems = []; // required conditions — any hit fails doctor (review P2, PR #53)
|
|
430
|
+
const agents = path.join(target, "AGENTS.md");
|
|
431
|
+
if (!fs.existsSync(agents)) {
|
|
432
|
+
out("AGENTS.md: MISSING — the agent will not see the structural contract");
|
|
433
|
+
problems.push("AGENTS.md is missing");
|
|
434
|
+
} else {
|
|
435
|
+
const text = fs.readFileSync(agents, "utf8");
|
|
436
|
+
const ok = text.includes(INJECT_START) && text.includes(INJECT_END);
|
|
437
|
+
out(`AGENTS.md: ${ok ? "injection markers present" : "MISSING injection markers"}`);
|
|
438
|
+
if (!ok) problems.push("AGENTS.md injection markers are missing or broken");
|
|
439
|
+
}
|
|
440
|
+
if (!py) {
|
|
441
|
+
out("kernel validate: skipped (no python)");
|
|
442
|
+
problems.push("Python 3.9+ not found — kernel validate not run");
|
|
443
|
+
} else {
|
|
444
|
+
out("");
|
|
445
|
+
if (kernel(target, ["validate"]) !== 0) problems.push("kernel validate failed (see above)");
|
|
446
|
+
}
|
|
447
|
+
out("");
|
|
448
|
+
if (problems.length) {
|
|
449
|
+
for (const p of problems) out(`problem: ${p}`);
|
|
450
|
+
out("doctor: failed — fix the problems above");
|
|
451
|
+
return 1;
|
|
452
|
+
}
|
|
453
|
+
out("doctor: ok");
|
|
454
|
+
return 0;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
|
|
458
|
+
function cmdList() {
|
|
459
|
+
const target = process.cwd();
|
|
460
|
+
if (!readVersion(target)) fail(`${target} is not an OWFA instance — run \`owfa init\` first`);
|
|
461
|
+
return kernel(target, ["list-remote"]);
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function cmdPkg() {
|
|
465
|
+
const target = process.cwd();
|
|
466
|
+
if (!readVersion(target)) fail(`${target} is not an OWFA instance — run \`owfa init\` first`);
|
|
467
|
+
return kernel(target, ["pkg"]);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function cmdAdd(args) {
|
|
471
|
+
const target = process.cwd();
|
|
472
|
+
const pkg = args._[0];
|
|
473
|
+
if (!pkg) fail("usage: owfa add <package> [--yes] [--without a,b] (run inside the instance)");
|
|
474
|
+
if (!readVersion(target)) fail(`${target} is not an OWFA instance — run \`owfa init\` first`);
|
|
475
|
+
const kernelArgs = ["install", pkg];
|
|
476
|
+
if (args.flags.has("yes")) kernelArgs.push("--yes");
|
|
477
|
+
if (args.without) kernelArgs.push("--without", args.without);
|
|
478
|
+
const code = kernel(target, kernelArgs);
|
|
479
|
+
if (code !== 0) return code;
|
|
480
|
+
if (reportPending(target)) return 1;
|
|
481
|
+
return kernel(target, ["all"]);
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function usage() {
|
|
485
|
+
out(`owfa ${PKG.version} — One Workspace For All (agents)
|
|
486
|
+
the owfa framework's install and upgrade channel
|
|
487
|
+
|
|
488
|
+
usage:
|
|
489
|
+
owfa init [dir] [--name NAME] [--packages a,b] [--force]
|
|
490
|
+
deploy a fresh workspace (framework ${FRAMEWORK_VERSION}); additive to existing content
|
|
491
|
+
owfa upgrade [dir] [--check] [--backup] [--force]
|
|
492
|
+
replace the framework trees with this channel's version and converge
|
|
493
|
+
owfa doctor [dir] python / framework / AGENTS.md / kernel validate
|
|
494
|
+
owfa add <pkg> [--yes] [--without a,b]
|
|
495
|
+
install a domain package from the registry, then converge
|
|
496
|
+
owfa list registry catalog (versions, compat, installed marks)
|
|
497
|
+
owfa pkg installed package inventory (members, core remainder)
|
|
498
|
+
owfa version CLI and framework versions
|
|
499
|
+
owfa help
|
|
500
|
+
|
|
501
|
+
notes:
|
|
502
|
+
- python 3.9+ is required to run the framework; node is only the installer
|
|
503
|
+
- \`init\` never deletes instance data; \`upgrade\` preserves the kernel's
|
|
504
|
+
migration ledger, journal and registry cache
|
|
505
|
+
- no install/postinstall scripts: npm install executes nothing
|
|
506
|
+
`);
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
function main() {
|
|
510
|
+
const argv = process.argv.slice(2);
|
|
511
|
+
const cmd = argv[0];
|
|
512
|
+
if (!cmd || cmd === "help" || cmd === "--help" || cmd === "-h") {
|
|
513
|
+
usage();
|
|
514
|
+
return 0;
|
|
515
|
+
}
|
|
516
|
+
if (cmd === "version" || cmd === "--version" || cmd === "-v") {
|
|
517
|
+
out(`owfa ${PKG.version} (framework ${FRAMEWORK_VERSION})`);
|
|
518
|
+
return 0;
|
|
519
|
+
}
|
|
520
|
+
const args = parseArgs(argv.slice(1));
|
|
521
|
+
switch (cmd) {
|
|
522
|
+
case "init":
|
|
523
|
+
return cmdInit(args);
|
|
524
|
+
case "upgrade":
|
|
525
|
+
return cmdUpgrade(args);
|
|
526
|
+
case "doctor":
|
|
527
|
+
return cmdDoctor(args);
|
|
528
|
+
case "add":
|
|
529
|
+
return cmdAdd(args);
|
|
530
|
+
case "list":
|
|
531
|
+
return cmdList();
|
|
532
|
+
case "pkg":
|
|
533
|
+
return cmdPkg();
|
|
534
|
+
default:
|
|
535
|
+
usage();
|
|
536
|
+
return 2;
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
process.exitCode = main();
|
package/package.json
CHANGED
|
@@ -1,6 +1,47 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "owfadev",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"description": "OWFA — One Workspace For All (agents). Install, initialize and upgrade the owfa agent workspace framework.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"owfa",
|
|
7
|
+
"owfa-agent-workspace",
|
|
8
|
+
"agent",
|
|
9
|
+
"ai",
|
|
10
|
+
"knowledge-base",
|
|
11
|
+
"wiki",
|
|
12
|
+
"markdown",
|
|
13
|
+
"obsidian",
|
|
14
|
+
"scaffold"
|
|
15
|
+
],
|
|
16
|
+
"homepage": "https://github.com/owfadev/owfa#readme",
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/owfadev/owfa/issues"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/owfadev/owfa.git"
|
|
23
|
+
},
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"author": "owfadev",
|
|
26
|
+
"bin": {
|
|
27
|
+
"owfa": "bin/owfa.js",
|
|
28
|
+
"owfadev": "bin/owfa.js"
|
|
29
|
+
},
|
|
30
|
+
"files": [
|
|
31
|
+
".meta",
|
|
32
|
+
".agents",
|
|
33
|
+
"bin",
|
|
34
|
+
"templates"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=16.7"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"test": "node test/smoke.js",
|
|
41
|
+
"prepack": "node scripts/clean-local-state.js",
|
|
42
|
+
"prepublishOnly": "node scripts/verify-release.js"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# {{name}}
|
|
2
|
+
|
|
3
|
+
This is an OWFA workspace — a `vault/` of real assets plus a `wiki/` the agent can re-enter without being re-introduced. `vault/` holds files in any format, `wiki/` holds the markdown pages the agent reads and writes, and `.meta/` + `.agents/skills/` are the framework body. Rewrite this paragraph freely: everything outside the injection region is yours, and the agent never edits it.
|
|
4
|
+
|
|
5
|
+
## Layout
|
|
6
|
+
|
|
7
|
+
`vault/` real assets · `wiki/` the md world · `.meta/` + `.agents/skills/` the framework body (domain packages under `.meta/plugins/domains/`).
|
|
8
|
+
|
|
9
|
+
## Git
|
|
10
|
+
|
|
11
|
+
Instance snapshots follow the instance-git plugin (core) — the agent commits at transaction close (`type: summary`); beyond that contract, git usage is the human's own.
|
|
12
|
+
|
|
13
|
+
<!-- wiki-inject:start -->
|
|
14
|
+
|
|
15
|
+
## wiki 注入区
|
|
16
|
+
|
|
17
|
+
> 本区为插件注入的投影,装卸插件时同步增删对应标记块;手写内容不进此区。
|
|
18
|
+
|
|
19
|
+
<!-- wiki-inject:end -->
|