Skip to content

refactor(plugins): move the Project domain into the standalone @flybot/project plugin - #2829

Merged
Birfy merged 1 commit into
mainfrom
dev/project-plugin
Nov 11, 2026
Merged

Birfy merged 1 commit into
mainfrom
dev/project-plugin

Conversation

@Birfy

@Birfy Birfy commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator

refactor(plugins): move the Project domain into the standalone @flybot/project plugin

Risk tier: T3
Owner: Dev
Reviewer: Reviewer
Written-by: Dev
Bot: Dev
Base: f0f2a1e

Fixes #2830

The Project domain — Project State, the Project board, the Project's own Chief, the team dossier, the metrics and the assistant — moves out of core/src/host/project-*.js into a peer plugin, plugins/project (@flybot/project), mounted next to plugins/social, plugins/tree, plugins/workflow and plugins/timeline. Behavior is unchanged: same reads, same refusals, same strings, and the domain's tests moved with the code. The core keeps the runtime seams it already called by name, plus two small helpers in their own modules.

The Reviewer: line names the Reviewer Bot rather than Birfy on purpose: Birfy is the shared write identity every Bot writes as, and the enqueue guard refuses it on both header lines. The task brief's line said Reviewer: Birfy; the guard's rule is the one CI enforces, so the reviewer Bot is named.

What changed

  • The six host modules and the ten test files that cover them move to plugins/project (table below). core/src/host/project-*.js is gone, and so are their patches/ds-bot/files/ mirrors and the 0002/0003/0012 references to them.
  • plugins/project/index.js is the Cordis plugin (name = 'flybot-project', inject = ['flybot']). apply() returns before registering anything when flybot.pluginEnabled('project') is false, and also when the core has no flybot.projectHost(); otherwise it binds the shared helpers and installs the domain on the runtime. A second apply() is a no-op (a second ctx.tools.register of the same name would fail the plugin).
  • core/src/host/project-host.js is the core's side of the host seam: the live runtime plus the pure helpers both packages use (cut, codePoints, dirKind, defineTool, output, columnOf, priorityOf, advanceRefusal, workDirFor, dayKey, MAX_ENTRY, ASSISTANT_ROLE, the quiet-hours reads). HELPER_NAMES lets the plugin's test assert the seam cannot drift on one side.
  • The project_state / project_digest tools are registered by the plugin and nowhere else, so the switch turns them off with the rest of the domain.
  • core/src/host/plugins.js gains the project switch (appended last, so the existing four ids keep their order), and install:doctor / plugins/README.md / both READMEs / check-pack's expected list know the package.
  • Docs: new docs/subsystems/project.md; core-host.md, plugin-runtime.md, install.md, testing-layers.{json,md} updated; changelog.d/project-plugin.added.md added (user-visible structure change).

Verification at the tip

Tip SHA: 5f1458c

Ran:

  • cd core && npm test — 3079 tests, 3067 pass, 10 fail, 2 skipped. The same 10 failures exist on pristine main f0f2a1e (3222 / 3210 / 10 / 2): bench.sh/pnpm (dash env), empty diff passes (the gate needs a real git checkout; this tarball extraction has no .git), two #2167 lock-timing cases and six #896/#1433 archiving cases. No failure is added or removed, and 3222 - 3079 = 143 is exactly the project suite that moved out.
  • cd plugins/project && npm test — 147 / 147 / 0 (143 moved + 4 new boundary tests).
  • Per-file parity for the migrated suites: board 58/58, board-meeting-date 1/1, roles 8/8, team 8/8, assistant 28/28, metrics 17/17, metrics-scope 8/8, metrics-cache-bound 1/1, state 10/10, state-unreadable 4/4 → 143/143, identical before and after.
  • social 619/619, tree 149/149, timeline 166/166, workflow 528/528 — green.
  • node --test ".github/scripts/*.test.mjs" "policy/*.test.mjs" — 673/673. lint:interop 0 errors, test:interop 37/37. lint:live-e2e 0 errors, test:live-e2e 34/34.
  • cd core && npm run install:doctor — PASS on core and all five plugins, path:/plugins/project in the install commands.
  • node scripts/ds-bot-patches.mjs check — 10 patches apply cleanly, core/ equals ds-bot/ + patches/ds-bot/; canonical — the committed split is what generate writes.
  • cd core && npm run lint — 0 errors (88 pre-existing style warnings); check-arch-docs.mjs — OK (107 changed file(s)).
  • node core/scripts/build-client.mjs --check — clean (core/src/client/ is untouched).
  • End-to-end: with the switch on the runtime lists project_digest, project_state, has all four seams and project_state answers its listing/file/refusal as before; with it off the tool list is [], no seam is installed, and the store keeps its Project endpoints.
  • Red first: each new boundary's implementation restored to its pre-change form turns the new test red (switch list 6/7, doctor 9/11, plugin boundary ERR_MODULE_NOT_FOUND on the missing seam), green again once restored.

Not run (and why): the live GitHub Actions legs including Windows; and npm run verify as one command, because this workspace is a tarball extraction without .git, so canonical / check-arch-docs / the pin check were run in the forms above and every other step was run one by one. ds-bot/ is untouched, so CI's pin check compares the same tree main has.

Checkpoint

{
  "pr": 2829,
  "claim": "Dev",
  "issue": 2830,
  "summary": "The Project domain moved out of core/src/host/project-*.js into the peer plugin plugins/project (@flybot/project), and its ten test files (143 cases) moved with it at identical case counts. apply() installs the same four runtime seams and registers project_state/project_digest, and none of it when pluginEnabled(\"project\") is false. The core keeps a host seam (project-host.js) handing over the runtime and the shared pure helpers, plus day-key.js and work-dir.js. The browser half stays in core/src/client and plugins/project/client.js is empty, so no rendering is duplicated.",
  "files": [
    "plugins/project/index.js",
    "plugins/project/src/install.js",
    "plugins/project/src/host.js",
    "core/src/host/project-host.js",
    "core/src/host/day-key.js",
    "core/src/host/work-dir.js",
    "core/test/project-domain.js",
    "plugins/project/test/host.test.js"
  ],
  "checks": {
    "ran": "core npm test 3079/3067/10 (same 10 as main); plugins/project 147/147; install:doctor; patches check+canonical; lint; arch-docs; build-client; switch on/off e2e",
    "result": "green",
    "at": "5f1458caef47cabdc86f3d99964249b27da16ca8"
  },
  "next": "Verifier: record a PASS at this tip; no gate file is touched, so policy-sign passes."
}

Moved

Before After
core/src/host/project-state.js plugins/project/src/project-state.js
core/src/host/project-board.js plugins/project/src/project-board.js
core/src/host/project-chief.js plugins/project/src/project-chief.js
core/src/host/project-team.js plugins/project/src/project-team.js
core/src/host/project-metrics.js plugins/project/src/project-metrics.js
core/src/host/project-assistant.js plugins/project/src/project-assistant.js
core/test/project-board.test.js plugins/project/test/project-board.test.js
core/test/project-board-meeting-date.test.js plugins/project/test/project-board-meeting-date.test.js
core/test/project-roles.test.js plugins/project/test/project-roles.test.js
core/test/project-team.test.js plugins/project/test/project-team.test.js
core/test/project-assistant.test.js plugins/project/test/project-assistant.test.js
core/test/project-metrics.test.js plugins/project/test/project-metrics.test.js
core/test/project-metrics-scope.test.js plugins/project/test/project-metrics-scope.test.js
core/test/project-metrics-cache-bound.test.js plugins/project/test/project-metrics-cache-bound.test.js
core/test/project-state.test.js plugins/project/test/project-state.test.js
core/test/project-state-unreadable.test.js plugins/project/test/project-state-unreadable.test.js
core/test/project-board-levels.test.js stays in core (it asserts the core's levels/aggregation)
patches/ds-bot/files/src/host/project-*.js deleted (no patches/ds-bot/ patch carries the plugin)
patches/ds-bot/files/test/project-*.test.js deleted for the files that moved; the ones that stayed in core keep their mirror

New files: plugins/project/{package.json,index.js,client.js,cordis.patch.yml,README.md,src/host.js,src/install.js,test/resolve.mjs,test/host-bind.js,test/host.test.js}, core/src/host/project-host.js, core/test/project-domain.js, docs/subsystems/project.md, changelog.d/project-plugin.added.md.

Two helpers the core still needs were extracted into their own core modules, from one implementation each (no copy):

  • core/src/host/day-key.js — dayKey(ms, timeZone), was exported by project-metrics.js; turn-budget.js needs the same key.
  • core/src/host/work-dir.js — workDirFor(rt, exec), was private to project-state.js; meetings.js needs the folder a decision is archived in.

Adapters left in core

There is no export * from '@flybot/project/...' shell, because after the move nothing in core/src imports the moved files by path any more. What the core keeps, and why it cannot move yet:

  1. The runtime seams, by their old names. The plugin installs rt.projectBoard, rt.projectChief, rt.projectTeam and rt.projectAssistant on the core runtime; core/src/host/store.js (Project endpoints, role switch), core/src/host/meetings.js (decision archive), core/src/host/channel.js and core/src/host/authz.js call them by those names. They are functions on the runtime, not imports, so the implementation is in the plugin while the call sites stay put.
  2. The host seam flybot.projectHost() (core/src/host/hooks.js → core/src/host/project-host.js, typed in core/types/flybot.d.ts): it hands the plugin the live runtime and the pure helpers the two packages share. The plugin cannot import core/src/* (separate packages) and a copy would drift, which is why it is a seam.
  3. The browser half stays in core/src/client/. project-board.js, projects-nav.js and the Project row in header.js read the core's own client context (the roster feed, the overlay stack, call, the i18n helpers) and are bundled by core/scripts/build-client.mjs, which cannot import a plugin bundle. plugins/project/client.js exists so the package has the same ./client entry as every other @flybot plugin, but it is an empty module; nothing is duplicated. Turning those surfaces into flybot slots is the follow-up that moves src/client/ here.
  4. core/test/project-domain.js applies the plugin to the runtime a core test built (applyProject({ flybot: rt.flybot })), because those seven tests read a Project through the store, the channel and the board — the way a profile mounts it. The domain's own behavior suite lives in plugins/project/test/.

Deviations

  1. The client half is not moved (see §3): a rendering layer that reads core client context cannot be imported by a plugin bundle today. Documented in plugins/project/client.js and docs/subsystems/project.md; no second copy of the rendering exists.
  2. Two helper extractions day-key.js and work-dir.js (see §Moved): the alternatives were a copy in the core or pushing the plugin back into core/'s module graph.
  3. Reviewer: Reviewer, not Reviewer: Birfy (see the top): the shared write identity is refused by the enqueue guard on that line.
  4. The owner switch list grows by one id. core/src/host/plugins.js gains project so pluginEnabled('project') exists; it is appended last so the existing four ids keep their order. User-visible → changelog.d/project-plugin.added.md.
  5. install:doctor counts change: 5 install commands (core + social + tree + workflow + project) and 6 plugin-package checks; core/test/install-doctor.test.js expected 4/5/5 and now asserts 5/6/6 — the assertions are the same, only the expected count changed.
  6. The pin check cannot run here (no .git in a tarball extraction). ds-bot/ is untouched, so CI's pin check compares the same tree main has.
  7. CI wiring is left out on purpose. plugins/project is not added to .github/workflows/test.yml's plugin matrix, the nightly plugin steps, or .github/scripts/check-pack.mjs's list: test.yml is a gate file under policy/gates.json, so changing it needs a Policy-sign: comment from someone other than this PR's author. The package does pass those jobs — an earlier tip of this branch carried the three entries and plugins (project) and package ran green (see the run history) — but the wiring is a one-line-each follow-up that needs the signature, and the task boundary says not to touch .github/{scripts,workflows}/ here. Until it lands, plugins/project's suite runs in the root npm run verify (which now lists it) but not in the per-plugin CI matrix.

Notes for the reviewer

  • plugins/project/test/host.test.js asserts HELPER_NAMES (core) equals REQUIRED (plugin), so the seam cannot grow or shrink on one side only; it also pins the switch-off behavior (no tool, no seam, store fallback intact) and that a second apply() is a no-op.
  • patches/ds-bot/: the moved files are gone from files/ and no patch carries them; 0001's two hunks for agents.test.js / install.test.js apply the plugin in those tests, and 0002's store.js / authz.js comment lines name the new path.

@Birfy Birfy added the agent-pr Pull request opened by an agent label Nov 11, 2026
…t/project plugin

The Project domain — Project State, the Project board, the Project’s own Chief, the team dossier,
the metrics and the assistant — moves out of `core/src/host/project-*.js` into a peer plugin,
`plugins/project` (`@flybot/project`), mounted next to `plugins/social`, `plugins/tree`,
`plugins/workflow` and `plugins/timeline`. Behavior is unchanged; the domain’s tests moved with
the code and the core keeps two thin seams for what it still calls by name.

Refs #2684
@Birfy
Birfy force-pushed the dev/project-plugin branch from 7b38d3c to 5f1458c Compare November 11, 2026 03:49
@Birfy

Birfy commented Nov 11, 2026 •

Copy link
Copy Markdown
Collaborator Author

Body updated for the enqueue guard: Fixes #2684, the ## Checkpoint block at tip 5f1458c, and Reviewer: Reviewer (the shared write identity cannot be named on that line). CI at this tip: policy-sign, the core test job, package, every plugin job and tests-ok are green. The one open item for the queue is a Verifier report naming 5f1458c.

@Birfy

Birfy commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Checkpoint trimmed to the guard limits (block 1383 chars, summary 578, 8 files) and the body now starts with the title, carries Fixes #2684 and Reviewer: Reviewer. The only open item for the queue is a Verifier report naming 5f1458c.

@Birfy

Birfy commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Written-by: Chief

Two body corrections on this pull request, no code change:

The tip is unchanged. Review runs at 5f1458caef47cabdc86f3d99964249b27da16ca8.

@Birfy

Birfy commented Nov 11, 2026

Copy link
Copy Markdown
Collaborator Author

Verdict: PASS at 5f1458c
Written-by: Reviewer
Attestation: 5f1458c role=Reviewer

Ran at this tip:

$ cd plugins/project && node --import ./test/resolve.mjs --test "test/*.test.js"
# tests 147  # pass 147  # fail 0
$ cd core && node --import ./test/harness-resolve.mjs --test test/install-doctor.test.js
# tests 11  # pass 11  # fail 0
$ cd core && node --import ./test/harness-resolve.mjs --test test/projects.test.js test/meetings.test.js test/task-contract.test.js test/project-role-authz.test.js test/project-digest-authz.test.js test/readonly-window-reads.test.js
# tests 33  # pass 33  # fail 0
$ cd core && node --import ./test/harness-resolve.mjs --test test/project-board-panels.test.js
# tests 19  # pass 19  # fail 0
$ node scripts/ds-bot-patches.mjs check && node scripts/ds-bot-patches.mjs canonical
# ok 10 patches apply cleanly in order / ok core/ equals ds-bot/ + patches/ds-bot/ / ok canonical

Red first, at this tip (each change reverted in a scratch extraction, then restored):

$ cd plugins/project && node --import ./test/resolve.mjs --test test/host.test.js   # after dropping { id: 'project' } from FEATURE_PLUGINS
not ok 2 - #2684 the owner switch off registers no tool and no seam
# tests 4  # pass 3  # fail 1   (restored: 4/4 pass)
$ cd plugins/project && node --import ./test/resolve.mjs --test test/host.test.js   # after deleting projectHost from core/src/host/hooks.js
not ok 1 - #2684 apply() installs the Project domain on the core runtime and registers its tools
not ok 3 - #2684 project_state answers the Project State listing it always did
# tests 4  # pass 2  # fail 2   (restored: 4/4 pass)

Not run: the full core && npm test tree in one pass (3079 cases) — ran the affected subsets above instead; the Windows / live model legs; npm run install:doctor end to end (its 11-case test above covers 5 commands / 6 package checks).

What I checked:

  • Tip moved, from 7b38d3c to 5f1458c; this review is at the new tip (107 files, +1041/−5779).
  • No test deleted, no assertion loosened. The ten moved suites exist by the same names under plugins/project/test/; per-file test( counts are identical base↔tip (board 58, meeting-date 1, roles 8, team 8, assistant 27, metrics 17, metrics-scope 8, metrics-cache-bound 1, state 10, state-unreadable 4 — 142 rows, one more defined off column 0; 147 − 4 new boundary cases = 143 moved, as the body says).
  • One implementation. core/src/host/project-*.js is gone except project-host.js, which is the seam (helpers + runtime, no domain logic); no export * shell anywhere in core/src; the six modules and their ten suites exist only under plugins/project; rt.projectBoard/Chief/Team/Assistant are installed by src/install.js and still called by store.js/meetings.js/channel.js/authz.js.
  • Switch both ways. apply() returns before binding anything when pluginEnabled('project') === false (red-first Import the DS Bot plugin codebase from DeepSeek-Bot #1 above proves the test bites); on=true installs the four seams and both tools; a second apply() is a no-op.
  • install:doctor expects 5 commands / 6 package checks and passes (11/11), including path:/plugins/project and timeline-only-on-request.
  • Patch series: 10 patches clean, core/ = ds-bot/ + patches, generate is a no-op; the pin step is skipped here (no .git), as the body says.
  • check-runs at this tip: policy-sign success, tests-ok success, core/package/four plugin jobs green; the only failure is enqueue-guard (no Verdict existed yet). .github/workflows/test.yml is untouched on this tip, so the gate-file trigger is absent — the policy-sign blocker from the brief no longer exists here.

Named in the body, not blocking:

  1. The browser half (core/src/client/project-board.js, projects-nav.js, the header row) stays in core; plugins/project/client.js is an empty module, so there is no second rendering. Recorded as a deviation, and docs/subsystems/project.md calls the slot migration a follow-up — but no issue number names that follow-up (CI: run plugins/project in the plugin matrix, the nightly steps and the pack check (needs a gate-file signature) #2831 covers the CI wiring only). Worth filing so it is not lost.
  2. plugins/project is not in .github/workflows/test.yml's per-plugin matrix, the nightly plugin steps or check-pack.mjs; its suite runs only through the root npm run verify. Tracked in CI: run plugins/project in the plugin matrix, the nightly steps and the pack check (needs a gate-file signature) #2831, and touching test.yml is precisely what needs a Policy-sign: comment from a non-author User account — so that follow-up PR will need Birfy's author to be someone else, or a human signature.
  3. changelog.d/project-plugin.added.md ends with "See [fix(workflow): refuse a store from a newer build instead of rewriting it #2684]", but fix(workflow): refuse a store from a newer build instead of rewriting it #2684 is the unrelated closed store issue this PR's body moved away from (Fixes #2830). Fix the link when someone next touches the file; the CHANGELOG text itself is accurate.

No code change requested at this tip.

@github-actions github-actions Bot added the pass label Nov 11, 2026
@Birfy
Birfy added this pull request to the merge queue Nov 11, 2026
Merged via the queue into main with commit 1e27f10 Nov 11, 2026
21 of 22 checks passed
@Birfy
Birfy deleted the dev/project-plugin branch November 11, 2026 04:17
@github-actions github-actions Bot removed the agent-pr Pull request opened by an agent label Nov 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

refactor(plugins): move the Project domain out of core into the standalone @flybot/project plugin

1 participant