@boopkit/cli 0.7.0 → 0.8.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.
@@ -8,9 +8,9 @@
8
8
  "@boopkit/cap-hello": "0.4.1",
9
9
  "@boopkit/cap-noticeboard": "0.4.1",
10
10
  "@boopkit/cap-oauth-client": "0.1.1",
11
- "@boopkit/cap-oauth-server": "0.3.0",
11
+ "@boopkit/cap-oauth-server": "0.3.2",
12
12
  "@boopkit/cap-product-design": "0.6.11",
13
- "@boopkit/cap-project-mgmt": "0.4.12",
13
+ "@boopkit/cap-project-mgmt": "0.4.13",
14
14
  "@boopkit/cap-services": "0.4.3",
15
15
  "@boopkit/cap-skills": "0.5.6",
16
16
  "@boopkit/cap-testing": "0.5.8",
package/dist/cli.js CHANGED
File without changes
@@ -244,6 +244,17 @@ export declare function getMatchingReadyIssues(prefix: string, includeFeatures:
244
244
  maxBuffer?: number;
245
245
  }) => string;
246
246
  }): ReadyIssue[];
247
+ /** A skill folder in a project's skills/ directory. */
248
+ export interface InstalledSkill {
249
+ name: string;
250
+ hasProjectFile: boolean;
251
+ }
252
+ /**
253
+ * The skills installed in a project folder: each folder under skills/ that holds
254
+ * a SKILL.md. statSync follows symlinks, which matters because boop's own
255
+ * skills/ entries are symlinks into each capability package's skills/ folder.
256
+ */
257
+ export declare function readInstalledSkills(projectDir: string): InstalledSkill[];
247
258
  /**
248
259
  * The instructions every loop session starts from. A session acts on this text
249
260
  * literally and reads it whether or not it opens a skill, so step 5 carries the
@@ -251,8 +262,15 @@ export declare function getMatchingReadyIssues(prefix: string, includeFeatures:
251
262
  * when the issue asks) and names no command, because the text is the same in
252
263
  * every project and the commands are not. For the same reason it names no
253
264
  * project.
265
+ *
266
+ * Step 3 lists, as paths, the installed skills the issue names: each SKILL.md,
267
+ * plus the PROJECT.md beside it where there is one. Sessions open a skill they
268
+ * only see named about one time in seven, but read a path the prompt spells out.
269
+ * An issue that names no installed skill is pointed at boop-skills-discovery
270
+ * instead, and without `installed` (or without discovery installed) step 3 keeps
271
+ * the general "read relevant skills" wording.
254
272
  */
255
- export declare function buildPrompt(issue: IssueInfo): string;
273
+ export declare function buildPrompt(issue: IssueInfo, installed?: InstalledSkill[]): string;
256
274
  /**
257
275
  * Default per-issue budget cap in USD. The planning phase and each task each get
258
276
  * this cap. Single source of truth — imported by cli.ts for its --budget help
@@ -52,6 +52,7 @@ exports.isIssueInScope = isIssueInScope;
52
52
  exports.isClaimableReadyIssue = isClaimableReadyIssue;
53
53
  exports.shouldCloseFeatureRoot = shouldCloseFeatureRoot;
54
54
  exports.getMatchingReadyIssues = getMatchingReadyIssues;
55
+ exports.readInstalledSkills = readInstalledSkills;
55
56
  exports.buildPrompt = buildPrompt;
56
57
  exports.shouldRotateSession = shouldRotateSession;
57
58
  exports.sessionDidWork = sessionDidWork;
@@ -883,6 +884,65 @@ function isIssueClosed(id) {
883
884
  return false;
884
885
  }
885
886
  }
887
+ /**
888
+ * The skills installed in a project folder: each folder under skills/ that holds
889
+ * a SKILL.md. statSync follows symlinks, which matters because boop's own
890
+ * skills/ entries are symlinks into each capability package's skills/ folder.
891
+ */
892
+ function readInstalledSkills(projectDir) {
893
+ const skillsDir = path.join(projectDir, 'skills');
894
+ let entries;
895
+ try {
896
+ entries = fs.readdirSync(skillsDir);
897
+ }
898
+ catch {
899
+ return [];
900
+ }
901
+ const skills = [];
902
+ for (const name of entries) {
903
+ const dir = path.join(skillsDir, name);
904
+ try {
905
+ if (!fs.statSync(dir).isDirectory() || !fs.existsSync(path.join(dir, 'SKILL.md')))
906
+ continue;
907
+ }
908
+ catch {
909
+ continue;
910
+ }
911
+ skills.push({ name, hasProjectFile: fs.existsSync(path.join(dir, 'PROJECT.md')) });
912
+ }
913
+ return skills;
914
+ }
915
+ /**
916
+ * The installed skills an issue description names, by bare name or by path, in
917
+ * the order each is first mentioned. A name only matches whole, so
918
+ * boop-testing-testing does not match inside boop-testing-testing-express-web.
919
+ */
920
+ function namedSkills(description, installed) {
921
+ const found = [];
922
+ for (const skill of installed) {
923
+ const escaped = skill.name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
924
+ const match = new RegExp(`(?<![\\w-])${escaped}(?![\\w-])`).exec(description);
925
+ if (match)
926
+ found.push({ skill, at: match.index });
927
+ }
928
+ return found.sort((a, b) => a.at - b.at).map((f) => f.skill);
929
+ }
930
+ function skillStep(issue, installed) {
931
+ const general = '3. Read relevant skills from skills/ (list folders, read each SKILL.md) before making changes';
932
+ if (!installed)
933
+ return general;
934
+ const named = namedSkills(issue.description, installed);
935
+ if (named.length > 0) {
936
+ const lines = named.map((s) => s.hasProjectFile
937
+ ? ` - skills/${s.name}/SKILL.md and skills/${s.name}/PROJECT.md`
938
+ : ` - skills/${s.name}/SKILL.md`);
939
+ return `3. Before making changes, read each of these in full, unless you have already read it in this session:\n${lines.join('\n')}`;
940
+ }
941
+ if (installed.some((s) => s.name === 'boop-skills-discovery')) {
942
+ return '3. Follow skills/boop-skills-discovery/SKILL.md to choose the skills relevant to this work, and read those skills before making changes';
943
+ }
944
+ return general;
945
+ }
886
946
  /**
887
947
  * The instructions every loop session starts from. A session acts on this text
888
948
  * literally and reads it whether or not it opens a skill, so step 5 carries the
@@ -890,8 +950,15 @@ function isIssueClosed(id) {
890
950
  * when the issue asks) and names no command, because the text is the same in
891
951
  * every project and the commands are not. For the same reason it names no
892
952
  * project.
953
+ *
954
+ * Step 3 lists, as paths, the installed skills the issue names: each SKILL.md,
955
+ * plus the PROJECT.md beside it where there is one. Sessions open a skill they
956
+ * only see named about one time in seven, but read a path the prompt spells out.
957
+ * An issue that names no installed skill is pointed at boop-skills-discovery
958
+ * instead, and without `installed` (or without discovery installed) step 3 keeps
959
+ * the general "read relevant skills" wording.
893
960
  */
894
- function buildPrompt(issue) {
961
+ function buildPrompt(issue, installed) {
895
962
  return `You are working on issue ${issue.id} in this project.
896
963
 
897
964
  ## Issue: ${issue.title}
@@ -903,7 +970,7 @@ ${issue.description}
903
970
  ## Instructions
904
971
  1. Read AGENTS.md for project orientation
905
972
  2. Claim the issue: bd update ${issue.id} --claim
906
- 3. Read relevant skills from skills/ (list folders, read each SKILL.md) before making changes
973
+ ${skillStep(issue, installed)}
907
974
  4. Implement the changes following TDD-Lite: observe, implement, verify
908
975
  5. Run the tests this issue needs, and no more:
909
976
  - If the issue description states which tests to run, run exactly those and skip the rest of this step. The issue's scope replaces the default below; do not run both.
@@ -1201,7 +1268,8 @@ async function processIssue(issue, opts) {
1201
1268
  }
1202
1269
  }
1203
1270
  // Initial run
1204
- const prompt = buildPrompt(issue);
1271
+ // opts.cwd is the worktree the session runs in, so its skills/ is the one the session sees.
1272
+ const prompt = buildPrompt(issue, readInstalledSkills(opts.cwd ?? process.cwd()));
1205
1273
  console.log(`\x1b[32m[bd-loop]\x1b[0m 🚀 Starting Claude session (budget: $${opts.budget}, session: ${sessionId.substring(0, 8)}...)`);
1206
1274
  const initialRun = await runClaude(prompt, {
1207
1275
  budget: opts.budget,
package/docs/loop.md CHANGED
@@ -29,6 +29,12 @@ Without `--run`, the command shows what it would do without executing anything.
29
29
 
30
30
  Each Claude session gets a full prompt with issue details and instructions to work autonomously through the complete workflow: claim, implement, test, commit, close, and push.
31
31
 
32
+ The prompt tells a session which skills to read before it makes changes.
33
+ The loop matches the names of the skills installed in the session's own `skills/` folder against the issue description, accepting either a bare name such as `boop-testing-testing` or a path such as `skills/boop-testing-testing/SKILL.md`.
34
+ It lists each skill the issue names, once and in the order first mentioned, as the path to its `SKILL.md`, adding the `PROJECT.md` beside it where there is one, and asks the session to read each in full unless it has already read it in this session.
35
+ When the issue names no installed skill, the prompt instead tells the session to follow `skills/boop-skills-discovery/SKILL.md` to choose the relevant skills.
36
+ Spelling out the paths matters: a session starts with every skill's name and description in view but none of their content, and it opens a skill it only sees named about one time in seven.
37
+
32
38
  The prompt does not tell a session to run the whole test suite. If the issue says which tests to run, the session runs those. Otherwise it runs the focused tests for the files it changed plus the project's static check, using the commands the `boop-testing-testing` skill and the `PROJECT.md` beside it give. The full suite runs only when an issue asks for it, which is normally the task that completes the branch, and again when `boop loop --merge` integrates the branch.
33
39
 
34
40
  ### Planning mode (`--work` + `--flow`)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@boopkit/cli",
3
- "version": "0.7.0",
3
+ "version": "0.8.0",
4
4
  "description": "boop: the opinionated bootstrap for founder friendly, AI-first web apps - code plus agent skills, composable from day one.",
5
5
  "main": "dist/cli.js",
6
6
  "workspaces": [