kennel 0.2.3 → 0.2.5
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/README.md +23 -13
- package/dist/cli/dispatch.js +16 -3
- package/dist/cli/src/cli/dispatch.js +16 -3
- package/dist/cli/src/cli/usage.js +1 -1
- package/dist/cli/src/core/command.js +1 -0
- package/dist/cli/src/core/init.js +5 -5
- package/dist/cli/src/plugins/external.d.ts +28 -2
- package/dist/cli/src/plugins/external.js +75 -21
- package/dist/cli/src/site/server-plugins.d.ts +2 -7
- package/dist/cli/src/site/viewer.js +1 -0
- package/dist/cli/usage.js +1 -1
- package/dist/core/command.js +1 -0
- package/dist/core/init.js +5 -5
- package/dist/core/runtime.js +357 -3207
- package/dist/package/preview.js +1232 -5113
- package/dist/plugins/external.d.ts +28 -2
- package/dist/plugins/external.js +75 -21
- package/dist/site/server-plugins.d.ts +2 -7
- package/dist/site/server-plugins.js +552 -10447
- package/dist/site/viewer.js +1 -0
- package/kernel/apps/web/package.json +1 -5
- package/kernel/apps/web/scripts/dev.mjs +53 -0
- package/kernel/apps/web/src/app.css +0 -3
- package/kernel/apps/web/src/main.tsx +0 -1
- package/kernel/apps/web/src/plugins.ts +0 -30
- package/kernel/apps/web/tsconfig.json +1 -2
- package/kernel/apps/web/vite.config.ts +31 -13
- package/kernel/packages/api/package.json +0 -4
- package/kernel/packages/api/src/server/folder-metadata-plugin.ts +13 -38
- package/kernel/packages/api/src/server/index.ts +0 -1
- package/kernel/packages/api/src/server/plugins.ts +49 -16
- package/kernel/packages/api/test/core.test.ts +13 -25
- package/kernel/packages/markdown/README.md +1 -1
- package/package.json +1 -1
- package/web/assets/{abnfDiagram-VRR7QNED-Bgfb1nEB.js → abnfDiagram-VRR7QNED-xd_g1uK3.js} +1 -1
- package/web/assets/app-CX20nNIa.js +2 -0
- package/web/assets/{app-options-D-RJiQ-X.js → app-options-CkPJE5ep.js} +1 -1
- package/web/assets/{app-view-header-BLvLq02T.js → app-view-header-CNk5KnA-.js} +1 -1
- package/web/assets/{arc-Dk_KI36B.js → arc-B0u5yBJA.js} +1 -1
- package/web/assets/{architectureDiagram-ZJ3FMSHR-NR_KhRCf.js → architectureDiagram-ZJ3FMSHR-DfxOAo8P.js} +1 -1
- package/web/assets/{ask-tray-TzeTcO6-.js → ask-tray-SscJecDY.js} +1 -1
- package/web/assets/{blockDiagram-677ZJIJ3-CkLkwx9C.js → blockDiagram-677ZJIJ3-Ci3Usxh6.js} +1 -1
- package/web/assets/{c4Diagram-LMCZKHZV-CFCUH8e_.js → c4Diagram-LMCZKHZV-BqgwIVgh.js} +1 -1
- package/web/assets/channel-X7WHyxxT.js +1 -0
- package/web/assets/{chunk-2Q5K7J3B-B_BPLWsW.js → chunk-2Q5K7J3B-CfX69l2U.js} +1 -1
- package/web/assets/{chunk-32BRIVSS-B1VB1hOy.js → chunk-32BRIVSS-CuFlzZkl.js} +1 -1
- package/web/assets/{chunk-5VM5RSS4-KFcYr8Co.js → chunk-5VM5RSS4-DidCMlJa.js} +1 -1
- package/web/assets/{chunk-EX3LRPZG-CpBX3kuK.js → chunk-EX3LRPZG-B5CDm1dS.js} +1 -1
- package/web/assets/{chunk-JWPE2WC7-BPSl40wG.js → chunk-JWPE2WC7-D5fGV_sH.js} +1 -1
- package/web/assets/{chunk-MOJQB5TN-D2RcYmoU.js → chunk-MOJQB5TN-ayhH5Q-H.js} +1 -1
- package/web/assets/{chunk-RYQCIY6F-m7Gyn-t3.js → chunk-RYQCIY6F-C4BfOOZn.js} +1 -1
- package/web/assets/{chunk-V7JOEXUC-TDvdopxS.js → chunk-V7JOEXUC-DpUEIM9V.js} +1 -1
- package/web/assets/{chunk-VR4S4FIN-DZTsG51a.js → chunk-VR4S4FIN-DoQuFFRh.js} +1 -1
- package/web/assets/{chunk-XXDRQBXY-Dp0--RE2.js → chunk-XXDRQBXY-CI9eiLwn.js} +1 -1
- package/web/assets/classDiagram-OUVF2IWQ-BjsLQ3dz.js +1 -0
- package/web/assets/classDiagram-v2-EOCWNBFH-BjsLQ3dz.js +1 -0
- package/web/assets/client-7OWw5kW2.js +6 -0
- package/web/assets/client-DYj_td4Q.js +17 -0
- package/web/assets/{client-j2kQmxsO.js → client-Dk-NBvJR.js} +4 -4
- package/web/assets/command-zkzH4hVz.js +38 -0
- package/web/assets/{copy-context-C0Uqsp-3.js → copy-context-D5oiWrYC.js} +1 -1
- package/web/assets/{cose-bilkent-JH36ORCC-CPu3PI8h.js → cose-bilkent-JH36ORCC-BHRk1mO3.js} +1 -1
- package/web/assets/{cynefin-VYW2F7L2-DWxftYAm.js → cynefin-VYW2F7L2-C1B09TRE.js} +1 -1
- package/web/assets/{cynefinDiagram-TSTJHNR4-DsmN2V-p.js → cynefinDiagram-TSTJHNR4-B27qXQbv.js} +1 -1
- package/web/assets/{dagre-VKFMJZFB-COEbApyY.js → dagre-VKFMJZFB-B1qEi32g.js} +1 -1
- package/web/assets/{diagram-FQU43EPY-0O9WGWKB.js → diagram-FQU43EPY-BMkkgADD.js} +1 -1
- package/web/assets/{diagram-G47NLZAW-CutEdMVh.js → diagram-G47NLZAW-Cb8_g1A5.js} +1 -1
- package/web/assets/{diagram-NH7WQ7WH-C9KZc3B2.js → diagram-NH7WQ7WH-B51wuc5m.js} +1 -1
- package/web/assets/{diagram-OA4YK3LP-D8kkPXbY.js → diagram-OA4YK3LP-xnsecbbI.js} +1 -1
- package/web/assets/{diagram-WEI45ONY-_YqiTaGP.js → diagram-WEI45ONY-DSZdyaAV.js} +1 -1
- package/web/assets/{document-Dgt5eZFj.js → document-Dc2dftTW.js} +3 -3
- package/web/assets/{ebnfDiagram-CCIWWBDH-JO-zQh6g.js → ebnfDiagram-CCIWWBDH-aTHmcUGy.js} +1 -1
- package/web/assets/{entities-C3wz7FCx.js → entities-CCUtpskY.js} +1 -1
- package/web/assets/{erDiagram-Q63AITRT-9UjQwJ4P.js → erDiagram-Q63AITRT-CUUPBcvo.js} +1 -1
- package/web/assets/{file-viewer-DZJ8nYnl.js → file-viewer-DJ3rsmef.js} +1 -1
- package/web/assets/{flowDiagram-23GEKE2U-Ca08tnYM.js → flowDiagram-23GEKE2U-aTQXx2Mh.js} +1 -1
- package/web/assets/{folder-image-BfGgxo7i.js → folder-image-Bnv43tzP.js} +1 -1
- package/web/assets/{folder-landing-qUKBCnG-.js → folder-landing-DCnzYXXY.js} +1 -1
- package/web/assets/{folder-list-BfFjv7rR.js → folder-list-DvVkVjiR.js} +1 -1
- package/web/assets/{folder-metadata-DCw8xzHk.js → folder-metadata-C9hMMzIH.js} +1 -1
- package/web/assets/{folder-mobile-bar-Ubz4babf.js → folder-mobile-bar-VoTL1GS0.js} +1 -1
- package/web/assets/folder-plugin-view-Cvd804ZU.js +6 -0
- package/web/assets/{ganttDiagram-NO4QXBWP-KnHzX5W3.js → ganttDiagram-NO4QXBWP-DiwdUp7u.js} +1 -1
- package/web/assets/{gitGraphDiagram-IHSO6WYX-Cm8woNtK.js → gitGraphDiagram-IHSO6WYX-BL2gG_IS.js} +1 -1
- package/web/assets/{index-CNyy98F8.js → index-CUTtMSyJ.js} +2 -2
- package/web/assets/index-CegvVQ1C.js +1 -0
- package/web/assets/index-Dwhf8F_U.css +1 -0
- package/web/assets/{infoDiagram-FWYZ7A6U-yyqmUtIb.js → infoDiagram-FWYZ7A6U-B3n0zZpt.js} +1 -1
- package/web/assets/{ishikawaDiagram-FXEZZL3T-BfvR_Zy4.js → ishikawaDiagram-FXEZZL3T-D7YytbEv.js} +1 -1
- package/web/assets/{journeyDiagram-5HDEW3XC-BKuw06HM.js → journeyDiagram-5HDEW3XC-BYwqEl3C.js} +1 -1
- package/web/assets/{kanban-definition-HUTT4EX6-n0010rmQ.js → kanban-definition-HUTT4EX6-BQHNJgXZ.js} +1 -1
- package/web/assets/{linear-C0d0lT8O.js → linear-BPiQCLNk.js} +1 -1
- package/web/assets/{mermaid.core-BDVbShyO.js → mermaid.core-CD-KvBjV.js} +4 -4
- package/web/assets/{metadata-KV0dpp4G.js → metadata-COJ7fZul.js} +1 -1
- package/web/assets/{mindmap-definition-LN4V7U3C--_0ks3GF.js → mindmap-definition-LN4V7U3C-C1L_gyoj.js} +1 -1
- package/web/assets/{pegDiagram-2B236MQR-CBoNIpKR.js → pegDiagram-2B236MQR-BDAfItqS.js} +1 -1
- package/web/assets/{pieDiagram-ENE6RG2P-MA5tkYh5.js → pieDiagram-ENE6RG2P-CkZYH3Vz.js} +1 -1
- package/web/assets/{popover-DBD7FF-W.js → popover-0oA2lLUV.js} +2 -2
- package/web/assets/{project-search-dialog-DM1IkSZu.js → project-search-dialog-B2oqMT6h.js} +1 -1
- package/web/assets/{quadrantDiagram-ABIIQ3AL-CWT1ryqF.js → quadrantDiagram-ABIIQ3AL-Dy8Al3YZ.js} +1 -1
- package/web/assets/{railroadDiagram-RFXS5EU6-ZiLB5FGi.js → railroadDiagram-RFXS5EU6-Dd9_yo7B.js} +1 -1
- package/web/assets/{requirementDiagram-TGXJPOKE-CQq9uneq.js → requirementDiagram-TGXJPOKE-DPhIUsbs.js} +1 -1
- package/web/assets/{sankeyDiagram-HTMAVEWB-qDG-0JqI.js → sankeyDiagram-HTMAVEWB-B2QkYtub.js} +1 -1
- package/web/assets/{search-DNrQA5fF.js → search-C1J0-xiB.js} +1 -1
- package/web/assets/select-CtXC_PLC.js +30 -0
- package/web/assets/{selection-popup-DmtTeRs2.js → selection-popup-qhlUFzGq.js} +1 -1
- package/web/assets/{sequenceDiagram-DBY2YBRQ-CiApurLO.js → sequenceDiagram-DBY2YBRQ-Bx7fcZDx.js} +1 -1
- package/web/assets/{sizeCapture-X5ZJPWSS-DZSR5QXm.js → sizeCapture-X5ZJPWSS-CIXIhl_A.js} +1 -1
- package/web/assets/{stateDiagram-2N3HPSRC-CjJJR9_z.js → stateDiagram-2N3HPSRC-DCrdAflB.js} +1 -1
- package/web/assets/stateDiagram-v2-6OUMAXLB-0KCFLYeQ.js +1 -0
- package/web/assets/{swimlanes-5IMT3BWC-CKmo1sg6.js → swimlanes-5IMT3BWC-DA2vc7iZ.js} +2 -2
- package/web/assets/swimlanesDiagram-G3AALYLV-CAw3AV84.js +8 -0
- package/web/assets/{timeline-definition-FHXFAJF6-D-NMwS3y.js → timeline-definition-FHXFAJF6-lKfJiV9V.js} +1 -1
- package/web/assets/{vennDiagram-L72KCM5P-CfL6LBWw.js → vennDiagram-L72KCM5P-DO1q2KzG.js} +1 -1
- package/web/assets/{view-context-gyDl_p2V.js → view-context-DXKM3-l1.js} +4 -4
- package/web/assets/{viewer-capabilities-Bpp8Uvcg.js → viewer-capabilities-Cig_xM9u.js} +1 -1
- package/web/assets/{wardleyDiagram-EHGQE667-DgldMxaj.js → wardleyDiagram-EHGQE667-Dal9LkuZ.js} +1 -1
- package/web/assets/{xychartDiagram-FW5EYKEG-CIoAIFX8.js → xychartDiagram-FW5EYKEG-B3dPLPII.js} +1 -1
- package/web/index.html +2 -2
- package/kernel/packages/api/test/partial-loading.test.ts +0 -43
- package/kernel/plugins/calendar/SKILL.md +0 -173
- package/kernel/plugins/calendar/eslint.config.js +0 -10
- package/kernel/plugins/calendar/package.json +0 -73
- package/kernel/plugins/calendar/schema.json +0 -137
- package/kernel/plugins/calendar/src/calendar.tsx +0 -328
- package/kernel/plugins/calendar/src/client.tsx +0 -139
- package/kernel/plugins/calendar/src/context.ts +0 -48
- package/kernel/plugins/calendar/src/copy-button.tsx +0 -66
- package/kernel/plugins/calendar/src/event-dialog.tsx +0 -270
- package/kernel/plugins/calendar/src/index.ts +0 -4
- package/kernel/plugins/calendar/src/manifest.ts +0 -7
- package/kernel/plugins/calendar/src/parse.ts +0 -610
- package/kernel/plugins/calendar/src/server.ts +0 -160
- package/kernel/plugins/calendar/src/utils.ts +0 -80
- package/kernel/plugins/calendar/src/views.tsx +0 -418
- package/kernel/plugins/calendar/test/context.test.mjs +0 -60
- package/kernel/plugins/calendar/test/package.test.mjs +0 -43
- package/kernel/plugins/calendar/test/parse.test.mjs +0 -392
- package/kernel/plugins/calendar/test/server.test.mjs +0 -126
- package/kernel/plugins/calendar/tsconfig.json +0 -17
- package/kernel/plugins/mail/SKILL.md +0 -97
- package/kernel/plugins/mail/eslint.config.js +0 -10
- package/kernel/plugins/mail/package.json +0 -79
- package/kernel/plugins/mail/src/api.ts +0 -359
- package/kernel/plugins/mail/src/bun-ffi.d.ts +0 -14
- package/kernel/plugins/mail/src/client.tsx +0 -14
- package/kernel/plugins/mail/src/flock.ts +0 -137
- package/kernel/plugins/mail/src/folders.ts +0 -37
- package/kernel/plugins/mail/src/history.ts +0 -485
- package/kernel/plugins/mail/src/index.ts +0 -3
- package/kernel/plugins/mail/src/mail-card.tsx +0 -123
- package/kernel/plugins/mail/src/mail-conversation.tsx +0 -153
- package/kernel/plugins/mail/src/mail-detail-page.tsx +0 -467
- package/kernel/plugins/mail/src/mail-hooks.ts +0 -252
- package/kernel/plugins/mail/src/mail-page.tsx +0 -109
- package/kernel/plugins/mail/src/mail-request.ts +0 -78
- package/kernel/plugins/mail/src/mail-send-box.tsx +0 -175
- package/kernel/plugins/mail/src/mailos.ts +0 -723
- package/kernel/plugins/mail/src/manifest.ts +0 -7
- package/kernel/plugins/mail/src/node.ts +0 -171
- package/kernel/plugins/mail/src/parse.ts +0 -139
- package/kernel/plugins/mail/src/server.ts +0 -125
- package/kernel/plugins/mail/src/types.ts +0 -10
- package/kernel/plugins/mail/src/viewer.tsx +0 -147
- package/kernel/plugins/mail/test/flock.test.mjs +0 -156
- package/kernel/plugins/mail/test/folders.test.mjs +0 -138
- package/kernel/plugins/mail/test/history.test.mjs +0 -460
- package/kernel/plugins/mail/test/mailos.test.mjs +0 -752
- package/kernel/plugins/mail/test/node.test.mjs +0 -207
- package/kernel/plugins/mail/test/package.test.mjs +0 -54
- package/kernel/plugins/mail/test/parse.test.mjs +0 -32
- package/kernel/plugins/mail/test/server.test.mjs +0 -116
- package/kernel/plugins/mail/test/viewer.test.mjs +0 -192
- package/kernel/plugins/mail/tsconfig.json +0 -17
- package/kernel/plugins/projects/SKILL.md +0 -73
- package/kernel/plugins/projects/eslint.config.js +0 -10
- package/kernel/plugins/projects/package.json +0 -70
- package/kernel/plugins/projects/src/client.tsx +0 -17
- package/kernel/plugins/projects/src/data/model.ts +0 -129
- package/kernel/plugins/projects/src/index.ts +0 -17
- package/kernel/plugins/projects/src/manifest.ts +0 -7
- package/kernel/plugins/projects/src/project-context.ts +0 -41
- package/kernel/plugins/projects/src/server.ts +0 -276
- package/kernel/plugins/projects/src/ui/project-card.tsx +0 -61
- package/kernel/plugins/projects/src/ui/projects-page.tsx +0 -102
- package/kernel/plugins/projects/test/package.test.mjs +0 -51
- package/kernel/plugins/projects/tsconfig.json +0 -17
- package/kernel/plugins/tasks/SKILL.md +0 -341
- package/kernel/plugins/tasks/eslint.config.js +0 -10
- package/kernel/plugins/tasks/package.json +0 -74
- package/kernel/plugins/tasks/schema.json +0 -939
- package/kernel/plugins/tasks/src/api.ts +0 -212
- package/kernel/plugins/tasks/src/client.tsx +0 -23
- package/kernel/plugins/tasks/src/data/parse.ts +0 -544
- package/kernel/plugins/tasks/src/data/task-id.ts +0 -56
- package/kernel/plugins/tasks/src/data/todo-model.ts +0 -795
- package/kernel/plugins/tasks/src/index.ts +0 -34
- package/kernel/plugins/tasks/src/manifest.ts +0 -7
- package/kernel/plugins/tasks/src/pages/task-detail.tsx +0 -48
- package/kernel/plugins/tasks/src/pages/task-list.tsx +0 -88
- package/kernel/plugins/tasks/src/server.ts +0 -447
- package/kernel/plugins/tasks/src/styles.css +0 -9
- package/kernel/plugins/tasks/src/ui/compact-todo-list.tsx +0 -372
- package/kernel/plugins/tasks/src/ui/error-details.tsx +0 -78
- package/kernel/plugins/tasks/src/ui/task-page.tsx +0 -392
- package/kernel/plugins/tasks/src/ui/todo-agent-filter.tsx +0 -36
- package/kernel/plugins/tasks/src/ui/todo-filter-bar.tsx +0 -66
- package/kernel/plugins/tasks/src/ui/todo-item.tsx +0 -494
- package/kernel/plugins/tasks/src/ui/todo-status-filter.tsx +0 -60
- package/kernel/plugins/tasks/src/ui/todo-tag-filter.tsx +0 -48
- package/kernel/plugins/tasks/src/ui/todo-warnings.tsx +0 -106
- package/kernel/plugins/tasks/test/api.test.mjs +0 -99
- package/kernel/plugins/tasks/test/folder-sources.test.mjs +0 -46
- package/kernel/plugins/tasks/test/package.test.mjs +0 -54
- package/kernel/plugins/tasks/test/parse.test.mjs +0 -225
- package/kernel/plugins/tasks/test/server.test.mjs +0 -386
- package/kernel/plugins/tasks/tsconfig.json +0 -17
- package/web/assets/app-DoGr2Zi7.js +0 -2
- package/web/assets/channel-uMEjV8qc.js +0 -1
- package/web/assets/classDiagram-OUVF2IWQ-fbZocVor.js +0 -1
- package/web/assets/classDiagram-v2-EOCWNBFH-fbZocVor.js +0 -1
- package/web/assets/client-C6Nxdfif.js +0 -2
- package/web/assets/client-CXYfBko5.js +0 -17
- package/web/assets/client-CoKtOq-X.js +0 -7
- package/web/assets/client-DyRQz5RU.js +0 -53
- package/web/assets/client-OY6HUzuD.js +0 -98
- package/web/assets/client-YOYWZIFi.js +0 -34
- package/web/assets/command-BugZBGXE.js +0 -38
- package/web/assets/folder-plugin-view-DsWR1zmQ.js +0 -6
- package/web/assets/index-BaRnbYRw.js +0 -1
- package/web/assets/index-BexmLxIP.css +0 -1
- package/web/assets/index-DDirZMgx.js +0 -1
- package/web/assets/note-card-DBfX6gW1.js +0 -6
- package/web/assets/select-D8cqNgcp.js +0 -30
- package/web/assets/stateDiagram-v2-6OUMAXLB-BpeGSCu9.js +0 -1
- package/web/assets/swimlanesDiagram-G3AALYLV-D9RjLKb9.js +0 -8
- package/web/assets/use-folder-filter-LQyXns5Q.js +0 -1
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { TriangleAlert } from "lucide-react"
|
|
2
|
-
|
|
3
|
-
import type { ContentIssue } from "@raggle-ai/ui/components/content-issues"
|
|
4
|
-
import { Button } from "@raggle-ai/ui/components/button"
|
|
5
|
-
import { CopyContextButton } from "@raggle-ai/ui/components/copy-context"
|
|
6
|
-
import {
|
|
7
|
-
Dialog,
|
|
8
|
-
DialogContent,
|
|
9
|
-
DialogDescription,
|
|
10
|
-
DialogHeader,
|
|
11
|
-
DialogTitle,
|
|
12
|
-
DialogTrigger,
|
|
13
|
-
} from "@raggle-ai/ui/components/dialog"
|
|
14
|
-
|
|
15
|
-
/**
|
|
16
|
-
* Advisory warnings stay hidden until wanted: a compact amber button (with
|
|
17
|
-
* the warning count) sits at the right end of the filter bar and opens the
|
|
18
|
-
* full list in a modal, so warnings never push the task list around.
|
|
19
|
-
*/
|
|
20
|
-
export function TodoWarningsTrigger({
|
|
21
|
-
warnings,
|
|
22
|
-
folder,
|
|
23
|
-
}: {
|
|
24
|
-
warnings?: ContentIssue[]
|
|
25
|
-
/** Viewed folder, for the copyable agent fix instructions. */
|
|
26
|
-
folder?: string
|
|
27
|
-
}) {
|
|
28
|
-
if (!warnings?.length) return null
|
|
29
|
-
return (
|
|
30
|
-
<Dialog>
|
|
31
|
-
<DialogTrigger asChild>
|
|
32
|
-
<Button
|
|
33
|
-
className="h-7 shrink-0 gap-1.5 border-amber-300 bg-amber-50 px-2
|
|
34
|
-
text-[13px] text-amber-700 hover:bg-amber-100"
|
|
35
|
-
size="sm"
|
|
36
|
-
variant="outline"
|
|
37
|
-
aria-label={`${warnings.length} todo.json warnings; open details`}
|
|
38
|
-
>
|
|
39
|
-
<TriangleAlert aria-hidden="true" className="size-3.5" />
|
|
40
|
-
Warnings ({warnings.length})
|
|
41
|
-
</Button>
|
|
42
|
-
</DialogTrigger>
|
|
43
|
-
<DialogContent>
|
|
44
|
-
<DialogHeader>
|
|
45
|
-
<div className="flex items-start justify-between gap-3">
|
|
46
|
-
<div>
|
|
47
|
-
<DialogTitle>todo.json warnings</DialogTitle>
|
|
48
|
-
<DialogDescription>
|
|
49
|
-
Advisory only — the task file is still valid and the counts are
|
|
50
|
-
unaffected. Link notes to these tasks to clear the warnings.
|
|
51
|
-
</DialogDescription>
|
|
52
|
-
</div>
|
|
53
|
-
<CopyContextButton
|
|
54
|
-
context={() => todoWarningsContext(warnings, folder)}
|
|
55
|
-
label="Copy fix instructions for an agent"
|
|
56
|
-
variant="outline"
|
|
57
|
-
className="mt-0.5 shrink-0 text-slate-500 hover:text-slate-900"
|
|
58
|
-
/>
|
|
59
|
-
</div>
|
|
60
|
-
</DialogHeader>
|
|
61
|
-
<ul
|
|
62
|
-
className="m-0 max-h-72 list-disc space-y-1.5 overflow-y-auto pl-5
|
|
63
|
-
text-sm text-amber-700"
|
|
64
|
-
>
|
|
65
|
-
{warnings.map((warning, index) => (
|
|
66
|
-
<li key={index}>
|
|
67
|
-
<code className="text-[13px]">{warning.path || "todo.json"}</code>
|
|
68
|
-
{warning.path ? ": " : " — "}
|
|
69
|
-
{warning.message}
|
|
70
|
-
</li>
|
|
71
|
-
))}
|
|
72
|
-
</ul>
|
|
73
|
-
</DialogContent>
|
|
74
|
-
</Dialog>
|
|
75
|
-
)
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/** Agent-facing fix prompt built from the warnings on click (not render). */
|
|
79
|
-
export const todoWarningsContext = (
|
|
80
|
-
warnings: readonly ContentIssue[],
|
|
81
|
-
folder?: string,
|
|
82
|
-
) =>
|
|
83
|
-
[
|
|
84
|
-
"You are fixing advisory warnings in a Kennel Tasks workspace.",
|
|
85
|
-
folder
|
|
86
|
-
? `The viewed folder is: ${folder}`
|
|
87
|
-
: "Work inside the viewed folder.",
|
|
88
|
-
"",
|
|
89
|
-
"The todo.json task files are still valid — these warnings are advisory.",
|
|
90
|
-
"Fix every warning; the context below lists each one.",
|
|
91
|
-
"",
|
|
92
|
-
"Warnings:",
|
|
93
|
-
...warnings.map(
|
|
94
|
-
(warning) => `- ${warning.path || "todo.json"}: ${warning.message}`,
|
|
95
|
-
),
|
|
96
|
-
"",
|
|
97
|
-
"Instructions for the LLM:",
|
|
98
|
-
"- Read every todo.json under the folder: the root todo.json, plus the file\n named before the ': ' separator in each warning path. Do not invent tasks\n and do not change statuses, priorities, or deadlines.",
|
|
99
|
-
'- For "no notes; link the accompanying note file or https reference":\n create or update one accompanying markdown note next to that todo.json,\n then add a notes array entry to the task — a note path relative to the\n folder that owns the todo.json (optionally with a #Section heading) or an\n https URL.',
|
|
100
|
-
"- Connect things where possible: put the related file references (specs,\n evidence, other notes, and the task key such as 'Tracked as\n office/todo.json TODO-7') into the note's linked sections so task and\n note point at each other, and link existing workspace notes instead of\n creating duplicates.",
|
|
101
|
-
'- For "references a missing note file": point the entry at an existing\n file, or create the referenced note when it is clearly the intended\n companion for that task.',
|
|
102
|
-
"- Keep todo.json valid: notes entries must be non-empty strings holding\n a folder-relative path (never absolute or containing '..') or an https URL.",
|
|
103
|
-
"- Verify afterwards: re-open the Tasks tab and confirm the Warnings\n button is gone, or GET /api/plugins/tasks?path=<folder> and confirm the\n warnings array is empty.",
|
|
104
|
-
]
|
|
105
|
-
.filter((line) => line != null)
|
|
106
|
-
.join("\n")
|
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test"
|
|
2
|
-
|
|
3
|
-
import { serializeTodoSource, toggleTaskSource } from "../src/api.ts"
|
|
4
|
-
|
|
5
|
-
const source = `{
|
|
6
|
-
"$schema": "https://raggle.co/schemas/tasks.json",
|
|
7
|
-
"version": 2,
|
|
8
|
-
"id": "todo",
|
|
9
|
-
"title": "Project",
|
|
10
|
-
"tasks": [
|
|
11
|
-
{
|
|
12
|
-
"id": "t1",
|
|
13
|
-
"key": "TODO-1",
|
|
14
|
-
"name": "One",
|
|
15
|
-
"status": "todo",
|
|
16
|
-
"done": false
|
|
17
|
-
},
|
|
18
|
-
{
|
|
19
|
-
"id": "t2",
|
|
20
|
-
"key": "TODO-2",
|
|
21
|
-
"name": "Two",
|
|
22
|
-
"status": "done",
|
|
23
|
-
"done": true,
|
|
24
|
-
"resolution": "completed"
|
|
25
|
-
}
|
|
26
|
-
]
|
|
27
|
-
}
|
|
28
|
-
`
|
|
29
|
-
|
|
30
|
-
function parse(sourceText) {
|
|
31
|
-
return JSON.parse(sourceText)
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
describe("toggleTaskSource", () => {
|
|
35
|
-
test("marks an open task done with resolution and timestamps", () => {
|
|
36
|
-
const { done, next } = toggleTaskSource(source, "t1")
|
|
37
|
-
expect(done).toBe(true)
|
|
38
|
-
const doc = parse(next)
|
|
39
|
-
const task = doc.tasks[0]
|
|
40
|
-
expect(task.done).toBe(true)
|
|
41
|
-
expect(task.status).toBe("done")
|
|
42
|
-
expect(task.resolution).toBe("completed")
|
|
43
|
-
expect(typeof task.completedAt).toBe("string")
|
|
44
|
-
expect(typeof task.updatedAt).toBe("string")
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
test("matches a task by key", () => {
|
|
48
|
-
const { done } = toggleTaskSource(source, "TODO-1")
|
|
49
|
-
expect(done).toBe(true)
|
|
50
|
-
})
|
|
51
|
-
|
|
52
|
-
test("reopens a done task back to todo", () => {
|
|
53
|
-
const { done, next } = toggleTaskSource(source, "t2")
|
|
54
|
-
expect(done).toBe(false)
|
|
55
|
-
const task = parse(next).tasks[1]
|
|
56
|
-
expect(task.done).toBe(false)
|
|
57
|
-
expect(task.status).toBe("todo")
|
|
58
|
-
expect(task.resolution).toBe(null)
|
|
59
|
-
expect(task.completedAt).toBe(null)
|
|
60
|
-
expect(typeof task.updatedAt).toBe("string")
|
|
61
|
-
})
|
|
62
|
-
|
|
63
|
-
test("leaves other tasks untouched", () => {
|
|
64
|
-
const { next } = toggleTaskSource(source, "t1")
|
|
65
|
-
const task = parse(next).tasks[1]
|
|
66
|
-
expect(task.id).toBe("t2")
|
|
67
|
-
expect(task.status).toBe("done")
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
test("throws not-found for an unknown id", () => {
|
|
71
|
-
expect(() => toggleTaskSource(source, "nope")).toThrow(
|
|
72
|
-
/Task not found: nope/,
|
|
73
|
-
)
|
|
74
|
-
})
|
|
75
|
-
|
|
76
|
-
test("throws bad-request for an invalid document", () => {
|
|
77
|
-
expect(() => toggleTaskSource('{"tasks": [', "t1")).toThrow(
|
|
78
|
-
/not valid JSON/,
|
|
79
|
-
)
|
|
80
|
-
})
|
|
81
|
-
})
|
|
82
|
-
|
|
83
|
-
describe("serializeTodoSource", () => {
|
|
84
|
-
test("preserves the original indent and trailing newline", () => {
|
|
85
|
-
const doc = parse(source)
|
|
86
|
-
const next = serializeTodoSource(doc, source)
|
|
87
|
-
expect(next.endsWith("\n")).toBe(true)
|
|
88
|
-
expect(next.includes('\n "id": "t1"')).toBe(true)
|
|
89
|
-
expect(JSON.parse(next)).toEqual(JSON.parse(source))
|
|
90
|
-
})
|
|
91
|
-
|
|
92
|
-
test("falls back to two-space indent without a trailing newline", () => {
|
|
93
|
-
const compact = '{"tasks":[{"id":"t1","name":"One"}]}'
|
|
94
|
-
const next = serializeTodoSource(parse(compact), compact)
|
|
95
|
-
expect(next.endsWith("\n")).toBe(false)
|
|
96
|
-
expect(next.includes('\n "tasks"')).toBe(true)
|
|
97
|
-
expect(JSON.parse(next)).toEqual(JSON.parse(compact))
|
|
98
|
-
})
|
|
99
|
-
})
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises"
|
|
2
|
-
import { tmpdir } from "node:os"
|
|
3
|
-
import { join } from "node:path"
|
|
4
|
-
import { expect, test } from "bun:test"
|
|
5
|
-
|
|
6
|
-
import { createTasksPlugin } from "../src/server.ts"
|
|
7
|
-
|
|
8
|
-
test("a nested workspace merges task files into folder-tagged sources", async () => {
|
|
9
|
-
const folder = await mkdtemp(join(tmpdir(), "kennel-tasks-plugin-"))
|
|
10
|
-
try {
|
|
11
|
-
const nested = join(folder, "clients", "client-one")
|
|
12
|
-
await mkdir(nested, { recursive: true })
|
|
13
|
-
await writeFile(
|
|
14
|
-
join(folder, "todo.json"),
|
|
15
|
-
JSON.stringify({
|
|
16
|
-
title: "Example Workspace",
|
|
17
|
-
people: [{ id: "member", name: "Team Member" }],
|
|
18
|
-
tasks: [{ id: "root1", name: "Root", done: false }],
|
|
19
|
-
}),
|
|
20
|
-
)
|
|
21
|
-
await writeFile(
|
|
22
|
-
join(nested, "todo.json"),
|
|
23
|
-
JSON.stringify({
|
|
24
|
-
tasks: [{ id: "n1", name: "Nested", done: true }],
|
|
25
|
-
}),
|
|
26
|
-
)
|
|
27
|
-
const plugin = createTasksPlugin()
|
|
28
|
-
const detection = await plugin.detect({ folder })
|
|
29
|
-
expect(detection.status).toBe("available")
|
|
30
|
-
expect(detection.count).toBe(2)
|
|
31
|
-
const result = await plugin.handle({ folder }, "")
|
|
32
|
-
expect(result.kind).toBe("available")
|
|
33
|
-
const dataset = result.data.dataset
|
|
34
|
-
expect(dataset.people).toEqual([{ id: "member", name: "Team Member" }])
|
|
35
|
-
expect(dataset.tasks).toHaveLength(2)
|
|
36
|
-
expect(dataset.summary).toEqual({ completed: 1, open: 1, sources: 2 })
|
|
37
|
-
expect(dataset.tasks.map((task) => task.source.id).sort()).toEqual(
|
|
38
|
-
[folder, nested].sort(),
|
|
39
|
-
)
|
|
40
|
-
expect(dataset.sources.map((source) => source.id).sort()).toEqual(
|
|
41
|
-
[folder, nested].sort(),
|
|
42
|
-
)
|
|
43
|
-
} finally {
|
|
44
|
-
await rm(folder, { recursive: true, force: true })
|
|
45
|
-
}
|
|
46
|
-
})
|
|
@@ -1,54 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict"
|
|
2
|
-
import { readFile } from "node:fs/promises"
|
|
3
|
-
import test from "node:test"
|
|
4
|
-
|
|
5
|
-
const packageJson = JSON.parse(
|
|
6
|
-
await readFile(new URL("../package.json", import.meta.url), "utf8"),
|
|
7
|
-
)
|
|
8
|
-
|
|
9
|
-
test("tasks package is ready for public npm publishing", () => {
|
|
10
|
-
assert.equal(packageJson.private, undefined)
|
|
11
|
-
assert.equal(packageJson.version, "0.1.0")
|
|
12
|
-
assert.equal(packageJson.license, "MIT")
|
|
13
|
-
assert.equal(packageJson.publishConfig?.access, "public")
|
|
14
|
-
assert.equal(packageJson.repository?.directory, "plugins/tasks")
|
|
15
|
-
assert.deepEqual(packageJson.files, ["src"])
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
test("tasks package ships the standard plugin script set", () => {
|
|
19
|
-
for (const script of [
|
|
20
|
-
"test",
|
|
21
|
-
"typecheck",
|
|
22
|
-
"format",
|
|
23
|
-
"lint",
|
|
24
|
-
"clean",
|
|
25
|
-
"pack:check",
|
|
26
|
-
"publish:public:dry-run",
|
|
27
|
-
]) {
|
|
28
|
-
assert.ok(
|
|
29
|
-
typeof packageJson.scripts?.[script] === "string",
|
|
30
|
-
`missing script "${script}"`,
|
|
31
|
-
)
|
|
32
|
-
}
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
test("tasks package exposes API routes, parser, manifest, and split server/client entry points", () => {
|
|
36
|
-
assert.deepEqual(Object.keys(packageJson.exports).sort(), [
|
|
37
|
-
".",
|
|
38
|
-
"./api",
|
|
39
|
-
"./client",
|
|
40
|
-
"./manifest",
|
|
41
|
-
"./parse",
|
|
42
|
-
"./server",
|
|
43
|
-
"./todo-model",
|
|
44
|
-
"./todo-model/styles.css",
|
|
45
|
-
])
|
|
46
|
-
})
|
|
47
|
-
|
|
48
|
-
test("tasks client entry does not import the Node server entry", async () => {
|
|
49
|
-
const clientSource = await readFile(
|
|
50
|
-
new URL("../src/client.tsx", import.meta.url),
|
|
51
|
-
"utf8",
|
|
52
|
-
)
|
|
53
|
-
assert.doesNotMatch(clientSource, /node:|\/server"/)
|
|
54
|
-
})
|
|
@@ -1,225 +0,0 @@
|
|
|
1
|
-
import { expect, test } from "bun:test"
|
|
2
|
-
|
|
3
|
-
import {
|
|
4
|
-
parseTodoSource,
|
|
5
|
-
starterTodoSource,
|
|
6
|
-
todoIssueSummary,
|
|
7
|
-
validateTodoFile,
|
|
8
|
-
} from "../src/data/parse.ts"
|
|
9
|
-
|
|
10
|
-
test("parses a valid v2 document", () => {
|
|
11
|
-
const parsed = parseTodoSource(
|
|
12
|
-
JSON.stringify({
|
|
13
|
-
$schema: "https://raggle.co/schemas/tasks.json",
|
|
14
|
-
version: 2,
|
|
15
|
-
id: "todo",
|
|
16
|
-
title: "Local AI Tasks",
|
|
17
|
-
tasks: [
|
|
18
|
-
{
|
|
19
|
-
id: "t1",
|
|
20
|
-
key: "TODO-1",
|
|
21
|
-
name: "Follow up",
|
|
22
|
-
status: "todo",
|
|
23
|
-
priority: 2,
|
|
24
|
-
deadline: "2026-12-31",
|
|
25
|
-
acceptanceCriteria: [{ id: "ac-1", text: "Reply", done: false }],
|
|
26
|
-
},
|
|
27
|
-
],
|
|
28
|
-
}),
|
|
29
|
-
)
|
|
30
|
-
expect(parsed.ok).toBe(true)
|
|
31
|
-
if (!parsed.ok) return
|
|
32
|
-
expect(parsed.document.tasks).toHaveLength(1)
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
test("reports invalid JSON with a readable detail", () => {
|
|
36
|
-
const parsed = parseTodoSource('{"tasks": [')
|
|
37
|
-
expect(parsed.ok).toBe(false)
|
|
38
|
-
if (parsed.ok) return
|
|
39
|
-
expect(parsed.issues).toHaveLength(1)
|
|
40
|
-
expect(parsed.issues[0].path).toBe("#")
|
|
41
|
-
expect(parsed.issues[0].message).toContain("not valid JSON")
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
test("non-object documents are rejected", () => {
|
|
45
|
-
expect(validateTodoFile(null)).toEqual([
|
|
46
|
-
{ path: "#", message: "todo.json must contain a JSON object" },
|
|
47
|
-
])
|
|
48
|
-
expect(validateTodoFile([1, 2])).toEqual([
|
|
49
|
-
{ path: "#", message: "todo.json must contain a JSON object" },
|
|
50
|
-
])
|
|
51
|
-
})
|
|
52
|
-
|
|
53
|
-
test("flags bad version and missing tasks array", () => {
|
|
54
|
-
const issues = validateTodoFile({ version: 3 })
|
|
55
|
-
expect(issues).toContainEqual({
|
|
56
|
-
path: "version",
|
|
57
|
-
message: "must be 1 or 2; got 3",
|
|
58
|
-
})
|
|
59
|
-
expect(issues).toContainEqual({
|
|
60
|
-
path: "tasks",
|
|
61
|
-
message: "must be an array of tasks",
|
|
62
|
-
})
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
test("flags a task without a name", () => {
|
|
66
|
-
const issues = validateTodoFile({ version: 2, tasks: [{ id: "t1" }] })
|
|
67
|
-
expect(issues).toContainEqual({
|
|
68
|
-
path: "tasks[0].name",
|
|
69
|
-
message: "required; a task needs a non-empty name",
|
|
70
|
-
})
|
|
71
|
-
})
|
|
72
|
-
|
|
73
|
-
test("flags an unknown status enum value", () => {
|
|
74
|
-
const issues = validateTodoFile({
|
|
75
|
-
version: 2,
|
|
76
|
-
tasks: [{ id: "t1", name: "One", status: "in-progress" }],
|
|
77
|
-
})
|
|
78
|
-
expect(issues).toContainEqual({
|
|
79
|
-
path: "tasks[0].status",
|
|
80
|
-
message:
|
|
81
|
-
'must be one of todo, in_progress, blocked, review, done, canceled; got "in-progress"',
|
|
82
|
-
})
|
|
83
|
-
})
|
|
84
|
-
|
|
85
|
-
test("flags an unknown type enum value", () => {
|
|
86
|
-
const issues = validateTodoFile({
|
|
87
|
-
version: 2,
|
|
88
|
-
tasks: [{ id: "t1", name: "One", type: "story" }],
|
|
89
|
-
})
|
|
90
|
-
expect(issues).toContainEqual({
|
|
91
|
-
path: "tasks[0].type",
|
|
92
|
-
message:
|
|
93
|
-
'must be one of task, bug, feature, chore, spike, epic, milestone; got "story"',
|
|
94
|
-
})
|
|
95
|
-
})
|
|
96
|
-
|
|
97
|
-
test("flags out-of-range priority and bad deadline", () => {
|
|
98
|
-
const issues = validateTodoFile({
|
|
99
|
-
version: 2,
|
|
100
|
-
tasks: [{ id: "t1", name: "One", priority: 9, deadline: "31/12/2026" }],
|
|
101
|
-
})
|
|
102
|
-
expect(issues).toContainEqual({
|
|
103
|
-
path: "tasks[0].priority",
|
|
104
|
-
message: "must be an integer between 1 and 5",
|
|
105
|
-
})
|
|
106
|
-
expect(issues).toContainEqual({
|
|
107
|
-
path: "tasks[0].deadline",
|
|
108
|
-
message: "must be an ISO date (YYYY-MM-DD)",
|
|
109
|
-
})
|
|
110
|
-
})
|
|
111
|
-
|
|
112
|
-
test("flags duplicate task ids", () => {
|
|
113
|
-
const issues = validateTodoFile({
|
|
114
|
-
version: 2,
|
|
115
|
-
tasks: [
|
|
116
|
-
{ id: "t1", name: "One" },
|
|
117
|
-
{ id: "t1", name: "Two" },
|
|
118
|
-
],
|
|
119
|
-
})
|
|
120
|
-
expect(issues).toContainEqual({
|
|
121
|
-
path: "tasks[1].id",
|
|
122
|
-
message: 'duplicate task id "t1"',
|
|
123
|
-
})
|
|
124
|
-
expect(issues).not.toContainEqual(
|
|
125
|
-
expect.objectContaining({ path: "tasks[0].id" }),
|
|
126
|
-
)
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
test("flags check items missing required fields", () => {
|
|
130
|
-
const issues = validateTodoFile({
|
|
131
|
-
version: 2,
|
|
132
|
-
tasks: [
|
|
133
|
-
{
|
|
134
|
-
id: "t1",
|
|
135
|
-
name: "One",
|
|
136
|
-
steps: [{ id: "s1", text: "Do it" }],
|
|
137
|
-
acceptanceCriteria: "nope",
|
|
138
|
-
},
|
|
139
|
-
],
|
|
140
|
-
})
|
|
141
|
-
expect(issues).toContainEqual({
|
|
142
|
-
path: "tasks[0].steps[0].done",
|
|
143
|
-
message: "required boolean",
|
|
144
|
-
})
|
|
145
|
-
expect(issues).toContainEqual({
|
|
146
|
-
path: "tasks[0].acceptanceCriteria",
|
|
147
|
-
message: "must be an array of check items",
|
|
148
|
-
})
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
test("flags invalid links", () => {
|
|
152
|
-
const issues = validateTodoFile({
|
|
153
|
-
version: 2,
|
|
154
|
-
tasks: [
|
|
155
|
-
{
|
|
156
|
-
id: "t1",
|
|
157
|
-
name: "One",
|
|
158
|
-
links: [
|
|
159
|
-
{
|
|
160
|
-
id: "l1",
|
|
161
|
-
provider: "github",
|
|
162
|
-
kind: "issue",
|
|
163
|
-
relation: "references",
|
|
164
|
-
url: "git@github.com:org/repo.git",
|
|
165
|
-
},
|
|
166
|
-
{
|
|
167
|
-
id: "l2",
|
|
168
|
-
provider: "linear",
|
|
169
|
-
kind: "issue",
|
|
170
|
-
relation: "references",
|
|
171
|
-
url: "https://example.com/x",
|
|
172
|
-
},
|
|
173
|
-
],
|
|
174
|
-
},
|
|
175
|
-
],
|
|
176
|
-
})
|
|
177
|
-
expect(issues).toContainEqual({
|
|
178
|
-
path: "tasks[0].links[0].url",
|
|
179
|
-
message: "is not a valid absolute URL",
|
|
180
|
-
})
|
|
181
|
-
expect(issues).toContainEqual({
|
|
182
|
-
path: "tasks[0].links[1].url",
|
|
183
|
-
message: "linear links must use linear.app",
|
|
184
|
-
})
|
|
185
|
-
})
|
|
186
|
-
|
|
187
|
-
test("flags relations that reference unknown task ids", () => {
|
|
188
|
-
const issues = validateTodoFile({
|
|
189
|
-
version: 2,
|
|
190
|
-
tasks: [
|
|
191
|
-
{ id: "t1", name: "One" },
|
|
192
|
-
{
|
|
193
|
-
id: "t2",
|
|
194
|
-
name: "Two",
|
|
195
|
-
relations: [{ type: "blocked_by", targetTaskId: "missing" }],
|
|
196
|
-
},
|
|
197
|
-
],
|
|
198
|
-
})
|
|
199
|
-
expect(issues).toContainEqual({
|
|
200
|
-
path: "tasks[1].relations[0].targetTaskId",
|
|
201
|
-
message: 'references unknown task id "missing"',
|
|
202
|
-
})
|
|
203
|
-
})
|
|
204
|
-
|
|
205
|
-
test("summary is compact for nav badges", () => {
|
|
206
|
-
expect(
|
|
207
|
-
todoIssueSummary([{ path: "tasks[0].name", message: "required" }]),
|
|
208
|
-
).toBe("tasks[0].name: required")
|
|
209
|
-
expect(
|
|
210
|
-
todoIssueSummary([
|
|
211
|
-
{ path: "tasks[0].name", message: "required" },
|
|
212
|
-
{ path: "tasks[1].status", message: "bad status" },
|
|
213
|
-
]),
|
|
214
|
-
).toBe("2 problems found in todo.json (first: tasks[0].name — required)")
|
|
215
|
-
})
|
|
216
|
-
|
|
217
|
-
test("starter todo source validates and uses the folder name as title", () => {
|
|
218
|
-
const source = starterTodoSource("/tmp/kennel-example/local-ai")
|
|
219
|
-
const parsed = parseTodoSource(source)
|
|
220
|
-
expect(parsed.ok).toBe(true)
|
|
221
|
-
if (!parsed.ok) return
|
|
222
|
-
expect(parsed.document.tasks).toHaveLength(1)
|
|
223
|
-
expect(parsed.document.version).toBe(2)
|
|
224
|
-
expect(parsed.document.title).toBe("local-ai")
|
|
225
|
-
})
|