@dsdenes/devboard 0.1.560 → 0.1.563
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/AGENTS.md +59 -0
- package/README.md +2497 -26
- package/bin/devboard.mjs +352 -118
- package/client/App.svelte +523 -0
- package/client/FeedbackOverlay.svelte +1125 -0
- package/client/FlowMode.svelte +1015 -0
- package/client/app.css +3922 -0
- package/client/env.d.ts +23 -0
- package/client/lib/commands.ts +836 -0
- package/client/lib/components/ActivityBar.svelte +295 -0
- package/client/lib/components/AdrTable.svelte +241 -0
- package/client/lib/components/CommandPalette.svelte +198 -0
- package/client/lib/components/ConceptKind.svelte +66 -0
- package/client/lib/components/ConfirmDialog.svelte +62 -0
- package/client/lib/components/DataTable.svelte +243 -0
- package/client/lib/components/DetailSplit.svelte +68 -0
- package/client/lib/components/DiscoverOverview.svelte +898 -0
- package/client/lib/components/DiscoverWorkspace.svelte +437 -0
- package/client/lib/components/DraftsPage.svelte +186 -0
- package/client/lib/components/GoalWorkspace.svelte +1042 -0
- package/client/lib/components/IssuesAgentFocus.svelte +117 -0
- package/client/lib/components/IssuesAgentLog.svelte +50 -0
- package/client/lib/components/IssuesQuestion.svelte +95 -0
- package/client/lib/components/IssuesWorkspace.svelte +577 -0
- package/client/lib/components/MermaidDiagram.svelte +89 -0
- package/client/lib/components/MonacoEditor.svelte +190 -0
- package/client/lib/components/NotificationRegion.svelte +195 -0
- package/client/lib/components/PaneHandle.svelte +25 -0
- package/client/lib/components/ScoutIssues.svelte +215 -0
- package/client/lib/components/ScoutWorkspace.svelte +180 -0
- package/client/lib/components/Sidebar.svelte +622 -0
- package/client/lib/components/StatusBar.svelte +118 -0
- package/client/lib/components/Terminal.svelte +193 -0
- package/client/lib/components/WatchdogWorkspace.svelte +537 -0
- package/client/lib/components/ui/button.svelte +56 -0
- package/client/lib/components/ui/icon-button.svelte +56 -0
- package/client/lib/components/ui/input.svelte +19 -0
- package/client/lib/components/ui/page-header.svelte +120 -0
- package/client/lib/components/ui/stage-track.svelte +141 -0
- package/client/lib/components/watchdog-indicator.svelte +189 -0
- package/client/lib/connection.svelte.ts +210 -0
- package/client/lib/dependency-diagram.test.ts +90 -0
- package/client/lib/dependency-diagram.ts +75 -0
- package/client/lib/discover-format.test.ts +265 -0
- package/client/lib/discover-format.ts +394 -0
- package/client/lib/discover.svelte.ts +412 -0
- package/client/lib/draft-format.test.ts +48 -0
- package/client/lib/draft-format.ts +13 -0
- package/client/lib/flow-save.test.ts +148 -0
- package/client/lib/flow.svelte.ts +445 -0
- package/client/lib/goal-reconnect.test.ts +282 -0
- package/client/lib/goal.svelte.ts +652 -0
- package/client/lib/issues.svelte.ts +439 -0
- package/client/lib/layout.svelte.ts +234 -0
- package/client/lib/notification-issue.test.ts +53 -0
- package/client/lib/notification-issue.ts +27 -0
- package/client/lib/notifications.svelte.ts +62 -0
- package/client/lib/poll-status.test.ts +141 -0
- package/client/lib/poll-status.ts +88 -0
- package/client/lib/record-status.test.ts +19 -0
- package/client/lib/record-status.ts +25 -0
- package/client/lib/scout.svelte.ts +164 -0
- package/client/lib/skill-document.test.ts +77 -0
- package/client/lib/skill-document.ts +57 -0
- package/client/lib/status-tone.test.ts +40 -0
- package/client/lib/status-tone.ts +33 -0
- package/client/lib/types.ts +578 -0
- package/client/lib/utils.ts +6 -0
- package/client/lib/viewport.svelte.ts +20 -0
- package/client/lib/watchdog.svelte.ts +69 -0
- package/client/main.ts +7 -0
- package/dist/apple-touch-icon.png +0 -0
- package/dist/assets/abnfDiagram-VCTEODGH-CmBSMArZ.js +1 -0
- package/dist/assets/arc-BZgCSX2H.js +1 -0
- package/dist/assets/architecture-7GRP2DOG-CF9MHlfo.js +1 -0
- package/dist/assets/architectureDiagram-5GKGNRK7-DzWB6xiQ.js +36 -0
- package/dist/assets/array-BifhSqXX.js +1 -0
- package/dist/assets/blockDiagram-I7D4REHJ-BtmGJ0hn.js +129 -0
- package/dist/assets/c4Diagram-7LVT6UL2-D5CHrrRX.js +38 -0
- package/dist/assets/channel-CgM4lcUW.js +1 -0
- package/dist/assets/chunk-2Q5K7J3B-C1jixKkw.js +1 -0
- package/dist/assets/chunk-4HAMMTFA-DeIufjFl.js +62 -0
- package/dist/assets/chunk-5VM5RSS4-ZNzvKenW.js +15 -0
- package/dist/assets/chunk-75Z2AOVW-DbtzyKUg.js +2 -0
- package/dist/assets/chunk-DU6HZSFF-C0fdbx3K.js +127 -0
- package/dist/assets/chunk-F27PBJKO-HObL2IvF.js +1 -0
- package/dist/assets/chunk-FOHPRMQF-DD4dKubD.js +161 -0
- package/dist/assets/chunk-GMAD6QVW-fg8wvtPY.js +72 -0
- package/dist/assets/chunk-GVQU2GXP-C0TaHOrD.js +1 -0
- package/dist/assets/chunk-IMKFNOWR-BVoIdx20.js +231 -0
- package/dist/assets/chunk-JWPE2WC7-DVXcaiue.js +1 -0
- package/dist/assets/chunk-L3NEJ4N5-B_kmqfk5.js +1 -0
- package/dist/assets/chunk-OSK3NFVY-DO0vhxHH.js +10 -0
- package/dist/assets/chunk-P2QGCYS3-CjljLvFj.js +1 -0
- package/dist/assets/chunk-POPQ4Y6H-BU0C7tw6.js +1 -0
- package/dist/assets/chunk-PWAF6VOD-Dn0OjBt0.js +1 -0
- package/dist/assets/chunk-SHT3W25Y-CMCZE2xx.js +168 -0
- package/dist/assets/chunk-SVP7TREG-BIRmRy_W.js +88 -0
- package/dist/assets/chunk-TICWLB2K-C1bc2CDE.js +206 -0
- package/dist/assets/chunk-XXDRQBXY-Cc38QUmR.js +1 -0
- package/dist/assets/chunk-Y2CYZVJY-DsF7k-Jl.js +1 -0
- package/dist/assets/classDiagram-ZZMXUADV-BDh5w6Nc.js +1 -0
- package/dist/assets/classDiagram-v2-VYDZK3BY-BDh5w6Nc.js +1 -0
- package/dist/assets/cose-bilkent-JH36ORCC-B7pU7qhM.js +1 -0
- package/dist/assets/cynefin-OW5HDTMX-76dswSqS.js +1 -0
- package/dist/assets/cynefinDiagram-5FMLGOSQ-2kS4q01d.js +62 -0
- package/dist/assets/cytoscape.esm-BegBNzOV.js +321 -0
- package/dist/assets/dagre-D7JM2Yhv.js +1 -0
- package/dist/assets/dagre-GXQ25YYZ-B5Y21TQM.js +4 -0
- package/dist/assets/defaultLocale-BFoDCU3G.js +1 -0
- package/dist/assets/diagram-S7CK7UJ4-CY6td7Et.js +30 -0
- package/dist/assets/diagram-UQ7AKVKN-DmDqnGZf.js +41 -0
- package/dist/assets/diagram-VSXAHHWV-KGLWOOcE.js +3 -0
- package/dist/assets/diagram-VX7I27RA-D5syUraJ.js +24 -0
- package/dist/assets/diagram-Z3DM3KII-Dtrbs1Om.js +24 -0
- package/dist/assets/dist-Dce-2YbA.js +1 -0
- package/dist/assets/ebnfDiagram-PWID7BFC-CQpJLtU3.js +1 -0
- package/dist/assets/editor.worker-BmdWdrHZ.js +33 -0
- package/dist/assets/erDiagram-RLTQ6QDP-cMTzKLu_.js +99 -0
- package/dist/assets/eventmodeling-NTZA5JFV-CMzLdNaL.js +1 -0
- package/dist/assets/flowDiagram-HODETNUW-BMUajB-G.js +1 -0
- package/dist/assets/ganttDiagram-EL5Y4UJY-DseBS5Fs.js +292 -0
- package/dist/assets/gitGraph-4MIJSDKK-CUQZRpxJ.js +1 -0
- package/dist/assets/gitGraphDiagram-WWUBYQGX-CfMfsJ3b.js +106 -0
- package/dist/assets/graphlib-DS17s2tU.js +1 -0
- package/dist/assets/index-ByQ-XX38.css +1 -0
- package/dist/assets/index-CIf5TpfY.js +214 -0
- package/dist/assets/info-A6RAGUB7-D3nAKZ8M.js +1 -0
- package/dist/assets/infoDiagram-27XIBGKW-zu1O0WOy.js +2 -0
- package/dist/assets/init-C-OQMol4.js +1 -0
- package/dist/assets/ishikawaDiagram-5VMMS53U-DKC7BO9p.js +70 -0
- package/dist/assets/journeyDiagram-3NMN7TZE-Ld5mXse7.js +139 -0
- package/dist/assets/kanban-definition-UXKFOSKX-CH7_QlHL.js +89 -0
- package/dist/assets/katex-DJhrJSbw.js +257 -0
- package/dist/assets/line-N9XiCREw.js +1 -0
- package/dist/assets/linear-C9hnqmJv.js +1 -0
- package/dist/assets/mermaid-parser.core-DgPwV5Bh.js +7 -0
- package/dist/assets/mermaid.core-BKqynXLR.js +44 -0
- package/dist/assets/mindmap-definition-YA3MSWOX-DkiL-wiW.js +96 -0
- package/dist/assets/monaco-DjpsE8to.css +1 -0
- package/dist/assets/monaco-DqzJaNIe.js +908 -0
- package/dist/assets/ordinal-BDEzSJ7C.js +1 -0
- package/dist/assets/packet-AYTQ26CC-DuMTvpEJ.js +1 -0
- package/dist/assets/path-fybaL0A-.js +1 -0
- package/dist/assets/pegDiagram-XKGWAZYB-DvMl_xSe.js +1 -0
- package/dist/assets/pie-WAS4IAKB-Da0xPaD8.js +1 -0
- package/dist/assets/pieDiagram-E7YTZNPT-amANNRJY.js +39 -0
- package/dist/assets/quadrantDiagram-AXDQQJYC-BrgvDMnk.js +7 -0
- package/dist/assets/radar-RG4KPBEZ-Dz3Ih5-N.js +1 -0
- package/dist/assets/railroad-74A4TZTK-CKMW-IV2.js +1 -0
- package/dist/assets/railroad-abnf-HS5TGJTU-ChqmSWSF.js +1 -0
- package/dist/assets/railroad-ebnf-LZEXJU2U-V3R4fFdb.js +1 -0
- package/dist/assets/railroad-peg-WCYAUIDC-B-RuhUQW.js +1 -0
- package/dist/assets/railroadDiagram-O6MQD6OU-FjG-6OLu.js +1 -0
- package/dist/assets/requirementDiagram-BXWQKSXE-Cnj8RfE4.js +84 -0
- package/dist/assets/rolldown-runtime-XKyaHtl9.js +1 -0
- package/dist/assets/rough.esm-Dy-Kn_BL.js +1 -0
- package/dist/assets/sankeyDiagram-P5KCCOFB-DMu1fnEh.js +40 -0
- package/dist/assets/sequenceDiagram-WJ2MYXX4-CtCtaRpW.js +162 -0
- package/dist/assets/sizeCapture-INFHLROL-3uHXrFqA.js +1 -0
- package/dist/assets/src-CGp3bfio.js +1 -0
- package/dist/assets/stateDiagram-D77RDMKH-xcJbLNhz.js +1 -0
- package/dist/assets/stateDiagram-v2-MP3YSRHH-DYHrQmJJ.js +1 -0
- package/dist/assets/swimlanes-42K2YHIH-BwLln_ga.js +1 -0
- package/dist/assets/swimlanesDiagram-VR7AAH4N-BcDfUQ0A.js +8 -0
- package/dist/assets/timeline-definition-24CTP7MA-ucNGsUUz.js +120 -0
- package/dist/assets/treeView-Q6P3EWNA-3uOomRue.js +1 -0
- package/dist/assets/treemap-WGGIJYW6-iGK7vwID.js +1 -0
- package/dist/assets/vennDiagram-4TSXK5OY-B9LgGe16.js +34 -0
- package/dist/assets/wardley-WFR3VGLG-FupMOf6k.js +1 -0
- package/dist/assets/wardleyDiagram-VM6X3IG4-CGZdEXM2.js +78 -0
- package/dist/assets/xterm-BrP-ENHg.css +1 -0
- package/dist/assets/xterm-C8zLTNGk.js +16 -0
- package/dist/assets/xychartDiagram-S5SC5T6Z-DafZrSBJ.js +7 -0
- package/dist/favicon.ico +0 -0
- package/dist/favicon.svg +6 -0
- package/dist/index.html +34 -0
- package/extensions/implement-goal-issue.ts +368 -0
- package/extensions/implement-next-epic.ts +835 -0
- package/index.html +28 -0
- package/install.sh +111 -0
- package/issues/control.ts +91 -0
- package/issues/guards.ts +12 -0
- package/issues/ledger.ts +122 -0
- package/issues/legacy.ts +68 -0
- package/issues/lock.ts +305 -0
- package/issues/paths.ts +76 -0
- package/issues/preflight.ts +189 -0
- package/issues/repository.ts +203 -0
- package/issues/scout/actions.ts +255 -0
- package/issues/scout/control.ts +80 -0
- package/issues/scout/github.ts +358 -0
- package/issues/scout/paths.ts +129 -0
- package/issues/scout/preflight.ts +277 -0
- package/issues/scout/recommendation.ts +400 -0
- package/issues/scout/scan.ts +143 -0
- package/issues/scout/state.ts +448 -0
- package/issues/scout/worker.ts +1282 -0
- package/issues/state.ts +584 -0
- package/issues/tracker.ts +271 -0
- package/issues/worker.ts +1065 -0
- package/lib/dependencies.ts +61 -0
- package/lib/forge-auth.ts +953 -0
- package/lib/forge.ts +141 -0
- package/lib/gitea.ts +729 -0
- package/lib/github-release.ts +318 -0
- package/lib/inventory.test.ts +79 -0
- package/lib/inventory.ts +53 -0
- package/lib/release.test.ts +269 -0
- package/lib/release.ts +801 -0
- package/omp/commands/adr-resolve.md +8 -0
- package/omp/commands/discover.md +6 -0
- package/omp/commands/epic-next.md +8 -0
- package/omp/commands/flow-change.md +8 -0
- package/omp/commands/flow-goal.md +8 -0
- package/omp/commands/flow-implement.md +8 -0
- package/omp/commands/flow-widget.md +8 -0
- package/omp/commands/goal-draft.md +8 -0
- package/omp/commands/goal-outcome.md +8 -0
- package/omp/commands/implement-issue.md +8 -0
- package/omp/commands/prepare-skills.md +6 -0
- package/omp/commands/scout-recommendations.md +8 -0
- package/omp/commands/setup-devboard.md +24 -0
- package/omp/config.yml +12 -0
- package/omp/extensions/implement-goal-issue.ts +2 -0
- package/omp/extensions/implement-next-epic.ts +2 -0
- package/omp/mcp.json +9 -0
- package/package.json +86 -7
- package/prompts/adr-resolve.md +130 -0
- package/prompts/discover.md +541 -0
- package/prompts/epic-next.md +533 -0
- package/prompts/flow-change.md +76 -0
- package/prompts/flow-goal.md +126 -0
- package/prompts/flow-implement.md +103 -0
- package/prompts/flow-widget.md +54 -0
- package/prompts/goal-draft.md +147 -0
- package/prompts/goal-outcome.md +87 -0
- package/prompts/implement-issue.md +66 -0
- package/prompts/prepare-skills.md +142 -0
- package/prompts/scout-recommendations.md +143 -0
- package/public/apple-touch-icon.png +0 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.svg +6 -0
- package/server/acp.ts +540 -0
- package/server/activity.test.ts +96 -0
- package/server/activity.ts +48 -0
- package/server/app.test.ts +882 -0
- package/server/app.ts +6179 -0
- package/server/ask.test.ts +543 -0
- package/server/ask.ts +589 -0
- package/server/dependencies.test.ts +118 -0
- package/server/discover-remote.test.ts +1602 -0
- package/server/discover-sheet.test.ts +176 -0
- package/server/discover.test.ts +1510 -0
- package/server/discover.ts +3198 -0
- package/server/feedback.test.ts +458 -0
- package/server/feedback.ts +369 -0
- package/server/fixtures/adr-source.ts +71 -0
- package/server/fixtures/hang-download.ts +14 -0
- package/server/fixtures/stub-agent.ts +1870 -0
- package/server/fixtures/stub-forge.ts +141 -0
- package/server/fixtures/stub-gh.ts +57 -0
- package/server/flow-pr.test.ts +976 -0
- package/server/flow.test.ts +1632 -0
- package/server/flow.ts +2655 -0
- package/server/forge-pr.ts +387 -0
- package/server/goal-creation.test.ts +957 -0
- package/server/goal-draft.test.ts +348 -0
- package/server/goal-drafts.ts +449 -0
- package/server/goal-proposal.test.ts +479 -0
- package/server/goal-proposal.ts +232 -0
- package/server/goal-revision.test.ts +613 -0
- package/server/goal-slug.test.ts +56 -0
- package/server/goal-slug.ts +96 -0
- package/server/goal.ts +20 -0
- package/server/index.ts +147 -0
- package/server/issue-close.test.ts +100 -0
- package/server/issue-close.ts +178 -0
- package/server/issues.test.ts +167 -0
- package/server/issues.ts +479 -0
- package/server/journal.test.ts +350 -0
- package/server/journal.ts +329 -0
- package/server/lifecycle-pause.test.ts +1795 -0
- package/server/lifecycle.test.ts +1879 -0
- package/server/parallel-lifecycle.test.ts +1029 -0
- package/server/persistence-restart.test.ts +723 -0
- package/server/persistence.test.ts +1935 -0
- package/server/records.test.ts +180 -0
- package/server/records.ts +99 -0
- package/server/reset-implementation.test.ts +997 -0
- package/server/run.test.ts +611 -0
- package/server/scout.test.ts +877 -0
- package/server/scout.ts +459 -0
- package/server/skills-preparation.test.ts +1073 -0
- package/server/state.ts +249 -0
- package/server/tracker.ts +369 -0
- package/server/updates.test.ts +444 -0
- package/server/updates.ts +198 -0
- package/server/version.test.ts +140 -0
- package/server/version.ts +151 -0
- package/server/watchdog.ts +389 -0
- package/svelte.config.js +5 -0
- package/templates/host-AGENTS.md +235 -0
- package/tsconfig.json +21 -0
- package/vite.config.ts +21 -0
- package/watchdog/cleanup-main.ts +98 -0
- package/watchdog/cleanup.ts +301 -0
- package/watchdog/collect.ts +311 -0
- package/watchdog/coordinator.ts +397 -0
- package/watchdog/delivery.ts +107 -0
- package/watchdog/discovery.ts +209 -0
- package/watchdog/findings.ts +211 -0
- package/watchdog/github.ts +189 -0
- package/watchdog/history.ts +138 -0
- package/watchdog/install.ts +103 -0
- package/watchdog/ledger.ts +188 -0
- package/watchdog/lock.ts +63 -0
- package/watchdog/main.ts +106 -0
- package/watchdog/status.ts +71 -0
- package/watchdog/systemd.ts +96 -0
- package/watchdog/uninstall.ts +30 -0
- package/watchdog/units/devboard-watchdog.service +9 -0
- package/watchdog/units/devboard-watchdog.timer +10 -0
|
@@ -0,0 +1,358 @@
|
|
|
1
|
+
import type { GhRunner } from "./actions";
|
|
2
|
+
import { ghCommand } from "./preflight";
|
|
3
|
+
import {
|
|
4
|
+
createRepositoryGitHub,
|
|
5
|
+
isFullSha,
|
|
6
|
+
isSha,
|
|
7
|
+
shaTokens,
|
|
8
|
+
type GitHubRelease,
|
|
9
|
+
} from "../../lib/github-release";
|
|
10
|
+
import { configuredGiteaToken, giteaBaseUrl, type Forge } from "../../lib/forge";
|
|
11
|
+
import {
|
|
12
|
+
giteaJson,
|
|
13
|
+
giteaListIssues,
|
|
14
|
+
repoPath,
|
|
15
|
+
type FetchLike,
|
|
16
|
+
type GiteaTarget,
|
|
17
|
+
} from "../../lib/gitea";
|
|
18
|
+
import { git } from "../repository";
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The GitHub facts Auto Scout reads: every `scout` issue in any state, every
|
|
22
|
+
* published release, and the commit that closed an issue. Reads go through the
|
|
23
|
+
* operator's `gh` credential; commit resolution also uses the checkout, and
|
|
24
|
+
* release containment prefers the checkout's tags before asking GitHub. The
|
|
25
|
+
* release resolution itself lives in `lib/github-release.ts`, shared with the
|
|
26
|
+
* watchdog activity page.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
export {
|
|
30
|
+
closingCommitFromComments,
|
|
31
|
+
closingCommitFromTimeline,
|
|
32
|
+
earliestRelease,
|
|
33
|
+
isSha,
|
|
34
|
+
parseJsonArray,
|
|
35
|
+
parseJsonLines,
|
|
36
|
+
shaTokens,
|
|
37
|
+
shippedVersion,
|
|
38
|
+
} from "../../lib/github-release";
|
|
39
|
+
|
|
40
|
+
export interface ScoutGitHubIssue {
|
|
41
|
+
number: number;
|
|
42
|
+
title: string;
|
|
43
|
+
url: string;
|
|
44
|
+
state: "open" | "closed";
|
|
45
|
+
createdAt: string;
|
|
46
|
+
body: string;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export type ScoutRelease = GitHubRelease;
|
|
50
|
+
|
|
51
|
+
export interface ScoutGitHubClient {
|
|
52
|
+
/** Every issue carrying the `scout` label, open or closed. */
|
|
53
|
+
listIssues(): Promise<ScoutGitHubIssue[]>;
|
|
54
|
+
/** Every open issue title in the repository, scout or not. */
|
|
55
|
+
listOpenIssueTitles(): Promise<string[]>;
|
|
56
|
+
/** Every published release, drafts excluded. */
|
|
57
|
+
listReleases(): Promise<ScoutRelease[]>;
|
|
58
|
+
/** The commit that closed an issue, from its timeline or newest comment. */
|
|
59
|
+
closingCommit(issueNumber: number): Promise<string | null>;
|
|
60
|
+
/** One unambiguous full commit, through the checkout or GitHub, or null. */
|
|
61
|
+
resolveCommit(short: string): Promise<string | null>;
|
|
62
|
+
/** The published release tags whose history contains `commit`. */
|
|
63
|
+
containingReleaseTags(commit: string, releases: ScoutRelease[]): Promise<string[]>;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function firstLine(text: string): string {
|
|
67
|
+
return (
|
|
68
|
+
text
|
|
69
|
+
.split("\n")
|
|
70
|
+
.map((line) => line.trim())
|
|
71
|
+
.find((line) => line.length > 0) ?? "no diagnostic"
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Parses a JSON array `gh` printed, refusing a shape it does not know. */
|
|
76
|
+
function parseIssues(text: string): unknown[] | undefined {
|
|
77
|
+
try {
|
|
78
|
+
const parsed = JSON.parse(text);
|
|
79
|
+
return Array.isArray(parsed) ? parsed : undefined;
|
|
80
|
+
} catch {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export interface ScoutGitHubOptions {
|
|
86
|
+
/** The host origin's `owner/name`. */
|
|
87
|
+
repository: string;
|
|
88
|
+
repoDir: string;
|
|
89
|
+
environment?: Record<string, string | undefined>;
|
|
90
|
+
/**
|
|
91
|
+
* Deadline for each command the default runner starts. The worker passes
|
|
92
|
+
* its periodic read deadline so a stalled `gh` always settles.
|
|
93
|
+
*/
|
|
94
|
+
timeoutMs?: number;
|
|
95
|
+
/** Aborts the default runner's in-flight commands; ignored by an injected `run`. */
|
|
96
|
+
signal?: AbortSignal;
|
|
97
|
+
/** The origin forge; defaults to GitHub for backward compatibility. */
|
|
98
|
+
forge?: Forge;
|
|
99
|
+
/** Gitea HTTP origin. */
|
|
100
|
+
baseUrl?: string;
|
|
101
|
+
/** Injectable transport for the Gitea API. */
|
|
102
|
+
giteaFetch?: FetchLike;
|
|
103
|
+
/**
|
|
104
|
+
* Injectable forge command; defaults to the operator's `gh`. An injected
|
|
105
|
+
* runner owns its own deadline: `createScoutGitHub` cannot bound a promise
|
|
106
|
+
* the runner never settles.
|
|
107
|
+
*/
|
|
108
|
+
run?: GhRunner;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function createScoutGitea(options: ScoutGitHubOptions): ScoutGitHubClient {
|
|
112
|
+
const environment = options.environment ?? process.env;
|
|
113
|
+
const remote = { forge: "gitea" as const, host: "", project: options.repository };
|
|
114
|
+
const baseUrl = options.baseUrl ?? giteaBaseUrl(remote, environment);
|
|
115
|
+
if (baseUrl === undefined || baseUrl.length === 0) {
|
|
116
|
+
throw new Error("the scout needs DEVBOARD_GITEA_URL for a Gitea origin");
|
|
117
|
+
}
|
|
118
|
+
const token = configuredGiteaToken(environment);
|
|
119
|
+
const target: GiteaTarget = {
|
|
120
|
+
baseUrl,
|
|
121
|
+
project: options.repository,
|
|
122
|
+
...(token === undefined ? {} : { token }),
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
async function readJson(path: string): Promise<unknown> {
|
|
126
|
+
return await giteaJson<unknown>(target, path, undefined, options.giteaFetch);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function releaseList(value: unknown): ScoutRelease[] {
|
|
130
|
+
if (!Array.isArray(value)) return [];
|
|
131
|
+
const releases: ScoutRelease[] = [];
|
|
132
|
+
for (const entry of value) {
|
|
133
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
134
|
+
const record = entry as Record<string, unknown>;
|
|
135
|
+
if (typeof record.tag_name !== "string" || record.tag_name.length === 0) continue;
|
|
136
|
+
if (record.draft === true) continue;
|
|
137
|
+
const published = record.published_at;
|
|
138
|
+
if (typeof published !== "string" || published.length === 0) continue;
|
|
139
|
+
releases.push({ tag: record.tag_name, publishedAt: published });
|
|
140
|
+
}
|
|
141
|
+
return releases;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return {
|
|
145
|
+
async listIssues(): Promise<ScoutGitHubIssue[]> {
|
|
146
|
+
const issues = await giteaListIssues(target, "all", options.giteaFetch);
|
|
147
|
+
return issues
|
|
148
|
+
.filter((issue) => issue.labels.includes("scout"))
|
|
149
|
+
.map((issue) => ({
|
|
150
|
+
number: issue.number,
|
|
151
|
+
title: issue.title,
|
|
152
|
+
url: issue.url,
|
|
153
|
+
state: issue.state,
|
|
154
|
+
createdAt: issue.createdAt,
|
|
155
|
+
body: issue.body,
|
|
156
|
+
}));
|
|
157
|
+
},
|
|
158
|
+
|
|
159
|
+
async listOpenIssueTitles(): Promise<string[]> {
|
|
160
|
+
const issues = await giteaListIssues(target, "open", options.giteaFetch);
|
|
161
|
+
return issues.map((issue) => issue.title);
|
|
162
|
+
},
|
|
163
|
+
|
|
164
|
+
async listReleases(): Promise<ScoutRelease[]> {
|
|
165
|
+
return releaseList(await readJson(`${repoPath(target)}/releases?limit=100`));
|
|
166
|
+
},
|
|
167
|
+
|
|
168
|
+
async closingCommit(issueNumber: number): Promise<string | null> {
|
|
169
|
+
const timeline = await readJson(`${repoPath(target)}/issues/${issueNumber}/timeline`);
|
|
170
|
+
const bodies: string[] = [];
|
|
171
|
+
if (Array.isArray(timeline)) {
|
|
172
|
+
for (const entry of timeline) {
|
|
173
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
174
|
+
const record = entry as Record<string, unknown>;
|
|
175
|
+
if (typeof record.commit_id === "string" && isSha(record.commit_id)) {
|
|
176
|
+
return record.commit_id;
|
|
177
|
+
}
|
|
178
|
+
if (typeof record.body === "string") bodies.push(record.body);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
const comments = await readJson(`${repoPath(target)}/issues/${issueNumber}/comments`);
|
|
182
|
+
if (Array.isArray(comments)) {
|
|
183
|
+
for (const entry of comments) {
|
|
184
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
185
|
+
const body = (entry as Record<string, unknown>).body;
|
|
186
|
+
if (typeof body === "string") bodies.push(body);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
for (let index = bodies.length - 1; index >= 0; index -= 1) {
|
|
190
|
+
const tokens = shaTokens(bodies[index] ?? "");
|
|
191
|
+
if (tokens.length > 0) return tokens[tokens.length - 1] ?? null;
|
|
192
|
+
}
|
|
193
|
+
return null;
|
|
194
|
+
},
|
|
195
|
+
|
|
196
|
+
async resolveCommit(short: string): Promise<string | null> {
|
|
197
|
+
if (!isSha(short)) return null;
|
|
198
|
+
const local = git(options.repoDir, "rev-parse", "--verify", "--quiet", `${short}^{commit}`);
|
|
199
|
+
if (local.code === 0) {
|
|
200
|
+
const sha = local.stdout.split(/\s+/)[0] ?? "";
|
|
201
|
+
if (isFullSha(sha)) return sha;
|
|
202
|
+
}
|
|
203
|
+
const value = await readJson(`${repoPath(target)}/git/commits/${short}`);
|
|
204
|
+
if (typeof value === "object" && value !== null && "sha" in value) {
|
|
205
|
+
return typeof value.sha === "string" && isFullSha(value.sha) ? value.sha : null;
|
|
206
|
+
}
|
|
207
|
+
return null;
|
|
208
|
+
},
|
|
209
|
+
|
|
210
|
+
async containingReleaseTags(
|
|
211
|
+
commit: string,
|
|
212
|
+
releases: ScoutRelease[],
|
|
213
|
+
): Promise<string[]> {
|
|
214
|
+
const local = git(options.repoDir, "tag", "--contains", commit);
|
|
215
|
+
if (local.code === 0) {
|
|
216
|
+
const tags = new Set(
|
|
217
|
+
local.stdout.split("\n").map((line) => line.trim()).filter(Boolean),
|
|
218
|
+
);
|
|
219
|
+
const contained = releases
|
|
220
|
+
.filter((release) => tags.has(release.tag))
|
|
221
|
+
.map((release) => release.tag);
|
|
222
|
+
if (contained.length > 0) return contained;
|
|
223
|
+
}
|
|
224
|
+
const contained: string[] = [];
|
|
225
|
+
for (const release of releases) {
|
|
226
|
+
const value = await readJson(
|
|
227
|
+
`${repoPath(target)}/compare/${commit}...${encodeURIComponent(release.tag)}`,
|
|
228
|
+
);
|
|
229
|
+
if (typeof value !== "object" || value === null || !("status" in value)) continue;
|
|
230
|
+
if (value.status === "ahead" || value.status === "identical") {
|
|
231
|
+
contained.push(release.tag);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
return contained;
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
export function createScoutGitHub(options: ScoutGitHubOptions): ScoutGitHubClient {
|
|
240
|
+
if ((options.forge ?? "github") === "gitea") return createScoutGitea(options);
|
|
241
|
+
const repository = options.repository;
|
|
242
|
+
const environment: Record<string, string> = {};
|
|
243
|
+
for (const [key, value] of Object.entries(options.environment ?? process.env)) {
|
|
244
|
+
if (value !== undefined) environment[key] = value;
|
|
245
|
+
}
|
|
246
|
+
const run =
|
|
247
|
+
options.run ??
|
|
248
|
+
((argv, env) =>
|
|
249
|
+
ghCommand(argv, env, {
|
|
250
|
+
...(options.timeoutMs === undefined ? {} : { timeoutMs: options.timeoutMs }),
|
|
251
|
+
...(options.signal === undefined ? {} : { signal: options.signal }),
|
|
252
|
+
}));
|
|
253
|
+
const repositoryGitHub = createRepositoryGitHub({
|
|
254
|
+
repository,
|
|
255
|
+
repoDir: options.repoDir,
|
|
256
|
+
run,
|
|
257
|
+
environment,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
async function read(argv: string[]): Promise<string> {
|
|
261
|
+
const result = await run(argv, environment);
|
|
262
|
+
if (result.code !== 0) throw new Error(firstLine(result.stderr));
|
|
263
|
+
return result.stdout;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return {
|
|
267
|
+
async listIssues(): Promise<ScoutGitHubIssue[]> {
|
|
268
|
+
const text = await read([
|
|
269
|
+
"issue",
|
|
270
|
+
"list",
|
|
271
|
+
"--repo",
|
|
272
|
+
repository,
|
|
273
|
+
"--label",
|
|
274
|
+
"scout",
|
|
275
|
+
"--state",
|
|
276
|
+
"all",
|
|
277
|
+
"--limit",
|
|
278
|
+
"1000",
|
|
279
|
+
"--json",
|
|
280
|
+
"number,title,url,state,createdAt,body",
|
|
281
|
+
]);
|
|
282
|
+
const entries = parseIssues(text);
|
|
283
|
+
if (entries === undefined) throw new Error("gh issue list did not print a JSON array");
|
|
284
|
+
const issues: ScoutGitHubIssue[] = [];
|
|
285
|
+
const seen = new Set<number>();
|
|
286
|
+
for (const entry of entries) {
|
|
287
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
288
|
+
const record = entry as Record<string, unknown>;
|
|
289
|
+
if (
|
|
290
|
+
typeof record.number !== "number" ||
|
|
291
|
+
!Number.isInteger(record.number) ||
|
|
292
|
+
typeof record.title !== "string" ||
|
|
293
|
+
typeof record.url !== "string" ||
|
|
294
|
+
typeof record.createdAt !== "string" ||
|
|
295
|
+
typeof record.body !== "string" ||
|
|
296
|
+
typeof record.state !== "string"
|
|
297
|
+
) {
|
|
298
|
+
continue;
|
|
299
|
+
}
|
|
300
|
+
if (seen.has(record.number)) continue;
|
|
301
|
+
seen.add(record.number);
|
|
302
|
+
const state = record.state.toLowerCase() === "closed" ? "closed" : "open";
|
|
303
|
+
issues.push({
|
|
304
|
+
number: record.number,
|
|
305
|
+
title: record.title,
|
|
306
|
+
url: record.url,
|
|
307
|
+
state,
|
|
308
|
+
createdAt: record.createdAt,
|
|
309
|
+
body: record.body,
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
return issues;
|
|
313
|
+
},
|
|
314
|
+
|
|
315
|
+
async listOpenIssueTitles(): Promise<string[]> {
|
|
316
|
+
const text = await read([
|
|
317
|
+
"issue",
|
|
318
|
+
"list",
|
|
319
|
+
"--repo",
|
|
320
|
+
repository,
|
|
321
|
+
"--state",
|
|
322
|
+
"open",
|
|
323
|
+
"--limit",
|
|
324
|
+
"1000",
|
|
325
|
+
"--json",
|
|
326
|
+
"title",
|
|
327
|
+
]);
|
|
328
|
+
const entries = parseIssues(text);
|
|
329
|
+
if (entries === undefined) throw new Error("gh issue list did not print a JSON array");
|
|
330
|
+
const titles: string[] = [];
|
|
331
|
+
for (const entry of entries) {
|
|
332
|
+
if (typeof entry !== "object" || entry === null) continue;
|
|
333
|
+
const title = (entry as Record<string, unknown>).title;
|
|
334
|
+
if (typeof title === "string") titles.push(title);
|
|
335
|
+
}
|
|
336
|
+
return titles;
|
|
337
|
+
},
|
|
338
|
+
|
|
339
|
+
async listReleases(): Promise<ScoutRelease[]> {
|
|
340
|
+
return await repositoryGitHub.listReleases();
|
|
341
|
+
},
|
|
342
|
+
|
|
343
|
+
async closingCommit(issueNumber: number): Promise<string | null> {
|
|
344
|
+
return await repositoryGitHub.closingCommit(issueNumber);
|
|
345
|
+
},
|
|
346
|
+
|
|
347
|
+
async resolveCommit(short: string): Promise<string | null> {
|
|
348
|
+
return await repositoryGitHub.resolveCommit(short);
|
|
349
|
+
},
|
|
350
|
+
|
|
351
|
+
async containingReleaseTags(
|
|
352
|
+
commit: string,
|
|
353
|
+
releases: ScoutRelease[],
|
|
354
|
+
): Promise<string[]> {
|
|
355
|
+
return await repositoryGitHub.containingReleaseTags(commit, releases);
|
|
356
|
+
},
|
|
357
|
+
};
|
|
358
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
import { join, resolve } from "node:path";
|
|
2
|
+
import { journalKey } from "../../server/journal";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Every path one repository's scout worker owns. State lives under the
|
|
6
|
+
* dashboard's `STATE_DIR`, keyed by the repository path exactly like the
|
|
7
|
+
* dashboard journal and the issues worker, so two checkouts sharing one state
|
|
8
|
+
* directory never read each other's state. The lock and the scan directories
|
|
9
|
+
* deliberately live in the checkout: one repository can have only one scout,
|
|
10
|
+
* whichever state directory a dashboard was configured with.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export const SCOUT_DIRECTORY = "scout";
|
|
14
|
+
export const STATE_VERSION = 2;
|
|
15
|
+
export const SCAN_VERSION = 2;
|
|
16
|
+
/**
|
|
17
|
+
* A scan's agent phase ends after this long without activity, and its
|
|
18
|
+
* separately timed settle-and-file phase ends after this long in total. A
|
|
19
|
+
* productive agent keeps resetting the first bound.
|
|
20
|
+
*/
|
|
21
|
+
export const SCAN_TIMEOUT_MS = 15 * 60_000;
|
|
22
|
+
/** The periodic GitHub read while the worker is started. */
|
|
23
|
+
export const SYNC_INTERVAL_MS = 60_000;
|
|
24
|
+
/** The rolling window the hourly cap counts inside. */
|
|
25
|
+
export const HOUR_MS = 60 * 60_000;
|
|
26
|
+
/** The default number of issues Auto Scout may create per rolling hour. */
|
|
27
|
+
export const DEFAULT_MAX_PER_HOUR = 10;
|
|
28
|
+
/** The default number of Auto Scout issues that may stay open. */
|
|
29
|
+
export const DEFAULT_MAX_PENDING = 5;
|
|
30
|
+
/** Both limits accept an integer from this value through `LIMIT_MAX`. */
|
|
31
|
+
export const LIMIT_MIN = 1;
|
|
32
|
+
export const LIMIT_MAX = 100;
|
|
33
|
+
/** A finding earns an issue only at this significance or above. */
|
|
34
|
+
export const SIGNIFICANCE_THRESHOLD = 4;
|
|
35
|
+
export const SIGNIFICANCE_MIN = 1;
|
|
36
|
+
export const SIGNIFICANCE_MAX = 5;
|
|
37
|
+
|
|
38
|
+
export const CATEGORIES = [
|
|
39
|
+
"clarity",
|
|
40
|
+
"structure",
|
|
41
|
+
"naming",
|
|
42
|
+
"tests",
|
|
43
|
+
"robustness",
|
|
44
|
+
"correctness",
|
|
45
|
+
] as const;
|
|
46
|
+
|
|
47
|
+
export type ScoutCategory = (typeof CATEGORIES)[number];
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Trees the scan request tells the agent to ignore and evidence may never
|
|
51
|
+
* point into: generated output, vendored packages, temporary state, and
|
|
52
|
+
* per-task worktrees are not the repository under review.
|
|
53
|
+
*/
|
|
54
|
+
export const EXCLUDED_ROOTS = [
|
|
55
|
+
".git",
|
|
56
|
+
"node_modules",
|
|
57
|
+
"dist",
|
|
58
|
+
".worktree",
|
|
59
|
+
".tmp",
|
|
60
|
+
".devboard/.tmp",
|
|
61
|
+
];
|
|
62
|
+
|
|
63
|
+
/** `<STATE_DIR>/scout`: every repository's state below one dashboard. */
|
|
64
|
+
export function scoutRoot(stateDir: string): string {
|
|
65
|
+
return join(stateDir, SCOUT_DIRECTORY);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** `<STATE_DIR>/scout/<journalKey(REPO_DIR)>`. */
|
|
69
|
+
export function repoStateDirectory(stateDir: string, repoDir: string): string {
|
|
70
|
+
return join(scoutRoot(stateDir), journalKey(resolve(repoDir)));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export function statePath(stateDir: string, repoDir: string): string {
|
|
74
|
+
return join(repoStateDirectory(stateDir, repoDir), "state.json");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* The limits the dashboard persists for a repository. It lives beside the
|
|
79
|
+
* worker state so a save survives a page reload and a worker restart, and the
|
|
80
|
+
* worker reads it at start rather than relying on a command it may have missed.
|
|
81
|
+
*/
|
|
82
|
+
export function settingsPath(stateDir: string, repoDir: string): string {
|
|
83
|
+
return join(repoStateDirectory(stateDir, repoDir), "settings.json");
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Command files the dashboard writes and the worker consumes in order. */
|
|
87
|
+
export function controlDirectory(stateDir: string, repoDir: string): string {
|
|
88
|
+
return join(repoStateDirectory(stateDir, repoDir), "control");
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/** `<REPO_DIR>/.devboard/.tmp/scout`: the checkout-anchored working tree. */
|
|
92
|
+
export function workDirectory(repoDir: string): string {
|
|
93
|
+
return join(resolve(repoDir), ".devboard", ".tmp", SCOUT_DIRECTORY);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The repository-anchored lock: `<REPO_DIR>/.devboard/.tmp/scout/lock`.
|
|
98
|
+
* A checkout cannot host two scouts even when their dashboards disagree about
|
|
99
|
+
* the state directory.
|
|
100
|
+
*/
|
|
101
|
+
export function lockPath(repoDir: string): string {
|
|
102
|
+
return join(workDirectory(repoDir), "lock");
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** `<REPO_DIR>/.devboard/.tmp/scout/scans`: one directory per scan. */
|
|
106
|
+
export function scansDirectory(repoDir: string): string {
|
|
107
|
+
return join(workDirectory(repoDir), "scans");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export function scanDirectory(repoDir: string, scanId: string): string {
|
|
111
|
+
return join(scansDirectory(repoDir), scanId);
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
export function requestPath(directory: string): string {
|
|
115
|
+
return join(directory, "request.json");
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function recommendationsPath(directory: string): string {
|
|
119
|
+
return join(directory, "recommendations.json");
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function scanFilePath(directory: string): string {
|
|
123
|
+
return join(directory, "scan.json");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/** Scratch directory for issue bodies the worker hands to `gh`. */
|
|
127
|
+
export function issueBodyDirectory(repoDir: string): string {
|
|
128
|
+
return join(workDirectory(repoDir), "issue-bodies");
|
|
129
|
+
}
|