@rolexjs/local-platform 0.2.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.
@@ -0,0 +1,52 @@
1
+ import { Platform, Organization, Feature, Goal, Plan, Task } from '@rolexjs/core';
2
+
3
+ /**
4
+ * LocalPlatform — Local filesystem implementation of Platform.
5
+ *
6
+ * Everything lives under a single .rolex/ directory.
7
+ * rolex.json manages organization relationships (including teams).
8
+ *
9
+ * Directory convention:
10
+ * <rootDir>/rolex.json ← Organization config
11
+ * <rootDir>/<role>/identity/*.identity.feature ← Identity features
12
+ * <rootDir>/<role>/goals/<name>/<name>.goal.feature
13
+ * <rootDir>/<role>/goals/<name>/<name>.plan.feature
14
+ * <rootDir>/<role>/goals/<name>/tasks/<name>.task.feature
15
+ */
16
+
17
+ declare class LocalPlatform implements Platform {
18
+ private readonly rootDir;
19
+ private config;
20
+ constructor(rootDir: string);
21
+ found(name: string): void;
22
+ organization(): Organization;
23
+ born(name: string, source: string): Feature;
24
+ hire(name: string): void;
25
+ fire(name: string): void;
26
+ growup(roleId: string, type: "knowledge" | "experience" | "voice", name: string, source: string): Feature;
27
+ identity(roleId: string): Feature[];
28
+ activeGoal(roleId: string): (Goal & {
29
+ plan: Plan | null;
30
+ tasks: Task[];
31
+ }) | null;
32
+ createGoal(roleId: string, name: string, source: string, testable?: boolean): Goal;
33
+ createPlan(roleId: string, source: string): Plan;
34
+ createTask(roleId: string, name: string, source: string, testable?: boolean): Task;
35
+ completeGoal(roleId: string, experience?: string): void;
36
+ abandonGoal(roleId: string, experience?: string): void;
37
+ completeTask(roleId: string, name: string): void;
38
+ private loadConfig;
39
+ private saveConfig;
40
+ private resolveRoleDir;
41
+ private getActiveGoalDir;
42
+ private toFeature;
43
+ private extractScenarios;
44
+ private loadPlan;
45
+ private loadTasks;
46
+ private detectIdentityType;
47
+ private findFeatureFile;
48
+ private addTag;
49
+ private addDoneTag;
50
+ }
51
+
52
+ export { LocalPlatform };
package/dist/index.js ADDED
@@ -0,0 +1,278 @@
1
+ // src/LocalPlatform.ts
2
+ import { readdirSync, readFileSync, writeFileSync, mkdirSync, existsSync, rmSync } from "fs";
3
+ import { join, basename } from "path";
4
+ import { parse } from "@rolexjs/parser";
5
+ var LocalPlatform = class {
6
+ rootDir;
7
+ config = null;
8
+ constructor(rootDir) {
9
+ this.rootDir = rootDir;
10
+ }
11
+ // ========== Found ==========
12
+ found(name) {
13
+ mkdirSync(this.rootDir, { recursive: true });
14
+ const config = {
15
+ name,
16
+ teams: { default: [] }
17
+ };
18
+ this.saveConfig(config);
19
+ }
20
+ // ========== Organization ==========
21
+ organization() {
22
+ const config = this.loadConfig();
23
+ const roles = [];
24
+ for (const [teamName, roleNames] of Object.entries(config.teams)) {
25
+ for (const roleName of roleNames) {
26
+ roles.push({
27
+ name: roleName,
28
+ team: teamName
29
+ });
30
+ }
31
+ }
32
+ return { name: config.name, roles };
33
+ }
34
+ // ========== Born ==========
35
+ born(name, source) {
36
+ const roleDir = join(this.rootDir, name);
37
+ const identityDir = join(roleDir, "identity");
38
+ mkdirSync(identityDir, { recursive: true });
39
+ const filePath = join(identityDir, "persona.identity.feature");
40
+ writeFileSync(filePath, source, "utf-8");
41
+ const doc = parse(source);
42
+ return this.toFeature(doc.feature, "persona");
43
+ }
44
+ hire(name) {
45
+ const roleDir = join(this.rootDir, name);
46
+ if (!existsSync(join(roleDir, "identity", "persona.identity.feature"))) {
47
+ throw new Error(`Role not found: ${name}. Call born() first.`);
48
+ }
49
+ mkdirSync(join(roleDir, "goals"), { recursive: true });
50
+ const config = this.loadConfig();
51
+ const [firstTeam] = Object.keys(config.teams);
52
+ if (!config.teams[firstTeam].includes(name)) {
53
+ config.teams[firstTeam].push(name);
54
+ this.saveConfig(config);
55
+ }
56
+ }
57
+ fire(name) {
58
+ const roleDir = join(this.rootDir, name);
59
+ const goalsDir = join(roleDir, "goals");
60
+ if (!existsSync(goalsDir)) {
61
+ throw new Error(`Role not hired: ${name}`);
62
+ }
63
+ rmSync(goalsDir, { recursive: true, force: true });
64
+ const config = this.loadConfig();
65
+ for (const teamName of Object.keys(config.teams)) {
66
+ const idx = config.teams[teamName].indexOf(name);
67
+ if (idx !== -1) {
68
+ config.teams[teamName].splice(idx, 1);
69
+ break;
70
+ }
71
+ }
72
+ this.saveConfig(config);
73
+ }
74
+ // ========== Growup ==========
75
+ growup(roleId, type, name, source) {
76
+ const roleDir = this.resolveRoleDir(roleId);
77
+ const dir = join(roleDir, "identity");
78
+ mkdirSync(dir, { recursive: true });
79
+ const filePath = join(dir, `${name}.${type}.identity.feature`);
80
+ writeFileSync(filePath, source, "utf-8");
81
+ const doc = parse(source);
82
+ return this.toFeature(doc.feature, type);
83
+ }
84
+ // ========== Query ==========
85
+ identity(roleId) {
86
+ const roleDir = this.resolveRoleDir(roleId);
87
+ const dir = join(roleDir, "identity");
88
+ if (!existsSync(dir)) return [];
89
+ return readdirSync(dir).filter((f) => f.endsWith(".identity.feature")).sort().map((f) => {
90
+ const source = readFileSync(join(dir, f), "utf-8");
91
+ const doc = parse(source);
92
+ return this.toFeature(doc.feature, this.detectIdentityType(f));
93
+ });
94
+ }
95
+ activeGoal(roleId) {
96
+ const roleDir = this.resolveRoleDir(roleId);
97
+ const goalsDir = join(roleDir, "goals");
98
+ if (!existsSync(goalsDir)) return null;
99
+ const goalDirs = readdirSync(goalsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
100
+ for (const goalName of goalDirs) {
101
+ const goalDir = join(goalsDir, goalName);
102
+ const goalFile = this.findFeatureFile(goalDir, ".goal.feature");
103
+ if (!goalFile) continue;
104
+ const source = readFileSync(goalFile, "utf-8");
105
+ const doc = parse(source);
106
+ if (!doc.feature) continue;
107
+ if (doc.feature.tags.some((t) => t.name === "@done" || t.name === "@abandoned")) continue;
108
+ const goal = this.toFeature(doc.feature, "goal");
109
+ return {
110
+ ...goal,
111
+ plan: this.loadPlan(goalDir),
112
+ tasks: this.loadTasks(goalDir)
113
+ };
114
+ }
115
+ return null;
116
+ }
117
+ // ========== Write ==========
118
+ createGoal(roleId, name, source, testable) {
119
+ const roleDir = this.resolveRoleDir(roleId);
120
+ const goalDir = join(roleDir, "goals", name);
121
+ mkdirSync(goalDir, { recursive: true });
122
+ if (testable) source = `@testable
123
+ ${source}`;
124
+ const filePath = join(goalDir, `${name}.goal.feature`);
125
+ writeFileSync(filePath, source, "utf-8");
126
+ const doc = parse(source);
127
+ return this.toFeature(doc.feature, "goal");
128
+ }
129
+ createPlan(roleId, source) {
130
+ const roleDir = this.resolveRoleDir(roleId);
131
+ const goalDir = this.getActiveGoalDir(roleDir);
132
+ if (!goalDir) throw new Error("No active goal");
133
+ const goalName = basename(goalDir);
134
+ const filePath = join(goalDir, `${goalName}.plan.feature`);
135
+ writeFileSync(filePath, source, "utf-8");
136
+ const doc = parse(source);
137
+ return this.toFeature(doc.feature, "plan");
138
+ }
139
+ createTask(roleId, name, source, testable) {
140
+ const roleDir = this.resolveRoleDir(roleId);
141
+ const goalDir = this.getActiveGoalDir(roleDir);
142
+ if (!goalDir) throw new Error("No active goal");
143
+ const tasksDir = join(goalDir, "tasks");
144
+ mkdirSync(tasksDir, { recursive: true });
145
+ if (testable) source = `@testable
146
+ ${source}`;
147
+ const filePath = join(tasksDir, `${name}.task.feature`);
148
+ writeFileSync(filePath, source, "utf-8");
149
+ const doc = parse(source);
150
+ return this.toFeature(doc.feature, "task");
151
+ }
152
+ // ========== Close ==========
153
+ completeGoal(roleId, experience) {
154
+ const roleDir = this.resolveRoleDir(roleId);
155
+ const goalDir = this.getActiveGoalDir(roleDir);
156
+ if (!goalDir) throw new Error("No active goal");
157
+ const goalFile = this.findFeatureFile(goalDir, ".goal.feature");
158
+ this.addDoneTag(goalFile);
159
+ if (experience) {
160
+ const goalName = basename(goalDir);
161
+ this.growup(roleId, "experience", goalName, experience);
162
+ }
163
+ }
164
+ abandonGoal(roleId, experience) {
165
+ const roleDir = this.resolveRoleDir(roleId);
166
+ const goalDir = this.getActiveGoalDir(roleDir);
167
+ if (!goalDir) throw new Error("No active goal");
168
+ const goalFile = this.findFeatureFile(goalDir, ".goal.feature");
169
+ this.addTag(goalFile, "@abandoned");
170
+ if (experience) {
171
+ const goalName = basename(goalDir);
172
+ this.growup(roleId, "experience", goalName, experience);
173
+ }
174
+ }
175
+ completeTask(roleId, name) {
176
+ const roleDir = this.resolveRoleDir(roleId);
177
+ const goalDir = this.getActiveGoalDir(roleDir);
178
+ if (!goalDir) throw new Error("No active goal");
179
+ const tasksDir = join(goalDir, "tasks");
180
+ const taskFile = join(tasksDir, `${name}.task.feature`);
181
+ if (!existsSync(taskFile)) throw new Error(`Task not found: ${name}`);
182
+ this.addDoneTag(taskFile);
183
+ }
184
+ // ========== Internal ==========
185
+ loadConfig() {
186
+ if (this.config) return this.config;
187
+ const configPath = join(this.rootDir, "rolex.json");
188
+ if (existsSync(configPath)) {
189
+ this.config = JSON.parse(readFileSync(configPath, "utf-8"));
190
+ return this.config;
191
+ }
192
+ throw new Error(`No rolex.json found in ${this.rootDir}. Call found() first.`);
193
+ }
194
+ saveConfig(config) {
195
+ writeFileSync(join(this.rootDir, "rolex.json"), JSON.stringify(config, null, 2), "utf-8");
196
+ this.config = config;
197
+ }
198
+ resolveRoleDir(roleId) {
199
+ const roleDir = join(this.rootDir, roleId);
200
+ if (!existsSync(roleDir)) {
201
+ throw new Error(`Role directory not found: ${roleDir}`);
202
+ }
203
+ return roleDir;
204
+ }
205
+ getActiveGoalDir(roleDir) {
206
+ const goalsDir = join(roleDir, "goals");
207
+ if (!existsSync(goalsDir)) return null;
208
+ const goalDirs = readdirSync(goalsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name).sort();
209
+ for (const goalName of goalDirs) {
210
+ const goalDir = join(goalsDir, goalName);
211
+ const goalFile = this.findFeatureFile(goalDir, ".goal.feature");
212
+ if (!goalFile) continue;
213
+ const source = readFileSync(goalFile, "utf-8");
214
+ const doc = parse(source);
215
+ if (!doc.feature) continue;
216
+ if (!doc.feature.tags.some((t) => t.name === "@done" || t.name === "@abandoned")) {
217
+ return goalDir;
218
+ }
219
+ }
220
+ return null;
221
+ }
222
+ toFeature(gherkin, type) {
223
+ return {
224
+ ...gherkin,
225
+ type,
226
+ scenarios: this.extractScenarios(gherkin)
227
+ };
228
+ }
229
+ extractScenarios(feature) {
230
+ const featureTestable = feature.tags.some((t) => t.name === "@testable");
231
+ return (feature.children || []).filter((c) => c.scenario).map((c) => ({
232
+ ...c.scenario,
233
+ verifiable: featureTestable || c.scenario.tags.some((t) => t.name === "@testable")
234
+ }));
235
+ }
236
+ loadPlan(goalDir) {
237
+ const planFile = this.findFeatureFile(goalDir, ".plan.feature");
238
+ if (!planFile) return null;
239
+ const source = readFileSync(planFile, "utf-8");
240
+ const doc = parse(source);
241
+ if (!doc.feature) return null;
242
+ return this.toFeature(doc.feature, "plan");
243
+ }
244
+ loadTasks(goalDir) {
245
+ const tasksDir = join(goalDir, "tasks");
246
+ if (!existsSync(tasksDir)) return [];
247
+ return readdirSync(tasksDir).filter((f) => f.endsWith(".task.feature")).sort().map((f) => {
248
+ const source = readFileSync(join(tasksDir, f), "utf-8");
249
+ const doc = parse(source);
250
+ return this.toFeature(doc.feature, "task");
251
+ });
252
+ }
253
+ detectIdentityType(filename) {
254
+ if (filename === "persona.identity.feature") return "persona";
255
+ if (filename.endsWith(".knowledge.identity.feature")) return "knowledge";
256
+ if (filename.endsWith(".experience.identity.feature")) return "experience";
257
+ if (filename.endsWith(".voice.identity.feature")) return "voice";
258
+ return "knowledge";
259
+ }
260
+ findFeatureFile(dir, suffix) {
261
+ if (!existsSync(dir)) return null;
262
+ const file = readdirSync(dir).find((f) => f.endsWith(suffix));
263
+ return file ? join(dir, file) : null;
264
+ }
265
+ addTag(filePath, tag) {
266
+ const content = readFileSync(filePath, "utf-8");
267
+ const updated = content.replace(/^(Feature:)/m, `${tag}
268
+ $1`);
269
+ writeFileSync(filePath, updated, "utf-8");
270
+ }
271
+ addDoneTag(filePath) {
272
+ this.addTag(filePath, "@done");
273
+ }
274
+ };
275
+ export {
276
+ LocalPlatform
277
+ };
278
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/LocalPlatform.ts"],"sourcesContent":["/**\n * LocalPlatform — Local filesystem implementation of Platform.\n *\n * Everything lives under a single .rolex/ directory.\n * rolex.json manages organization relationships (including teams).\n *\n * Directory convention:\n * <rootDir>/rolex.json ← Organization config\n * <rootDir>/<role>/identity/*.identity.feature ← Identity features\n * <rootDir>/<role>/goals/<name>/<name>.goal.feature\n * <rootDir>/<role>/goals/<name>/<name>.plan.feature\n * <rootDir>/<role>/goals/<name>/tasks/<name>.task.feature\n */\n\nimport { readdirSync, readFileSync, writeFileSync, mkdirSync, existsSync, rmSync } from \"node:fs\";\nimport { join, basename } from \"node:path\";\nimport { parse } from \"@rolexjs/parser\";\nimport type { Feature as GherkinFeature } from \"@rolexjs/parser\";\nimport type {\n Platform,\n Organization,\n RoleEntry,\n Feature,\n Scenario,\n Goal,\n Plan,\n Task,\n} from \"@rolexjs/core\";\n\ninterface RolexConfig {\n name: string;\n teams: Record<string, string[]>;\n}\n\nexport class LocalPlatform implements Platform {\n private readonly rootDir: string;\n private config: RolexConfig | null = null;\n\n constructor(rootDir: string) {\n this.rootDir = rootDir;\n }\n\n // ========== Found ==========\n\n found(name: string): void {\n mkdirSync(this.rootDir, { recursive: true });\n\n const config: RolexConfig = {\n name,\n teams: { default: [] },\n };\n\n this.saveConfig(config);\n }\n\n // ========== Organization ==========\n\n organization(): Organization {\n const config = this.loadConfig();\n const roles: RoleEntry[] = [];\n\n for (const [teamName, roleNames] of Object.entries(config.teams)) {\n for (const roleName of roleNames) {\n roles.push({\n name: roleName,\n team: teamName,\n });\n }\n }\n\n return { name: config.name, roles };\n }\n\n // ========== Born ==========\n\n born(name: string, source: string): Feature {\n const roleDir = join(this.rootDir, name);\n const identityDir = join(roleDir, \"identity\");\n mkdirSync(identityDir, { recursive: true });\n\n const filePath = join(identityDir, \"persona.identity.feature\");\n writeFileSync(filePath, source, \"utf-8\");\n\n const doc = parse(source);\n return this.toFeature(doc.feature!, \"persona\");\n }\n\n hire(name: string): void {\n const roleDir = join(this.rootDir, name);\n\n if (!existsSync(join(roleDir, \"identity\", \"persona.identity.feature\"))) {\n throw new Error(`Role not found: ${name}. Call born() first.`);\n }\n\n mkdirSync(join(roleDir, \"goals\"), { recursive: true });\n\n // Update rolex.json — add role to default team\n const config = this.loadConfig();\n const [firstTeam] = Object.keys(config.teams);\n if (!config.teams[firstTeam].includes(name)) {\n config.teams[firstTeam].push(name);\n this.saveConfig(config);\n }\n }\n\n fire(name: string): void {\n const roleDir = join(this.rootDir, name);\n const goalsDir = join(roleDir, \"goals\");\n\n if (!existsSync(goalsDir)) {\n throw new Error(`Role not hired: ${name}`);\n }\n\n rmSync(goalsDir, { recursive: true, force: true });\n\n // Update rolex.json — remove role from team\n const config = this.loadConfig();\n for (const teamName of Object.keys(config.teams)) {\n const idx = config.teams[teamName].indexOf(name);\n if (idx !== -1) {\n config.teams[teamName].splice(idx, 1);\n break;\n }\n }\n this.saveConfig(config);\n }\n\n // ========== Growup ==========\n\n growup(\n roleId: string,\n type: \"knowledge\" | \"experience\" | \"voice\",\n name: string,\n source: string\n ): Feature {\n const roleDir = this.resolveRoleDir(roleId);\n const dir = join(roleDir, \"identity\");\n mkdirSync(dir, { recursive: true });\n\n const filePath = join(dir, `${name}.${type}.identity.feature`);\n writeFileSync(filePath, source, \"utf-8\");\n\n const doc = parse(source);\n return this.toFeature(doc.feature!, type);\n }\n\n // ========== Query ==========\n\n identity(roleId: string): Feature[] {\n const roleDir = this.resolveRoleDir(roleId);\n const dir = join(roleDir, \"identity\");\n if (!existsSync(dir)) return [];\n\n return readdirSync(dir)\n .filter((f) => f.endsWith(\".identity.feature\"))\n .sort()\n .map((f) => {\n const source = readFileSync(join(dir, f), \"utf-8\");\n const doc = parse(source);\n return this.toFeature(doc.feature!, this.detectIdentityType(f));\n });\n }\n\n activeGoal(roleId: string): (Goal & { plan: Plan | null; tasks: Task[] }) | null {\n const roleDir = this.resolveRoleDir(roleId);\n const goalsDir = join(roleDir, \"goals\");\n if (!existsSync(goalsDir)) return null;\n\n const goalDirs = readdirSync(goalsDir, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => d.name)\n .sort();\n\n for (const goalName of goalDirs) {\n const goalDir = join(goalsDir, goalName);\n const goalFile = this.findFeatureFile(goalDir, \".goal.feature\");\n if (!goalFile) continue;\n\n const source = readFileSync(goalFile, \"utf-8\");\n const doc = parse(source);\n if (!doc.feature) continue;\n\n if (doc.feature.tags.some((t) => t.name === \"@done\" || t.name === \"@abandoned\")) continue;\n\n const goal = this.toFeature(doc.feature, \"goal\") as Goal;\n return {\n ...goal,\n plan: this.loadPlan(goalDir),\n tasks: this.loadTasks(goalDir),\n };\n }\n\n return null;\n }\n\n // ========== Write ==========\n\n createGoal(roleId: string, name: string, source: string, testable?: boolean): Goal {\n const roleDir = this.resolveRoleDir(roleId);\n const goalDir = join(roleDir, \"goals\", name);\n mkdirSync(goalDir, { recursive: true });\n\n if (testable) source = `@testable\\n${source}`;\n const filePath = join(goalDir, `${name}.goal.feature`);\n writeFileSync(filePath, source, \"utf-8\");\n\n const doc = parse(source);\n return this.toFeature(doc.feature!, \"goal\") as Goal;\n }\n\n createPlan(roleId: string, source: string): Plan {\n const roleDir = this.resolveRoleDir(roleId);\n const goalDir = this.getActiveGoalDir(roleDir);\n if (!goalDir) throw new Error(\"No active goal\");\n\n const goalName = basename(goalDir);\n const filePath = join(goalDir, `${goalName}.plan.feature`);\n writeFileSync(filePath, source, \"utf-8\");\n\n const doc = parse(source);\n return this.toFeature(doc.feature!, \"plan\") as Plan;\n }\n\n createTask(roleId: string, name: string, source: string, testable?: boolean): Task {\n const roleDir = this.resolveRoleDir(roleId);\n const goalDir = this.getActiveGoalDir(roleDir);\n if (!goalDir) throw new Error(\"No active goal\");\n\n const tasksDir = join(goalDir, \"tasks\");\n mkdirSync(tasksDir, { recursive: true });\n\n if (testable) source = `@testable\\n${source}`;\n const filePath = join(tasksDir, `${name}.task.feature`);\n writeFileSync(filePath, source, \"utf-8\");\n\n const doc = parse(source);\n return this.toFeature(doc.feature!, \"task\") as Task;\n }\n\n // ========== Close ==========\n\n completeGoal(roleId: string, experience?: string): void {\n const roleDir = this.resolveRoleDir(roleId);\n const goalDir = this.getActiveGoalDir(roleDir);\n if (!goalDir) throw new Error(\"No active goal\");\n\n const goalFile = this.findFeatureFile(goalDir, \".goal.feature\")!;\n this.addDoneTag(goalFile);\n\n if (experience) {\n const goalName = basename(goalDir);\n this.growup(roleId, \"experience\", goalName, experience);\n }\n }\n\n abandonGoal(roleId: string, experience?: string): void {\n const roleDir = this.resolveRoleDir(roleId);\n const goalDir = this.getActiveGoalDir(roleDir);\n if (!goalDir) throw new Error(\"No active goal\");\n\n const goalFile = this.findFeatureFile(goalDir, \".goal.feature\")!;\n this.addTag(goalFile, \"@abandoned\");\n\n if (experience) {\n const goalName = basename(goalDir);\n this.growup(roleId, \"experience\", goalName, experience);\n }\n }\n\n completeTask(roleId: string, name: string): void {\n const roleDir = this.resolveRoleDir(roleId);\n const goalDir = this.getActiveGoalDir(roleDir);\n if (!goalDir) throw new Error(\"No active goal\");\n\n const tasksDir = join(goalDir, \"tasks\");\n const taskFile = join(tasksDir, `${name}.task.feature`);\n if (!existsSync(taskFile)) throw new Error(`Task not found: ${name}`);\n\n this.addDoneTag(taskFile);\n }\n\n // ========== Internal ==========\n\n private loadConfig(): RolexConfig {\n if (this.config) return this.config;\n\n const configPath = join(this.rootDir, \"rolex.json\");\n if (existsSync(configPath)) {\n this.config = JSON.parse(readFileSync(configPath, \"utf-8\"));\n return this.config!;\n }\n\n throw new Error(`No rolex.json found in ${this.rootDir}. Call found() first.`);\n }\n\n private saveConfig(config: RolexConfig): void {\n writeFileSync(join(this.rootDir, \"rolex.json\"), JSON.stringify(config, null, 2), \"utf-8\");\n this.config = config;\n }\n\n private resolveRoleDir(roleId: string): string {\n const roleDir = join(this.rootDir, roleId);\n if (!existsSync(roleDir)) {\n throw new Error(`Role directory not found: ${roleDir}`);\n }\n return roleDir;\n }\n\n private getActiveGoalDir(roleDir: string): string | null {\n const goalsDir = join(roleDir, \"goals\");\n if (!existsSync(goalsDir)) return null;\n\n const goalDirs = readdirSync(goalsDir, { withFileTypes: true })\n .filter((d) => d.isDirectory())\n .map((d) => d.name)\n .sort();\n\n for (const goalName of goalDirs) {\n const goalDir = join(goalsDir, goalName);\n const goalFile = this.findFeatureFile(goalDir, \".goal.feature\");\n if (!goalFile) continue;\n\n const source = readFileSync(goalFile, \"utf-8\");\n const doc = parse(source);\n if (!doc.feature) continue;\n\n if (!doc.feature.tags.some((t) => t.name === \"@done\" || t.name === \"@abandoned\")) {\n return goalDir;\n }\n }\n\n return null;\n }\n\n private toFeature(gherkin: GherkinFeature, type: Feature[\"type\"]): Feature {\n return {\n ...gherkin,\n type,\n scenarios: this.extractScenarios(gherkin),\n };\n }\n\n private extractScenarios(feature: GherkinFeature): Scenario[] {\n const featureTestable = feature.tags.some((t) => t.name === \"@testable\");\n return (feature.children || [])\n .filter((c) => c.scenario)\n .map((c) => ({\n ...c.scenario!,\n verifiable: featureTestable || c.scenario!.tags.some((t) => t.name === \"@testable\"),\n }));\n }\n\n private loadPlan(goalDir: string): Plan | null {\n const planFile = this.findFeatureFile(goalDir, \".plan.feature\");\n if (!planFile) return null;\n\n const source = readFileSync(planFile, \"utf-8\");\n const doc = parse(source);\n if (!doc.feature) return null;\n\n return this.toFeature(doc.feature, \"plan\") as Plan;\n }\n\n private loadTasks(goalDir: string): Task[] {\n const tasksDir = join(goalDir, \"tasks\");\n if (!existsSync(tasksDir)) return [];\n\n return readdirSync(tasksDir)\n .filter((f) => f.endsWith(\".task.feature\"))\n .sort()\n .map((f) => {\n const source = readFileSync(join(tasksDir, f), \"utf-8\");\n const doc = parse(source);\n return this.toFeature(doc.feature!, \"task\") as Task;\n });\n }\n\n private detectIdentityType(filename: string): Feature[\"type\"] {\n if (filename === \"persona.identity.feature\") return \"persona\";\n if (filename.endsWith(\".knowledge.identity.feature\")) return \"knowledge\";\n if (filename.endsWith(\".experience.identity.feature\")) return \"experience\";\n if (filename.endsWith(\".voice.identity.feature\")) return \"voice\";\n return \"knowledge\";\n }\n\n private findFeatureFile(dir: string, suffix: string): string | null {\n if (!existsSync(dir)) return null;\n const file = readdirSync(dir).find((f) => f.endsWith(suffix));\n return file ? join(dir, file) : null;\n }\n\n private addTag(filePath: string, tag: string): void {\n const content = readFileSync(filePath, \"utf-8\");\n const updated = content.replace(/^(Feature:)/m, `${tag}\\n$1`);\n writeFileSync(filePath, updated, \"utf-8\");\n }\n\n private addDoneTag(filePath: string): void {\n this.addTag(filePath, \"@done\");\n }\n}\n"],"mappings":"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","names":[]}
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@rolexjs/local-platform",
3
+ "version": "0.2.0",
4
+ "description": "Local filesystem Platform for RoleX — stores roles in .rolex/ directories",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "default": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist"
16
+ ],
17
+ "scripts": {
18
+ "build": "tsup",
19
+ "clean": "rm -rf dist"
20
+ },
21
+ "dependencies": {
22
+ "@rolexjs/core": "^0.3.0",
23
+ "@rolexjs/parser": "^0.3.0"
24
+ },
25
+ "devDependencies": {
26
+ "rolexjs": "^0.3.0"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ }
31
+ }