@flood-md/cli 0.2.1 → 0.2.3

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 (3) hide show
  1. package/README.md +9 -2
  2. package/lib/index.mjs +49 -34
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -4,8 +4,15 @@
4
4
  `npx @flood-md/cli doctor` checks package versions and agent file presence.
5
5
 
6
6
  Requires Node 22.12+. Installs matching tokens, React and agent packages; creates
7
- flood.json, flood/DESIGN.md and .agents/skills/flood-ui/SKILL.md. Refuses existing
8
- owned files or linked destinations. Does not rewrite source code or AGENTS.md.
7
+ flood.json and every owned guide listed in the matching agent manifest: DESIGN.md,
8
+ rules.json and manifest.json under flood/, plus SKILL.md and focused references
9
+ under .agents/skills/flood-ui/. Version 0.2.3 includes the expanded bundle and
10
+ spacing reference. Refuses existing owned files or linked destinations.
11
+ Does not rewrite source code, client settings or AGENTS.md.
9
12
  After init, import @flood-md/react/styles.css once at the application root.
10
13
  Wrap new interfaces in FloodRoot from @flood-md/react to select the dark
11
14
  foundations profile. Read the installed flood/DESIGN.md for the matching API.
15
+ Read the skill's routing reference for the task/source/check matrix. Doctor checks
16
+ the complete guide set and package alignment; it does not validate application
17
+ behavior, CSS imports or agent compliance. Merge customized guide upgrades
18
+ deliberately; init has no overwrite mode.
package/lib/index.mjs CHANGED
@@ -1,35 +1,43 @@
1
- import { readFile, writeFile, mkdir, lstat, access } from "node:fs/promises";
1
+ import { readFile, writeFile, mkdir, lstat } from "node:fs/promises";
2
2
  import { resolve, join } from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
4
  import { spawnSync } from "node:child_process";
5
5
 
6
- export const VERSION = "0.2.1";
6
+ export const VERSION = "0.2.3";
7
7
  const packages = ["@flood-md/tokens", "@flood-md/react", "@flood-md/agent"];
8
8
  const json = async (path) => JSON.parse(await readFile(path, "utf8"));
9
- const exists = async (path) => {
10
- try {
11
- await access(path);
12
- return true;
13
- } catch {
14
- return false;
15
- }
16
- };
17
9
  const agentFile = (name) =>
18
10
  fileURLToPath(import.meta.resolve(`@flood-md/agent/${name}`));
11
+ const agentManifest = await json(agentFile("manifest.json"));
12
+ const safeRelative = (path) => typeof path === "string" && /^[a-zA-Z0-9._/-]+$/.test(path) &&
13
+ !path.startsWith("/") && !path.split("/").some(part => !part || part === "." || part === "..");
14
+ if (agentManifest.schemaVersion !== 1 || agentManifest.version !== VERSION || !Array.isArray(agentManifest.files) ||
15
+ !agentManifest.files.length || agentManifest.files.some(file =>
16
+ !safeRelative(file.source) || !safeRelative(file.target) ||
17
+ !(file.target.startsWith("flood/") || file.target.startsWith(".agents/skills/flood-ui/"))) ||
18
+ new Set(agentManifest.files.map(file => file.target)).size !== agentManifest.files.length)
19
+ throw new Error("Invalid Flood agent file manifest.");
20
+ export const AGENT_FILES = Object.freeze(agentManifest.files.map(file => Object.freeze({ ...file })));
19
21
 
20
- // Refuse links and existing instruction files before changing any project data.
21
- export async function checkTarget(root, relative) {
22
+ async function inspectTarget(root, relative) {
22
23
  let current = root;
24
+ let info;
23
25
  for (const part of relative.split("/")) {
24
26
  current = join(current, part);
25
27
  try {
26
- if ((await lstat(current)).isSymbolicLink())
27
- throw new Error(`Refusing linked path: ${relative}`);
28
+ info = await lstat(current);
29
+ if (info.isSymbolicLink()) throw new Error(`Refusing linked path: ${relative}`);
28
30
  } catch (error) {
29
- if (error.code !== "ENOENT") throw error;
31
+ if (error.code === "ENOENT") return undefined;
32
+ throw error;
30
33
  }
31
34
  }
32
- if (await exists(join(root, relative)))
35
+ return info;
36
+ }
37
+
38
+ // Refuse links and existing instruction files before changing any project data.
39
+ export async function checkTarget(root, relative) {
40
+ if (await inspectTarget(root, relative))
33
41
  throw new Error(`${relative} already exists. No files were overwritten.`);
34
42
  }
35
43
 
@@ -67,16 +75,16 @@ export async function initProject(
67
75
  manifest.dependencies?.next || manifest.devDependencies?.next
68
76
  ? "next"
69
77
  : "react";
70
- const files = [
71
- "flood.json",
72
- "flood/DESIGN.md",
73
- ".agents/skills/flood-ui/SKILL.md",
74
- ];
78
+ const files = ["flood.json", ...AGENT_FILES.map(file => file.target)];
75
79
  for (const file of files) await checkTarget(root, file);
76
80
  log(
77
81
  `Flood ${VERSION} · ${framework} · npm\nInstall: ${packages.map((p) => `${p}@${VERSION}`).join(", ")}\nCreate: ${files.join(", ")}`,
78
82
  );
79
83
  if (dryRun) return;
84
+ // Read the complete bundled guide set before npm or owned writes can change anything.
85
+ const guides = await Promise.all(AGENT_FILES.map(async file => ({
86
+ ...file, content: await readFile(agentFile(file.source)),
87
+ })));
80
88
  await installer(root);
81
89
  const config = {
82
90
  $schema: "https://flood.md/schema/config.json",
@@ -89,27 +97,24 @@ export async function initProject(
89
97
  };
90
98
  // Recheck after npm has run, before creating owned files.
91
99
  for (const file of files) await checkTarget(root, file);
92
- await mkdir(join(root, "flood"), { recursive: true });
93
- await mkdir(join(root, ".agents/skills/flood-ui"), { recursive: true });
100
+ for (const file of files) await mkdir(resolve(root, file, ".."), { recursive: true });
94
101
  await writeFile(
95
102
  join(root, files[0]),
96
103
  JSON.stringify(config, null, 2) + "\n",
97
104
  { flag: "wx" },
98
105
  );
99
- await writeFile(
100
- join(root, files[1]),
101
- await readFile(agentFile("DESIGN.md")),
102
- { flag: "wx" },
103
- );
104
- await writeFile(join(root, files[2]), await readFile(agentFile("SKILL.md")), {
105
- flag: "wx",
106
- });
106
+ for (const file of guides) {
107
+ await checkTarget(root, file.target);
108
+ await writeFile(join(root, file.target), file.content, { flag: "wx" });
109
+ }
107
110
  log(
108
111
  `\nConnected. Import '@flood-md/react/styles.css' in ${framework === "next" ? "app/layout.tsx" : "your app entry"}.\nWrap your interface in <FloodRoot> from '@flood-md/react' to use the dark foundations.\nRead flood/DESIGN.md for component APIs. Run npx @flood-md/cli doctor to verify.`,
109
112
  );
110
113
  }
111
114
 
112
115
  export async function doctor(root, log = console.log) {
116
+ const configInfo = await inspectTarget(root, "flood.json");
117
+ if (!configInfo?.isFile()) throw new Error("No regular flood.json. Run init first.");
113
118
  const config = await json(join(root, "flood.json")).catch(() => {
114
119
  throw new Error("No flood.json. Run init first.");
115
120
  });
@@ -131,11 +136,21 @@ export async function doctor(root, log = console.log) {
131
136
  `${name}: expected ${config.version}, found ${found?.version ?? "missing"}`,
132
137
  );
133
138
  }
134
- for (const name of ["flood/DESIGN.md", ".agents/skills/flood-ui/SKILL.md"])
135
- if (!(await exists(join(root, name)))) failures.push(`${name}: missing`);
139
+ for (const { target } of AGENT_FILES) {
140
+ try {
141
+ const info = await inspectTarget(root, target);
142
+ if (!info?.isFile()) failures.push(`${target}: missing or not a regular file`);
143
+ } catch (error) { failures.push(error.message); }
144
+ }
145
+ if (!failures.length) {
146
+ const installed = await json(join(root, "flood/manifest.json")).catch(() => undefined);
147
+ if (installed?.version !== config.version) failures.push("Agent manifest version differs; compare and merge the matching guide bundle.");
148
+ const header = (await readFile(join(root, "flood/DESIGN.md"), "utf8")).split(/\r?\n/, 1)[0];
149
+ if (!header.endsWith("\u00b7 " + config.version)) failures.push("DESIGN.md version differs; preserve custom edits while updating the contract.");
150
+ }
136
151
  if (failures.length) throw new Error(failures.join("\n"));
137
152
  log(
138
- `Flood ${config.version}: package versions and agent files are aligned.\nCheck the stylesheet import in your application entry; doctor does not parse application code.`,
153
+ `Flood ${config.version}: package versions and ${AGENT_FILES.length} agent files are aligned.\nCheck the stylesheet import in your application entry; doctor does not parse application code or validate UI.`,
139
154
  );
140
155
  }
141
156
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@flood-md/cli",
3
- "version": "0.2.1",
3
+ "version": "0.2.3",
4
4
  "type": "module",
5
5
  "description": "Connect Flood to React and Next.js projects.",
6
6
  "license": "MIT",
@@ -21,7 +21,7 @@
21
21
  "node": ">=22.12.0"
22
22
  },
23
23
  "dependencies": {
24
- "@flood-md/agent": "0.2.1"
24
+ "@flood-md/agent": "0.2.3"
25
25
  },
26
26
  "publishConfig": {
27
27
  "access": "public"