From a1e8436f0990645fdfc1f163fa550fa43a497366 Mon Sep 17 00:00:00 2001 From: brendohxd Date: Sun, 19 Jul 2026 14:22:03 +0800 Subject: [PATCH 1/3] Add project status control plane --- STATUS.md | 147 +++++++ apps/console/app.js | 88 +++- apps/console/index.html | 31 +- apps/console/state/index.json | 607 +++++++++++++++++++++++++ apps/console/styles.css | 36 ++ apps/mcp-server/src/index.js | 41 ++ docs/.nojekyll | 0 docs/app.js | 137 ++++++ docs/index.html | 122 ++++++ docs/state/index.json | 729 +++++++++++++++++++++++++++++++ docs/styles.css | 144 ++++++ package.json | 7 +- project/status.json | 402 +++++++++++++++++ project/status.schema.json | 104 +++++ scripts/build-console-state.mjs | 9 +- scripts/build-pages.mjs | 11 + scripts/build-project-status.mjs | 7 + scripts/check-pages.mjs | 19 + scripts/check-project-status.mjs | 18 + scripts/project-status-lib.mjs | 164 +++++++ test/mcp.test.js | 10 + test/project-status.test.js | 38 ++ 22 files changed, 2853 insertions(+), 18 deletions(-) create mode 100644 STATUS.md create mode 100644 docs/.nojekyll create mode 100644 docs/app.js create mode 100644 docs/index.html create mode 100644 docs/state/index.json create mode 100644 docs/styles.css create mode 100644 project/status.json create mode 100644 project/status.schema.json create mode 100644 scripts/build-pages.mjs create mode 100644 scripts/build-project-status.mjs create mode 100644 scripts/check-pages.mjs create mode 100644 scripts/check-project-status.mjs create mode 100644 scripts/project-status-lib.mjs create mode 100644 test/project-status.test.js diff --git a/STATUS.md b/STATUS.md new file mode 100644 index 0000000..7754f60 --- /dev/null +++ b/STATUS.md @@ -0,0 +1,147 @@ +# Project status + +> Generated from [project/status.json](project/status.json) on 2026-07-19. Do not edit this file directly. + +**Stage:** pre-alpha
+**Current milestone:** M1 — Local vertical slice
+**Roadmap:** 12/28 actionable items complete; 3 blocked; 3 deferred. + +| Milestone | Status | Complete | Blocked | Deferred | +|---|---:|---:|---:|---:| +| M0 — Protocol kernel | complete | 3/3 | 0 | 0 | +| M1 — Local vertical slice | planned | 3/6 | 2 | 0 | +| M2 — GitHub collaboration adapter | planned | 2/6 | 0 | 0 | +| M3 — Research and assessment profiles | planned | 1/6 | 0 | 1 | +| M4 — Public console and integrations | planned | 3/7 | 1 | 0 | +| M5 — Optional managed service | deferred | 0/0 | 0 | 2 | + +## M0 — Protocol kernel + +**Status:** complete + +Exit criteria: +- The provider-neutral protocol kernel, local read-only MCP surface, fixtures, and public boundary are implemented. + +- [x] **M0-PROTOCOL: Canonical schemas, hashes, and event-chain verification** — done + - Evidence: [Foundation commit](https://github.com/brendohxd/Project-Relay/commit/99f8c9c) +- [x] **M0-MCP: Read-only local MCP server** — done + - Evidence: [M0 implementation](https://github.com/brendohxd/Project-Relay/commit/99f8c9c) +- [x] **M0-PUBLIC: Public-boundary scanner and repository checks** — done + - Evidence: [M0 implementation](https://github.com/brendohxd/Project-Relay/commit/99f8c9c) + +## M1 — Local vertical slice + +**Status:** planned + +Exit criteria: +- The complete local remediation loop passes Windows and Linux verification and receives human review. + +- [x] **M1-LIFECYCLE: Policy transition engine and remediation fixture** — done + - Evidence: [M1 implementation commit](https://github.com/brendohxd/Project-Relay/commit/d899218) +- [x] **M1-EVIDENCE: Evidence CLI, independent review, reproduction, and human gates** — done + - Evidence: [M1 review fixes](https://github.com/brendohxd/Project-Relay/commit/786648e) +- [x] **M1-LOCAL-VERIFY: Windows local verification with 18 tests** — done + - Evidence: [Draft PR verification record](https://github.com/brendohxd/Project-Relay/pull/1#issuecomment-5014591903) +- [ ] **M1-HUMAN-REVIEW: Human review of draft PR #1** — blocked + - Blocker (human-action): Draft PR #1 has no recorded human review decision. + - Next: Review the protocol behavior, limitations, and public boundary in draft PR #1. +- [ ] **M1-LINUX-VERIFY: Linux conformance verification** — blocked + - Blocker (external): GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists. + - Next: Rerun the existing matrix when GitHub-hosted runners become available. + - Evidence: [Blocked GitHub Actions run](https://github.com/brendohxd/Project-Relay/actions) +- [ ] **M1-MERGE: Merge the reviewed M1 candidate** — planned + - Next: Merge only after human review and Linux verification both pass. + +## M2 — GitHub collaboration adapter + +**Status:** planned + +Exit criteria: +- An approved Relay proposal can be translated into an idempotent, least-privilege GitHub change with conflict and failure records. + +- [x] **M2-CAPABILITIES: Declared actor capabilities** — done + - Evidence: [Capability foundation](https://github.com/brendohxd/Project-Relay/commit/11dc90a) +- [x] **M2-CAUSAL-LINKS: Typed causal provenance links** — done + - Evidence: [Causal-link foundation](https://github.com/brendohxd/Project-Relay/commit/11dc90a) +- [ ] **M2-PROPOSAL: Deterministic network-free GitHub mutation proposal** — planned + - Next: Define proposal input and output contracts before authentication or writes. +- [ ] **M2-CONTRACT: Fake-GitHub contract, conflict, retry, and idempotency tests** — planned + - Next: Test the adapter without live repository mutations. +- [ ] **M2-AUTH: Repository-scoped least-privilege authentication** — planned + - Next: Add dedicated fine-grained credentials only after proposal behavior is reviewed. +- [ ] **M2-WRITE-PILOT: Explicitly approved live write pilot** — planned + - Next: Require fresh user approval and a clean threat-model review. + +## M3 — Research and assessment profiles + +**Status:** planned + +Exit criteria: +- Synthetic high-risk profiles prove blind reproduction, independence, disagreement, supersession, and human authority before real domain use. + +- [x] **M3-REPRODUCTION: Independent high-risk reproduction core** — done + - Evidence: [Independent reproduction gates](https://github.com/brendohxd/Project-Relay/commit/786648e) +- [ ] **M3-BLIND-PACKET: Blind reproduction packet contract** — planned + - Next: Design and test against synthetic claims before any ITSM packet. +- [ ] **M3-DISAGREEMENT: Adversarial review and disagreement ledger** — planned + - Next: Preserve incompatible findings without forced consensus. +- [ ] **M3-SUPERSESSION: Remediation and supersession graph** — planned + - Next: Build on typed causal links and immutable prior records. +- [ ] **M3-ITSM-PROFILE: ITSM operational policy profile** — planned + - Next: Implement only after the synthetic research profile passes. +- [ ] **M3-EDUCATION-PROFILE: Synthetic education marking and moderation profile** — planned + - Next: Model rubric assessment, moderation, appeals, and named human authority without real student data. +- [ ] **M3-HIRING-PROFILE: Synthetic hiring-assistance profile** — deferred + - Rationale: Hiring requires dedicated privacy, fairness, discrimination, explainability, and legal threat models; Relay must remain decision support with human authority. + +## M4 — Public console and integrations + +**Status:** planned + +Exit criteria: +- Users can inspect project and task status through credential-free projections and supported client guides. + +- [x] **M4-STATUS: Canonical project status and generated GitHub view** — done + - Evidence: [Canonical project status](project/status.json), [Generated GitHub status](STATUS.md) +- [x] **M4-CONSOLE: Visual milestone, blocker, and pilot dashboard** — done + - Evidence: [Read-only console](apps/console/index.html) +- [x] **M4-PAGES-READY: GitHub Pages-ready static bundle** — done + - Evidence: [Pages entry point](docs/index.html) +- [ ] **M4-PAGES-PUBLISH: Publish the visual console on GitHub Pages** — blocked + - Blocker (external): GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction. + - Next: After merge, configure Pages from main /docs and record the resulting public URL or blocker. +- [ ] **M4-CLIENT-GUIDES: Codex, Claude, Gemini, and compatible MCP guides** — planned + - Next: Document read-only status and task access before any write integrations. +- [ ] **M4-MANUAL-BRIDGE: Manual packet bridge** — planned + - Next: Support clients without GitHub or MCP while preserving the same evidence contract. +- [ ] **M4-NOTIFICATIONS: Notifications and human approval inbox** — planned + - Next: Begin only after authenticated M2 write boundaries are proven. + +## M5 — Optional managed service + +**Status:** deferred + +Exit criteria: +- Security, funding, licensing, product demand, and service responsibilities are explicitly approved before hosted development. + +- [ ] **M5-HOSTED: Hosted authentication, secrets, tenancy, and audit service** — deferred + - Rationale: No hosted service is authorised before security, funding, licensing, and demand gates are resolved. +- [ ] **M5-BILLING: Usage controls, billing, support, and service objectives** — deferred + - Rationale: No paid infrastructure or billing work is required for the current local-first roadmap. + +# Proving grounds + +## ITSM proving ground + +**Visibility:** operational-summary-only
+**Boundary:** Relay may show workflow readiness and gate progress only. ITSM equations, claims, datasets, results, manuscripts, and scientific acceptance remain in the separate ITSM repository and knowledge base. + +- [x] **Relay and ITSM separation rule documented** — done + - Evidence: [ITSM pilot operating model](https://app.notion.com/p/3a236fb4bd1d810fb409f87153bc3e84) +- [x] **Operational owner, review, reproduction, audit, and authority roles documented** — done + - Evidence: [ITSM pilot operating model](https://app.notion.com/p/3a236fb4bd1d810fb409f87153bc3e84) +- [ ] **Synthetic research profile passes before ITSM use** — planned + - Next: Complete the M3 synthetic research-gate fixture. +- [ ] **First real ITSM Relay packet** — blocked + - Blocker (dependency): A real ITSM packet must wait for the synthetic M3 profile and explicit selection of a boundary-safe pilot task. + - Next: Select the pilot only after the synthetic profile passes. diff --git a/apps/console/app.js b/apps/console/app.js index d601cc5..6456080 100644 --- a/apps/console/app.js +++ b/apps/console/app.js @@ -1,4 +1,12 @@ const elements = { + projectStage: document.querySelector("#project-stage"), + currentMilestone: document.querySelector("#current-milestone"), + roadmapComplete: document.querySelector("#roadmap-complete"), + roadmapBlocked: document.querySelector("#roadmap-blocked"), + roadmapDeferred: document.querySelector("#roadmap-deferred"), + milestoneRows: document.querySelector("#milestone-rows"), + pilotRows: document.querySelector("#pilot-rows"), + projectUpdated: document.querySelector("#project-updated"), tasks: document.querySelector("#metric-tasks"), active: document.querySelector("#metric-active"), evidence: document.querySelector("#metric-evidence"), @@ -17,11 +25,65 @@ function cell(className, text) { return node; } +function stateLabel(state) { + return state.replaceAll("_", " "); +} + +function renderMilestone(milestone) { + const card = document.createElement("article"); + card.className = `milestone-card milestone-${milestone.summary.state}`; + const heading = document.createElement("div"); + heading.className = "milestone-heading"; + const name = document.createElement("div"); + const id = cell("milestone-id", milestone.id); + const title = document.createElement("h3"); + title.textContent = milestone.title; + name.append(id, title); + heading.append(name, cell(`badge badge-${milestone.summary.state}`, stateLabel(milestone.summary.state))); + const progress = document.createElement("p"); + progress.className = "milestone-progress"; + progress.textContent = `${milestone.summary.completed}/${milestone.summary.actionable} actionable items complete`; + const list = document.createElement("ul"); + list.className = "roadmap-items"; + for (const item of milestone.items) { + const row = document.createElement("li"); + row.className = `roadmap-item item-${item.state}`; + row.append(cell("item-marker", item.state === "done" ? "✓" : item.state === "blocked" ? "!" : "·")); + const detail = document.createElement("span"); + const itemTitle = document.createElement("strong"); + itemTitle.textContent = item.title; + detail.append(itemTitle, cell("item-state", stateLabel(item.state))); + if (item.blocker) detail.append(cell("item-note", item.blocker.summary)); + else if (item.next_action && item.state !== "done") detail.append(cell("item-note", item.next_action)); + row.append(detail); + list.append(row); + } + card.append(heading, progress, list); + return card; +} + +function renderPilot(pilot) { + const card = document.createElement("article"); + card.className = "pilot-card"; + const heading = document.createElement("div"); + heading.className = "pilot-heading"; + const title = document.createElement("h3"); + title.textContent = pilot.name; + heading.append(title, cell("badge badge-boundary", pilot.visibility)); + const boundary = document.createElement("p"); + boundary.className = "pilot-boundary"; + boundary.textContent = pilot.boundary; + const progress = document.createElement("p"); + progress.className = "pilot-progress"; + progress.textContent = `${pilot.summary.completed}/${pilot.summary.actionable} operational gates complete · ${pilot.summary.counts.blocked} blocked`; + card.append(heading, boundary, progress); + return card; +} + function renderTask(task) { const row = document.createElement("article"); row.className = "task-row"; row.setAttribute("role", "row"); - const title = document.createElement("span"); title.className = "task-title"; const strong = document.createElement("strong"); @@ -29,27 +91,17 @@ function renderTask(task) { const small = document.createElement("small"); small.textContent = `${task.id} · ${task.history.length} transitions · policy ${task.policy_valid ? "valid" : "failed"}`; title.append(strong, small); - row.append( title, - cell( - `badge badge-state ${task.policy_valid ? "" : "badge-policy-failed"}`, - task.derived_state.replaceAll("_", " ") - ), + cell(`badge badge-state ${task.policy_valid ? "" : "badge-policy-failed"}`, stateLabel(task.derived_state)), cell(`badge badge-risk-${task.risk}`, task.risk), cell("", String(task.evidence_count)), cell("", String(task.review_count)) ); - const gates = document.createElement("div"); gates.className = "task-gates"; for (const [name, satisfied] of Object.entries(task.gates)) { - gates.append( - cell( - `gate ${satisfied ? "gate-pass" : "gate-fail"}`, - `${satisfied ? "✓" : "×"} ${name.replaceAll("_", " ")}` - ) - ); + gates.append(cell(`gate ${satisfied ? "gate-pass" : "gate-fail"}`, `${satisfied ? "✓" : "×"} ${stateLabel(name)}`)); } row.append(gates); return row; @@ -59,6 +111,16 @@ try { const response = await fetch("./state/index.json", { cache: "no-store" }); if (!response.ok) throw new Error(`State request failed with ${response.status}`); const state = await response.json(); + const project = state.project; + + elements.projectStage.textContent = project.project.stage; + elements.currentMilestone.textContent = project.summary.current_milestone ?? "complete"; + elements.roadmapComplete.textContent = `${project.summary.completed}/${project.summary.actionable}`; + elements.roadmapBlocked.textContent = project.summary.counts.blocked; + elements.roadmapDeferred.textContent = project.summary.counts.deferred; + elements.projectUpdated.textContent = `updated · ${project.updated_at}`; + elements.milestoneRows.replaceChildren(...project.milestones.map(renderMilestone)); + elements.pilotRows.replaceChildren(...project.pilots.map(renderPilot)); elements.tasks.textContent = state.summary.tasks; elements.active.textContent = state.summary.active; diff --git a/apps/console/index.html b/apps/console/index.html index 13761df..a79b4fb 100644 --- a/apps/console/index.html +++ b/apps/console/index.html @@ -29,8 +29,8 @@

One record for work
across many minds.

reproducible evidence, explicit disagreements, and accountable decisions.

- Inspect workspace - Read architecture + Inspect roadmap + Read architecture
@@ -38,6 +38,33 @@

One record for work
across many minds.

Tasks
Active
Evidence
+
+
+
+
Project control plane
+

Roadmap and blockers

+
+
Loading status?
+
+
+
Stage?
+
Current?
+
Complete?
+
Blocked?
+
Deferred?
+
+
+
+ +
+
+
+
Boundary-safe proving grounds
+

Pilot readiness

+
+
+
+
Reviews
Decisions
diff --git a/apps/console/state/index.json b/apps/console/state/index.json index 0f7f62f..5628241 100644 --- a/apps/console/state/index.json +++ b/apps/console/state/index.json @@ -1,6 +1,613 @@ { "protocol_version": "0.1.0", "generated_from": "examples/m1", + "project": { + "schema_version": "0.1.0", + "project": { + "id": "project-relay", + "name": "Project Relay", + "stage": "pre-alpha", + "repository": "https://github.com/brendohxd/Project-Relay" + }, + "updated_at": "2026-07-19", + "milestones": [ + { + "id": "M0", + "title": "Protocol kernel", + "exit_criteria": [ + "The provider-neutral protocol kernel, local read-only MCP surface, fixtures, and public boundary are implemented." + ], + "items": [ + { + "id": "M0-PROTOCOL", + "title": "Canonical schemas, hashes, and event-chain verification", + "state": "done", + "evidence": [ + { + "label": "Foundation commit", + "uri": "https://github.com/brendohxd/Project-Relay/commit/99f8c9c" + } + ] + }, + { + "id": "M0-MCP", + "title": "Read-only local MCP server", + "state": "done", + "evidence": [ + { + "label": "M0 implementation", + "uri": "https://github.com/brendohxd/Project-Relay/commit/99f8c9c" + } + ] + }, + { + "id": "M0-PUBLIC", + "title": "Public-boundary scanner and repository checks", + "state": "done", + "evidence": [ + { + "label": "M0 implementation", + "uri": "https://github.com/brendohxd/Project-Relay/commit/99f8c9c" + } + ] + } + ], + "summary": { + "state": "complete", + "counts": { + "done": 3, + "in_progress": 0, + "blocked": 0, + "planned": 0, + "deferred": 0 + }, + "total": 3, + "actionable": 3, + "completed": 3, + "blockers": [] + } + }, + { + "id": "M1", + "title": "Local vertical slice", + "exit_criteria": [ + "The complete local remediation loop passes Windows and Linux verification and receives human review." + ], + "items": [ + { + "id": "M1-LIFECYCLE", + "title": "Policy transition engine and remediation fixture", + "state": "done", + "evidence": [ + { + "label": "M1 implementation commit", + "uri": "https://github.com/brendohxd/Project-Relay/commit/d899218" + } + ] + }, + { + "id": "M1-EVIDENCE", + "title": "Evidence CLI, independent review, reproduction, and human gates", + "state": "done", + "evidence": [ + { + "label": "M1 review fixes", + "uri": "https://github.com/brendohxd/Project-Relay/commit/786648e" + } + ] + }, + { + "id": "M1-LOCAL-VERIFY", + "title": "Windows local verification with 18 tests", + "state": "done", + "evidence": [ + { + "label": "Draft PR verification record", + "uri": "https://github.com/brendohxd/Project-Relay/pull/1#issuecomment-5014591903" + } + ] + }, + { + "id": "M1-HUMAN-REVIEW", + "title": "Human review of draft PR #1", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "human-action", + "summary": "Draft PR #1 has no recorded human review decision." + }, + "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." + }, + { + "id": "M1-LINUX-VERIFY", + "title": "Linux conformance verification", + "state": "blocked", + "evidence": [ + { + "label": "Blocked GitHub Actions run", + "uri": "https://github.com/brendohxd/Project-Relay/actions" + } + ], + "blocker": { + "kind": "external", + "summary": "GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists." + }, + "next_action": "Rerun the existing matrix when GitHub-hosted runners become available." + }, + { + "id": "M1-MERGE", + "title": "Merge the reviewed M1 candidate", + "state": "planned", + "evidence": [], + "next_action": "Merge only after human review and Linux verification both pass." + } + ], + "summary": { + "state": "planned", + "counts": { + "done": 3, + "in_progress": 0, + "blocked": 2, + "planned": 1, + "deferred": 0 + }, + "total": 6, + "actionable": 6, + "completed": 3, + "blockers": [ + { + "id": "M1-HUMAN-REVIEW", + "title": "Human review of draft PR #1", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "human-action", + "summary": "Draft PR #1 has no recorded human review decision." + }, + "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." + }, + { + "id": "M1-LINUX-VERIFY", + "title": "Linux conformance verification", + "state": "blocked", + "evidence": [ + { + "label": "Blocked GitHub Actions run", + "uri": "https://github.com/brendohxd/Project-Relay/actions" + } + ], + "blocker": { + "kind": "external", + "summary": "GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists." + }, + "next_action": "Rerun the existing matrix when GitHub-hosted runners become available." + } + ] + } + }, + { + "id": "M2", + "title": "GitHub collaboration adapter", + "exit_criteria": [ + "An approved Relay proposal can be translated into an idempotent, least-privilege GitHub change with conflict and failure records." + ], + "items": [ + { + "id": "M2-CAPABILITIES", + "title": "Declared actor capabilities", + "state": "done", + "evidence": [ + { + "label": "Capability foundation", + "uri": "https://github.com/brendohxd/Project-Relay/commit/11dc90a" + } + ] + }, + { + "id": "M2-CAUSAL-LINKS", + "title": "Typed causal provenance links", + "state": "done", + "evidence": [ + { + "label": "Causal-link foundation", + "uri": "https://github.com/brendohxd/Project-Relay/commit/11dc90a" + } + ] + }, + { + "id": "M2-PROPOSAL", + "title": "Deterministic network-free GitHub mutation proposal", + "state": "planned", + "evidence": [], + "next_action": "Define proposal input and output contracts before authentication or writes." + }, + { + "id": "M2-CONTRACT", + "title": "Fake-GitHub contract, conflict, retry, and idempotency tests", + "state": "planned", + "evidence": [], + "next_action": "Test the adapter without live repository mutations." + }, + { + "id": "M2-AUTH", + "title": "Repository-scoped least-privilege authentication", + "state": "planned", + "evidence": [], + "next_action": "Add dedicated fine-grained credentials only after proposal behavior is reviewed." + }, + { + "id": "M2-WRITE-PILOT", + "title": "Explicitly approved live write pilot", + "state": "planned", + "evidence": [], + "next_action": "Require fresh user approval and a clean threat-model review." + } + ], + "summary": { + "state": "planned", + "counts": { + "done": 2, + "in_progress": 0, + "blocked": 0, + "planned": 4, + "deferred": 0 + }, + "total": 6, + "actionable": 6, + "completed": 2, + "blockers": [] + } + }, + { + "id": "M3", + "title": "Research and assessment profiles", + "exit_criteria": [ + "Synthetic high-risk profiles prove blind reproduction, independence, disagreement, supersession, and human authority before real domain use." + ], + "items": [ + { + "id": "M3-REPRODUCTION", + "title": "Independent high-risk reproduction core", + "state": "done", + "evidence": [ + { + "label": "Independent reproduction gates", + "uri": "https://github.com/brendohxd/Project-Relay/commit/786648e" + } + ] + }, + { + "id": "M3-BLIND-PACKET", + "title": "Blind reproduction packet contract", + "state": "planned", + "evidence": [], + "next_action": "Design and test against synthetic claims before any ITSM packet." + }, + { + "id": "M3-DISAGREEMENT", + "title": "Adversarial review and disagreement ledger", + "state": "planned", + "evidence": [], + "next_action": "Preserve incompatible findings without forced consensus." + }, + { + "id": "M3-SUPERSESSION", + "title": "Remediation and supersession graph", + "state": "planned", + "evidence": [], + "next_action": "Build on typed causal links and immutable prior records." + }, + { + "id": "M3-ITSM-PROFILE", + "title": "ITSM operational policy profile", + "state": "planned", + "evidence": [], + "next_action": "Implement only after the synthetic research profile passes." + }, + { + "id": "M3-EDUCATION-PROFILE", + "title": "Synthetic education marking and moderation profile", + "state": "planned", + "evidence": [], + "next_action": "Model rubric assessment, moderation, appeals, and named human authority without real student data." + }, + { + "id": "M3-HIRING-PROFILE", + "title": "Synthetic hiring-assistance profile", + "state": "deferred", + "evidence": [], + "rationale": "Hiring requires dedicated privacy, fairness, discrimination, explainability, and legal threat models; Relay must remain decision support with human authority." + } + ], + "summary": { + "state": "planned", + "counts": { + "done": 1, + "in_progress": 0, + "blocked": 0, + "planned": 5, + "deferred": 1 + }, + "total": 7, + "actionable": 6, + "completed": 1, + "blockers": [] + } + }, + { + "id": "M4", + "title": "Public console and integrations", + "exit_criteria": [ + "Users can inspect project and task status through credential-free projections and supported client guides." + ], + "items": [ + { + "id": "M4-STATUS", + "title": "Canonical project status and generated GitHub view", + "state": "done", + "evidence": [ + { + "label": "Canonical project status", + "uri": "project/status.json" + }, + { + "label": "Generated GitHub status", + "uri": "STATUS.md" + } + ] + }, + { + "id": "M4-CONSOLE", + "title": "Visual milestone, blocker, and pilot dashboard", + "state": "done", + "evidence": [ + { + "label": "Read-only console", + "uri": "apps/console/index.html" + } + ] + }, + { + "id": "M4-PAGES-READY", + "title": "GitHub Pages-ready static bundle", + "state": "done", + "evidence": [ + { + "label": "Pages entry point", + "uri": "docs/index.html" + } + ] + }, + { + "id": "M4-PAGES-PUBLISH", + "title": "Publish the visual console on GitHub Pages", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "external", + "summary": "GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction." + }, + "next_action": "After merge, configure Pages from main /docs and record the resulting public URL or blocker." + }, + { + "id": "M4-CLIENT-GUIDES", + "title": "Codex, Claude, Gemini, and compatible MCP guides", + "state": "planned", + "evidence": [], + "next_action": "Document read-only status and task access before any write integrations." + }, + { + "id": "M4-MANUAL-BRIDGE", + "title": "Manual packet bridge", + "state": "planned", + "evidence": [], + "next_action": "Support clients without GitHub or MCP while preserving the same evidence contract." + }, + { + "id": "M4-NOTIFICATIONS", + "title": "Notifications and human approval inbox", + "state": "planned", + "evidence": [], + "next_action": "Begin only after authenticated M2 write boundaries are proven." + } + ], + "summary": { + "state": "planned", + "counts": { + "done": 3, + "in_progress": 0, + "blocked": 1, + "planned": 3, + "deferred": 0 + }, + "total": 7, + "actionable": 7, + "completed": 3, + "blockers": [ + { + "id": "M4-PAGES-PUBLISH", + "title": "Publish the visual console on GitHub Pages", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "external", + "summary": "GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction." + }, + "next_action": "After merge, configure Pages from main /docs and record the resulting public URL or blocker." + } + ] + } + }, + { + "id": "M5", + "title": "Optional managed service", + "exit_criteria": [ + "Security, funding, licensing, product demand, and service responsibilities are explicitly approved before hosted development." + ], + "items": [ + { + "id": "M5-HOSTED", + "title": "Hosted authentication, secrets, tenancy, and audit service", + "state": "deferred", + "evidence": [], + "rationale": "No hosted service is authorised before security, funding, licensing, and demand gates are resolved." + }, + { + "id": "M5-BILLING", + "title": "Usage controls, billing, support, and service objectives", + "state": "deferred", + "evidence": [], + "rationale": "No paid infrastructure or billing work is required for the current local-first roadmap." + } + ], + "summary": { + "state": "deferred", + "counts": { + "done": 0, + "in_progress": 0, + "blocked": 0, + "planned": 0, + "deferred": 2 + }, + "total": 2, + "actionable": 0, + "completed": 0, + "blockers": [] + } + } + ], + "pilots": [ + { + "id": "itsm", + "name": "ITSM proving ground", + "visibility": "operational-summary-only", + "boundary": "Relay may show workflow readiness and gate progress only. ITSM equations, claims, datasets, results, manuscripts, and scientific acceptance remain in the separate ITSM repository and knowledge base.", + "items": [ + { + "id": "ITSM-BOUNDARY", + "title": "Relay and ITSM separation rule documented", + "state": "done", + "evidence": [ + { + "label": "ITSM pilot operating model", + "uri": "https://app.notion.com/p/3a236fb4bd1d810fb409f87153bc3e84" + } + ] + }, + { + "id": "ITSM-ROLES", + "title": "Operational owner, review, reproduction, audit, and authority roles documented", + "state": "done", + "evidence": [ + { + "label": "ITSM pilot operating model", + "uri": "https://app.notion.com/p/3a236fb4bd1d810fb409f87153bc3e84" + } + ] + }, + { + "id": "ITSM-SYNTHETIC", + "title": "Synthetic research profile passes before ITSM use", + "state": "planned", + "evidence": [], + "next_action": "Complete the M3 synthetic research-gate fixture." + }, + { + "id": "ITSM-REAL-PILOT", + "title": "First real ITSM Relay packet", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "dependency", + "summary": "A real ITSM packet must wait for the synthetic M3 profile and explicit selection of a boundary-safe pilot task." + }, + "next_action": "Select the pilot only after the synthetic profile passes." + } + ], + "summary": { + "state": "planned", + "counts": { + "done": 2, + "in_progress": 0, + "blocked": 1, + "planned": 1, + "deferred": 0 + }, + "total": 4, + "actionable": 4, + "completed": 2, + "blockers": [ + { + "id": "ITSM-REAL-PILOT", + "title": "First real ITSM Relay packet", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "dependency", + "summary": "A real ITSM packet must wait for the synthetic M3 profile and explicit selection of a boundary-safe pilot task." + }, + "next_action": "Select the pilot only after the synthetic profile passes." + } + ] + } + } + ], + "summary": { + "state": "planned", + "counts": { + "done": 12, + "in_progress": 0, + "blocked": 3, + "planned": 13, + "deferred": 3 + }, + "total": 31, + "actionable": 28, + "completed": 12, + "blockers": [ + { + "id": "M1-HUMAN-REVIEW", + "title": "Human review of draft PR #1", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "human-action", + "summary": "Draft PR #1 has no recorded human review decision." + }, + "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." + }, + { + "id": "M1-LINUX-VERIFY", + "title": "Linux conformance verification", + "state": "blocked", + "evidence": [ + { + "label": "Blocked GitHub Actions run", + "uri": "https://github.com/brendohxd/Project-Relay/actions" + } + ], + "blocker": { + "kind": "external", + "summary": "GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists." + }, + "next_action": "Rerun the existing matrix when GitHub-hosted runners become available." + }, + { + "id": "M4-PAGES-PUBLISH", + "title": "Publish the visual console on GitHub Pages", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "external", + "summary": "GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction." + }, + "next_action": "After merge, configure Pages from main /docs and record the resulting public URL or blocker." + } + ], + "current_milestone": "M1", + "current_milestone_title": "Local vertical slice" + } + }, "summary": { "tasks": 1, "active": 0, diff --git a/apps/console/styles.css b/apps/console/styles.css index 9520fcc..782a7a2 100644 --- a/apps/console/styles.css +++ b/apps/console/styles.css @@ -106,3 +106,39 @@ footer { border-top: 1px solid var(--line); color: var(--muted); display: flex; .flow li { min-height: 150px; } .flow li > span { margin-top: 36px; } } + +.project-status { padding: 88px 0 24px; } +.pilot-section { padding: 72px 0 24px; } +.project-metrics { border: 1px solid var(--line); display: grid; grid-template-columns: repeat(5, 1fr); margin-bottom: 22px; } +.project-metrics article { border-right: 1px solid var(--line); padding: 18px; } +.project-metrics article:last-child { border-right: 0; } +.project-metrics span, .item-state { color: var(--muted); display: block; font-size: 10px; letter-spacing: .08em; text-transform: uppercase; } +.project-metrics strong { display: block; font-size: 24px; margin-top: 7px; } +.milestone-grid { display: grid; gap: 12px; grid-template-columns: repeat(2, 1fr); } +.milestone-card, .pilot-card { background: var(--panel); border: 1px solid var(--line); padding: 22px; } +.milestone-complete { border-top-color: rgba(185, 247, 124, .55); } +.milestone-blocked { border-top-color: rgba(255, 107, 53, .65); } +.milestone-heading, .pilot-heading { align-items: start; display: flex; gap: 16px; justify-content: space-between; } +.milestone-id { color: var(--orange); font-family: ui-monospace, monospace; font-size: 11px; } +h3 { font-size: 19px; margin: 5px 0 0; } +.milestone-progress, .pilot-progress { color: var(--muted); font-size: 12px; } +.badge-complete { border-color: rgba(185, 247, 124, .4); color: var(--lime); } +.badge-blocked { border-color: rgba(255, 107, 53, .5); color: var(--orange); } +.badge-deferred { color: var(--muted); } +.badge-boundary { border-color: rgba(121, 168, 255, .4); color: var(--blue); } +.roadmap-items { list-style: none; margin: 20px 0 0; padding: 0; } +.roadmap-item { align-items: start; border-top: 1px solid var(--line); display: grid; gap: 10px; grid-template-columns: 18px 1fr; padding: 12px 0; } +.roadmap-item strong { display: block; font-size: 12px; } +.item-marker { color: var(--muted); font-family: ui-monospace, monospace; } +.item-done .item-marker { color: var(--lime); } +.item-blocked .item-marker { color: var(--orange); } +.item-state { margin-top: 4px; } +.item-note { color: var(--muted); display: block; font-size: 11px; line-height: 1.4; margin-top: 6px; } +.pilot-grid { display: grid; gap: 12px; } +.pilot-boundary { border-left: 2px solid var(--blue); color: var(--muted); font-size: 13px; line-height: 1.55; padding-left: 14px; } + +@media (max-width: 800px) { + .project-metrics { grid-template-columns: repeat(2, 1fr); } + .project-metrics article { border-bottom: 1px solid var(--line); } + .milestone-grid { grid-template-columns: 1fr; } + .milestone-heading, .pilot-heading { align-items: start; flex-direction: column; } diff --git a/apps/mcp-server/src/index.js b/apps/mcp-server/src/index.js index 81654c7..f861884 100644 --- a/apps/mcp-server/src/index.js +++ b/apps/mcp-server/src/index.js @@ -13,10 +13,13 @@ import { validateWorkspace } from "@project-relay/protocol"; import { z } from "zod"; +import { loadProjectStatus } from "../../../scripts/project-status-lib.mjs"; const VERSION = "0.1.0"; const workspace = path.resolve(process.env.RELAY_WORKSPACE ?? process.cwd()); const taskIdPattern = /^[A-Z][A-Z0-9_-]{1,31}-[0-9]{4,}$/; +const projectStatusFile = path.resolve(process.env.RELAY_PROJECT_STATUS ?? "project/status.json"); +const projectStatusSchema = path.resolve(process.env.RELAY_PROJECT_STATUS_SCHEMA ?? "project/status.schema.json"); const eventTypes = [ "task.created", "task.ready", @@ -241,6 +244,44 @@ server.registerTool( return result({ event, validation }, !validation.valid); } ); +server.registerTool( + "relay_get_project_status", + { + title: "Get Project Relay status", + description: "Read the validated roadmap, blockers, and operational-only pilot summaries.", + inputSchema: {} + }, + async () => { + try { + return result(await loadProjectStatus(projectStatusFile, projectStatusSchema)); + } catch (error) { + return result({ error: error.message, issues: error.issues ?? [] }, true); + } + } +); + +server.registerResource( + "relay-project-status", + "relay://project/status", + { + title: "Project Relay status", + description: "Validated project roadmap and operational-only proving-ground status.", + mimeType: "application/json" + }, + async (uri) => { + const status = await loadProjectStatus(projectStatusFile, projectStatusSchema); + return { + contents: [ + { + uri: uri.href, + mimeType: "application/json", + text: JSON.stringify(status, null, 2) + } + ] + }; + } +); + const transport = new StdioServerTransport(); await server.connect(transport); diff --git a/docs/.nojekyll b/docs/.nojekyll new file mode 100644 index 0000000..e69de29 diff --git a/docs/app.js b/docs/app.js new file mode 100644 index 0000000..6456080 --- /dev/null +++ b/docs/app.js @@ -0,0 +1,137 @@ +const elements = { + projectStage: document.querySelector("#project-stage"), + currentMilestone: document.querySelector("#current-milestone"), + roadmapComplete: document.querySelector("#roadmap-complete"), + roadmapBlocked: document.querySelector("#roadmap-blocked"), + roadmapDeferred: document.querySelector("#roadmap-deferred"), + milestoneRows: document.querySelector("#milestone-rows"), + pilotRows: document.querySelector("#pilot-rows"), + projectUpdated: document.querySelector("#project-updated"), + tasks: document.querySelector("#metric-tasks"), + active: document.querySelector("#metric-active"), + evidence: document.querySelector("#metric-evidence"), + reviews: document.querySelector("#metric-reviews"), + decisions: document.querySelector("#metric-decisions"), + rows: document.querySelector("#task-rows"), + source: document.querySelector("#source-note"), + error: document.querySelector("#load-error"), + version: document.querySelector("#protocol-version") +}; + +function cell(className, text) { + const node = document.createElement("span"); + if (className) node.className = className; + node.textContent = text; + return node; +} + +function stateLabel(state) { + return state.replaceAll("_", " "); +} + +function renderMilestone(milestone) { + const card = document.createElement("article"); + card.className = `milestone-card milestone-${milestone.summary.state}`; + const heading = document.createElement("div"); + heading.className = "milestone-heading"; + const name = document.createElement("div"); + const id = cell("milestone-id", milestone.id); + const title = document.createElement("h3"); + title.textContent = milestone.title; + name.append(id, title); + heading.append(name, cell(`badge badge-${milestone.summary.state}`, stateLabel(milestone.summary.state))); + const progress = document.createElement("p"); + progress.className = "milestone-progress"; + progress.textContent = `${milestone.summary.completed}/${milestone.summary.actionable} actionable items complete`; + const list = document.createElement("ul"); + list.className = "roadmap-items"; + for (const item of milestone.items) { + const row = document.createElement("li"); + row.className = `roadmap-item item-${item.state}`; + row.append(cell("item-marker", item.state === "done" ? "✓" : item.state === "blocked" ? "!" : "·")); + const detail = document.createElement("span"); + const itemTitle = document.createElement("strong"); + itemTitle.textContent = item.title; + detail.append(itemTitle, cell("item-state", stateLabel(item.state))); + if (item.blocker) detail.append(cell("item-note", item.blocker.summary)); + else if (item.next_action && item.state !== "done") detail.append(cell("item-note", item.next_action)); + row.append(detail); + list.append(row); + } + card.append(heading, progress, list); + return card; +} + +function renderPilot(pilot) { + const card = document.createElement("article"); + card.className = "pilot-card"; + const heading = document.createElement("div"); + heading.className = "pilot-heading"; + const title = document.createElement("h3"); + title.textContent = pilot.name; + heading.append(title, cell("badge badge-boundary", pilot.visibility)); + const boundary = document.createElement("p"); + boundary.className = "pilot-boundary"; + boundary.textContent = pilot.boundary; + const progress = document.createElement("p"); + progress.className = "pilot-progress"; + progress.textContent = `${pilot.summary.completed}/${pilot.summary.actionable} operational gates complete · ${pilot.summary.counts.blocked} blocked`; + card.append(heading, boundary, progress); + return card; +} + +function renderTask(task) { + const row = document.createElement("article"); + row.className = "task-row"; + row.setAttribute("role", "row"); + const title = document.createElement("span"); + title.className = "task-title"; + const strong = document.createElement("strong"); + strong.textContent = task.title; + const small = document.createElement("small"); + small.textContent = `${task.id} · ${task.history.length} transitions · policy ${task.policy_valid ? "valid" : "failed"}`; + title.append(strong, small); + row.append( + title, + cell(`badge badge-state ${task.policy_valid ? "" : "badge-policy-failed"}`, stateLabel(task.derived_state)), + cell(`badge badge-risk-${task.risk}`, task.risk), + cell("", String(task.evidence_count)), + cell("", String(task.review_count)) + ); + const gates = document.createElement("div"); + gates.className = "task-gates"; + for (const [name, satisfied] of Object.entries(task.gates)) { + gates.append(cell(`gate ${satisfied ? "gate-pass" : "gate-fail"}`, `${satisfied ? "✓" : "×"} ${stateLabel(name)}`)); + } + row.append(gates); + return row; +} + +try { + const response = await fetch("./state/index.json", { cache: "no-store" }); + if (!response.ok) throw new Error(`State request failed with ${response.status}`); + const state = await response.json(); + const project = state.project; + + elements.projectStage.textContent = project.project.stage; + elements.currentMilestone.textContent = project.summary.current_milestone ?? "complete"; + elements.roadmapComplete.textContent = `${project.summary.completed}/${project.summary.actionable}`; + elements.roadmapBlocked.textContent = project.summary.counts.blocked; + elements.roadmapDeferred.textContent = project.summary.counts.deferred; + elements.projectUpdated.textContent = `updated · ${project.updated_at}`; + elements.milestoneRows.replaceChildren(...project.milestones.map(renderMilestone)); + elements.pilotRows.replaceChildren(...project.pilots.map(renderPilot)); + + elements.tasks.textContent = state.summary.tasks; + elements.active.textContent = state.summary.active; + elements.evidence.textContent = state.summary.evidence_bundles; + elements.reviews.textContent = state.summary.reviews; + elements.decisions.textContent = state.summary.decisions; + elements.source.textContent = `source · ${state.generated_from}`; + elements.version.textContent = `Protocol ${state.protocol_version}`; + elements.rows.replaceChildren(...state.tasks.map(renderTask)); +} catch (error) { + elements.source.textContent = "state unavailable"; + elements.error.hidden = false; + elements.error.textContent = `${error.message}. Serve this directory over HTTP rather than opening index.html directly.`; +} diff --git a/docs/index.html b/docs/index.html new file mode 100644 index 0000000..a79b4fb --- /dev/null +++ b/docs/index.html @@ -0,0 +1,122 @@ + + + + + + + Project Relay + + + +
+
+ + + Project Relay + +
Protocol pre-alpha
+
+ +
+
+
Evidence before consensus
+

One record for work
across many minds.

+

+ Relay turns disconnected AI sessions and human reviews into bounded tasks, + reproducible evidence, explicit disagreements, and accountable decisions. +

+ +
+ +
+
Tasks
+
Active
+
Evidence
+
+
+
+
Project control plane
+

Roadmap and blockers

+
+
Loading status?
+
+
+
Stage?
+
Current?
+
Complete?
+
Blocked?
+
Deferred?
+
+
+
+ +
+
+
+
Boundary-safe proving grounds
+

Pilot readiness

+
+
+
+
+
Reviews
+
Decisions
+
+ +
+
+
+
Canonical projection
+

Current tasks

+
+
Loading state…
+
+ +
+
+ Task + State + Risk + Evidence + Reviews +
+
+
+ +
+ +
+
+
+
Default evidence gate
+

From question to decision

+
+
+
    +
  1. 01Bound the taskOne question, inputs, constraints, and acceptance criteria.
  2. +
  3. 02Submit evidenceMethods, commands, environment, artifacts, failures, and hashes.
  4. +
  5. 03Review independentlyReproduce where required and preserve material disagreement.
  6. +
  7. 04Record authorityA named human accepts, remediates, rejects, or defers.
  8. +
+
+ + +
+ +
+ Project Relay · working title + Protocol — +
+
+ + + diff --git a/docs/state/index.json b/docs/state/index.json new file mode 100644 index 0000000..5628241 --- /dev/null +++ b/docs/state/index.json @@ -0,0 +1,729 @@ +{ + "protocol_version": "0.1.0", + "generated_from": "examples/m1", + "project": { + "schema_version": "0.1.0", + "project": { + "id": "project-relay", + "name": "Project Relay", + "stage": "pre-alpha", + "repository": "https://github.com/brendohxd/Project-Relay" + }, + "updated_at": "2026-07-19", + "milestones": [ + { + "id": "M0", + "title": "Protocol kernel", + "exit_criteria": [ + "The provider-neutral protocol kernel, local read-only MCP surface, fixtures, and public boundary are implemented." + ], + "items": [ + { + "id": "M0-PROTOCOL", + "title": "Canonical schemas, hashes, and event-chain verification", + "state": "done", + "evidence": [ + { + "label": "Foundation commit", + "uri": "https://github.com/brendohxd/Project-Relay/commit/99f8c9c" + } + ] + }, + { + "id": "M0-MCP", + "title": "Read-only local MCP server", + "state": "done", + "evidence": [ + { + "label": "M0 implementation", + "uri": "https://github.com/brendohxd/Project-Relay/commit/99f8c9c" + } + ] + }, + { + "id": "M0-PUBLIC", + "title": "Public-boundary scanner and repository checks", + "state": "done", + "evidence": [ + { + "label": "M0 implementation", + "uri": "https://github.com/brendohxd/Project-Relay/commit/99f8c9c" + } + ] + } + ], + "summary": { + "state": "complete", + "counts": { + "done": 3, + "in_progress": 0, + "blocked": 0, + "planned": 0, + "deferred": 0 + }, + "total": 3, + "actionable": 3, + "completed": 3, + "blockers": [] + } + }, + { + "id": "M1", + "title": "Local vertical slice", + "exit_criteria": [ + "The complete local remediation loop passes Windows and Linux verification and receives human review." + ], + "items": [ + { + "id": "M1-LIFECYCLE", + "title": "Policy transition engine and remediation fixture", + "state": "done", + "evidence": [ + { + "label": "M1 implementation commit", + "uri": "https://github.com/brendohxd/Project-Relay/commit/d899218" + } + ] + }, + { + "id": "M1-EVIDENCE", + "title": "Evidence CLI, independent review, reproduction, and human gates", + "state": "done", + "evidence": [ + { + "label": "M1 review fixes", + "uri": "https://github.com/brendohxd/Project-Relay/commit/786648e" + } + ] + }, + { + "id": "M1-LOCAL-VERIFY", + "title": "Windows local verification with 18 tests", + "state": "done", + "evidence": [ + { + "label": "Draft PR verification record", + "uri": "https://github.com/brendohxd/Project-Relay/pull/1#issuecomment-5014591903" + } + ] + }, + { + "id": "M1-HUMAN-REVIEW", + "title": "Human review of draft PR #1", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "human-action", + "summary": "Draft PR #1 has no recorded human review decision." + }, + "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." + }, + { + "id": "M1-LINUX-VERIFY", + "title": "Linux conformance verification", + "state": "blocked", + "evidence": [ + { + "label": "Blocked GitHub Actions run", + "uri": "https://github.com/brendohxd/Project-Relay/actions" + } + ], + "blocker": { + "kind": "external", + "summary": "GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists." + }, + "next_action": "Rerun the existing matrix when GitHub-hosted runners become available." + }, + { + "id": "M1-MERGE", + "title": "Merge the reviewed M1 candidate", + "state": "planned", + "evidence": [], + "next_action": "Merge only after human review and Linux verification both pass." + } + ], + "summary": { + "state": "planned", + "counts": { + "done": 3, + "in_progress": 0, + "blocked": 2, + "planned": 1, + "deferred": 0 + }, + "total": 6, + "actionable": 6, + "completed": 3, + "blockers": [ + { + "id": "M1-HUMAN-REVIEW", + "title": "Human review of draft PR #1", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "human-action", + "summary": "Draft PR #1 has no recorded human review decision." + }, + "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." + }, + { + "id": "M1-LINUX-VERIFY", + "title": "Linux conformance verification", + "state": "blocked", + "evidence": [ + { + "label": "Blocked GitHub Actions run", + "uri": "https://github.com/brendohxd/Project-Relay/actions" + } + ], + "blocker": { + "kind": "external", + "summary": "GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists." + }, + "next_action": "Rerun the existing matrix when GitHub-hosted runners become available." + } + ] + } + }, + { + "id": "M2", + "title": "GitHub collaboration adapter", + "exit_criteria": [ + "An approved Relay proposal can be translated into an idempotent, least-privilege GitHub change with conflict and failure records." + ], + "items": [ + { + "id": "M2-CAPABILITIES", + "title": "Declared actor capabilities", + "state": "done", + "evidence": [ + { + "label": "Capability foundation", + "uri": "https://github.com/brendohxd/Project-Relay/commit/11dc90a" + } + ] + }, + { + "id": "M2-CAUSAL-LINKS", + "title": "Typed causal provenance links", + "state": "done", + "evidence": [ + { + "label": "Causal-link foundation", + "uri": "https://github.com/brendohxd/Project-Relay/commit/11dc90a" + } + ] + }, + { + "id": "M2-PROPOSAL", + "title": "Deterministic network-free GitHub mutation proposal", + "state": "planned", + "evidence": [], + "next_action": "Define proposal input and output contracts before authentication or writes." + }, + { + "id": "M2-CONTRACT", + "title": "Fake-GitHub contract, conflict, retry, and idempotency tests", + "state": "planned", + "evidence": [], + "next_action": "Test the adapter without live repository mutations." + }, + { + "id": "M2-AUTH", + "title": "Repository-scoped least-privilege authentication", + "state": "planned", + "evidence": [], + "next_action": "Add dedicated fine-grained credentials only after proposal behavior is reviewed." + }, + { + "id": "M2-WRITE-PILOT", + "title": "Explicitly approved live write pilot", + "state": "planned", + "evidence": [], + "next_action": "Require fresh user approval and a clean threat-model review." + } + ], + "summary": { + "state": "planned", + "counts": { + "done": 2, + "in_progress": 0, + "blocked": 0, + "planned": 4, + "deferred": 0 + }, + "total": 6, + "actionable": 6, + "completed": 2, + "blockers": [] + } + }, + { + "id": "M3", + "title": "Research and assessment profiles", + "exit_criteria": [ + "Synthetic high-risk profiles prove blind reproduction, independence, disagreement, supersession, and human authority before real domain use." + ], + "items": [ + { + "id": "M3-REPRODUCTION", + "title": "Independent high-risk reproduction core", + "state": "done", + "evidence": [ + { + "label": "Independent reproduction gates", + "uri": "https://github.com/brendohxd/Project-Relay/commit/786648e" + } + ] + }, + { + "id": "M3-BLIND-PACKET", + "title": "Blind reproduction packet contract", + "state": "planned", + "evidence": [], + "next_action": "Design and test against synthetic claims before any ITSM packet." + }, + { + "id": "M3-DISAGREEMENT", + "title": "Adversarial review and disagreement ledger", + "state": "planned", + "evidence": [], + "next_action": "Preserve incompatible findings without forced consensus." + }, + { + "id": "M3-SUPERSESSION", + "title": "Remediation and supersession graph", + "state": "planned", + "evidence": [], + "next_action": "Build on typed causal links and immutable prior records." + }, + { + "id": "M3-ITSM-PROFILE", + "title": "ITSM operational policy profile", + "state": "planned", + "evidence": [], + "next_action": "Implement only after the synthetic research profile passes." + }, + { + "id": "M3-EDUCATION-PROFILE", + "title": "Synthetic education marking and moderation profile", + "state": "planned", + "evidence": [], + "next_action": "Model rubric assessment, moderation, appeals, and named human authority without real student data." + }, + { + "id": "M3-HIRING-PROFILE", + "title": "Synthetic hiring-assistance profile", + "state": "deferred", + "evidence": [], + "rationale": "Hiring requires dedicated privacy, fairness, discrimination, explainability, and legal threat models; Relay must remain decision support with human authority." + } + ], + "summary": { + "state": "planned", + "counts": { + "done": 1, + "in_progress": 0, + "blocked": 0, + "planned": 5, + "deferred": 1 + }, + "total": 7, + "actionable": 6, + "completed": 1, + "blockers": [] + } + }, + { + "id": "M4", + "title": "Public console and integrations", + "exit_criteria": [ + "Users can inspect project and task status through credential-free projections and supported client guides." + ], + "items": [ + { + "id": "M4-STATUS", + "title": "Canonical project status and generated GitHub view", + "state": "done", + "evidence": [ + { + "label": "Canonical project status", + "uri": "project/status.json" + }, + { + "label": "Generated GitHub status", + "uri": "STATUS.md" + } + ] + }, + { + "id": "M4-CONSOLE", + "title": "Visual milestone, blocker, and pilot dashboard", + "state": "done", + "evidence": [ + { + "label": "Read-only console", + "uri": "apps/console/index.html" + } + ] + }, + { + "id": "M4-PAGES-READY", + "title": "GitHub Pages-ready static bundle", + "state": "done", + "evidence": [ + { + "label": "Pages entry point", + "uri": "docs/index.html" + } + ] + }, + { + "id": "M4-PAGES-PUBLISH", + "title": "Publish the visual console on GitHub Pages", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "external", + "summary": "GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction." + }, + "next_action": "After merge, configure Pages from main /docs and record the resulting public URL or blocker." + }, + { + "id": "M4-CLIENT-GUIDES", + "title": "Codex, Claude, Gemini, and compatible MCP guides", + "state": "planned", + "evidence": [], + "next_action": "Document read-only status and task access before any write integrations." + }, + { + "id": "M4-MANUAL-BRIDGE", + "title": "Manual packet bridge", + "state": "planned", + "evidence": [], + "next_action": "Support clients without GitHub or MCP while preserving the same evidence contract." + }, + { + "id": "M4-NOTIFICATIONS", + "title": "Notifications and human approval inbox", + "state": "planned", + "evidence": [], + "next_action": "Begin only after authenticated M2 write boundaries are proven." + } + ], + "summary": { + "state": "planned", + "counts": { + "done": 3, + "in_progress": 0, + "blocked": 1, + "planned": 3, + "deferred": 0 + }, + "total": 7, + "actionable": 7, + "completed": 3, + "blockers": [ + { + "id": "M4-PAGES-PUBLISH", + "title": "Publish the visual console on GitHub Pages", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "external", + "summary": "GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction." + }, + "next_action": "After merge, configure Pages from main /docs and record the resulting public URL or blocker." + } + ] + } + }, + { + "id": "M5", + "title": "Optional managed service", + "exit_criteria": [ + "Security, funding, licensing, product demand, and service responsibilities are explicitly approved before hosted development." + ], + "items": [ + { + "id": "M5-HOSTED", + "title": "Hosted authentication, secrets, tenancy, and audit service", + "state": "deferred", + "evidence": [], + "rationale": "No hosted service is authorised before security, funding, licensing, and demand gates are resolved." + }, + { + "id": "M5-BILLING", + "title": "Usage controls, billing, support, and service objectives", + "state": "deferred", + "evidence": [], + "rationale": "No paid infrastructure or billing work is required for the current local-first roadmap." + } + ], + "summary": { + "state": "deferred", + "counts": { + "done": 0, + "in_progress": 0, + "blocked": 0, + "planned": 0, + "deferred": 2 + }, + "total": 2, + "actionable": 0, + "completed": 0, + "blockers": [] + } + } + ], + "pilots": [ + { + "id": "itsm", + "name": "ITSM proving ground", + "visibility": "operational-summary-only", + "boundary": "Relay may show workflow readiness and gate progress only. ITSM equations, claims, datasets, results, manuscripts, and scientific acceptance remain in the separate ITSM repository and knowledge base.", + "items": [ + { + "id": "ITSM-BOUNDARY", + "title": "Relay and ITSM separation rule documented", + "state": "done", + "evidence": [ + { + "label": "ITSM pilot operating model", + "uri": "https://app.notion.com/p/3a236fb4bd1d810fb409f87153bc3e84" + } + ] + }, + { + "id": "ITSM-ROLES", + "title": "Operational owner, review, reproduction, audit, and authority roles documented", + "state": "done", + "evidence": [ + { + "label": "ITSM pilot operating model", + "uri": "https://app.notion.com/p/3a236fb4bd1d810fb409f87153bc3e84" + } + ] + }, + { + "id": "ITSM-SYNTHETIC", + "title": "Synthetic research profile passes before ITSM use", + "state": "planned", + "evidence": [], + "next_action": "Complete the M3 synthetic research-gate fixture." + }, + { + "id": "ITSM-REAL-PILOT", + "title": "First real ITSM Relay packet", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "dependency", + "summary": "A real ITSM packet must wait for the synthetic M3 profile and explicit selection of a boundary-safe pilot task." + }, + "next_action": "Select the pilot only after the synthetic profile passes." + } + ], + "summary": { + "state": "planned", + "counts": { + "done": 2, + "in_progress": 0, + "blocked": 1, + "planned": 1, + "deferred": 0 + }, + "total": 4, + "actionable": 4, + "completed": 2, + "blockers": [ + { + "id": "ITSM-REAL-PILOT", + "title": "First real ITSM Relay packet", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "dependency", + "summary": "A real ITSM packet must wait for the synthetic M3 profile and explicit selection of a boundary-safe pilot task." + }, + "next_action": "Select the pilot only after the synthetic profile passes." + } + ] + } + } + ], + "summary": { + "state": "planned", + "counts": { + "done": 12, + "in_progress": 0, + "blocked": 3, + "planned": 13, + "deferred": 3 + }, + "total": 31, + "actionable": 28, + "completed": 12, + "blockers": [ + { + "id": "M1-HUMAN-REVIEW", + "title": "Human review of draft PR #1", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "human-action", + "summary": "Draft PR #1 has no recorded human review decision." + }, + "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." + }, + { + "id": "M1-LINUX-VERIFY", + "title": "Linux conformance verification", + "state": "blocked", + "evidence": [ + { + "label": "Blocked GitHub Actions run", + "uri": "https://github.com/brendohxd/Project-Relay/actions" + } + ], + "blocker": { + "kind": "external", + "summary": "GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists." + }, + "next_action": "Rerun the existing matrix when GitHub-hosted runners become available." + }, + { + "id": "M4-PAGES-PUBLISH", + "title": "Publish the visual console on GitHub Pages", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "external", + "summary": "GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction." + }, + "next_action": "After merge, configure Pages from main /docs and record the resulting public URL or blocker." + } + ], + "current_milestone": "M1", + "current_milestone_title": "Local vertical slice" + } + }, + "summary": { + "tasks": 1, + "active": 0, + "evidence_bundles": 2, + "reviews": 2, + "decisions": 1, + "failed_gates": 0 + }, + "tasks": [ + { + "id": "RELAY-1001", + "title": "Exercise the default remediation policy", + "question": "Can Relay preserve a failed independent review, require remediation, and record human-authorised acceptance?", + "state": "accepted", + "derived_state": "accepted", + "policy_valid": true, + "gates": { + "evidence_present": true, + "independent_review": true, + "independent_reproduction": true, + "remediation_recorded": true, + "human_decision": true, + "final_state_consistent": true + }, + "history": [ + { + "sequence": 1, + "event_id": "EVT-m1event0001", + "type": "task.created", + "from_state": null, + "to_state": "draft", + "occurred_at": "2026-07-19T01:00:00Z" + }, + { + "sequence": 2, + "event_id": "EVT-m1event0002", + "type": "task.ready", + "from_state": "draft", + "to_state": "ready", + "occurred_at": "2026-07-19T01:01:00Z" + }, + { + "sequence": 3, + "event_id": "EVT-m1event0003", + "type": "task.claimed", + "from_state": "ready", + "to_state": "claimed", + "occurred_at": "2026-07-19T01:02:00Z" + }, + { + "sequence": 4, + "event_id": "EVT-m1event0004", + "type": "task.started", + "from_state": "claimed", + "to_state": "in_progress", + "occurred_at": "2026-07-19T01:03:00Z" + }, + { + "sequence": 5, + "event_id": "EVT-m1event0005", + "type": "task.submitted", + "from_state": "in_progress", + "to_state": "submitted", + "occurred_at": "2026-07-19T01:05:00Z" + }, + { + "sequence": 6, + "event_id": "EVT-m1event0006", + "type": "review.requested", + "from_state": "submitted", + "to_state": "under_review", + "occurred_at": "2026-07-19T01:06:00Z" + }, + { + "sequence": 8, + "event_id": "EVT-m1event0008", + "type": "remediation.requested", + "from_state": "under_review", + "to_state": "remediation", + "occurred_at": "2026-07-19T01:08:00Z" + }, + { + "sequence": 9, + "event_id": "EVT-m1event0009", + "type": "task.submitted", + "from_state": "remediation", + "to_state": "submitted", + "occurred_at": "2026-07-19T01:10:00Z" + }, + { + "sequence": 10, + "event_id": "EVT-m1event0010", + "type": "review.requested", + "from_state": "submitted", + "to_state": "under_review", + "occurred_at": "2026-07-19T01:11:00Z" + }, + { + "sequence": 12, + "event_id": "EVT-m1event0012", + "type": "decision.recorded", + "from_state": "under_review", + "to_state": "accepted", + "occurred_at": "2026-07-19T01:13:00Z" + } + ], + "risk": "moderate", + "owner": { + "id": "model:builder", + "type": "model", + "role": "implementation" + }, + "reviewer_count": 1, + "evidence_count": 2, + "review_count": 2, + "decision_count": 1 + } + ] +} diff --git a/docs/styles.css b/docs/styles.css new file mode 100644 index 0000000..782a7a2 --- /dev/null +++ b/docs/styles.css @@ -0,0 +1,144 @@ +:root { + color-scheme: dark; + --ink: #f3f1eb; + --muted: #a7a49a; + --line: rgba(255, 255, 255, 0.12); + --panel: rgba(255, 255, 255, 0.035); + --orange: #ff6b35; + --lime: #b9f77c; + --blue: #79a8ff; + --page: #0b0d0e; +} + +* { box-sizing: border-box; } +html { scroll-behavior: smooth; } +body { + margin: 0; + background: + radial-gradient(circle at 83% 4%, rgba(121, 168, 255, 0.12), transparent 25rem), + radial-gradient(circle at 15% 18%, rgba(255, 107, 53, 0.08), transparent 28rem), + var(--page); + color: var(--ink); + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; + min-height: 100vh; +} + +a { color: inherit; } +.page-shell { width: min(1180px, calc(100% - 40px)); margin: 0 auto; } +.topbar { + align-items: center; + border-bottom: 1px solid var(--line); + display: flex; + justify-content: space-between; + padding: 24px 0; +} +.brand { align-items: center; display: flex; font-size: 15px; font-weight: 700; gap: 12px; text-decoration: none; } +.brand-mark { display: flex; gap: 3px; height: 22px; transform: skewX(-14deg); } +.brand-mark i { background: var(--orange); display: block; width: 5px; } +.brand-mark i:nth-child(2) { background: var(--lime); } +.brand-mark i:nth-child(3) { background: var(--blue); } +.status-pill { + align-items: center; + border: 1px solid var(--line); + border-radius: 999px; + color: var(--muted); + display: flex; + font-size: 12px; + gap: 8px; + padding: 8px 12px; +} +.status-pill span { background: var(--lime); border-radius: 50%; box-shadow: 0 0 12px var(--lime); height: 7px; width: 7px; } +.hero { padding: 108px 0 88px; } +.eyebrow { color: var(--orange); font-family: ui-monospace, monospace; font-size: 11px; letter-spacing: .16em; text-transform: uppercase; } +h1 { font-size: clamp(48px, 8.3vw, 104px); letter-spacing: -.065em; line-height: .9; margin: 23px 0 30px; max-width: 1000px; } +.hero p { color: var(--muted); font-size: clamp(17px, 2vw, 21px); line-height: 1.55; margin: 0; max-width: 750px; } +.hero-actions { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 38px; } +.hero-actions a { border-radius: 3px; font-size: 13px; font-weight: 700; padding: 13px 18px; text-decoration: none; } +.primary-action { background: var(--ink); color: #111; } +.secondary-action { border: 1px solid var(--line); } +.metrics { border-bottom: 1px solid var(--line); border-top: 1px solid var(--line); display: grid; grid-template-columns: repeat(5, 1fr); } +.metrics article { border-right: 1px solid var(--line); padding: 24px 18px; } +.metrics article:first-child { padding-left: 0; } +.metrics article:last-child { border-right: 0; } +.metrics span { color: var(--muted); display: block; font-size: 11px; letter-spacing: .08em; text-transform: uppercase; } +.metrics strong { display: block; font-size: 31px; margin-top: 8px; } +.workspace, .flow-section { padding: 90px 0 16px; } +.section-heading { align-items: end; display: flex; justify-content: space-between; margin-bottom: 28px; } +h2 { font-size: clamp(28px, 4vw, 44px); letter-spacing: -.035em; margin: 8px 0 0; } +.source-note { color: var(--muted); font-family: ui-monospace, monospace; font-size: 11px; } +.task-table { border-bottom: 1px solid var(--line); border-top: 1px solid var(--line); } +.task-row { align-items: center; display: grid; gap: 16px; grid-template-columns: minmax(280px, 1fr) 120px 90px 80px 80px; min-height: 72px; padding: 0 14px; } +.task-row + .task-row { border-top: 1px solid var(--line); } +.task-header { color: var(--muted); font-size: 10px; letter-spacing: .12em; min-height: 42px; text-transform: uppercase; } +.task-title strong, .task-title small { display: block; } +.task-title strong { font-size: 14px; } +.task-title small { color: var(--muted); font-family: ui-monospace, monospace; margin-top: 5px; } +.badge { border: 1px solid var(--line); border-radius: 999px; display: inline-flex; font-family: ui-monospace, monospace; font-size: 10px; padding: 6px 9px; text-transform: uppercase; width: fit-content; } +.badge-state { border-color: rgba(121, 168, 255, .4); color: var(--blue); } +.badge-policy-failed { border-color: rgba(255, 107, 53, .5); color: var(--orange); } +.badge-risk-high, .badge-risk-critical { border-color: rgba(255, 107, 53, .5); color: var(--orange); } +.task-gates { display: flex; flex-wrap: wrap; gap: 7px; grid-column: 1 / -1; padding-bottom: 14px; } +.gate { border: 1px solid var(--line); border-radius: 999px; font-family: ui-monospace, monospace; font-size: 9px; letter-spacing: .04em; padding: 5px 8px; text-transform: uppercase; } +.gate-pass { border-color: rgba(185, 247, 124, .32); color: var(--lime); } +.gate-fail { border-color: rgba(255, 107, 53, .5); color: var(--orange); } +.load-error { background: rgba(255, 107, 53, .09); border: 1px solid rgba(255, 107, 53, .35); color: #ffc2ac; padding: 16px; } +.flow { counter-reset: relay; display: grid; gap: 1px; grid-template-columns: repeat(4, 1fr); list-style: none; margin: 0; padding: 0; } +.flow li { background: var(--panel); min-height: 210px; padding: 22px; } +.flow b { color: var(--orange); font-family: ui-monospace, monospace; font-size: 11px; } +.flow li > span { color: var(--muted); display: block; font-size: 13px; line-height: 1.55; margin-top: 72px; } +.flow strong { color: var(--ink); display: block; font-size: 15px; margin-bottom: 7px; } +.principle { align-items: start; border-left: 3px solid var(--lime); display: grid; gap: 25px; grid-template-columns: 60px 1fr; margin: 90px 0; padding: 4px 0 4px 24px; } +.principle-index { color: var(--lime); font-family: ui-monospace, monospace; font-size: 11px; } +.principle p { font-size: clamp(22px, 4vw, 40px); letter-spacing: -.035em; line-height: 1.13; margin: 0; max-width: 900px; } +footer { border-top: 1px solid var(--line); color: var(--muted); display: flex; font-size: 11px; justify-content: space-between; padding: 26px 0 42px; } + +@media (max-width: 800px) { + .page-shell { width: min(100% - 28px, 1180px); } + .hero { padding: 80px 0 64px; } + .metrics { grid-template-columns: repeat(2, 1fr); } + .metrics article { border-bottom: 1px solid var(--line); } + .metrics article:nth-child(2n) { border-right: 0; } + .metrics article:first-child { padding-left: 18px; } + .task-header { display: none; } + .task-row { gap: 12px; grid-template-columns: 1fr 1fr; padding: 18px 6px; } + .task-title { grid-column: 1 / -1; } + .flow { grid-template-columns: 1fr; } + .flow li { min-height: 150px; } + .flow li > span { margin-top: 36px; } +} + +.project-status { padding: 88px 0 24px; } +.pilot-section { padding: 72px 0 24px; } +.project-metrics { border: 1px solid var(--line); display: grid; grid-template-columns: repeat(5, 1fr); margin-bottom: 22px; } +.project-metrics article { border-right: 1px solid var(--line); padding: 18px; } +.project-metrics article:last-child { border-right: 0; } +.project-metrics span, .item-state { color: var(--muted); display: block; font-size: 10px; letter-spacing: .08em; text-transform: uppercase; } +.project-metrics strong { display: block; font-size: 24px; margin-top: 7px; } +.milestone-grid { display: grid; gap: 12px; grid-template-columns: repeat(2, 1fr); } +.milestone-card, .pilot-card { background: var(--panel); border: 1px solid var(--line); padding: 22px; } +.milestone-complete { border-top-color: rgba(185, 247, 124, .55); } +.milestone-blocked { border-top-color: rgba(255, 107, 53, .65); } +.milestone-heading, .pilot-heading { align-items: start; display: flex; gap: 16px; justify-content: space-between; } +.milestone-id { color: var(--orange); font-family: ui-monospace, monospace; font-size: 11px; } +h3 { font-size: 19px; margin: 5px 0 0; } +.milestone-progress, .pilot-progress { color: var(--muted); font-size: 12px; } +.badge-complete { border-color: rgba(185, 247, 124, .4); color: var(--lime); } +.badge-blocked { border-color: rgba(255, 107, 53, .5); color: var(--orange); } +.badge-deferred { color: var(--muted); } +.badge-boundary { border-color: rgba(121, 168, 255, .4); color: var(--blue); } +.roadmap-items { list-style: none; margin: 20px 0 0; padding: 0; } +.roadmap-item { align-items: start; border-top: 1px solid var(--line); display: grid; gap: 10px; grid-template-columns: 18px 1fr; padding: 12px 0; } +.roadmap-item strong { display: block; font-size: 12px; } +.item-marker { color: var(--muted); font-family: ui-monospace, monospace; } +.item-done .item-marker { color: var(--lime); } +.item-blocked .item-marker { color: var(--orange); } +.item-state { margin-top: 4px; } +.item-note { color: var(--muted); display: block; font-size: 11px; line-height: 1.4; margin-top: 6px; } +.pilot-grid { display: grid; gap: 12px; } +.pilot-boundary { border-left: 2px solid var(--blue); color: var(--muted); font-size: 13px; line-height: 1.55; padding-left: 14px; } + +@media (max-width: 800px) { + .project-metrics { grid-template-columns: repeat(2, 1fr); } + .project-metrics article { border-bottom: 1px solid var(--line); } + .milestone-grid { grid-template-columns: 1fr; } + .milestone-heading, .pilot-heading { align-items: start; flex-direction: column; } diff --git a/package.json b/package.json index 9938c66..02a15a8 100644 --- a/package.json +++ b/package.json @@ -14,15 +14,20 @@ ], "scripts": { "build:m1-events": "node scripts/build-m1-example-events.mjs", + "build:status": "node scripts/build-project-status.mjs", "build:state": "node scripts/build-console-state.mjs examples/m1 apps/console/state/index.json", "build:state:check": "node scripts/check-console-state.mjs", - "check": "npm run public:check && npm run validate && npm test && npm run build:state:check", + "check": "npm run public:check && npm run validate && npm test && npm run status:check && npm run build:state:check && npm run pages:check", "console": "node scripts/serve-console.mjs", "doctor": "node scripts/project-doctor.mjs", "evidence:create": "node scripts/create-evidence-bundle.mjs", "mcp": "npm --workspace @project-relay/mcp-server start", + "pages:build": "node scripts/build-pages.mjs", + "pages:check": "node scripts/check-pages.mjs", "public:check": "node scripts/check-public-boundary.mjs", "test": "node --test", + "status:build": "npm run build:status && npm run build:state && npm run pages:build", + "status:check": "node scripts/check-project-status.mjs", "validate": "node scripts/validate-repository.mjs examples/minimal examples/m1", "versions:check": "node scripts/version-doctor.mjs check", "versions:update": "node scripts/version-doctor.mjs update" diff --git a/project/status.json b/project/status.json new file mode 100644 index 0000000..d0342df --- /dev/null +++ b/project/status.json @@ -0,0 +1,402 @@ +{ + "schema_version": "0.1.0", + "project": { + "id": "project-relay", + "name": "Project Relay", + "stage": "pre-alpha", + "repository": "https://github.com/brendohxd/Project-Relay" + }, + "updated_at": "2026-07-19", + "milestones": [ + { + "id": "M0", + "title": "Protocol kernel", + "exit_criteria": [ + "The provider-neutral protocol kernel, local read-only MCP surface, fixtures, and public boundary are implemented." + ], + "items": [ + { + "id": "M0-PROTOCOL", + "title": "Canonical schemas, hashes, and event-chain verification", + "state": "done", + "evidence": [ + { + "label": "Foundation commit", + "uri": "https://github.com/brendohxd/Project-Relay/commit/99f8c9c" + } + ] + }, + { + "id": "M0-MCP", + "title": "Read-only local MCP server", + "state": "done", + "evidence": [ + { + "label": "M0 implementation", + "uri": "https://github.com/brendohxd/Project-Relay/commit/99f8c9c" + } + ] + }, + { + "id": "M0-PUBLIC", + "title": "Public-boundary scanner and repository checks", + "state": "done", + "evidence": [ + { + "label": "M0 implementation", + "uri": "https://github.com/brendohxd/Project-Relay/commit/99f8c9c" + } + ] + } + ] + }, + { + "id": "M1", + "title": "Local vertical slice", + "exit_criteria": [ + "The complete local remediation loop passes Windows and Linux verification and receives human review." + ], + "items": [ + { + "id": "M1-LIFECYCLE", + "title": "Policy transition engine and remediation fixture", + "state": "done", + "evidence": [ + { + "label": "M1 implementation commit", + "uri": "https://github.com/brendohxd/Project-Relay/commit/d899218" + } + ] + }, + { + "id": "M1-EVIDENCE", + "title": "Evidence CLI, independent review, reproduction, and human gates", + "state": "done", + "evidence": [ + { + "label": "M1 review fixes", + "uri": "https://github.com/brendohxd/Project-Relay/commit/786648e" + } + ] + }, + { + "id": "M1-LOCAL-VERIFY", + "title": "Windows local verification with 18 tests", + "state": "done", + "evidence": [ + { + "label": "Draft PR verification record", + "uri": "https://github.com/brendohxd/Project-Relay/pull/1#issuecomment-5014591903" + } + ] + }, + { + "id": "M1-HUMAN-REVIEW", + "title": "Human review of draft PR #1", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "human-action", + "summary": "Draft PR #1 has no recorded human review decision." + }, + "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." + }, + { + "id": "M1-LINUX-VERIFY", + "title": "Linux conformance verification", + "state": "blocked", + "evidence": [ + { + "label": "Blocked GitHub Actions run", + "uri": "https://github.com/brendohxd/Project-Relay/actions" + } + ], + "blocker": { + "kind": "external", + "summary": "GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists." + }, + "next_action": "Rerun the existing matrix when GitHub-hosted runners become available." + }, + { + "id": "M1-MERGE", + "title": "Merge the reviewed M1 candidate", + "state": "planned", + "evidence": [], + "next_action": "Merge only after human review and Linux verification both pass." + } + ] + }, + { + "id": "M2", + "title": "GitHub collaboration adapter", + "exit_criteria": [ + "An approved Relay proposal can be translated into an idempotent, least-privilege GitHub change with conflict and failure records." + ], + "items": [ + { + "id": "M2-CAPABILITIES", + "title": "Declared actor capabilities", + "state": "done", + "evidence": [ + { + "label": "Capability foundation", + "uri": "https://github.com/brendohxd/Project-Relay/commit/11dc90a" + } + ] + }, + { + "id": "M2-CAUSAL-LINKS", + "title": "Typed causal provenance links", + "state": "done", + "evidence": [ + { + "label": "Causal-link foundation", + "uri": "https://github.com/brendohxd/Project-Relay/commit/11dc90a" + } + ] + }, + { + "id": "M2-PROPOSAL", + "title": "Deterministic network-free GitHub mutation proposal", + "state": "planned", + "evidence": [], + "next_action": "Define proposal input and output contracts before authentication or writes." + }, + { + "id": "M2-CONTRACT", + "title": "Fake-GitHub contract, conflict, retry, and idempotency tests", + "state": "planned", + "evidence": [], + "next_action": "Test the adapter without live repository mutations." + }, + { + "id": "M2-AUTH", + "title": "Repository-scoped least-privilege authentication", + "state": "planned", + "evidence": [], + "next_action": "Add dedicated fine-grained credentials only after proposal behavior is reviewed." + }, + { + "id": "M2-WRITE-PILOT", + "title": "Explicitly approved live write pilot", + "state": "planned", + "evidence": [], + "next_action": "Require fresh user approval and a clean threat-model review." + } + ] + }, + { + "id": "M3", + "title": "Research and assessment profiles", + "exit_criteria": [ + "Synthetic high-risk profiles prove blind reproduction, independence, disagreement, supersession, and human authority before real domain use." + ], + "items": [ + { + "id": "M3-REPRODUCTION", + "title": "Independent high-risk reproduction core", + "state": "done", + "evidence": [ + { + "label": "Independent reproduction gates", + "uri": "https://github.com/brendohxd/Project-Relay/commit/786648e" + } + ] + }, + { + "id": "M3-BLIND-PACKET", + "title": "Blind reproduction packet contract", + "state": "planned", + "evidence": [], + "next_action": "Design and test against synthetic claims before any ITSM packet." + }, + { + "id": "M3-DISAGREEMENT", + "title": "Adversarial review and disagreement ledger", + "state": "planned", + "evidence": [], + "next_action": "Preserve incompatible findings without forced consensus." + }, + { + "id": "M3-SUPERSESSION", + "title": "Remediation and supersession graph", + "state": "planned", + "evidence": [], + "next_action": "Build on typed causal links and immutable prior records." + }, + { + "id": "M3-ITSM-PROFILE", + "title": "ITSM operational policy profile", + "state": "planned", + "evidence": [], + "next_action": "Implement only after the synthetic research profile passes." + }, + { + "id": "M3-EDUCATION-PROFILE", + "title": "Synthetic education marking and moderation profile", + "state": "planned", + "evidence": [], + "next_action": "Model rubric assessment, moderation, appeals, and named human authority without real student data." + }, + { + "id": "M3-HIRING-PROFILE", + "title": "Synthetic hiring-assistance profile", + "state": "deferred", + "evidence": [], + "rationale": "Hiring requires dedicated privacy, fairness, discrimination, explainability, and legal threat models; Relay must remain decision support with human authority." + } + ] + }, + { + "id": "M4", + "title": "Public console and integrations", + "exit_criteria": [ + "Users can inspect project and task status through credential-free projections and supported client guides." + ], + "items": [ + { + "id": "M4-STATUS", + "title": "Canonical project status and generated GitHub view", + "state": "done", + "evidence": [ + { + "label": "Canonical project status", + "uri": "project/status.json" + }, + { + "label": "Generated GitHub status", + "uri": "STATUS.md" + } + ] + }, + { + "id": "M4-CONSOLE", + "title": "Visual milestone, blocker, and pilot dashboard", + "state": "done", + "evidence": [ + { + "label": "Read-only console", + "uri": "apps/console/index.html" + } + ] + }, + { + "id": "M4-PAGES-READY", + "title": "GitHub Pages-ready static bundle", + "state": "done", + "evidence": [ + { + "label": "Pages entry point", + "uri": "docs/index.html" + } + ] + }, + { + "id": "M4-PAGES-PUBLISH", + "title": "Publish the visual console on GitHub Pages", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "external", + "summary": "GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction." + }, + "next_action": "After merge, configure Pages from main /docs and record the resulting public URL or blocker." + }, + { + "id": "M4-CLIENT-GUIDES", + "title": "Codex, Claude, Gemini, and compatible MCP guides", + "state": "planned", + "evidence": [], + "next_action": "Document read-only status and task access before any write integrations." + }, + { + "id": "M4-MANUAL-BRIDGE", + "title": "Manual packet bridge", + "state": "planned", + "evidence": [], + "next_action": "Support clients without GitHub or MCP while preserving the same evidence contract." + }, + { + "id": "M4-NOTIFICATIONS", + "title": "Notifications and human approval inbox", + "state": "planned", + "evidence": [], + "next_action": "Begin only after authenticated M2 write boundaries are proven." + } + ] + }, + { + "id": "M5", + "title": "Optional managed service", + "exit_criteria": [ + "Security, funding, licensing, product demand, and service responsibilities are explicitly approved before hosted development." + ], + "items": [ + { + "id": "M5-HOSTED", + "title": "Hosted authentication, secrets, tenancy, and audit service", + "state": "deferred", + "evidence": [], + "rationale": "No hosted service is authorised before security, funding, licensing, and demand gates are resolved." + }, + { + "id": "M5-BILLING", + "title": "Usage controls, billing, support, and service objectives", + "state": "deferred", + "evidence": [], + "rationale": "No paid infrastructure or billing work is required for the current local-first roadmap." + } + ] + } + ], + "pilots": [ + { + "id": "itsm", + "name": "ITSM proving ground", + "visibility": "operational-summary-only", + "boundary": "Relay may show workflow readiness and gate progress only. ITSM equations, claims, datasets, results, manuscripts, and scientific acceptance remain in the separate ITSM repository and knowledge base.", + "items": [ + { + "id": "ITSM-BOUNDARY", + "title": "Relay and ITSM separation rule documented", + "state": "done", + "evidence": [ + { + "label": "ITSM pilot operating model", + "uri": "https://app.notion.com/p/3a236fb4bd1d810fb409f87153bc3e84" + } + ] + }, + { + "id": "ITSM-ROLES", + "title": "Operational owner, review, reproduction, audit, and authority roles documented", + "state": "done", + "evidence": [ + { + "label": "ITSM pilot operating model", + "uri": "https://app.notion.com/p/3a236fb4bd1d810fb409f87153bc3e84" + } + ] + }, + { + "id": "ITSM-SYNTHETIC", + "title": "Synthetic research profile passes before ITSM use", + "state": "planned", + "evidence": [], + "next_action": "Complete the M3 synthetic research-gate fixture." + }, + { + "id": "ITSM-REAL-PILOT", + "title": "First real ITSM Relay packet", + "state": "blocked", + "evidence": [], + "blocker": { + "kind": "dependency", + "summary": "A real ITSM packet must wait for the synthetic M3 profile and explicit selection of a boundary-safe pilot task." + }, + "next_action": "Select the pilot only after the synthetic profile passes." + } + ] + } + ] +} diff --git a/project/status.schema.json b/project/status.schema.json new file mode 100644 index 0000000..0395cfd --- /dev/null +++ b/project/status.schema.json @@ -0,0 +1,104 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://project-relay.dev/project/status.schema.json", + "title": "Project Relay Status", + "type": "object", + "additionalProperties": false, + "required": ["schema_version", "project", "updated_at", "milestones", "pilots"], + "properties": { + "schema_version": { "const": "0.1.0" }, + "project": { + "type": "object", + "additionalProperties": false, + "required": ["id", "name", "stage", "repository"], + "properties": { + "id": { "type": "string", "pattern": "^[a-z][a-z0-9-]{2,63}$" }, + "name": { "type": "string", "minLength": 3, "maxLength": 100 }, + "stage": { "enum": ["pre-alpha", "alpha", "beta", "stable"] }, + "repository": { "type": "string", "format": "uri" } + } + }, + "updated_at": { "type": "string", "format": "date" }, + "milestones": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/milestone" } + }, + "pilots": { + "type": "array", + "items": { "$ref": "#/$defs/pilot" } + } + }, + "$defs": { + "evidence_link": { + "type": "object", + "additionalProperties": false, + "required": ["label", "uri"], + "properties": { + "label": { "type": "string", "minLength": 1, "maxLength": 160 }, + "uri": { "type": "string", "minLength": 1, "maxLength": 2000 } + } + }, + "blocker": { + "type": "object", + "additionalProperties": false, + "required": ["kind", "summary"], + "properties": { + "kind": { "enum": ["external", "human-action", "dependency", "decision"] }, + "summary": { "type": "string", "minLength": 5, "maxLength": 1000 } + } + }, + "work_item": { + "type": "object", + "additionalProperties": false, + "required": ["id", "title", "state", "evidence"], + "properties": { + "id": { "type": "string", "pattern": "^[A-Z][A-Z0-9-]{2,63}$" }, + "title": { "type": "string", "minLength": 3, "maxLength": 200 }, + "state": { "enum": ["done", "in_progress", "blocked", "planned", "deferred"] }, + "evidence": { + "type": "array", + "items": { "$ref": "#/$defs/evidence_link" } + }, + "blocker": { "$ref": "#/$defs/blocker" }, + "rationale": { "type": "string", "minLength": 5, "maxLength": 2000 }, + "next_action": { "type": "string", "minLength": 5, "maxLength": 1000 } + } + }, + "milestone": { + "type": "object", + "additionalProperties": false, + "required": ["id", "title", "exit_criteria", "items"], + "properties": { + "id": { "type": "string", "pattern": "^M[0-9]+$" }, + "title": { "type": "string", "minLength": 3, "maxLength": 160 }, + "exit_criteria": { + "type": "array", + "minItems": 1, + "items": { "type": "string", "minLength": 5, "maxLength": 1000 } + }, + "items": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/work_item" } + } + } + }, + "pilot": { + "type": "object", + "additionalProperties": false, + "required": ["id", "name", "visibility", "boundary", "items"], + "properties": { + "id": { "type": "string", "pattern": "^[a-z][a-z0-9-]{2,63}$" }, + "name": { "type": "string", "minLength": 2, "maxLength": 100 }, + "visibility": { "const": "operational-summary-only" }, + "boundary": { "type": "string", "minLength": 20, "maxLength": 2000 }, + "items": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/work_item" } + } + } + } + } +} diff --git a/scripts/build-console-state.mjs b/scripts/build-console-state.mjs index 82df3ba..29295c2 100644 --- a/scripts/build-console-state.mjs +++ b/scripts/build-console-state.mjs @@ -2,11 +2,14 @@ import { mkdir, writeFile } from "node:fs/promises"; import path from "node:path"; import { readDocuments, readTaskPacket, validateWorkspace } from "@project-relay/protocol"; +import { loadProjectStatus } from "./project-status-lib.mjs"; const workspaceArgument = process.argv[2] ?? "examples/minimal"; const outputArgument = process.argv[3] ?? "apps/console/state/index.json"; const workspace = path.resolve(workspaceArgument); const output = path.resolve(outputArgument); +const projectStatusFile = path.resolve(process.argv[4] ?? "project/status.json"); +const projectStatusSchema = path.resolve("project/status.schema.json"); const verification = await validateWorkspace(workspace); if (!verification.valid) { @@ -26,11 +29,12 @@ async function load(kind) { ); } -const [tasks, evidence, reviews, decisions] = await Promise.all([ +const [tasks, evidence, reviews, decisions, project] = await Promise.all([ load("task"), load("evidence"), load("review"), - load("decision") + load("decision"), + loadProjectStatus(projectStatusFile, projectStatusSchema) ]); const packets = new Map( ( @@ -44,6 +48,7 @@ const countFor = (records, taskId) => records.filter((record) => record.task_id const snapshot = { protocol_version: "0.1.0", generated_from: workspaceArgument.replaceAll("\\", "/"), + project, summary: { tasks: tasks.length, active: tasks.filter((task) => !["accepted", "rejected", "cancelled"].includes(task.state)).length, diff --git a/scripts/build-pages.mjs b/scripts/build-pages.mjs new file mode 100644 index 0000000..9891d97 --- /dev/null +++ b/scripts/build-pages.mjs @@ -0,0 +1,11 @@ +import { copyFile, mkdir, writeFile } from "node:fs/promises"; + +await mkdir("docs/state", { recursive: true }); +await Promise.all([ + copyFile("apps/console/index.html", "docs/index.html"), + copyFile("apps/console/app.js", "docs/app.js"), + copyFile("apps/console/styles.css", "docs/styles.css"), + copyFile("apps/console/state/index.json", "docs/state/index.json"), + writeFile("docs/.nojekyll", "", "utf8") +]); +console.log("Wrote GitHub Pages bundle to docs/."); diff --git a/scripts/build-project-status.mjs b/scripts/build-project-status.mjs new file mode 100644 index 0000000..fb9a3c6 --- /dev/null +++ b/scripts/build-project-status.mjs @@ -0,0 +1,7 @@ +import { writeFile } from "node:fs/promises"; + +import { loadProjectStatus, renderProjectStatus } from "./project-status-lib.mjs"; + +const status = await loadProjectStatus(); +await writeFile("STATUS.md", renderProjectStatus(status), "utf8"); +console.log("Wrote STATUS.md"); diff --git a/scripts/check-pages.mjs b/scripts/check-pages.mjs new file mode 100644 index 0000000..db003d1 --- /dev/null +++ b/scripts/check-pages.mjs @@ -0,0 +1,19 @@ +import { readFile } from "node:fs/promises"; + +const pairs = [ + ["apps/console/index.html", "docs/index.html"], + ["apps/console/app.js", "docs/app.js"], + ["apps/console/styles.css", "docs/styles.css"], + ["apps/console/state/index.json", "docs/state/index.json"] +]; +const stale = []; +for (const [source, generated] of pairs) { + const [left, right] = await Promise.all([readFile(source), readFile(generated)]); + if (!left.equals(right)) stale.push(generated); +} +if ((await readFile("docs/.nojekyll", "utf8")) !== "") stale.push("docs/.nojekyll"); +if (stale.length > 0) { + console.error(`Pages bundle is stale: ${stale.join(", ")}. Run npm run pages:build.`); + process.exit(1); +} +console.log("GitHub Pages bundle is current."); diff --git a/scripts/check-project-status.mjs b/scripts/check-project-status.mjs new file mode 100644 index 0000000..bca0f96 --- /dev/null +++ b/scripts/check-project-status.mjs @@ -0,0 +1,18 @@ +import { readFile } from "node:fs/promises"; + +import { loadProjectStatus, renderProjectStatus } from "./project-status-lib.mjs"; + +try { + const status = await loadProjectStatus(); + const actual = await readFile("STATUS.md", "utf8"); + const expected = renderProjectStatus(status); + if (actual !== expected) { + console.error("STATUS.md is stale. Run npm run build:status."); + process.exit(1); + } + console.log("Project status is valid and STATUS.md is current."); +} catch (error) { + console.error(error.message); + if (error.issues) console.error(JSON.stringify(error.issues, null, 2)); + process.exit(1); +} diff --git a/scripts/project-status-lib.mjs b/scripts/project-status-lib.mjs new file mode 100644 index 0000000..466d974 --- /dev/null +++ b/scripts/project-status-lib.mjs @@ -0,0 +1,164 @@ +import { readFile } from "node:fs/promises"; + +import Ajv2020 from "ajv/dist/2020.js"; +import addFormats from "ajv-formats"; + +const STATE_ORDER = ["done", "in_progress", "blocked", "planned", "deferred"]; + +function issue(path, message) { + return { path, message }; +} + +export function deriveCollection(items) { + const counts = Object.fromEntries(STATE_ORDER.map((state) => [state, 0])); + for (const item of items) counts[item.state] += 1; + let state = "planned"; + if (counts.done === items.length) state = "complete"; + else if (counts.in_progress > 0) state = "in_progress"; + else if (counts.deferred === items.length) state = "deferred"; + else if (counts.blocked > 0 && counts.blocked + counts.done + counts.deferred === items.length) { + state = "blocked"; + } + return { + state, + counts, + total: items.length, + actionable: items.length - counts.deferred, + completed: counts.done, + blockers: items.filter((item) => item.state === "blocked") + }; +} + +export function validateStatusSemantics(status) { + const issues = []; + const identifiers = new Map(); + const recordId = (id, path) => { + if (identifiers.has(id)) issues.push(issue(path, `duplicate id ${id}; first used at ${identifiers.get(id)}`)); + else identifiers.set(id, path); + }; + const inspectItems = (items, base) => { + items.forEach((item, index) => { + const path = `${base}/${index}`; + recordId(item.id, `${path}/id`); + if (item.state === "done" && item.evidence.length === 0) { + issues.push(issue(`${path}/evidence`, "done items require at least one evidence link")); + } + if (item.state === "blocked" && !item.blocker) { + issues.push(issue(`${path}/blocker`, "blocked items require a blocker")); + } + if (item.state !== "blocked" && item.blocker) { + issues.push(issue(`${path}/blocker`, "only blocked items may carry a blocker")); + } + if (item.state === "deferred" && !item.rationale) { + issues.push(issue(`${path}/rationale`, "deferred items require a rationale")); + } + }); + }; + status.milestones.forEach((milestone, index) => { + recordId(milestone.id, `/milestones/${index}/id`); + inspectItems(milestone.items, `/milestones/${index}/items`); + }); + status.pilots.forEach((pilot, index) => { + recordId(pilot.id, `/pilots/${index}/id`); + inspectItems(pilot.items, `/pilots/${index}/items`); + }); + return issues; +} + +export function deriveProjectStatus(status) { + const milestones = status.milestones.map((milestone) => ({ + ...milestone, + summary: deriveCollection(milestone.items) + })); + const pilots = status.pilots.map((pilot) => ({ ...pilot, summary: deriveCollection(pilot.items) })); + const allItems = milestones.flatMap((milestone) => milestone.items); + const summary = deriveCollection(allItems); + const current = milestones.find((milestone) => !["complete", "deferred"].includes(milestone.summary.state)); + return { + ...status, + summary: { + ...summary, + current_milestone: current?.id ?? null, + current_milestone_title: current?.title ?? null + }, + milestones, + pilots + }; +} + +export async function loadProjectStatus( + statusFile = "project/status.json", + schemaFile = "project/status.schema.json" +) { + const [status, schema] = await Promise.all([ + readFile(statusFile, "utf8").then(JSON.parse), + readFile(schemaFile, "utf8").then(JSON.parse) + ]); + const ajv = new Ajv2020({ allErrors: true, strict: true }); + addFormats(ajv); + const validate = ajv.compile(schema); + const issues = []; + if (!validate(status)) { + issues.push(...validate.errors.map((error) => issue(error.instancePath || "/", error.message))); + } else { + issues.push(...validateStatusSemantics(status)); + } + if (issues.length > 0) { + const error = new Error(`Project status validation failed with ${issues.length} issue(s)`); + error.issues = issues; + throw error; + } + return deriveProjectStatus(status); +} + +function evidenceMarkdown(evidence) { + return evidence.map(({ label, uri }) => `[${label}](${uri})`).join(", "); +} + +function stateLabel(state) { + return state.replaceAll("_", " "); +} + +export function renderProjectStatus(status) { + const derived = status.summary ? status : deriveProjectStatus(status); + const lines = [ + "# Project status", + "", + `> Generated from [project/status.json](project/status.json) on ${derived.updated_at}. Do not edit this file directly.`, + "", + `**Stage:** ${derived.project.stage}
`, + `**Current milestone:** ${derived.summary.current_milestone ?? "None"} — ${derived.summary.current_milestone_title ?? "All planned milestones complete or deferred"}
`, + `**Roadmap:** ${derived.summary.completed}/${derived.summary.actionable} actionable items complete; ${derived.summary.counts.blocked} blocked; ${derived.summary.counts.deferred} deferred.`, + "", + "| Milestone | Status | Complete | Blocked | Deferred |", + "|---|---:|---:|---:|---:|" + ]; + for (const milestone of derived.milestones) { + lines.push(`| ${milestone.id} — ${milestone.title} | ${stateLabel(milestone.summary.state)} | ${milestone.summary.completed}/${milestone.summary.actionable} | ${milestone.summary.counts.blocked} | ${milestone.summary.counts.deferred} |`); + } + for (const milestone of derived.milestones) { + lines.push("", `## ${milestone.id} — ${milestone.title}`, "", `**Status:** ${stateLabel(milestone.summary.state)}`, "", "Exit criteria:"); + for (const criterion of milestone.exit_criteria) lines.push(`- ${criterion}`); + lines.push(""); + for (const item of milestone.items) { + const checked = item.state === "done" ? "x" : " "; + lines.push(`- [${checked}] **${item.id}: ${item.title}** — ${stateLabel(item.state)}`); + if (item.blocker) lines.push(` - Blocker (${item.blocker.kind}): ${item.blocker.summary}`); + if (item.rationale) lines.push(` - Rationale: ${item.rationale}`); + if (item.next_action) lines.push(` - Next: ${item.next_action}`); + if (item.evidence.length > 0) lines.push(` - Evidence: ${evidenceMarkdown(item.evidence)}`); + } + } + if (derived.pilots.length > 0) lines.push("", "# Proving grounds"); + for (const pilot of derived.pilots) { + lines.push("", `## ${pilot.name}`, "", `**Visibility:** ${pilot.visibility}
`, `**Boundary:** ${pilot.boundary}`, ""); + for (const item of pilot.items) { + const checked = item.state === "done" ? "x" : " "; + lines.push(`- [${checked}] **${item.title}** — ${stateLabel(item.state)}`); + if (item.blocker) lines.push(` - Blocker (${item.blocker.kind}): ${item.blocker.summary}`); + if (item.next_action) lines.push(` - Next: ${item.next_action}`); + if (item.evidence.length > 0) lines.push(` - Evidence: ${evidenceMarkdown(item.evidence)}`); + } + } + return `${lines.join("\n")}\n`; +} diff --git a/test/mcp.test.js b/test/mcp.test.js index f9eef43..9f69596 100644 --- a/test/mcp.test.js +++ b/test/mcp.test.js @@ -17,6 +17,16 @@ test("MCP exposes the M1 task resource and bounded review prompt", async () => { try { await client.connect(transport); + const resources = await client.listResources(); + assert.ok(resources.resources.some((item) => item.uri === "relay://project/status")); + const projectResource = await client.readResource({ uri: "relay://project/status" }); + const projectStatus = JSON.parse(projectResource.contents[0].text); + assert.equal(projectStatus.summary.current_milestone, "M1"); + assert.equal(projectStatus.pilots[0].visibility, "operational-summary-only"); + + const statusTool = await client.callTool({ name: "relay_get_project_status", arguments: {} }); + assert.equal(statusTool.isError, false); + assert.equal(statusTool.structuredContent.summary.current_milestone, "M1"); const templates = await client.listResourceTemplates(); assert.ok( templates.resourceTemplates.some( diff --git a/test/project-status.test.js b/test/project-status.test.js new file mode 100644 index 0000000..fa9d538 --- /dev/null +++ b/test/project-status.test.js @@ -0,0 +1,38 @@ +import assert from "node:assert/strict"; +import { readFile } from "node:fs/promises"; +import test from "node:test"; + +import { + deriveProjectStatus, + loadProjectStatus, + renderProjectStatus, + validateStatusSemantics +} from "../scripts/project-status-lib.mjs"; + +const source = JSON.parse(await readFile("project/status.json", "utf8")); + +test("canonical project status validates and identifies the current milestone", async () => { + const status = await loadProjectStatus(); + assert.equal(status.milestones[0].summary.state, "complete"); + assert.equal(status.summary.current_milestone, "M1"); + assert.equal(status.summary.counts.blocked, 3); +}); + +test("generated status preserves the ITSM publication boundary", () => { + const markdown = renderProjectStatus(deriveProjectStatus(source)); + assert.match(markdown, /operational-summary-only/); + assert.match(markdown, /ITSM equations, claims, datasets, results, manuscripts/); +}); + +test("semantic validation rejects unsupported state claims and duplicate ids", () => { + const invalid = structuredClone(source); + invalid.milestones[0].items[0].evidence = []; + invalid.milestones[1].items[3].blocker = undefined; + invalid.milestones[2].items[2].id = invalid.milestones[0].items[1].id; + invalid.milestones[5].items[0].rationale = undefined; + const messages = validateStatusSemantics(invalid).map(({ message }) => message); + assert.ok(messages.some((message) => message.includes("done items require"))); + assert.ok(messages.some((message) => message.includes("blocked items require"))); + assert.ok(messages.some((message) => message.includes("duplicate id"))); + assert.ok(messages.some((message) => message.includes("deferred items require"))); +}); From 16cda18d68664519d05980fa5f04ce997401b98d Mon Sep 17 00:00:00 2001 From: brendohxd Date: Sun, 19 Jul 2026 16:42:48 +0800 Subject: [PATCH 2/3] Update status after cross-platform verification --- STATUS.md | 15 +++--- apps/console/state/index.json | 94 ++++++----------------------------- docs/state/index.json | 94 ++++++----------------------------- project/status.json | 19 ++----- test/project-status.test.js | 2 +- 5 files changed, 42 insertions(+), 182 deletions(-) diff --git a/STATUS.md b/STATUS.md index 7754f60..59de34f 100644 --- a/STATUS.md +++ b/STATUS.md @@ -4,15 +4,15 @@ **Stage:** pre-alpha
**Current milestone:** M1 — Local vertical slice
-**Roadmap:** 12/28 actionable items complete; 3 blocked; 3 deferred. +**Roadmap:** 13/28 actionable items complete; 1 blocked; 3 deferred. | Milestone | Status | Complete | Blocked | Deferred | |---|---:|---:|---:|---:| | M0 — Protocol kernel | complete | 3/3 | 0 | 0 | -| M1 — Local vertical slice | planned | 3/6 | 2 | 0 | +| M1 — Local vertical slice | planned | 4/6 | 1 | 0 | | M2 — GitHub collaboration adapter | planned | 2/6 | 0 | 0 | | M3 — Research and assessment profiles | planned | 1/6 | 0 | 1 | -| M4 — Public console and integrations | planned | 3/7 | 1 | 0 | +| M4 — Public console and integrations | planned | 3/7 | 0 | 0 | | M5 — Optional managed service | deferred | 0/0 | 0 | 2 | ## M0 — Protocol kernel @@ -45,10 +45,8 @@ Exit criteria: - [ ] **M1-HUMAN-REVIEW: Human review of draft PR #1** — blocked - Blocker (human-action): Draft PR #1 has no recorded human review decision. - Next: Review the protocol behavior, limitations, and public boundary in draft PR #1. -- [ ] **M1-LINUX-VERIFY: Linux conformance verification** — blocked - - Blocker (external): GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists. - - Next: Rerun the existing matrix when GitHub-hosted runners become available. - - Evidence: [Blocked GitHub Actions run](https://github.com/brendohxd/Project-Relay/actions) +- [x] **M1-LINUX-VERIFY: Linux conformance verification** — done + - Evidence: [Passing Windows and Linux M1 matrix](https://github.com/brendohxd/Project-Relay/actions/runs/29680192255) - [ ] **M1-MERGE: Merge the reviewed M1 candidate** — planned - Next: Merge only after human review and Linux verification both pass. @@ -107,8 +105,7 @@ Exit criteria: - Evidence: [Read-only console](apps/console/index.html) - [x] **M4-PAGES-READY: GitHub Pages-ready static bundle** — done - Evidence: [Pages entry point](docs/index.html) -- [ ] **M4-PAGES-PUBLISH: Publish the visual console on GitHub Pages** — blocked - - Blocker (external): GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction. +- [ ] **M4-PAGES-PUBLISH: Publish the visual console on GitHub Pages** — planned - Next: After merge, configure Pages from main /docs and record the resulting public URL or blocker. - [ ] **M4-CLIENT-GUIDES: Codex, Claude, Gemini, and compatible MCP guides** — planned - Next: Document read-only status and task access before any write integrations. diff --git a/apps/console/state/index.json b/apps/console/state/index.json index 5628241..32b9cbd 100644 --- a/apps/console/state/index.json +++ b/apps/console/state/index.json @@ -121,18 +121,13 @@ { "id": "M1-LINUX-VERIFY", "title": "Linux conformance verification", - "state": "blocked", + "state": "done", "evidence": [ { - "label": "Blocked GitHub Actions run", - "uri": "https://github.com/brendohxd/Project-Relay/actions" + "label": "Passing Windows and Linux M1 matrix", + "uri": "https://github.com/brendohxd/Project-Relay/actions/runs/29680192255" } - ], - "blocker": { - "kind": "external", - "summary": "GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists." - }, - "next_action": "Rerun the existing matrix when GitHub-hosted runners become available." + ] }, { "id": "M1-MERGE", @@ -145,15 +140,15 @@ "summary": { "state": "planned", "counts": { - "done": 3, + "done": 4, "in_progress": 0, - "blocked": 2, + "blocked": 1, "planned": 1, "deferred": 0 }, "total": 6, "actionable": 6, - "completed": 3, + "completed": 4, "blockers": [ { "id": "M1-HUMAN-REVIEW", @@ -165,22 +160,6 @@ "summary": "Draft PR #1 has no recorded human review decision." }, "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." - }, - { - "id": "M1-LINUX-VERIFY", - "title": "Linux conformance verification", - "state": "blocked", - "evidence": [ - { - "label": "Blocked GitHub Actions run", - "uri": "https://github.com/brendohxd/Project-Relay/actions" - } - ], - "blocker": { - "kind": "external", - "summary": "GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists." - }, - "next_action": "Rerun the existing matrix when GitHub-hosted runners become available." } ] } @@ -381,12 +360,8 @@ { "id": "M4-PAGES-PUBLISH", "title": "Publish the visual console on GitHub Pages", - "state": "blocked", + "state": "planned", "evidence": [], - "blocker": { - "kind": "external", - "summary": "GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction." - }, "next_action": "After merge, configure Pages from main /docs and record the resulting public URL or blocker." }, { @@ -416,26 +391,14 @@ "counts": { "done": 3, "in_progress": 0, - "blocked": 1, - "planned": 3, + "blocked": 0, + "planned": 4, "deferred": 0 }, "total": 7, "actionable": 7, "completed": 3, - "blockers": [ - { - "id": "M4-PAGES-PUBLISH", - "title": "Publish the visual console on GitHub Pages", - "state": "blocked", - "evidence": [], - "blocker": { - "kind": "external", - "summary": "GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction." - }, - "next_action": "After merge, configure Pages from main /docs and record the resulting public URL or blocker." - } - ] + "blockers": [] } }, { @@ -555,15 +518,15 @@ "summary": { "state": "planned", "counts": { - "done": 12, + "done": 13, "in_progress": 0, - "blocked": 3, - "planned": 13, + "blocked": 1, + "planned": 14, "deferred": 3 }, "total": 31, "actionable": 28, - "completed": 12, + "completed": 13, "blockers": [ { "id": "M1-HUMAN-REVIEW", @@ -575,33 +538,6 @@ "summary": "Draft PR #1 has no recorded human review decision." }, "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." - }, - { - "id": "M1-LINUX-VERIFY", - "title": "Linux conformance verification", - "state": "blocked", - "evidence": [ - { - "label": "Blocked GitHub Actions run", - "uri": "https://github.com/brendohxd/Project-Relay/actions" - } - ], - "blocker": { - "kind": "external", - "summary": "GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists." - }, - "next_action": "Rerun the existing matrix when GitHub-hosted runners become available." - }, - { - "id": "M4-PAGES-PUBLISH", - "title": "Publish the visual console on GitHub Pages", - "state": "blocked", - "evidence": [], - "blocker": { - "kind": "external", - "summary": "GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction." - }, - "next_action": "After merge, configure Pages from main /docs and record the resulting public URL or blocker." } ], "current_milestone": "M1", diff --git a/docs/state/index.json b/docs/state/index.json index 5628241..32b9cbd 100644 --- a/docs/state/index.json +++ b/docs/state/index.json @@ -121,18 +121,13 @@ { "id": "M1-LINUX-VERIFY", "title": "Linux conformance verification", - "state": "blocked", + "state": "done", "evidence": [ { - "label": "Blocked GitHub Actions run", - "uri": "https://github.com/brendohxd/Project-Relay/actions" + "label": "Passing Windows and Linux M1 matrix", + "uri": "https://github.com/brendohxd/Project-Relay/actions/runs/29680192255" } - ], - "blocker": { - "kind": "external", - "summary": "GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists." - }, - "next_action": "Rerun the existing matrix when GitHub-hosted runners become available." + ] }, { "id": "M1-MERGE", @@ -145,15 +140,15 @@ "summary": { "state": "planned", "counts": { - "done": 3, + "done": 4, "in_progress": 0, - "blocked": 2, + "blocked": 1, "planned": 1, "deferred": 0 }, "total": 6, "actionable": 6, - "completed": 3, + "completed": 4, "blockers": [ { "id": "M1-HUMAN-REVIEW", @@ -165,22 +160,6 @@ "summary": "Draft PR #1 has no recorded human review decision." }, "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." - }, - { - "id": "M1-LINUX-VERIFY", - "title": "Linux conformance verification", - "state": "blocked", - "evidence": [ - { - "label": "Blocked GitHub Actions run", - "uri": "https://github.com/brendohxd/Project-Relay/actions" - } - ], - "blocker": { - "kind": "external", - "summary": "GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists." - }, - "next_action": "Rerun the existing matrix when GitHub-hosted runners become available." } ] } @@ -381,12 +360,8 @@ { "id": "M4-PAGES-PUBLISH", "title": "Publish the visual console on GitHub Pages", - "state": "blocked", + "state": "planned", "evidence": [], - "blocker": { - "kind": "external", - "summary": "GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction." - }, "next_action": "After merge, configure Pages from main /docs and record the resulting public URL or blocker." }, { @@ -416,26 +391,14 @@ "counts": { "done": 3, "in_progress": 0, - "blocked": 1, - "planned": 3, + "blocked": 0, + "planned": 4, "deferred": 0 }, "total": 7, "actionable": 7, "completed": 3, - "blockers": [ - { - "id": "M4-PAGES-PUBLISH", - "title": "Publish the visual console on GitHub Pages", - "state": "blocked", - "evidence": [], - "blocker": { - "kind": "external", - "summary": "GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction." - }, - "next_action": "After merge, configure Pages from main /docs and record the resulting public URL or blocker." - } - ] + "blockers": [] } }, { @@ -555,15 +518,15 @@ "summary": { "state": "planned", "counts": { - "done": 12, + "done": 13, "in_progress": 0, - "blocked": 3, - "planned": 13, + "blocked": 1, + "planned": 14, "deferred": 3 }, "total": 31, "actionable": 28, - "completed": 12, + "completed": 13, "blockers": [ { "id": "M1-HUMAN-REVIEW", @@ -575,33 +538,6 @@ "summary": "Draft PR #1 has no recorded human review decision." }, "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." - }, - { - "id": "M1-LINUX-VERIFY", - "title": "Linux conformance verification", - "state": "blocked", - "evidence": [ - { - "label": "Blocked GitHub Actions run", - "uri": "https://github.com/brendohxd/Project-Relay/actions" - } - ], - "blocker": { - "kind": "external", - "summary": "GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists." - }, - "next_action": "Rerun the existing matrix when GitHub-hosted runners become available." - }, - { - "id": "M4-PAGES-PUBLISH", - "title": "Publish the visual console on GitHub Pages", - "state": "blocked", - "evidence": [], - "blocker": { - "kind": "external", - "summary": "GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction." - }, - "next_action": "After merge, configure Pages from main /docs and record the resulting public URL or blocker." } ], "current_milestone": "M1", diff --git a/project/status.json b/project/status.json index d0342df..ff1b7f3 100644 --- a/project/status.json +++ b/project/status.json @@ -104,18 +104,13 @@ { "id": "M1-LINUX-VERIFY", "title": "Linux conformance verification", - "state": "blocked", + "state": "done", "evidence": [ { - "label": "Blocked GitHub Actions run", - "uri": "https://github.com/brendohxd/Project-Relay/actions" + "label": "Passing Windows and Linux M1 matrix", + "uri": "https://github.com/brendohxd/Project-Relay/actions/runs/29680192255" } - ], - "blocker": { - "kind": "external", - "summary": "GitHub-hosted runners are unavailable because of the account-level billing lock; no Linux result exists." - }, - "next_action": "Rerun the existing matrix when GitHub-hosted runners become available." + ] }, { "id": "M1-MERGE", @@ -294,12 +289,8 @@ { "id": "M4-PAGES-PUBLISH", "title": "Publish the visual console on GitHub Pages", - "state": "blocked", + "state": "planned", "evidence": [], - "blocker": { - "kind": "external", - "summary": "GitHub Pages is not configured and its deployment job may be blocked by the same account-level Actions restriction." - }, "next_action": "After merge, configure Pages from main /docs and record the resulting public URL or blocker." }, { diff --git a/test/project-status.test.js b/test/project-status.test.js index fa9d538..5a629ce 100644 --- a/test/project-status.test.js +++ b/test/project-status.test.js @@ -15,7 +15,7 @@ test("canonical project status validates and identifies the current milestone", const status = await loadProjectStatus(); assert.equal(status.milestones[0].summary.state, "complete"); assert.equal(status.summary.current_milestone, "M1"); - assert.equal(status.summary.counts.blocked, 3); + assert.equal(status.summary.counts.blocked, 1); }); test("generated status preserves the ITSM publication boundary", () => { From b512040972dc125cb135e5df6cdbc9ef94d4ee62 Mon Sep 17 00:00:00 2001 From: brendohxd Date: Sun, 19 Jul 2026 17:07:24 +0800 Subject: [PATCH 3/3] Mark M1 complete after review and merge --- STATUS.md | 17 ++++---- apps/console/state/index.json | 74 +++++++++++++---------------------- docs/state/index.json | 74 +++++++++++++---------------------- project/status.json | 24 +++++++----- test/mcp.test.js | 4 +- test/project-status.test.js | 6 +-- 6 files changed, 81 insertions(+), 118 deletions(-) diff --git a/STATUS.md b/STATUS.md index 59de34f..25e24f1 100644 --- a/STATUS.md +++ b/STATUS.md @@ -3,13 +3,13 @@ > Generated from [project/status.json](project/status.json) on 2026-07-19. Do not edit this file directly. **Stage:** pre-alpha
-**Current milestone:** M1 — Local vertical slice
-**Roadmap:** 13/28 actionable items complete; 1 blocked; 3 deferred. +**Current milestone:** M2 — GitHub collaboration adapter
+**Roadmap:** 15/28 actionable items complete; 0 blocked; 3 deferred. | Milestone | Status | Complete | Blocked | Deferred | |---|---:|---:|---:|---:| | M0 — Protocol kernel | complete | 3/3 | 0 | 0 | -| M1 — Local vertical slice | planned | 4/6 | 1 | 0 | +| M1 — Local vertical slice | complete | 6/6 | 0 | 0 | | M2 — GitHub collaboration adapter | planned | 2/6 | 0 | 0 | | M3 — Research and assessment profiles | planned | 1/6 | 0 | 1 | | M4 — Public console and integrations | planned | 3/7 | 0 | 0 | @@ -31,7 +31,7 @@ Exit criteria: ## M1 — Local vertical slice -**Status:** planned +**Status:** complete Exit criteria: - The complete local remediation loop passes Windows and Linux verification and receives human review. @@ -42,13 +42,12 @@ Exit criteria: - Evidence: [M1 review fixes](https://github.com/brendohxd/Project-Relay/commit/786648e) - [x] **M1-LOCAL-VERIFY: Windows local verification with 18 tests** — done - Evidence: [Draft PR verification record](https://github.com/brendohxd/Project-Relay/pull/1#issuecomment-5014591903) -- [ ] **M1-HUMAN-REVIEW: Human review of draft PR #1** — blocked - - Blocker (human-action): Draft PR #1 has no recorded human review decision. - - Next: Review the protocol behavior, limitations, and public boundary in draft PR #1. +- [x] **M1-HUMAN-REVIEW: Human review of draft PR #1** — done + - Evidence: [Approved and merged M1 pull request](https://github.com/brendohxd/Project-Relay/pull/1) - [x] **M1-LINUX-VERIFY: Linux conformance verification** — done - Evidence: [Passing Windows and Linux M1 matrix](https://github.com/brendohxd/Project-Relay/actions/runs/29680192255) -- [ ] **M1-MERGE: Merge the reviewed M1 candidate** — planned - - Next: Merge only after human review and Linux verification both pass. +- [x] **M1-MERGE: Merge the reviewed M1 candidate** — done + - Evidence: [M1 merge commit](https://github.com/brendohxd/Project-Relay/commit/65a7d36ea2d9d29271b970ba883baa9c5a5a2b34) ## M2 — GitHub collaboration adapter diff --git a/apps/console/state/index.json b/apps/console/state/index.json index 32b9cbd..a7b1d37 100644 --- a/apps/console/state/index.json +++ b/apps/console/state/index.json @@ -110,13 +110,13 @@ { "id": "M1-HUMAN-REVIEW", "title": "Human review of draft PR #1", - "state": "blocked", - "evidence": [], - "blocker": { - "kind": "human-action", - "summary": "Draft PR #1 has no recorded human review decision." - }, - "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." + "state": "done", + "evidence": [ + { + "label": "Approved and merged M1 pull request", + "uri": "https://github.com/brendohxd/Project-Relay/pull/1" + } + ] }, { "id": "M1-LINUX-VERIFY", @@ -132,36 +132,28 @@ { "id": "M1-MERGE", "title": "Merge the reviewed M1 candidate", - "state": "planned", - "evidence": [], - "next_action": "Merge only after human review and Linux verification both pass." + "state": "done", + "evidence": [ + { + "label": "M1 merge commit", + "uri": "https://github.com/brendohxd/Project-Relay/commit/65a7d36ea2d9d29271b970ba883baa9c5a5a2b34" + } + ] } ], "summary": { - "state": "planned", + "state": "complete", "counts": { - "done": 4, + "done": 6, "in_progress": 0, - "blocked": 1, - "planned": 1, + "blocked": 0, + "planned": 0, "deferred": 0 }, "total": 6, "actionable": 6, - "completed": 4, - "blockers": [ - { - "id": "M1-HUMAN-REVIEW", - "title": "Human review of draft PR #1", - "state": "blocked", - "evidence": [], - "blocker": { - "kind": "human-action", - "summary": "Draft PR #1 has no recorded human review decision." - }, - "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." - } - ] + "completed": 6, + "blockers": [] } }, { @@ -518,30 +510,18 @@ "summary": { "state": "planned", "counts": { - "done": 13, + "done": 15, "in_progress": 0, - "blocked": 1, - "planned": 14, + "blocked": 0, + "planned": 13, "deferred": 3 }, "total": 31, "actionable": 28, - "completed": 13, - "blockers": [ - { - "id": "M1-HUMAN-REVIEW", - "title": "Human review of draft PR #1", - "state": "blocked", - "evidence": [], - "blocker": { - "kind": "human-action", - "summary": "Draft PR #1 has no recorded human review decision." - }, - "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." - } - ], - "current_milestone": "M1", - "current_milestone_title": "Local vertical slice" + "completed": 15, + "blockers": [], + "current_milestone": "M2", + "current_milestone_title": "GitHub collaboration adapter" } }, "summary": { diff --git a/docs/state/index.json b/docs/state/index.json index 32b9cbd..a7b1d37 100644 --- a/docs/state/index.json +++ b/docs/state/index.json @@ -110,13 +110,13 @@ { "id": "M1-HUMAN-REVIEW", "title": "Human review of draft PR #1", - "state": "blocked", - "evidence": [], - "blocker": { - "kind": "human-action", - "summary": "Draft PR #1 has no recorded human review decision." - }, - "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." + "state": "done", + "evidence": [ + { + "label": "Approved and merged M1 pull request", + "uri": "https://github.com/brendohxd/Project-Relay/pull/1" + } + ] }, { "id": "M1-LINUX-VERIFY", @@ -132,36 +132,28 @@ { "id": "M1-MERGE", "title": "Merge the reviewed M1 candidate", - "state": "planned", - "evidence": [], - "next_action": "Merge only after human review and Linux verification both pass." + "state": "done", + "evidence": [ + { + "label": "M1 merge commit", + "uri": "https://github.com/brendohxd/Project-Relay/commit/65a7d36ea2d9d29271b970ba883baa9c5a5a2b34" + } + ] } ], "summary": { - "state": "planned", + "state": "complete", "counts": { - "done": 4, + "done": 6, "in_progress": 0, - "blocked": 1, - "planned": 1, + "blocked": 0, + "planned": 0, "deferred": 0 }, "total": 6, "actionable": 6, - "completed": 4, - "blockers": [ - { - "id": "M1-HUMAN-REVIEW", - "title": "Human review of draft PR #1", - "state": "blocked", - "evidence": [], - "blocker": { - "kind": "human-action", - "summary": "Draft PR #1 has no recorded human review decision." - }, - "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." - } - ] + "completed": 6, + "blockers": [] } }, { @@ -518,30 +510,18 @@ "summary": { "state": "planned", "counts": { - "done": 13, + "done": 15, "in_progress": 0, - "blocked": 1, - "planned": 14, + "blocked": 0, + "planned": 13, "deferred": 3 }, "total": 31, "actionable": 28, - "completed": 13, - "blockers": [ - { - "id": "M1-HUMAN-REVIEW", - "title": "Human review of draft PR #1", - "state": "blocked", - "evidence": [], - "blocker": { - "kind": "human-action", - "summary": "Draft PR #1 has no recorded human review decision." - }, - "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." - } - ], - "current_milestone": "M1", - "current_milestone_title": "Local vertical slice" + "completed": 15, + "blockers": [], + "current_milestone": "M2", + "current_milestone_title": "GitHub collaboration adapter" } }, "summary": { diff --git a/project/status.json b/project/status.json index ff1b7f3..58a6542 100644 --- a/project/status.json +++ b/project/status.json @@ -93,13 +93,13 @@ { "id": "M1-HUMAN-REVIEW", "title": "Human review of draft PR #1", - "state": "blocked", - "evidence": [], - "blocker": { - "kind": "human-action", - "summary": "Draft PR #1 has no recorded human review decision." - }, - "next_action": "Review the protocol behavior, limitations, and public boundary in draft PR #1." + "state": "done", + "evidence": [ + { + "label": "Approved and merged M1 pull request", + "uri": "https://github.com/brendohxd/Project-Relay/pull/1" + } + ] }, { "id": "M1-LINUX-VERIFY", @@ -115,9 +115,13 @@ { "id": "M1-MERGE", "title": "Merge the reviewed M1 candidate", - "state": "planned", - "evidence": [], - "next_action": "Merge only after human review and Linux verification both pass." + "state": "done", + "evidence": [ + { + "label": "M1 merge commit", + "uri": "https://github.com/brendohxd/Project-Relay/commit/65a7d36ea2d9d29271b970ba883baa9c5a5a2b34" + } + ] } ] }, diff --git a/test/mcp.test.js b/test/mcp.test.js index 9f69596..6f0bafa 100644 --- a/test/mcp.test.js +++ b/test/mcp.test.js @@ -21,12 +21,12 @@ test("MCP exposes the M1 task resource and bounded review prompt", async () => { assert.ok(resources.resources.some((item) => item.uri === "relay://project/status")); const projectResource = await client.readResource({ uri: "relay://project/status" }); const projectStatus = JSON.parse(projectResource.contents[0].text); - assert.equal(projectStatus.summary.current_milestone, "M1"); + assert.equal(projectStatus.summary.current_milestone, "M2"); assert.equal(projectStatus.pilots[0].visibility, "operational-summary-only"); const statusTool = await client.callTool({ name: "relay_get_project_status", arguments: {} }); assert.equal(statusTool.isError, false); - assert.equal(statusTool.structuredContent.summary.current_milestone, "M1"); + assert.equal(statusTool.structuredContent.summary.current_milestone, "M2"); const templates = await client.listResourceTemplates(); assert.ok( templates.resourceTemplates.some( diff --git a/test/project-status.test.js b/test/project-status.test.js index 5a629ce..7608c57 100644 --- a/test/project-status.test.js +++ b/test/project-status.test.js @@ -14,8 +14,8 @@ const source = JSON.parse(await readFile("project/status.json", "utf8")); test("canonical project status validates and identifies the current milestone", async () => { const status = await loadProjectStatus(); assert.equal(status.milestones[0].summary.state, "complete"); - assert.equal(status.summary.current_milestone, "M1"); - assert.equal(status.summary.counts.blocked, 1); + assert.equal(status.summary.current_milestone, "M2"); + assert.equal(status.summary.counts.blocked, 0); }); test("generated status preserves the ITSM publication boundary", () => { @@ -27,7 +27,7 @@ test("generated status preserves the ITSM publication boundary", () => { test("semantic validation rejects unsupported state claims and duplicate ids", () => { const invalid = structuredClone(source); invalid.milestones[0].items[0].evidence = []; - invalid.milestones[1].items[3].blocker = undefined; + invalid.pilots[0].items[3].blocker = undefined; invalid.milestones[2].items[2].id = invalid.milestones[0].items[1].id; invalid.milestones[5].items[0].rationale = undefined; const messages = validateStatusSemantics(invalid).map(({ message }) => message);