@nuucognition/flint-cli 0.5.0-alpha.1 → 0.5.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.
@@ -7,7 +7,7 @@ import {
7
7
  registerFlint,
8
8
  registerFlintByPath,
9
9
  updateFlintEntry
10
- } from "./chunk-O7OVKLLV.js";
10
+ } from "./chunk-C66KJDI7.js";
11
11
  import {
12
12
  exists,
13
13
  runConcurrent
@@ -15,7 +15,7 @@ import {
15
15
  import {
16
16
  ensureMetadataDirs,
17
17
  syncSourceRepoMetadata
18
- } from "./chunk-PONDZIXS.js";
18
+ } from "./chunk-M3NSYVYR.js";
19
19
  import {
20
20
  addPlateDeclaration,
21
21
  addShardToConfig,
@@ -39,18 +39,18 @@ import {
39
39
  toKebabCase,
40
40
  writeFlintJson,
41
41
  writeFlintToml
42
- } from "./chunk-XCVQLFHY.js";
42
+ } from "./chunk-CBGQBE6C.js";
43
43
 
44
44
  // ../../packages/flint/dist/index.js
45
- import { mkdir as mkdir2, stat as stat8 } from "fs/promises";
46
- import { join as join10 } from "path";
45
+ import { mkdir as mkdir2, stat as stat9 } from "fs/promises";
46
+ import { join as join11 } from "path";
47
47
  import { stat as stat22 } from "fs/promises";
48
48
  import { dirname as dirname2, isAbsolute, join as join22 } from "path";
49
49
  import { mkdir as mkdir22, writeFile as writeFile5 } from "fs/promises";
50
50
  import { join as join32 } from "path";
51
51
  import { exec } from "child_process";
52
52
  import { promisify } from "util";
53
- import { readdir as readdir7, stat as stat32, access as access2 } from "fs/promises";
53
+ import { readdir as readdir8, stat as stat32, access as access2 } from "fs/promises";
54
54
  import { join as join42, resolve } from "path";
55
55
  import { homedir } from "os";
56
56
  import { readFile as readFile5, writeFile as writeFile22, mkdir as mkdir32 } from "fs/promises";
@@ -76,7 +76,7 @@ import { rm as rm32 } from "fs/promises";
76
76
  import { execFile } from "child_process";
77
77
  import { cp as cp3, mkdir as mkdir9, readdir as readdir42, readFile as readFile6, rename as rename22, rm as rm52, stat as stat72, writeFile as writeFile7 } from "fs/promises";
78
78
  import { homedir as homedir2 } from "os";
79
- import { basename as basename3, dirname as dirname3, join as join11, resolve as resolve4 } from "path";
79
+ import { basename as basename3, dirname as dirname3, join as join112, resolve as resolve4 } from "path";
80
80
  import { promisify as promisify4 } from "util";
81
81
  import { readFile as readFile52, writeFile as writeFile6, mkdir as mkdir8, rm as rm42, stat as stat62 } from "fs/promises";
82
82
  import { join as join102 } from "path";
@@ -88,7 +88,7 @@ import { parse as parseYaml4 } from "yaml";
88
88
 
89
89
  // ../../packages/flint-migrations/dist/index.js
90
90
  import { randomUUID as randomUUID2 } from "crypto";
91
- import { join as join9, dirname } from "path";
91
+ import { join as join10, dirname } from "path";
92
92
  import { access, readFile as readFile4, writeFile as writeFile4, unlink, mkdir as mkdir5 } from "fs/promises";
93
93
  import { join } from "path";
94
94
  import { rename, mkdir, stat, cp, rm } from "fs/promises";
@@ -107,6 +107,8 @@ import { join as join7 } from "path";
107
107
  import { readdir as readdir5, rm as rm5, mkdir as mkdir4 } from "fs/promises";
108
108
  import { rename as rename4, readdir as readdir6, rmdir, stat as stat7 } from "fs/promises";
109
109
  import { join as join8 } from "path";
110
+ import { join as join9 } from "path";
111
+ import { readdir as readdir7, stat as stat8 } from "fs/promises";
110
112
  function compareVersions(a, b) {
111
113
  const partsA = a.split(".").map(Number);
112
114
  const partsB = b.split(".").map(Number);
@@ -1827,6 +1829,212 @@ var sourceReposRestructure = {
1827
1829
  return true;
1828
1830
  }
1829
1831
  };
1832
+ var MIGRATION_ID3 = "unify-session-tracking-0.4.0-0.5.0";
1833
+ var SESSION_FIELD_PATTERN = /^[a-z]+-sessions$/;
1834
+ var UNIFIED_FIELD = "orbh-sessions";
1835
+ async function findMarkdownFiles3(dir) {
1836
+ const results = [];
1837
+ try {
1838
+ const entries = await readdir7(dir, { withFileTypes: true });
1839
+ for (const entry of entries) {
1840
+ const fullPath = join9(dir, entry.name);
1841
+ if (entry.isDirectory()) {
1842
+ const nested = await findMarkdownFiles3(fullPath);
1843
+ results.push(...nested);
1844
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
1845
+ results.push(fullPath);
1846
+ }
1847
+ }
1848
+ } catch {
1849
+ }
1850
+ return results;
1851
+ }
1852
+ function parseFrontmatter(content) {
1853
+ const lines = content.split("\n");
1854
+ if (lines[0]?.trim() !== "---") {
1855
+ return { hasFrontmatter: false, frontmatterLines: [], bodyStart: 0 };
1856
+ }
1857
+ let endIndex = -1;
1858
+ for (let i = 1; i < lines.length; i++) {
1859
+ if (lines[i]?.trim() === "---") {
1860
+ endIndex = i;
1861
+ break;
1862
+ }
1863
+ }
1864
+ if (endIndex === -1) {
1865
+ return { hasFrontmatter: false, frontmatterLines: [], bodyStart: 0 };
1866
+ }
1867
+ return {
1868
+ hasFrontmatter: true,
1869
+ frontmatterLines: lines.slice(1, endIndex),
1870
+ bodyStart: endIndex + 1
1871
+ };
1872
+ }
1873
+ function extractSessionValues(lines, startIdx) {
1874
+ const ids = [];
1875
+ const keyLine = lines[startIdx];
1876
+ const inlineMatch = keyLine.match(/:\s*\[(.+)\]\s*$/);
1877
+ if (inlineMatch) {
1878
+ const entries = inlineMatch[1].split(",");
1879
+ for (const e of entries) {
1880
+ const trimmed = e.trim().replace(/^["']|["']$/g, "");
1881
+ if (trimmed) ids.push(trimmed);
1882
+ }
1883
+ return { ids, endIdx: startIdx };
1884
+ }
1885
+ let i = startIdx + 1;
1886
+ while (i < lines.length) {
1887
+ const line = lines[i];
1888
+ const listMatch = line.match(/^\s+-\s+(.+)$/);
1889
+ if (!listMatch) break;
1890
+ const val = listMatch[1].trim().replace(/^["']|["']$/g, "");
1891
+ if (val) ids.push(val);
1892
+ i++;
1893
+ }
1894
+ return { ids, endIdx: i - 1 };
1895
+ }
1896
+ function rewriteFrontmatter(content) {
1897
+ const { hasFrontmatter, frontmatterLines, bodyStart } = parseFrontmatter(content);
1898
+ if (!hasFrontmatter) return null;
1899
+ const lines = content.split("\n");
1900
+ const allIds = [];
1901
+ const linesToRemove = /* @__PURE__ */ new Set();
1902
+ let hasOldFields = false;
1903
+ let existingOrbhIdx = -1;
1904
+ for (let i = 0; i < frontmatterLines.length; i++) {
1905
+ const line = frontmatterLines[i];
1906
+ const keyMatch = line.match(/^([a-z]+-sessions)\s*:/);
1907
+ if (!keyMatch) continue;
1908
+ const fieldName = keyMatch[1];
1909
+ const { ids, endIdx } = extractSessionValues(frontmatterLines, i);
1910
+ if (fieldName === UNIFIED_FIELD) {
1911
+ existingOrbhIdx = i;
1912
+ allIds.push(...ids);
1913
+ for (let j = i; j <= endIdx; j++) {
1914
+ linesToRemove.add(j + 1);
1915
+ }
1916
+ } else if (SESSION_FIELD_PATTERN.test(fieldName)) {
1917
+ hasOldFields = true;
1918
+ allIds.push(...ids);
1919
+ for (let j = i; j <= endIdx; j++) {
1920
+ linesToRemove.add(j + 1);
1921
+ }
1922
+ }
1923
+ }
1924
+ if (!hasOldFields) return null;
1925
+ const seen = /* @__PURE__ */ new Set();
1926
+ const uniqueIds = [];
1927
+ for (const id of allIds) {
1928
+ const normalized = id.trim();
1929
+ if (normalized && !seen.has(normalized)) {
1930
+ seen.add(normalized);
1931
+ uniqueIds.push(normalized);
1932
+ }
1933
+ }
1934
+ const newLines = ["---"];
1935
+ let insertedOrbh = false;
1936
+ for (let i = 1; i < bodyStart; i++) {
1937
+ if (i === bodyStart - 1) {
1938
+ if (!insertedOrbh && uniqueIds.length > 0) {
1939
+ newLines.push(`${UNIFIED_FIELD}:`);
1940
+ for (const id of uniqueIds) {
1941
+ newLines.push(` - "${id}"`);
1942
+ }
1943
+ insertedOrbh = true;
1944
+ }
1945
+ newLines.push(lines[i]);
1946
+ break;
1947
+ }
1948
+ if (linesToRemove.has(i)) {
1949
+ if (!insertedOrbh && uniqueIds.length > 0) {
1950
+ newLines.push(`${UNIFIED_FIELD}:`);
1951
+ for (const id of uniqueIds) {
1952
+ newLines.push(` - "${id}"`);
1953
+ }
1954
+ insertedOrbh = true;
1955
+ }
1956
+ continue;
1957
+ }
1958
+ newLines.push(lines[i]);
1959
+ }
1960
+ for (let i = bodyStart; i < lines.length; i++) {
1961
+ newLines.push(lines[i]);
1962
+ }
1963
+ return newLines.join("\n");
1964
+ }
1965
+ var unifySessionTracking = {
1966
+ id: MIGRATION_ID3,
1967
+ name: "Unify Session Tracking",
1968
+ description: "Merges per-runtime session fields (claude-sessions, codex-sessions) into a single orbh-sessions field",
1969
+ from: "0.4.0",
1970
+ to: "0.5.0",
1971
+ async check(ctx) {
1972
+ try {
1973
+ await stat8(join9(ctx.flintPath, "Mesh"));
1974
+ } catch {
1975
+ return false;
1976
+ }
1977
+ const meshDir = join9(ctx.flintPath, "Mesh");
1978
+ const files = await findMarkdownFiles3(meshDir);
1979
+ for (const file of files) {
1980
+ const relativePath = file.slice(ctx.flintPath.length + 1);
1981
+ const content = await ctx.read(relativePath);
1982
+ const result = rewriteFrontmatter(content);
1983
+ if (result !== null) return true;
1984
+ }
1985
+ return false;
1986
+ },
1987
+ async run(ctx) {
1988
+ const changes = [];
1989
+ try {
1990
+ const meshDir = join9(ctx.flintPath, "Mesh");
1991
+ const files = await findMarkdownFiles3(meshDir);
1992
+ let modifiedCount = 0;
1993
+ for (const file of files) {
1994
+ const relativePath = file.slice(ctx.flintPath.length + 1);
1995
+ const content = await ctx.read(relativePath);
1996
+ const rewritten = rewriteFrontmatter(content);
1997
+ if (rewritten !== null) {
1998
+ await ctx.write(relativePath, rewritten);
1999
+ modifiedCount++;
2000
+ }
2001
+ }
2002
+ if (modifiedCount > 0) {
2003
+ changes.push(
2004
+ `Merged per-runtime session fields into orbh-sessions in ${modifiedCount} file(s)`
2005
+ );
2006
+ } else {
2007
+ changes.push("No files contained per-runtime session fields");
2008
+ }
2009
+ return { success: true, changes };
2010
+ } catch (err) {
2011
+ const error = err instanceof Error ? err.message : String(err);
2012
+ ctx.error(`Migration failed: ${error}`);
2013
+ return { success: false, changes, warnings: [`Error: ${error}`] };
2014
+ }
2015
+ },
2016
+ async verify(ctx) {
2017
+ const meshDir = join9(ctx.flintPath, "Mesh");
2018
+ const files = await findMarkdownFiles3(meshDir);
2019
+ for (const file of files) {
2020
+ const relativePath = file.slice(ctx.flintPath.length + 1);
2021
+ const content = await ctx.read(relativePath);
2022
+ const { hasFrontmatter, frontmatterLines } = parseFrontmatter(content);
2023
+ if (!hasFrontmatter) continue;
2024
+ for (const line of frontmatterLines) {
2025
+ const keyMatch = line.match(/^([a-z]+-sessions)\s*:/);
2026
+ if (keyMatch && keyMatch[1] !== UNIFIED_FIELD) {
2027
+ ctx.error(
2028
+ `Verification failed: ${relativePath} still has ${keyMatch[1]}`
2029
+ );
2030
+ return false;
2031
+ }
2032
+ }
2033
+ }
2034
+ ctx.log("Verification passed: no per-runtime session fields remain");
2035
+ return true;
2036
+ }
2037
+ };
1830
2038
  var migrations = [
1831
2039
  // 0.0.0 → 0.2.0
1832
2040
  workspaceTomlToReferences,
@@ -1839,8 +2047,19 @@ var migrations = [
1839
2047
  sourcingSystem,
1840
2048
  // 0.3.0 → 0.4.0
1841
2049
  unifiedReferences,
1842
- sourceReposRestructure
2050
+ sourceReposRestructure,
2051
+ // 0.4.0 → 0.5.0
2052
+ unifySessionTracking
1843
2053
  ];
2054
+ var versionStepSealed = {
2055
+ "0.2.0": true,
2056
+ "0.3.0": true,
2057
+ "0.4.0": true,
2058
+ "0.5.0": false
2059
+ };
2060
+ function isVersionStepSealed(version) {
2061
+ return versionStepSealed[version] ?? false;
2062
+ }
1844
2063
  function getVersionSteps() {
1845
2064
  const seen = /* @__PURE__ */ new Set();
1846
2065
  const steps = [];
@@ -1869,28 +2088,28 @@ function createMigrationContext(flintPath, options = {}) {
1869
2088
  error,
1870
2089
  async exists(path) {
1871
2090
  try {
1872
- await access(join9(flintPath, path));
2091
+ await access(join10(flintPath, path));
1873
2092
  return true;
1874
2093
  } catch {
1875
2094
  return false;
1876
2095
  }
1877
2096
  },
1878
2097
  async read(path) {
1879
- return readFile4(join9(flintPath, path), "utf-8");
2098
+ return readFile4(join10(flintPath, path), "utf-8");
1880
2099
  },
1881
2100
  async write(path, content) {
1882
- const fullPath = join9(flintPath, path);
2101
+ const fullPath = join10(flintPath, path);
1883
2102
  await mkdir5(dirname(fullPath), { recursive: true });
1884
2103
  await writeFile4(fullPath, content, "utf-8");
1885
2104
  },
1886
2105
  async delete(path) {
1887
- await unlink(join9(flintPath, path));
2106
+ await unlink(join10(flintPath, path));
1888
2107
  }
1889
2108
  };
1890
2109
  }
1891
2110
  async function readFlintJson3(flintPath) {
1892
2111
  try {
1893
- const content = await readFile4(join9(flintPath, "flint.json"), "utf-8");
2112
+ const content = await readFile4(join10(flintPath, "flint.json"), "utf-8");
1894
2113
  return JSON.parse(content);
1895
2114
  } catch (error) {
1896
2115
  if (error.code === "ENOENT") {
@@ -1907,7 +2126,7 @@ function createFlintJson2() {
1907
2126
  };
1908
2127
  }
1909
2128
  async function writeFlintJson2(flintPath, json) {
1910
- await writeFile4(join9(flintPath, "flint.json"), JSON.stringify(json, null, 2) + "\n");
2129
+ await writeFile4(join10(flintPath, "flint.json"), JSON.stringify(json, null, 2) + "\n");
1911
2130
  }
1912
2131
  function ensureFlintJsonIdentity(json) {
1913
2132
  if (!json.id) json.id = randomUUID2();
@@ -2059,11 +2278,81 @@ async function runMigrations(flintPath, options = {}) {
2059
2278
  (m) => result.applied.some((a) => a.id === m.id) || result.skipped.some((s) => s.id === m.id)
2060
2279
  );
2061
2280
  if (allComplete) {
2062
- await bumpVersion(flintPath, step);
2281
+ if (isVersionStepSealed(step)) {
2282
+ await bumpVersion(flintPath, step);
2283
+ } else {
2284
+ const log = options.log || (() => {
2285
+ });
2286
+ log(`Version step ${step} is unsealed \u2014 migrations applied but version not advanced. Seal the version step when all planned migrations are implemented.`);
2287
+ }
2063
2288
  }
2064
2289
  }
2065
2290
  return result;
2066
2291
  }
2292
+ async function runSingleMigration(flintPath, migrationId, options = {}) {
2293
+ const result = {
2294
+ applied: [],
2295
+ skipped: [],
2296
+ failed: [],
2297
+ pending: []
2298
+ };
2299
+ const migration = migrations.find((m) => m.id === migrationId);
2300
+ if (!migration) {
2301
+ result.failed.push({
2302
+ id: migrationId,
2303
+ name: migrationId,
2304
+ error: `No migration found with ID "${migrationId}"`
2305
+ });
2306
+ return result;
2307
+ }
2308
+ const alreadyDone = await hasMigration(flintPath, migration);
2309
+ if (alreadyDone) {
2310
+ await canonicalizeMigrationRecord(flintPath, migration);
2311
+ result.skipped.push({ id: migration.id, name: migration.name });
2312
+ return result;
2313
+ }
2314
+ const ctx = createMigrationContext(flintPath, options);
2315
+ try {
2316
+ const shouldRun = await migration.check(ctx);
2317
+ if (!shouldRun) {
2318
+ await recordMigration(flintPath, migration.id);
2319
+ result.skipped.push({ id: migration.id, name: migration.name });
2320
+ return result;
2321
+ }
2322
+ const runResult = await migration.run(ctx);
2323
+ if (!runResult.success) {
2324
+ result.failed.push({
2325
+ id: migration.id,
2326
+ name: migration.name,
2327
+ error: "Migration run() returned success: false"
2328
+ });
2329
+ return result;
2330
+ }
2331
+ const verified = await migration.verify(ctx);
2332
+ if (!verified) {
2333
+ result.failed.push({
2334
+ id: migration.id,
2335
+ name: migration.name,
2336
+ error: "Migration verification failed"
2337
+ });
2338
+ return result;
2339
+ }
2340
+ await recordMigration(flintPath, migration.id);
2341
+ result.applied.push({
2342
+ id: migration.id,
2343
+ name: migration.name,
2344
+ changes: runResult.changes,
2345
+ warnings: runResult.warnings
2346
+ });
2347
+ } catch (err) {
2348
+ result.failed.push({
2349
+ id: migration.id,
2350
+ name: migration.name,
2351
+ error: err instanceof Error ? err.message : String(err)
2352
+ });
2353
+ }
2354
+ return result;
2355
+ }
2067
2356
 
2068
2357
  // ../../packages/flint/dist/index.js
2069
2358
  import { join as join12, resolve as resolve5, isAbsolute as isAbsolute2 } from "path";
@@ -2085,7 +2374,7 @@ import { rename as rename32 } from "fs/promises";
2085
2374
  import { basename as basename4, dirname as dirname5, join as join17 } from "path";
2086
2375
  import { exec as exec5 } from "child_process";
2087
2376
  import { promisify as promisify6 } from "util";
2088
- import { readFile as readFile9, writeFile as writeFile10, readdir as readdir8 } from "fs/promises";
2377
+ import { readFile as readFile9, writeFile as writeFile10, readdir as readdir82 } from "fs/promises";
2089
2378
  import { join as join16 } from "path";
2090
2379
  import { stat as stat82, mkdir as mkdir12 } from "fs/promises";
2091
2380
  import { readdir as readdir9, readFile as readFile10, mkdir as mkdir13 } from "fs/promises";
@@ -2129,9 +2418,9 @@ async function createFlintStructure(options) {
2129
2418
  }
2130
2419
  }
2131
2420
  const folderName = formatFlintFolderName(name);
2132
- const flintPath = join10(parentDir, folderName);
2421
+ const flintPath = join11(parentDir, folderName);
2133
2422
  try {
2134
- await stat8(flintPath);
2423
+ await stat9(flintPath);
2135
2424
  throw new Error(`Folder already exists: ${flintPath}`);
2136
2425
  } catch (error) {
2137
2426
  if (error.code !== "ENOENT") {
@@ -2140,7 +2429,7 @@ async function createFlintStructure(options) {
2140
2429
  }
2141
2430
  await mkdir2(flintPath, { recursive: true });
2142
2431
  for (const dir of STANDARD_DIRECTORIES) {
2143
- await mkdir2(join10(flintPath, dir), { recursive: true });
2432
+ await mkdir2(join11(flintPath, dir), { recursive: true });
2144
2433
  }
2145
2434
  const flintConfigDir = getFlintConfigDir(flintPath);
2146
2435
  await mkdir2(flintConfigDir, { recursive: true });
@@ -2255,7 +2544,7 @@ async function scanDirectory(dirPath, currentDepth, maxDepth, registeredPaths) {
2255
2544
  }
2256
2545
  return discovered;
2257
2546
  }
2258
- const entries = await readdir7(dirPath, { withFileTypes: true });
2547
+ const entries = await readdir8(dirPath, { withFileTypes: true });
2259
2548
  for (const entry of entries) {
2260
2549
  if (entry.name.startsWith(".")) continue;
2261
2550
  if (entry.name === "node_modules") continue;
@@ -2436,10 +2725,10 @@ function validateManifest(manifest, manifestPath) {
2436
2725
  }
2437
2726
  const record = manifest;
2438
2727
  const name = typeof record.name === "string" ? record.name.trim() : "";
2439
- const display = typeof record.display === "string" ? record.display.trim() : "";
2728
+ const title = typeof record.title === "string" ? record.title.trim() : "";
2440
2729
  const entry = typeof record.entry === "string" ? record.entry.trim() : "";
2441
- if (!name || !display || !entry) {
2442
- throw new Error(`plate.yaml is missing required fields (name, display, entry): ${manifestPath}`);
2730
+ if (!name || !title || !entry) {
2731
+ throw new Error(`plate.yaml is missing required fields (name, title, entry): ${manifestPath}`);
2443
2732
  }
2444
2733
  if (!isSlug(name)) {
2445
2734
  throw new Error(`Invalid plate name "${name}" in ${manifestPath}`);
@@ -2459,7 +2748,7 @@ function validateManifest(manifest, manifestPath) {
2459
2748
  }) : void 0;
2460
2749
  return {
2461
2750
  name,
2462
- display,
2751
+ title,
2463
2752
  entry,
2464
2753
  version: typeof record.version === "string" ? record.version : void 0,
2465
2754
  description: typeof record.description === "string" ? record.description : void 0,
@@ -2600,8 +2889,7 @@ async function syncPlateRepos(flintPath, declarations) {
2600
2889
  const exists2 = await fileExists2(absolutePath);
2601
2890
  try {
2602
2891
  if (exists2) {
2603
- await updatePlateFromRepo(flintPath, name, decl.repo, decl.path);
2604
- results.push({ name, status: "updated" });
2892
+ results.push({ name, status: "skipped" });
2605
2893
  } else {
2606
2894
  await clonePlateFromRepo(flintPath, name, decl.repo, decl.path);
2607
2895
  results.push({ name, status: "cloned" });
@@ -2629,7 +2917,7 @@ async function listPlates(flintPath) {
2629
2917
  }
2630
2918
  seenNames.add(normalized);
2631
2919
  }
2632
- return infos.sort((left, right) => left.manifest.display.localeCompare(right.manifest.display));
2920
+ return infos.sort((left, right) => left.manifest.title.localeCompare(right.manifest.title));
2633
2921
  }
2634
2922
  async function getPlate(flintPath, plateName) {
2635
2923
  const normalized = normalizePlateName(plateName);
@@ -2638,12 +2926,12 @@ async function getPlate(flintPath, plateName) {
2638
2926
  (plate) => normalizePlateName(plate.declarationName) === normalized || normalizePlateName(plate.manifest.name) === normalized
2639
2927
  ) ?? null;
2640
2928
  }
2641
- function renderPlateManifestYaml(slug, display, shard) {
2929
+ function renderPlateManifestYaml(slug, title, shard) {
2642
2930
  const manifest = {
2643
2931
  name: slug,
2644
- display,
2932
+ title,
2645
2933
  version: "0.1.0",
2646
- description: `${display} Plate`,
2934
+ description: `${title} Plate`,
2647
2935
  entry: "./dist/index.html",
2648
2936
  dev: "./src/index.tsx",
2649
2937
  api: ["GET /api/plates", "GET /api/artifacts"]
@@ -2846,6 +3134,13 @@ function spawnBufferedProcess(command, args, options) {
2846
3134
  });
2847
3135
  });
2848
3136
  }
3137
+ async function installPlateDeps(flintPath, plateName) {
3138
+ const plate = await getPlate(flintPath, plateName);
3139
+ if (!plate) {
3140
+ throw new Error(`Plate not found: ${plateName}`);
3141
+ }
3142
+ return spawnBufferedProcess("pnpm", ["install"], { cwd: plate.path });
3143
+ }
2849
3144
  async function buildPlate(flintPath, plateName) {
2850
3145
  const plate = await getPlate(flintPath, plateName);
2851
3146
  if (!plate) {
@@ -3112,7 +3407,7 @@ function sanitizeLimit(limit) {
3112
3407
  if (!Number.isFinite(limit)) {
3113
3408
  return 100;
3114
3409
  }
3115
- return Math.max(1, Math.min(1e3, Math.trunc(limit)));
3410
+ return Math.max(1, Math.min(1e4, Math.trunc(limit)));
3116
3411
  }
3117
3412
  function sanitizeOffset(offset) {
3118
3413
  if (!Number.isFinite(offset)) {
@@ -3415,6 +3710,11 @@ async function createProposalArtifact(flintPath, data) {
3415
3710
  const identity = await getIdentity(flintPath);
3416
3711
  const id = randomUUID3();
3417
3712
  const increment = ensureWikilink(typeof data.increment === "string" ? data.increment : void 0);
3713
+ const relatedDocuments = asStringArray(data.relatedDocuments);
3714
+ const alternativesConsidered = asStringArray(data.alternativesConsidered);
3715
+ const tradeOffsAndRisks = asStringArray(data.tradeOffsAndRisks);
3716
+ const notes = asStringArray(data.notes);
3717
+ const logEntries = asStringArray(data.proposalLog);
3418
3718
  const frontmatter = {
3419
3719
  id,
3420
3720
  tags: ["#prop/proposal"],
@@ -3423,41 +3723,90 @@ async function createProposalArtifact(flintPath, data) {
3423
3723
  };
3424
3724
  if (increment) frontmatter.increment = increment;
3425
3725
  if (identity) frontmatter.authors = [`[[${identity.person}]]`];
3426
- const body = typeof data.context === "string" && data.context.trim() ? data.context.trim() : [
3726
+ const proposalLog = [
3727
+ ...logEntries,
3728
+ `${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}: Artifact created via Flint Server artifact API.`
3729
+ ];
3730
+ const bodyLines = [
3427
3731
  "# Context & Motivation",
3428
3732
  "",
3429
- "Created via the Flint Server artifact API.",
3733
+ typeof data.context === "string" && data.context.trim() ? data.context.trim() : "Created via the Flint Server artifact API.",
3430
3734
  "",
3431
3735
  "**Related Documents**",
3432
3736
  "",
3737
+ ...relatedDocuments.length > 0 ? relatedDocuments.map((doc) => `- ${ensureWikilink(doc) ?? doc}`) : ["-"],
3738
+ "",
3433
3739
  "# Current State",
3434
3740
  "",
3741
+ typeof data.currentState === "string" && data.currentState.trim() ? data.currentState.trim() : "The current state will be researched and documented by the agent.",
3742
+ "",
3435
3743
  "# Proposed Approach",
3436
3744
  "",
3745
+ typeof data.proposedApproach === "string" && data.proposedApproach.trim() ? data.proposedApproach.trim() : "The proposed approach will be refined from the raw prompt and surrounding context.",
3746
+ "",
3437
3747
  "# Alternatives Considered",
3438
3748
  "",
3749
+ ...alternativesConsidered.length > 0 ? alternativesConsidered : ["Compare viable alternatives and explain why they were not selected."],
3750
+ "",
3439
3751
  "# Trade-offs & Risks",
3440
3752
  "",
3753
+ ...tradeOffsAndRisks.length > 0 ? tradeOffsAndRisks.map((item) => `- ${item}`) : ["- Document key trade-offs, risks, and mitigations before finalizing the recommendation."],
3754
+ "",
3441
3755
  "# Recommendation",
3442
3756
  "",
3757
+ typeof data.recommendation === "string" && data.recommendation.trim() ? data.recommendation.trim() : "A recommendation will be provided once the proposal is complete.",
3758
+ "",
3443
3759
  "# Decision",
3444
3760
  "",
3761
+ "",
3445
3762
  "# Proposal Log",
3446
3763
  "",
3447
- `- ${(/* @__PURE__ */ new Date()).toISOString().slice(0, 10)}: Artifact created via Flint Server artifact API.`,
3764
+ ...proposalLog.map((entry) => `- ${entry}`),
3448
3765
  "",
3449
3766
  "# Notes",
3767
+ "",
3768
+ ...notes.length > 0 ? notes.map((note) => `- ${note}`) : ["-"],
3450
3769
  ""
3451
- ].join("\n");
3770
+ ];
3452
3771
  const filePath = join82(flintPath, "Mesh", "Types", "Proposals", `(Proposal) ${number} ${title}.md`);
3453
3772
  await mkdir6(dirname22(filePath), { recursive: true });
3454
- await writeFile52(filePath, stringifyArtifact(frontmatter, body), "utf8");
3773
+ await writeFile52(filePath, stringifyArtifact(frontmatter, bodyLines.join("\n")), "utf8");
3455
3774
  const created = await readArtifactAtPath(flintPath, filePath);
3456
3775
  if (!created) {
3457
3776
  throw new Error("Failed to create proposal artifact");
3458
3777
  }
3459
3778
  return created;
3460
3779
  }
3780
+ async function createStubArtifact(flintPath, data) {
3781
+ const relativePath = typeof data.path === "string" ? data.path : "";
3782
+ if (!relativePath) {
3783
+ throw new Error('Stub artifact requires a "path" field');
3784
+ }
3785
+ const tags = Array.isArray(data.tags) ? data.tags : [];
3786
+ const status = typeof data.status === "string" ? data.status : "stub";
3787
+ const stubType = typeof data.type === "string" ? data.type : "Artifact";
3788
+ const identity = await getIdentity(flintPath);
3789
+ const frontmatter = {
3790
+ id: randomUUID3(),
3791
+ tags,
3792
+ status
3793
+ };
3794
+ if (identity) {
3795
+ frontmatter.authors = [`[[${identity.person}]]`];
3796
+ }
3797
+ const body = `# ${stubType} Stub
3798
+
3799
+ This artifact is being created by an agent session. Content will replace this stub shortly.
3800
+ `;
3801
+ const filePath = join82(flintPath, relativePath);
3802
+ await mkdir6(dirname22(filePath), { recursive: true });
3803
+ await writeFile52(filePath, stringifyArtifact(frontmatter, body), "utf8");
3804
+ const created = await readArtifactAtPath(flintPath, filePath);
3805
+ if (!created) {
3806
+ throw new Error("Failed to create stub artifact");
3807
+ }
3808
+ return created;
3809
+ }
3461
3810
  async function createArtifactFromTemplate(flintPath, request) {
3462
3811
  switch (request.template) {
3463
3812
  case "tmp-proj-task-v0.1":
@@ -3470,6 +3819,8 @@ async function createArtifactFromTemplate(flintPath, request) {
3470
3819
  return createNoteArtifact(flintPath, "#note/concept", request.data);
3471
3820
  case "tmp-f-record-v0.1":
3472
3821
  return createNoteArtifact(flintPath, "#note/record", request.data);
3822
+ case "stub":
3823
+ return createStubArtifact(flintPath, request.data);
3473
3824
  default:
3474
3825
  throw new Error(`Unsupported template: ${request.template}`);
3475
3826
  }
@@ -4007,7 +4358,7 @@ function isDirectionalConnection(value) {
4007
4358
  return "from" in value && "to" in value;
4008
4359
  }
4009
4360
  function getConfigPath(root) {
4010
- return join11(root, TINDERBOX_CONFIG_FILENAME);
4361
+ return join112(root, TINDERBOX_CONFIG_FILENAME);
4011
4362
  }
4012
4363
  function getTinderboxFlintMode(declaration) {
4013
4364
  if (declaration.mode) {
@@ -4145,7 +4496,7 @@ async function validateTinderboxToml(root, config) {
4145
4496
  if (!config.tinderbox.name.trim()) {
4146
4497
  throw new Error("Invalid tinderbox.toml: [tinderbox].name must be a non-empty string");
4147
4498
  }
4148
- if (await pathExists3(join11(root, "flint.toml"))) {
4499
+ if (await pathExists3(join112(root, "flint.toml"))) {
4149
4500
  throw new Error("Invalid tinderbox root: the tinderbox root must not also be a Flint");
4150
4501
  }
4151
4502
  if (!config.flints.required.length) {
@@ -4250,8 +4601,8 @@ async function scanTinderboxFlints(root) {
4250
4601
  for (const entry of entries) {
4251
4602
  if (!entry.isDirectory()) continue;
4252
4603
  if (entry.name.startsWith(".")) continue;
4253
- const flintPath = join11(root, entry.name);
4254
- if (!await pathExists3(join11(flintPath, "flint.toml"))) {
4604
+ const flintPath = join112(root, entry.name);
4605
+ if (!await pathExists3(join112(flintPath, "flint.toml"))) {
4255
4606
  continue;
4256
4607
  }
4257
4608
  const config = await readFlintToml(flintPath);
@@ -4281,8 +4632,8 @@ async function reconcileTinderbox(root, config) {
4281
4632
  matched.push({ declaration, actual });
4282
4633
  continue;
4283
4634
  }
4284
- const expectedPath = join11(root, getTinderboxFolderName(declaration.name));
4285
- if (await pathExists3(join11(expectedPath, "flint.toml"))) {
4635
+ const expectedPath = join112(root, getTinderboxFolderName(declaration.name));
4636
+ if (await pathExists3(join112(expectedPath, "flint.toml"))) {
4286
4637
  const expectedConfig = await readFlintToml(expectedPath);
4287
4638
  const actualName = expectedConfig?.flint?.name;
4288
4639
  if (actualName && actualName !== declaration.name) {
@@ -4359,11 +4710,11 @@ var REQUIRED_FLINT_DIRS = [
4359
4710
  ];
4360
4711
  async function ensureFlintStructure(flintPath) {
4361
4712
  for (const dir of REQUIRED_FLINT_DIRS) {
4362
- await mkdir9(join11(flintPath, dir), { recursive: true });
4713
+ await mkdir9(join112(flintPath, dir), { recursive: true });
4363
4714
  }
4364
4715
  }
4365
4716
  async function materializeGitFlint(root, declaration) {
4366
- const tempPath = join11(root, `.tinderbox-clone-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
4717
+ const tempPath = join112(root, `.tinderbox-clone-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`);
4367
4718
  try {
4368
4719
  await execFileAsync("git", ["clone", declaration.source, tempPath]);
4369
4720
  const config = await readFlintToml(tempPath);
@@ -4374,7 +4725,7 @@ async function materializeGitFlint(root, declaration) {
4374
4725
  if (actualName !== declaration.name) {
4375
4726
  throw new Error(`Cloned Flint name "${actualName}" does not match declared name "${declaration.name}"`);
4376
4727
  }
4377
- const destination = join11(root, getTinderboxFolderName(actualName));
4728
+ const destination = join112(root, getTinderboxFolderName(actualName));
4378
4729
  if (await pathExists3(destination)) {
4379
4730
  throw new Error(`Destination already exists: ${destination}`);
4380
4731
  }
@@ -4529,6 +4880,7 @@ async function syncTinderbox(root) {
4529
4880
  const config = await readTinderboxToml(root);
4530
4881
  const initialReconciliation = await reconcileTinderbox(root, config);
4531
4882
  const cloned = [];
4883
+ const moved = [];
4532
4884
  const referenced = [];
4533
4885
  const present = initialReconciliation.matched.map(({ declaration }) => declaration.name);
4534
4886
  const registryUpdated = [];
@@ -4536,6 +4888,13 @@ async function syncTinderbox(root) {
4536
4888
  const errors = [];
4537
4889
  for (const { declaration } of initialReconciliation.missing) {
4538
4890
  try {
4891
+ const existingEntry = await findFlintByName(declaration.name);
4892
+ if (existingEntry && await pathExists3(existingEntry.path)) {
4893
+ const destination = join112(root, getTinderboxFolderName(declaration.name));
4894
+ const moveResult = await moveFlint(existingEntry.path, destination);
4895
+ moved.push({ name: declaration.name, from: moveResult.from, to: moveResult.to });
4896
+ continue;
4897
+ }
4539
4898
  await materializeGitFlint(root, declaration);
4540
4899
  cloned.push(declaration.name);
4541
4900
  } catch (error) {
@@ -4571,6 +4930,7 @@ async function syncTinderbox(root) {
4571
4930
  return {
4572
4931
  root,
4573
4932
  cloned,
4933
+ moved,
4574
4934
  referenced,
4575
4935
  present,
4576
4936
  registryUpdated,
@@ -4728,13 +5088,13 @@ async function countFiles(path) {
4728
5088
  const entries = await readdir42(path, { withFileTypes: true });
4729
5089
  let count = 1;
4730
5090
  for (const entry of entries) {
4731
- count += await countFiles(join11(path, entry.name));
5091
+ count += await countFiles(join112(path, entry.name));
4732
5092
  }
4733
5093
  return count;
4734
5094
  }
4735
5095
  async function resolveMoveSource(nameOrPath) {
4736
5096
  const directPath = resolve4(nameOrPath);
4737
- if (await pathExists3(join11(directPath, "flint.toml"))) {
5097
+ if (await pathExists3(join112(directPath, "flint.toml"))) {
4738
5098
  const config = await readFlintToml(directPath);
4739
5099
  const name = config?.flint?.name;
4740
5100
  if (!name) {
@@ -4805,7 +5165,7 @@ async function moveFlint(nameOrPath, destination, options = {}) {
4805
5165
  async function removeTinderboxFlint(flint) {
4806
5166
  const entry = await findFlintByPath(flint.path);
4807
5167
  if (entry) {
4808
- const { unregisterFlint: unregisterFlint2 } = await import("./registry-YN5W7EY7-2W767O74.js");
5168
+ const { unregisterFlint: unregisterFlint2 } = await import("./registry-YN5W7EY7-SZNXPBV5.js");
4809
5169
  await unregisterFlint2(entry.path);
4810
5170
  }
4811
5171
  await rm52(flint.path, { recursive: true, force: true });
@@ -6444,7 +6804,7 @@ async function buildShardEntries(flintPath) {
6444
6804
  if (!await exists(shardsDir)) return [];
6445
6805
  const entries = [];
6446
6806
  try {
6447
- const folders = await readdir8(shardsDir, { withFileTypes: true });
6807
+ const folders = await readdir82(shardsDir, { withFileTypes: true });
6448
6808
  for (const folder of folders) {
6449
6809
  if (!folder.isDirectory() || folder.name.startsWith("(System)")) continue;
6450
6810
  const gitDir = join16(shardsDir, folder.name, ".git");
@@ -6876,9 +7236,9 @@ async function syncFlint(flintPath, progress) {
6876
7236
  }
6877
7237
  }
6878
7238
  const requiredMeshExports = config.sources?.meshexports || [];
6879
- const { getFlintRegistry: getFlintRegistry2 } = await import("./registry-YN5W7EY7-2W767O74.js");
7239
+ const { getFlintRegistry: getFlintRegistry2 } = await import("./registry-YN5W7EY7-SZNXPBV5.js");
6880
7240
  const { readdir: readdir10, readFile: fsReadFile, rm: rm8, stat: fsStat, mkdir: fsMkdir, copyFile: fsCopyFile } = await import("fs/promises");
6881
- const { syncSourceMeshExportMetadata: syncSourceMeshExportMetadata2 } = await import("./metadata-SJT4H53O-EXBS6PS4.js");
7241
+ const { syncSourceMeshExportMetadata: syncSourceMeshExportMetadata2 } = await import("./metadata-SJT4H53O-7W2752ZT.js");
6882
7242
  const requiredMeshExportSet = new Set(requiredMeshExports);
6883
7243
  const sourcesFlintDir = join17(flintPath, "Sources", "Flints");
6884
7244
  try {
@@ -6920,7 +7280,7 @@ async function syncFlint(flintPath, progress) {
6920
7280
  }
6921
7281
  if (requiredMeshExports.length > 0) {
6922
7282
  const registry = await getFlintRegistry2();
6923
- const { buildExportByName: buildExportByName2, scanExports: scanExports2 } = await import("./exports-E3262H6I-A4GXPTSR.js");
7283
+ const { buildExportByName: buildExportByName2, scanExports: scanExports2 } = await import("./exports-FO5IMLM7-EN6H3N2A.js");
6924
7284
  for (const ref of requiredMeshExports) {
6925
7285
  const parts = ref.split("/");
6926
7286
  if (parts.length !== 2) {
@@ -7064,7 +7424,7 @@ async function syncFlint(flintPath, progress) {
7064
7424
  error: `Failed to sync source repo metadata: ${err instanceof Error ? err.message : String(err)}`
7065
7425
  });
7066
7426
  }
7067
- const { getPlateDeclarationsFromConfig } = await import("./mesh-config-BAIYF4KD-QNNQOBSL.js");
7427
+ const { getPlateDeclarationsFromConfig } = await import("./mesh-config-BAIYF4KD-W2RGZQ2V.js");
7068
7428
  const plateDeclarations = getPlateDeclarationsFromConfig(config);
7069
7429
  const hasRepoPlates = Object.values(plateDeclarations).some((d) => d.repo);
7070
7430
  if (hasRepoPlates) {
@@ -7334,7 +7694,7 @@ function getSessionInterface(flintPath, sessionId, key) {
7334
7694
  }
7335
7695
  function getCurrentRun(session) {
7336
7696
  if (session.runs.length === 0) return null;
7337
- return session.runs[session.runs.length - 1];
7697
+ return session.runs[session.runs.length - 1] ?? null;
7338
7698
  }
7339
7699
  function addRun(flintPath, sessionId, options) {
7340
7700
  const session = readSession(flintPath, sessionId);
@@ -7422,7 +7782,7 @@ function resolveSession(flintPath, partialId) {
7422
7782
  if (exact) return exact;
7423
7783
  const sessions = listSessions(flintPath);
7424
7784
  const matches = sessions.filter((s) => s.id.startsWith(partialId));
7425
- if (matches.length === 1) return matches[0];
7785
+ if (matches.length === 1) return matches[0] ?? null;
7426
7786
  return null;
7427
7787
  }
7428
7788
  var VALID_STATUSES = ["queued", "in-progress", "blocked", "finished", "failed"];
@@ -7939,8 +8299,12 @@ var LiveSessionManager = class {
7939
8299
 
7940
8300
  export {
7941
8301
  compareVersions,
8302
+ isVersionStepSealed,
8303
+ getVersionSteps,
8304
+ getMigrationsForVersion,
7942
8305
  checkMigrations,
7943
8306
  runMigrations,
8307
+ runSingleMigration,
7944
8308
  STANDARD_DIRECTORIES,
7945
8309
  createFlint,
7946
8310
  findFlintRoot,
@@ -7970,6 +8334,7 @@ export {
7970
8334
  listPlates,
7971
8335
  getPlate,
7972
8336
  createPlate,
8337
+ installPlateDeps,
7973
8338
  buildPlate,
7974
8339
  runPlateTool,
7975
8340
  listPlateTools,