toolfactory 0.2.1 → 0.2.4

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 (59) hide show
  1. package/README.md +1 -1
  2. package/dist/bindings/typescript.js +1 -1
  3. package/dist/bindings/typescript.test.js +29 -1
  4. package/dist/commands.d.ts +6 -0
  5. package/dist/commands.js +13 -2
  6. package/dist/hosts/openclaw.d.ts +8 -0
  7. package/dist/hosts/openclaw.js +70 -4
  8. package/dist/hosts/openclaw.test.d.ts +1 -0
  9. package/dist/hosts/openclaw.test.js +58 -0
  10. package/dist/hosts/web.d.ts +1 -0
  11. package/dist/hosts/web.js +6 -3
  12. package/dist/hosts/web.test.d.ts +1 -0
  13. package/dist/hosts/web.test.js +8 -0
  14. package/dist/model.d.ts +12 -0
  15. package/dist/model.js +4 -0
  16. package/dist/ops.d.ts +26 -0
  17. package/dist/ops.js +25 -1
  18. package/dist/project/gate.js +7 -1
  19. package/dist/project/history.d.ts +1 -0
  20. package/dist/project/load.js +19 -2
  21. package/dist/prompt/review.d.ts +23 -0
  22. package/dist/prompt/review.js +103 -0
  23. package/dist/prompt/review.test.d.ts +1 -0
  24. package/dist/prompt/review.test.js +55 -0
  25. package/dist/prompt/standard.d.ts +26 -0
  26. package/dist/prompt/standard.js +68 -0
  27. package/dist/surfaces/openclaw-native.d.ts +11 -1
  28. package/dist/surfaces/openclaw-native.js +23 -3
  29. package/dist/surfaces/openclaw-native.test.js +11 -1
  30. package/dist/surfaces/web.d.ts +6 -6
  31. package/dist/surfaces/web.js +7 -7
  32. package/dist/surfaces/web.test.js +1 -1
  33. package/dist/surfaces/workflows.js +2 -2
  34. package/dist/toolfactory/cli.js +1 -1
  35. package/dist/toolfactory/mcp.js +1 -1
  36. package/package.json +1 -1
  37. package/src/bindings/typescript.test.ts +32 -1
  38. package/src/bindings/typescript.ts +1 -1
  39. package/src/commands.ts +14 -2
  40. package/src/hosts/openclaw.test.ts +62 -0
  41. package/src/hosts/openclaw.ts +73 -5
  42. package/src/hosts/web.test.ts +9 -0
  43. package/src/hosts/web.ts +6 -3
  44. package/src/model.ts +15 -0
  45. package/src/ops.ts +30 -1
  46. package/src/project/gate.ts +7 -1
  47. package/src/project/load.ts +23 -2
  48. package/src/prompt/review.test.ts +68 -0
  49. package/src/prompt/review.ts +148 -0
  50. package/src/prompt/standard.ts +93 -0
  51. package/src/surfaces/openclaw-native.test.ts +11 -1
  52. package/src/surfaces/openclaw-native.ts +27 -3
  53. package/src/surfaces/web.test.ts +1 -1
  54. package/src/surfaces/web.ts +7 -7
  55. package/src/surfaces/workflows.ts +2 -2
  56. package/src/toolfactory/cli.ts +1 -1
  57. package/src/toolfactory/mcp.ts +1 -1
  58. package/web/dist/assets/{index-BHR6gCfm.js → index-GlWisnfJ.js} +2 -2
  59. package/web/dist/index.html +1 -1
@@ -0,0 +1,93 @@
1
+ /**
2
+ * The codex-prompt-standard rubric, as data. The prose canon is
3
+ * `.agents/skills/codex-prompt-standard/SKILL.md` — it ships inside the OpenClaw plugin and
4
+ * syncs to every dev harness; this module is the same guidance shaped for `review-prompt` to
5
+ * evaluate against. `review.test.ts` pins every anchor below to the canon text so the two
6
+ * cannot drift apart silently.
7
+ */
8
+
9
+ export interface PromptSection {
10
+ id: string;
11
+ title: string;
12
+ /** Substring of the canon SKILL.md this section is pinned to (drift guard). */
13
+ canonAnchor: string;
14
+ /** Any hit marks the section present in a draft or a reference prompt. */
15
+ anchors: RegExp[];
16
+ /** The next-iteration instruction when the section is missing. */
17
+ directive: string;
18
+ }
19
+
20
+ /** The anatomy a Codex-class prompt reliably contains, in order (canon §2). */
21
+ export const ANATOMY: PromptSection[] = [
22
+ {
23
+ id: "identity",
24
+ title: "one-sentence identity",
25
+ canonAnchor: "One-sentence identity",
26
+ anchors: [/\byou are\b/i, /\bacting (?:for|on behalf of|as)\b/i],
27
+ directive:
28
+ "Open with one sentence of identity: who the model is, where it runs, how it is expected to behave.",
29
+ },
30
+ {
31
+ id: "capabilities",
32
+ title: "capability inventory",
33
+ canonAnchor: "Capability inventory",
34
+ anchors: [/\byou can\b/i, /\bcapabilit/i, /\b(?:available|your) tools?\b/i],
35
+ directive: "State the capability inventory: what the agent can receive and emit.",
36
+ },
37
+ {
38
+ id: "how-you-work",
39
+ title: '"How you work" sections',
40
+ canonAnchor: '"How you work" sections',
41
+ anchors: [/how you work/i, /\bpreceden/i, /\bpersisten/i, /\bautonom/i, /\btone\b/i],
42
+ directive:
43
+ 'Add "how you work" sections: personality and tone, instruction precedence, the autonomy and persistence contract.',
44
+ },
45
+ {
46
+ id: "planning",
47
+ title: "planning guidance",
48
+ canonAnchor: "Planning guidance",
49
+ anchors: [/\bplan\b/i, /\bpersist until\b/i, /before (?:acting|starting|diving)/i],
50
+ directive:
51
+ "Add planning guidance with explicit high-quality vs low-quality examples for what a good plan is.",
52
+ },
53
+ {
54
+ id: "execution-criteria",
55
+ title: "execution criteria",
56
+ canonAnchor: "Execution criteria",
57
+ anchors: [/\bMUST\b/, /\bNEVER\b/, /scope creep/i, /no unrelated changes/i],
58
+ directive:
59
+ "Add execution criteria: hard MUST/NEVER lists, root-cause fixes, no scope creep, no unrelated changes.",
60
+ },
61
+ {
62
+ id: "validation",
63
+ title: "validation philosophy",
64
+ canonAnchor: "Validation philosophy",
65
+ anchors: [/\bverify\b/i, /\bvalidat/i, /before claiming done/i, /\bdone when\b/i],
66
+ directive:
67
+ "Add the validation philosophy: start specific, broaden as confidence grows, verify before claiming done.",
68
+ },
69
+ {
70
+ id: "final-message",
71
+ title: "final-message guidance",
72
+ canonAnchor: "Final-message guidance",
73
+ anchors: [/final message/i, /\boutput depth\b/i, /\bfinal reply\b/i],
74
+ directive:
75
+ "Add final-message guidance: output depth matches task size; skip formatting for trivial replies.",
76
+ },
77
+ ];
78
+
79
+ export interface PromptConvention {
80
+ id: string;
81
+ /** Substring of the canon SKILL.md this convention is pinned to (drift guard). */
82
+ canonAnchor: string;
83
+ }
84
+
85
+ /** Convention anchors the canon must mention; the checks in review.ts implement them. */
86
+ export const CONVENTIONS: PromptConvention[] = [
87
+ { id: "positive-imperatives", canonAnchor: "Positive imperatives" },
88
+ { id: "precedence", canonAnchor: "Explicit precedence" },
89
+ { id: "must-never-discipline", canonAnchor: "MUST/NEVER reserved" },
90
+ { id: "examples", canonAnchor: "Good-vs-bad examples" },
91
+ { id: "harness-adaptation", canonAnchor: "Adapt to the target harness" },
92
+ { id: "checkable-criteria", canonAnchor: "checkable criterion" },
93
+ ];
@@ -133,6 +133,7 @@ describe("openclaw-native", () => {
133
133
  expect(files[`${HOST_DIR}/README.md`]).toContain("`shoot` — requires browser");
134
134
 
135
135
  const pkg = JSON.parse(files[`${HOST_DIR}/package.json`] ?? "{}");
136
+ expect(pkg.devDependencies).toMatchObject(OPENCLAW_SCAFFOLD.devDependencies);
136
137
  expect(pkg.openclaw).toEqual({
137
138
  extensions: ["./dist/index.js"],
138
139
  compat: { pluginApi: OPENCLAW_SCAFFOLD.pluginApi },
@@ -212,6 +213,10 @@ describe("openclaw-native", () => {
212
213
 
213
214
  it("drives manifest, inspector and test from one `registers` declaration", () => {
214
215
  const target = voice();
216
+ target.tool.openclaw = {
217
+ ...target.tool.openclaw,
218
+ devDependencies: { openclaw: "2026.9.2", "@types/ws": "^8.18.0" },
219
+ };
215
220
  const files = emitted(target);
216
221
  const manifest = JSON.parse(files[`${HOST_DIR}/openclaw.plugin.json`] ?? "{}");
217
222
  expect(manifest.contracts).toEqual({ tools: [], realtimeVoiceProviders: ["codex"] });
@@ -223,6 +228,11 @@ describe("openclaw-native", () => {
223
228
  });
224
229
  // The author's extra dependency merges after the scaffold's; the core is not imported at all.
225
230
  expect(pkg.dependencies).toEqual({ ...OPENCLAW_SCAFFOLD.dependencies, ws: "^8.18.0" });
231
+ expect(pkg.devDependencies).toEqual({
232
+ ...OPENCLAW_SCAFFOLD.devDependencies,
233
+ openclaw: "2026.9.2",
234
+ "@types/ws": "^8.18.0",
235
+ });
226
236
  expect(pkg.peerDependencies.openclaw).toBe(">=2026.8.2");
227
237
  expect(pkg.openclaw.compat.pluginApi).toBe(">=2026.8.2");
228
238
 
@@ -312,7 +322,7 @@ function hasOpenclaw(): boolean {
312
322
  }
313
323
 
314
324
  describe.skipIf(!hasOpenclaw())("openclaw scaffold", () => {
315
- it("still matches `openclaw plugins init --type tool`", () => {
325
+ it("still matches `openclaw plugins init --type tool`", { timeout: 60_000 }, () => {
316
326
  expect(scaffoldDrift(project())).toEqual([]);
317
327
  // A declared `pluginApi` is an intentional override, not drift.
318
328
  expect(scaffoldDrift(voice())).toEqual([]);
@@ -35,7 +35,7 @@ import {
35
35
  /** Transcribed from `openclaw plugins init --type tool`; `validate()` proves them still current. */
36
36
  export const OPENCLAW_SCAFFOLD = {
37
37
  /** The OpenClaw release the scaffold was taken from (`package.json openclaw.build`). */
38
- openclawVersion: "2026.9.2",
38
+ openclawVersion: "2026.9.4",
39
39
  /** `peerDependencies.openclaw` and `openclaw.compat.pluginApi`. */
40
40
  pluginApi: ">=2026.5.17",
41
41
  private: true,
@@ -86,6 +86,17 @@ export default defineConfig({
86
86
  * survive into the projection but allows extra ones: upstream stays the source of truth for
87
87
  * everything it does write.
88
88
  */
89
+ /**
90
+ * The version `openclaw plugins init` stamps into its scaffold is the CLI's own version, so
91
+ * the generated manifest carries the openclaw this project actually pins (author override via
92
+ * `openclaw.devDependencies.openclaw`), and the scaffold default only when the project rides
93
+ * `latest`. hosts/openclaw.ts runs the same pin in its drift probe, so the upstream/generated
94
+ * comparison is identical in every environment.
95
+ */
96
+ export function openclawVersion(project: Project): string {
97
+ return project.tool.openclaw?.devDependencies?.openclaw ?? OPENCLAW_SCAFFOLD.openclawVersion;
98
+ }
99
+
89
100
  export const OPENCLAW_ADDITIONS = {
90
101
  /** Without it `tsc` emits a `dist/` from source that failed type-checking. */
91
102
  compilerOptions: { noEmitOnError: true },
@@ -123,6 +134,11 @@ export function registrations(project: Project): Registration[] {
123
134
  return project.tool.openclaw?.registers ?? [];
124
135
  }
125
136
 
137
+ /** Whether the plugin ships `.agents/skills/` directories (`openclaw.skills` in tool.json). */
138
+ export function shipsSkills(project: Project): boolean {
139
+ return (project.tool.openclaw?.skills?.length ?? 0) > 0;
140
+ }
141
+
126
142
  /**
127
143
  * The gateway route and the Control UI tab the plugin adds when the tool also has a `web`
128
144
  * surface. Only for the in-process binding: the route hands requests to the core package's own
@@ -257,7 +273,7 @@ function packageJson(
257
273
  type: "module",
258
274
  private: OPENCLAW_SCAFFOLD.private,
259
275
  scripts: { ...OPENCLAW_SCAFFOLD.scripts, ...e2e },
260
- files: OPENCLAW_SCAFFOLD.files,
276
+ files: shipsSkills(project) ? [...OPENCLAW_SCAFFOLD.files, "skills"] : OPENCLAW_SCAFFOLD.files,
261
277
  peerDependencies: { openclaw: pluginApi(project), ...extra?.peerDependencies },
262
278
  dependencies: { ...OPENCLAW_SCAFFOLD.dependencies, ...core, ...extra?.dependencies },
263
279
  devDependencies: {
@@ -268,7 +284,7 @@ function packageJson(
268
284
  openclaw: {
269
285
  extensions: [OPENCLAW_SCAFFOLD.entry],
270
286
  compat: { pluginApi: pluginApi(project) },
271
- build: { openclawVersion: OPENCLAW_SCAFFOLD.openclawVersion },
287
+ build: { openclawVersion: openclawVersion(project) },
272
288
  },
273
289
  pluginInspector: expectedRegistrations(project, operations).length
274
290
  ? pluginInspector(project, operations)
@@ -299,6 +315,7 @@ function pluginManifest(project: Project, operations: Operation[]): Record<strin
299
315
  configSchema: configSchema(project),
300
316
  uiHints: sensitive.length ? Object.fromEntries(sensitive) : undefined,
301
317
  activation: activation(project),
318
+ skills: shipsSkills(project) ? ["./skills"] : undefined,
302
319
  contracts: {
303
320
  tools: operations.map((operation) => operation.name),
304
321
  ...Object.fromEntries(declared),
@@ -955,6 +972,13 @@ export const surface: Surface = {
955
972
  path: `${HOST_DIR}/vitest.config.ts`,
956
973
  content: OPENCLAW_SCAFFOLD.vitestConfig,
957
974
  },
975
+ ...(project.skills ?? []).flatMap((skill) =>
976
+ skill.files.map((file) => ({
977
+ kind: "file" as const,
978
+ path: `${HOST_DIR}/skills/${skill.name}/${file.path}`,
979
+ content: file.content,
980
+ })),
981
+ ),
958
982
  {
959
983
  kind: "merge",
960
984
  path: `${HOST_DIR}/openclaw.plugin.json`,
@@ -192,7 +192,7 @@ describe("web", () => {
192
192
  expect(app).toContain("Authorization: `Bearer ${TOKEN}`");
193
193
  // The Secrets panel is the developer's `.env`, and it is the kernel's `/env` that answers.
194
194
  expect(app).toContain('new URL("env", document.baseURI).href');
195
- expect(app).toContain("toolfactory secrets check");
195
+ expect(app).toContain("toolfactory secrets-usage check");
196
196
  expect(app).toContain('type="password"');
197
197
 
198
198
  // The smoke drives the real kernel over --http instead of a static server of its own.
@@ -153,8 +153,8 @@ export default defineConfig({
153
153
  "@fontsource-variable/geist": "^5.3.0",
154
154
  "@tailwindcss/vite": "^4.3.3",
155
155
  "class-variance-authority": "^0.7.1",
156
- cn: "^0.2.6",
157
- "lucide-react": "^1.43.0",
156
+ cn: "^0.3.0",
157
+ "lucide-react": "^1.46.0",
158
158
  react: "^19.2.8",
159
159
  "react-dom": "^19.2.8",
160
160
  shadcn: "^4.21.0",
@@ -164,11 +164,11 @@ export default defineConfig({
164
164
  devDependencies: {
165
165
  "@types/node": "^24.13.3",
166
166
  "@types/react": "^19.2.18",
167
- "@types/react-dom": "^19.2.4",
168
- "@vitejs/plugin-react": "^6.1.0",
169
- oxlint: "^1.79.0",
167
+ "@types/react-dom": "^19.2.7",
168
+ "@vitejs/plugin-react": "^6.1.1",
169
+ oxlint: "^1.81.0",
170
170
  typescript: "~6.0.2",
171
- vite: "^8.2.2",
171
+ vite: "^8.3.0",
172
172
  },
173
173
  },
174
174
  componentsJson: {
@@ -637,7 +637,7 @@ function SecretsPanel() {
637
637
  <CardTitle>Secrets</CardTitle>
638
638
  <CardDescription>
639
639
  Written to this checkout's gitignored <code>.env</code>, which the live tests and
640
- <code> toolfactory bootstrap-repo</code> read. Run <code>toolfactory secrets check</code>{" "}
640
+ <code> toolfactory bootstrap-repo</code> read. Run <code>toolfactory secrets-usage check</code>{" "}
641
641
  to ask each registry whether it accepts them.
642
642
  </CardDescription>
643
643
  </CardHeader>
@@ -625,7 +625,7 @@ function releaseDocument(
625
625
  "`gh workflow run release.yml -f tag=vX.Y.Z` — never `gh run rerun`, which replays the",
626
626
  "original run's secret snapshot. `release` first retracts every registry a surface dropped",
627
627
  "since the previous tag published to (git is the ledger; `toolfactory unpublish`), and",
628
- "`toolfactory secrets status` / `bootstrap-repo` cover the one-time steps that are left.",
628
+ "`toolfactory secrets-usage status` / `bootstrap-repo` cover the one-time steps that are left.",
629
629
  ];
630
630
  if (clawhubSelected) {
631
631
  jobs["publish-clawhub"] = compact({
@@ -904,7 +904,7 @@ function envExample(project: Project): string {
904
904
  lines.push(
905
905
  "",
906
906
  "# Release credentials (repository secrets; `toolfactory bootstrap-repo` pushes them from .env,",
907
- "# `toolfactory secrets status` explains each). Leave one empty to skip that registry.",
907
+ "# `toolfactory secrets-usage status` explains each). Leave one empty to skip that registry.",
908
908
  );
909
909
  for (const row of release) {
910
910
  lines.push("", `# ${row.id}: ${row.url}`, ...row.secrets.map((name) => `${name}=`));
@@ -13,7 +13,7 @@ const operations = [...authored, web];
13
13
  const program = new Command()
14
14
  .name("toolfactory")
15
15
  .description("Build an agent tool once; ship it as Agent Skills, MCP servers, Agent Plugins / Claude / Codex / Cursor bundles, OpenClaw and Hermes plugins, CLIs, and packages, with the tests and releases to match.")
16
- .version("0.2.1");
16
+ .version("0.2.4");
17
17
 
18
18
  for (const op of operations) {
19
19
  if (!serves(op, "cli")) continue;
@@ -33,7 +33,7 @@ const introspecting = Boolean(process.env.TOOLFACTORY_INTROSPECT);
33
33
 
34
34
  /** Builds one server instance with every servable operation registered; called once for stdio, once per HTTP request. */
35
35
  function createServer(): McpServer {
36
- const server = new McpServer({ name: "toolfactory", version: "0.2.1" });
36
+ const server = new McpServer({ name: "toolfactory", version: "0.2.4" });
37
37
  for (const op of operations) {
38
38
  if (!introspecting && !serves(op, "mcp")) continue;
39
39
  server.registerTool(