diff --git a/AGENTS.md b/AGENTS.md index 395d430..ce0c48a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -32,6 +32,7 @@ - **YOU MUST** use Playbacks to judge whether work actually moved a Hill - **YOU MUST** treat tests as the executable form of design acceptance criteria for substantial work - **YOU MUST** prefer canonical repo fixtures over ad hoc temp-repo setup when testing repository-shaped behavior +- **YOU MUST** resolve stale review threads and document false positives before merge - **YOU MUST** tag all memories saved to your memory banks with at least `#git-mind` - **YOU MUST** include the POSIX timestamp (via `$(date +%s)`) in memory file names - **YOU MUST** document significant decisions or events diff --git a/CLAUDE.md b/CLAUDE.md index 7c51c2c..65ede65 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -32,6 +32,7 @@ - **YOU MUST** use Playbacks to judge whether work actually moved a Hill - **YOU MUST** treat tests as the executable form of design acceptance criteria for substantial work - **YOU MUST** prefer canonical repo fixtures over ad hoc temp-repo setup when testing repository-shaped behavior +- **YOU MUST** resolve stale review threads and document false positives before merge - **YOU MUST** tag all memories saved to your memory banks with at least `#git-mind` - **YOU MUST** include the POSIX timestamp (via `$(date +%s)`) in memory file names - **YOU MUST** document significant decisions or events diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a39a124..ca46ffe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -45,7 +45,9 @@ For substantial work: 4. implement until the tests are green 5. run a playback / retrospective and capture backlog follow-ups 6. update [README.md](README.md) if user-facing reality changed -7. open the PR, land it, and capture review-cycle learnings back into the backlog +7. open the PR and process review feedback +8. resolve stale review threads and document false positives before merge +9. land it, then capture review-cycle learnings back into the backlog This is not just for happy paths. Tests should cover edge cases, failure modes, and fuzz/stress behavior when the design risk justifies it. diff --git a/README.md b/README.md index 9005376..e7515c7 100644 --- a/README.md +++ b/README.md @@ -49,7 +49,9 @@ That means: 4. implement until the tests are green 5. run a playback / retrospective and capture backlog items and follow-on ideas explicitly 6. update `README.md` if product reality changed -7. land the PR, then capture review-cycle learnings back into the backlog +7. open the PR and process review feedback +8. resolve stale review threads and document false positives before merge +9. land the PR, then capture review-cycle learnings back into the backlog For repository-shaped behavior, prefer canonical repo fixtures over one-off temporary repo setup. See [docs/design/repo-fixture-strategy.md](docs/design/repo-fixture-strategy.md) and [docs/adr/ADR-0006.md](docs/adr/ADR-0006.md). diff --git a/ROADMAP.md b/ROADMAP.md index 9d13434..eb5ee73 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -100,7 +100,9 @@ Substantial delivery work should follow the repository's canonical cycle from [A 4. implement until the tests are green 5. run a playback / retrospective 6. update `README.md` if shipped reality changed -7. land the PR and capture review learnings back into the backlog +7. open the PR and process review feedback +8. resolve stale review threads and document false positives before merge +9. land the PR and capture review learnings back into the backlog The first Hill 1 implementation cycles should explicitly invest in the testing substrate needed to make bootstrap behavior executable and trustworthy. @@ -109,6 +111,7 @@ The first Hill 1 implementation cycles should explicitly invest in the testing s Status: - in progress +- prerequisite substrate upgrade merged via issue [#312](https://github.com/flyingrobots/git-mind/issues/312) Sponsor user: @@ -124,7 +127,6 @@ Goal: Deliverables: -- git-warp audit / upgrade cycle before major Hill 1 implementation (issue [#312](https://github.com/flyingrobots/git-mind/issues/312)) - bootstrap command contract with default write behavior and `--dry-run` - canonical repo fixture substrate for repository-shaped bootstrap scenarios (issue [#311](https://github.com/flyingrobots/git-mind/issues/311)) - repo-local artifact inventory and scan boundaries @@ -150,7 +152,7 @@ Primary references: - [docs/design/git-warp-upgrade-audit.md](docs/design/git-warp-upgrade-audit.md) - [docs/design/repo-fixture-strategy.md](docs/design/repo-fixture-strategy.md) - issue [#303](https://github.com/flyingrobots/git-mind/issues/303) -- issues [#304](https://github.com/flyingrobots/git-mind/issues/304), [#305](https://github.com/flyingrobots/git-mind/issues/305), [#306](https://github.com/flyingrobots/git-mind/issues/306), [#307](https://github.com/flyingrobots/git-mind/issues/307), [#310](https://github.com/flyingrobots/git-mind/issues/310), [#311](https://github.com/flyingrobots/git-mind/issues/311), and [#312](https://github.com/flyingrobots/git-mind/issues/312) +- issues [#304](https://github.com/flyingrobots/git-mind/issues/304), [#305](https://github.com/flyingrobots/git-mind/issues/305), [#306](https://github.com/flyingrobots/git-mind/issues/306), [#307](https://github.com/flyingrobots/git-mind/issues/307), [#310](https://github.com/flyingrobots/git-mind/issues/310), and [#311](https://github.com/flyingrobots/git-mind/issues/311) --- diff --git a/docs/README.md b/docs/README.md index 5dea030..e98a192 100644 --- a/docs/README.md +++ b/docs/README.md @@ -12,7 +12,7 @@ These describe what Git Mind is now and how work should be judged: - [ROADMAP.md](../ROADMAP.md) — active Hills, supporting lanes, and playback cadence - [Git Mind Product Frame](./design/git-mind.md) — IBM Design Thinking style product frame - [Hill 1 Semantic Bootstrap Spec](./design/h1-semantic-bootstrap.md) — first executable Hill 1 slice -- [git-warp Upgrade Audit](./design/git-warp-upgrade-audit.md) — next enabling cycle before major Hill 1 implementation +- [git-warp Upgrade Audit](./design/git-warp-upgrade-audit.md) — completed enabling cycle that revalidated the git-warp substrate before Hill 1 implementation - [Git Mind North Star](./VISION_NORTH_STAR.md) — longer-form strategic articulation - [ADR-0005](./adr/ADR-0005.md) — official planning and governance model - [ADR-0006](./adr/ADR-0006.md) — official delivery cycle and tests-as-spec model @@ -88,4 +88,5 @@ When implementing substantial work, continue with: 1. explicit acceptance criteria 2. failing tests 3. shared repo fixtures where repository behavior matters -4. playback evidence and README reality updates before cycle close +4. review-hygiene cleanup for stale threads and false positives before merge +5. playback evidence and README reality updates before cycle close diff --git a/docs/adr/ADR-0006.md b/docs/adr/ADR-0006.md index f24cb84..8e0f53e 100644 --- a/docs/adr/ADR-0006.md +++ b/docs/adr/ADR-0006.md @@ -81,6 +81,7 @@ If the shipped behavior changes the user-facing truth of the product, update `RE The cycle does not end at "tests green." It ends after: +- review feedback is processed and stale or false-positive threads are explicitly resolved - the PR is reviewed and merged to `main` - learnings from review are captured back into the backlog - any follow-on ideas are explicitly recorded instead of left implicit @@ -95,8 +96,10 @@ The canonical Git Mind delivery cycle is: 4. Implement until the tests are green. 5. Run the playback / retrospective. 6. Update the README if product reality changed. -7. Open and land the PR to `main`. -8. Capture post-merge review learnings and follow-on ideas in the backlog. +7. Open the PR and process review feedback. +8. Resolve stale review threads and document false positives before merge. +9. Land the PR to `main`. +10. Capture post-merge review learnings and follow-on ideas in the backlog. ## Alternatives Rejected diff --git a/docs/design/git-warp-upgrade-audit.md b/docs/design/git-warp-upgrade-audit.md index 9dc0962..36bff3d 100644 --- a/docs/design/git-warp-upgrade-audit.md +++ b/docs/design/git-warp-upgrade-audit.md @@ -1,6 +1,6 @@ # git-warp Upgrade Audit -Status: active execution on `feat/git-warp-upgrade-audit` +Status: completed and merged via PR [#314](https://github.com/flyingrobots/git-mind/pull/314) Related: @@ -12,7 +12,7 @@ Related: ## Purpose -Define the next enabling cycle before major Hill 1 implementation: +Define the enabling cycle that preceded major Hill 1 implementation: > audit and upgrade Git Mind's `@git-stunts/git-warp` dependency so new Hill 1 work is not built on an outdated substrate by accident. diff --git a/test/context.test.js b/test/context.test.js index 7432ef7..1bf1b1b 100644 --- a/test/context.test.js +++ b/test/context.test.js @@ -1,10 +1,7 @@ import { describe, it, expect, beforeEach, afterEach } from 'vitest'; -import { mkdtemp, rm, writeFile } from 'node:fs/promises'; -import { join } from 'node:path'; -import { tmpdir } from 'node:os'; -import { execSync } from 'node:child_process'; import { initGraph } from '../src/graph.js'; import { createEdge } from '../src/edges.js'; +import { repoFixture } from './helpers/repo-fixture.js'; import { extractFileContext, extractCommitContext, @@ -14,27 +11,26 @@ import { } from '../src/context.js'; describe('context', () => { + let repo; let tempDir; let graph; beforeEach(async () => { - tempDir = await mkdtemp(join(tmpdir(), 'gitmind-test-')); - execSync('git init', { cwd: tempDir, stdio: 'ignore' }); - execSync('git config user.email "test@test.com"', { cwd: tempDir, stdio: 'ignore' }); - execSync('git config user.name "Test"', { cwd: tempDir, stdio: 'ignore' }); + repo = await repoFixture('context').build(); + tempDir = repo.root; graph = await initGraph(tempDir); }); afterEach(async () => { - await rm(tempDir, { recursive: true, force: true }); + await repo.cleanup(); }); // ── extractFileContext ────────────────────────────────────── it('extracts tracked files with inferred languages', async () => { - await writeFile(join(tempDir, 'app.js'), 'console.log("hello")'); - await writeFile(join(tempDir, 'README.md'), '# Hello'); - execSync('git add app.js README.md && git commit -m "init"', { cwd: tempDir, stdio: 'ignore' }); + await repo.write('app.js', 'console.log("hello")'); + await repo.write('README.md', '# Hello'); + await repo.commit('init'); const files = extractFileContext(tempDir); @@ -54,10 +50,10 @@ describe('context', () => { }); it('respects the limit option', async () => { - await writeFile(join(tempDir, 'a.js'), ''); - await writeFile(join(tempDir, 'b.js'), ''); - await writeFile(join(tempDir, 'c.js'), ''); - execSync('git add a.js b.js c.js && git commit -m "init"', { cwd: tempDir, stdio: 'ignore' }); + await repo.write('a.js', ''); + await repo.write('b.js', ''); + await repo.write('c.js', ''); + await repo.commit('init'); const files = extractFileContext(tempDir, { limit: 2 }); expect(files).toHaveLength(2); @@ -66,11 +62,11 @@ describe('context', () => { // ── extractCommitContext ──────────────────────────────────── it('extracts recent commits with files', async () => { - await writeFile(join(tempDir, 'app.js'), 'v1'); - execSync('git add app.js && git commit -m "feat: initial"', { cwd: tempDir, stdio: 'ignore' }); + await repo.write('app.js', 'v1'); + await repo.commit('feat: initial'); - await writeFile(join(tempDir, 'app.js'), 'v2'); - execSync('git add app.js && git commit -m "fix: update app"', { cwd: tempDir, stdio: 'ignore' }); + await repo.write('app.js', 'v2'); + await repo.commit('fix: update app'); const commits = extractCommitContext(tempDir); @@ -170,8 +166,8 @@ describe('context', () => { // ── extractContext orchestrator ───────────────────────────── it('assembles full context with prompt', async () => { - await writeFile(join(tempDir, 'app.js'), 'console.log("hi")'); - execSync('git add app.js && git commit -m "init"', { cwd: tempDir, stdio: 'ignore' }); + await repo.write('app.js', 'console.log("hi")'); + await repo.commit('init'); await createEdge(graph, { source: 'file:app.js', target: 'spec:main', type: 'implements' }); const ctx = await extractContext(tempDir, graph); diff --git a/test/helpers/repo-bases.js b/test/helpers/repo-bases.js new file mode 100644 index 0000000..8bc2915 --- /dev/null +++ b/test/helpers/repo-bases.js @@ -0,0 +1,25 @@ +/** + * Base repo catalog for repository-shaped tests. + */ + +export function minimalDocsAndCodeBase() { + return async repo => { + await repo.writeFiles({ + 'README.md': '# Echo Service\n\nSee docs/overview.md and src/app.js.\n', + 'docs/overview.md': '# Overview\n\nThe service entry point lives in `src/app.js`.\n', + 'src/app.js': 'export function buildApp() {\n return { ok: true };\n}\n', + }); + await repo.commit('chore: scaffold minimal docs and code fixture'); + }; +} + +export function adrLinkedServiceBase() { + return async repo => { + await repo.writeFiles({ + 'README.md': '# Auth Service\n\nSee docs/adr/0001-auth.md for the auth decision.\n', + 'docs/adr/0001-auth.md': '# ADR 0001: Auth\n\nWe use token-based auth in `src/auth.js`.\n', + 'src/auth.js': 'export function authenticate(token) {\n return token === "ok";\n}\n', + }); + await repo.commit('chore: scaffold adr-linked service fixture'); + }; +} diff --git a/test/helpers/repo-fixture.js b/test/helpers/repo-fixture.js new file mode 100644 index 0000000..bf63418 --- /dev/null +++ b/test/helpers/repo-fixture.js @@ -0,0 +1,272 @@ +import { execFileSync } from 'node:child_process'; +import { mkdir, mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { dirname, join } from 'node:path'; + +const DEFAULT_AUTHOR_NAME = 'Test User'; +const DEFAULT_AUTHOR_EMAIL = 'test@test.com'; +const BASE_COMMIT_TIME = Date.parse('2026-01-01T00:00:00Z'); + +function sanitizeName(name) { + return name.replace(/[^a-z0-9-]+/gi, '-').replace(/^-+|-+$/g, '').toLowerCase() || 'repo-fixture'; +} + +function runGit(cwd, args, options = {}) { + const { capture = false, env = {} } = options; + return execFileSync('git', args, { + cwd, + encoding: capture ? 'utf-8' : undefined, + stdio: capture ? ['ignore', 'pipe', 'pipe'] : 'ignore', + env: { ...process.env, ...env }, + }); +} + +function isoTimestamp(index) { + return new Date(BASE_COMMIT_TIME + index * 60_000).toISOString(); +} + +export class RepoFixture { + #commitIndex = 0; + + /** + * @param {string} name + * @param {string} root + */ + constructor(name, root) { + this.name = name; + this.root = root; + } + + async init() { + runGit(this.root, ['init', '--initial-branch=main']); + runGit(this.root, ['config', 'user.email', DEFAULT_AUTHOR_EMAIL]); + runGit(this.root, ['config', 'user.name', DEFAULT_AUTHOR_NAME]); + return this; + } + + /** + * @param {string} relativePath + * @returns {string} + */ + resolve(relativePath) { + return join(this.root, relativePath); + } + + /** + * @param {string} relativePath + * @param {string | Uint8Array} content + */ + async write(relativePath, content) { + const absolutePath = this.resolve(relativePath); + await mkdir(dirname(absolutePath), { recursive: true }); + await writeFile(absolutePath, content); + return this; + } + + /** + * @param {Record} files + */ + async writeFiles(files) { + for (const [relativePath, content] of Object.entries(files)) { + await this.write(relativePath, content); + } + return this; + } + + /** + * @param {string} relativePath + */ + async remove(relativePath) { + await rm(this.resolve(relativePath), { recursive: true, force: true }); + return this; + } + + /** + * @param {string[]} args + * @param {{ capture?: boolean, env?: Record }} [options] + * @returns {string | Buffer} + */ + git(args, options = {}) { + return runGit(this.root, args, options); + } + + currentSha() { + return this.git(['rev-parse', 'HEAD'], { capture: true }).trim(); + } + + currentBranch() { + return this.git(['branch', '--show-current'], { capture: true }).trim(); + } + + /** + * Stage and commit all current repo changes using deterministic timestamps. + * + * @param {string} message + * @param {{ allowEmpty?: boolean }} [options] + */ + async commit(message, options = {}) { + const { allowEmpty = false } = options; + runGit(this.root, ['add', '--all', '--', '.']); + const timestamp = isoTimestamp(this.#commitIndex); + const args = ['commit', '-m', message]; + + if (allowEmpty) { + args.splice(1, 0, '--allow-empty'); + } + + runGit(this.root, args, { + env: { + GIT_AUTHOR_DATE: timestamp, + GIT_COMMITTER_DATE: timestamp, + }, + }); + + this.#commitIndex += 1; + return this.currentSha(); + } + + /** + * @param {string} ref + */ + async checkout(ref) { + runGit(this.root, ['checkout', ref]); + return this; + } + + /** + * @param {string} name + * @param {{ from?: string, checkout?: boolean }} [options] + */ + async createBranch(name, options = {}) { + const { from = 'HEAD', checkout = true } = options; + + if (checkout && from === 'HEAD') { + runGit(this.root, ['checkout', '-b', name]); + return this; + } + + runGit(this.root, ['branch', name, from]); + if (checkout) { + await this.checkout(name); + } + return this; + } + + /** + * @param {string} ref + * @param {string} message + */ + async merge(ref, message) { + runGit(this.root, ['merge', '--no-ff', '-m', message, ref]); + return this.currentSha(); + } + + async cleanup() { + await rm(this.root, { recursive: true, force: true }); + } +} + +export class RepoFixtureBuilder { + #name; + #base = null; + #steps = []; + #overlays = []; + + /** + * @param {string} name + */ + constructor(name) { + this.#name = name; + } + + /** + * @param {(repo: RepoFixture) => Promise} baseFactory + */ + base(baseFactory) { + this.#base = baseFactory; + return this; + } + + /** + * @param {(repo: RepoFixture) => Promise} overlayFactory + */ + overlay(overlayFactory) { + this.#overlays.push(overlayFactory); + return this; + } + + /** + * @param {(repo: RepoFixture) => Promise} overlayFactory + */ + applyOverlay(overlayFactory) { + return this.overlay(overlayFactory); + } + + /** + * @param {string} relativePath + * @param {string | Uint8Array} content + */ + withFile(relativePath, content) { + this.#steps.push(repo => repo.write(relativePath, content)); + return this; + } + + /** + * @param {Record} files + */ + withFiles(files) { + this.#steps.push(repo => repo.writeFiles(files)); + return this; + } + + /** + * @param {string} relativePath + */ + removeFile(relativePath) { + this.#steps.push(repo => repo.remove(relativePath)); + return this; + } + + /** + * @param {string} message + * @param {{ allowEmpty?: boolean }} [options] + */ + commit(message, options = {}) { + this.#steps.push(repo => repo.commit(message, options)); + return this; + } + + async build() { + const prefix = sanitizeName(this.#name); + const root = await mkdtemp(join(tmpdir(), `gitmind-${prefix}-`)); + const repo = await new RepoFixture(this.#name, root).init(); + + if (this.#base) { + await this.#base(repo); + } + + for (const step of this.#steps) { + await step(repo); + } + + for (const overlay of this.#overlays) { + await overlay(repo); + } + + return repo; + } +} + +/** + * @param {string} name + */ +export function repoFixture(name) { + return new RepoFixtureBuilder(name); +} + +/** + * @param {string} name + */ +export function createRepoFixture(name) { + return repoFixture(name); +} diff --git a/test/helpers/repo-overlays.js b/test/helpers/repo-overlays.js new file mode 100644 index 0000000..12014ee --- /dev/null +++ b/test/helpers/repo-overlays.js @@ -0,0 +1,82 @@ +/** + * Reusable overlays for repository-shaped test fixtures. + */ + +export function withIssueRefOverlay(options = {}) { + const { issue = 42, pr = 9 } = options; + + return async repo => { + await repo.write( + 'docs/references.md', + [ + '# References', + '', + `Follow-up tracked in issue #${issue}.`, + `Initial change discussed in PR #${pr}.`, + '', + ].join('\n'), + ); + await repo.commit('docs: add repo-local references overlay'); + }; +} + +export function withRecentHistoryOverlay() { + return async repo => { + await repo.write( + 'src/app.js', + [ + 'export function buildApp() {', + ' return { ok: true, version: 2 };', + '}', + '', + 'export function buildHealthcheck() {', + ' return { status: "healthy" };', + '}', + '', + ].join('\n'), + ); + await repo.commit('feat: add healthcheck helper'); + + await repo.write( + 'docs/overview.md', + [ + '# Overview', + '', + 'The service entry point lives in `src/app.js`.', + 'Recent change: `buildHealthcheck()` reports service readiness.', + '', + ].join('\n'), + ); + await repo.commit('docs: capture recent fixture history'); + }; +} + +export function withFeatureBranchOverlay(options = {}) { + const { branchName = 'feature/bootstrap-overlay' } = options; + + return async repo => { + await repo.createBranch(branchName); + await repo.write( + 'src/feature.js', + 'export const featureBranchNote = "branch overlay";\n', + ); + await repo.commit('feat: add feature branch overlay note'); + await repo.checkout('main'); + await repo.merge(branchName, 'merge: fold feature branch overlay back to main'); + }; +} + +export function withNoisyHistoryOverlay() { + return async repo => { + await repo.write( + 'notes/brainstorm.txt', + [ + 'maybe move auth into a plugin?', + 'this might relate to issue #77 or maybe not', + 'check if docs/overview.md is still accurate', + '', + ].join('\n'), + ); + await repo.commit('wip: add noisy brainstorming notes'); + }; +} diff --git a/test/repo-fixture.test.js b/test/repo-fixture.test.js new file mode 100644 index 0000000..418d0a1 --- /dev/null +++ b/test/repo-fixture.test.js @@ -0,0 +1,82 @@ +import { afterEach, describe, expect, it } from 'vitest'; +import { access, readFile } from 'node:fs/promises'; +import { join } from 'node:path'; +import { adrLinkedServiceBase, minimalDocsAndCodeBase } from './helpers/repo-bases.js'; +import { repoFixture } from './helpers/repo-fixture.js'; +import { + withFeatureBranchOverlay, + withIssueRefOverlay, + withNoisyHistoryOverlay, + withRecentHistoryOverlay, +} from './helpers/repo-overlays.js'; + +const fixtures = []; + +async function buildFixture(builder) { + const repo = await builder.build(); + fixtures.push(repo); + return repo; +} + +afterEach(async () => { + while (fixtures.length > 0) { + const repo = fixtures.pop(); + await repo.cleanup(); + } +}); + +describe('repo fixtures', () => { + it('builds a scratch repo with a fluent file + commit API', async () => { + const repo = await buildFixture( + repoFixture('scratch') + .withFile('README.md', '# Scratch Fixture\n') + .commit('docs: add readme') + .withFile('src/app.js', 'export const app = true;\n') + .commit('feat: add app module'), + ); + + const branch = repo.git(['branch', '--show-current'], { capture: true }).trim(); + const log = repo.git(['log', '--format=%s'], { capture: true }).trim().split('\n'); + const readme = await readFile(join(repo.root, 'README.md'), 'utf-8'); + + expect(branch).toBe('main'); + expect(log).toEqual(['feat: add app module', 'docs: add readme']); + expect(readme).toContain('Scratch Fixture'); + }); + + it('composes a base repo with history and reference overlays', async () => { + const repo = await buildFixture( + repoFixture('minimal-doc-code') + .base(minimalDocsAndCodeBase()) + .overlay(withIssueRefOverlay({ issue: 42, pr: 9 })) + .overlay(withRecentHistoryOverlay()) + .overlay(withFeatureBranchOverlay()) + .overlay(withNoisyHistoryOverlay()), + ); + + const log = repo.git(['log', '--format=%s'], { capture: true }); + const references = await readFile(join(repo.root, 'docs', 'references.md'), 'utf-8'); + const todo = await readFile(join(repo.root, 'notes', 'brainstorm.txt'), 'utf-8'); + const appSource = await readFile(join(repo.root, 'src', 'app.js'), 'utf-8'); + + expect(log).toContain('merge: fold feature branch overlay back to main'); + expect(log).toContain('docs: add repo-local references overlay'); + expect(references).toContain('#42'); + expect(references).toContain('PR #9'); + expect(todo).toContain('maybe move auth into a plugin?'); + expect(appSource).toContain('buildApp'); + }); + + it('provides an ADR-linked service base repo', async () => { + const repo = await buildFixture(repoFixture('adr-linked-service').base(adrLinkedServiceBase())); + + await access(join(repo.root, 'docs', 'adr', '0001-auth.md')); + await access(join(repo.root, 'src', 'auth.js')); + + const adr = await readFile(join(repo.root, 'docs', 'adr', '0001-auth.md'), 'utf-8'); + const readme = await readFile(join(repo.root, 'README.md'), 'utf-8'); + + expect(adr).toContain('token-based auth'); + expect(readme).toContain('Auth Service'); + }); +});