role-os 1.0.0

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 (71) hide show
  1. package/CHANGELOG.md +13 -0
  2. package/LICENSE +21 -0
  3. package/README.es.md +160 -0
  4. package/README.fr.md +160 -0
  5. package/README.hi.md +160 -0
  6. package/README.it.md +160 -0
  7. package/README.ja.md +160 -0
  8. package/README.md +160 -0
  9. package/README.pt-BR.md +160 -0
  10. package/README.zh.md +160 -0
  11. package/bin/roleos.mjs +90 -0
  12. package/package.json +41 -0
  13. package/src/fs-utils.mjs +60 -0
  14. package/src/init.mjs +36 -0
  15. package/src/packet.mjs +144 -0
  16. package/src/prompts.mjs +76 -0
  17. package/src/review.mjs +94 -0
  18. package/src/route.mjs +169 -0
  19. package/src/status.mjs +352 -0
  20. package/starter-pack/.claude/workflows/full-treatment.md +74 -0
  21. package/starter-pack/README.md +74 -0
  22. package/starter-pack/agents/core/critic-reviewer.md +39 -0
  23. package/starter-pack/agents/core/orchestrator.md +40 -0
  24. package/starter-pack/agents/core/product-strategist.md +40 -0
  25. package/starter-pack/agents/design/brand-guardian.md +41 -0
  26. package/starter-pack/agents/design/ui-designer.md +42 -0
  27. package/starter-pack/agents/engineering/backend-engineer.md +39 -0
  28. package/starter-pack/agents/engineering/dependency-auditor.md +40 -0
  29. package/starter-pack/agents/engineering/frontend-developer.md +40 -0
  30. package/starter-pack/agents/engineering/performance-engineer.md +42 -0
  31. package/starter-pack/agents/engineering/refactor-engineer.md +41 -0
  32. package/starter-pack/agents/engineering/security-reviewer.md +42 -0
  33. package/starter-pack/agents/engineering/test-engineer.md +38 -0
  34. package/starter-pack/agents/growth/community-manager.md +41 -0
  35. package/starter-pack/agents/growth/content-strategist.md +41 -0
  36. package/starter-pack/agents/growth/launch-strategist.md +42 -0
  37. package/starter-pack/agents/growth/support-triage-lead.md +41 -0
  38. package/starter-pack/agents/marketing/launch-copywriter.md +39 -0
  39. package/starter-pack/agents/product/feedback-synthesizer.md +39 -0
  40. package/starter-pack/agents/product/information-architect.md +40 -0
  41. package/starter-pack/agents/product/roadmap-prioritizer.md +41 -0
  42. package/starter-pack/agents/product/spec-writer.md +42 -0
  43. package/starter-pack/agents/research/competitive-analyst.md +40 -0
  44. package/starter-pack/agents/research/trend-researcher.md +40 -0
  45. package/starter-pack/agents/research/user-interview-synthesizer.md +41 -0
  46. package/starter-pack/agents/research/ux-researcher.md +40 -0
  47. package/starter-pack/agents/treatment/coverage-auditor.md +40 -0
  48. package/starter-pack/agents/treatment/deployment-verifier.md +41 -0
  49. package/starter-pack/agents/treatment/docs-architect.md +40 -0
  50. package/starter-pack/agents/treatment/metadata-curator.md +40 -0
  51. package/starter-pack/agents/treatment/release-engineer.md +43 -0
  52. package/starter-pack/agents/treatment/repo-researcher.md +40 -0
  53. package/starter-pack/agents/treatment/repo-translator.md +38 -0
  54. package/starter-pack/context/brand-rules.md +52 -0
  55. package/starter-pack/context/current-priorities.md +33 -0
  56. package/starter-pack/context/product-brief.md +47 -0
  57. package/starter-pack/context/repo-map.md +45 -0
  58. package/starter-pack/examples/feature-packet.md +39 -0
  59. package/starter-pack/examples/identity-packet.md +39 -0
  60. package/starter-pack/examples/integration-packet.md +39 -0
  61. package/starter-pack/handbook.md +67 -0
  62. package/starter-pack/policy/done-definition.md +15 -0
  63. package/starter-pack/policy/escalation-rules.md +20 -0
  64. package/starter-pack/policy/routing-rules.md +199 -0
  65. package/starter-pack/policy/tool-permissions.md +134 -0
  66. package/starter-pack/schemas/handoff.md +52 -0
  67. package/starter-pack/schemas/review-verdict.md +26 -0
  68. package/starter-pack/schemas/task-packet.md +44 -0
  69. package/starter-pack/workflows/fix-bug.md +18 -0
  70. package/starter-pack/workflows/launch-update.md +15 -0
  71. package/starter-pack/workflows/ship-feature.md +22 -0
package/README.zh.md ADDED
@@ -0,0 +1,160 @@
1
+ <p align="center">
2
+ <a href="README.ja.md">日本語</a> | <a href="README.md">English</a> | <a href="README.es.md">Español</a> | <a href="README.fr.md">Français</a> | <a href="README.hi.md">हिन्दी</a> | <a href="README.it.md">Italiano</a> | <a href="README.pt-BR.md">Português (BR)</a>
3
+ </p>
4
+
5
+ # Role OS
6
+
7
+ <p align="center">
8
+ <img src="https://raw.githubusercontent.com/mcp-tool-shop-org/brand/main/logos/role-os/readme.png" alt="Role OS" width="400">
9
+ </p>
10
+
11
+ <p align="center">
12
+ <a href="https://github.com/mcp-tool-shop-org/role-os/actions"><img src="https://github.com/mcp-tool-shop-org/role-os/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
13
+ <a href="https://www.npmjs.com/package/@mcptoolshop/role-os"><img src="https://img.shields.io/npm/v/@mcptoolshop/role-os" alt="npm"></a>
14
+ <a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT License"></a>
15
+ <a href="https://mcp-tool-shop-org.github.io/role-os/"><img src="https://img.shields.io/badge/Landing_Page-live-brightgreen" alt="Landing Page"></a>
16
+ </p>
17
+
18
+ Role OS 是一种可移植、原生支持代码仓库的操作系统层,它通过角色合约、结构化数据包、评审和升级流程,确保团队在进行功能开发、集成、身份修复以及完整的代码仓库管理时,不会出现偏差、虚报完成情况或基于主观感受的进度报告。
19
+
20
+ ## 其作用
21
+
22
+ Role OS 旨在避免通用人工智能工作流程中常见的特定问题:
23
+
24
+ - **偏差 (Drift)**:每个角色都专注于其职责范围。产品不会被重新设计。前端不会重新定义范围。后端不会自行决定产品方向。
25
+ - **虚报完成 (False completion)**:完成的标准是明确的。隐藏缺陷、跳过验证或解决不同问题的成果会被拒绝。
26
+ - **污染 (Contamination)**:分叉或继承的项目可能包含身份残留。Role OS 可以检测并拒绝跨项目的术语、视觉和思维模式上的偏差。
27
+ - **基于主观感受的进度 (Vibes-based progress)**:每个交接环节都是结构化的。每个结论都必须基于证据。 “感觉完成了” 并不是一个有效的状态。
28
+
29
+ ## 工作原理
30
+
31
+ 1. **创建数据包 (Create a packet)**:定义完成工作后需要存在的内容。
32
+ 2. **通过链条进行处理 (Route through a chain)**:选择完成工作所需的最少且专业的角色集合。
33
+ 3. **每个角色进行交接 (Each role produces a handoff)**:提供结构化的输出,以减少对下一个角色的歧义。
34
+ 4. **评审员根据合约进行评审 (Critic reviews against contract)**:根据证据,而不是主观印象,接受、拒绝或阻止。
35
+
36
+ ## 内存与连续性
37
+
38
+ Role OS 不拥有或复制内存层。如果 Claude 项目中存在内存,它就是标准的连续性系统,代码仓库的事实、决策、未解决的问题和处理历史都存储在那里。
39
+
40
+ Role OS 与 Claude 项目的内存集成,而不是替代它。
41
+
42
+ ## 完整的处理流程和发布检查
43
+
44
+ 完整的处理流程是一个由 7 个阶段组成的规范流程,定义在 Claude 项目的内存中(`memory/full-treatment.md`)。Role OS 使用角色合约、交接和评审环节来处理流程,而不是重新定义该流程。
45
+
46
+ **发布检查 (Shipcheck)** 是在完整处理流程之前执行的 31 个项目质量检查。在开始任何处理之前,必须通过 A 到 D 这四个关键检查。参考文档:`memory/shipcheck.md`。
47
+
48
+ 顺序:首先进行发布检查,然后进行完整的处理流程。在通过所有关键检查之前,不能发布 v1.0.0 版本。
49
+
50
+ ## 32个角色,分布在8个模块中
51
+
52
+ | 模块 | 角色 |
53
+ |------|-------|
54
+ | **Core** (3) | 协调员、产品策略师、评审专家 |
55
+ | **Engineering** (7) | 前端开发工程师、后端工程师、测试工程师、重构工程师、性能工程师、依赖性审计员、安全评审员 |
56
+ | **Design** (2) | UI设计师、品牌守护者 |
57
+ | **Marketing** (1) | 发布文案撰写员 |
58
+ | **Treatment** (7) | 代码仓库研究员、代码仓库翻译员、文档架构师、元数据管理员、覆盖范围审计员、部署验证员、发布工程师 |
59
+ | **Product** (4) | 反馈综合分析员、路线图优先级排序员、规格文档撰写员、信息架构师 |
60
+ | **Research** (4) | 用户体验研究员、竞争分析师、趋势研究员、用户访谈综合分析员 |
61
+ | **Growth** (4) | 发布策略师、内容策略师、社区管理员、支持问题处理负责人 |
62
+
63
+ 每个角色都有完整的说明:任务、适用时机、不适用时机、预期输入、所需输出、质量标准以及升级流程。
64
+
65
+ ## 快速入门
66
+
67
+ ```bash
68
+ npx @mcptoolshop/role-os init
69
+
70
+ # Fill context/ files for your project, then:
71
+ roleos packet new feature
72
+ roleos route .claude/packets/my-feature.md
73
+ roleos review .claude/packets/my-feature.md accept
74
+ roleos status
75
+ ```
76
+
77
+ ## 何时不应使用角色操作系统
78
+
79
+ - 简单的修复、拼写错误或明显的错误
80
+ - 没有明确输出的探索性研究
81
+ - 可以在一个人5分钟内完成的工作
82
+ - 需要在评审流程完成之前立即发布的紧急修复
83
+ - 追求速度而非结构的工程项目
84
+
85
+ ## 证据
86
+
87
+ Role OS 已在两个结构不同的代码仓库中的三个试验项目中得到验证:
88
+
89
+ **试验 001 — 功能开发 (Feature work)** (Crew Screen, Star Freight)
90
+ - 7 个角色链,45 个测试场景,0 个角色冲突
91
+ - 避免了从父代码仓库的污染,发现了代码中的即兴创作,并暴露了真实的障碍。
92
+
93
+ **试验 002 — 集成 (Integration work)** (CampaignState wiring, Star Freight)
94
+ - 5 个角色链,解决了架构接口问题,避免了虚假的回滚。
95
+ - 抗回滚测试证明了当前路径是真实的,而不是占位符。
96
+
97
+ **试验 003 — 身份验证 (Identity work)** (Contamination purge, Star Freight)
98
+ - 6 个角色链,51 个测试场景,包括持久的 CI 污染防御
99
+ - 在修复继承的错误偏差时,避免了对整个产品的重新设计。
100
+
101
+ **可移植性试验**(角色一致性,传感器幽默)
102
+ - 相同的核心,不同的语言/领域/技术栈
103
+ - 仅在上下文发生变化的情况下采用,不进行核心合同的修改。
104
+
105
+ **完整方案 FT-001** (portlight-desktop)
106
+ - 包含7个阶段,配备人员,使用方案模块中的角色
107
+ - 已验证的发布检查机制,无角色冲突
108
+
109
+ **完整方案 FT-002** (studioflow)
110
+ - 使用相同的方案模块,但结构不同的代码仓库(创意工作区与游戏)
111
+ - 方案模块可移植,无需修改任何说明
112
+
113
+ ## 核心特性
114
+
115
+ 这些是不可谈判的。如果任何一项特性被削弱,则应拒绝该更改。
116
+
117
+ - 角色边界保持不变
118
+ - 审查具有实际效力
119
+ - 升级流程保持诚实
120
+ - 测试用例保持可测试性
121
+ - 可移植性需要根据上下文进行调整,而不是进行核心修改。
122
+
123
+ ## 项目结构
124
+
125
+ ```
126
+ role-os/
127
+ README.md ← You are here
128
+ bin/roleos.mjs ← CLI entrypoint
129
+ src/ ← CLI implementation
130
+ starter-pack/
131
+ handbook.md ← How Role OS works
132
+ context/ ← Fill these for your repo
133
+ examples/ ← Feature, integration, identity packets
134
+ agents/ ← 32 role contracts across 8 packs
135
+ schemas/ ← Packet, handoff, verdict formats
136
+ policy/ ← Routing, permissions, escalation, done
137
+ workflows/ ← Ship feature, fix bug, launch update, full treatment
138
+ ```
139
+
140
+ ## 安全性
141
+
142
+ 角色操作系统仅在本地运行。它复制 Markdown 模板,并将数据包/判决文件写入到您仓库的 `.claude/` 目录中。它不访问网络,不处理敏感信息,也不收集遥测数据。没有危险操作——所有文件写入默认使用“如果存在则跳过”的方式。请参阅 [SECURITY.md](SECURITY.md) 以获取完整策略。
143
+
144
+ ## 状态
145
+
146
+ **v1.0.0 — 广泛应用,相同规则**
147
+
148
+ - v0.1: 试运行 — 3次试用,3次通过,无角色冲突
149
+ - v0.2: 采用 — 默认工作流程,可移植到第二个代码仓库
150
+ - v0.3: 产品化 — 基础模块,启动命令行工具,提供示例
151
+ - v0.4: 方案模块 — 8个方案/身份角色,配备完整人员,可移植到2个代码仓库
152
+ - v1.0.0: 32个角色,分布在8个模块中,完整命令行工具,经过验证的方案,支持多代码仓库移植
153
+
154
+ ## 许可证
155
+
156
+ MIT
157
+
158
+ ---
159
+
160
+ 由 <a href="https://mcp-tool-shop.github.io/">MCP Tool Shop</a> 构建。
package/bin/roleos.mjs ADDED
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { initCommand } from "../src/init.mjs";
4
+ import { packetCommand } from "../src/packet.mjs";
5
+ import { routeCommand } from "../src/route.mjs";
6
+ import { reviewCommand } from "../src/review.mjs";
7
+ import { statusCommand } from "../src/status.mjs";
8
+
9
+ const VERSION = "1.0.0";
10
+
11
+ function printHelp() {
12
+ console.log(`
13
+ roleos v${VERSION} — Role OS bootstrap CLI
14
+
15
+ Usage:
16
+ roleos init Scaffold Role OS into .claude/
17
+ roleos packet new <type> Create a new packet (feature|integration|identity)
18
+ roleos route <packet-file> Recommend the smallest valid chain
19
+ roleos review <packet-file> <verdict> Record a review verdict
20
+ roleos status Show active work, verdicts, and health
21
+ roleos status --write Write .claude/status/index.md
22
+ roleos status --json Output as JSON
23
+ roleos help Show this help
24
+
25
+ Verdicts: accept | accept-with-notes | reject | blocked
26
+ `);
27
+ }
28
+
29
+ /**
30
+ * Structured error output. Matches shipcheck error shape:
31
+ * code, message, hint, cause?, retryable?
32
+ */
33
+ function handleError(err) {
34
+ const isUserError = err.exitCode === 1;
35
+ const code = isUserError ? "USER_ERROR" : "RUNTIME_ERROR";
36
+ const exitCode = isUserError ? 1 : 2;
37
+
38
+ if (process.argv.includes("--debug")) {
39
+ console.error(err.stack || err);
40
+ } else {
41
+ console.error(JSON.stringify({
42
+ code,
43
+ message: err.message,
44
+ hint: err.hint || null,
45
+ retryable: false,
46
+ }));
47
+ }
48
+
49
+ process.exit(exitCode);
50
+ }
51
+
52
+ const command = process.argv[2] || "help";
53
+ const args = process.argv.slice(3);
54
+
55
+ try {
56
+ switch (command) {
57
+ case "init":
58
+ await initCommand(args);
59
+ break;
60
+ case "packet":
61
+ await packetCommand(args);
62
+ break;
63
+ case "route":
64
+ await routeCommand(args);
65
+ break;
66
+ case "review":
67
+ await reviewCommand(args);
68
+ break;
69
+ case "status":
70
+ await statusCommand(args);
71
+ break;
72
+ case "help":
73
+ case "--help":
74
+ case "-h":
75
+ printHelp();
76
+ break;
77
+ case "--version":
78
+ case "-v":
79
+ console.log(`roleos v${VERSION}`);
80
+ break;
81
+ default: {
82
+ const err = new Error(`Unknown command: ${command}`);
83
+ err.exitCode = 1;
84
+ err.hint = "Run 'roleos help' for usage.";
85
+ throw err;
86
+ }
87
+ }
88
+ } catch (err) {
89
+ handleError(err);
90
+ }
package/package.json ADDED
@@ -0,0 +1,41 @@
1
+ {
2
+ "name": "role-os",
3
+ "version": "1.0.0",
4
+ "description": "Role OS — a repo-native operating layer where specialized roles execute work through contracts, handoffs, review, and escalation",
5
+ "type": "module",
6
+ "bin": {
7
+ "roleos": "bin/roleos.mjs"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "starter-pack",
13
+ "README.md",
14
+ "CHANGELOG.md",
15
+ "LICENSE"
16
+ ],
17
+ "scripts": {
18
+ "test": "node --test test/*.test.mjs",
19
+ "verify": "node --test test/*.test.mjs && node bin/roleos.mjs help && echo '✓ verify passed'"
20
+ },
21
+ "engines": {
22
+ "node": ">=18.0.0"
23
+ },
24
+ "author": "mcp-tool-shop <64996768+mcp-tool-shop@users.noreply.github.com>",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "https://github.com/mcp-tool-shop-org/role-os.git"
29
+ },
30
+ "keywords": [
31
+ "role-os",
32
+ "agents",
33
+ "contracts",
34
+ "handoffs",
35
+ "review",
36
+ "workflow"
37
+ ],
38
+ "publishConfig": {
39
+ "registry": "https://registry.npmjs.org"
40
+ }
41
+ }
@@ -0,0 +1,60 @@
1
+ import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync, readFileSync, writeFileSync } from "node:fs";
2
+ import { join, dirname, relative } from "node:path";
3
+
4
+ /**
5
+ * Recursively copy a directory, skipping files that already exist at the target.
6
+ * Returns { created: string[], skipped: string[] } with relative paths.
7
+ */
8
+ export function copyDirSafe(srcDir, destDir, baseDir = destDir) {
9
+ const created = [];
10
+ const skipped = [];
11
+
12
+ if (!existsSync(srcDir)) {
13
+ throw new Error(`Source directory does not exist: ${srcDir}`);
14
+ }
15
+
16
+ const entries = readdirSync(srcDir, { withFileTypes: true });
17
+
18
+ for (const entry of entries) {
19
+ const srcPath = join(srcDir, entry.name);
20
+ const destPath = join(destDir, entry.name);
21
+ const relPath = relative(baseDir, destPath);
22
+
23
+ if (entry.isDirectory()) {
24
+ const sub = copyDirSafe(srcPath, destPath, baseDir);
25
+ created.push(...sub.created);
26
+ skipped.push(...sub.skipped);
27
+ } else {
28
+ if (existsSync(destPath)) {
29
+ skipped.push(relPath);
30
+ } else {
31
+ mkdirSync(dirname(destPath), { recursive: true });
32
+ copyFileSync(srcPath, destPath);
33
+ created.push(relPath);
34
+ }
35
+ }
36
+ }
37
+
38
+ return { created, skipped };
39
+ }
40
+
41
+ /**
42
+ * Write a file, creating parent directories as needed.
43
+ * Does NOT overwrite if the file already exists unless force is true.
44
+ */
45
+ export function writeFileSafe(filePath, content, { force = false } = {}) {
46
+ if (existsSync(filePath) && !force) {
47
+ return false;
48
+ }
49
+ mkdirSync(dirname(filePath), { recursive: true });
50
+ writeFileSync(filePath, content, "utf-8");
51
+ return true;
52
+ }
53
+
54
+ /**
55
+ * Read a file as UTF-8, returning null if it doesn't exist.
56
+ */
57
+ export function readFileSafe(filePath) {
58
+ if (!existsSync(filePath)) return null;
59
+ return readFileSync(filePath, "utf-8");
60
+ }
package/src/init.mjs ADDED
@@ -0,0 +1,36 @@
1
+ import { join, dirname } from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+ import { copyDirSafe } from "./fs-utils.mjs";
4
+
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ const STARTER_PACK_DIR = join(__dirname, "..", "starter-pack");
7
+
8
+ export async function initCommand(args) {
9
+ const targetDir = args[0] || ".";
10
+ const claudeDir = join(targetDir, ".claude");
11
+
12
+ console.log("roleos init — scaffolding Role OS into .claude/\n");
13
+
14
+ const { created, skipped } = copyDirSafe(STARTER_PACK_DIR, claudeDir);
15
+
16
+ if (created.length > 0) {
17
+ console.log(`Created (${created.length}):`);
18
+ for (const f of created) {
19
+ console.log(` + ${f}`);
20
+ }
21
+ }
22
+
23
+ if (skipped.length > 0) {
24
+ console.log(`\nSkipped (${skipped.length} — already exist):`);
25
+ for (const f of skipped) {
26
+ console.log(` - ${f}`);
27
+ }
28
+ }
29
+
30
+ if (created.length === 0 && skipped.length > 0) {
31
+ console.log("\nRole OS is already scaffolded. No files were overwritten.");
32
+ } else {
33
+ console.log(`\nDone. Fill the context/ files for your project, then run:`);
34
+ console.log(` roleos packet new feature`);
35
+ }
36
+ }
package/src/packet.mjs ADDED
@@ -0,0 +1,144 @@
1
+ import { join } from "node:path";
2
+ import { existsSync } from "node:fs";
3
+ import { writeFileSafe } from "./fs-utils.mjs";
4
+ import { askRequired, askWithDefault, closePrompts } from "./prompts.mjs";
5
+
6
+ const TYPES = ["feature", "integration", "identity"];
7
+
8
+ const TYPICAL_CHAINS = {
9
+ feature:
10
+ "Orchestrator → Product Strategist → UI Designer → Backend Engineer → Frontend Developer → Test Engineer → Critic Reviewer",
11
+ integration:
12
+ "Orchestrator → Backend Engineer → Frontend Developer → Test Engineer → Critic Reviewer",
13
+ identity:
14
+ "Orchestrator → Product Strategist → UI Designer → Frontend Developer → Test Engineer → Critic Reviewer",
15
+ };
16
+
17
+ const DELIVERABLE_DEFAULTS = {
18
+ feature: "Code",
19
+ integration: "Code",
20
+ identity: "Code",
21
+ };
22
+
23
+ function slugify(text) {
24
+ return text
25
+ .toLowerCase()
26
+ .replace(/[^a-z0-9]+/g, "-")
27
+ .replace(/^-|-$/g, "")
28
+ .slice(0, 60);
29
+ }
30
+
31
+ function today() {
32
+ return new Date().toISOString().split("T")[0];
33
+ }
34
+
35
+ export async function packetCommand(args) {
36
+ const subcommand = args[0];
37
+ const type = args[1];
38
+
39
+ if (subcommand !== "new") {
40
+ const err = new Error(`Usage: roleos packet new <type>\nTypes: ${TYPES.join(", ")}`);
41
+ err.exitCode = 1;
42
+ err.hint = "Run 'roleos packet new feature' to create a feature packet.";
43
+ throw err;
44
+ }
45
+
46
+ if (!type || !TYPES.includes(type)) {
47
+ const err = new Error(`Unknown packet type: ${type || "(none)"}\nTypes: ${TYPES.join(", ")}`);
48
+ err.exitCode = 1;
49
+ err.hint = `Valid types: ${TYPES.join(", ")}`;
50
+ throw err;
51
+ }
52
+
53
+ const claudeDir = join(".", ".claude");
54
+ if (!existsSync(claudeDir)) {
55
+ const err = new Error("No .claude/ directory found. Run 'roleos init' first.");
56
+ err.exitCode = 1;
57
+ err.hint = "Run 'roleos init' to scaffold Role OS.";
58
+ throw err;
59
+ }
60
+
61
+ console.log(`\nroleos packet new ${type}\n`);
62
+
63
+ const title = await askRequired("Title");
64
+ const outcome = await askRequired("Requested outcome");
65
+ const scope = await askWithDefault("Scope", "See requested outcome");
66
+ const nonGoals = await askWithDefault("Non-goals", "None specified");
67
+ const constraints = await askWithDefault("Constraints", "None specified");
68
+ const inputs = await askWithDefault("Key inputs (files, docs)", "See repo map");
69
+
70
+ const slug = slugify(title);
71
+ const taskId = `${today()}-${slug}`;
72
+ const chain = TYPICAL_CHAINS[type];
73
+ const deliverable = DELIVERABLE_DEFAULTS[type];
74
+
75
+ const content = `# Task Packet
76
+
77
+ ## Task ID
78
+ ${taskId}
79
+
80
+ ## Title
81
+ ${title}
82
+
83
+ ## Requested Outcome
84
+ ${outcome}
85
+
86
+ ## User Intent
87
+ <!-- Why does this matter? Fill in after creation if needed. -->
88
+
89
+ ## Scope
90
+ ${scope}
91
+
92
+ ## Non-Goals
93
+ ${nonGoals}
94
+
95
+ ## Inputs
96
+ ${inputs}
97
+
98
+ **Verification requirement:** All referenced files, classes, and modules must be verified as live and relevant to the current product before the packet is finalized.
99
+
100
+ ## Constraints
101
+ ${constraints}
102
+
103
+ ## Deliverable Type
104
+ ${deliverable}
105
+
106
+ ## Assigned Role
107
+ <!-- Assigned during routing -->
108
+
109
+ ## Upstream Dependencies
110
+ <!-- Fill during routing or execution -->
111
+
112
+ **Verification requirement:** Each dependency must be confirmed against repo truth.
113
+
114
+ ## Done Definition
115
+ <!-- Define concrete success criteria -->
116
+
117
+ ## Open Questions
118
+ <!-- Unknowns that may block execution -->
119
+
120
+ ---
121
+
122
+ ## Packet Type
123
+ ${type}
124
+
125
+ ## Typical Chain
126
+ ${chain}
127
+ `;
128
+
129
+ const packetsDir = join(claudeDir, "packets");
130
+ const filePath = join(packetsDir, `${taskId}.md`);
131
+
132
+ closePrompts();
133
+
134
+ const wrote = writeFileSafe(filePath, content);
135
+ if (wrote) {
136
+ console.log(`\nCreated: .claude/packets/${taskId}.md`);
137
+ console.log(`\nNext: roleos route .claude/packets/${taskId}.md`);
138
+ } else {
139
+ const err = new Error(`Packet already exists: ${filePath}`);
140
+ err.exitCode = 1;
141
+ err.hint = "Choose a different title or delete the existing packet.";
142
+ throw err;
143
+ }
144
+ }
@@ -0,0 +1,76 @@
1
+ import { createInterface } from "node:readline";
2
+
3
+ let _rl = null;
4
+ let _closed = false;
5
+
6
+ function getRL() {
7
+ if (_closed) return null;
8
+ if (!_rl) {
9
+ _rl = createInterface({
10
+ input: process.stdin,
11
+ output: process.stderr,
12
+ terminal: false,
13
+ });
14
+ _rl.on("close", () => {
15
+ _closed = true;
16
+ _rl = null;
17
+ });
18
+ }
19
+ return _rl;
20
+ }
21
+
22
+ /**
23
+ * Close the shared readline interface.
24
+ */
25
+ export function closePrompts() {
26
+ if (_rl) {
27
+ _rl.close();
28
+ _rl = null;
29
+ _closed = true;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * Ask a single question via readline and return the answer.
35
+ */
36
+ export function ask(question) {
37
+ process.stderr.write(question);
38
+ const rl = getRL();
39
+ if (!rl) return Promise.resolve("");
40
+
41
+ return new Promise((resolve) => {
42
+ const onLine = (line) => {
43
+ rl.removeListener("close", onClose);
44
+ resolve(line.trim());
45
+ };
46
+ const onClose = () => {
47
+ rl.removeListener("line", onLine);
48
+ resolve("");
49
+ };
50
+ rl.once("line", onLine);
51
+ rl.once("close", onClose);
52
+ });
53
+ }
54
+
55
+ /**
56
+ * Ask a question with a default value shown in brackets.
57
+ */
58
+ export async function askWithDefault(question, defaultValue) {
59
+ const answer = await ask(`${question} [${defaultValue}]: `);
60
+ return answer || defaultValue;
61
+ }
62
+
63
+ /**
64
+ * Ask a required question — re-prompts if empty.
65
+ * In non-TTY mode, accepts empty after first attempt to avoid infinite loop.
66
+ */
67
+ export async function askRequired(question) {
68
+ const isTTY = process.stdin.isTTY;
69
+
70
+ let answer = await ask(`${question}: `);
71
+ if (!answer && isTTY) {
72
+ console.error(" (required)");
73
+ answer = await ask(`${question}: `);
74
+ }
75
+ return answer || "(not provided)";
76
+ }