Skip to content
Merged

Dev #30

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .changeset/fix-focus-goal-only.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
---
"rolexjs": patch
"@rolexjs/prototype": patch
---

fix(focus): reject non-goal ids passed to focus

focus() now validates that the provided id is a goal node. Passing a plan, task, or other node type returns a clear error instead of silently corrupting the focused state.
13 changes: 13 additions & 0 deletions .changeset/issuex-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
---
"rolexjs": minor
"@rolexjs/prototype": minor
"@rolexjs/core": minor
---

feat: integrate IssueX for issue tracking between AI individuals

- Add IssueX support to Platform and LocalPlatform
- Add issue operations (publish, get, list, update, close, reopen, assign, comment, label, unlabel) to prototype ops
- Add issue-render module in rolexjs for human-readable output formatting
- Role.use() now renders issue results as readable text instead of raw JSON
- Add "number" to ParamType for issue instruction schemas
14 changes: 14 additions & 0 deletions .changeset/project-primitive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
"rolexjs": minor
"@rolexjs/prototype": minor
"@rolexjs/core": minor
"@rolexjs/mcp-server": minor
---

feat: add Project as a top-level organizational primitive

- Add project structure with 5 sub-concepts: scope, milestone, deliverable, wiki, and participation relation
- Add 9 project operations: launch, scope, milestone, achieve, enroll, remove, deliver, wiki, archive
- Add project-render module for human-readable project output (compact member display, milestone progress)
- Add project lifecycle BDD feature with full test coverage
- Fix BDD test suite for async Runtime refactor (await createRoleX, Role methods, writeContext via SQLite)
8 changes: 8 additions & 0 deletions .github/workflows/changesets.yml
Original file line number Diff line number Diff line change
Expand Up @@ -98,3 +98,11 @@ jobs:
echo "" >> $GITHUB_STEP_SUMMARY
echo "### Published packages:" >> $GITHUB_STEP_SUMMARY
echo '${{ steps.changesets.outputs.publishedPackages }}' | jq -r '.[] | "- `\(.name)@\(.version)`"' >> $GITHUB_STEP_SUMMARY

- name: Sync main back to dev
if: steps.changesets.outputs.published == 'true'
run: |
git fetch origin dev
git checkout dev
git merge main --no-edit
git push origin dev
8 changes: 7 additions & 1 deletion apps/mcp-server/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import { localPlatform } from "@rolexjs/local-platform";
import { FastMCP } from "fastmcp";
import { createRoleX, detail } from "rolexjs";
import { createRoleX, detail, type ProjectAction, renderProjectResult, type State } from "rolexjs";

import { z } from "zod";
import { instructions } from "./instructions.js";
Expand Down Expand Up @@ -257,6 +257,12 @@ server.addTool({
const result = await rolex.direct(locator, args);
if (result == null) return `${locator} done.`;
if (typeof result === "string") return result;
// Render project results as readable text
if (locator.startsWith("!project.")) {
const action = locator.slice("!project.".length) as ProjectAction;
const opResult = result as { state: State };
return renderProjectResult(action, opResult.state);
}
return JSON.stringify(result, null, 2);
},
});
Expand Down
95 changes: 95 additions & 0 deletions bdd/features/project-lifecycle.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
@project
Feature: Project lifecycle
Launch, scope, milestone, enroll, deliver, wiki, archive.

Background:
Given a fresh Rolex instance

# ===== launch =====

Scenario: Launch creates a project
When I direct "!project.launch" with:
| content | Feature: RoleX v2 |
| id | rolex-v2 |
Then the result process should be "launch"
And the result state name should be "project"
And the result state id should be "rolex-v2"

# ===== scope =====

Scenario: Scope defines project boundary
Given project "rolex-v2" exists
When I direct "!project.scope" with:
| project | rolex-v2 |
| content | Feature: Add Project primitive to RoleX |
| id | rolex-v2-scope |
Then the result process should be "scope"
And the result state name should be "scope"

# ===== milestone =====

Scenario: Milestone adds a checkpoint to project
Given project "rolex-v2" exists
When I direct "!project.milestone" with:
| project | rolex-v2 |
| content | Feature: Core structures complete |
| id | core-done |
Then the result process should be "milestone"
And the result state name should be "milestone"

Scenario: Achieve marks a milestone as done
Given project "rolex-v2" exists
And milestone "core-done" exists in project "rolex-v2"
When I direct "!project.achieve" with:
| milestone | core-done |
Then the result process should be "achieve"

# ===== enroll / remove =====

Scenario: Enroll adds individual to project
Given individual "sean" exists
And project "rolex-v2" exists
When I direct "!project.enroll" with:
| project | rolex-v2 |
| individual | sean |
Then the result process should be "enroll"

Scenario: Remove removes individual from project
Given individual "sean" exists
And project "rolex-v2" exists
And "sean" is enrolled in "rolex-v2"
When I direct "!project.remove" with:
| project | rolex-v2 |
| individual | sean |
Then the result process should be "remove"

# ===== deliver =====

Scenario: Deliver adds a deliverable to project
Given project "rolex-v2" exists
When I direct "!project.deliver" with:
| project | rolex-v2 |
| content | Feature: @rolexjs/core v2.0.0 published |
| id | core-v2-release |
Then the result process should be "deliver"
And the result state name should be "deliverable"

# ===== wiki =====

Scenario: Wiki adds a knowledge entry to project
Given project "rolex-v2" exists
When I direct "!project.wiki" with:
| project | rolex-v2 |
| content | Feature: Tech debt - parser needs refactoring |
| id | parser-tech-debt |
Then the result process should be "wiki"
And the result state name should be "wiki"

# ===== archive =====

Scenario: Archive moves project to past
Given project "rolex-v2" exists
When I direct "!project.archive" with:
| project | rolex-v2 |
Then the result process should be "archive"
And the result state name should be "past"
2 changes: 1 addition & 1 deletion bdd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"private": true,
"type": "module",
"devDependencies": {
"@deepracticex/bdd": "^0.2.0",
"@deepracticex/bdd": "^0.3.0",
"@modelcontextprotocol/sdk": "^1.27.1",
"@rolexjs/core": "workspace:*",
"@rolexjs/local-platform": "workspace:*",
Expand Down
37 changes: 6 additions & 31 deletions bdd/run.test.ts
Original file line number Diff line number Diff line change
@@ -1,32 +1,7 @@
/**
* BDD test entry point.
*
* Imports step definitions and support, then loads feature files.
* Bun's test runner executes the generated describe/test blocks natively.
*/
import { configure } from "@deepracticex/bdd";

import { loadFeature, setDefaultTimeout } from "@deepracticex/bdd";

// Support (unified world)
import "./support/world";

// Steps
import "./steps/mcp.steps";
import "./steps/context.steps";
import "./steps/direct.steps";
import "./steps/role.steps";

// Timeout: MCP/npx startup can take a while
setDefaultTimeout(60_000);

// ===== Journeys =====
loadFeature("bdd/journeys/mcp-startup.feature");
loadFeature("bdd/journeys/onboarding.feature");

// ===== Features =====
loadFeature("bdd/features/context-persistence.feature");
loadFeature("bdd/features/individual-lifecycle.feature");
loadFeature("bdd/features/organization-lifecycle.feature");
loadFeature("bdd/features/position-lifecycle.feature");
loadFeature("bdd/features/execution-loop.feature");
loadFeature("bdd/features/cognition-loop.feature");
await configure({
features: ["bdd/journeys/**/*.feature", "bdd/features/**/*.feature"],
steps: ["bdd/support/**/*.ts", "bdd/steps/**/*.ts"],
timeout: 60_000,
});
20 changes: 10 additions & 10 deletions bdd/steps/context.steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import type { BddWorld } from "../support/world";

// ===== Setup =====

Given("a fresh Rolex instance", function (this: BddWorld) {
this.initRolex();
Given("a fresh Rolex instance", async function (this: BddWorld) {
await this.initRolex();
});

Given(
Expand Down Expand Up @@ -43,19 +43,19 @@ Given(

// ===== Persistence setup =====

Given("persisted focusedGoalId is null", function (this: BddWorld) {
this.writeContext("sean", { focusedGoalId: null, focusedPlanId: null });
this.newSession();
Given("persisted focusedGoalId is null", async function (this: BddWorld) {
await this.writeContext("sean", { focusedGoalId: null, focusedPlanId: null });
await this.newSession();
});

Given("persisted focusedGoalId is {string}", function (this: BddWorld, goalId: string) {
this.writeContext("sean", { focusedGoalId: goalId, focusedPlanId: null });
this.newSession();
Given("persisted focusedGoalId is {string}", async function (this: BddWorld, goalId: string) {
await this.writeContext("sean", { focusedGoalId: goalId, focusedPlanId: null });
await this.newSession();
});

Given("no persisted context exists", function (this: BddWorld) {
Given("no persisted context exists", async function (this: BddWorld) {
// Don't write any context file — just create a new session
this.newSession();
await this.newSession();
});

// ===== Actions =====
Expand Down
22 changes: 22 additions & 0 deletions bdd/steps/direct.steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,28 @@ Given(
}
);

Given("project {string} exists", async function (this: BddWorld, id: string) {
await this.rolex!.direct("!project.launch", { content: `Feature: ${id}`, id });
});

Given(
"milestone {string} exists in project {string}",
async function (this: BddWorld, milestone: string, project: string) {
await this.rolex!.direct("!project.milestone", {
project,
content: `Feature: ${milestone}`,
id: milestone,
});
}
);

Given(
"{string} is enrolled in {string}",
async function (this: BddWorld, individual: string, project: string) {
await this.rolex!.direct("!project.enroll", { project, individual });
}
);

// ===== Direct call =====

When("I direct {string} with:", async function (this: BddWorld, command: string, table: DataTable) {
Expand Down
Loading