@inharness-ai/claude4spec 1.0.14 → 1.0.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/dist/bin/c4s/args.d.ts +2 -0
- package/dist/bin/c4s/args.js +3 -0
- package/dist/bin/c4s/args.js.map +1 -1
- package/dist/bin/c4s/commands/ask.js +2 -0
- package/dist/bin/c4s/commands/ask.js.map +1 -1
- package/dist/bin/c4s/commands/catalog.js +1 -1
- package/dist/bin/c4s/commands/catalog.js.map +1 -1
- package/dist/bin/c4s/commands/describe.d.ts +2 -0
- package/dist/bin/c4s/commands/describe.js +22 -0
- package/dist/bin/c4s/commands/describe.js.map +1 -0
- package/dist/bin/c4s/commands/find-references.d.ts +13 -0
- package/dist/bin/c4s/commands/find-references.js +81 -0
- package/dist/bin/c4s/commands/find-references.js.map +1 -0
- package/dist/bin/c4s/context.d.ts +4 -3
- package/dist/bin/c4s/context.js +17 -6
- package/dist/bin/c4s/context.js.map +1 -1
- package/dist/bin/c4s/errors.d.ts +1 -1
- package/dist/bin/c4s/errors.js.map +1 -1
- package/dist/bin/c4s/type-validation.d.ts +3 -0
- package/dist/bin/c4s/type-validation.js +14 -0
- package/dist/bin/c4s/type-validation.js.map +1 -1
- package/dist/bin/c4s-mcp.d.ts +1 -1
- package/dist/bin/c4s-mcp.js +23 -12
- package/dist/bin/c4s-mcp.js.map +1 -1
- package/dist/bin/c4s.js +20 -3
- package/dist/bin/c4s.js.map +1 -1
- package/dist/bin/claude4spec.js +122 -74
- package/dist/bin/claude4spec.js.map +1 -1
- package/dist/client/assets/{arc-E0e7X_zn.js → arc-DrA6qHTw.js} +1 -1
- package/dist/client/assets/{architectureDiagram-3BPJPVTR-BzlqOVZv.js → architectureDiagram-3BPJPVTR-TEnwTtVX.js} +1 -1
- package/dist/client/assets/{blockDiagram-GPEHLZMM-BIQcApN7.js → blockDiagram-GPEHLZMM-txRnQm5w.js} +1 -1
- package/dist/client/assets/{c4Diagram-AAUBKEIU-CDU0V1yk.js → c4Diagram-AAUBKEIU-ad8X3Q8n.js} +1 -1
- package/dist/client/assets/channel-zmRfuI3H.js +1 -0
- package/dist/client/assets/{chunk-2J33WTMH-PtDSwC5f.js → chunk-2J33WTMH-Cp08Xa7f.js} +1 -1
- package/dist/client/assets/{chunk-4BX2VUAB-DTUPWrSO.js → chunk-4BX2VUAB-Dc_0On6j.js} +1 -1
- package/dist/client/assets/{chunk-55IACEB6-Cpd59xZT.js → chunk-55IACEB6-8d1qQ5z6.js} +1 -1
- package/dist/client/assets/{chunk-727SXJPM-C4DOvpJT.js → chunk-727SXJPM-TZenviWx.js} +1 -1
- package/dist/client/assets/{chunk-AQP2D5EJ-CtjeHPBB.js → chunk-AQP2D5EJ-DKRRWy-J.js} +1 -1
- package/dist/client/assets/{chunk-FMBD7UC4-CZA3OvVr.js → chunk-FMBD7UC4-DLEErJWK.js} +1 -1
- package/dist/client/assets/{chunk-ND2GUHAM-Cbx6nQHm.js → chunk-ND2GUHAM-pKGgwz_0.js} +1 -1
- package/dist/client/assets/{chunk-QZHKN3VN-3DunELI_.js → chunk-QZHKN3VN-DVcNz8ng.js} +1 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-B9RFgPu-.js +1 -0
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-B9RFgPu-.js +1 -0
- package/dist/client/assets/{cose-bilkent-S5V4N54A-CKYJnuL7.js → cose-bilkent-S5V4N54A-DZejbxEG.js} +1 -1
- package/dist/client/assets/{dagre-BM42HDAG-BrvRwrr_.js → dagre-BM42HDAG-DrPrkFcW.js} +1 -1
- package/dist/client/assets/{diagram-2AECGRRQ-BaVLuzcD.js → diagram-2AECGRRQ-Dpn8TJa2.js} +1 -1
- package/dist/client/assets/{diagram-5GNKFQAL-9Cu7Wa7J.js → diagram-5GNKFQAL-BxkzEc_t.js} +1 -1
- package/dist/client/assets/{diagram-KO2AKTUF-XKiCVdjO.js → diagram-KO2AKTUF-KG0o4LQ7.js} +1 -1
- package/dist/client/assets/{diagram-LMA3HP47-DerecU5v.js → diagram-LMA3HP47-CJ-AjQPt.js} +1 -1
- package/dist/client/assets/{diagram-OG6HWLK6-BWcREvZ9.js → diagram-OG6HWLK6-D_FG9cDJ.js} +1 -1
- package/dist/client/assets/{erDiagram-TEJ5UH35-DU3BBQJY.js → erDiagram-TEJ5UH35-DllhUlzY.js} +1 -1
- package/dist/client/assets/{flowDiagram-I6XJVG4X-DXO_IJeR.js → flowDiagram-I6XJVG4X-Bg3OotXu.js} +1 -1
- package/dist/client/assets/{ganttDiagram-6RSMTGT7-CXiFZEPx.js → ganttDiagram-6RSMTGT7-BUpUL2Cj.js} +1 -1
- package/dist/client/assets/{gitGraphDiagram-PVQCEYII-DHewdln-.js → gitGraphDiagram-PVQCEYII-DMRhyWth.js} +1 -1
- package/dist/client/assets/{index-DT5g2AtL.js → index-BB2RmhGR.js} +1 -1
- package/dist/client/assets/index-BXzOTCgN.css +1 -0
- package/dist/client/assets/index-DRFbyGvK.js +788 -0
- package/dist/client/assets/{index-DD0GkjkG.js → index-Daewu-VD.js} +1 -1
- package/dist/client/assets/{infoDiagram-5YYISTIA-CN40PAGW.js → infoDiagram-5YYISTIA-Bq9D64Ar.js} +1 -1
- package/dist/client/assets/{ishikawaDiagram-YF4QCWOH-B8KsyuPk.js → ishikawaDiagram-YF4QCWOH-DIaKHHF-.js} +1 -1
- package/dist/client/assets/{journeyDiagram-JHISSGLW-BlEb3E_P.js → journeyDiagram-JHISSGLW-gQWGLTeZ.js} +1 -1
- package/dist/client/assets/{kanban-definition-UN3LZRKU-B080CGS9.js → kanban-definition-UN3LZRKU-vRgZ5RjE.js} +1 -1
- package/dist/client/assets/{linear-D6Tg-IET.js → linear-B4a11szP.js} +1 -1
- package/dist/client/assets/{mermaid.core-DP18nW3T.js → mermaid.core-JhUbsJFB.js} +4 -4
- package/dist/client/assets/{mindmap-definition-RKZ34NQL-bfo7Qm5I.js → mindmap-definition-RKZ34NQL-ZH-Oz_D-.js} +1 -1
- package/dist/client/assets/{pieDiagram-4H26LBE5-ZsPztPu6.js → pieDiagram-4H26LBE5-6rpNrJR6.js} +1 -1
- package/dist/client/assets/{quadrantDiagram-W4KKPZXB-DKrTleIL.js → quadrantDiagram-W4KKPZXB-DpTKtxEM.js} +1 -1
- package/dist/client/assets/{requirementDiagram-4Y6WPE33-D4YX8ZgG.js → requirementDiagram-4Y6WPE33-DpDvZkrK.js} +1 -1
- package/dist/client/assets/{sankeyDiagram-5OEKKPKP-Bo-xm6Mc.js → sankeyDiagram-5OEKKPKP-KKjWCTYP.js} +1 -1
- package/dist/client/assets/{sequenceDiagram-3UESZ5HK-CozgdKp6.js → sequenceDiagram-3UESZ5HK-BCmXos1U.js} +1 -1
- package/dist/client/assets/{stateDiagram-AJRCARHV-CiK61MwV.js → stateDiagram-AJRCARHV-Xq7jqpMj.js} +1 -1
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-C3mDHsxD.js +1 -0
- package/dist/client/assets/{timeline-definition-PNZ67QCA-CgHoguI7.js → timeline-definition-PNZ67QCA-CMmq8p5q.js} +1 -1
- package/dist/client/assets/{vennDiagram-CIIHVFJN-TY32dxQb.js → vennDiagram-CIIHVFJN-DgK-w2Jv.js} +1 -1
- package/dist/client/assets/{wardley-L42UT6IY-C6Q7VFwH.js → wardley-L42UT6IY-C5CwlFHh.js} +1 -1
- package/dist/client/assets/{wardleyDiagram-YWT4CUSO-nZLnVsQL.js → wardleyDiagram-YWT4CUSO-D47CtDSc.js} +1 -1
- package/dist/client/assets/{xychartDiagram-2RQKCTM6-DHdYRHEr.js → xychartDiagram-2RQKCTM6-C1M_xTVv.js} +1 -1
- package/dist/client/index.html +2 -2
- package/dist/core/ask/run-ask.d.ts +3 -1
- package/dist/core/ask/run-ask.js +75 -54
- package/dist/core/ask/run-ask.js.map +1 -1
- package/dist/core/references/find-references.d.ts +13 -0
- package/dist/core/references/find-references.js +41 -0
- package/dist/core/references/find-references.js.map +1 -0
- package/dist/core/references/index.d.ts +2 -0
- package/dist/core/references/index.js +2 -0
- package/dist/core/references/index.js.map +1 -0
- package/dist/core/references/types.d.ts +45 -0
- package/dist/core/references/types.js +9 -0
- package/dist/core/references/types.js.map +1 -0
- package/dist/core/workspace/resolve.d.ts +29 -0
- package/dist/core/workspace/resolve.js +80 -0
- package/dist/core/workspace/resolve.js.map +1 -0
- package/dist/server/config.d.ts +58 -6
- package/dist/server/config.js +94 -19
- package/dist/server/config.js.map +1 -1
- package/dist/server/core/plugin-host/cli-engine.d.ts +8 -0
- package/dist/server/core/plugin-host/cli-engine.js +16 -0
- package/dist/server/core/plugin-host/cli-engine.js.map +1 -0
- package/dist/server/core/plugin-host/cross-cutting.js +1 -1
- package/dist/server/core/plugin-host/cross-cutting.js.map +1 -1
- package/dist/server/core/plugin-host/entities-router.d.ts +3 -1
- package/dist/server/core/plugin-host/entities-router.js +24 -19
- package/dist/server/core/plugin-host/entities-router.js.map +1 -1
- package/dist/server/core/plugin-host/host.js +4 -9
- package/dist/server/core/plugin-host/host.js.map +1 -1
- package/dist/server/core/plugin-host/project-host.d.ts +38 -0
- package/dist/server/core/plugin-host/project-host.js +118 -0
- package/dist/server/core/plugin-host/project-host.js.map +1 -0
- package/dist/server/core/plugin-host/registry.d.ts +14 -0
- package/dist/server/core/plugin-host/registry.js +26 -0
- package/dist/server/core/plugin-host/registry.js.map +1 -0
- package/dist/server/core/plugin-host/serialization-engine.d.ts +38 -8
- package/dist/server/core/plugin-host/serialization-engine.js +63 -39
- package/dist/server/core/plugin-host/serialization-engine.js.map +1 -1
- package/dist/server/core/plugin-host/types.d.ts +49 -27
- package/dist/server/db/index.d.ts +8 -1
- package/dist/server/db/index.js +12 -3
- package/dist/server/db/index.js.map +1 -1
- package/dist/server/db/migrations/035_m29_slug_identity.sql +248 -0
- package/dist/server/db/readonly.d.ts +8 -3
- package/dist/server/db/readonly.js +7 -4
- package/dist/server/db/readonly.js.map +1 -1
- package/dist/server/domain/raw-entity-reader.d.ts +5 -5
- package/dist/server/domain/raw-entity-reader.js +34 -39
- package/dist/server/domain/raw-entity-reader.js.map +1 -1
- package/dist/server/entities/ac/ac-analysis.service.d.ts +54 -0
- package/dist/server/entities/ac/ac-analysis.service.js +162 -0
- package/dist/server/entities/ac/ac-analysis.service.js.map +1 -0
- package/dist/server/entities/ac/mcp-server.d.ts +10 -2
- package/dist/server/entities/ac/mcp-server.js +34 -3
- package/dist/server/entities/ac/mcp-server.js.map +1 -1
- package/dist/server/entities/ac/plugin.d.ts +3 -1
- package/dist/server/entities/ac/plugin.js +9 -5
- package/dist/server/entities/ac/plugin.js.map +1 -1
- package/dist/server/entities/ac/serializer.js +1 -1
- package/dist/server/entities/ac/serializer.js.map +1 -1
- package/dist/server/entities/ac/services.d.ts +19 -7
- package/dist/server/entities/ac/services.js +65 -42
- package/dist/server/entities/ac/services.js.map +1 -1
- package/dist/server/entities/ac/system-prompt.js +1 -8
- package/dist/server/entities/ac/system-prompt.js.map +1 -1
- package/dist/server/entities/database-table/mcp-server.d.ts +2 -2
- package/dist/server/entities/database-table/mcp-server.js +5 -2
- package/dist/server/entities/database-table/mcp-server.js.map +1 -1
- package/dist/server/entities/database-table/plugin.d.ts +3 -1
- package/dist/server/entities/database-table/plugin.js +6 -5
- package/dist/server/entities/database-table/plugin.js.map +1 -1
- package/dist/server/entities/database-table/routes.d.ts +2 -2
- package/dist/server/entities/database-table/serializer.js +1 -1
- package/dist/server/entities/database-table/serializer.js.map +1 -1
- package/dist/server/entities/database-table/services.d.ts +19 -7
- package/dist/server/entities/database-table/services.js +77 -49
- package/dist/server/entities/database-table/services.js.map +1 -1
- package/dist/server/entities/database-table/system-prompt.js +1 -1
- package/dist/server/entities/database-table/system-prompt.js.map +1 -1
- package/dist/server/entities/dto/mcp-server.d.ts +2 -2
- package/dist/server/entities/dto/mcp-server.js +5 -2
- package/dist/server/entities/dto/mcp-server.js.map +1 -1
- package/dist/server/entities/dto/plugin.d.ts +3 -1
- package/dist/server/entities/dto/plugin.js +6 -5
- package/dist/server/entities/dto/plugin.js.map +1 -1
- package/dist/server/entities/dto/serializer.js +2 -2
- package/dist/server/entities/dto/serializer.js.map +1 -1
- package/dist/server/entities/dto/services.d.ts +19 -7
- package/dist/server/entities/dto/services.js +73 -49
- package/dist/server/entities/dto/services.js.map +1 -1
- package/dist/server/entities/dto/system-prompt.js +1 -1
- package/dist/server/entities/dto/system-prompt.js.map +1 -1
- package/dist/server/entities/endpoint/mcp-server.d.ts +2 -2
- package/dist/server/entities/endpoint/mcp-server.js +5 -2
- package/dist/server/entities/endpoint/mcp-server.js.map +1 -1
- package/dist/server/entities/endpoint/plugin.d.ts +3 -1
- package/dist/server/entities/endpoint/plugin.js +6 -5
- package/dist/server/entities/endpoint/plugin.js.map +1 -1
- package/dist/server/entities/endpoint/serializer.js +4 -4
- package/dist/server/entities/endpoint/serializer.js.map +1 -1
- package/dist/server/entities/endpoint/services.d.ts +21 -9
- package/dist/server/entities/endpoint/services.js +94 -65
- package/dist/server/entities/endpoint/services.js.map +1 -1
- package/dist/server/entities/endpoint/system-prompt.js +1 -1
- package/dist/server/entities/endpoint/system-prompt.js.map +1 -1
- package/dist/server/entities/mutate-opts.d.ts +15 -0
- package/dist/server/entities/mutate-opts.js +2 -0
- package/dist/server/entities/mutate-opts.js.map +1 -0
- package/dist/server/entities/ui-view/mcp-server.d.ts +2 -2
- package/dist/server/entities/ui-view/mcp-server.js +5 -2
- package/dist/server/entities/ui-view/mcp-server.js.map +1 -1
- package/dist/server/entities/ui-view/plugin.d.ts +3 -1
- package/dist/server/entities/ui-view/plugin.js +6 -5
- package/dist/server/entities/ui-view/plugin.js.map +1 -1
- package/dist/server/entities/ui-view/routes.d.ts +2 -2
- package/dist/server/entities/ui-view/serializer.js +1 -1
- package/dist/server/entities/ui-view/serializer.js.map +1 -1
- package/dist/server/entities/ui-view/services.d.ts +19 -7
- package/dist/server/entities/ui-view/services.js +68 -44
- package/dist/server/entities/ui-view/services.js.map +1 -1
- package/dist/server/entities/ui-view/system-prompt.d.ts +0 -5
- package/dist/server/entities/ui-view/system-prompt.js +3 -7
- package/dist/server/entities/ui-view/system-prompt.js.map +1 -1
- package/dist/server/external-skills/brief-implementer-template.d.ts +1 -1
- package/dist/server/external-skills/brief-implementer-template.js +1 -1
- package/dist/server/external-skills/spec-reader-template.d.ts +1 -1
- package/dist/server/external-skills/spec-reader-template.js +3 -2
- package/dist/server/external-skills/spec-reader-template.js.map +1 -1
- package/dist/server/fs/entities-watcher.d.ts +31 -0
- package/dist/server/fs/entities-watcher.js +68 -0
- package/dist/server/fs/entities-watcher.js.map +1 -0
- package/dist/server/fs/watcher.d.ts +2 -2
- package/dist/server/fs/watcher.js +7 -1
- package/dist/server/fs/watcher.js.map +1 -1
- package/dist/server/index.d.ts +11 -1
- package/dist/server/index.js +134 -639
- package/dist/server/index.js.map +1 -1
- package/dist/server/mcp/c4s-reader.d.ts +2 -2
- package/dist/server/mcp/c4s-reader.js +41 -4
- package/dist/server/mcp/c4s-reader.js.map +1 -1
- package/dist/server/mcp/c4s-tools.js +2 -0
- package/dist/server/mcp/c4s-tools.js.map +1 -1
- package/dist/server/mcp/ensure-mcp-json.d.ts +9 -2
- package/dist/server/mcp/ensure-mcp-json.js +18 -4
- package/dist/server/mcp/ensure-mcp-json.js.map +1 -1
- package/dist/server/mcp/reference-tools.d.ts +8 -2
- package/dist/server/mcp/reference-tools.js +52 -179
- package/dist/server/mcp/reference-tools.js.map +1 -1
- package/dist/server/mcp/release-tools/index.d.ts +2 -2
- package/dist/server/routes/agent-turn.d.ts +41 -7
- package/dist/server/routes/agent-turn.js +84 -20
- package/dist/server/routes/agent-turn.js.map +1 -1
- package/dist/server/routes/chat.js +47 -15
- package/dist/server/routes/chat.js.map +1 -1
- package/dist/server/routes/config.d.ts +24 -0
- package/dist/server/routes/config.js +231 -0
- package/dist/server/routes/config.js.map +1 -0
- package/dist/server/routes/references.d.ts +2 -1
- package/dist/server/routes/references.js +4 -5
- package/dist/server/routes/references.js.map +1 -1
- package/dist/server/routes/releases.d.ts +2 -2
- package/dist/server/routes/static.d.ts +11 -0
- package/dist/server/routes/static.js +44 -0
- package/dist/server/routes/static.js.map +1 -0
- package/dist/server/routes/threads.js +14 -2
- package/dist/server/routes/threads.js.map +1 -1
- package/dist/server/serialization/auto-schema.d.ts +2 -1
- package/dist/server/serialization/auto-schema.js +5 -6
- package/dist/server/serialization/auto-schema.js.map +1 -1
- package/dist/server/serialization/registerAll.d.ts +7 -6
- package/dist/server/serialization/registerAll.js +17 -6
- package/dist/server/serialization/registerAll.js.map +1 -1
- package/dist/server/serialization/resolve-page.d.ts +2 -2
- package/dist/server/serialization/serializers/section.d.ts +6 -1
- package/dist/server/serialization/serializers/section.js +6 -3
- package/dist/server/serialization/serializers/section.js.map +1 -1
- package/dist/server/services/brief.d.ts +2 -2
- package/dist/server/services/chat-context.d.ts +24 -0
- package/dist/server/services/chat-context.js +70 -13
- package/dist/server/services/chat-context.js.map +1 -1
- package/dist/server/services/entity-indexer.d.ts +42 -0
- package/dist/server/services/entity-indexer.js +179 -0
- package/dist/server/services/entity-indexer.js.map +1 -0
- package/dist/server/services/entity-store.d.ts +68 -0
- package/dist/server/services/entity-store.js +162 -0
- package/dist/server/services/entity-store.js.map +1 -0
- package/dist/server/services/entity-writer.d.ts +12 -1
- package/dist/server/services/entity-writer.js +22 -11
- package/dist/server/services/entity-writer.js.map +1 -1
- package/dist/server/services/git.js +6 -1
- package/dist/server/services/git.js.map +1 -1
- package/dist/server/services/pages-frontmatter-indexer.d.ts +2 -2
- package/dist/server/services/pages-link-indexer.d.ts +2 -2
- package/dist/server/services/pages.d.ts +2 -0
- package/dist/server/services/pages.js +24 -9
- package/dist/server/services/pages.js.map +1 -1
- package/dist/server/services/plan.d.ts +2 -2
- package/dist/server/services/references.d.ts +25 -0
- package/dist/server/services/references.js +102 -21
- package/dist/server/services/references.js.map +1 -1
- package/dist/server/services/release-bundle.d.ts +0 -1
- package/dist/server/services/release-bundle.js +0 -1
- package/dist/server/services/release-bundle.js.map +1 -1
- package/dist/server/services/release-import.d.ts +2 -0
- package/dist/server/services/release-import.js +6 -4
- package/dist/server/services/release-import.js.map +1 -1
- package/dist/server/services/release.d.ts +10 -10
- package/dist/server/services/release.js +32 -63
- package/dist/server/services/release.js.map +1 -1
- package/dist/server/services/section-indexer.d.ts +4 -3
- package/dist/server/services/section-indexer.js +8 -9
- package/dist/server/services/section-indexer.js.map +1 -1
- package/dist/server/services/skill-registry.d.ts +28 -1
- package/dist/server/services/skill-registry.js +51 -10
- package/dist/server/services/skill-registry.js.map +1 -1
- package/dist/server/services/slug.js +6 -0
- package/dist/server/services/slug.js.map +1 -1
- package/dist/server/services/static-html.d.ts +24 -0
- package/dist/server/services/static-html.js +49 -0
- package/dist/server/services/static-html.js.map +1 -0
- package/dist/server/services/tags.d.ts +13 -3
- package/dist/server/services/tags.js +65 -26
- package/dist/server/services/tags.js.map +1 -1
- package/dist/server/services/todos-indexer.d.ts +2 -2
- package/dist/server/services/versions.d.ts +7 -7
- package/dist/server/services/versions.js +34 -37
- package/dist/server/services/versions.js.map +1 -1
- package/dist/server/workspace/bootstrap.d.ts +42 -0
- package/dist/server/workspace/bootstrap.js +73 -0
- package/dist/server/workspace/bootstrap.js.map +1 -0
- package/dist/server/workspace/context-cache.d.ts +48 -0
- package/dist/server/workspace/context-cache.js +147 -0
- package/dist/server/workspace/context-cache.js.map +1 -0
- package/dist/server/workspace/db-migration.d.ts +11 -0
- package/dist/server/workspace/db-migration.js +44 -0
- package/dist/server/workspace/db-migration.js.map +1 -0
- package/dist/server/workspace/middleware.d.ts +15 -0
- package/dist/server/workspace/middleware.js +36 -0
- package/dist/server/workspace/middleware.js.map +1 -0
- package/dist/server/workspace/project-context.d.ts +65 -0
- package/dist/server/workspace/project-context.js +655 -0
- package/dist/server/workspace/project-context.js.map +1 -0
- package/dist/server/workspace/project-id.d.ts +7 -0
- package/dist/server/workspace/project-id.js +11 -0
- package/dist/server/workspace/project-id.js.map +1 -0
- package/dist/server/workspace/registry.d.ts +58 -0
- package/dist/server/workspace/registry.js +264 -0
- package/dist/server/workspace/registry.js.map +1 -0
- package/dist/server/workspace/routes.d.ts +21 -0
- package/dist/server/workspace/routes.js +181 -0
- package/dist/server/workspace/routes.js.map +1 -0
- package/dist/server/workspace/types.d.ts +30 -0
- package/dist/server/workspace/types.js +6 -0
- package/dist/server/workspace/types.js.map +1 -0
- package/dist/server/ws/gateway.d.ts +12 -2
- package/dist/server/ws/gateway.js +51 -9
- package/dist/server/ws/gateway.js.map +1 -1
- package/dist/server/ws/project-emitter.d.ts +17 -0
- package/dist/server/ws/project-emitter.js +12 -0
- package/dist/server/ws/project-emitter.js.map +1 -0
- package/dist/shared/entities.d.ts +2 -2
- package/dist/shared/languages.d.ts +17 -0
- package/dist/shared/languages.js +31 -0
- package/dist/shared/languages.js.map +1 -0
- package/dist/shared/types.d.ts +11 -0
- package/dist/shared/xml-tags.d.ts +8 -0
- package/dist/shared/xml-tags.js +24 -0
- package/dist/shared/xml-tags.js.map +1 -1
- package/package.json +10 -3
- package/dist/client/assets/channel-BqjILLUr.js +0 -1
- package/dist/client/assets/classDiagram-4FO5ZUOK-LjT84-kK.js +0 -1
- package/dist/client/assets/classDiagram-v2-Q7XG4LA2-LjT84-kK.js +0 -1
- package/dist/client/assets/index-BTXWIk4x.css +0 -1
- package/dist/client/assets/index-DcIG_8JB.js +0 -760
- package/dist/client/assets/stateDiagram-v2-BHNVJYJU-C6-8M5Sb.js +0 -1
package/dist/server/index.js
CHANGED
|
@@ -4,64 +4,21 @@ import path from 'node:path';
|
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import fs from 'node:fs';
|
|
6
6
|
import { createHash } from 'node:crypto';
|
|
7
|
-
import { readConfig, writeConfig } from './config.js';
|
|
8
|
-
import { openDb } from './db/index.js';
|
|
9
|
-
import { PagesService } from './services/pages.js';
|
|
10
|
-
import { pagesRouter } from './routes/pages.js';
|
|
11
|
-
import { tagsRouter } from './routes/tags.js';
|
|
12
|
-
import { entitiesRouter } from './core/plugin-host/entities-router.js';
|
|
13
|
-
import { referencesRouter } from './routes/references.js';
|
|
14
|
-
import { TagsService, DomainError } from './services/tags.js';
|
|
15
|
-
import { VersionService } from './services/versions.js';
|
|
16
|
-
import { ReferencesService } from './services/references.js';
|
|
17
|
-
import { ChatService } from './services/chat.js';
|
|
18
|
-
import { SectionsService } from './services/sections.js';
|
|
19
|
-
import { registerExtensionReferenceType } from '../shared/reference-extensions.js';
|
|
20
|
-
import { PlanService } from './services/plan.js';
|
|
21
|
-
import { plansRouter } from './routes/plans.js';
|
|
22
|
-
import { BriefService } from './services/brief.js';
|
|
23
|
-
import { briefsRouter } from './routes/briefs.js';
|
|
24
|
-
import { PatchService } from './services/patch.js';
|
|
25
|
-
import { patchesRouter } from './routes/patches.js';
|
|
26
|
-
import { RemoteAuthService } from './services/remote-auth.js';
|
|
27
|
-
import { RemoteHttpClient, assertRemoteApiReachable } from './services/remote-http-client.js';
|
|
28
|
-
import { remoteAccountRouter } from './routes/remote-account.js';
|
|
29
|
-
import { remoteProjectRouter } from './routes/remote-project.js';
|
|
30
|
-
import { PagesFrontmatterIndexer } from './services/pages-frontmatter-indexer.js';
|
|
31
|
-
import { SectionIndexerService } from './services/section-indexer.js';
|
|
32
|
-
import { TodosIndexerService } from './services/todos-indexer.js';
|
|
33
|
-
import { PagesLinkIndexerService } from './services/pages-link-indexer.js';
|
|
34
|
-
import { PageSerializer } from './services/page-serializer.js';
|
|
35
|
-
import { PageVersionService } from './services/page-version.js';
|
|
36
|
-
import { RawEntityReader } from './domain/raw-entity-reader.js';
|
|
37
|
-
import { ReleaseService } from './services/release.js';
|
|
38
|
-
import { releasesRouter } from './routes/releases.js';
|
|
39
|
-
import { ReleasePushService } from './services/release-push.js';
|
|
40
|
-
import { releasePushesRouter } from './routes/release-pushes.js';
|
|
41
|
-
import { ReleaseImportService, rollbackClone } from './services/release-import.js';
|
|
42
|
-
import { C4S_VERSION } from './services/release-bundle.js';
|
|
43
|
-
import { createReleaseToolsServer } from './mcp/release-tools/index.js';
|
|
44
|
-
import { GitService } from './services/git.js';
|
|
45
|
-
import { gitRouter } from './routes/git.js';
|
|
46
7
|
import { WsGateway } from './ws/gateway.js';
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
49
|
-
import {
|
|
50
|
-
import {
|
|
51
|
-
import {
|
|
52
|
-
import {
|
|
53
|
-
import {
|
|
54
|
-
import {
|
|
55
|
-
import {
|
|
56
|
-
import { pluginHost } from './core/plugin-host/host.js';
|
|
57
|
-
import { pluginHostRouter } from './core/plugin-host/cross-cutting.js';
|
|
58
|
-
// Side-effect import: populates pluginHost via legacy adapter for the 4 entity types.
|
|
59
|
-
import './serialization/registerAll.js';
|
|
8
|
+
import { WorkspaceRegistry } from './workspace/registry.js';
|
|
9
|
+
import { migrateLegacyDbIfNeeded } from './workspace/db-migration.js';
|
|
10
|
+
import { bootstrapProject } from './workspace/bootstrap.js';
|
|
11
|
+
import { buildProjectContext } from './workspace/project-context.js';
|
|
12
|
+
import { ProjectContextCache } from './workspace/context-cache.js';
|
|
13
|
+
import { projectDispatchMiddleware } from './workspace/middleware.js';
|
|
14
|
+
import { workspaceRouter } from './workspace/routes.js';
|
|
15
|
+
import { PluginRegistryImpl } from './core/plugin-host/registry.js';
|
|
16
|
+
import { registerAllPlugins } from './serialization/registerAll.js';
|
|
60
17
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
61
18
|
const DEFAULT_PORT = 3000;
|
|
62
19
|
// M01: deterministyczny port. Przy zajetym porcie serwer NIE wskakuje juz na
|
|
63
20
|
// `port+1` — failuje z czytelnym bledem i niezerowym exit code. Powod: stały
|
|
64
|
-
//
|
|
21
|
+
// port workspace'u jest warunkiem discovery serwera przez `c4s ask`.
|
|
65
22
|
async function listenOrExit(server, port) {
|
|
66
23
|
try {
|
|
67
24
|
await new Promise((resolve, reject) => {
|
|
@@ -81,7 +38,7 @@ async function listenOrExit(server, port) {
|
|
|
81
38
|
}
|
|
82
39
|
catch (err) {
|
|
83
40
|
if (err.code === 'EADDRINUSE') {
|
|
84
|
-
console.error(`port ${port} is in use — stop the other instance or change the port
|
|
41
|
+
console.error(`port ${port} is in use — stop the other instance or change the workspace port / pass --port`);
|
|
85
42
|
process.exit(1);
|
|
86
43
|
}
|
|
87
44
|
throw err;
|
|
@@ -90,16 +47,41 @@ async function listenOrExit(server, port) {
|
|
|
90
47
|
// Wstrzykuje tozsamosc projektu do serwowanego HTML — zaraz po <head>, czyli PRZED
|
|
91
48
|
// inline theme-scriptem i bundlem React. Klient czyta `window.__C4S_PROJECT__.id`
|
|
92
49
|
// synchronicznie przy module-load, zeby suffiksowac project-scoped klucze localStorage
|
|
93
|
-
// (`::<scope>`)
|
|
94
|
-
//
|
|
95
|
-
function injectProjectGlobal(html, cwd) {
|
|
50
|
+
// (`::<scope>`), zbudowac API_BASE `/api/projects/<id>` i basepath routera `/p/<id>`.
|
|
51
|
+
// M31: cwd przychodzi z ROUTE (`/p/<id>/…` → registry lookup), nie z procesu.
|
|
52
|
+
function injectProjectGlobal(html, cwd, name) {
|
|
96
53
|
const id = createHash('sha1').update(cwd).digest('hex').slice(0, 12);
|
|
97
|
-
const payload = JSON.stringify({ id, name
|
|
54
|
+
const payload = JSON.stringify({ id, name }).replace(/</g, '\\u003c');
|
|
98
55
|
return html.replace('<head>', `<head><script>window.__C4S_PROJECT__=${payload};</script>`);
|
|
99
56
|
}
|
|
100
|
-
|
|
57
|
+
const PROJECT_ROUTE_RE = /^\/p\/([0-9a-f]{12})(\/|$)/;
|
|
58
|
+
/**
|
|
59
|
+
* M31 route scheme `/p/<project-id>/…` (assets stay at root — no Vite
|
|
60
|
+
* base changes). Decision #11 (0.1.57): `/` → 302 to the last-opened project
|
|
61
|
+
* (else first registered, else `/welcome`); `/welcome` serves the SPA with NO
|
|
62
|
+
* project injected (workspace-scope project list); unknown id and any other
|
|
63
|
+
* non-asset path → redirect `/`.
|
|
64
|
+
*/
|
|
65
|
+
function resolveSpaRoute(registry, workspace, urlPath) {
|
|
66
|
+
const m = urlPath.match(PROJECT_ROUTE_RE);
|
|
67
|
+
if (m) {
|
|
68
|
+
const project = registry.getProject(workspace, m[1]);
|
|
69
|
+
return project ? { kind: 'project', project } : { kind: 'redirect', to: '/' };
|
|
70
|
+
}
|
|
71
|
+
if (urlPath === '/welcome')
|
|
72
|
+
return { kind: 'welcome' };
|
|
73
|
+
if (urlPath === '/' || urlPath === '') {
|
|
74
|
+
const fresh = registry.getWorkspace(workspace.name) ?? workspace;
|
|
75
|
+
if (fresh.projects.length === 0)
|
|
76
|
+
return { kind: 'redirect', to: '/welcome' };
|
|
77
|
+
const byRecency = [...fresh.projects].sort((a, b) => (b.lastOpened ?? b.addedAt).localeCompare(a.lastOpened ?? a.addedAt));
|
|
78
|
+
return { kind: 'redirect', to: `/p/${byRecency[0].id}/` };
|
|
79
|
+
}
|
|
80
|
+
return { kind: 'redirect', to: '/' };
|
|
81
|
+
}
|
|
82
|
+
async function mountDevVite(app, deps) {
|
|
101
83
|
const { createServer } = await import('vite');
|
|
102
|
-
const repoRoot = findRepoRoot(
|
|
84
|
+
const repoRoot = findRepoRoot(deps.startCwd);
|
|
103
85
|
const vite = await createServer({
|
|
104
86
|
configFile: path.join(repoRoot, 'vite.config.ts'),
|
|
105
87
|
root: path.join(repoRoot, 'src/client'),
|
|
@@ -111,10 +93,23 @@ async function mountDevVite(app, cwd) {
|
|
|
111
93
|
if (req.originalUrl.startsWith('/api') || req.originalUrl.startsWith('/ws'))
|
|
112
94
|
return next();
|
|
113
95
|
try {
|
|
96
|
+
// req.path inside app.use('*') is the stripped mount path ('/') — the
|
|
97
|
+
// route lives in originalUrl.
|
|
98
|
+
const urlPath = (req.originalUrl ?? '/').split('?')[0];
|
|
99
|
+
const resolution = resolveSpaRoute(deps.registry, deps.workspace, urlPath);
|
|
100
|
+
if (resolution.kind === 'redirect')
|
|
101
|
+
return res.redirect(302, resolution.to);
|
|
114
102
|
const htmlPath = path.join(repoRoot, 'src/client/index.html');
|
|
115
103
|
let html = await fs.promises.readFile(htmlPath, 'utf-8');
|
|
116
104
|
html = await vite.transformIndexHtml(req.originalUrl, html);
|
|
117
|
-
|
|
105
|
+
// `/welcome` runs the SPA project-less: no `window.__C4S_PROJECT__`, so the
|
|
106
|
+
// client computes PROJECT_ID='' → API_BASE='/api' → router basepath '/'.
|
|
107
|
+
if (resolution.kind === 'welcome') {
|
|
108
|
+
return res.status(200).set({ 'Content-Type': 'text/html' }).end(html);
|
|
109
|
+
}
|
|
110
|
+
const { project } = resolution;
|
|
111
|
+
deps.registry.touchLastOpened(deps.workspace.name, project.id);
|
|
112
|
+
html = injectProjectGlobal(html, project.cwd, project.name);
|
|
118
113
|
res.status(200).set({ 'Content-Type': 'text/html' }).end(html);
|
|
119
114
|
}
|
|
120
115
|
catch (err) {
|
|
@@ -124,16 +119,30 @@ async function mountDevVite(app, cwd) {
|
|
|
124
119
|
});
|
|
125
120
|
return async () => { await vite.close(); };
|
|
126
121
|
}
|
|
127
|
-
function mountProd(app,
|
|
128
|
-
const repoRoot = findRepoRoot(
|
|
122
|
+
function mountProd(app, deps) {
|
|
123
|
+
const repoRoot = findRepoRoot(deps.startCwd);
|
|
129
124
|
const clientDist = path.join(repoRoot, 'dist/client');
|
|
130
|
-
|
|
125
|
+
// index:false — '/' must reach the catch-all (redirect to /p/<id>/), never a raw un-injected index.html.
|
|
126
|
+
app.use(express.static(clientDist, { index: false }));
|
|
131
127
|
app.use('*', (req, res, next) => {
|
|
132
128
|
if (req.originalUrl.startsWith('/api') || req.originalUrl.startsWith('/ws'))
|
|
133
129
|
return next();
|
|
134
130
|
try {
|
|
131
|
+
const urlPath = (req.originalUrl ?? '/').split('?')[0];
|
|
132
|
+
const resolution = resolveSpaRoute(deps.registry, deps.workspace, urlPath);
|
|
133
|
+
if (resolution.kind === 'redirect')
|
|
134
|
+
return res.redirect(302, resolution.to);
|
|
135
135
|
const raw = fs.readFileSync(path.join(clientDist, 'index.html'), 'utf-8');
|
|
136
|
-
|
|
136
|
+
// `/welcome` runs the SPA project-less (no `window.__C4S_PROJECT__`).
|
|
137
|
+
if (resolution.kind === 'welcome') {
|
|
138
|
+
return res.status(200).set({ 'Content-Type': 'text/html' }).end(raw);
|
|
139
|
+
}
|
|
140
|
+
const { project } = resolution;
|
|
141
|
+
deps.registry.touchLastOpened(deps.workspace.name, project.id);
|
|
142
|
+
res
|
|
143
|
+
.status(200)
|
|
144
|
+
.set({ 'Content-Type': 'text/html' })
|
|
145
|
+
.end(injectProjectGlobal(raw, project.cwd, project.name));
|
|
137
146
|
}
|
|
138
147
|
catch (err) {
|
|
139
148
|
next(err);
|
|
@@ -154,595 +163,81 @@ export async function startServer(opts) {
|
|
|
154
163
|
const cwd = opts.cwd;
|
|
155
164
|
const mode = opts.mode ?? (process.env.NODE_ENV === 'production' ? 'prod' : 'dev');
|
|
156
165
|
const portRef = { current: opts.port ?? DEFAULT_PORT };
|
|
157
|
-
// M26 §3: runtime-only ISO timestamp captured at boot; surfaced in
|
|
158
|
-
// GET /api/config so the client can detect "Restart required" by comparing
|
|
159
|
-
// it to `localStorage['c4s:settings:last-restart-patch-at']`. Not persisted.
|
|
160
|
-
const serverStartedAt = new Date().toISOString();
|
|
161
166
|
const app = express();
|
|
162
167
|
app.use(express.json({ limit: '2mb' }));
|
|
163
|
-
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
//
|
|
168
|
-
//
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
const briefsRel = path.relative(cwd, briefsAbs);
|
|
177
|
-
if (briefsRel.startsWith('..') || path.isAbsolute(briefsRel)) {
|
|
178
|
-
throw new Error(`config.json: briefsDir must not escape project root, got: ${briefsDir}`);
|
|
179
|
-
}
|
|
180
|
-
if (briefsDir === pagesDir) {
|
|
181
|
-
console.warn(`[config] briefsDir === pagesDir ("${pagesDir}") — brief files will be visible in both indexers`);
|
|
182
|
-
}
|
|
183
|
-
// M23: patchesDir, default '.claude4spec/patches'. Same validation as briefsDir.
|
|
184
|
-
const patchesDir = bootConfig.patchesDir ?? '.claude4spec/patches';
|
|
185
|
-
if (path.isAbsolute(patchesDir)) {
|
|
186
|
-
throw new Error(`config.json: patchesDir must be relative to cwd, got: ${patchesDir}`);
|
|
187
|
-
}
|
|
188
|
-
const patchesAbs = path.resolve(cwd, patchesDir);
|
|
189
|
-
const patchesRel = path.relative(cwd, patchesAbs);
|
|
190
|
-
if (patchesRel.startsWith('..') || path.isAbsolute(patchesRel)) {
|
|
191
|
-
throw new Error(`config.json: patchesDir must not escape project root, got: ${patchesDir}`);
|
|
192
|
-
}
|
|
193
|
-
if (patchesDir === pagesDir || patchesDir === briefsDir) {
|
|
194
|
-
console.warn(`[config] patchesDir collides with pagesDir/briefsDir ("${patchesDir}") — patch files will be visible in multiple indexers`);
|
|
195
|
-
}
|
|
196
|
-
// M01 (0.1.36): resolve the remote base URL with precedence
|
|
197
|
-
// `--remote-url` flag (opts) > config.json > prod constant. The prod-constant
|
|
198
|
-
// fallback lives in RemoteHttpClient; here `null` means "use prod".
|
|
199
|
-
const remoteApiUrl = opts.remoteApiUrl ?? bootConfig.remoteApiUrl;
|
|
200
|
-
// M24: an explicit remoteApiUrl override (flag or config.json) must be a valid,
|
|
201
|
-
// reachable host — hard error at boot, no fallback to the production constant.
|
|
202
|
-
if (remoteApiUrl != null && remoteApiUrl.trim() !== '') {
|
|
203
|
-
await assertRemoteApiReachable(remoteApiUrl);
|
|
204
|
-
}
|
|
205
|
-
pluginHost.consolidate(bootConfig.entities);
|
|
206
|
-
const hostState = pluginHost.state();
|
|
207
|
-
console.log(`[plugin-host] active: [${hostState.active.join(', ') || '∅'}]` +
|
|
208
|
-
(hostState.inactive.length ? `, inactive: [${hostState.inactive.join(', ')}]` : '') +
|
|
209
|
-
(hostState.unknown.length ? `, unknown: [${hostState.unknown.join(', ')}]` : ''));
|
|
210
|
-
const initialWritingStyle = bootConfig.writingStyle;
|
|
211
|
-
if (initialWritingStyle !== null && !skillRegistry.isSelectable(initialWritingStyle)) {
|
|
212
|
-
const available = skillRegistry.listSelectable().map((s) => s.slug).join(', ') || '(none)';
|
|
213
|
-
throw new Error(`config.json: writingStyle "${initialWritingStyle}" not a selectable writing-style skill. Available: ${available}`);
|
|
214
|
-
}
|
|
215
|
-
const skillResolver = new SkillResolver(skillRegistry, cwd);
|
|
216
|
-
const db = openDb(cwd);
|
|
217
|
-
const pages = new PagesService(cwd, pagesDir);
|
|
218
|
-
await pages.ensureRoot();
|
|
219
|
-
// M21 m02multidir: drugi PagesService dla briefsDir. Wspoldziel z pierwszym
|
|
220
|
-
// zarowno PageVersionService jak i WsGateway, ale ma osobny watcher (debounce
|
|
221
|
-
// independent per katalog) i osobny PageSerializer (constructor-bound do
|
|
222
|
-
// odpowiedniego rootDir).
|
|
223
|
-
const briefsPages = new PagesService(cwd, briefsDir);
|
|
224
|
-
await briefsPages.ensureRoot();
|
|
225
|
-
// M23 m02multidir: trzeci PagesService dla patchesDir (analogicznie do briefsDir).
|
|
226
|
-
const patchesPages = new PagesService(cwd, patchesDir);
|
|
227
|
-
await patchesPages.ensureRoot();
|
|
228
|
-
const tagsService = new TagsService(db.handle);
|
|
229
|
-
const versionService = new VersionService(db.handle);
|
|
230
|
-
const rawReader = new RawEntityReader(db.handle);
|
|
231
|
-
// M17: wire snapshot capture deps. After this, every entity service
|
|
232
|
-
// mutation captures a deterministic snapshot via host.snapshot(...).
|
|
233
|
-
versionService.configureSnapshot(rawReader, pluginHost);
|
|
234
|
-
// M24: remote-account identity (device flow + local session). Single HTTP
|
|
235
|
-
// client per process; base URL from config.remoteApiUrl (or the prod constant).
|
|
236
|
-
const remoteHttpClient = new RemoteHttpClient(remoteApiUrl);
|
|
237
|
-
const remoteAuthService = new RemoteAuthService(db.handle, remoteHttpClient);
|
|
238
|
-
const chatService = new ChatService(db.handle);
|
|
239
|
-
// Orphan cleanup: rowsy chat_message.status='streaming' pozostale po crashu poprzedniego
|
|
240
|
-
// procesu (SIGKILL/OOM) — brak aktywnego adaptera po starcie, flipujemy wszystkie na 'complete'.
|
|
241
|
-
chatService.finalizeAllStreamingRows();
|
|
242
|
-
app.get('/api/health', (_req, res) => {
|
|
243
|
-
res.json({ ok: true, mode, cwd });
|
|
244
|
-
});
|
|
245
|
-
app.get('/api/meta', (_req, res) => {
|
|
246
|
-
res.json({ cwd, cwdName: path.basename(cwd), c4sVersion: C4S_VERSION });
|
|
247
|
-
});
|
|
248
|
-
app.get('/api/config', (_req, res) => {
|
|
249
|
-
// readConfig per-request: PATCH /api/config musi byc widoczny w GET bez restartu.
|
|
250
|
-
// Spojne z istniejacym wzorcem SkillResolver (per-query disk read).
|
|
251
|
-
const c = readConfig(cwd);
|
|
252
|
-
res.json({
|
|
253
|
-
name: c.name,
|
|
254
|
-
port: portRef.current,
|
|
255
|
-
pagesDir: c.pagesDir,
|
|
256
|
-
mode,
|
|
257
|
-
writingStyle: c.writingStyle,
|
|
258
|
-
onboarding: { completed: c.onboardingCompleted },
|
|
259
|
-
briefsDir: c.briefsDir,
|
|
260
|
-
patchesDir: c.patchesDir,
|
|
261
|
-
entities: c.entities,
|
|
262
|
-
agent: { claudeUsePreset: c.agent?.claudeUsePreset ?? true },
|
|
263
|
-
git: {
|
|
264
|
-
syncCommitOnRelease: c.git?.syncCommitOnRelease ?? false,
|
|
265
|
-
syncPushOnPush: c.git?.syncPushOnPush ?? false,
|
|
266
|
-
},
|
|
267
|
-
remoteProjectId: c.remoteProjectId ?? null,
|
|
268
|
-
remoteApiUrl: c.remoteApiUrl ?? null,
|
|
269
|
-
$schemaVersion: c.$schemaVersion,
|
|
270
|
-
serverStartedAt,
|
|
271
|
-
});
|
|
272
|
-
});
|
|
273
|
-
app.patch('/api/config', (req, res, next) => {
|
|
274
|
-
try {
|
|
275
|
-
const body = (req.body ?? {});
|
|
276
|
-
// M26 §2: hot-reload (name, writingStyle, agent.claudeUsePreset) and
|
|
277
|
-
// restart-required (port, mode, pagesDir, briefsDir, patchesDir, entities)
|
|
278
|
-
// share one atomic disk write. `writeConfig` re-runs the full validation in
|
|
279
|
-
// `config.ts`; this handler only enforces semantic checks the validator
|
|
280
|
-
// cannot do (writingStyle selectability, name regex, path safety).
|
|
281
|
-
const patch = {};
|
|
282
|
-
if ('name' in body) {
|
|
283
|
-
if (typeof body.name !== 'string') {
|
|
284
|
-
return res.status(400).json({ error: { code: 'VALIDATION', message: 'name must be a string' } });
|
|
285
|
-
}
|
|
286
|
-
const trimmed = body.name.trim();
|
|
287
|
-
if (trimmed.length < 1 || trimmed.length > 80 || !/^[a-zA-Z0-9._\- ]+$/.test(trimmed)) {
|
|
288
|
-
return res.status(400).json({ error: { code: 'VALIDATION', message: 'name: 1-80 chars, allowed [a-zA-Z0-9._- ]' } });
|
|
289
|
-
}
|
|
290
|
-
patch.name = trimmed;
|
|
291
|
-
}
|
|
292
|
-
if ('port' in body) {
|
|
293
|
-
if (typeof body.port !== 'number' || !Number.isInteger(body.port) || body.port < 1 || body.port > 65535) {
|
|
294
|
-
return res.status(400).json({ error: { code: 'VALIDATION', message: 'port must be an integer in [1, 65535]' } });
|
|
295
|
-
}
|
|
296
|
-
patch.port = body.port;
|
|
297
|
-
}
|
|
298
|
-
if ('mode' in body) {
|
|
299
|
-
if (body.mode !== 'dev' && body.mode !== 'prod') {
|
|
300
|
-
return res.status(400).json({ error: { code: 'VALIDATION', message: "mode must be 'dev' | 'prod'" } });
|
|
301
|
-
}
|
|
302
|
-
patch.mode = body.mode;
|
|
303
|
-
}
|
|
304
|
-
// pagesDir/briefsDir/patchesDir share the same path-safety contract as boot:
|
|
305
|
-
// must be relative, must not escape cwd. Reuse the local helper inline to
|
|
306
|
-
// keep validation co-located with the patch handler.
|
|
307
|
-
const validateDir = (field, value) => {
|
|
308
|
-
if (typeof value !== 'string' || value.trim() === '') {
|
|
309
|
-
return { error: `${field} must be a non-empty string` };
|
|
310
|
-
}
|
|
311
|
-
if (path.isAbsolute(value)) {
|
|
312
|
-
return { error: `${field} must be relative to cwd` };
|
|
313
|
-
}
|
|
314
|
-
const abs = path.resolve(cwd, value);
|
|
315
|
-
const rel = path.relative(cwd, abs);
|
|
316
|
-
if (rel.startsWith('..') || path.isAbsolute(rel)) {
|
|
317
|
-
return { error: `${field} must not escape project root` };
|
|
318
|
-
}
|
|
319
|
-
return value;
|
|
320
|
-
};
|
|
321
|
-
for (const field of ['pagesDir', 'briefsDir', 'patchesDir']) {
|
|
322
|
-
if (field in body) {
|
|
323
|
-
const result = validateDir(field, body[field]);
|
|
324
|
-
if (typeof result === 'object') {
|
|
325
|
-
return res.status(400).json({ error: { code: 'VALIDATION', message: result.error } });
|
|
326
|
-
}
|
|
327
|
-
patch[field] = result;
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
if ('writingStyle' in body) {
|
|
331
|
-
if (body.writingStyle !== null && typeof body.writingStyle !== 'string') {
|
|
332
|
-
return res.status(400).json({ error: { code: 'VALIDATION', message: 'writingStyle must be string | null' } });
|
|
333
|
-
}
|
|
334
|
-
if (typeof body.writingStyle === 'string' && !skillRegistry.isSelectable(body.writingStyle)) {
|
|
335
|
-
const available = skillRegistry.listSelectable().map((s) => s.slug).join(', ') || '(none)';
|
|
336
|
-
return res.status(400).json({ error: { code: 'VALIDATION', message: `writingStyle "${body.writingStyle}" not a selectable writing-style skill. Available: ${available}` } });
|
|
337
|
-
}
|
|
338
|
-
patch.writingStyle = body.writingStyle;
|
|
339
|
-
}
|
|
340
|
-
if ('onboardingCompleted' in body) {
|
|
341
|
-
if (typeof body.onboardingCompleted !== 'boolean') {
|
|
342
|
-
return res.status(400).json({ error: { code: 'VALIDATION', message: 'onboardingCompleted must be boolean' } });
|
|
343
|
-
}
|
|
344
|
-
patch.onboardingCompleted = body.onboardingCompleted;
|
|
345
|
-
}
|
|
346
|
-
if ('entities' in body) {
|
|
347
|
-
if (!Array.isArray(body.entities) || !body.entities.every((e) => typeof e === 'string')) {
|
|
348
|
-
return res.status(400).json({ error: { code: 'VALIDATION', message: 'entities must be string[]' } });
|
|
349
|
-
}
|
|
350
|
-
patch.entities = body.entities;
|
|
351
|
-
}
|
|
352
|
-
if ('agent' in body) {
|
|
353
|
-
const a = body.agent;
|
|
354
|
-
if (a === null || typeof a !== 'object' || Array.isArray(a)) {
|
|
355
|
-
return res.status(400).json({ error: { code: 'VALIDATION', message: 'agent must be an object' } });
|
|
356
|
-
}
|
|
357
|
-
const ar = a;
|
|
358
|
-
const next = {};
|
|
359
|
-
if ('claudeUsePreset' in ar) {
|
|
360
|
-
if (typeof ar.claudeUsePreset !== 'boolean') {
|
|
361
|
-
return res.status(400).json({ error: { code: 'VALIDATION', message: 'agent.claudeUsePreset must be boolean' } });
|
|
362
|
-
}
|
|
363
|
-
next.claudeUsePreset = ar.claudeUsePreset;
|
|
364
|
-
}
|
|
365
|
-
patch.agent = next;
|
|
366
|
-
}
|
|
367
|
-
// M28: hot-reload git-sync toggles. Only present subfields are forwarded;
|
|
368
|
-
// writeConfig deep-merges `git` so the untouched toggle is preserved.
|
|
369
|
-
if ('git' in body) {
|
|
370
|
-
const g = body.git;
|
|
371
|
-
if (g === null || typeof g !== 'object' || Array.isArray(g)) {
|
|
372
|
-
return res.status(400).json({ error: { code: 'VALIDATION', message: 'git must be an object' } });
|
|
373
|
-
}
|
|
374
|
-
const gr = g;
|
|
375
|
-
const next = {};
|
|
376
|
-
if ('syncCommitOnRelease' in gr) {
|
|
377
|
-
if (typeof gr.syncCommitOnRelease !== 'boolean') {
|
|
378
|
-
return res.status(400).json({ error: { code: 'VALIDATION', message: 'git.syncCommitOnRelease must be boolean' } });
|
|
379
|
-
}
|
|
380
|
-
next.syncCommitOnRelease = gr.syncCommitOnRelease;
|
|
381
|
-
}
|
|
382
|
-
if ('syncPushOnPush' in gr) {
|
|
383
|
-
if (typeof gr.syncPushOnPush !== 'boolean') {
|
|
384
|
-
return res.status(400).json({ error: { code: 'VALIDATION', message: 'git.syncPushOnPush must be boolean' } });
|
|
385
|
-
}
|
|
386
|
-
next.syncPushOnPush = gr.syncPushOnPush;
|
|
387
|
-
}
|
|
388
|
-
patch.git = next;
|
|
389
|
-
}
|
|
390
|
-
// M25: allow manual clear/override of remoteProjectId (e.g. UI "clear" after
|
|
391
|
-
// a stale UUID). null ⇒ next push is a first push again.
|
|
392
|
-
if ('remoteProjectId' in body) {
|
|
393
|
-
if (body.remoteProjectId !== null && typeof body.remoteProjectId !== 'string') {
|
|
394
|
-
return res.status(400).json({ error: { code: 'VALIDATION', message: 'remoteProjectId must be string | null' } });
|
|
395
|
-
}
|
|
396
|
-
patch.remoteProjectId = body.remoteProjectId;
|
|
397
|
-
}
|
|
398
|
-
const updated = writeConfig(cwd, patch);
|
|
399
|
-
res.json({
|
|
400
|
-
name: updated.name,
|
|
401
|
-
port: portRef.current,
|
|
402
|
-
pagesDir: updated.pagesDir,
|
|
403
|
-
mode,
|
|
404
|
-
writingStyle: updated.writingStyle,
|
|
405
|
-
onboarding: { completed: updated.onboardingCompleted },
|
|
406
|
-
briefsDir: updated.briefsDir,
|
|
407
|
-
patchesDir: updated.patchesDir,
|
|
408
|
-
entities: updated.entities,
|
|
409
|
-
agent: { claudeUsePreset: updated.agent?.claudeUsePreset ?? true },
|
|
410
|
-
git: {
|
|
411
|
-
syncCommitOnRelease: updated.git?.syncCommitOnRelease ?? false,
|
|
412
|
-
syncPushOnPush: updated.git?.syncPushOnPush ?? false,
|
|
413
|
-
},
|
|
414
|
-
remoteProjectId: updated.remoteProjectId ?? null,
|
|
415
|
-
remoteApiUrl: updated.remoteApiUrl ?? null,
|
|
416
|
-
$schemaVersion: updated.$schemaVersion,
|
|
417
|
-
serverStartedAt,
|
|
418
|
-
});
|
|
419
|
-
}
|
|
420
|
-
catch (err) {
|
|
421
|
-
next(err);
|
|
422
|
-
}
|
|
423
|
-
});
|
|
424
|
-
app.get('/api/writing-styles', (_req, res) => {
|
|
425
|
-
const c = readConfig(cwd);
|
|
426
|
-
res.json({
|
|
427
|
-
active: c.writingStyle,
|
|
428
|
-
available: skillRegistry.listSelectable().map((s) => ({
|
|
429
|
-
slug: s.slug,
|
|
430
|
-
title: s.title,
|
|
431
|
-
description: s.description,
|
|
432
|
-
version: s.version,
|
|
433
|
-
language: s.language,
|
|
434
|
-
})),
|
|
435
|
-
});
|
|
436
|
-
});
|
|
168
|
+
// M31: workspace registry — DB lives in the workspace slot, not the project dir.
|
|
169
|
+
const registry = new WorkspaceRegistry();
|
|
170
|
+
const workspace = opts.workspace ?? registry.selectOrCreate({ port: opts.port, mode: opts.mode });
|
|
171
|
+
// Decision #11: a workspace-only start (`createProject === false`) registers
|
|
172
|
+
// and activates nothing — `cwd` stays untouched and the bare command lands on
|
|
173
|
+
// `/welcome`. Default `true` keeps the historic "always create" behavior.
|
|
174
|
+
const createProject = opts.createProject ?? true;
|
|
175
|
+
const initialProject = createProject ? registry.registerProject(workspace, cwd) : null;
|
|
176
|
+
if (createProject)
|
|
177
|
+
migrateLegacyDbIfNeeded(registry, workspace, cwd);
|
|
178
|
+
// M31: process-immutable plugin catalog, populated exactly once.
|
|
179
|
+
const pluginRegistry = new PluginRegistryImpl();
|
|
180
|
+
registerAllPlugins(pluginRegistry);
|
|
437
181
|
const httpServer = createHttpServer(app);
|
|
438
182
|
const gateway = new WsGateway(httpServer);
|
|
439
|
-
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
//
|
|
451
|
-
//
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
// PageVersionService akceptuje override serializera w recordVersion(),
|
|
471
|
-
// wiec dwa serializery dziela jedna instancje wersjonowania.
|
|
472
|
-
const briefsSerializer = new PageSerializer(briefsPages);
|
|
473
|
-
// M23: serializer dla patchesDir (analogicznie do briefsSerializer).
|
|
474
|
-
const patchesSerializer = new PageSerializer(patchesPages);
|
|
475
|
-
const pageVersions = new PageVersionService(db.handle, pageSerializer);
|
|
476
|
-
// M21 m02fmidx / M23: in-memory frontmatter indexer feeded przez trzy watchery.
|
|
477
|
-
const pagesFrontmatterIndexer = new PagesFrontmatterIndexer(pages, briefsPages, patchesPages, gateway);
|
|
478
|
-
// Mount all active backend modules — each plugin constructs its own service,
|
|
479
|
-
// mounts its router, registers its MCP server, and wires its id resolver via
|
|
480
|
-
// the supplied MountContext. Inactive plugins are skipped (config.entities).
|
|
481
|
-
pluginHost.mountBackend({
|
|
482
|
-
app,
|
|
483
|
-
db: db.handle,
|
|
484
|
-
ws: gateway,
|
|
485
|
-
tagsService,
|
|
486
|
-
versionService,
|
|
487
|
-
referencesService,
|
|
488
|
-
registerMcpServer: (name, server) => pluginHost.registerMcpServer(name, server),
|
|
489
|
-
setIdResolver: (type, fn) => pluginHost.setIdResolver(type, fn),
|
|
490
|
-
registerEntityService: (type, service) => pluginHost.registerEntityService(type, service),
|
|
491
|
-
});
|
|
492
|
-
// Cross-cutting MCP server — owned by the host, not a plugin (M13).
|
|
493
|
-
// Registered as a factory: a fresh instance is built per agent turn so
|
|
494
|
-
// concurrent turns never share one MCP transport.
|
|
495
|
-
pluginHost.registerMcpServer('reference-tools', () => createReferenceToolsServer({
|
|
496
|
-
tagsService,
|
|
497
|
-
referencesService,
|
|
498
|
-
pagesService: pages,
|
|
499
|
-
sectionsService,
|
|
500
|
-
ws: gateway,
|
|
501
|
-
db: db.handle,
|
|
502
|
-
cwd,
|
|
503
|
-
}));
|
|
504
|
-
// M17: ReleaseService + cross-cutting `release-tools` MCP. Like
|
|
505
|
-
// reference-tools, owned by the host (not a plugin) — release semantics
|
|
506
|
-
// are dual-track (entities + pages), neither side is a plugin owner.
|
|
507
|
-
const releaseService = new ReleaseService(db.handle, pluginHost, versionService, pageVersions, pageSerializer, rawReader, tagsService, pages, watcher, cwd);
|
|
508
|
-
// M28 Git Sync — best-effort mirroring of release create/push into the user's
|
|
509
|
-
// git repo. Probes `pages.root` for the worktree; reads config per-action.
|
|
510
|
-
const gitService = new GitService(cwd, pages.root);
|
|
511
|
-
// M25 Release Push — coordinates M17 bundle build + M24 transport; owns release_push.
|
|
512
|
-
const releasePushService = new ReleasePushService(db.handle, releaseService, remoteAuthService, gitService, cwd);
|
|
513
|
-
pluginHost.registerMcpServer('release-tools', () => createReleaseToolsServer({ releaseService, gitService, ws: gateway }));
|
|
514
|
-
// M27 Project Clone — bootstrap-time only. Runs after services exist (DB
|
|
515
|
-
// migrated, plugin host mounted, pages root ensured) but BEFORE watchers start
|
|
516
|
-
// and before app.listen, so restore writes land without watcher double-capture.
|
|
517
|
-
if (opts.clone) {
|
|
518
|
-
const importService = new ReleaseImportService(db.handle, releaseService, remoteHttpClient, cwd);
|
|
519
|
-
try {
|
|
520
|
-
const result = await importService.clone(opts.clone, { nameOverride: opts.name });
|
|
521
|
-
console.log(` cloned remote project '${opts.clone}' → local release #${result.localReleaseId ?? '?'}`);
|
|
522
|
-
}
|
|
523
|
-
catch (err) {
|
|
524
|
-
const code = err instanceof DomainError ? err.code : 'CLONE_FAILED';
|
|
525
|
-
console.error(`\x1b[31mclone failed\x1b[0m: ${code} — ${err instanceof Error ? err.message : String(err)}`);
|
|
526
|
-
// Full all-or-nothing rollback: cwd returns to its pre-`--clone` state. Close
|
|
527
|
-
// the DB handle first so db.sqlite can be unlinked (required on Windows).
|
|
528
|
-
db.close();
|
|
529
|
-
rollbackClone(cwd, {
|
|
530
|
-
pagesDir,
|
|
531
|
-
configCreated: opts.configCreated ?? false,
|
|
532
|
-
claudeDirCreated: opts.claudeDirCreated ?? false,
|
|
533
|
-
gitignoreCreated: opts.gitignoreCreated ?? false,
|
|
534
|
-
});
|
|
535
|
-
process.exit(1);
|
|
536
|
-
}
|
|
537
|
-
}
|
|
538
|
-
// M21: BriefService — top-level (nie plugin), wzorzec analogiczny do
|
|
539
|
-
// PlanService. Mountowany router /api/briefs poniżej.
|
|
540
|
-
const briefService = new BriefService({
|
|
541
|
-
briefsPages,
|
|
542
|
-
briefsWatcher,
|
|
543
|
-
briefsSerializer,
|
|
544
|
-
pageVersions,
|
|
545
|
-
chatService,
|
|
546
|
-
releaseService,
|
|
547
|
-
frontmatterIndexer: pagesFrontmatterIndexer,
|
|
548
|
-
ws: gateway,
|
|
549
|
-
});
|
|
550
|
-
// M23: PatchService — top-level (nie plugin), wzorzec analogiczny do
|
|
551
|
-
// BriefService. Mountowany router /api/patches poniżej.
|
|
552
|
-
const patchService = new PatchService({
|
|
553
|
-
patchesPages,
|
|
554
|
-
patchesWatcher,
|
|
555
|
-
patchesSerializer,
|
|
556
|
-
pageVersions,
|
|
557
|
-
chatService,
|
|
558
|
-
frontmatterIndexer: pagesFrontmatterIndexer,
|
|
183
|
+
// M27 clone runs only on the FIRST build of the CLI-started project —
|
|
184
|
+
// consumed here so a later cache rebuild never re-clones.
|
|
185
|
+
let clonePending = opts.clone
|
|
186
|
+
? {
|
|
187
|
+
slug: opts.clone,
|
|
188
|
+
nameOverride: opts.name,
|
|
189
|
+
configCreated: opts.configCreated ?? false,
|
|
190
|
+
claudeDirCreated: opts.claudeDirCreated ?? false,
|
|
191
|
+
gitignoreCreated: opts.gitignoreCreated ?? false,
|
|
192
|
+
}
|
|
193
|
+
: undefined;
|
|
194
|
+
// M31: lazily-built, cached per-project contexts. The CLI-only overrides
|
|
195
|
+
// (pagesDir flag, --remote-url, clone) apply solely to the initial project.
|
|
196
|
+
const cache = new ProjectContextCache(async (project) => {
|
|
197
|
+
const isInitial = initialProject != null && project.id === initialProject.id;
|
|
198
|
+
const clone = isInitial ? clonePending : undefined;
|
|
199
|
+
if (clone)
|
|
200
|
+
clonePending = undefined;
|
|
201
|
+
return buildProjectContext({
|
|
202
|
+
registry,
|
|
203
|
+
pluginRegistry,
|
|
204
|
+
workspace,
|
|
205
|
+
cwd: project.cwd,
|
|
206
|
+
gateway,
|
|
207
|
+
mode,
|
|
208
|
+
remoteApiUrl: isInitial ? opts.remoteApiUrl : undefined,
|
|
209
|
+
pagesDirOverride: isInitial ? opts.pagesDir : undefined,
|
|
210
|
+
clone,
|
|
211
|
+
onTurnFinished: () => cache.reapIdle(),
|
|
212
|
+
onContextConfigChanged: () => cache.invalidate(project.id),
|
|
213
|
+
});
|
|
559
214
|
});
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
app.use('/api/entities', entitiesRouter(tagsService, versionService));
|
|
565
|
-
// Wspolne deps tury agenta — `threadsRouter` (POST /:id/ask) i `chatRouter`
|
|
566
|
-
// (POST /api/chat, SSE) dziela ten sam runtime i rejestr `activeAdapters`.
|
|
567
|
-
const agentDeps = {
|
|
568
|
-
chatService,
|
|
569
|
-
pagesService: pages,
|
|
570
|
-
tagsService,
|
|
571
|
-
sectionsService,
|
|
572
|
-
planService,
|
|
573
|
-
briefService,
|
|
574
|
-
patchService,
|
|
575
|
-
pageVersions,
|
|
576
|
-
skillResolver,
|
|
577
|
-
skillRegistry,
|
|
578
|
-
ws: gateway,
|
|
579
|
-
cwd,
|
|
580
|
-
pagesDir,
|
|
581
|
-
mode,
|
|
582
|
-
db,
|
|
215
|
+
// Per-project activation — POST /api/workspace/projects runs the SAME full
|
|
216
|
+
// bootstrap as a CLI start (M01/M12/M22 hooks + registration + db migration).
|
|
217
|
+
const activateProject = async (projectCwd) => {
|
|
218
|
+
return bootstrapProject(registry, workspace, projectCwd).project;
|
|
583
219
|
};
|
|
584
|
-
app.use('/api
|
|
585
|
-
app.use('/api/
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
app.use('/api/briefs', briefsRouter(briefService, pageVersions));
|
|
593
|
-
app.use('/api/patches', patchesRouter(patchService));
|
|
594
|
-
app.use('/api/remote-account', remoteAccountRouter(remoteAuthService));
|
|
595
|
-
app.use('/api/remote-project', remoteProjectRouter(remoteAuthService, cwd));
|
|
596
|
-
app.use('/api/chat', chatRouter(agentDeps));
|
|
597
|
-
app.use(errorHandler);
|
|
598
|
-
watcher.onChange((relPath, kind) => {
|
|
599
|
-
if (kind === 'unlink') {
|
|
600
|
-
sectionIndexer.handleUnlink(relPath).catch((err) => {
|
|
601
|
-
console.error('[section-indexer] unlink error:', err);
|
|
602
|
-
});
|
|
603
|
-
todosIndexer.handleUnlink(relPath);
|
|
604
|
-
pagesLinkIndexer.handleUnlink(relPath);
|
|
605
|
-
pagesFrontmatterIndexer.handleUnlink('pages', relPath);
|
|
606
|
-
// M17: capture filesystem-origin delete (chokidar saw external rm)
|
|
607
|
-
pageVersions.recordVersion(relPath, 'delete', 'filesystem').catch((err) => {
|
|
608
|
-
console.warn(`[page-version] watcher delete capture for ${relPath}:`, err.message);
|
|
609
|
-
});
|
|
610
|
-
}
|
|
611
|
-
else {
|
|
612
|
-
sectionIndexer.schedulePage(relPath);
|
|
613
|
-
todosIndexer.schedulePage(relPath);
|
|
614
|
-
pagesLinkIndexer.schedulePage(relPath);
|
|
615
|
-
pagesFrontmatterIndexer.schedulePage('pages', relPath);
|
|
616
|
-
// M17: capture filesystem-origin add/change. `kind === 'add'` may be a
|
|
617
|
-
// real new file (op=create) or a re-detection — pageVersions.hasAny
|
|
618
|
-
// distinguishes.
|
|
619
|
-
const op = kind === 'add' && !pageVersions.hasAny(relPath) ? 'create' : 'update';
|
|
620
|
-
pageVersions.recordVersion(relPath, op, 'filesystem').catch((err) => {
|
|
621
|
-
console.warn(`[page-version] watcher capture for ${relPath}:`, err.message);
|
|
622
|
-
});
|
|
623
|
-
}
|
|
624
|
-
});
|
|
625
|
-
// M21 m02multidir: drugi watcher dla briefsDir. Tylko frontmatter indexer
|
|
626
|
-
// + page_version (z dedykowanym briefsSerializer). Section/todos/pages-link
|
|
627
|
-
// indexery NIE pracuja na briefsDir (briefs to nie pages w sensie M02 →
|
|
628
|
-
// nie czesc nawigowalnego drzewa, nie agreguja section_ref/todo'ow do tabel).
|
|
629
|
-
briefsWatcher.onChange((relPath, kind) => {
|
|
630
|
-
if (kind === 'unlink') {
|
|
631
|
-
pagesFrontmatterIndexer.handleUnlink('briefs', relPath);
|
|
632
|
-
pageVersions.recordVersion(relPath, 'delete', 'filesystem', undefined, briefsSerializer, 'brief').catch((err) => {
|
|
633
|
-
console.warn(`[page-version] brief delete capture for ${relPath}:`, err.message);
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
else {
|
|
637
|
-
pagesFrontmatterIndexer.schedulePage('briefs', relPath);
|
|
638
|
-
const op = kind === 'add' && !pageVersions.hasAny(relPath) ? 'create' : 'update';
|
|
639
|
-
pageVersions.recordVersion(relPath, op, 'filesystem', undefined, briefsSerializer, 'brief').catch((err) => {
|
|
640
|
-
console.warn(`[page-version] brief capture for ${relPath}:`, err.message);
|
|
641
|
-
});
|
|
642
|
-
// Direct disk edit (not suppressed): refresh open BriefEditors. The indexer
|
|
643
|
-
// only fires `briefs:changed` on frontmatter changes, so body-only edits
|
|
644
|
-
// need this explicit broadcast. `external` → reload-or-confirm like Pages.
|
|
645
|
-
gateway.broadcast({ kind: 'briefs:changed', path: relPath, origin: 'external' });
|
|
646
|
-
}
|
|
647
|
-
});
|
|
648
|
-
// M23: trzeci watcher dla patchesDir — analogicznie do briefsWatcher.
|
|
649
|
-
patchesWatcher.onChange((relPath, kind) => {
|
|
650
|
-
if (kind === 'unlink') {
|
|
651
|
-
pagesFrontmatterIndexer.handleUnlink('patches', relPath);
|
|
652
|
-
pageVersions.recordVersion(relPath, 'delete', 'filesystem', undefined, patchesSerializer, 'patch').catch((err) => {
|
|
653
|
-
console.warn(`[page-version] patch delete capture for ${relPath}:`, err.message);
|
|
654
|
-
});
|
|
655
|
-
}
|
|
656
|
-
else {
|
|
657
|
-
pagesFrontmatterIndexer.schedulePage('patches', relPath);
|
|
658
|
-
const op = kind === 'add' && !pageVersions.hasAny(relPath) ? 'create' : 'update';
|
|
659
|
-
pageVersions.recordVersion(relPath, op, 'filesystem', undefined, patchesSerializer, 'patch').catch((err) => {
|
|
660
|
-
console.warn(`[page-version] patch capture for ${relPath}:`, err.message);
|
|
661
|
-
});
|
|
662
|
-
}
|
|
663
|
-
});
|
|
664
|
-
watcher.start();
|
|
665
|
-
briefsWatcher.start();
|
|
666
|
-
patchesWatcher.start();
|
|
667
|
-
sectionIndexer.indexAll().catch((err) => {
|
|
668
|
-
console.error('[section-indexer] initial indexAll failed:', err);
|
|
669
|
-
});
|
|
670
|
-
todosIndexer.indexAll().catch((err) => {
|
|
671
|
-
console.error('[todos-indexer] initial indexAll failed:', err);
|
|
672
|
-
});
|
|
673
|
-
pagesLinkIndexer.indexAll().catch((err) => {
|
|
674
|
-
console.error('[pages-link-indexer] initial indexAll failed:', err);
|
|
675
|
-
});
|
|
676
|
-
// M17: initial sync of page_version. For each markdown file with no captured
|
|
677
|
-
// version — or whose latest captured version is a `delete` tombstone while the
|
|
678
|
-
// file is back on disk — write an `op = 'create'` baseline. The latter case
|
|
679
|
-
// covers delete+recreate that happened while the server wasn't watching
|
|
680
|
-
// (server down, `git checkout` between restarts): without this, the phantom
|
|
681
|
-
// `delete` stays the latest row and release diffs show the page as removed.
|
|
682
|
-
(async () => {
|
|
683
|
-
try {
|
|
684
|
-
const files = await pages.listMarkdownFiles();
|
|
685
|
-
for (const relPath of files) {
|
|
686
|
-
const latest = pageVersions.getLatestForPath(relPath);
|
|
687
|
-
if (latest && latest.op !== 'delete')
|
|
688
|
-
continue;
|
|
689
|
-
await pageVersions.recordVersion(relPath, 'create', 'filesystem');
|
|
690
|
-
}
|
|
691
|
-
}
|
|
692
|
-
catch (err) {
|
|
693
|
-
console.warn('[page-version] initial sync failed:', err.message);
|
|
694
|
-
}
|
|
695
|
-
})();
|
|
696
|
-
// M21: initial sync — page_version baseline dla briefów + frontmatter indexer.
|
|
697
|
-
(async () => {
|
|
698
|
-
try {
|
|
699
|
-
const files = await briefsPages.listMarkdownFiles();
|
|
700
|
-
for (const relPath of files) {
|
|
701
|
-
if (pageVersions.hasAny(relPath))
|
|
702
|
-
continue;
|
|
703
|
-
await pageVersions.recordVersion(relPath, 'create', 'filesystem', undefined, briefsSerializer, 'brief');
|
|
704
|
-
}
|
|
705
|
-
}
|
|
706
|
-
catch (err) {
|
|
707
|
-
console.warn('[page-version] briefs initial sync failed:', err.message);
|
|
708
|
-
}
|
|
709
|
-
// M23: initial sync — page_version baseline dla patchy.
|
|
710
|
-
try {
|
|
711
|
-
const files = await patchesPages.listMarkdownFiles();
|
|
712
|
-
for (const relPath of files) {
|
|
713
|
-
if (pageVersions.hasAny(relPath))
|
|
714
|
-
continue;
|
|
715
|
-
await pageVersions.recordVersion(relPath, 'create', 'filesystem', undefined, patchesSerializer, 'patch');
|
|
716
|
-
}
|
|
717
|
-
}
|
|
718
|
-
catch (err) {
|
|
719
|
-
console.warn('[page-version] patches initial sync failed:', err.message);
|
|
720
|
-
}
|
|
721
|
-
try {
|
|
722
|
-
await pagesFrontmatterIndexer.indexAll();
|
|
723
|
-
}
|
|
724
|
-
catch (err) {
|
|
725
|
-
console.warn('[pages-frontmatter-indexer] initial sync failed:', err.message);
|
|
726
|
-
}
|
|
727
|
-
})();
|
|
728
|
-
const closeAssets = mode === 'dev' ? await mountDevVite(app, cwd) : mountProd(app, cwd);
|
|
220
|
+
app.use('/api', workspaceRouter({ registry, workspace, cache, mode, activateProject }));
|
|
221
|
+
app.use('/api/projects/:id', projectDispatchMiddleware(registry, workspace, cache));
|
|
222
|
+
// Eager warm of the CLI-started project: clone executes before listen and a
|
|
223
|
+
// broken initial config still fails the boot fast (parity with pre-M31). A
|
|
224
|
+
// workspace-only start has no initial project to warm.
|
|
225
|
+
const initialCtx = initialProject ? await cache.get(initialProject) : null;
|
|
226
|
+
const spaDeps = { registry, workspace, startCwd: cwd };
|
|
227
|
+
const closeAssets = mode === 'dev' ? await mountDevVite(app, spaDeps) : mountProd(app, spaDeps);
|
|
729
228
|
const port = await listenOrExit(httpServer, portRef.current);
|
|
730
229
|
portRef.current = port;
|
|
230
|
+
if (initialProject)
|
|
231
|
+
registry.touchLastOpened(workspace.name, initialProject.id);
|
|
731
232
|
const url = `http://localhost:${port}`;
|
|
732
|
-
const writingStyle = initialWritingStyle
|
|
733
|
-
? { slug: initialWritingStyle, title: skillRegistry.resolve(initialWritingStyle).metadata.title }
|
|
734
|
-
: null;
|
|
735
233
|
return {
|
|
736
234
|
url,
|
|
737
235
|
port,
|
|
738
|
-
writingStyle,
|
|
236
|
+
writingStyle: initialCtx?.writingStyle ?? null,
|
|
739
237
|
shutdown: async () => {
|
|
740
|
-
await
|
|
741
|
-
await briefsWatcher.close();
|
|
742
|
-
await patchesWatcher.close();
|
|
238
|
+
await cache.disposeAll();
|
|
743
239
|
await gateway.close();
|
|
744
240
|
await closeAssets();
|
|
745
|
-
db.close();
|
|
746
241
|
await new Promise((resolve) => httpServer.close(() => resolve()));
|
|
747
242
|
},
|
|
748
243
|
};
|