@floomhq/floom 1.0.55 → 1.0.56

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/dist/audit.js CHANGED
@@ -5,6 +5,7 @@ import { getJson, postJson } from "./lib/api.js";
5
5
  import { FloomError } from "./errors.js";
6
6
  import { c, symbols } from "./ui.js";
7
7
  const FIXTURE_RE = /\b(?:cli lifecycle audit fixture|launch gate|temp skill|test fixture|audit fixture)\b/i;
8
+ const ARCHIVE_CHUNK_SIZE = 1000;
8
9
  function contentHash(skill) {
9
10
  return skill.content_sha256 ?? skill.content_hash ?? undefined;
10
11
  }
@@ -71,10 +72,21 @@ function scoreSkill(skill, duplicateGroup) {
71
72
  reasons,
72
73
  ...(hash ? { content_hash: hash } : {}),
73
74
  ...(duplicateGroup ? { duplicate_group: duplicateGroup } : {}),
74
- recommended_action: score >= 70 ? "archive" : "review",
75
+ recommended_action: archiveRecommended(reasons) ? "archive" : "review",
75
76
  safe: !reasons.includes("possible_secret"),
76
77
  };
77
78
  }
79
+ function archiveRecommended(reasons) {
80
+ if (reasons.includes("possible_secret"))
81
+ return true;
82
+ if (reasons.includes("launch_or_test_fixture"))
83
+ return true;
84
+ if (reasons.includes("blank_title") && reasons.includes("duplicate_content_hash"))
85
+ return true;
86
+ if (reasons.includes("near_empty_body") && reasons.includes("duplicate_content_hash"))
87
+ return true;
88
+ return false;
89
+ }
78
90
  async function loadOwnedSkills() {
79
91
  const cfg = await readConfig();
80
92
  if (!cfg)
@@ -123,7 +135,20 @@ async function applyArchivePlan(planPath, yes, json) {
123
135
  throw new FloomError("Not signed in.", "Run `npx -y @floomhq/floom login` first.");
124
136
  const raw = JSON.parse(await readFile(planPath, "utf8"));
125
137
  const slugs = archiveSlugsFromPlan(raw);
126
- const payload = await postJson(`${resolveApiUrl(cfg)}/api/v1/me/skills/archive`, yes ? "archive skills" : "preview skill archive", cfg.accessToken, { slugs, dry_run: !yes });
138
+ const chunks = [];
139
+ for (let i = 0; i < slugs.length; i += ARCHIVE_CHUNK_SIZE)
140
+ chunks.push(slugs.slice(i, i + ARCHIVE_CHUNK_SIZE));
141
+ const responses = [];
142
+ for (const chunk of chunks) {
143
+ responses.push(await postJson(`${resolveApiUrl(cfg)}/api/v1/me/skills/archive`, yes ? "archive skills" : "preview skill archive", cfg.accessToken, { slugs: chunk, dry_run: !yes }));
144
+ }
145
+ const payload = {
146
+ dry_run: responses.every((response) => response.dry_run),
147
+ requested: responses.reduce((sum, response) => sum + response.requested, 0),
148
+ matched: responses.flatMap((response) => response.matched),
149
+ missing: responses.flatMap((response) => response.missing),
150
+ ...(yes ? { archived: responses.flatMap((response) => response.archived ?? []) } : {}),
151
+ };
127
152
  if (json) {
128
153
  process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
129
154
  return;
@@ -6,7 +6,7 @@ import { CONFIG_DIR } from "./config.js";
6
6
  import { FloomError } from "./errors.js";
7
7
  import { publishSkillPath } from "./publish.js";
8
8
  import { readSkillPackage } from "./package.js";
9
- import { manifestKey, markSynced, readSyncManifest, writeSyncManifest } from "./sync-manifest.js";
9
+ import { manifestKey, markSynced, readSyncManifest, unmarkSynced, writeSyncManifest } from "./sync-manifest.js";
10
10
  import { c, symbols } from "./ui.js";
11
11
  import { targetSkillsDir } from "./targets.js";
12
12
  const MANIFEST_VERSION = 1;
@@ -134,10 +134,13 @@ export async function pushWatchOnce(opts) {
134
134
  const pushManifest = await readPushManifest();
135
135
  const syncManifest = await readSyncManifest();
136
136
  const packages = await findSkillPackages(root);
137
+ const activePushKeys = new Set();
138
+ const activeSyncKeys = new Set();
137
139
  let published = 0;
138
140
  let updated = 0;
139
141
  let adopted = 0;
140
142
  let skipped = 0;
143
+ let syncManifestChanged = false;
141
144
  for (const packagePath of packages) {
142
145
  let skillPackage;
143
146
  try {
@@ -152,12 +155,18 @@ export async function pushWatchOnce(opts) {
152
155
  }
153
156
  const key = safeRootRelative(root, skillPackage.skillPath);
154
157
  const pushKey = pushManifestKey(opts.target, key);
158
+ activePushKeys.add(pushKey);
159
+ activeSyncKeys.add(manifestKey(root, skillPackage.skillPath));
160
+ for (const file of skillPackage.packageFiles) {
161
+ activeSyncKeys.add(manifestKey(root, join(dirname(skillPackage.skillPath), ...file.path.split("/"))));
162
+ }
155
163
  const hash = hashPackage(key, skillPackage.skillBody, skillPackage.packageFiles);
156
164
  const pushed = pushManifest.files[pushKey];
157
165
  if (pushed?.hash === hash) {
158
166
  if (!isUnchangedSyncedPackage(root, skillPackage, syncManifest)) {
159
167
  adopted += 1;
160
168
  markPackageSynced(root, skillPackage, syncManifest, pushed.slug);
169
+ syncManifestChanged = true;
161
170
  await writeSyncManifest(syncManifest);
162
171
  }
163
172
  else {
@@ -202,6 +211,7 @@ export async function pushWatchOnce(opts) {
202
211
  updated += 1;
203
212
  pushManifest.files[pushKey] = { hash, slug, path: key, pushedAt: new Date().toISOString() };
204
213
  markPackageSynced(root, skillPackage, syncManifest, slug);
214
+ syncManifestChanged = true;
205
215
  await writeSyncManifest(syncManifest);
206
216
  }
207
217
  catch (err) {
@@ -210,6 +220,7 @@ export async function pushWatchOnce(opts) {
210
220
  published += 1;
211
221
  pushManifest.files[pushKey] = { hash, slug: result.data.slug, path: key, pushedAt: new Date().toISOString() };
212
222
  markPackageSynced(root, skillPackage, syncManifest, result.data.slug);
223
+ syncManifestChanged = true;
213
224
  await writeSyncManifest(syncManifest);
214
225
  continue;
215
226
  }
@@ -225,6 +236,7 @@ export async function pushWatchOnce(opts) {
225
236
  published += 1;
226
237
  pushManifest.files[pushKey] = { hash, slug: result.data.slug, path: key, pushedAt: new Date().toISOString() };
227
238
  markPackageSynced(root, skillPackage, syncManifest, result.data.slug);
239
+ syncManifestChanged = true;
228
240
  await writeSyncManifest(syncManifest);
229
241
  }
230
242
  catch (err) {
@@ -234,7 +246,19 @@ export async function pushWatchOnce(opts) {
234
246
  }
235
247
  }
236
248
  }
249
+ for (const key of Object.keys(pushManifest.files)) {
250
+ if (key.startsWith(`${opts.target}:`) && !activePushKeys.has(key))
251
+ delete pushManifest.files[key];
252
+ }
253
+ for (const key of Object.keys(syncManifest.files)) {
254
+ if (!activeSyncKeys.has(key)) {
255
+ unmarkSynced(syncManifest, key);
256
+ syncManifestChanged = true;
257
+ }
258
+ }
237
259
  await writePushManifest(pushManifest);
260
+ if (syncManifestChanged)
261
+ await writeSyncManifest(syncManifest);
238
262
  if (!opts.quiet && (published > 0 || updated > 0 || adopted > 0)) {
239
263
  process.stdout.write(`${symbols.ok} Floom push watch: ${packages.length} scanned, ${published} published, ${updated} updated, ${adopted} adopted\n`);
240
264
  }
package/dist/sync.js CHANGED
@@ -547,7 +547,7 @@ async function loadSyncPayload(apiUrl, token) {
547
547
  if (!Array.isArray(payload.skills))
548
548
  throw new FloomError("Invalid sync response.");
549
549
  all.push(...payload.skills);
550
- fullSync = payload.full_sync === true;
550
+ fullSync = fullSync || payload.full_sync === true;
551
551
  if (!payload.next_cursor) {
552
552
  return { skills: all, full_sync: fullSync };
553
553
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@floomhq/floom",
3
- "version": "1.0.55",
3
+ "version": "1.0.56",
4
4
  "description": "Sync AI skills across agents and machines.",
5
5
  "license": "MIT",
6
6
  "type": "module",