From 03f036334c8915ab2d7a3926ea80553f62d9d683 Mon Sep 17 00:00:00 2001 From: "Sakamoto, Kazunori" Date: Tue, 10 Nov 2026 23:51:06 +0900 Subject: [PATCH 1/9] feat(wbfy): read the project's purpose, goals, non-goals, and milestones from README.md AGENTS.md lists the `## Purpose`, `## Goals`, `## Non-goals`, and `## Milestones` sections of the root README.md, so the text people read is the text agents get. The configuration file is `wbfy.json` and holds only `typos`. Co-authored-by: WillBooster (Claude Code) --- docs/expected-repository-rules.md | 5 +- packages/wbfy/src/fixers/typos.ts | 3 +- packages/wbfy/src/generators/agents.ts | 35 +++++++++-- packages/wbfy/src/index.ts | 15 ++++- packages/wbfy/src/packageConfig.ts | 11 +--- packages/wbfy/src/projectDirection.ts | 44 ++++++++++++++ .../wbfy/test/unit/agentInstructions.test.ts | 60 ++++++++++++++----- packages/wbfy/test/unit/typos.test.ts | 6 +- 8 files changed, 140 insertions(+), 39 deletions(-) create mode 100644 packages/wbfy/src/projectDirection.ts diff --git a/docs/expected-repository-rules.md b/docs/expected-repository-rules.md index 2e109207b..0ce32e761 100644 --- a/docs/expected-repository-rules.md +++ b/docs/expected-repository-rules.md @@ -25,8 +25,9 @@ A repository that deviates from these rules is fixed manually (or by re-running | File(s) | Ownership | | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `AGENTS_EXTRA.md` | The only human-edited agent-instruction file. `AGENTS.md` and `.gemini/styleguide.md` are fully generated from it (and `AGENTS.md` also from `wbfy.yaml`) and are overwritten on every run; `CLAUDE.md`, `GEMINI.md`, and `.cursor/rules/general.mdc` are removed. | -| `wbfy.yaml` | Optional and human-edited: the only `wbfy` configuration file, read only from the directory `wbfy` is run on (workspace copies are otherwise ignored). `purpose` (one line) and `goals` (one-line items) appear under `Project Information` in `AGENTS.md`; `typos` adds replacements (`all`, `doc`, `ts`, `text`). `wbfy` skips a repository whose file is invalid, including one with unknown keys. | +| `AGENTS_EXTRA.md` | The only human-edited agent-instruction file. `AGENTS.md` and `.gemini/styleguide.md` are fully generated from it (and `AGENTS.md` also from the `README.md` sections below) and are overwritten on every run; `CLAUDE.md`, `GEMINI.md`, and `.cursor/rules/general.mdc` are removed. | +| `## Purpose`, `## Goals`, `## Non-goals`, `## Milestones` in `README.md` | Optional and human-edited, in the README of the directory `wbfy` is run on (workspace READMEs are not read); `wbfy` copies them under `Project Information` in `AGENTS.md`. The headings are spelled exactly so, whatever language the README is written in. `## Purpose` holds one paragraph: whose situation the project exists to change, and how. Each of the others holds one list: `## Goals` the product states to reach, in priority order (at most five); `## Non-goals` what is deliberately left out; `## Milestones` business results a number can verify, in the order to reach them (at most three), as target values without current ones. An achieved line is deleted. A public repository writes only what may be public. `wbfy` skips a repository in which a section is repeated or holds anything but that one paragraph or list. | +| `wbfy.json` | Optional and human-edited: the only `wbfy` configuration file, read only from the directory `wbfy` is run on (workspace copies are otherwise ignored). `typos` adds replacements (`all`, `doc`, `ts`, `text`). `wbfy` skips a repository whose file is invalid, including one with unknown keys. | | `.gitignore`, `.prettierignore`, `.dockerignore` | Human content goes only before `# Generated by wbfy` (head) and after the last `# Project-specific settings` (tail); everything between is regenerated. `.prettierignore` exists only in Java repositories; remove it manually when Java is removed. `.dockerignore` exists only alongside a `Dockerfile`/`docker-compose.yml` and is deleted wholesale when the repository stops qualifying. | | `oxlint.config.ts` | Only content outside `// wbfy:start ` / `// wbfy:end ` pairs survives; a file without markers is replaced wholesale — except a published `willbooster-configs` package's `oxlint.config.ts`, which is preserved as product content. | | Self-contained GitHub workflows | Owned by `wbfy` iff the file starts with `# Generated by wbfy.`; removing that prefix is the opt-out. When regenerating a file, `wbfy` writes for each step pinned to a version tag the newest tag among its own pin and every existing step of that action (e.g. one Renovate updated). Any other existing ref, such as a commit SHA or a branch name, is replaced: the file is rewritten without comments, so a kept SHA would lose the version comment Renovate needs to update it. For the same reason `wbfy`'s own commit SHA pins are always written. In reusable-workflow callers that are not skipped, every job calling the organization's `reusable-workflows` is normalized on each run; other jobs in the same file are left alone, and comments stay with the entries they precede or follow. `release.yml` is deleted when no release branch is configured, except for skipped custom or pinned callers. | diff --git a/packages/wbfy/src/fixers/typos.ts b/packages/wbfy/src/fixers/typos.ts index 5d20adf4d..2890079e7 100644 --- a/packages/wbfy/src/fixers/typos.ts +++ b/packages/wbfy/src/fixers/typos.ts @@ -46,8 +46,7 @@ export async function fixTypos(packageConfig: PackageConfig): Promise { const textBasedFiles = await fg.glob('**/*.{csv,htm,html,tsv,xml,yaml,yml}', { dot: true, cwd: dirPath, - // Rewriting wbfy.yaml would apply its `typos` to their own keys and disable them on the next run. - ignore: [...globIgnore, 'wbfy.yaml'], + ignore: globIgnore, }); if (options.isVerbose) { console.info(`Found ${textBasedFiles.length} text-based files in ${dirPath}`); diff --git a/packages/wbfy/src/generators/agents.ts b/packages/wbfy/src/generators/agents.ts index 8b0f72e40..44972badc 100644 --- a/packages/wbfy/src/generators/agents.ts +++ b/packages/wbfy/src/generators/agents.ts @@ -8,6 +8,8 @@ import { enablesViteReactCompiler } from '../fixers/viteConfig.js'; import { logger } from '../logger.js'; import type { PackageConfig } from '../packageConfig.js'; import { generatesWorkerTypes } from '../packageConfig.js'; +import type { ProjectDirection } from '../projectDirection.js'; +import { projectDirectionTitles, readProjectDirection } from '../projectDirection.js'; import { fsUtil } from '../utils/fsUtil.js'; import { runAllInPool } from '../utils/promisePool.js'; import { hasCloudflareDeployWorkflow, invokesWbDeploy } from './workflow.js'; @@ -31,7 +33,14 @@ export async function generateAgentInstructions(rootConfig: PackageConfig, allCo const codingStyle = generateAgentCodingStyle(rootConfig, allConfigs, await usesReactCompiler(allConfigs)); const agentsMdPath = path.resolve(rootConfig.dirPath, 'AGENTS.md'); - const agentsMdContent = generateAgentInstruction(rootConfig, allConfigs, deployFacts, codingStyle, extraContent); + const agentsMdContent = generateAgentInstruction( + rootConfig, + allConfigs, + deployFacts, + codingStyle, + await readProjectDirection(rootConfig.dirPath), + extraContent + ); // Cursor reads AGENTS.md itself, and its CLI applies it alongside `.cursor/rules`, so the // former copy in `.cursor/rules/general.mdc` only duplicated these instructions. await runAllInPool([ @@ -71,11 +80,15 @@ function generateAgentInstruction( allConfigs: PackageConfig[], deployFacts: DeployFacts, codingStyle: string, + direction: ProjectDirection, extraContent?: string ): string { const packageManager = 'bun'; const description = rootConfig.packageJson?.description; - const { purpose, goals } = rootConfig.wbfyConfig ?? {}; + const directionInstruction = + direction.Goals || direction.Milestones + ? '\n- `Goals` and `Milestones` under Project Information are not yet achieved: use them to choose between alternatives and to propose work, never to implement beyond what a task requests, and never present them as existing features or achieved results (e.g., in announcements). Delete a line from `README.md` once it is achieved.' + : ''; const coAuthorInstruction = rootConfig.isWillBoosterRepo ? '\n - End your commit message with a blank line followed by `Co-authored-by: WillBooster (AGENT_NAME) `, replacing only `AGENT_NAME` with your agent name (e.g., `Claude Code`), the only AI attribution to add.' : ''; @@ -91,7 +104,7 @@ function generateAgentInstruction( ## Project Information - Name: \`${projectName}\`${description ? `\n- Description: ${description}` : ''} -- Package Manager: ${packageManager}${purpose ? `\n- Purpose: ${purpose}` : ''}${goals ? `\n- Goals:\n${goals.map((goal) => ` - ${goal}`).join('\n')}` : ''} +- Package Manager: ${packageManager}${renderProjectDirection(direction)} ## General Instructions @@ -107,11 +120,11 @@ ${TEST_WRITING_RULES} - Once verified, commit and push to the current branch, and create a PR via \`gh\` if none exists for the branch, unless the user asked only for an investigation, an experiment, or a local change. - Follow the Conventional Commits format (e.g., \`feat:\`, \`fix:\`).${coAuthorInstruction} - Always create new commits; avoid \`--amend\`.${prTemplateInstruction}${issueTemplateInstruction} -- In any explanatory text (commit messages, PR descriptions, documentation, code comments, etc.) other than the PR body's Requirements section, describe only the current implementation: drop any statement naming an identifier, feature, or concept you cannot confirm exists in the final diff or the current codebase (e.g., one added and later removed or renamed along the way). Whenever your change invalidates documentation or comments, or you find ones that no longer match the current implementation, delete or rewrite them, even in files you are not otherwise changing. Mention a past state only where it is needed to understand why the current design is as it is, or when explicitly asked; files that record history by design (e.g., a changelog) are exempt. +- In any explanatory text (commit messages, PR descriptions, documentation, code comments, etc.) other than the PR body's Requirements section and the \`## Purpose\`, \`## Goals\`, \`## Non-goals\`, and \`## Milestones\` sections of \`README.md\`, describe only the current implementation: drop any statement naming an identifier, feature, or concept you cannot confirm exists in the final diff or the current codebase (e.g., one added and later removed or renamed along the way). Whenever your change invalidates documentation or comments, or you find ones that no longer match the current implementation, delete or rewrite them, even in files you are not otherwise changing. Mention a past state only where it is needed to understand why the current design is as it is, or when explicitly asked; files that record history by design (e.g., a changelog) are exempt. - Use heredoc for multi-line command input (e.g., \`git commit -F -\`, \`gh pr create --body-file -\`, \`gh issue create --body-file -\`). - Put temporary files in \`.tmp\`; use \`/tmp\` only for files that must live outside the repo. - Before finishing, delete every temporary file, backup, and external resource (e.g., databases, deployed Workers, branches) you created during the task for experiments or verification, without asking, after recording any findings worth keeping in your final response or in a file you keep. Keep an item only when a next step the user requested needs it or it holds the only copy of pre-task data the task was not meant to discard (e.g., a backup a tool kept because restoring it conflicted), and then state in your final response why and the command that deletes it. -- \`AGENTS.md\` and \`.gemini/styleguide.md\` are generated by \`wbfy\`; edit agent instructions only in \`AGENTS_EXTRA.md\` and the project purpose and goals only in \`wbfy.yaml\`.${generateStackInstructions(rootConfig, allConfigs, deployFacts, packageManager)} +- \`AGENTS.md\` and \`.gemini/styleguide.md\` are generated by \`wbfy\`; edit agent instructions only in \`AGENTS_EXTRA.md\`, and the project's purpose, goals, non-goals, and milestones only in the \`## Purpose\` (one paragraph), \`## Goals\`, \`## Non-goals\`, and \`## Milestones\` (one list each) sections of \`README.md\`.${directionInstruction}${generateStackInstructions(rootConfig, allConfigs, deployFacts, packageManager)} ${codingStyle} ` @@ -128,6 +141,18 @@ ${codingStyle} return baseContent + normalizedExtraContent; } +function renderProjectDirection(direction: ProjectDirection): string { + return projectDirectionTitles + .map((title) => { + const body = direction[title]; + if (!body) return ''; + return title === 'Purpose' + ? `\n- Purpose: ${body.replaceAll(/\s*\n\s*/g, ' ')}` + : `\n- ${title}:\n${body.replaceAll(/^(?=.)/gm, ' ')}`; + }) + .join(''); +} + function generateStackInstructions( rootConfig: PackageConfig, allConfigs: PackageConfig[], diff --git a/packages/wbfy/src/index.ts b/packages/wbfy/src/index.ts index e98ef72ed..3cbb7e44a 100644 --- a/packages/wbfy/src/index.ts +++ b/packages/wbfy/src/index.ts @@ -53,6 +53,7 @@ import { generateGitHubTemplates } from './github/template.js'; import { options } from './options.js'; import type { PackageConfig } from './packageConfig.js'; import { getPackageConfig, getWorkerTypesScriptError, readWbfyConfig } from './packageConfig.js'; +import { readProjectDirection } from './projectDirection.js'; import { assertSafeDependencySources } from './utils/dependencySourcePolicy.js'; import { fsUtil } from './utils/fsUtil.js'; import { hasGitHubToken } from './utils/githubUtil.js'; @@ -195,7 +196,8 @@ async function willboosterifyRepository(rootDirPath: string, skipDeps: boolean): const preflightError = (await findUnwritableManagedFileError(rootDirPath, subDirPaths)) ?? findInvalidPackageJsonError(rootDirPath, subDirPaths) ?? - findInvalidWbfyConfigError(rootDirPath); + findInvalidWbfyConfigError(rootDirPath) ?? + (await findInvalidProjectDirectionError(rootDirPath)); if (preflightError) { console.error(preflightError); return false; @@ -380,7 +382,16 @@ function findInvalidWbfyConfigError(rootDirPath: string): string | undefined { readWbfyConfig(rootDirPath); return undefined; } catch (error) { - return `Skip ${rootDirPath}:\n${path.resolve(rootDirPath, 'wbfy.yaml')} is invalid: ${String(error)}`; + return `Skip ${rootDirPath}:\n${path.resolve(rootDirPath, 'wbfy.json')} is invalid: ${String(error)}`; + } +} + +async function findInvalidProjectDirectionError(rootDirPath: string): Promise { + try { + await readProjectDirection(rootDirPath); + return undefined; + } catch (error) { + return `Skip ${rootDirPath}:\n${String(error)}`; } } diff --git a/packages/wbfy/src/packageConfig.ts b/packages/wbfy/src/packageConfig.ts index 23fc2669f..6870a0ff5 100644 --- a/packages/wbfy/src/packageConfig.ts +++ b/packages/wbfy/src/packageConfig.ts @@ -6,7 +6,6 @@ import path from 'node:path'; import { getGlobIgnore } from '@willbooster/shared-lib-node/src'; import { escapeRegExp } from '@willbooster/shared-lib/src'; import fg from 'fast-glob'; -import * as yaml from 'js-yaml'; import { parse as parseToml } from 'smol-toml'; import type { PackageJson } from 'type-fest'; import { z } from 'zod'; @@ -110,11 +109,7 @@ export interface PackageConfig { type WbfyConfig = z.infer; -const singleLineTextSchema = z.string().trim().min(1).regex(/^.+$/u, 'Must be a single line'); - const wbfyConfigSchema = z.strictObject({ - purpose: singleLineTextSchema.optional(), - goals: z.array(singleLineTextSchema).min(1).optional(), typos: z .strictObject({ all: z.record(z.string(), z.string()).optional(), @@ -391,11 +386,11 @@ async function readDockerfile(dirPath: string): Promise { } } -/** Throws when `wbfy.yaml` is invalid, which the CLI reports before changing anything. */ +/** Throws when `wbfy.json` is invalid, which the CLI reports before changing anything. */ export function readWbfyConfig(dirPath: string): WbfyConfig | undefined { - const wbfyConfigPath = path.resolve(dirPath, 'wbfy.yaml'); + const wbfyConfigPath = path.resolve(dirPath, 'wbfy.json'); if (!fs.existsSync(wbfyConfigPath)) return undefined; - return wbfyConfigSchema.parse(yaml.load(fs.readFileSync(wbfyConfigPath, 'utf8'))); + return wbfyConfigSchema.parse(JSON.parse(fs.readFileSync(wbfyConfigPath, 'utf8'))); } async function resolveRepoIdentity( diff --git a/packages/wbfy/src/projectDirection.ts b/packages/wbfy/src/projectDirection.ts new file mode 100644 index 000000000..0f9cf2548 --- /dev/null +++ b/packages/wbfy/src/projectDirection.ts @@ -0,0 +1,44 @@ +import path from 'node:path'; + +import type { RootContent } from 'mdast'; +import { fromMarkdown } from 'mdast-util-from-markdown'; + +import { fsUtil } from './utils/fsUtil.js'; + +const sectionBodyTypes = { Purpose: 'paragraph', Goals: 'list', 'Non-goals': 'list', Milestones: 'list' } as const; + +type SectionTitle = keyof typeof sectionBodyTypes; + +/** The Markdown source of each section body, keyed by the section's `##` heading text in `README.md`. */ +export type ProjectDirection = Partial>; + +export const projectDirectionTitles = Object.keys(sectionBodyTypes) as SectionTitle[]; + +/** Throws when a section is not exactly one paragraph or list, which the CLI reports before changing anything. */ +export async function readProjectDirection(rootDirPath: string): Promise { + const readme = await fsUtil.readFileConfinedIfExists(path.resolve(rootDirPath, 'README.md')); + if (readme === undefined) return {}; + + const direction: ProjectDirection = {}; + const nodes = fromMarkdown(readme).children; + for (const [index, node] of nodes.entries()) { + const title = getSectionTitle(node); + if (!title) continue; + if (title in direction) throw new Error(`README.md has more than one \`## ${title}\` section.`); + + const bodyType = sectionBodyTypes[title]; + const body = nodes[index + 1]; + const following = nodes[index + 2]; + if (body?.type !== bodyType || (following && following.type !== 'heading')) { + throw new Error(`The \`## ${title}\` section of README.md must consist of exactly one ${bodyType}.`); + } + direction[title] = readme.slice(body.position?.start.offset, body.position?.end.offset); + } + return direction; +} + +function getSectionTitle(node: RootContent): SectionTitle | undefined { + if (node.type !== 'heading' || node.depth !== 2 || node.children.length !== 1) return undefined; + const [child] = node.children; + return child?.type === 'text' && child.value in sectionBodyTypes ? (child.value as SectionTitle) : undefined; +} diff --git a/packages/wbfy/test/unit/agentInstructions.test.ts b/packages/wbfy/test/unit/agentInstructions.test.ts index c983802da..9b1580eb0 100644 --- a/packages/wbfy/test/unit/agentInstructions.test.ts +++ b/packages/wbfy/test/unit/agentInstructions.test.ts @@ -99,17 +99,39 @@ test('describes wb deploy only when the package script invokes it', async () => } }); -test('lists the purpose and goals from wbfy.yaml under Project Information', async () => { +test('lists the purpose, goals, non-goals, and milestones of README.md under Project Information', async () => { const tempRootPath = path.join(process.cwd(), '.tmp'); await fs.promises.mkdir(tempRootPath, { recursive: true }); - const dirPath = await fs.promises.mkdtemp(path.join(tempRootPath, 'wbfy-config-')); + const dirPath = await fs.promises.mkdtemp(path.join(tempRootPath, 'project-direction-')); try { fsUtil.setRootDirPath(dirPath); await fs.promises.writeFile(path.join(dirPath, 'package.json'), '{ "name": "example" }'); await fs.promises.writeFile( - path.join(dirPath, 'wbfy.yaml'), - 'purpose: Make reviews faster\ngoals:\n - Review within an hour\n - Keep false positives rare\n' + path.join(dirPath, 'README.md'), + `# example + +## Milestones + +- 10,000 users + +## Purpose + +Anyone can learn English +in a short time. + +## Usage + +## Goals + +- Learners finish a lesson in five minutes + - On mobile too +- Lessons adapt to each learner + +## Non-goals + +- Teaching other languages +` ); const config = await getPackageConfig(dirPath, { isRoot: true }); assert.ok(config); @@ -117,10 +139,15 @@ test('lists the purpose and goals from wbfy.yaml under Project Information', asy expect(await fs.promises.readFile(path.join(dirPath, 'AGENTS.md'), 'utf8')).toContain( `- Package Manager: bun -- Purpose: Make reviews faster +- Purpose: Anyone can learn English in a short time. - Goals: - - Review within an hour - - Keep false positives rare + - Learners finish a lesson in five minutes + - On mobile too + - Lessons adapt to each learner +- Non-goals: + - Teaching other languages +- Milestones: + - 10,000 users ## General Instructions` ); @@ -131,20 +158,21 @@ test('lists the purpose and goals from wbfy.yaml under Project Information', asy }); test.each([ - ['a multi-line value', 'purpose: |\n Line one\n ## Line two\n'], - ['an unknown key', 'goal:\n - Review within an hour\n'], - ['an empty list', 'goals: []\n'], - ['broken YAML', 'purpose: Make reviews faster: always\n'], + ['an unknown key in wbfy.json', 'wbfy.json', '{ "typo": {} }', 'wbfy.json is invalid'], + ['broken JSON in wbfy.json', 'wbfy.json', '{ "typos": ', 'wbfy.json is invalid'], + ['prose in a list section', 'README.md', '## Goals\n\nBe fast.\n', '`## Goals` section of README.md must consist'], + ['two blocks in a section', 'README.md', '## Purpose\n\nOne.\n\nTwo.\n', '`## Purpose` section of README.md must'], + ['a repeated section', 'README.md', '## Goals\n\n- A\n\n## Goals\n\n- B\n', 'more than one `## Goals` section'], ])( - 'stops before changing anything when wbfy.yaml has %s', - async (_name, wbfyYaml) => { + 'stops before changing anything when the repository has %s', + async (_name, fileName, content, expectedError) => { const tempRootPath = path.join(process.cwd(), '.tmp'); await fs.promises.mkdir(tempRootPath, { recursive: true }); - const dirPath = await fs.promises.mkdtemp(path.join(tempRootPath, 'invalid-wbfy-config-')); + const dirPath = await fs.promises.mkdtemp(path.join(tempRootPath, 'invalid-project-input-')); try { await fs.promises.writeFile(path.join(dirPath, 'package.json'), '{ "name": "example" }'); - await fs.promises.writeFile(path.join(dirPath, 'wbfy.yaml'), wbfyYaml); + await fs.promises.writeFile(path.join(dirPath, fileName), content); const spawned = Bun.spawn(['bun', path.join(import.meta.dir, '../../src/index.ts'), dirPath], { stderr: 'pipe', stdout: 'pipe', @@ -152,7 +180,7 @@ test.each([ const [exitCode, stderr] = await Promise.all([spawned.exited, new Response(spawned.stderr).text()]); expect(exitCode).not.toBe(0); - expect(stderr).toContain('wbfy.yaml is invalid'); + expect(stderr).toContain(expectedError); expect(fs.existsSync(path.join(dirPath, 'AGENTS.md'))).toBe(false); } finally { await fs.promises.rm(dirPath, { force: true, recursive: true }); diff --git a/packages/wbfy/test/unit/typos.test.ts b/packages/wbfy/test/unit/typos.test.ts index 4cae3db60..eae559359 100644 --- a/packages/wbfy/test/unit/typos.test.ts +++ b/packages/wbfy/test/unit/typos.test.ts @@ -48,20 +48,18 @@ test('fixTypos resolves only after every file is rewritten, including files queu } }); -test('fixTypos keeps wbfy.yaml intact so its replacements apply on every run', async () => { +test('fixTypos applies the replacements of wbfy.json', async () => { const dirPath = await fs.promises.realpath(fs.mkdtempSync(path.join(os.tmpdir(), 'wbfy-typos-config-'))); - const wbfyYaml = 'typos:\n all:\n teh: the\n'; try { fsUtil.setRootDirPath(dirPath); fs.writeFileSync(path.join(dirPath, 'package.json'), '{ "name": "example" }'); - fs.writeFileSync(path.join(dirPath, 'wbfy.yaml'), wbfyYaml); + fs.writeFileSync(path.join(dirPath, 'wbfy.json'), '{ "typos": { "all": { "teh": "the" } } }'); fs.writeFileSync(path.join(dirPath, 'doc.md'), 'teh example\n'); const config = await getPackageConfig(dirPath, { isRoot: true }); assert.ok(config); await fixTypos(config); expect(fs.readFileSync(path.join(dirPath, 'doc.md'), 'utf8')).toBe('the example\n'); - expect(fs.readFileSync(path.join(dirPath, 'wbfy.yaml'), 'utf8')).toBe(wbfyYaml); } finally { fsUtil.setRootDirPath(undefined); fs.rmSync(dirPath, { force: true, recursive: true }); From 50f1f05fbde10fc25d48e5c081d12f5b10157886 Mon Sep 17 00:00:00 2001 From: "Sakamoto, Kazunori" Date: Tue, 10 Nov 2026 23:51:59 +0900 Subject: [PATCH 2/9] chore: regenerate AGENTS.md with wbfy Co-authored-by: WillBooster (Claude Code) --- AGENTS.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 604d279c7..49ccc9d44 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -33,11 +33,11 @@ - Follow the closest template under `.github/ISSUE_TEMPLATE/`: `bug.md` for wrong behavior, `change.md` for anything to build or alter; a question or note fitting neither, or a repository without templates, needs no template. - Title: a Conventional Commits prefix for the type that fits the change (`feat:`, `fix:`, `refactor:`, `docs:`, `chore:`, ...). - The YAML front matter between the `---` lines is metadata, not body text: submit only the content below the closing `---` as the body. -- In any explanatory text (commit messages, PR descriptions, documentation, code comments, etc.) other than the PR body's Requirements section, describe only the current implementation: drop any statement naming an identifier, feature, or concept you cannot confirm exists in the final diff or the current codebase (e.g., one added and later removed or renamed along the way). Whenever your change invalidates documentation or comments, or you find ones that no longer match the current implementation, delete or rewrite them, even in files you are not otherwise changing. Mention a past state only where it is needed to understand why the current design is as it is, or when explicitly asked; files that record history by design (e.g., a changelog) are exempt. +- In any explanatory text (commit messages, PR descriptions, documentation, code comments, etc.) other than the PR body's Requirements section and the `## Purpose`, `## Goals`, `## Non-goals`, and `## Milestones` sections of `README.md`, describe only the current implementation: drop any statement naming an identifier, feature, or concept you cannot confirm exists in the final diff or the current codebase (e.g., one added and later removed or renamed along the way). Whenever your change invalidates documentation or comments, or you find ones that no longer match the current implementation, delete or rewrite them, even in files you are not otherwise changing. Mention a past state only where it is needed to understand why the current design is as it is, or when explicitly asked; files that record history by design (e.g., a changelog) are exempt. - Use heredoc for multi-line command input (e.g., `git commit -F -`, `gh pr create --body-file -`, `gh issue create --body-file -`). - Put temporary files in `.tmp`; use `/tmp` only for files that must live outside the repo. - Before finishing, delete every temporary file, backup, and external resource (e.g., databases, deployed Workers, branches) you created during the task for experiments or verification, without asking, after recording any findings worth keeping in your final response or in a file you keep. Keep an item only when a next step the user requested needs it or it holds the only copy of pre-task data the task was not meant to discard (e.g., a backup a tool kept because restoring it conflicted), and then state in your final response why and the command that deletes it. -- `AGENTS.md` and `.gemini/styleguide.md` are generated by `wbfy`; edit agent instructions only in `AGENTS_EXTRA.md` and the project purpose and goals only in `wbfy.yaml`. +- `AGENTS.md` and `.gemini/styleguide.md` are generated by `wbfy`; edit agent instructions only in `AGENTS_EXTRA.md`, and the project's purpose, goals, non-goals, and milestones only in the `## Purpose` (one paragraph), `## Goals`, `## Non-goals`, and `## Milestones` (one list each) sections of `README.md`. - Tool versions (e.g., node) are pinned in `mise.toml`; run `mise install` after changing it and never install those tools globally instead. - `bunfig.toml` uses Bun's isolated linker, so only declared dependencies resolve. If an import fails to resolve, declare that package in the `package.json` that imports it; never switch `linker` to `hoisted` or add to `publicHoistPattern` to work around it. From 93e949ae997ff587d6c3cbedf38e980d2bf6abfb Mon Sep 17 00:00:00 2001 From: "Sakamoto, Kazunori" Date: Tue, 10 Nov 2026 23:52:16 +0900 Subject: [PATCH 3/9] docs: state the purpose, goals, and non-goals of the repository and each package Co-authored-by: WillBooster (Claude Code) --- AGENTS.md | 9 +++++++++ README.md | 15 +++++++++++++++ packages/shared-lib-blitz-next/README.md | 4 ++++ packages/shared-lib-next/README.md | 4 ++++ packages/shared-lib-node/README.md | 10 +++++++++- packages/shared-lib-react/README.md | 4 ++++ packages/shared-lib/README.md | 8 ++++++++ packages/wb/README.md | 13 +++++++++++++ packages/wbfy/README.md | 17 ++++++++++++----- 9 files changed, 78 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 49ccc9d44..e5b01a137 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -2,6 +2,14 @@ - Name: `willbooster-shared` - Package Manager: bun +- Purpose: Every WillBooster repository is developed, verified, and released the same way, so people and AI agents move between repositories without relearning each one. +- Goals: + - An AI agent develops, verifies, and releases any WillBooster repository without repository-specific guidance from a person. + - A change to an organization convention reaches every repository through one `wbfy` run. + - Code that two or more WillBooster projects need exists once, in a `shared-lib` package. +- Non-goals: + - Supporting repositories outside WillBooster and WillBoosterLab beyond best effort. + - Running on Windows. ## General Instructions @@ -38,6 +46,7 @@ - Put temporary files in `.tmp`; use `/tmp` only for files that must live outside the repo. - Before finishing, delete every temporary file, backup, and external resource (e.g., databases, deployed Workers, branches) you created during the task for experiments or verification, without asking, after recording any findings worth keeping in your final response or in a file you keep. Keep an item only when a next step the user requested needs it or it holds the only copy of pre-task data the task was not meant to discard (e.g., a backup a tool kept because restoring it conflicted), and then state in your final response why and the command that deletes it. - `AGENTS.md` and `.gemini/styleguide.md` are generated by `wbfy`; edit agent instructions only in `AGENTS_EXTRA.md`, and the project's purpose, goals, non-goals, and milestones only in the `## Purpose` (one paragraph), `## Goals`, `## Non-goals`, and `## Milestones` (one list each) sections of `README.md`. +- `Goals` and `Milestones` under Project Information are not yet achieved: use them to choose between alternatives and to propose work, never to implement beyond what a task requests, and never present them as existing features or achieved results (e.g., in announcements). Delete a line from `README.md` once it is achieved. - Tool versions (e.g., node) are pinned in `mise.toml`; run `mise install` after changing it and never install those tools globally instead. - `bunfig.toml` uses Bun's isolated linker, so only declared dependencies resolve. If an import fails to resolve, declare that package in the `package.json` that imports it; never switch `linker` to `hoisted` or add to `publicHoistPattern` to work around it. diff --git a/README.md b/README.md index a50450233..2423e0340 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,21 @@ :recycle: An npm package designed for reusing general code across multiple projects at WillBooster Inc. +## Purpose + +Every WillBooster repository is developed, verified, and released the same way, so people and AI agents move between repositories without relearning each one. + +## Goals + +- An AI agent develops, verifies, and releases any WillBooster repository without repository-specific guidance from a person. +- A change to an organization convention reaches every repository through one `wbfy` run. +- Code that two or more WillBooster projects need exists once, in a `shared-lib` package. + +## Non-goals + +- Supporting repositories outside WillBooster and WillBoosterLab beyond best effort. +- Running on Windows. + ## Documents - [Rules that `wbfy` and `wb` expect of target repositories](docs/expected-repository-rules.md) diff --git a/packages/shared-lib-blitz-next/README.md b/packages/shared-lib-blitz-next/README.md index 992c328a8..6f5e1514a 100644 --- a/packages/shared-lib-blitz-next/README.md +++ b/packages/shared-lib-blitz-next/README.md @@ -1 +1,5 @@ # @willbooster/shared-lib-blitz-next + +## Purpose + +WillBooster Blitz.js apps share one tested implementation of common middleware instead of each writing its own. diff --git a/packages/shared-lib-next/README.md b/packages/shared-lib-next/README.md index b48682351..1ea723209 100644 --- a/packages/shared-lib-next/README.md +++ b/packages/shared-lib-next/README.md @@ -1 +1,5 @@ # @willbooster/shared-lib-next + +## Purpose + +WillBooster Next.js apps share one tested implementation of common middleware instead of each writing its own. diff --git a/packages/shared-lib-node/README.md b/packages/shared-lib-node/README.md index 4aed12305..57d2a5b09 100644 --- a/packages/shared-lib-node/README.md +++ b/packages/shared-lib-node/README.md @@ -1 +1,9 @@ -# library +# @willbooster/shared-lib-node + +## Purpose + +WillBooster tools and servers share one tested implementation of process, file-system, and environment-variable handling instead of each writing its own. + +## Goals + +- Child processes started through this package never outlive the command that started them. diff --git a/packages/shared-lib-react/README.md b/packages/shared-lib-react/README.md index 99aa19619..8cf488cf6 100644 --- a/packages/shared-lib-react/README.md +++ b/packages/shared-lib-react/README.md @@ -1 +1,5 @@ # @willbooster/shared-lib-react + +## Purpose + +WillBooster React apps share one tested implementation of common hooks and components instead of each writing its own. diff --git a/packages/shared-lib/README.md b/packages/shared-lib/README.md index 9416a6df3..d072ed3e6 100644 --- a/packages/shared-lib/README.md +++ b/packages/shared-lib/README.md @@ -1,5 +1,13 @@ # @willbooster/shared-lib +## Purpose + +WillBooster projects on any JavaScript runtime share one tested implementation of general-purpose code instead of each writing its own. + +## Goals + +- The package runs unchanged on Node.js, Bun, browsers, and Cloudflare Workers, with no runtime dependencies. + `recoverJson(response)` extracts JSON candidates from text and Markdown without runtime dependencies. It repairs common syntax errors and recovers partial strings and containers. Each candidate includes its parsed `value`, serialized `json`, original `start`/`end` offsets, and `repairs` with original UTF-16 offsets. The range includes consumed source, such as trailing whitespace and removed comments; use `json` or `value`, not the source slice, for parsing. ```ts diff --git a/packages/wb/README.md b/packages/wb/README.md index 38ebbd386..525a98fb6 100644 --- a/packages/wb/README.md +++ b/packages/wb/README.md @@ -2,6 +2,19 @@ `wb` offers a collection of reusable npm scripts, designed primarily for WillBooster Inc. but with potential utility for other projects as well. +## Purpose + +Developers and AI agents run the same commands in every WillBooster repository, whatever its framework or deploy target. + +## Goals + +- One command verifies, tests, starts, or deploys a project on every stack WillBooster uses. +- The output of a failed command tells an AI agent what to fix without a person interpreting it. + +## Non-goals + +- Running on Windows. + ## Supported Platforms `wb` runs on macOS and Linux with Bun >= 1.4.4 or Node.js >= 26. The packaged command uses Bun (`bun wb `). To run it with Node.js, invoke `bin/index.js` with `node`. Keep real Node.js 26 on `PATH` for tools such as Playwright, Wrangler, and vinext. diff --git a/packages/wbfy/README.md b/packages/wbfy/README.md index 17ba1094b..1bcab9864 100644 --- a/packages/wbfy/README.md +++ b/packages/wbfy/README.md @@ -5,14 +5,21 @@ :books::rocket: A tool for applying WillBooster's conventional configurations to npm packages -## Motivation +## Purpose -There are some resources for creating a project with conventional configurations like [Yeoman](https://yeoman.io/), GitHub's Template repository, and starter-kit repositories. -However, we sometimes faced the cases where we wanted to apply conventional configurations to an existing repository, so we've launch this tool! +Every WillBooster repository stays on the organization's current conventions without anyone configuring it by hand. -## Philosophy +## Goals -This tool must keep idempotency, i.e., it always yields the same result when a user applies this tool to a project even multiple times. +- Running `wbfy` on any WillBooster repository yields the current conventional configuration with no manual follow-up. +- Running `wbfy` again on its own output changes nothing. +- The agent instructions `wbfy` generates let an AI agent work in a repository without further guidance from a person. + +## Non-goals + +- Supporting repositories outside WillBooster and WillBoosterLab beyond best effort. +- Accepting hand-written, legacy, or third-party variants of the files `wbfy` generates. +- Running on Windows. ## How to Use From c541c8fa378401147c190cce4d4907bfbd722ab3 Mon Sep 17 00:00:00 2001 From: "Sakamoto, Kazunori" Date: Wed, 11 Nov 2026 00:18:33 +0900 Subject: [PATCH 4/9] docs: keep only goals that are not yet achieved Co-authored-by: WillBooster (Claude Code) --- AGENTS.md | 2 -- README.md | 2 -- packages/shared-lib-node/README.md | 4 ---- packages/shared-lib/README.md | 4 ---- packages/wb/README.md | 1 - packages/wbfy/README.md | 6 ++++-- 6 files changed, 4 insertions(+), 15 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index e5b01a137..4a870f0cd 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,8 +5,6 @@ - Purpose: Every WillBooster repository is developed, verified, and released the same way, so people and AI agents move between repositories without relearning each one. - Goals: - An AI agent develops, verifies, and releases any WillBooster repository without repository-specific guidance from a person. - - A change to an organization convention reaches every repository through one `wbfy` run. - - Code that two or more WillBooster projects need exists once, in a `shared-lib` package. - Non-goals: - Supporting repositories outside WillBooster and WillBoosterLab beyond best effort. - Running on Windows. diff --git a/README.md b/README.md index 2423e0340..5dcd5419e 100644 --- a/README.md +++ b/README.md @@ -13,8 +13,6 @@ Every WillBooster repository is developed, verified, and released the same way, ## Goals - An AI agent develops, verifies, and releases any WillBooster repository without repository-specific guidance from a person. -- A change to an organization convention reaches every repository through one `wbfy` run. -- Code that two or more WillBooster projects need exists once, in a `shared-lib` package. ## Non-goals diff --git a/packages/shared-lib-node/README.md b/packages/shared-lib-node/README.md index 57d2a5b09..6d34bee71 100644 --- a/packages/shared-lib-node/README.md +++ b/packages/shared-lib-node/README.md @@ -3,7 +3,3 @@ ## Purpose WillBooster tools and servers share one tested implementation of process, file-system, and environment-variable handling instead of each writing its own. - -## Goals - -- Child processes started through this package never outlive the command that started them. diff --git a/packages/shared-lib/README.md b/packages/shared-lib/README.md index d072ed3e6..1110bce1a 100644 --- a/packages/shared-lib/README.md +++ b/packages/shared-lib/README.md @@ -4,10 +4,6 @@ WillBooster projects on any JavaScript runtime share one tested implementation of general-purpose code instead of each writing its own. -## Goals - -- The package runs unchanged on Node.js, Bun, browsers, and Cloudflare Workers, with no runtime dependencies. - `recoverJson(response)` extracts JSON candidates from text and Markdown without runtime dependencies. It repairs common syntax errors and recovers partial strings and containers. Each candidate includes its parsed `value`, serialized `json`, original `start`/`end` offsets, and `repairs` with original UTF-16 offsets. The range includes consumed source, such as trailing whitespace and removed comments; use `json` or `value`, not the source slice, for parsing. ```ts diff --git a/packages/wb/README.md b/packages/wb/README.md index 525a98fb6..525c0dde8 100644 --- a/packages/wb/README.md +++ b/packages/wb/README.md @@ -8,7 +8,6 @@ Developers and AI agents run the same commands in every WillBooster repository, ## Goals -- One command verifies, tests, starts, or deploys a project on every stack WillBooster uses. - The output of a failed command tells an AI agent what to fix without a person interpreting it. ## Non-goals diff --git a/packages/wbfy/README.md b/packages/wbfy/README.md index 1bcab9864..1b32343aa 100644 --- a/packages/wbfy/README.md +++ b/packages/wbfy/README.md @@ -11,8 +11,6 @@ Every WillBooster repository stays on the organization's current conventions wit ## Goals -- Running `wbfy` on any WillBooster repository yields the current conventional configuration with no manual follow-up. -- Running `wbfy` again on its own output changes nothing. - The agent instructions `wbfy` generates let an AI agent work in a repository without further guidance from a person. ## Non-goals @@ -21,6 +19,10 @@ Every WillBooster repository stays on the organization's current conventions wit - Accepting hand-written, legacy, or third-party variants of the files `wbfy` generates. - Running on Windows. +## Philosophy + +This tool must keep idempotency, i.e., it always yields the same result when a user applies this tool to a project even multiple times. + ## How to Use Run with Bun >= 1.4.4 or Node.js >= 26 on macOS or Linux. mise must be on `PATH`. The target repository may still pin an older Bun: wbfy installs and activates the managed Node.js and Bun before dependency commands, then checks that the Bun CLI is at least 1.4.4. `bunx @willbooster/wbfy` uses its Bun entry point, including when the target repository still pins Node.js 24. To run the CLI with Node.js, invoke its `bin/wbfy.js` with `node`. After updating `mise.toml`, wbfy uses the pinned Node.js and Bun for dependency installation and cleanup. From e7e99aa8d76b71e8fa9e535e78f934d5f144a3ab Mon Sep 17 00:00:00 2001 From: "Sakamoto, Kazunori" Date: Wed, 11 Nov 2026 00:27:03 +0900 Subject: [PATCH 5/9] feat(wbfy): define goals as lasting conditions and milestones as checkpoints Generated instructions define the four README sections where agents are told to edit them, forbid inferring them from the code, and apply the "not yet reached, delete once reached" rule to milestones only. Co-authored-by: WillBooster (Claude Code) --- AGENTS.md | 2 +- docs/expected-repository-rules.md | 24 ++++++++++++------------ packages/wbfy/src/generators/agents.ts | 12 +++++++----- 3 files changed, 20 insertions(+), 18 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 49ccc9d44..6ceb68ad6 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -37,7 +37,7 @@ - Use heredoc for multi-line command input (e.g., `git commit -F -`, `gh pr create --body-file -`, `gh issue create --body-file -`). - Put temporary files in `.tmp`; use `/tmp` only for files that must live outside the repo. - Before finishing, delete every temporary file, backup, and external resource (e.g., databases, deployed Workers, branches) you created during the task for experiments or verification, without asking, after recording any findings worth keeping in your final response or in a file you keep. Keep an item only when a next step the user requested needs it or it holds the only copy of pre-task data the task was not meant to discard (e.g., a backup a tool kept because restoring it conflicted), and then state in your final response why and the command that deletes it. -- `AGENTS.md` and `.gemini/styleguide.md` are generated by `wbfy`; edit agent instructions only in `AGENTS_EXTRA.md`, and the project's purpose, goals, non-goals, and milestones only in the `## Purpose` (one paragraph), `## Goals`, `## Non-goals`, and `## Milestones` (one list each) sections of `README.md`. +- `AGENTS.md` and `.gemini/styleguide.md` are generated by `wbfy`; edit agent instructions only in `AGENTS_EXTRA.md`, and the following only in the `README.md` section named here, from what the project's owners have stated, never inferred from the code: `## Purpose` (one paragraph: whose situation the project exists to change, and how), `## Goals` (one list: the conditions under which the purpose is realized, highest priority first), `## Non-goals` (one list: what is deliberately left out), and `## Milestones` (one list: the checkpoints on the way to the goals, each a capability to ship or a business result to reach that can be decided as reached or not, in the order to reach them). - Tool versions (e.g., node) are pinned in `mise.toml`; run `mise install` after changing it and never install those tools globally instead. - `bunfig.toml` uses Bun's isolated linker, so only declared dependencies resolve. If an import fails to resolve, declare that package in the `package.json` that imports it; never switch `linker` to `hoisted` or add to `publicHoistPattern` to work around it. diff --git a/docs/expected-repository-rules.md b/docs/expected-repository-rules.md index 0ce32e761..8146434e8 100644 --- a/docs/expected-repository-rules.md +++ b/docs/expected-repository-rules.md @@ -23,18 +23,18 @@ A repository that deviates from these rules is fixed manually (or by re-running ## Ownership markers: what a human may edit -| File(s) | Ownership | -| ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `AGENTS_EXTRA.md` | The only human-edited agent-instruction file. `AGENTS.md` and `.gemini/styleguide.md` are fully generated from it (and `AGENTS.md` also from the `README.md` sections below) and are overwritten on every run; `CLAUDE.md`, `GEMINI.md`, and `.cursor/rules/general.mdc` are removed. | -| `## Purpose`, `## Goals`, `## Non-goals`, `## Milestones` in `README.md` | Optional and human-edited, in the README of the directory `wbfy` is run on (workspace READMEs are not read); `wbfy` copies them under `Project Information` in `AGENTS.md`. The headings are spelled exactly so, whatever language the README is written in. `## Purpose` holds one paragraph: whose situation the project exists to change, and how. Each of the others holds one list: `## Goals` the product states to reach, in priority order (at most five); `## Non-goals` what is deliberately left out; `## Milestones` business results a number can verify, in the order to reach them (at most three), as target values without current ones. An achieved line is deleted. A public repository writes only what may be public. `wbfy` skips a repository in which a section is repeated or holds anything but that one paragraph or list. | -| `wbfy.json` | Optional and human-edited: the only `wbfy` configuration file, read only from the directory `wbfy` is run on (workspace copies are otherwise ignored). `typos` adds replacements (`all`, `doc`, `ts`, `text`). `wbfy` skips a repository whose file is invalid, including one with unknown keys. | -| `.gitignore`, `.prettierignore`, `.dockerignore` | Human content goes only before `# Generated by wbfy` (head) and after the last `# Project-specific settings` (tail); everything between is regenerated. `.prettierignore` exists only in Java repositories; remove it manually when Java is removed. `.dockerignore` exists only alongside a `Dockerfile`/`docker-compose.yml` and is deleted wholesale when the repository stops qualifying. | -| `oxlint.config.ts` | Only content outside `// wbfy:start ` / `// wbfy:end ` pairs survives; a file without markers is replaced wholesale — except a published `willbooster-configs` package's `oxlint.config.ts`, which is preserved as product content. | -| Self-contained GitHub workflows | Owned by `wbfy` iff the file starts with `# Generated by wbfy.`; removing that prefix is the opt-out. When regenerating a file, `wbfy` writes for each step pinned to a version tag the newest tag among its own pin and every existing step of that action (e.g. one Renovate updated). Any other existing ref, such as a commit SHA or a branch name, is replaced: the file is rewritten without comments, so a kept SHA would lose the version comment Renovate needs to update it. For the same reason `wbfy`'s own commit SHA pins are always written. In reusable-workflow callers that are not skipped, every job calling the organization's `reusable-workflows` is normalized on each run; other jobs in the same file are left alone, and comments stay with the entries they precede or follow. `release.yml` is deleted when no release branch is configured, except for skipped custom or pinned callers. | -| `lefthook.yml`, `.lefthook/`, `bunfig.toml` (except raw `[test]` sections, an explicit `[install]` `exact = false`, and the `[fmt]` `ignorePatterns` lines after `# Project-specific patterns`), `.editorconfig`, `.gitattributes` | Fully regenerated; manual edits are lost. | -| `.github/pull_request_template.md`, `.github/ISSUE_TEMPLATE/bug.md`, `.github/ISSUE_TEMPLATE/change.md`, `.github/ISSUE_TEMPLATE/config.yml` | Fully regenerated on every run; manual edits are lost. Other files under `.github/ISSUE_TEMPLATE/` are not touched. Repository-specific guidance for agents belongs in `AGENTS_EXTRA.md`. | -| `.npmrc` | Regenerated by a repository-root application for WillBooster / WillBoosterLab repositories; targeting a workspace alone leaves the repository npmrc layout unchanged. Repositories that consume or publish `@willbooster-private/*` contain only the non-secret scope mapping to Verdaccio in the root `.npmrc`; workspace `.npmrc` files link to the root so Renovate artifact credentials reach Bun at the root lockfile. Repositories without private packages have their root and workspace `.npmrc` files removed. Local authentication belongs in `~/.npmrc`, while CI and Mend supply tokens separately. | -| `package.json` scripts | `wbfy` recognizes only its own generated command shapes (e.g. `bun wb test`, `bun wb gen-code`, `mise run `); a wrapper of a generated script (e.g. `test`) must _start with_ the generated command followed by `&&`, while custom segments in `postinstall` and in a managed `gen-code` keep their position relative to the recognized generation segment. Scripts containing shell metacharacters or `cd` are left untouched. The root `test/ci` is the exception to both: `wbfy` replaces, with a warning, any body other than `bun wb test-on-ci` with an optional ` --max-minutes `. Direct `wrangler types` invocations and a `postinstall` that runs a package script as `bun