owfadev 0.0.1 → 0.3.1

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