@bx-h/meta-flow 0.1.0 → 0.1.2
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 +15 -0
- package/README.md +8 -2
- package/bin/meta-flow.js +3 -1
- package/marketplace/marketplace.json +2 -1
- package/package.json +1 -1
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/templates/milestone-plan.json +9 -3
- package/plugin/templates/task-list.json +9 -3
- package/plugin/templates/task-spec.json +9 -3
- package/src/cli/commands/doctor.js +84 -9
- package/src/cli/commands/install.js +18 -0
- package/src/cli/commands/uninstall.js +14 -0
- package/src/cli/commands/verify.js +45 -12
- package/src/cli/lib/managed_dirs.js +55 -0
- package/src/cli/lib/marketplace.js +7 -2
- package/src/cli/lib/paths.js +7 -0
- package/src/cli/lib/plugin.js +3 -2
- package/src/cli/lib/skill.js +52 -0
- package/src/cli/lib/support.js +72 -0
- package/src/cli/lib/version.js +2 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.1.2
|
|
4
|
+
|
|
5
|
+
- Make installed JSON templates pass their matching validation scripts.
|
|
6
|
+
- Add template validation to `verify` and `doctor`.
|
|
7
|
+
- Propagate command return codes through the CLI entrypoint.
|
|
8
|
+
- Tighten `doctor` checks for marketplace, plugin version, support scripts, and CLI-visible failures.
|
|
9
|
+
- Make the release workflow skip npm publish when the tag version already exists.
|
|
10
|
+
|
|
11
|
+
## 0.1.1
|
|
12
|
+
|
|
13
|
+
- Install `meta-flow` into `.agents/skills/meta-flow` so `$meta-flow` can be discovered directly.
|
|
14
|
+
- Install support scripts and templates into `.meta-flow/scripts` and `.meta-flow/templates`.
|
|
15
|
+
- Update `doctor` to check the discoverable Skill and runtime support files, not only the plugin bundle.
|
|
16
|
+
- Keep `.meta-flow/tasks` during uninstall while removing managed support files.
|
|
17
|
+
|
|
3
18
|
## 0.1.0
|
|
4
19
|
|
|
5
20
|
- 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.
|
|
29
|
+
npx @bx-h/meta-flow@0.1.2 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.
|
|
168
|
+
codex plugin marketplace add bx-h/meta-flow --ref v0.1.2
|
|
163
169
|
```
|
|
164
170
|
|
|
165
171
|
The npm installer still matters because it also materializes custom agent TOML files and validation scripts.
|
package/bin/meta-flow.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { main } from "../src/cli/index.js";
|
|
3
3
|
|
|
4
|
-
main(process.argv.slice(2)).
|
|
4
|
+
main(process.argv.slice(2)).then((code) => {
|
|
5
|
+
process.exitCode = code;
|
|
6
|
+
}).catch((error) => {
|
|
5
7
|
console.error(error?.message || String(error));
|
|
6
8
|
process.exitCode = 1;
|
|
7
9
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@bx-h/meta-flow",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
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": {
|
|
@@ -4,10 +4,16 @@
|
|
|
4
4
|
{
|
|
5
5
|
"id": "M1",
|
|
6
6
|
"objective": "string",
|
|
7
|
-
"scope": [
|
|
7
|
+
"scope": [
|
|
8
|
+
"string"
|
|
9
|
+
],
|
|
8
10
|
"out_of_scope": [],
|
|
9
|
-
"acceptance_checks": [
|
|
10
|
-
|
|
11
|
+
"acceptance_checks": [
|
|
12
|
+
"string"
|
|
13
|
+
],
|
|
14
|
+
"expected_outputs": [
|
|
15
|
+
"string"
|
|
16
|
+
],
|
|
11
17
|
"risk": "medium",
|
|
12
18
|
"status": "pending"
|
|
13
19
|
}
|
|
@@ -6,11 +6,17 @@
|
|
|
6
6
|
"id": "T1",
|
|
7
7
|
"objective": "string",
|
|
8
8
|
"inputs": [],
|
|
9
|
-
"expected_outputs": [
|
|
10
|
-
|
|
9
|
+
"expected_outputs": [
|
|
10
|
+
"string"
|
|
11
|
+
],
|
|
12
|
+
"allowed_files": [
|
|
13
|
+
"path-or-glob"
|
|
14
|
+
],
|
|
11
15
|
"forbidden_files": [],
|
|
12
16
|
"allowed_commands": [],
|
|
13
|
-
"acceptance_checks": [
|
|
17
|
+
"acceptance_checks": [
|
|
18
|
+
"string"
|
|
19
|
+
],
|
|
14
20
|
"dependencies": [],
|
|
15
21
|
"risk": "medium",
|
|
16
22
|
"repair_attempts": 0,
|
|
@@ -4,11 +4,17 @@
|
|
|
4
4
|
"concrete_task_id": "T1",
|
|
5
5
|
"objective": "string",
|
|
6
6
|
"inputs": [],
|
|
7
|
-
"expected_outputs": [
|
|
8
|
-
|
|
7
|
+
"expected_outputs": [
|
|
8
|
+
"string"
|
|
9
|
+
],
|
|
10
|
+
"allowed_files": [
|
|
11
|
+
"path-or-glob"
|
|
12
|
+
],
|
|
9
13
|
"forbidden_files": [],
|
|
10
14
|
"allowed_commands": [],
|
|
11
|
-
"acceptance_checks": [
|
|
15
|
+
"acceptance_checks": [
|
|
16
|
+
"string"
|
|
17
|
+
],
|
|
12
18
|
"dependencies": [],
|
|
13
19
|
"risk": "medium",
|
|
14
20
|
"repair_attempts": 0,
|
|
@@ -6,7 +6,11 @@ import { AGENT_FILES, validateAgentTemplate } from "../lib/agents.js";
|
|
|
6
6
|
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
|
+
import { marketplaceEntry } from "../lib/marketplace.js";
|
|
9
10
|
import { resolveTargets, sampleTaskRoot } from "../lib/paths.js";
|
|
11
|
+
import { validateInstalledSkill } from "../lib/skill.js";
|
|
12
|
+
import { HELP_SCRIPT_FILES, validateSupportFiles } from "../lib/support.js";
|
|
13
|
+
import { META_FLOW_VERSION } from "../lib/version.js";
|
|
10
14
|
|
|
11
15
|
export function doctorHelp() {
|
|
12
16
|
return `Usage: meta-flow doctor --scope repo|user [--target <path>] [--verbose]`;
|
|
@@ -27,10 +31,13 @@ export async function runDoctor(argv = []) {
|
|
|
27
31
|
|
|
28
32
|
results.push(checkCodexCli());
|
|
29
33
|
results.push(await checkPlugin(targets));
|
|
34
|
+
results.push(await checkDiscoverableSkill(targets));
|
|
35
|
+
results.push(await checkSupportFiles(targets));
|
|
30
36
|
results.push(await checkMarketplace(targets));
|
|
31
37
|
results.push(await checkAgents(targets));
|
|
32
38
|
results.push(await checkConfig(targets));
|
|
33
39
|
results.push(await checkPythonScripts(targets));
|
|
40
|
+
results.push(await checkInstalledTemplates(targets));
|
|
34
41
|
results.push(await checkSampleTask(targets));
|
|
35
42
|
|
|
36
43
|
for (const result of results) {
|
|
@@ -58,6 +65,9 @@ async function checkPlugin(targets) {
|
|
|
58
65
|
if (manifest.name !== "meta-flow") {
|
|
59
66
|
return { level: "FAIL", title: "plugin manifest invalid", message: "Expected name=meta-flow." };
|
|
60
67
|
}
|
|
68
|
+
if (manifest.version !== META_FLOW_VERSION) {
|
|
69
|
+
return { level: "FAIL", title: "plugin manifest version mismatch", message: `Expected version=${META_FLOW_VERSION}.` };
|
|
70
|
+
}
|
|
61
71
|
if (!(await pathExists(skillPath))) {
|
|
62
72
|
return { level: "FAIL", title: "SKILL.md missing", message: "Re-run install." };
|
|
63
73
|
}
|
|
@@ -68,6 +78,22 @@ async function checkPlugin(targets) {
|
|
|
68
78
|
return { level: "PASS", title: "plugin and skill present" };
|
|
69
79
|
}
|
|
70
80
|
|
|
81
|
+
async function checkDiscoverableSkill(targets) {
|
|
82
|
+
const result = await validateInstalledSkill(targets);
|
|
83
|
+
if (result.errors.length) {
|
|
84
|
+
return { level: "FAIL", title: "discoverable skill incomplete", message: result.errors.join("; ") };
|
|
85
|
+
}
|
|
86
|
+
return { level: "PASS", title: "discoverable skill present" };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
async function checkSupportFiles(targets) {
|
|
90
|
+
const result = await validateSupportFiles(targets);
|
|
91
|
+
if (result.errors.length) {
|
|
92
|
+
return { level: "FAIL", title: "support scripts/templates incomplete", message: result.errors.join("; ") };
|
|
93
|
+
}
|
|
94
|
+
return { level: "PASS", title: "support scripts/templates present" };
|
|
95
|
+
}
|
|
96
|
+
|
|
71
97
|
async function checkMarketplace(targets) {
|
|
72
98
|
if (!(await pathExists(targets.marketplaceTarget))) {
|
|
73
99
|
return { level: "FAIL", title: "marketplace missing", message: "Run install to create marketplace.json." };
|
|
@@ -79,9 +105,35 @@ async function checkMarketplace(targets) {
|
|
|
79
105
|
if (!entry) {
|
|
80
106
|
return { level: "FAIL", title: "marketplace entry missing", message: "Run install to add meta-flow entry." };
|
|
81
107
|
}
|
|
108
|
+
const expected = marketplaceEntry(targets);
|
|
109
|
+
const errors = [];
|
|
110
|
+
if (entry.version !== expected.version) {
|
|
111
|
+
errors.push(`version expected ${expected.version}, got ${entry.version ?? "missing"}`);
|
|
112
|
+
}
|
|
113
|
+
if (entry.source?.source !== expected.source.source) {
|
|
114
|
+
errors.push(`source.source expected ${expected.source.source}`);
|
|
115
|
+
}
|
|
116
|
+
if (entry.source?.path !== expected.source.path) {
|
|
117
|
+
errors.push(`source.path expected ${expected.source.path}, got ${entry.source?.path ?? "missing"}`);
|
|
118
|
+
}
|
|
119
|
+
const pluginPath = resolveMarketplacePluginPath(targets, entry);
|
|
120
|
+
if (!pluginPath || !(await pathExists(path.join(pluginPath, ".codex-plugin", "plugin.json")))) {
|
|
121
|
+
errors.push("source.path does not point to an installed plugin manifest");
|
|
122
|
+
}
|
|
123
|
+
if (errors.length) {
|
|
124
|
+
return { level: "FAIL", title: "marketplace entry invalid", message: errors.join("; ") };
|
|
125
|
+
}
|
|
82
126
|
return { level: "PASS", title: "marketplace entry present" };
|
|
83
127
|
}
|
|
84
128
|
|
|
129
|
+
function resolveMarketplacePluginPath(targets, entry) {
|
|
130
|
+
const sourcePath = entry?.source?.path;
|
|
131
|
+
if (!sourcePath || typeof sourcePath !== "string") {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
return path.isAbsolute(sourcePath) ? sourcePath : path.resolve(targets.target, sourcePath);
|
|
135
|
+
}
|
|
136
|
+
|
|
85
137
|
async function checkAgents(targets) {
|
|
86
138
|
const missing = [];
|
|
87
139
|
const invalid = [];
|
|
@@ -118,24 +170,47 @@ async function checkConfig(targets) {
|
|
|
118
170
|
}
|
|
119
171
|
|
|
120
172
|
async function checkPythonScripts(targets) {
|
|
121
|
-
const scriptsDir =
|
|
122
|
-
const
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
173
|
+
const scriptsDir = targets.scriptsTarget;
|
|
174
|
+
const python = resolvePython();
|
|
175
|
+
for (const fileName of HELP_SCRIPT_FILES) {
|
|
176
|
+
const scriptPath = path.join(scriptsDir, fileName);
|
|
177
|
+
if (!(await pathExists(scriptPath))) {
|
|
178
|
+
return { level: "FAIL", title: "Python scripts missing", message: `Missing ${scriptPath}` };
|
|
179
|
+
}
|
|
180
|
+
const result = spawnSync(python, [scriptPath, "--help"], pythonOptions());
|
|
181
|
+
if (result.status !== 0) {
|
|
182
|
+
return { level: "FAIL", title: "Python scripts not runnable", message: `${fileName}: ${result.stderr || result.stdout}` };
|
|
183
|
+
}
|
|
129
184
|
}
|
|
130
185
|
return { level: "PASS", title: "Python scripts runnable" };
|
|
131
186
|
}
|
|
132
187
|
|
|
188
|
+
async function checkInstalledTemplates(targets) {
|
|
189
|
+
const python = resolvePython();
|
|
190
|
+
const scripts = targets.scriptsTarget;
|
|
191
|
+
const templates = targets.templatesTarget;
|
|
192
|
+
const runs = [
|
|
193
|
+
[path.join(scripts, "validate_goal_contract.py"), path.join(templates, "goal-contract.json")],
|
|
194
|
+
[path.join(scripts, "validate_adjudication.py"), path.join(templates, "adjudication-report.json")],
|
|
195
|
+
[path.join(scripts, "validate_milestone_plan.py"), path.join(templates, "milestone-plan.json")],
|
|
196
|
+
[path.join(scripts, "validate_task_list.py"), path.join(templates, "task-list.json")],
|
|
197
|
+
[path.join(scripts, "validate_task_verification.py"), path.join(templates, "task-verification-report.json")]
|
|
198
|
+
];
|
|
199
|
+
for (const args of runs) {
|
|
200
|
+
const result = spawnSync(python, args, pythonOptions());
|
|
201
|
+
if (result.status !== 0) {
|
|
202
|
+
return { level: "FAIL", title: "installed templates invalid", message: result.stderr || result.stdout };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return { level: "PASS", title: "installed templates valid" };
|
|
206
|
+
}
|
|
207
|
+
|
|
133
208
|
async function checkSampleTask(targets) {
|
|
134
209
|
if (!(await pathExists(sampleTaskRoot))) {
|
|
135
210
|
return { level: "WARN", title: "sample task unavailable", message: "Package examples are not present." };
|
|
136
211
|
}
|
|
137
212
|
const python = resolvePython();
|
|
138
|
-
const scripts =
|
|
213
|
+
const scripts = targets.scriptsTarget;
|
|
139
214
|
const runs = [
|
|
140
215
|
[path.join(scripts, "validate_goal_contract.py"), path.join(sampleTaskRoot, "goal-contract.json")],
|
|
141
216
|
[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) {
|
|
@@ -9,6 +9,8 @@ import { runInstall } from "./install.js";
|
|
|
9
9
|
import { runUninstall } from "./uninstall.js";
|
|
10
10
|
import { validatePlugin } from "../lib/plugin.js";
|
|
11
11
|
import { packageRoot, pluginSource, sampleTaskRoot } from "../lib/paths.js";
|
|
12
|
+
import { HELP_SCRIPT_FILES } from "../lib/support.js";
|
|
13
|
+
import { META_FLOW_VERSION, PACKAGE_NAME } from "../lib/version.js";
|
|
12
14
|
|
|
13
15
|
export async function runVerify(argv = []) {
|
|
14
16
|
const lintOnly = argv.includes("--lint-only");
|
|
@@ -22,6 +24,7 @@ export async function runVerify(argv = []) {
|
|
|
22
24
|
checks.push(await checkPlugin());
|
|
23
25
|
checks.push(await checkAgents());
|
|
24
26
|
checks.push(await checkPythonHelp());
|
|
27
|
+
checks.push(await checkTemplates());
|
|
25
28
|
checks.push(await checkSampleTask());
|
|
26
29
|
checks.push(await checkDryRun());
|
|
27
30
|
checks.push(await checkInstallUninstallSimulation());
|
|
@@ -32,7 +35,8 @@ export async function runVerify(argv = []) {
|
|
|
32
35
|
async function checkPackageJson() {
|
|
33
36
|
const packageJson = JSON.parse(await fs.readFile(path.join(packageRoot, "package.json"), "utf8"));
|
|
34
37
|
const errors = [];
|
|
35
|
-
if (packageJson.name !==
|
|
38
|
+
if (packageJson.name !== PACKAGE_NAME) errors.push("unexpected package name");
|
|
39
|
+
if (packageJson.version !== META_FLOW_VERSION) errors.push(`unexpected package version: ${packageJson.version}`);
|
|
36
40
|
if (packageJson.scripts?.postinstall) errors.push("postinstall must not exist");
|
|
37
41
|
if (packageJson.bin?.["meta-flow"] !== "bin/meta-flow.js") errors.push("bin meta-flow is invalid");
|
|
38
42
|
if (packageJson.type !== "module") errors.push("type must be module");
|
|
@@ -65,19 +69,9 @@ async function checkAgents() {
|
|
|
65
69
|
}
|
|
66
70
|
|
|
67
71
|
async function checkPythonHelp() {
|
|
68
|
-
const scripts = [
|
|
69
|
-
"new_task.py",
|
|
70
|
-
"validate_goal_contract.py",
|
|
71
|
-
"aggregate_reviews.py",
|
|
72
|
-
"validate_adjudication.py",
|
|
73
|
-
"validate_milestone_plan.py",
|
|
74
|
-
"validate_task_list.py",
|
|
75
|
-
"validate_task_verification.py",
|
|
76
|
-
"status.py"
|
|
77
|
-
];
|
|
78
72
|
const python = resolvePython();
|
|
79
73
|
const errors = [];
|
|
80
|
-
for (const script of
|
|
74
|
+
for (const script of HELP_SCRIPT_FILES) {
|
|
81
75
|
const run = spawnSync(python, [path.join(pluginSource, "scripts", script), "--help"], pythonOptions());
|
|
82
76
|
if (run.status !== 0) {
|
|
83
77
|
errors.push(`${script} --help failed: ${run.stderr || run.stdout}`);
|
|
@@ -86,6 +80,27 @@ async function checkPythonHelp() {
|
|
|
86
80
|
return result("Python script --help", errors);
|
|
87
81
|
}
|
|
88
82
|
|
|
83
|
+
async function checkTemplates() {
|
|
84
|
+
const python = resolvePython();
|
|
85
|
+
const scripts = path.join(pluginSource, "scripts");
|
|
86
|
+
const templates = path.join(pluginSource, "templates");
|
|
87
|
+
const runs = [
|
|
88
|
+
[path.join(scripts, "validate_goal_contract.py"), path.join(templates, "goal-contract.json")],
|
|
89
|
+
[path.join(scripts, "validate_adjudication.py"), path.join(templates, "adjudication-report.json")],
|
|
90
|
+
[path.join(scripts, "validate_milestone_plan.py"), path.join(templates, "milestone-plan.json")],
|
|
91
|
+
[path.join(scripts, "validate_task_list.py"), path.join(templates, "task-list.json")],
|
|
92
|
+
[path.join(scripts, "validate_task_verification.py"), path.join(templates, "task-verification-report.json")]
|
|
93
|
+
];
|
|
94
|
+
const errors = [];
|
|
95
|
+
for (const args of runs) {
|
|
96
|
+
const run = spawnSync(python, args, pythonOptions());
|
|
97
|
+
if (run.status !== 0) {
|
|
98
|
+
errors.push(`${path.basename(args[1])} failed: ${run.stderr || run.stdout}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return result("templates validate against scripts", errors);
|
|
102
|
+
}
|
|
103
|
+
|
|
89
104
|
async function checkSampleTask() {
|
|
90
105
|
const python = resolvePython();
|
|
91
106
|
const tmp = await fs.mkdtemp(path.join(os.tmpdir(), "meta-flow-verify-"));
|
|
@@ -135,6 +150,15 @@ async function checkInstallUninstallSimulation() {
|
|
|
135
150
|
const marketplace = JSON.parse(await fs.readFile(path.join(tmp, ".agents", "plugins", "marketplace.json"), "utf8"));
|
|
136
151
|
const entries = marketplace.plugins.filter((plugin) => plugin.name === "meta-flow");
|
|
137
152
|
if (entries.length !== 1) errors.push(`marketplace has ${entries.length} meta-flow entries`);
|
|
153
|
+
if (!(await exists(path.join(tmp, ".agents", "skills", "meta-flow", "SKILL.md")))) {
|
|
154
|
+
errors.push("discoverable skill was not installed");
|
|
155
|
+
}
|
|
156
|
+
if (!(await exists(path.join(tmp, ".meta-flow", "scripts", "new_task.py")))) {
|
|
157
|
+
errors.push("support scripts were not installed");
|
|
158
|
+
}
|
|
159
|
+
if (!(await exists(path.join(tmp, ".meta-flow", "templates", "state.json")))) {
|
|
160
|
+
errors.push("support templates were not installed");
|
|
161
|
+
}
|
|
138
162
|
const doctorCode = await runDoctor(["--scope", "repo", "--target", tmp]);
|
|
139
163
|
if (doctorCode !== 0) errors.push("doctor returned non-zero after install");
|
|
140
164
|
const uninstallCode = await runUninstall(["--scope", "repo", "--target", tmp, "--yes"]);
|
|
@@ -142,6 +166,15 @@ async function checkInstallUninstallSimulation() {
|
|
|
142
166
|
return result("repo install/doctor/uninstall simulation", errors);
|
|
143
167
|
}
|
|
144
168
|
|
|
169
|
+
async function exists(filePath) {
|
|
170
|
+
try {
|
|
171
|
+
await fs.access(filePath);
|
|
172
|
+
return true;
|
|
173
|
+
} catch {
|
|
174
|
+
return false;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
145
178
|
async function collectJsFiles(dir) {
|
|
146
179
|
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
147
180
|
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
|
+
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { readJsonOrDefault, writeJsonPretty } from "./fs_safe.js";
|
|
2
|
+
import { pathExists, readJsonOrDefault, writeJsonPretty } from "./fs_safe.js";
|
|
3
3
|
import { repoMarketplacePath } from "./paths.js";
|
|
4
|
+
import { META_FLOW_VERSION } from "./version.js";
|
|
4
5
|
|
|
5
6
|
export function marketplaceEntry(targets) {
|
|
6
7
|
return {
|
|
7
8
|
name: "meta-flow",
|
|
8
|
-
version:
|
|
9
|
+
version: META_FLOW_VERSION,
|
|
9
10
|
source: {
|
|
10
11
|
source: "local",
|
|
11
12
|
path: targets.scope === "repo" ? repoMarketplacePath() : targets.pluginTarget
|
|
@@ -58,6 +59,9 @@ export async function updateMarketplace(targets, options = {}) {
|
|
|
58
59
|
}
|
|
59
60
|
|
|
60
61
|
export async function uninstallMarketplace(targets, options = {}) {
|
|
62
|
+
if (!(await pathExists(targets.marketplaceTarget))) {
|
|
63
|
+
return { updated: false, skipped: true };
|
|
64
|
+
}
|
|
61
65
|
const current = await readJsonOrDefault(targets.marketplaceTarget, {
|
|
62
66
|
name: "meta-flow-marketplace",
|
|
63
67
|
interface: { displayName: "Meta Flow Marketplace" },
|
|
@@ -65,6 +69,7 @@ export async function uninstallMarketplace(targets, options = {}) {
|
|
|
65
69
|
});
|
|
66
70
|
const next = removeMarketplaceEntry(current);
|
|
67
71
|
await writeJsonPretty(targets.marketplaceTarget, next, options);
|
|
72
|
+
return { updated: true, skipped: false };
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
export function describeMarketplace(targets) {
|
package/src/cli/lib/paths.js
CHANGED
|
@@ -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
|
}
|
package/src/cli/lib/plugin.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
|
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { backupIfExists, copyDirSafe, pathExists, removeDirSafe } from "./fs_safe.js";
|
|
4
4
|
import { pluginSource } from "./paths.js";
|
|
5
|
+
import { META_FLOW_VERSION } from "./version.js";
|
|
5
6
|
|
|
6
7
|
async function isMetaFlowPlugin(dir) {
|
|
7
8
|
try {
|
|
@@ -47,8 +48,8 @@ export async function validatePlugin(root = pluginSource) {
|
|
|
47
48
|
if (manifest.name !== "meta-flow") {
|
|
48
49
|
errors.push("plugin manifest name must be meta-flow");
|
|
49
50
|
}
|
|
50
|
-
if (manifest.version !==
|
|
51
|
-
errors.push(
|
|
51
|
+
if (manifest.version !== META_FLOW_VERSION) {
|
|
52
|
+
errors.push(`plugin manifest version must be ${META_FLOW_VERSION}`);
|
|
52
53
|
}
|
|
53
54
|
if (!/^---\n[\s\S]*?name:\s*meta-flow[\s\S]*?---/m.test(skill)) {
|
|
54
55
|
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,72 @@
|
|
|
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 const REQUIRED_SCRIPT_FILES = [
|
|
7
|
+
"_common.py",
|
|
8
|
+
"aggregate_reviews.py",
|
|
9
|
+
"new_task.py",
|
|
10
|
+
"status.py",
|
|
11
|
+
"validate_adjudication.py",
|
|
12
|
+
"validate_goal_contract.py",
|
|
13
|
+
"validate_milestone_plan.py",
|
|
14
|
+
"validate_task_list.py",
|
|
15
|
+
"validate_task_verification.py"
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
export const HELP_SCRIPT_FILES = REQUIRED_SCRIPT_FILES.filter((fileName) => fileName !== "_common.py");
|
|
19
|
+
|
|
20
|
+
export const REQUIRED_TEMPLATE_FILES = [
|
|
21
|
+
"adjudication-report.json",
|
|
22
|
+
"direction-evaluation.json",
|
|
23
|
+
"final-report.md",
|
|
24
|
+
"goal-contract.json",
|
|
25
|
+
"milestone-plan.json",
|
|
26
|
+
"proposal-summary.md",
|
|
27
|
+
"proposal.md",
|
|
28
|
+
"questioning-report.json",
|
|
29
|
+
"raw-request.md",
|
|
30
|
+
"review-aggregate.json",
|
|
31
|
+
"reviewer-report.json",
|
|
32
|
+
"state.json",
|
|
33
|
+
"task-execution-report.json",
|
|
34
|
+
"task-list.json",
|
|
35
|
+
"task-spec.json",
|
|
36
|
+
"task-verification-report.json"
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export async function installSupportFiles(targets, options = {}) {
|
|
40
|
+
const scripts = await installManagedDir(scriptsSource, targets.scriptsTarget, options);
|
|
41
|
+
const templates = await installManagedDir(templatesSource, targets.templatesTarget, options);
|
|
42
|
+
return { scripts, templates };
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export async function uninstallSupportFiles(targets, options = {}) {
|
|
46
|
+
const scripts = await uninstallManagedDir(targets.supportTarget, targets.scriptsTarget, options);
|
|
47
|
+
const templates = await uninstallManagedDir(targets.supportTarget, targets.templatesTarget, options);
|
|
48
|
+
return { scripts, templates };
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export async function validateSupportFiles(targets) {
|
|
52
|
+
const errors = [];
|
|
53
|
+
for (const fileName of REQUIRED_SCRIPT_FILES) {
|
|
54
|
+
const filePath = path.join(targets.scriptsTarget, fileName);
|
|
55
|
+
if (!(await pathExists(filePath))) {
|
|
56
|
+
errors.push(`missing ${filePath}`);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
for (const fileName of REQUIRED_TEMPLATE_FILES) {
|
|
60
|
+
const filePath = path.join(targets.templatesTarget, fileName);
|
|
61
|
+
if (!(await pathExists(filePath))) {
|
|
62
|
+
errors.push(`missing ${filePath}`);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (!(await hasManagedMarker(targets.scriptsTarget))) {
|
|
66
|
+
errors.push("scripts directory is not managed by meta-flow installer");
|
|
67
|
+
}
|
|
68
|
+
if (!(await hasManagedMarker(targets.templatesTarget))) {
|
|
69
|
+
errors.push("templates directory is not managed by meta-flow installer");
|
|
70
|
+
}
|
|
71
|
+
return { errors };
|
|
72
|
+
}
|