role-os 1.0.1 → 1.0.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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.2
4
+
5
+ ### Fixed
6
+ - Fix double-nested `.claude/.claude/` directory created by `roleos init` — `starter-pack/.claude/workflows/full-treatment.md` moved to `starter-pack/workflows/`
7
+ - Read VERSION from `package.json` at runtime instead of hardcoded constant — prevents version drift between CLI and package metadata
8
+
9
+ ### Added
10
+ - `roleos init --force` — update canonical scaffolded files while always protecting user-filled `context/` files
11
+ - 4 regression tests: no double-nesting, correct workflow placement, version sync, --force context protection
12
+
3
13
  ## 1.0.0
4
14
 
5
15
  ### Added
package/bin/roleos.mjs CHANGED
@@ -1,12 +1,16 @@
1
1
  #!/usr/bin/env node
2
2
 
3
+ import { readFileSync } from "node:fs";
4
+ import { join, dirname } from "node:path";
5
+ import { fileURLToPath } from "node:url";
3
6
  import { initCommand } from "../src/init.mjs";
4
7
  import { packetCommand } from "../src/packet.mjs";
5
8
  import { routeCommand } from "../src/route.mjs";
6
9
  import { reviewCommand } from "../src/review.mjs";
7
10
  import { statusCommand } from "../src/status.mjs";
8
11
 
9
- const VERSION = "1.0.0";
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+ const VERSION = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8")).version;
10
14
 
11
15
  function printHelp() {
12
16
  console.log(`
@@ -14,6 +18,7 @@ roleos v${VERSION} — Role OS bootstrap CLI
14
18
 
15
19
  Usage:
16
20
  roleos init Scaffold Role OS into .claude/
21
+ roleos init --force Update canonical files (protects context/)
17
22
  roleos packet new <type> Create a new packet (feature|integration|identity)
18
23
  roleos route <packet-file> Recommend the smallest valid chain
19
24
  roleos review <packet-file> <verdict> Record a review verdict
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "role-os",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "Role OS — a repo-native operating layer where specialized roles execute work through contracts, handoffs, review, and escalation",
5
5
  "type": "module",
6
6
  "bin": {
package/src/fs-utils.mjs CHANGED
@@ -3,11 +3,14 @@ import { join, dirname, relative } from "node:path";
3
3
 
4
4
  /**
5
5
  * Recursively copy a directory, skipping files that already exist at the target.
6
- * Returns { created: string[], skipped: string[] } with relative paths.
6
+ * With force=true, overwrites existing files (except those matching protectedPaths).
7
+ * Returns { created: string[], skipped: string[], updated: string[] } with relative paths.
7
8
  */
8
- export function copyDirSafe(srcDir, destDir, baseDir = destDir) {
9
+ export function copyDirSafe(srcDir, destDir, { baseDir, force = false, protectedPaths = [] } = {}) {
10
+ const root = baseDir || destDir;
9
11
  const created = [];
10
12
  const skipped = [];
13
+ const updated = [];
11
14
 
12
15
  if (!existsSync(srcDir)) {
13
16
  throw new Error(`Source directory does not exist: ${srcDir}`);
@@ -18,15 +21,23 @@ export function copyDirSafe(srcDir, destDir, baseDir = destDir) {
18
21
  for (const entry of entries) {
19
22
  const srcPath = join(srcDir, entry.name);
20
23
  const destPath = join(destDir, entry.name);
21
- const relPath = relative(baseDir, destPath);
24
+ const relPath = relative(root, destPath);
22
25
 
23
26
  if (entry.isDirectory()) {
24
- const sub = copyDirSafe(srcPath, destPath, baseDir);
27
+ const sub = copyDirSafe(srcPath, destPath, { baseDir: root, force, protectedPaths });
25
28
  created.push(...sub.created);
26
29
  skipped.push(...sub.skipped);
30
+ updated.push(...sub.updated);
27
31
  } else {
28
32
  if (existsSync(destPath)) {
29
- skipped.push(relPath);
33
+ const normalizedRel = relPath.replace(/\\/g, "/");
34
+ if (force && !protectedPaths.some(p => normalizedRel.startsWith(p))) {
35
+ mkdirSync(dirname(destPath), { recursive: true });
36
+ copyFileSync(srcPath, destPath);
37
+ updated.push(relPath);
38
+ } else {
39
+ skipped.push(relPath);
40
+ }
30
41
  } else {
31
42
  mkdirSync(dirname(destPath), { recursive: true });
32
43
  copyFileSync(srcPath, destPath);
@@ -35,7 +46,7 @@ export function copyDirSafe(srcDir, destDir, baseDir = destDir) {
35
46
  }
36
47
  }
37
48
 
38
- return { created, skipped };
49
+ return { created, skipped, updated };
39
50
  }
40
51
 
41
52
  /**
package/src/init.mjs CHANGED
@@ -5,13 +5,23 @@ import { copyDirSafe } from "./fs-utils.mjs";
5
5
  const __dirname = dirname(fileURLToPath(import.meta.url));
6
6
  const STARTER_PACK_DIR = join(__dirname, "..", "starter-pack");
7
7
 
8
+ // Paths that --force must never overwrite (user-filled content)
9
+ const PROTECTED_PATHS = [
10
+ "context/",
11
+ ];
12
+
8
13
  export async function initCommand(args) {
9
- const targetDir = args[0] || ".";
14
+ const force = args.includes("--force");
15
+ const positional = args.filter(a => !a.startsWith("--"));
16
+ const targetDir = positional[0] || ".";
10
17
  const claudeDir = join(targetDir, ".claude");
11
18
 
12
- console.log("roleos init — scaffolding Role OS into .claude/\n");
19
+ console.log(`roleos init${force ? " --force" : ""} — scaffolding Role OS into .claude/\n`);
13
20
 
14
- const { created, skipped } = copyDirSafe(STARTER_PACK_DIR, claudeDir);
21
+ const { created, skipped, updated } = copyDirSafe(STARTER_PACK_DIR, claudeDir, {
22
+ force,
23
+ protectedPaths: PROTECTED_PATHS,
24
+ });
15
25
 
16
26
  if (created.length > 0) {
17
27
  console.log(`Created (${created.length}):`);
@@ -20,6 +30,13 @@ export async function initCommand(args) {
20
30
  }
21
31
  }
22
32
 
33
+ if (updated.length > 0) {
34
+ console.log(`\nUpdated (${updated.length}):`);
35
+ for (const f of updated) {
36
+ console.log(` ~ ${f}`);
37
+ }
38
+ }
39
+
23
40
  if (skipped.length > 0) {
24
41
  console.log(`\nSkipped (${skipped.length} — already exist):`);
25
42
  for (const f of skipped) {
@@ -27,8 +44,11 @@ export async function initCommand(args) {
27
44
  }
28
45
  }
29
46
 
30
- if (created.length === 0 && skipped.length > 0) {
47
+ if (created.length === 0 && updated.length === 0 && skipped.length > 0) {
31
48
  console.log("\nRole OS is already scaffolded. No files were overwritten.");
49
+ if (!force) {
50
+ console.log("Use --force to update canonical files (context/ files are always protected).");
51
+ }
32
52
  } else {
33
53
  console.log(`\nDone. Fill the context/ files for your project, then run:`);
34
54
  console.log(` roleos packet new feature`);