jackherd 1.12.7 → 1.14.0
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 +15 -0
- package/INSTALL.md +1 -1
- package/README.md +17 -17
- package/dist/config.d.ts +35 -11
- package/dist/config.js +49 -83
- package/herdr-plugin/herdr-plugin.toml +1 -1
- package/install.sh +1 -1
- package/kit/hooks/hooks/merge-guard/board-settings.ts +7 -32
- package/kit/hooks/hooks/merge-guard/lane-plan.ts +2 -29
- package/kit/hooks/hooks/plain-question/code-cache.ts +70 -0
- package/kit/hooks/hooks/plain-question/index.ts +80 -0
- package/kit/hooks/hooks/plain-question/run.ts +50 -0
- package/kit/hooks/hooks/shared/board-lanes.ts +19 -0
- package/kit/hooks/hooks/shared/boards.ts +96 -0
- package/kit/hooks/hooks/shared/plan-codes.ts +68 -0
- package/kit/hooks/hooks.json +9 -0
- package/kit/review/skills/diagnose/SKILL.md +2 -2
- package/kit/review/skills/diagnose/references/feedback-loops.md +4 -13
- package/kit/rules/rules/kit/code-style.md +15 -18
- package/kit/rules/rules/kit/testing.md +7 -28
- package/kit/workflow/skills/implement/SKILL.md +3 -3
- package/kit/workflow/skills/orchestrate/SKILL.md +4 -1
- package/kit/workflow/skills/orchestrate/references/events.md +6 -1
- package/kit/workflow/skills/orchestrate/references/plan-commit.md +2 -1
- package/kit/workflow/skills/start/SKILL.md +9 -6
- package/kit/workflow/skills/start/references/missing-commands.md +17 -0
- package/package.json +1 -1
- package/schemas/jackherd.schema.json +38 -11
- package/src/check/command.ts +167 -35
- package/src/check/file-kinds.ts +142 -0
- package/src/check/plan-structure-check.ts +34 -1
- package/src/check/plan.ts +230 -45
- package/src/check/report.ts +111 -3
- package/src/check/result.ts +13 -3
- package/src/check/roots.ts +47 -10
- package/src/check/schema.ts +20 -3
- package/src/check/settings.ts +25 -4
- package/src/check/skill-budget-defaults.ts +7 -4
- package/src/config/base-branch.ts +2 -0
- package/src/config/command.ts +32 -13
- package/src/config/detect.ts +242 -74
- package/src/config/location.ts +6 -0
- package/src/config/repository-facts.ts +32 -7
- package/src/config/repository-values.ts +162 -5
- package/src/config/schema.ts +34 -16
- package/src/lane-prompt/texts.ts +2 -2
- package/src/plan-format/bare-codes.ts +28 -0
- package/src/plans/lane-plans.ts +2 -1
- package/src/sync/command-context.ts +4 -2
- package/src/sync/herdr-plugin.ts +30 -6
- package/src/sync/init.ts +17 -16
- package/src/sync/render.ts +4 -6
- package/src/watch/command.ts +32 -1
- package/src/watch/lane-progress.ts +6 -1
- package/src/watch/lane-report.ts +10 -0
- package/src/watch/plan-missing-read.ts +47 -0
- package/src/watch/plan-missing.ts +43 -0
- package/src/watch/reference-paths.ts +1 -0
- package/src/watch/texts.ts +4 -0
- package/src/watch/watch-events.ts +7 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
What changed for people who use jackherd, newest first.
|
|
4
4
|
|
|
5
|
+
## 1.14.0
|
|
6
|
+
|
|
7
|
+
- **Any language, once its commands are set.** PHP and Node are recognised on their own, as before. For any other language, set its test command once, or let `/jh-start` in Claude Code propose the commands from your CI: it writes only the ones you approve. From then on `jh check` runs your tests before a commit. In a language jackherd does not know, a changed file without a test of its name gets a warning, not a block, since some languages keep tests in the source file.
|
|
8
|
+
- **The herd shows the plan a lane really works on.** When a lane's plan is not on the main branch yet, Jack hears about it once and gets it there, so the herd no longer shows an older plan for that lane.
|
|
9
|
+
|
|
10
|
+
## 1.13.0
|
|
11
|
+
|
|
12
|
+
- **Repositories without Node.** `jh init` reads the stack from the lock file: next to the Node managers, `composer.lock` writes the tools `composer.json` names (Pest or PHPUnit, PHPStan or Psalm, Pint or PHP-CS-Fixer, each as `vendor/bin/…`) and `composer install`. Before the first install, `composer.json` alone decides Composer, with no `lanes.lockFile`; a `package.json` without a lock file names no package manager, so none is guessed. A repository without a known stack gets no Bun commands any more; `init` leaves them out and says so, and the config gets a `$schema` only where `node_modules` can sit beside `.jackherd/`.
|
|
13
|
+
- **An unset command stays unset.** `commands.*`, `lanes.lockFile` and `check.lintFilesCommand` have no Bun default any more. A kit text shows "not set: `commands.<key>` in `.jackherd/config.json`" in place of the command, and `models` has a default, so a config written by hand without it no longer stops `init`.
|
|
14
|
+
- **`jh config` names what is missing.** A missing command, or one the lock files contradict (`bun test` beside only `composer.lock`), goes to stderr with the line to set; `/jh-start` passes those lines on. `jh config <key>` for an unset value exits 3 instead of printing "not set" as if it were the command.
|
|
15
|
+
- **No green without a test.** `jh check` finds roots by the stack's manifest (`composer.json` as well as `package.json`), counts the repository root unless it gathers workspaces, and lints only the files the stack lints. It fails when a changed source file is reached by no test, judged per file (kit copies are tested where the kit keeps them), and when `commands.test` belongs to another stack than the lock file. A `check.checks` entry with `"kind": "tests"` counts as a test for the files its globs match.
|
|
16
|
+
- **`jh init` in another repository keeps the herdr link.** Run from a checkout of jackherd for a repository that is not the checkout's own, `init` and `sync` leave a working herdr link where it is.
|
|
17
|
+
- **Kit rules without a language.** The kit's rules and the diagnose and implement skills no longer name Bun, Zod, Biome, `tsconfig` or `mock.module()`; a team writes its stack's conventions into a rule of its own beside `.claude/rules/kit/`. The README has a section on other languages.
|
|
18
|
+
- **A dialog that names a plan code bare is sent back.** The new hook `plain-question` (`PreToolUse` on `AskUserQuestion`) reads the codes the plans linked from the boards define (phases as `P<id>`, codes that open a table row, a heading or a bold list item; else `F`/`P`/`D`/`E` plus a number) and denies a question, header or option naming one outside brackets, so the user reads words, not shorthand. `jh check` warns about a changed "Waiting for you" row doing the same. The orchestrate skill and the lane prompt say it too.
|
|
19
|
+
|
|
5
20
|
## 1.12.7
|
|
6
21
|
|
|
7
22
|
- **Newest first in the herd.** The rows of Your turn that wait on your answer (the board's open questions, a session's questions, the files and Artifacts sent to you) stand newest first; what frees a lane still comes before the rest, newest first within it. A since without a year counts in the year nearest to now, so a row of 31 December stands under one of 1 January. With more than five rows, the oldest are the ones under `▸ n more`. An opened source lists its entries newest first by the time each shows: before, an entry with its own time from the source (say, when an error was last seen) could stand among entries in the order the watcher saw them change. Rows of the same time keep their order. Appointments keep their order, the ones without a time first, then the next due.
|
package/INSTALL.md
CHANGED
|
@@ -54,7 +54,7 @@ When `init` creates a new config, it asks:
|
|
|
54
54
|
| Use it when | the team shares the kit through the repository | the repository must stay untouched, not even untracked files |
|
|
55
55
|
| Lives in | the project's `.claude/` | `~/.claude/` |
|
|
56
56
|
| Skill names | `/plan`, `/implement`, `/orchestrate`, … | `/jh-plan`, `/jh-implement`, `/jh-orchestrate`, … |
|
|
57
|
-
| Hooks | `git-guard`, `watch-guard`, `merge-guard`, `approval-gate`, `model-gate`, `model-log`, `ask-hint`, `open-question`, `sent-file`, `format-on-edit` | none; `init --global` offers only a `SessionStart` update notice |
|
|
57
|
+
| Hooks | `git-guard`, `watch-guard`, `merge-guard`, `approval-gate`, `model-gate`, `model-log`, `ask-hint`, `open-question`, `plain-question`, `sent-file`, `format-on-edit` | none; `init --global` offers only a `SessionStart` update notice |
|
|
58
58
|
| Config | `.jackherd/config.json` | `~/.jackherd/config.json`, used where a repository has none |
|
|
59
59
|
| Update | `jh sync` | `jh update` |
|
|
60
60
|
|
package/README.md
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
<p>
|
|
2
|
-
<img alt="jackherd: a black and white Shih Tzu in a turquoise bandana, three lanes running from him to three agent nodes" src="https://unpkg.com/jackherd@1.
|
|
2
|
+
<img alt="jackherd: a black and white Shih Tzu in a turquoise bandana, three lanes running from him to three agent nodes" src="https://unpkg.com/jackherd@1.14.0/assets/branding/jackherd-logo-outline.svg" width="340">
|
|
3
3
|
<br>
|
|
4
4
|
</p>
|
|
5
5
|
|
|
6
|
-
[](https://www.npmjs.com/package/jackherd) [](https://www.npmjs.com/package/jackherd) [](https://www.npmjs.com/package/jackherd?activeTab=dependencies)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.
|
|
6
|
+
[](https://www.npmjs.com/package/jackherd) [](https://www.npmjs.com/package/jackherd) [](https://www.npmjs.com/package/jackherd?activeTab=dependencies)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.14.0/assets/icons/space.svg">
|
|
7
7
|
|
|
8
8
|
# Jack herds your coding agents.
|
|
9
9
|
|
|
@@ -15,25 +15,25 @@ For developers who'd rather decide than supervise.
|
|
|
15
15
|
curl -fsSL https://unpkg.com/jackherd/install.sh | sh
|
|
16
16
|
```
|
|
17
17
|
|
|
18
|
-
Then `/jh-start` in a repository, and Jack offers your first plan. → [Quickstart](#quickstart)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.
|
|
18
|
+
Then `/jh-start` in a repository, and Jack offers your first plan. → [Quickstart](#quickstart)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.14.0/assets/icons/space.svg">
|
|
19
19
|
|
|
20
20
|
<p>
|
|
21
|
-
<img src="https://unpkg.com/jackherd@1.
|
|
21
|
+
<img src="https://unpkg.com/jackherd@1.14.0/docs/workflow-dark.svg" width="720" alt="jackherd in herdr: a lane hits a question, Jack asks you as a choice, you pick, and the lane works on while the herd view counts its progress.">
|
|
22
22
|
<br>
|
|
23
|
-
<sub>Jack and his lanes in <a href="https://herdr.dev/">herdr</a>, a terminal for coding agents (recommended). Without it, one Claude Code session builds with subagents.</sub><br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.
|
|
23
|
+
<sub>Jack and his lanes in <a href="https://herdr.dev/">herdr</a>, a terminal for coding agents (recommended). Without it, one Claude Code session builds with subagents.</sub><br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.14.0/assets/icons/space.svg">
|
|
24
24
|
</p>
|
|
25
25
|
|
|
26
|
-
<p><img src="https://unpkg.com/jackherd@1.
|
|
26
|
+
<p><img src="https://unpkg.com/jackherd@1.14.0/assets/icons/lanes.svg" width="24" height="34" align="middle" alt=""> <b>Parallel where it pays.</b><br>Phases that don't collide run side by side, each lane on its own branch and worktree. Jack keeps the merge order and has the others rebase.</p>
|
|
27
27
|
|
|
28
|
-
<p><img src="https://unpkg.com/jackherd@1.
|
|
28
|
+
<p><img src="https://unpkg.com/jackherd@1.14.0/assets/icons/bark.svg" width="24" height="34" align="middle" alt=""> <b>Jack barks.</b><br>A blocked lane, a red check, a review comment: Jack wakes, and the herd view shows where every lane stands.</p>
|
|
29
29
|
|
|
30
|
-
<p><img src="https://unpkg.com/jackherd@1.
|
|
30
|
+
<p><img src="https://unpkg.com/jackherd@1.14.0/assets/icons/ear.svg" width="24" height="34" align="middle" alt=""> <b>Jack listens.</b><br>Review comments go to the lane that fixes them, with a test. <code>/jh-follow</code> brings anything with a CLI or an API into the herd: tickets, error logs, status pages, deploys, and whatever else you point it at.</p>
|
|
31
31
|
|
|
32
|
-
<p><img src="https://unpkg.com/jackherd@1.
|
|
32
|
+
<p><img src="https://unpkg.com/jackherd@1.14.0/assets/icons/bowl.svg" width="24" height="34" align="middle" alt=""> <b>Picks up where you left off.</b><br>Plan and board hold the state, not the chat.</p>
|
|
33
33
|
|
|
34
|
-
<p><img src="https://unpkg.com/jackherd@1.
|
|
34
|
+
<p><img src="https://unpkg.com/jackherd@1.14.0/assets/icons/plan.svg" width="24" height="34" align="middle" alt=""> <b>Lives in your repository.</b><br>Plans, board and every decision are Markdown, reviewed like code. <code>jh init</code> gives your team the same setup.</p>
|
|
35
35
|
|
|
36
|
-
<p><img src="https://unpkg.com/jackherd@1.
|
|
36
|
+
<p><img src="https://unpkg.com/jackherd@1.14.0/assets/icons/decide.svg" width="24" height="34" align="middle" alt=""> <b>You make the calls.</b><br>Nothing is committed, pushed or merged without your approval. Jack merges on green checks only where you said so: one pull request, or a plan's for up to 24 hours.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.14.0/assets/icons/space.svg"></p>
|
|
37
37
|
|
|
38
38
|
## Quickstart
|
|
39
39
|
|
|
@@ -69,22 +69,22 @@ Then `/jh-start` in a repository, and Jack offers your first plan. → [Quicksta
|
|
|
69
69
|
/jh-orchestrate docs/plans/board-<name>/board.md
|
|
70
70
|
```
|
|
71
71
|
|
|
72
|
-
By hand (Bun 1.3+): `bun add -g jackherd` or `npm install -g jackherd`, then `jh init --global` for yourself, or `jh init` in a repository to give your team the same skills and guard hooks. → [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.
|
|
72
|
+
By hand (Bun 1.3+): `bun add -g jackherd` or `npm install -g jackherd`, then `jh init --global` for yourself, or `jh init` in a repository to give your team the same skills and guard hooks. → [INSTALL.md](https://unpkg.com/jackherd/INSTALL.md)<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.14.0/assets/icons/space.svg">
|
|
73
73
|
|
|
74
74
|
## Follow anything outside GitHub
|
|
75
75
|
|
|
76
76
|
Whatever you check by hand, Jack can watch: tickets, error logs, status pages, deploys. One `/jh-follow`, one yes, and every change comes to you like this:
|
|
77
77
|
|
|
78
|
-
<img src="https://unpkg.com/jackherd@1.
|
|
78
|
+
<img src="https://unpkg.com/jackherd@1.14.0/docs/follow-dark.svg" width="720" alt="A source (ticket, error log or status page) has a new or changed entry; jh watch reports it to Jack; Jack puts a row into Your turn, prefixed with the source's label; its [ask] button makes Jack ask you, and then a lane works it.">
|
|
79
79
|
|
|
80
|
-
The herd sums up your sources in one `Sources` line: per source its new entries, or when it last ran, red while it fails; opened, a click on an entry opens it. A ticket shows on the lane that works it, and a row in Your turn that came from a source starts with that source's label.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.
|
|
80
|
+
The herd sums up your sources in one `Sources` line: per source its new entries, or when it last ran, red while it fails; opened, a click on an entry opens it. A ticket shows on the lane that works it, and a row in Your turn that came from a source starts with that source's label.<br><img width="1" height="8" alt="" src="https://unpkg.com/jackherd@1.14.0/assets/icons/space.svg">
|
|
81
81
|
|
|
82
82
|
## In detail
|
|
83
83
|
|
|
84
84
|
<details>
|
|
85
85
|
<summary><b>What Jack does on his own, and what waits for you</b></summary>
|
|
86
86
|
|
|
87
|
-
<img src="https://unpkg.com/jackherd@1.
|
|
87
|
+
<img src="https://unpkg.com/jackherd@1.14.0/docs/lanes-dark.svg" alt="Jack prompts two lanes and asks you for decisions and approvals; the lanes send questions back as text; jh watch reads lane state, pull requests and checks and wakes Jack.">
|
|
88
88
|
|
|
89
89
|
| On his own | After your approval | Never |
|
|
90
90
|
| --- | --- | --- |
|
|
@@ -117,7 +117,7 @@ The guards catch mistakes, not intent; for that, use branch protection. The hook
|
|
|
117
117
|
|
|
118
118
|
A **plan** is Markdown with phases, checkbox tasks, a model and a review gate per phase. A **board** lists the lanes of one or more plans: which phases each lane builds, on which model, after what. A **lane** is one agent session in its own git worktree (a second checkout of your repository), and **Jack** is the session in the main checkout that runs `/jh-orchestrate` on a board.
|
|
119
119
|
|
|
120
|
-
<img src="https://unpkg.com/jackherd@1.
|
|
120
|
+
<img src="https://unpkg.com/jackherd@1.14.0/docs/skills-dark.svg" alt="/jh-grill hands decisions to /jh-plan; /jh-plan feeds /jh-orchestrate or a single /jh-implement; /jh-implement ends in /jh-pre-commit, which runs the reviewers; /jh-diagnose hands a red command to /jh-implement.">
|
|
121
121
|
|
|
122
122
|
| Skill | Does |
|
|
123
123
|
| --- | --- |
|
|
@@ -222,7 +222,7 @@ Each phase in the plan names its model: the planning model (Opus by default) whe
|
|
|
222
222
|
| `jh preview <file>` | renders a Markdown file through the GitHub API into a preview in GitHub and npm style |
|
|
223
223
|
| `jh waiting` | the files and Artifacts sent to you that you have not answered yet (`close <id>…` or `close --all` once you have) |
|
|
224
224
|
| `jh context` | how full an agent's context is, as JSON |
|
|
225
|
-
| `jh config` | the commands and the base branch the repository uses |
|
|
225
|
+
| `jh config` | the commands and the base branch the repository uses; a missing value goes to stderr with the line to set, and `jh config <key>` for an unset value exits 3 |
|
|
226
226
|
|
|
227
227
|
Without a global install, run them as `bunx jackherd <command>`. The config is `.jackherd/config.json` in a project and `~/.jackherd/config.json` globally, checked against [one schema](https://unpkg.com/jackherd/schemas/jackherd.schema.json), which your editor completes in the project config. What usually needs setting:
|
|
228
228
|
|
package/dist/config.d.ts
CHANGED
|
@@ -15,10 +15,27 @@ declare const MODEL_ROLE: {
|
|
|
15
15
|
readonly orchestrate: "orchestrate";
|
|
16
16
|
};
|
|
17
17
|
type ModelRole = (typeof MODEL_ROLE)[keyof typeof MODEL_ROLE];
|
|
18
|
+
/** The model of each role when the config names none: a new project's, and one written by hand without `models`. */
|
|
19
|
+
declare const DEFAULT_MODELS: Record<ModelRole, string>;
|
|
18
20
|
declare const isTerminalAdapter: (value: string) => value is TerminalAdapter;
|
|
19
21
|
declare const isModelRole: (value: string) => value is ModelRole;
|
|
20
22
|
/** Lower-case letters and digits ending in a dash, so a prefixed name stays one plain path segment. */
|
|
21
23
|
declare const SKILL_PREFIX: RegExp;
|
|
24
|
+
/**
|
|
25
|
+
* The project's own commands. Each is optional: detection fills what the repository's stack decides, and a command
|
|
26
|
+
* nobody set stays out instead of a guessed one (a kit text then says where to set it).
|
|
27
|
+
*/
|
|
28
|
+
declare const commandsSchema: z.ZodObject<{
|
|
29
|
+
test: z.ZodOptional<z.ZodString>;
|
|
30
|
+
typecheck: z.ZodOptional<z.ZodString>;
|
|
31
|
+
lint: z.ZodOptional<z.ZodString>;
|
|
32
|
+
format: z.ZodOptional<z.ZodString>;
|
|
33
|
+
install: z.ZodOptional<z.ZodString>;
|
|
34
|
+
dev: z.ZodOptional<z.ZodString>;
|
|
35
|
+
}, z.core.$strict>;
|
|
36
|
+
type CommandKey = keyof z.infer<typeof commandsSchema>;
|
|
37
|
+
/** Every key of the `commands` block, as the schema names them. */
|
|
38
|
+
declare const COMMAND_KEYS: CommandKey[];
|
|
22
39
|
/** The window `plans.context.defaultWindow` assumes when the config leaves it out. */
|
|
23
40
|
declare const DEFAULT_CONTEXT_WINDOW = 200000;
|
|
24
41
|
declare const sourceSchema: z.ZodObject<{
|
|
@@ -77,24 +94,24 @@ declare const jackherdConfigSchema: z.ZodObject<{
|
|
|
77
94
|
baseBranch: z.ZodDefault<z.ZodString>;
|
|
78
95
|
skillPrefix: z.ZodDefault<z.ZodUnion<readonly [z.ZodLiteral<"">, z.ZodString]>>;
|
|
79
96
|
commands: z.ZodPrefault<z.ZodObject<{
|
|
80
|
-
test: z.
|
|
81
|
-
typecheck: z.
|
|
82
|
-
lint: z.
|
|
83
|
-
format: z.
|
|
84
|
-
install: z.
|
|
85
|
-
dev: z.
|
|
97
|
+
test: z.ZodOptional<z.ZodString>;
|
|
98
|
+
typecheck: z.ZodOptional<z.ZodString>;
|
|
99
|
+
lint: z.ZodOptional<z.ZodString>;
|
|
100
|
+
format: z.ZodOptional<z.ZodString>;
|
|
101
|
+
install: z.ZodOptional<z.ZodString>;
|
|
102
|
+
dev: z.ZodOptional<z.ZodString>;
|
|
86
103
|
}, z.core.$strict>>;
|
|
87
104
|
lanes: z.ZodPrefault<z.ZodObject<{
|
|
88
105
|
copy: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
89
|
-
lockFile: z.
|
|
106
|
+
lockFile: z.ZodOptional<z.ZodString>;
|
|
90
107
|
}, z.core.$strict>>;
|
|
91
|
-
models: z.ZodRecord<z.ZodEnum<{
|
|
108
|
+
models: z.ZodDefault<z.ZodRecord<z.ZodEnum<{
|
|
92
109
|
plan: "plan";
|
|
93
110
|
implement: "implement";
|
|
94
111
|
review: "review";
|
|
95
112
|
explore: "explore";
|
|
96
113
|
orchestrate: "orchestrate";
|
|
97
|
-
}>, z.ZodString
|
|
114
|
+
}>, z.ZodString>>;
|
|
98
115
|
terminal: z.ZodPrefault<z.ZodObject<{
|
|
99
116
|
adapter: z.ZodDefault<z.ZodEnum<{
|
|
100
117
|
none: "none";
|
|
@@ -277,11 +294,15 @@ declare const jackherdConfigSchema: z.ZodObject<{
|
|
|
277
294
|
taskSignal: z.ZodDefault<z.ZodBoolean>;
|
|
278
295
|
}, z.core.$strict>>;
|
|
279
296
|
check: z.ZodOptional<z.ZodObject<{
|
|
280
|
-
lintFilesCommand: z.
|
|
297
|
+
lintFilesCommand: z.ZodOptional<z.ZodString>;
|
|
281
298
|
checks: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
282
299
|
name: z.ZodString;
|
|
283
300
|
command: z.ZodString;
|
|
284
301
|
globs: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
302
|
+
kind: z.ZodOptional<z.ZodEnum<{
|
|
303
|
+
types: "types";
|
|
304
|
+
tests: "tests";
|
|
305
|
+
}>>;
|
|
285
306
|
}, z.core.$strict>>>;
|
|
286
307
|
generatorOwned: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
287
308
|
glob: z.ZodString;
|
|
@@ -300,6 +321,9 @@ declare const jackherdConfigSchema: z.ZodObject<{
|
|
|
300
321
|
}, z.core.$strict>>;
|
|
301
322
|
skillBudgets: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodNumber>>;
|
|
302
323
|
looseTests: z.ZodDefault<z.ZodBoolean>;
|
|
324
|
+
roots: z.ZodOptional<z.ZodArray<z.ZodPipe<z.ZodString, z.ZodTransform<string, string>>>>;
|
|
325
|
+
sourceFiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
326
|
+
testFiles: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
303
327
|
}, z.core.$strict>>;
|
|
304
328
|
}, z.core.$strict>;
|
|
305
329
|
type JackherdConfig = z.infer<typeof jackherdConfigSchema>;
|
|
@@ -309,4 +333,4 @@ declare const parseConfigFile: (raw: unknown, label: string) => JackherdConfig;
|
|
|
309
333
|
/** One `watch.sources` entry outside the config, such as a draft to try; invalid throws with `label` and the issues. */
|
|
310
334
|
declare const parseSourceDraft: (raw: unknown, label: string) => z.infer<typeof sourceSchema>;
|
|
311
335
|
|
|
312
|
-
export { DEFAULT_CONTEXT_WINDOW, type JackherdConfig, MODEL_ROLE, type ModelRole, SKILL_PREFIX, TERMINAL_ADAPTER, type TerminalAdapter, isModelRole, isTerminalAdapter, jackherdConfigSchema, parseConfigFile, parseJackherdConfig, parseSourceDraft, renderConfigJsonSchema };
|
|
336
|
+
export { COMMAND_KEYS, type CommandKey, DEFAULT_CONTEXT_WINDOW, DEFAULT_MODELS, type JackherdConfig, MODEL_ROLE, type ModelRole, SKILL_PREFIX, TERMINAL_ADAPTER, type TerminalAdapter, isModelRole, isTerminalAdapter, jackherdConfigSchema, parseConfigFile, parseJackherdConfig, parseSourceDraft, renderConfigJsonSchema };
|
package/dist/config.js
CHANGED
|
@@ -12,72 +12,6 @@ import { z as z2 } from "zod";
|
|
|
12
12
|
// src/check/schema.ts
|
|
13
13
|
import { z } from "zod";
|
|
14
14
|
|
|
15
|
-
// src/version/release.ts
|
|
16
|
-
var PACKAGE_NAME = "jackherd";
|
|
17
|
-
|
|
18
|
-
// src/config/location.ts
|
|
19
|
-
import { dirname, join, resolve } from "path";
|
|
20
|
-
var TOOL_DIRECTORY = ".jackherd";
|
|
21
|
-
var CONFIG_FILE = "config.json";
|
|
22
|
-
var CONFIG_SCHEMA_REFERENCE = `../node_modules/${PACKAGE_NAME}/schemas/${PACKAGE_NAME}.schema.json`;
|
|
23
|
-
var PROJECT_CONFIG_PATH = `${TOOL_DIRECTORY}/${CONFIG_FILE}`;
|
|
24
|
-
|
|
25
|
-
// src/config/detect.ts
|
|
26
|
-
var PACKAGE_MANAGER = { bun: "bun", yarn: "yarn", pnpm: "pnpm", npm: "npm" };
|
|
27
|
-
var LOCK_FILES = {
|
|
28
|
-
"bun.lock": PACKAGE_MANAGER.bun,
|
|
29
|
-
"bun.lockb": PACKAGE_MANAGER.bun,
|
|
30
|
-
"yarn.lock": PACKAGE_MANAGER.yarn,
|
|
31
|
-
"pnpm-lock.yaml": PACKAGE_MANAGER.pnpm,
|
|
32
|
-
"package-lock.json": PACKAGE_MANAGER.npm
|
|
33
|
-
};
|
|
34
|
-
var DEFAULT_BASE_BRANCH = "main";
|
|
35
|
-
var DERIVED_COMMANDS = {
|
|
36
|
-
bun: {
|
|
37
|
-
test: "bun test",
|
|
38
|
-
typecheck: "bunx -p typescript tsc --noEmit",
|
|
39
|
-
lint: "bunx @biomejs/biome check .",
|
|
40
|
-
format: "bunx @biomejs/biome format --write .",
|
|
41
|
-
install: "bun install",
|
|
42
|
-
dev: "bun run dev",
|
|
43
|
-
lintFiles: "bunx @biomejs/biome check --no-errors-on-unmatched"
|
|
44
|
-
},
|
|
45
|
-
yarn: {
|
|
46
|
-
test: "yarn test",
|
|
47
|
-
typecheck: "yarn tsc --noEmit",
|
|
48
|
-
lint: "yarn biome check .",
|
|
49
|
-
format: "yarn biome format --write .",
|
|
50
|
-
install: "yarn install",
|
|
51
|
-
dev: "yarn dev",
|
|
52
|
-
lintFiles: "yarn biome check --no-errors-on-unmatched"
|
|
53
|
-
},
|
|
54
|
-
pnpm: {
|
|
55
|
-
test: "pnpm test",
|
|
56
|
-
typecheck: "pnpm exec tsc --noEmit",
|
|
57
|
-
lint: "pnpm exec biome check .",
|
|
58
|
-
format: "pnpm exec biome format --write .",
|
|
59
|
-
install: "pnpm install",
|
|
60
|
-
dev: "pnpm dev",
|
|
61
|
-
lintFiles: "pnpm exec biome check --no-errors-on-unmatched"
|
|
62
|
-
},
|
|
63
|
-
npm: {
|
|
64
|
-
test: "npm test",
|
|
65
|
-
typecheck: "npx tsc --noEmit",
|
|
66
|
-
lint: "npx @biomejs/biome check .",
|
|
67
|
-
format: "npx @biomejs/biome format --write .",
|
|
68
|
-
install: "npm install",
|
|
69
|
-
dev: "npm run dev",
|
|
70
|
-
lintFiles: "npx @biomejs/biome check --no-errors-on-unmatched"
|
|
71
|
-
}
|
|
72
|
-
};
|
|
73
|
-
var FROZEN_INSTALL = {
|
|
74
|
-
[DERIVED_COMMANDS.bun.install]: "bun install --frozen-lockfile",
|
|
75
|
-
[DERIVED_COMMANDS.yarn.install]: "yarn install --immutable",
|
|
76
|
-
[DERIVED_COMMANDS.pnpm.install]: "pnpm install --frozen-lockfile",
|
|
77
|
-
[DERIVED_COMMANDS.npm.install]: "npm ci"
|
|
78
|
-
};
|
|
79
|
-
var BUN_COMMANDS = DERIVED_COMMANDS.bun;
|
|
80
|
-
|
|
81
15
|
// src/check/command-line.ts
|
|
82
16
|
var TOKEN = /(?:"[^"]*"|'[^']*'|\S)+/g;
|
|
83
17
|
var QUOTED = /"([^"]*)"|'([^']*)'/g;
|
|
@@ -86,6 +20,9 @@ var tokenize = (command3) => (command3.match(TOKEN) ?? []).map((token) => {
|
|
|
86
20
|
return token.replace(QUOTED, "$1$2");
|
|
87
21
|
});
|
|
88
22
|
|
|
23
|
+
// src/check/result.ts
|
|
24
|
+
var CHECK_KIND = { lint: "lint", project: "project", types: "types", tests: "tests" };
|
|
25
|
+
|
|
89
26
|
// src/check/schema.ts
|
|
90
27
|
var nonEmpty = z.string().min(1);
|
|
91
28
|
var isTokenizable = (command3) => {
|
|
@@ -99,6 +36,7 @@ var isTokenizable = (command3) => {
|
|
|
99
36
|
var command = nonEmpty.refine(isTokenizable, "Unterminated quote in command");
|
|
100
37
|
var glob = nonEmpty.refine((value) => !/[{}[\]]/.test(value), "Braces and brackets are not supported in globs");
|
|
101
38
|
var globs = z.array(glob).min(1);
|
|
39
|
+
var directory = nonEmpty.transform((value) => value.replace(/^(\.\/)+/, "").replace(/\/+$/, "") || ".");
|
|
102
40
|
var DEFAULT_BUILD_ARTIFACT_GLOBS = [
|
|
103
41
|
"**/tsup.config.bundled_*",
|
|
104
42
|
"**/dist/**",
|
|
@@ -115,7 +53,12 @@ var projectCheck = z.strictObject({
|
|
|
115
53
|
name: nonEmpty,
|
|
116
54
|
command,
|
|
117
55
|
/** Without globs the check runs on every change; with them, only when a changed path matches. */
|
|
118
|
-
globs: globs.optional()
|
|
56
|
+
globs: globs.optional(),
|
|
57
|
+
/**
|
|
58
|
+
* `tests` for a check that runs the project's tests, `types` for its type check; without one, a plain project check.
|
|
59
|
+
* A test run counts towards "no green without a test", and both run with the slow checks.
|
|
60
|
+
*/
|
|
61
|
+
kind: z.enum([CHECK_KIND.tests, CHECK_KIND.types]).optional()
|
|
119
62
|
});
|
|
120
63
|
var generatorOwned = z.strictObject({
|
|
121
64
|
glob,
|
|
@@ -133,9 +76,9 @@ var skillBudgets = z.record(glob, z.number().int().positive());
|
|
|
133
76
|
var checkConfigSchema = z.strictObject({
|
|
134
77
|
/**
|
|
135
78
|
* Run once over the changed files, which are appended as arguments. Separate from `commands.lint`,
|
|
136
|
-
* which lints the whole project.
|
|
79
|
+
* which lints the whole project. Detected from the repository when left out; without one, no lint runs.
|
|
137
80
|
*/
|
|
138
|
-
lintFilesCommand: command.
|
|
81
|
+
lintFilesCommand: command.optional(),
|
|
139
82
|
/** Project checks, run alongside lint; add repository-specific scripts here. */
|
|
140
83
|
checks: z.array(projectCheck).default([]),
|
|
141
84
|
generatorOwned: z.array(generatorOwned).default([]),
|
|
@@ -148,7 +91,16 @@ var checkConfigSchema = z.strictObject({
|
|
|
148
91
|
*/
|
|
149
92
|
skillBudgets: skillBudgets.optional(),
|
|
150
93
|
/** Also run tests in directories that hold test files but belong to no package. */
|
|
151
|
-
looseTests: z.boolean().default(false)
|
|
94
|
+
looseTests: z.boolean().default(false),
|
|
95
|
+
/**
|
|
96
|
+
* Package roots, as directories from the repository root, read only where no stack is detected: a changed file
|
|
97
|
+
* belongs to the nearest one above it, else to the repository root `.`.
|
|
98
|
+
*/
|
|
99
|
+
roots: z.array(directory).min(1).optional(),
|
|
100
|
+
/** The files a test covers; replaces the stack's pattern, or the rule of thumb where no stack is detected. */
|
|
101
|
+
sourceFiles: globs.optional(),
|
|
102
|
+
/** The test files; replaces the stack's pattern, or the rule of thumb where no stack is detected. */
|
|
103
|
+
testFiles: globs.optional()
|
|
152
104
|
});
|
|
153
105
|
|
|
154
106
|
// src/claude/paths.ts
|
|
@@ -196,6 +148,9 @@ var DAY_MS = 864e5;
|
|
|
196
148
|
var MAX_AUTO_WINDOW_DAYS = 7;
|
|
197
149
|
var windowDays = (since, until) => Math.round((Date.parse(`${until}T00:00:00Z`) - Date.parse(`${since}T00:00:00Z`)) / DAY_MS);
|
|
198
150
|
|
|
151
|
+
// src/config/base-branch.ts
|
|
152
|
+
var DEFAULT_BASE_BRANCH = "main";
|
|
153
|
+
|
|
199
154
|
// src/config/link-kinds.ts
|
|
200
155
|
var ARTIFACT_LINK_KIND = {
|
|
201
156
|
pattern: "^https://claude\\.ai/(code/)?artifact/",
|
|
@@ -310,6 +265,13 @@ var MODEL_ROLE = {
|
|
|
310
265
|
explore: "explore",
|
|
311
266
|
orchestrate: "orchestrate"
|
|
312
267
|
};
|
|
268
|
+
var DEFAULT_MODELS = {
|
|
269
|
+
[MODEL_ROLE.plan]: "claude-opus-5-5",
|
|
270
|
+
[MODEL_ROLE.implement]: "claude-sonnet-5-5",
|
|
271
|
+
[MODEL_ROLE.review]: "claude-sonnet-5-5",
|
|
272
|
+
[MODEL_ROLE.explore]: "claude-sonnet-5-5",
|
|
273
|
+
[MODEL_ROLE.orchestrate]: "claude-opus-5-5"
|
|
274
|
+
};
|
|
313
275
|
var isTerminalAdapter = (value) => Object.values(TERMINAL_ADAPTER).includes(value);
|
|
314
276
|
var isModelRole = (value) => Object.values(MODEL_ROLE).includes(value);
|
|
315
277
|
var SKILL_PREFIX = /^[a-z0-9]+-$/;
|
|
@@ -318,6 +280,17 @@ var language = z2.enum(Object.values(PLAN_LANGUAGE));
|
|
|
318
280
|
var modelId = z2.string().regex(FULL_MODEL_ID, "Use a full model ID such as claude-opus-5-5, not an alias like opus or sonnet");
|
|
319
281
|
var command2 = z2.string().min(1);
|
|
320
282
|
var tokenCount = z2.number().int().positive();
|
|
283
|
+
var commandsSchema = z2.strictObject({
|
|
284
|
+
test: command2.optional(),
|
|
285
|
+
typecheck: command2.optional(),
|
|
286
|
+
lint: command2.optional(),
|
|
287
|
+
format: command2.optional(),
|
|
288
|
+
/** Installs a new lane's dependencies. */
|
|
289
|
+
install: command2.optional(),
|
|
290
|
+
/** Starts a lane's dev server, run as `PORT=<port> <dev>`; a project supplement may name another. */
|
|
291
|
+
dev: command2.optional()
|
|
292
|
+
});
|
|
293
|
+
var COMMAND_KEYS = Object.keys(commandsSchema.shape);
|
|
321
294
|
var DEFAULT_CONTEXT_WINDOW = 2e5;
|
|
322
295
|
var laneCopyPath = z2.string().regex(
|
|
323
296
|
// biome-ignore lint/suspicious/noControlCharactersInRegex: the class exists to refuse control characters
|
|
@@ -587,24 +560,15 @@ var jackherdConfigSchema = z2.strictObject({
|
|
|
587
560
|
* kit name (`implement`, `plan`, …) apart from the kit's.
|
|
588
561
|
*/
|
|
589
562
|
skillPrefix: z2.union([z2.literal(""), z2.string().regex(SKILL_PREFIX, "lower-case letters and digits ending in a dash, e.g. kit-")]).default(""),
|
|
590
|
-
commands:
|
|
591
|
-
test: command2.default(BUN_COMMANDS.test),
|
|
592
|
-
typecheck: command2.default(BUN_COMMANDS.typecheck),
|
|
593
|
-
lint: command2.default(BUN_COMMANDS.lint),
|
|
594
|
-
format: command2.default(BUN_COMMANDS.format),
|
|
595
|
-
/** Installs a new lane's dependencies. */
|
|
596
|
-
install: command2.default(BUN_COMMANDS.install),
|
|
597
|
-
/** Starts a lane's dev server, run as `PORT=<port> <dev>`; a project supplement may name another. */
|
|
598
|
-
dev: command2.default(BUN_COMMANDS.dev)
|
|
599
|
-
}).prefault({}),
|
|
563
|
+
commands: commandsSchema.prefault({}),
|
|
600
564
|
/** How `/orchestrate` sets up a lane's worktree. */
|
|
601
565
|
lanes: z2.strictObject({
|
|
602
566
|
/** Untracked files copied into every new worktree; empty means the files `.worktreeinclude` lists. */
|
|
603
567
|
copy: z2.array(laneCopyPath).default([]),
|
|
604
|
-
/** The dependency lock file a rebase takes from the base branch and regenerates, never merges by hand. */
|
|
605
|
-
lockFile: lockFilePath.
|
|
568
|
+
/** The dependency lock file a rebase takes from the base branch and regenerates, never merges by hand; detected from the repository when left out. */
|
|
569
|
+
lockFile: lockFilePath.optional()
|
|
606
570
|
}).prefault({}),
|
|
607
|
-
models: z2.record(z2.enum(Object.values(MODEL_ROLE)), modelId),
|
|
571
|
+
models: z2.record(z2.enum(Object.values(MODEL_ROLE)), modelId).default(DEFAULT_MODELS),
|
|
608
572
|
terminal: z2.strictObject({ adapter: z2.enum(Object.values(TERMINAL_ADAPTER)).default(TERMINAL_ADAPTER.none) }).prefault({}),
|
|
609
573
|
/** How the coordinator shows a draft for review (`orchestrate` § 4). */
|
|
610
574
|
preview: z2.strictObject({
|
|
@@ -669,7 +633,9 @@ var parseSourceDraft = (raw, label) => parseLabelled(sourceSchema, raw, label);
|
|
|
669
633
|
var renderConfigJsonSchema = () => `${JSON.stringify(z3.toJSONSchema(jackherdConfigSchema, { io: "input" }), null, 2)}
|
|
670
634
|
`;
|
|
671
635
|
export {
|
|
636
|
+
COMMAND_KEYS,
|
|
672
637
|
DEFAULT_CONTEXT_WINDOW,
|
|
638
|
+
DEFAULT_MODELS,
|
|
673
639
|
MODEL_ROLE,
|
|
674
640
|
SKILL_PREFIX,
|
|
675
641
|
TERMINAL_ADAPTER,
|
package/install.sh
CHANGED
|
@@ -9,7 +9,7 @@ set -eu
|
|
|
9
9
|
|
|
10
10
|
# The default is the version this file ships with. The release bump changes it together with
|
|
11
11
|
# package.json; install.test.ts fails while the two differ. Overridable, but only by a plain release.
|
|
12
|
-
JACKHERD_VERSION="${JACKHERD_VERSION:-1.
|
|
12
|
+
JACKHERD_VERSION="${JACKHERD_VERSION:-1.14.0}"
|
|
13
13
|
|
|
14
14
|
JACKHERD_PACKAGE='jackherd'
|
|
15
15
|
# Test seam: the terminal the wizard asks its questions on.
|
|
@@ -1,42 +1,24 @@
|
|
|
1
1
|
import { baseBranchOf } from '../shared/base-branch'
|
|
2
|
-
import {
|
|
2
|
+
import { type BoardLocation, boardLocationValues } from '../shared/boards'
|
|
3
3
|
import { isRecord } from '../shared/json'
|
|
4
|
-
import { isRepositoryDirectory } from '../shared/repository-directory'
|
|
5
4
|
import { isControlCharacter } from './plain-path'
|
|
6
5
|
|
|
7
6
|
/**
|
|
8
7
|
* Where plans, boards and lanes live, the base branch, and the checks a standing approval waits for. `null` from
|
|
9
8
|
* `boardSettingsOf` means the standing approval is off and every merge asks.
|
|
10
9
|
*/
|
|
11
|
-
export type BoardSettings = {
|
|
12
|
-
planLanguage: BoardLanguage
|
|
13
|
-
planDirectory: string
|
|
14
|
-
board: { pattern: string; file: string }
|
|
15
|
-
worktreeDirectory: string
|
|
10
|
+
export type BoardSettings = BoardLocation & {
|
|
16
11
|
baseBranch: string
|
|
17
12
|
/** The checks that must be among the head commit's and green; undefined: at least one check, every one green. */
|
|
18
13
|
requiredChecks: readonly string[] | undefined
|
|
19
14
|
}
|
|
20
15
|
|
|
21
16
|
const MAX_CHECK_NAME = 200
|
|
22
|
-
const BOARD_PATTERN = /^(?![-.])[A-Za-z0-9._*?-]+$/
|
|
23
|
-
const BOARD_FILE = /^(?![-.])[A-Za-z0-9._-]+\.md$/
|
|
24
|
-
|
|
25
|
-
/** The value at `key`, its default when absent, `undefined` when present but invalid. */
|
|
26
|
-
const valueOr = <T>(section: Record<string, unknown>, key: string, isValid: (value: unknown) => value is T, fallback: T) =>
|
|
27
|
-
!(key in section) ? fallback : isValid(section[key]) ? section[key] : undefined
|
|
28
17
|
|
|
29
18
|
/** An optional value: `{ value }` with `value` undefined when absent, `undefined` when present but invalid. */
|
|
30
19
|
const optionalValue = <T>(section: Record<string, unknown>, key: string, isValid: (value: unknown) => value is T) =>
|
|
31
20
|
!(key in section) ? { value: undefined } : isValid(section[key]) ? { value: section[key] } : undefined
|
|
32
21
|
|
|
33
|
-
const matches =
|
|
34
|
-
(pattern: RegExp) =>
|
|
35
|
-
(value: unknown): value is string =>
|
|
36
|
-
typeof value === 'string' && pattern.test(value)
|
|
37
|
-
|
|
38
|
-
const isPlanLanguage = (value: unknown): value is BoardLanguage => typeof value === 'string' && Object.hasOwn(BOARD_WORDS, value)
|
|
39
|
-
|
|
40
22
|
const isSectionOrAbsent = (config: Record<string, unknown>, key: string): boolean =>
|
|
41
23
|
!(key in config) || isRecord(config[key])
|
|
42
24
|
|
|
@@ -64,18 +46,11 @@ const requiredChecksOf = (guard: Record<string, unknown>) =>
|
|
|
64
46
|
: optionalValue(sectionAt(guard, 'planApproval'), 'requiredChecks', isCheckList)
|
|
65
47
|
|
|
66
48
|
/** Every value the standing approval reads, with its default where absent; `undefined` stands for a present but invalid one. */
|
|
67
|
-
const boardValues = (config: Record<string, unknown>) => {
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
planDirectory: valueOr(config, 'planDirectory', isRepositoryDirectory, 'docs/plans'),
|
|
73
|
-
pattern: valueOr(board, 'pattern', matches(BOARD_PATTERN), 'board-*'),
|
|
74
|
-
file: valueOr(board, 'file', matches(BOARD_FILE), 'board.md'),
|
|
75
|
-
worktreeDirectory: valueOr(config, 'worktreeDirectory', isRepositoryDirectory, '.claude/worktrees'),
|
|
76
|
-
baseBranch: baseBranchOf(config),
|
|
77
|
-
}
|
|
78
|
-
}
|
|
49
|
+
const boardValues = (config: Record<string, unknown>) => ({
|
|
50
|
+
requiredChecks: requiredChecksOf(sectionAt(config, 'guard')),
|
|
51
|
+
...boardLocationValues(config, sectionAt(config, 'board')),
|
|
52
|
+
baseBranch: baseBranchOf(config),
|
|
53
|
+
})
|
|
79
54
|
|
|
80
55
|
type Values = ReturnType<typeof boardValues>
|
|
81
56
|
type ValidValues = { [Key in keyof Values]: Exclude<Values[Key], undefined> }
|
|
@@ -3,15 +3,12 @@
|
|
|
3
3
|
* link on the boards, as the main checkout holds them (the coordinator keeps its board there, committed or not). The
|
|
4
4
|
* git call runs without a shell and with a timeout; any failure answers nothing, so the guard asks.
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
7
|
-
import { join, posix, relative, resolve } from 'node:path'
|
|
6
|
+
import { relative, resolve } from 'node:path'
|
|
8
7
|
import { type BoardLanguage, lanePlansOf } from '../shared/board-lanes'
|
|
8
|
+
import { type Board, boardsIn } from '../shared/boards'
|
|
9
9
|
import { laneOf } from '../shared/lane'
|
|
10
10
|
import type { BoardSettings } from './board-settings'
|
|
11
11
|
|
|
12
|
-
/** A board's directory relative to the repository, and its text. */
|
|
13
|
-
export type Board = { directory: string; text: string }
|
|
14
|
-
|
|
15
12
|
const GIT_TIMEOUT_MS = 3000
|
|
16
13
|
|
|
17
14
|
/** The lane whose worktree, directly below the worktree directory, has `branch` checked out. */
|
|
@@ -47,30 +44,6 @@ const worktreeListOf = (main: string): string | undefined => {
|
|
|
47
44
|
return result.exitCode === 0 ? result.stdout.toString() : undefined
|
|
48
45
|
}
|
|
49
46
|
|
|
50
|
-
const readTextOrUndefined = (path: string): string | undefined => {
|
|
51
|
-
try {
|
|
52
|
-
return readFileSync(path, 'utf8')
|
|
53
|
-
} catch {
|
|
54
|
-
return undefined
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
/** The boards under the plan directory of the main checkout whose directory name matches the board pattern. */
|
|
59
|
-
const boardsIn = (main: string, settings: BoardSettings): Board[] => {
|
|
60
|
-
const pattern = new Bun.Glob(settings.board.pattern)
|
|
61
|
-
try {
|
|
62
|
-
return readdirSync(join(main, settings.planDirectory), { withFileTypes: true })
|
|
63
|
-
.filter((entry) => entry.isDirectory() && pattern.match(entry.name))
|
|
64
|
-
.flatMap((entry) => {
|
|
65
|
-
const directory = posix.join(settings.planDirectory, entry.name)
|
|
66
|
-
const text = readTextOrUndefined(join(main, directory, settings.board.file))
|
|
67
|
-
return text === undefined ? [] : [{ directory, text }]
|
|
68
|
-
})
|
|
69
|
-
} catch {
|
|
70
|
-
return []
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
|
|
74
47
|
/** The one plan the lane of `branch` links on the boards of the main checkout; undefined when it cannot tell. */
|
|
75
48
|
export const planOfBranch = (main: string, settings: BoardSettings, branch: string): string | undefined => {
|
|
76
49
|
const list = worktreeListOf(main)
|