| 1 | import { test } from "node:test"; |
| 2 | import assert from "node:assert/strict"; |
| 3 | |
| 4 | import { assemble } from "./assemble.ts"; |
| 5 | import { extract } from "./extract.ts"; |
| 6 | import { aspirational, bulletsOf, classify, harvestable, planLike, sentencesOf, wholeSentences } from "./harvest.ts"; |
| 7 | |
| 8 | const ctx = { project: "g1t", siblings: [] }; |
| 9 | |
| 10 | test("memory comes from docs on how to work here, never plans, feedback or history", () => { |
| 11 | for (const path of ["README.md", "AGENTS.md", "CLAUDE.md", "CONTRIBUTING.md"]) { |
| 12 | const role = path.startsWith("README") ? "readme" : path.startsWith("CONTRIBUTING") ? "contributing" : "agents"; |
| 13 | assert.ok(harvestable(path, role), path); |
| 14 | } |
| 15 | for (const path of ["docs/DEPLOYING.md", "docs/testing.md", "docs/architecture.md", "docs/SELF_HOSTING.md", "runbooks/restore.md", "docs/getting-started.md"]) { |
| 16 | assert.ok(harvestable(path, "doc"), path); |
| 17 | } |
| 18 | for (const path of ["docs/PLAN.md", "docs/CLOUDFLARE_FEEDBACK.md", "docs/CHANGELOG.md", "docs/roadmap.md", "docs/DEMO.md", "docs/INCIDENTS.md", "docs/PERFORMANCE.md", "docs/release-notes.md"]) { |
| 19 | assert.ok(!harvestable(path, "doc"), path); |
| 20 | } |
| 21 | // project.yml adds a doc, or leaves one out, and leaving out wins. |
| 22 | assert.ok(harvestable("docs/PERFORMANCE.md", "doc", { docs: ["docs/PERFORMANCE.md"], skip: [] })); |
| 23 | assert.ok(harvestable("docs/notes/x.md", "doc", { docs: ["docs/notes/*"], skip: [] })); |
| 24 | assert.ok(!harvestable("README.md", "readme", { docs: [], skip: ["README.md"] })); |
| 25 | assert.ok(!harvestable("docs/testing.md", "doc", { docs: ["docs/testing.md"], skip: ["docs/*"] })); |
| 26 | }); |
| 27 | |
| 28 | const FEEDBACK = `# Building a platform: a field report |
| 29 | |
| 30 | ### A1. What is a billable operation? (blocking) |
| 31 | |
| 32 | - **What we tried.** Price from cost: pass storage through to each workspace with a modest |
| 33 | uniform overhead, so a bill tracks what it really costs us. |
| 34 | - **What we hit.** Pricing defines operations only loosely. |
| 35 | - **What it means.** Our capacity model gives a 15x spread. |
| 36 | - **Ask.** A table: each binding method and each endpoint, billable or not. |
| 37 | |
| 38 | ### Conventions we would like |
| 39 | |
| 40 | - **Ask.** Metrics with the same event names as the invoice, per repository. |
| 41 | `; |
| 42 | |
| 43 | const PLAN = `# Plan |
| 44 | |
| 45 | ## For people who do not write code |
| 46 | |
| 47 | - **Roles.** A person can plan and approve work without ever cloning a |
| 48 | repo. The roles that were sketched here map onto the five repository |
| 49 | roles that are built. |
| 50 | |
| 51 | ### Rules every automation obeys |
| 52 | |
| 53 | - **Deduplication.** The same Sentry issue firing 500 times maps to one |
| 54 | issue. |
| 55 | - **Loop protection.** Work started by an automation cannot retrigger the |
| 56 | same automation without a person in between. |
| 57 | |
| 58 | ## Roadmap |
| 59 | |
| 60 | ## Later |
| 61 | |
| 62 | - g1t will learn from docs. |
| 63 | `; |
| 64 | |
| 65 | test("a doc that reads as feedback or a plan says nothing, whatever its name", () => { |
| 66 | assert.ok(planLike(FEEDBACK)); |
| 67 | assert.deepEqual(extract("CONTRIBUTING.md", FEEDBACK, ctx).hints, []); |
| 68 | assert.ok(planLike(PLAN)); |
| 69 | assert.deepEqual(extract("docs/testing.md", PLAN, ctx).hints, []); |
| 70 | const files = [ |
| 71 | { path: "docs/PLAN.md", facts: extract("docs/PLAN.md", "# Working on g1t\n\n## Conventions\n\n- Run the whole suite before you push, every time.\n", ctx) }, |
| 72 | { path: "docs/CLOUDFLARE_FEEDBACK.md", facts: extract("docs/CLOUDFLARE_FEEDBACK.md", FEEDBACK, ctx) }, |
| 73 | ]; |
| 74 | const project = { id: "p", workspace: "w", slug: "g1t", name: "g1t", description: null, private: false, repoId: "r", repo: { namespace: "w", name: "g1t" }, rootDir: "", defaultBranch: "main" }; |
| 75 | const around = { owners: [], dependsOn: [], deploy: null, integrations: [] }; |
| 76 | assert.deepEqual(assemble(project, files, around).hints, [], "docs/PLAN.md is not a source of memory"); |
| 77 | // Unless project.yml says it is. |
| 78 | const withConfig = [...files, { path: ".g1t/project.yml", facts: extract(".g1t/project.yml", "memory:\n docs:\n - docs/PLAN.md\n", ctx) }]; |
| 79 | assert.deepEqual(assemble(project, withConfig, around).hints.map((hint) => hint.path), ["docs/PLAN.md"]); |
| 80 | }); |
| 81 | |
| 82 | test("a bullet is taken whole, with the lines it wraps onto", () => { |
| 83 | const bullets = bulletsOf( |
| 84 | "## Conventions\n\n- **Loop protection.** Work started by an automation cannot retrigger the\n same automation without a person in between.\n- Short one.\n\nA paragraph.\n\n```\n- not a bullet\n```\n", |
| 85 | ); |
| 86 | assert.deepEqual(bullets, [ |
| 87 | { text: "**Loop protection.** Work started by an automation cannot retrigger the same automation without a person in between.", heading: "Conventions" }, |
| 88 | { text: "Short one.", heading: "Conventions" }, |
| 89 | ]); |
| 90 | // Bullets in a plan section, and its subsections, are left out. |
| 91 | assert.deepEqual(bulletsOf("## Roadmap\n\n### Next\n\n- One.\n\n## Testing\n\n- Two.\n").map((b) => b.text), ["Two."]); |
| 92 | }); |
| 93 | |
| 94 | test("never cut inside a sentence: whole sentences up to the limit, or nothing", () => { |
| 95 | assert.deepEqual(sentencesOf("Run `npm test` first. It reads README.md, e.g. the setup. Done!"), [ |
| 96 | "Run `npm test` first.", |
| 97 | "It reads README.md, e.g. the setup.", |
| 98 | "Done!", |
| 99 | ]); |
| 100 | assert.equal(wholeSentences("Short and whole", 40), "Short and whole"); |
| 101 | assert.equal(wholeSentences("First sentence is here. Second sentence pushes it past the limit.", 30), "First sentence is here."); |
| 102 | assert.equal(wholeSentences("One very long sentence that never ends before the limit is reached at all", 30), null); |
| 103 | }); |
| 104 | |
| 105 | test("plans and wishes are not memory; kinds are earned, not assumed", () => { |
| 106 | assert.ok(aspirational("g1t will learn from docs.", "doc")); |
| 107 | assert.ok(aspirational("Tests should run on every push.", "contributing")); |
| 108 | assert.ok(aspirational("**Ask.** A usage endpoint.", "doc")); |
| 109 | assert.ok(aspirational("**What we tried.** Pass storage through.", "doc")); |
| 110 | assert.ok(!aspirational("Run `cargo test` in the crate you changed.", "contributing")); |
| 111 | // In AGENTS.md "should" states a rule. |
| 112 | assert.ok(!aspirational("You should run the formatter before committing.", "agents")); |
| 113 | assert.ok(aspirational("TODO: document the release flow.", "agents")); |
| 114 | |
| 115 | assert.deepEqual(classify("Never edit generated files by hand.", "Conventions", "contributing"), { kind: "gotcha", confidence: 0.6 }); |
| 116 | assert.deepEqual(classify("Use the shared client for every call.", "Conventions", "contributing"), { kind: "convention", confidence: 0.6 }); |
| 117 | assert.deepEqual(classify("The API lives in apps/api and serves api.g1t.sh.", "Conventions", "readme"), { kind: "fact", confidence: 0.5 }); |
| 118 | }); |
| 119 | |
| 120 | test("CONTRIBUTING's before-you-push bullets are suggested whole; nothing is cut", () => { |
| 121 | const facts = extract( |
| 122 | "CONTRIBUTING.md", |
| 123 | "# Contributing\n\n## Before you push\n\n- `cargo test` in the crate or service you changed.\n- `npx tsc -b --force` in `apps/web` (the incremental build misses changes\n in `packages/contracts`).\n- Look at what you changed in a browser. Screenshots catch what type\n checks do not.\n\n## Deploying\n\nPushes to `main` deploy themselves.\n", |
| 124 | ctx, |
| 125 | ); |
| 126 | assert.deepEqual( |
| 127 | facts.hints.map((hint) => [hint.kind, hint.text, hint.confidence]), |
| 128 | [ |
| 129 | ["fact", "`cargo test` in the crate or service you changed.", 0.5], |
| 130 | ["fact", "`npx tsc -b --force` in `apps/web` (the incremental build misses changes in `packages/contracts`).", 0.5], |
| 131 | ["convention", "Look at what you changed in a browser. Screenshots catch what type checks do not.", 0.6], |
| 132 | ], |
| 133 | ); |
| 134 | for (const hint of facts.hints) assert.match(hint.text, /[.!?)`]$/); |
| 135 | }); |