@bx-h/meta-flow 0.1.0 → 0.1.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.1.1
4
+
5
+ - Install `meta-flow` into `.agents/skills/meta-flow` so `$meta-flow` can be discovered directly.
6
+ - Install support scripts and templates into `.meta-flow/scripts` and `.meta-flow/templates`.
7
+ - Update `doctor` to check the discoverable Skill and runtime support files, not only the plugin bundle.
8
+ - Keep `.meta-flow/tasks` during uninstall while removing managed support files.
9
+
3
10
  ## 0.1.0
4
11
 
5
12
  - Initial Codex Plugin and npm installer package.
package/README.md CHANGED
@@ -26,7 +26,7 @@ meta-flow install --scope repo
26
26
  Pinned version:
27
27
 
28
28
  ```bash
29
- npx @bx-h/meta-flow@0.1.0 install --scope repo
29
+ npx @bx-h/meta-flow@0.1.1 install --scope repo
30
30
  ```
31
31
 
32
32
  ## Quick Start
@@ -117,6 +117,9 @@ flowchart LR
117
117
  Repo scope writes under the target repo:
118
118
 
119
119
  - `<repo>/plugins/meta-flow`
120
+ - `<repo>/.agents/skills/meta-flow`
121
+ - `<repo>/.meta-flow/scripts`
122
+ - `<repo>/.meta-flow/templates`
120
123
  - `<repo>/.agents/plugins/marketplace.json`
121
124
  - `<repo>/.codex/agents/*.toml`
122
125
  - `<repo>/.codex/config.toml`
@@ -124,6 +127,9 @@ Repo scope writes under the target repo:
124
127
  User scope writes under the current user home:
125
128
 
126
129
  - `~/.codex/plugins/meta-flow`
130
+ - `~/.agents/skills/meta-flow`
131
+ - `~/.meta-flow/scripts`
132
+ - `~/.meta-flow/templates`
127
133
  - `~/.agents/plugins/marketplace.json`
128
134
  - `~/.codex/agents/*.toml`
129
135
  - `~/.codex/config.toml`
@@ -159,7 +165,7 @@ meta-flow uninstall --scope repo --dry-run
159
165
  You can also distribute the plugin through a Codex marketplace entry:
160
166
 
161
167
  ```bash
162
- codex plugin marketplace add bx-h/meta-flow --ref v0.1.0
168
+ codex plugin marketplace add bx-h/meta-flow --ref v0.1.1
163
169
  ```
164
170
 
165
171
  The npm installer still matters because it also materializes custom agent TOML files and validation scripts.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bx-h/meta-flow",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A Codex-native workflow for clarified goals, reviewed proposals, adjudicated plans, task-level execution, verification, and direction-aware iteration.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "meta-flow",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "A Codex workflow for turning vague requests into clarified, reviewed, adjudicated, task-level executed, and verified outcomes.",
5
5
  "author": {
6
6
  "name": "huangbaixi",
@@ -7,6 +7,8 @@ import { inspectCodexConfig } from "../lib/codex_config.js";
7
7
  import { pathExists, readJsonOrDefault } from "../lib/fs_safe.js";
8
8
  import { createLogger } from "../lib/logger.js";
9
9
  import { resolveTargets, sampleTaskRoot } from "../lib/paths.js";
10
+ import { validateInstalledSkill } from "../lib/skill.js";
11
+ import { validateSupportFiles } from "../lib/support.js";
10
12
 
11
13
  export function doctorHelp() {
12
14
  return `Usage: meta-flow doctor --scope repo|user [--target <path>] [--verbose]`;
@@ -27,6 +29,8 @@ export async function runDoctor(argv = []) {
27
29
 
28
30
  results.push(checkCodexCli());
29
31
  results.push(await checkPlugin(targets));
32
+ results.push(await checkDiscoverableSkill(targets));
33
+ results.push(await checkSupportFiles(targets));
30
34
  results.push(await checkMarketplace(targets));
31
35
  results.push(await checkAgents(targets));
32
36
  results.push(await checkConfig(targets));
@@ -68,6 +72,22 @@ async function checkPlugin(targets) {
68
72
  return { level: "PASS", title: "plugin and skill present" };
69
73
  }
70
74
 
75
+ async function checkDiscoverableSkill(targets) {
76
+ const result = await validateInstalledSkill(targets);
77
+ if (result.errors.length) {
78
+ return { level: "FAIL", title: "discoverable skill incomplete", message: result.errors.join("; ") };
79
+ }
80
+ return { level: "PASS", title: "discoverable skill present" };
81
+ }
82
+
83
+ async function checkSupportFiles(targets) {
84
+ const result = await validateSupportFiles(targets);
85
+ if (result.errors.length) {
86
+ return { level: "FAIL", title: "support scripts/templates incomplete", message: result.errors.join("; ") };
87
+ }
88
+ return { level: "PASS", title: "support scripts/templates present" };
89
+ }
90
+
71
91
  async function checkMarketplace(targets) {
72
92
  if (!(await pathExists(targets.marketplaceTarget))) {
73
93
  return { level: "FAIL", title: "marketplace missing", message: "Run install to create marketplace.json." };
@@ -118,7 +138,7 @@ async function checkConfig(targets) {
118
138
  }
119
139
 
120
140
  async function checkPythonScripts(targets) {
121
- const scriptsDir = path.join(targets.pluginTarget, "scripts");
141
+ const scriptsDir = targets.scriptsTarget;
122
142
  const scriptPath = path.join(scriptsDir, "validate_goal_contract.py");
123
143
  if (!(await pathExists(scriptPath))) {
124
144
  return { level: "FAIL", title: "Python scripts missing", message: "Reinstall plugin files." };
@@ -135,7 +155,7 @@ async function checkSampleTask(targets) {
135
155
  return { level: "WARN", title: "sample task unavailable", message: "Package examples are not present." };
136
156
  }
137
157
  const python = resolvePython();
138
- const scripts = path.join(targets.pluginTarget, "scripts");
158
+ const scripts = targets.scriptsTarget;
139
159
  const runs = [
140
160
  [path.join(scripts, "validate_goal_contract.py"), path.join(sampleTaskRoot, "goal-contract.json")],
141
161
  [path.join(scripts, "validate_adjudication.py"), path.join(sampleTaskRoot, "adjudication-report.json")],
@@ -8,6 +8,8 @@ import { createLogger } from "../lib/logger.js";
8
8
  import { updateMarketplace } from "../lib/marketplace.js";
9
9
  import { installPlugin } from "../lib/plugin.js";
10
10
  import { resolveTargets } from "../lib/paths.js";
11
+ import { installSkill } from "../lib/skill.js";
12
+ import { installSupportFiles } from "../lib/support.js";
11
13
 
12
14
  export function installHelp() {
13
15
  return `Usage: meta-flow install --scope repo|user [options]
@@ -61,6 +63,18 @@ export async function runInstall(argv = []) {
61
63
  }
62
64
  }
63
65
 
66
+ const skillResult = await installSkill(targets, { dryRun, force, backup, logger });
67
+ if (skillResult.conflict) {
68
+ logger.warn(`skill target exists and is not managed by meta-flow: ${skillResult.conflict}. Re-run with --force to overwrite.`);
69
+ }
70
+
71
+ const supportResult = await installSupportFiles(targets, { dryRun, force, backup, logger });
72
+ for (const [name, result] of Object.entries(supportResult)) {
73
+ if (result.conflict) {
74
+ logger.warn(`${name} target exists and is not managed by meta-flow: ${result.conflict}. Re-run with --force to overwrite.`);
75
+ }
76
+ }
77
+
64
78
  await updateMarketplace(targets, { dryRun, logger });
65
79
 
66
80
  if (installAgentsEnabled) {
@@ -95,6 +109,8 @@ function printInstallPlan(targets, options) {
95
109
  console.log(`- scope: ${targets.scope}`);
96
110
  console.log(`- target: ${targets.target}`);
97
111
  console.log(`- plugin: ${targets.pluginTarget}`);
112
+ console.log(`- skill: ${targets.skillTarget}`);
113
+ console.log(`- support: ${targets.supportTarget}`);
98
114
  console.log(`- marketplace: ${targets.marketplaceTarget}`);
99
115
  console.log(`- agents: ${targets.agentsTarget}`);
100
116
  console.log(`- config: ${targets.codexConfigTarget}`);
@@ -105,6 +121,8 @@ function printInstallPlan(targets, options) {
105
121
  if (options.installAgentsEnabled) {
106
122
  console.log("- install 14 agent templates");
107
123
  }
124
+ console.log("- install discoverable skill");
125
+ console.log("- install support scripts and templates");
108
126
  console.log("- update marketplace");
109
127
  console.log("- ensure Codex agent config");
110
128
  if (options.dryRun) {
@@ -4,6 +4,8 @@ import { createLogger } from "../lib/logger.js";
4
4
  import { uninstallMarketplace } from "../lib/marketplace.js";
5
5
  import { uninstallPlugin } from "../lib/plugin.js";
6
6
  import { resolveTargets } from "../lib/paths.js";
7
+ import { uninstallSkill } from "../lib/skill.js";
8
+ import { uninstallSupportFiles } from "../lib/support.js";
7
9
 
8
10
  export function uninstallHelp() {
9
11
  return `Usage: meta-flow uninstall --scope repo|user [options]
@@ -34,6 +36,8 @@ export async function runUninstall(argv = []) {
34
36
  console.log(`- scope: ${targets.scope}`);
35
37
  console.log(`- target: ${targets.target}`);
36
38
  console.log(`- remove plugin: ${targets.pluginTarget}`);
39
+ console.log(`- remove skill: ${targets.skillTarget}`);
40
+ console.log(`- remove support scripts/templates: ${targets.supportTarget}`);
37
41
  console.log(`- update marketplace: ${targets.marketplaceTarget}`);
38
42
  console.log(`- remove marked agents: ${targets.agentsTarget}`);
39
43
  console.log(`- keep tasks: ${targets.tasksTarget}`);
@@ -46,6 +50,16 @@ export async function runUninstall(argv = []) {
46
50
  if (pluginResult.skipped) {
47
51
  logger.warn(`plugin target is not a confirmed meta-flow plugin; skipped: ${targets.pluginTarget}`);
48
52
  }
53
+ const skillResult = await uninstallSkill(targets, { dryRun, logger });
54
+ if (skillResult.skipped) {
55
+ logger.warn(`skill target has no meta-flow marker; skipped: ${targets.skillTarget}`);
56
+ }
57
+ const supportResult = await uninstallSupportFiles(targets, { dryRun, logger });
58
+ for (const [name, result] of Object.entries(supportResult)) {
59
+ if (result.skipped) {
60
+ logger.warn(`${name} target has no meta-flow marker; skipped.`);
61
+ }
62
+ }
49
63
  await uninstallMarketplace(targets, { dryRun, logger });
50
64
  const agentResult = await uninstallAgents(targets, { dryRun, logger });
51
65
  for (const skipped of agentResult.skipped) {
@@ -135,6 +135,15 @@ async function checkInstallUninstallSimulation() {
135
135
  const marketplace = JSON.parse(await fs.readFile(path.join(tmp, ".agents", "plugins", "marketplace.json"), "utf8"));
136
136
  const entries = marketplace.plugins.filter((plugin) => plugin.name === "meta-flow");
137
137
  if (entries.length !== 1) errors.push(`marketplace has ${entries.length} meta-flow entries`);
138
+ if (!(await exists(path.join(tmp, ".agents", "skills", "meta-flow", "SKILL.md")))) {
139
+ errors.push("discoverable skill was not installed");
140
+ }
141
+ if (!(await exists(path.join(tmp, ".meta-flow", "scripts", "new_task.py")))) {
142
+ errors.push("support scripts were not installed");
143
+ }
144
+ if (!(await exists(path.join(tmp, ".meta-flow", "templates", "state.json")))) {
145
+ errors.push("support templates were not installed");
146
+ }
138
147
  const doctorCode = await runDoctor(["--scope", "repo", "--target", tmp]);
139
148
  if (doctorCode !== 0) errors.push("doctor returned non-zero after install");
140
149
  const uninstallCode = await runUninstall(["--scope", "repo", "--target", tmp, "--yes"]);
@@ -142,6 +151,15 @@ async function checkInstallUninstallSimulation() {
142
151
  return result("repo install/doctor/uninstall simulation", errors);
143
152
  }
144
153
 
154
+ async function exists(filePath) {
155
+ try {
156
+ await fs.access(filePath);
157
+ return true;
158
+ } catch {
159
+ return false;
160
+ }
161
+ }
162
+
145
163
  async function collectJsFiles(dir) {
146
164
  const entries = await fs.readdir(dir, { withFileTypes: true });
147
165
  const files = [];
@@ -0,0 +1,55 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { backupIfExists, copyDirSafe, pathExists, removeDirSafe } from "./fs_safe.js";
4
+
5
+ export const MANAGED_MARKER = ".meta-flow-managed.json";
6
+
7
+ export async function hasManagedMarker(dir) {
8
+ try {
9
+ const marker = JSON.parse(await fs.readFile(path.join(dir, MANAGED_MARKER), "utf8"));
10
+ return marker.installedBy === "meta-flow";
11
+ } catch {
12
+ return false;
13
+ }
14
+ }
15
+
16
+ export async function installManagedDir(source, target, options = {}) {
17
+ const { dryRun = false, force = false, backup = false, logger } = options;
18
+ if (await pathExists(target)) {
19
+ if (!(await hasManagedMarker(target)) && !force) {
20
+ return { installed: false, conflict: target };
21
+ }
22
+ if (force || backup) {
23
+ await backupIfExists(target, { dryRun, logger });
24
+ }
25
+ }
26
+
27
+ await copyDirSafe(source, target, { dryRun, logger });
28
+ await writeManagedMarker(target, { dryRun, logger });
29
+ return { installed: true, conflict: null };
30
+ }
31
+
32
+ export async function uninstallManagedDir(parent, target, options = {}) {
33
+ if (!(await pathExists(target))) {
34
+ return { removed: false, skipped: false };
35
+ }
36
+ if (!(await hasManagedMarker(target))) {
37
+ return { removed: false, skipped: true };
38
+ }
39
+ await removeDirSafe(parent, target, options);
40
+ return { removed: true, skipped: false };
41
+ }
42
+
43
+ async function writeManagedMarker(target, { dryRun = false, logger } = {}) {
44
+ const markerPath = path.join(target, MANAGED_MARKER);
45
+ const content = {
46
+ installedBy: "meta-flow",
47
+ source: "https://github.com/bx-h/meta-flow"
48
+ };
49
+ if (dryRun) {
50
+ logger?.info(`DRY-RUN write ${markerPath}`);
51
+ return;
52
+ }
53
+ await fs.mkdir(target, { recursive: true });
54
+ await fs.writeFile(markerPath, `${JSON.stringify(content, null, 2)}\n`, "utf8");
55
+ }
@@ -5,7 +5,7 @@ import { repoMarketplacePath } from "./paths.js";
5
5
  export function marketplaceEntry(targets) {
6
6
  return {
7
7
  name: "meta-flow",
8
- version: "0.1.0",
8
+ version: "0.1.1",
9
9
  source: {
10
10
  source: "local",
11
11
  path: targets.scope === "repo" ? repoMarketplacePath() : targets.pluginTarget
@@ -6,6 +6,9 @@ const thisFile = fileURLToPath(import.meta.url);
6
6
  export const packageRoot = path.resolve(path.dirname(thisFile), "../../..");
7
7
  export const pluginSource = path.join(packageRoot, "plugin");
8
8
  export const agentTemplatesSource = path.join(pluginSource, "agent-templates");
9
+ export const skillSource = path.join(pluginSource, "skills", "meta-flow");
10
+ export const scriptsSource = path.join(pluginSource, "scripts");
11
+ export const templatesSource = path.join(pluginSource, "templates");
9
12
  export const marketplaceSource = path.join(packageRoot, "marketplace", "marketplace.json");
10
13
  export const examplesRoot = path.join(packageRoot, "examples");
11
14
  export const sampleTaskRoot = path.join(examplesRoot, "sample-task");
@@ -25,9 +28,13 @@ export function resolveTargets({ scope = "repo", target } = {}) {
25
28
  pluginTarget: scope === "user"
26
29
  ? path.join(base, ".codex", "plugins", "meta-flow")
27
30
  : path.join(base, "plugins", "meta-flow"),
31
+ skillTarget: path.join(base, ".agents", "skills", "meta-flow"),
28
32
  marketplaceTarget: path.join(base, ".agents", "plugins", "marketplace.json"),
29
33
  agentsTarget: path.join(base, ".codex", "agents"),
30
34
  codexConfigTarget: path.join(base, ".codex", "config.toml"),
35
+ supportTarget: path.join(base, ".meta-flow"),
36
+ scriptsTarget: path.join(base, ".meta-flow", "scripts"),
37
+ templatesTarget: path.join(base, ".meta-flow", "templates"),
31
38
  tasksTarget: path.join(base, ".meta-flow", "tasks")
32
39
  };
33
40
  }
@@ -47,8 +47,8 @@ export async function validatePlugin(root = pluginSource) {
47
47
  if (manifest.name !== "meta-flow") {
48
48
  errors.push("plugin manifest name must be meta-flow");
49
49
  }
50
- if (manifest.version !== "0.1.0") {
51
- errors.push("plugin manifest version must be 0.1.0");
50
+ if (manifest.version !== "0.1.1") {
51
+ errors.push("plugin manifest version must be 0.1.1");
52
52
  }
53
53
  if (!/^---\n[\s\S]*?name:\s*meta-flow[\s\S]*?---/m.test(skill)) {
54
54
  errors.push("SKILL.md frontmatter must contain name: meta-flow");
@@ -0,0 +1,52 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { hasManagedMarker, installManagedDir, uninstallManagedDir } from "./managed_dirs.js";
4
+ import { pathExists } from "./fs_safe.js";
5
+ import { skillSource } from "./paths.js";
6
+
7
+ export async function installSkill(targets, options = {}) {
8
+ const result = await installManagedDir(skillSource, targets.skillTarget, options);
9
+ if (result.conflict || options.dryRun) {
10
+ return result;
11
+ }
12
+
13
+ const skillPath = path.join(targets.skillTarget, "SKILL.md");
14
+ const text = await fs.readFile(skillPath, "utf8");
15
+ await fs.writeFile(skillPath, materializeSkillText(text, targets), "utf8");
16
+ return result;
17
+ }
18
+
19
+ export async function uninstallSkill(targets, options = {}) {
20
+ return uninstallManagedDir(path.dirname(targets.skillTarget), targets.skillTarget, options);
21
+ }
22
+
23
+ export async function validateInstalledSkill(targets) {
24
+ const skillPath = path.join(targets.skillTarget, "SKILL.md");
25
+ const errors = [];
26
+ if (!(await pathExists(skillPath))) {
27
+ errors.push("discoverable SKILL.md missing");
28
+ return { skillPath, errors };
29
+ }
30
+ if (!(await hasManagedMarker(targets.skillTarget))) {
31
+ errors.push("discoverable skill is not managed by meta-flow installer");
32
+ }
33
+ const skill = await fs.readFile(skillPath, "utf8");
34
+ if (!/^---\n[\s\S]*?name:\s*meta-flow[\s\S]*?---/m.test(skill)) {
35
+ errors.push("discoverable SKILL.md frontmatter invalid");
36
+ }
37
+ if (!skill.includes(`${supportRootForSkill(targets)}/scripts/new_task.py`)) {
38
+ errors.push("discoverable SKILL.md points at the wrong support script path");
39
+ }
40
+ return { skillPath, errors };
41
+ }
42
+
43
+ function materializeSkillText(text, targets) {
44
+ const supportRoot = supportRootForSkill(targets);
45
+ return text
46
+ .replaceAll("python3 .meta-flow/scripts/", `python3 ${supportRoot}/scripts/`)
47
+ .replaceAll(".meta-flow/templates/", `${supportRoot}/templates/`);
48
+ }
49
+
50
+ function supportRootForSkill(targets) {
51
+ return targets.scope === "user" ? "~/.meta-flow" : ".meta-flow";
52
+ }
@@ -0,0 +1,38 @@
1
+ import path from "node:path";
2
+ import { hasManagedMarker, installManagedDir, uninstallManagedDir } from "./managed_dirs.js";
3
+ import { pathExists } from "./fs_safe.js";
4
+ import { scriptsSource, templatesSource } from "./paths.js";
5
+
6
+ export async function installSupportFiles(targets, options = {}) {
7
+ const scripts = await installManagedDir(scriptsSource, targets.scriptsTarget, options);
8
+ const templates = await installManagedDir(templatesSource, targets.templatesTarget, options);
9
+ return { scripts, templates };
10
+ }
11
+
12
+ export async function uninstallSupportFiles(targets, options = {}) {
13
+ const scripts = await uninstallManagedDir(targets.supportTarget, targets.scriptsTarget, options);
14
+ const templates = await uninstallManagedDir(targets.supportTarget, targets.templatesTarget, options);
15
+ return { scripts, templates };
16
+ }
17
+
18
+ export async function validateSupportFiles(targets) {
19
+ const required = [
20
+ path.join(targets.scriptsTarget, "new_task.py"),
21
+ path.join(targets.scriptsTarget, "validate_goal_contract.py"),
22
+ path.join(targets.templatesTarget, "state.json"),
23
+ path.join(targets.templatesTarget, "goal-contract.json")
24
+ ];
25
+ const errors = [];
26
+ for (const filePath of required) {
27
+ if (!(await pathExists(filePath))) {
28
+ errors.push(`missing ${filePath}`);
29
+ }
30
+ }
31
+ if (!(await hasManagedMarker(targets.scriptsTarget))) {
32
+ errors.push("scripts directory is not managed by meta-flow installer");
33
+ }
34
+ if (!(await hasManagedMarker(targets.templatesTarget))) {
35
+ errors.push("templates directory is not managed by meta-flow installer");
36
+ }
37
+ return { errors };
38
+ }