@turri-dev/cli 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.pi/themes/turri-dark.json +80 -0
- package/.pi/themes/turri-light.json +82 -0
- package/LICENSE +21 -0
- package/README.md +272 -2
- package/assets/jellyfish/dark-00.png +0 -0
- package/assets/jellyfish/dark-01.png +0 -0
- package/assets/jellyfish/dark-02.png +0 -0
- package/assets/jellyfish/dark-03.png +0 -0
- package/assets/jellyfish/dark-04.png +0 -0
- package/assets/jellyfish/dark-05.png +0 -0
- package/assets/jellyfish/dark-06.png +0 -0
- package/assets/jellyfish/dark-07.png +0 -0
- package/assets/jellyfish/dark-08.png +0 -0
- package/assets/jellyfish/dark-09.png +0 -0
- package/assets/jellyfish/dark-10.png +0 -0
- package/assets/jellyfish/dark-11.png +0 -0
- package/assets/jellyfish/dark-12.png +0 -0
- package/assets/jellyfish/dark-13.png +0 -0
- package/assets/jellyfish/dark-14.png +0 -0
- package/assets/jellyfish/fallback.txt +252 -0
- package/assets/jellyfish/light-00.png +0 -0
- package/assets/jellyfish/light-01.png +0 -0
- package/assets/jellyfish/light-02.png +0 -0
- package/assets/jellyfish/light-03.png +0 -0
- package/assets/jellyfish/light-04.png +0 -0
- package/assets/jellyfish/light-05.png +0 -0
- package/assets/jellyfish/light-06.png +0 -0
- package/assets/jellyfish/light-07.png +0 -0
- package/assets/jellyfish/light-08.png +0 -0
- package/assets/jellyfish/light-09.png +0 -0
- package/assets/jellyfish/light-10.png +0 -0
- package/assets/jellyfish/light-11.png +0 -0
- package/assets/jellyfish/light-12.png +0 -0
- package/assets/jellyfish/light-13.png +0 -0
- package/assets/jellyfish/light-14.png +0 -0
- package/bin/turri.mjs +29 -0
- package/dist/ask.js +149 -0
- package/dist/brand.js +50 -0
- package/dist/btw.js +174 -0
- package/dist/chat-models.js +37 -0
- package/dist/chat-skills.js +18 -0
- package/dist/cli.js +178 -0
- package/dist/config-command.js +197 -0
- package/dist/config-edit.js +367 -0
- package/dist/config.js +489 -0
- package/dist/conform-cli.js +29 -0
- package/dist/cultivate-cli.js +110 -0
- package/dist/drift-cli.js +25 -0
- package/dist/environment.js +70 -0
- package/dist/extension.js +2372 -0
- package/dist/genome.js +301 -0
- package/dist/graph.js +246 -0
- package/dist/identity.js +35 -0
- package/dist/init-config.js +511 -0
- package/dist/judge.js +62 -0
- package/dist/lineage-cli.js +13 -0
- package/dist/loop-cli.js +132 -0
- package/dist/loop-tools.js +225 -0
- package/dist/mature-tools.js +193 -0
- package/dist/model-check.js +117 -0
- package/dist/module-report.js +9 -0
- package/dist/pi-launch.js +31 -0
- package/dist/pi.js +813 -0
- package/dist/plain/check.js +76 -0
- package/dist/plain/concepts.js +213 -0
- package/dist/plain/errors.js +43 -0
- package/dist/plain/files.js +57 -0
- package/dist/plain/highlight.js +195 -0
- package/dist/plain/index.js +15 -0
- package/dist/plain/liquid.js +129 -0
- package/dist/plain/markdown.js +208 -0
- package/dist/plain/parser.js +752 -0
- package/dist/plain/plain.tmLanguage.json +124 -0
- package/dist/plain/spec.js +291 -0
- package/dist/plain/stdlib.js +44 -0
- package/dist/question-dialog.js +125 -0
- package/dist/questioning.js +109 -0
- package/dist/quiet.js +125 -0
- package/dist/regenerate/activity.js +187 -0
- package/dist/regenerate/agent-env.js +190 -0
- package/dist/regenerate/agent-shell.js +114 -0
- package/dist/regenerate/arbiter.js +133 -0
- package/dist/regenerate/audit.js +203 -0
- package/dist/regenerate/cases.js +83 -0
- package/dist/regenerate/change-view.js +102 -0
- package/dist/regenerate/choose-conformance.js +117 -0
- package/dist/regenerate/chunk-plan.js +625 -0
- package/dist/regenerate/codebase.js +75 -0
- package/dist/regenerate/command-check.js +312 -0
- package/dist/regenerate/compaction.js +20 -0
- package/dist/regenerate/confine.js +310 -0
- package/dist/regenerate/conform.js +133 -0
- package/dist/regenerate/conformance-agents.js +955 -0
- package/dist/regenerate/conformance.js +936 -0
- package/dist/regenerate/conversation-writes.js +100 -0
- package/dist/regenerate/credentials.js +88 -0
- package/dist/regenerate/debug-log.js +228 -0
- package/dist/regenerate/debugger.js +90 -0
- package/dist/regenerate/delivery.js +19 -0
- package/dist/regenerate/diagnose-job.js +40 -0
- package/dist/regenerate/diagnose.js +81 -0
- package/dist/regenerate/diff-view.js +240 -0
- package/dist/regenerate/drift.js +194 -0
- package/dist/regenerate/engine.js +118 -0
- package/dist/regenerate/environment-error.js +220 -0
- package/dist/regenerate/job-log.js +102 -0
- package/dist/regenerate/job-panel.js +336 -0
- package/dist/regenerate/jobs-tree.js +300 -0
- package/dist/regenerate/jobs.js +427 -0
- package/dist/regenerate/lanes.js +32 -0
- package/dist/regenerate/lineage.js +384 -0
- package/dist/regenerate/loop.js +1435 -0
- package/dist/regenerate/masking.js +95 -0
- package/dist/regenerate/notes.js +397 -0
- package/dist/regenerate/os-sandbox.js +243 -0
- package/dist/regenerate/paths.js +165 -0
- package/dist/regenerate/plain-palette.js +83 -0
- package/dist/regenerate/prompt.js +704 -0
- package/dist/regenerate/records.js +294 -0
- package/dist/regenerate/regenerate.js +598 -0
- package/dist/regenerate/regression.js +181 -0
- package/dist/regenerate/release.js +262 -0
- package/dist/regenerate/requires.js +241 -0
- package/dist/regenerate/resources.js +164 -0
- package/dist/regenerate/self-check.js +111 -0
- package/dist/regenerate/services.js +155 -0
- package/dist/regenerate/shell.js +319 -0
- package/dist/regenerate/side-panel.js +278 -0
- package/dist/regenerate/spec-map-view.js +574 -0
- package/dist/regenerate/spec-map.js +561 -0
- package/dist/regenerate/spec-steps.js +527 -0
- package/dist/regenerate/state.js +10 -0
- package/dist/regenerate/steer.js +91 -0
- package/dist/regenerate/steering.js +119 -0
- package/dist/regenerate/stop-report.js +161 -0
- package/dist/regenerate/submit-work.js +71 -0
- package/dist/regenerate/test-kinds.js +124 -0
- package/dist/regenerate/test-scripts.js +695 -0
- package/dist/regenerate/testing.js +406 -0
- package/dist/regenerate/transcript-view.js +1 -0
- package/dist/regenerate/unit-gate.js +20 -0
- package/dist/regenerate/unit-review.js +347 -0
- package/dist/regenerate/web.js +256 -0
- package/dist/regenerate/workspace.js +99 -0
- package/dist/regenerate-cli.js +68 -0
- package/dist/release-agent.js +114 -0
- package/dist/release-cli.js +131 -0
- package/dist/release-tools.js +148 -0
- package/dist/review/live.js +165 -0
- package/dist/rules.js +47 -0
- package/dist/self-invocation.js +110 -0
- package/dist/setup.js +245 -0
- package/dist/skill-progress.js +78 -0
- package/dist/skill.js +149 -0
- package/dist/skills.js +74 -0
- package/dist/spec-diff-cli.js +37 -0
- package/dist/spec-tools.js +155 -0
- package/dist/stack.js +158 -0
- package/dist/stacks.js +97 -0
- package/dist/stdlib.js +100 -0
- package/dist/terminal-images.js +46 -0
- package/dist/tools.js +242 -0
- package/dist/validate-cli.js +52 -0
- package/docs/README.md +58 -0
- package/docs/authoring-plain.md +245 -0
- package/docs/config.md +353 -0
- package/docs/deploy.md +109 -0
- package/docs/development.md +75 -0
- package/docs/headless.md +148 -0
- package/docs/images/how-turri-works.svg +50 -0
- package/docs/images/turri-banner.png +0 -0
- package/docs/installation.md +118 -0
- package/docs/parser.md +108 -0
- package/docs/regenerating.md +1116 -0
- package/docs/sandbox.md +233 -0
- package/docs/skills-and-gates.md +254 -0
- package/docs/theming.md +76 -0
- package/docs/using.md +454 -0
- package/genome/rules/bullet-continuation.md +69 -0
- package/genome/rules/concise-specs-examples.md +431 -0
- package/genome/rules/concise-specs-integration-examples.md +270 -0
- package/genome/rules/concise-specs.md +80 -0
- package/genome/rules/definitions.md +107 -0
- package/genome/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +163 -0
- package/genome/rules/examples/integration-embedded-testing/run_unittests_java.sh +51 -0
- package/genome/rules/exported-concepts.md +39 -0
- package/genome/rules/func-specs.md +129 -0
- package/genome/rules/impl-reqs.md +68 -0
- package/genome/rules/import-modules.md +48 -0
- package/genome/rules/integration-embedded-testing.md +276 -0
- package/genome/rules/integration-embedded.md +171 -0
- package/genome/rules/integration-standalone.md +135 -0
- package/genome/rules/integrations.md +137 -0
- package/genome/rules/linked-resources.md +69 -0
- package/genome/rules/module-structure.md +105 -0
- package/genome/rules/project-model.md +88 -0
- package/genome/rules/required-concepts.md +44 -0
- package/genome/rules/requires-modules.md +80 -0
- package/genome/rules/stdlib.md +73 -0
- package/genome/rules/test-reqs.md +57 -0
- package/genome/skills/add-acceptance-test/SKILL.md +100 -0
- package/genome/skills/add-concept/SKILL.md +71 -0
- package/genome/skills/add-functional-spec/SKILL.md +84 -0
- package/genome/skills/add-functional-specs/SKILL.md +118 -0
- package/genome/skills/add-implementation-requirement/SKILL.md +86 -0
- package/genome/skills/add-resource/SKILL.md +105 -0
- package/genome/skills/add-template/SKILL.md +64 -0
- package/genome/skills/add-test-requirement/SKILL.md +73 -0
- package/genome/skills/analyze-2-func-specs/SKILL.md +101 -0
- package/genome/skills/analyze-func-specs/SKILL.md +123 -0
- package/genome/skills/analyze-if-func-spec-too-complex/SKILL.md +211 -0
- package/genome/skills/break-down-func-spec/SKILL.md +205 -0
- package/genome/skills/brood/SKILL.md +229 -0
- package/genome/skills/brood/references/intent.md +35 -0
- package/genome/skills/brood/references/product.md +101 -0
- package/genome/skills/brood/references/stack.md +61 -0
- package/genome/skills/brood/references/testing.md +56 -0
- package/genome/skills/bud/SKILL.md +148 -0
- package/genome/skills/bud/references/checklist.md +44 -0
- package/genome/skills/bud/references/intent.md +30 -0
- package/genome/skills/check-plain-env/SKILL.md +285 -0
- package/genome/skills/consolidate-concepts/SKILL.md +192 -0
- package/genome/skills/create-import-module/SKILL.md +104 -0
- package/genome/skills/create-requires-module/SKILL.md +109 -0
- package/genome/skills/debug-specs/SKILL.md +188 -0
- package/genome/skills/implement-conformance-testing-script/SKILL.md +238 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_cypress.ps1 +337 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_golang.ps1 +111 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +136 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.ps1 +103 -0
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +150 -0
- package/genome/skills/implement-unit-testing-script/SKILL.md +160 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_flutter.ps1 +87 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_golang.ps1 +73 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_java.sh +72 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.ps1 +81 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_python.sh +98 -0
- package/genome/skills/implement-unit-testing-script/assets/run_unittests_react.ps1 +87 -0
- package/genome/skills/init-plain-project/SKILL.md +117 -0
- package/genome/skills/init-plain-project/references/checklist.md +30 -0
- package/genome/skills/mature/SKILL.md +126 -0
- package/genome/skills/refactor-module/SKILL.md +196 -0
- package/genome/skills/resolve-section-ownership/SKILL.md +64 -0
- package/genome/skills/resolve-spec-conflict/SKILL.md +96 -0
- package/genome/skills/spawn/SKILL.md +163 -0
- package/package.json +68 -4
- package/stdlib/backend/common.plain +25 -0
- package/stdlib/backend/conformance/common.plain +12 -0
- package/stdlib/backend/conformance/go-gin.plain +9 -0
- package/stdlib/backend/conformance/go-nethttp.plain +9 -0
- package/stdlib/backend/conformance/java-springboot.plain +9 -0
- package/stdlib/backend/conformance/python-django.plain +9 -0
- package/stdlib/backend/conformance/python-fastapi.plain +9 -0
- package/stdlib/backend/conformance/python-flask.plain +9 -0
- package/stdlib/backend/conformance/rust-axum.plain +9 -0
- package/stdlib/backend/conformance/typescript-express.plain +9 -0
- package/stdlib/backend/conformance/typescript-fastify.plain +9 -0
- package/stdlib/backend/conformance/typescript-nestjs.plain +9 -0
- package/stdlib/backend/go-gin.plain +18 -0
- package/stdlib/backend/go-nethttp.plain +18 -0
- package/stdlib/backend/java-springboot.plain +19 -0
- package/stdlib/backend/python-django.plain +19 -0
- package/stdlib/backend/python-fastapi.plain +23 -0
- package/stdlib/backend/python-flask.plain +23 -0
- package/stdlib/backend/rust-axum.plain +17 -0
- package/stdlib/backend/typescript-express.plain +19 -0
- package/stdlib/backend/typescript-fastify.plain +19 -0
- package/stdlib/backend/typescript-nestjs.plain +19 -0
- package/stdlib/cli/common.plain +17 -0
- package/stdlib/cli/conformance/common.plain +11 -0
- package/stdlib/cli/conformance/go-cobra.plain +9 -0
- package/stdlib/cli/conformance/go-flag.plain +9 -0
- package/stdlib/cli/conformance/java-picocli.plain +9 -0
- package/stdlib/cli/conformance/python-argparse.plain +9 -0
- package/stdlib/cli/conformance/python-click.plain +9 -0
- package/stdlib/cli/conformance/python-typer.plain +9 -0
- package/stdlib/cli/conformance/rust-clap.plain +9 -0
- package/stdlib/cli/conformance/typescript-commander.plain +9 -0
- package/stdlib/cli/go-cobra.plain +14 -0
- package/stdlib/cli/go-flag.plain +14 -0
- package/stdlib/cli/java-picocli.plain +15 -0
- package/stdlib/cli/python-argparse.plain +18 -0
- package/stdlib/cli/python-click.plain +16 -0
- package/stdlib/cli/python-typer.plain +16 -0
- package/stdlib/cli/rust-clap.plain +14 -0
- package/stdlib/cli/typescript-commander.plain +17 -0
- package/stdlib/frontend/angular.plain +17 -0
- package/stdlib/frontend/common.plain +17 -0
- package/stdlib/frontend/conformance/angular.plain +5 -0
- package/stdlib/frontend/conformance/common.plain +11 -0
- package/stdlib/frontend/conformance/react-vite.plain +5 -0
- package/stdlib/frontend/conformance/sveltekit-spa.plain +5 -0
- package/stdlib/frontend/conformance/vue-vite.plain +5 -0
- package/stdlib/frontend/react-vite.plain +16 -0
- package/stdlib/frontend/sveltekit-spa.plain +16 -0
- package/stdlib/frontend/vue-vite.plain +16 -0
- package/stdlib/lang/go.plain +13 -0
- package/stdlib/lang/java.plain +12 -0
- package/stdlib/lang/python.plain +18 -0
- package/stdlib/lang/rust.plain +12 -0
- package/stdlib/lang/typescript.plain +13 -0
- package/stdlib/library/common.plain +15 -0
- package/stdlib/library/conformance/common.plain +9 -0
- package/stdlib/library/conformance/go.plain +9 -0
- package/stdlib/library/conformance/java-gradle.plain +9 -0
- package/stdlib/library/conformance/java-maven.plain +9 -0
- package/stdlib/library/conformance/python.plain +9 -0
- package/stdlib/library/conformance/rust.plain +9 -0
- package/stdlib/library/conformance/typescript.plain +9 -0
- package/stdlib/library/go.plain +12 -0
- package/stdlib/library/java-gradle.plain +13 -0
- package/stdlib/library/java-maven.plain +13 -0
- package/stdlib/library/python.plain +13 -0
- package/stdlib/library/rust.plain +12 -0
- package/stdlib/library/typescript.plain +15 -0
|
@@ -0,0 +1,704 @@
|
|
|
1
|
+
import { modulesScratchDir } from "./paths.js";
|
|
2
|
+
import { ACCEPTANCE_TESTS, DEFINITIONS, FUNCTIONAL_REQUIREMENTS, NON_FUNCTIONAL_REQUIREMENTS, REQUIRED_FUNCTIONAL_SPECS, TEST_REQUIREMENTS, } from "../plain/spec.js";
|
|
3
|
+
import { codeCopyOf, isCodeResource } from "./resources.js";
|
|
4
|
+
import { acceptanceLabel, acceptanceText, conformanceLabel } from "./test-kinds.js";
|
|
5
|
+
import { codeCasesBlock, conformanceCasesBlock } from "./cases.js";
|
|
6
|
+
import { marksRegenerateAgain } from "./records.js";
|
|
7
|
+
import { resourcesChanged } from "./spec-steps.js";
|
|
8
|
+
/**
|
|
9
|
+
* Everything already in the code: the required modules' functional specs, bottom of the
|
|
10
|
+
* chain first, then this module's own earlier ones. Null when there is nothing.
|
|
11
|
+
*
|
|
12
|
+
* Every agent that works on this codebase gets the same list, whatever its job. A render
|
|
13
|
+
* that does not know what exists re-implements it; a test writer that does not know it
|
|
14
|
+
* cannot set up what a functionality depends on; a fixer that does not know it breaks it.
|
|
15
|
+
*/
|
|
16
|
+
function alreadyImplemented(moduleName, own, required) {
|
|
17
|
+
if (own.length === 0 && required.length === 0)
|
|
18
|
+
return null;
|
|
19
|
+
const entries = [...required, ...own.map((spec, index) => ({ module: moduleName, frid: String(index + 1), spec }))];
|
|
20
|
+
return `<already implemented>\n${functionalitiesByModule(entries, moduleName, "implemented")}\n</already implemented>`;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Functional specs grouped by the module they belong to - in the order the modules are built on
|
|
24
|
+
* one another, the current module marked - and each tagged with its module and its id there. A codebase holds the
|
|
25
|
+
* functionalities of every module it is built on, and each module numbers its own from 1, so
|
|
26
|
+
* "functionality 1" alone is ambiguous: the first listed may be base's 1 and the next top's 3.
|
|
27
|
+
* (plain2code's _get_implemented_functional_requirements_prompt_section, as tags.)
|
|
28
|
+
*/
|
|
29
|
+
export function functionalitiesByModule(entries, currentModule, status) {
|
|
30
|
+
// In the order given: the bottom of the chain first. A regeneration in a later module's
|
|
31
|
+
// codebase has modules built on the current one after it, so "current" is marked, never assumed last.
|
|
32
|
+
const modules = [...new Set(entries.map((entry) => entry.module))];
|
|
33
|
+
return modules
|
|
34
|
+
.map((module) => {
|
|
35
|
+
const items = entries
|
|
36
|
+
.filter((entry) => entry.module === module)
|
|
37
|
+
.map((entry) => `<functionality module="${entry.module}" id="${entry.frid}"${status !== undefined ? ` status="${status}"` : ""}>\n${entry.spec}\n</functionality>`);
|
|
38
|
+
return `<module name="${module}"${module === currentModule ? ' role="current"' : ""}>\n${items.join("\n\n")}\n</module>`;
|
|
39
|
+
})
|
|
40
|
+
.join("\n\n");
|
|
41
|
+
}
|
|
42
|
+
/** "functionality 3 of module `app`": a functionality, named so that no other module's can be meant. */
|
|
43
|
+
export function functionalityOf(frid, moduleName) {
|
|
44
|
+
return `functionality ${frid} of module \`${moduleName}\``;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* How to read a ***plain specification: what each section is, and what the predefined concepts
|
|
48
|
+
* mean. At the head of every prompt for an agent that builds, tests or judges from a spec - the
|
|
49
|
+
* spec-writing agents have it from the authoring rules. Without it an agent reads
|
|
50
|
+
* "- :Implementation: uses FastAPI." as a term it should have found in the definitions.
|
|
51
|
+
*
|
|
52
|
+
* `testReqs`: whether this agent is given the test reqs - the agents that write and judge the
|
|
53
|
+
* conformance tests are, the ones that write code are not.
|
|
54
|
+
*/
|
|
55
|
+
export function specPrimer(options) {
|
|
56
|
+
return [
|
|
57
|
+
"<how to read the specification>",
|
|
58
|
+
"The specification is written in ***plain. It reaches you in tags, one for each section, and every section is called",
|
|
59
|
+
"by its tag's name wherever this prompt mentions it:",
|
|
60
|
+
"- <definitions>: the concepts the rest of the specification uses, each written :LikeThis:. Wherever such a term",
|
|
61
|
+
" appears, it means exactly what its definition says.",
|
|
62
|
+
"- <implementation reqs>: how the system is built and the constraints it meets - language, frameworks, storage,",
|
|
63
|
+
" conventions, how the unit tests are written. They say how, not what the system does, and they bind the whole",
|
|
64
|
+
" codebase: every functionality, not just the one at hand.",
|
|
65
|
+
options.testReqs
|
|
66
|
+
? "- <test reqs>: how the conformance tests are written and run."
|
|
67
|
+
: "- <test reqs>: how the conformance tests are written and run. They are not given to you: the conformance tests\n" +
|
|
68
|
+
" are written apart from the code, from the specification.",
|
|
69
|
+
"- <functional specs>: what the system does, one functionality at a time, numbered in order, each built on the ones",
|
|
70
|
+
' before it. Each functional spec is a <functionality module="..." id="...">; those in <already implemented> are',
|
|
71
|
+
" in the code.",
|
|
72
|
+
"- Modules: a ***plain specification is split into modules, each a .plain file of its own. Modules are how the",
|
|
73
|
+
" specification is written, in ***plain's own syntax - not how the code is laid out. A module can require others:",
|
|
74
|
+
" it is built on their code, in one codebase with them, so the codebase holds their definitions, implementation",
|
|
75
|
+
" reqs and functionalities as well as its own. A module can also import others: those hold only definitions and",
|
|
76
|
+
" reqs - shared ones, never functional specs or code of their own - and what a module imports is its own. Every",
|
|
77
|
+
" section reaches you grouped under the module each item belongs to - <module name=\"...\">, the modules in the",
|
|
78
|
+
" order they are built on one another, the current one marked role=\"current\". Each module numbers its",
|
|
79
|
+
" functional specs from 1, so a functionality is always given with its module and its id there: functionality 1",
|
|
80
|
+
" of `base` and functionality 1 of `app` are two different functionalities.",
|
|
81
|
+
"- You work only in the codebase of the current module: it is all you can reach, and it already holds the code of",
|
|
82
|
+
" every module it requires. Organise and write the code as the <implementation reqs> say - its layout, its",
|
|
83
|
+
" structure, its conventions - never by the ***plain modules: they say where the specification puts an item,",
|
|
84
|
+
" not where its code goes.",
|
|
85
|
+
"- <acceptance tests>: under a functional spec, specific checks that functionality's implementation must pass.",
|
|
86
|
+
" They carry the same authority as the functional spec itself.",
|
|
87
|
+
"- <linked resources>: files the specification refers to as `[resource]name`. They are part of the specification.",
|
|
88
|
+
" A linked resource is not in your workspace. When the code needs what one holds while it runs - a schema it",
|
|
89
|
+
" validates against, a version it reports - it keeps exactly one copy, at the path its code-copy names in the",
|
|
90
|
+
" codebase (or where the <implementation reqs> put such files), and reads it from there; otherwise it copies none.",
|
|
91
|
+
" copy_resource makes that copy, whole and byte for byte - never type a resource out. A long one is shown cut",
|
|
92
|
+
" short: copy it to read the whole of it. A resource that is code - a source file or a build manifest, with no",
|
|
93
|
+
" code-copy - is code that lives elsewhere, linked so you know what your code plugs into: build on it as the",
|
|
94
|
+
" specification says, and never copy it, nor recreate it or what it belongs to.",
|
|
95
|
+
" An image among them is attached to your first message, numbered in the order they are listed: look at it.",
|
|
96
|
+
"- <spec change>: a change to the specification, section by section, each section in its own tag - what it adds,",
|
|
97
|
+
" what it changes (before and now), and what it removes.",
|
|
98
|
+
"",
|
|
99
|
+
"Predefined concepts - used in every specification without being defined in it:",
|
|
100
|
+
"- :Implementation: - the system that implements :plainFunctionality:.",
|
|
101
|
+
"- :plainImplementationCode: - the code of :Implementation:.",
|
|
102
|
+
"- :UnitTests: - the unit tests of individual parts of :plainImplementationCode:, written with the code.",
|
|
103
|
+
"- :ConformanceTests: - black-box tests that check :plainImplementationCode: conforms to :plainFunctionality:,",
|
|
104
|
+
" written apart from it and following :plainTestReqs:.",
|
|
105
|
+
"- :AcceptanceTest: - one acceptance test: how some aspect of :plainImplementationCode: must be checked against",
|
|
106
|
+
" the specification. :AcceptanceTests: are a functional spec's collection of them.",
|
|
107
|
+
"- :plainDefinitions:, :plainImplementationReqs:, :plainFunctionality:, :plainTestReqs: - how the specification's own",
|
|
108
|
+
" text names <definitions>, <implementation reqs>, <functional specs> and <test reqs>.",
|
|
109
|
+
'So "- :Implementation: uses FastAPI." is an implementation req on the whole system, and "- :UnitTests: use',
|
|
110
|
+
'pytest." says how the unit tests are written.',
|
|
111
|
+
"</how to read the specification>",
|
|
112
|
+
"",
|
|
113
|
+
].join("\n");
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* How a failure is fixed without being hidden - for every agent that fixes code or tests. The
|
|
117
|
+
* point of a failing test is the failure: a fix that makes it go away without making the software
|
|
118
|
+
* right leaves the defect in, and the test unable to catch it again.
|
|
119
|
+
*/
|
|
120
|
+
export const INTEGRITY_RULES = [
|
|
121
|
+
"- Fix the real cause of a failure. Never change test data or an expected value just so a test passes, never",
|
|
122
|
+
" mock or stub the system under test to hide a failure, and never skip, suppress or silence a failing test -",
|
|
123
|
+
" a known issue included: it keeps failing until the software does what the specification says.",
|
|
124
|
+
"- A partial fix that makes real progress - a syntax error, a configuration problem, a test that could not run",
|
|
125
|
+
" at all - is fine, and so is adding diagnostic logging to find the cause.",
|
|
126
|
+
"- Never make the code behave differently when a test runs it - for a test's input, name, id or environment -",
|
|
127
|
+
" than it would for anyone else.",
|
|
128
|
+
];
|
|
129
|
+
/**
|
|
130
|
+
* How a failure is fixed - for every agent sent back with one: understand it, reproduce it, fix the
|
|
131
|
+
* cause, check the fix the way the harness will. A fix made from the last line of the output alone
|
|
132
|
+
* fixes the symptom, if anything, and comes back next round.
|
|
133
|
+
*/
|
|
134
|
+
export function fixMethod(options) {
|
|
135
|
+
const runOne = options.what === "unit tests"
|
|
136
|
+
? "run just that test with bash - one file, one case - and watch it fail the same way"
|
|
137
|
+
: `run it with ${options.runTool} and watch it fail the same way - the output names the failing test`;
|
|
138
|
+
return [
|
|
139
|
+
"Work through it in order:",
|
|
140
|
+
`1. Understand the failure. Which ${options.what === "unit tests" ? "test" : "conformance test"} failed, what it expected and what it got, and the exact error`,
|
|
141
|
+
" with its whole trace and every cause it names. When the output is cut, the whole of it is in the file the cut",
|
|
142
|
+
" names: read it. Don't go on the last line alone.",
|
|
143
|
+
`2. Find where it comes from. Read the lines the trace points at, in the ${options.fixes === "code" ? "code - and the test, to see exactly what it does and expects" : "test - and the code, to see how it is reached"}.`,
|
|
144
|
+
` Hold what ${options.fixes === "code" ? "the code does" : "the test asserts"} against what the specification says. Say to yourself what the root cause is before`,
|
|
145
|
+
" you change anything - not the symptom: the null behind the exception, the wrong path behind the 404.",
|
|
146
|
+
`3. Reproduce it: ${runOne}. A failure you cannot reproduce, you cannot`,
|
|
147
|
+
" know you fixed. If it does not fail, find out why before you change anything.",
|
|
148
|
+
`4. Fix the cause, in the ${options.fixes}: the change that makes ${options.fixes === "code" ? "the code do" : "the test check"} what the specification says. One cause at a time.`,
|
|
149
|
+
`5. Check the fix: run the failing test again and see it pass, then the whole suite with ${options.runTool}. A fix`,
|
|
150
|
+
" that breaks another test is not a fix. If it still fails, go back to 1 with the new output:",
|
|
151
|
+
" it may be the next cause, now that this one is out of the way.",
|
|
152
|
+
"6. Leave in your notes what the cause was and what fixed it, in a line, for whoever meets it next.",
|
|
153
|
+
];
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Why something failed is never lost - for every agent that writes or fixes code. A failure answered
|
|
157
|
+
* with a bare "failed" and logged nowhere leaves whoever has to fix it guessing, round after round.
|
|
158
|
+
*/
|
|
159
|
+
export const ERROR_CAUSE_RULES = [
|
|
160
|
+
"- Never lose why something failed. Where the code catches an error and turns it into a response, a status, a",
|
|
161
|
+
" result or a retry, it logs the error with its whole trace, and what it hands on names the cause - never a bare",
|
|
162
|
+
" \"failed\" or \"error\" in its place, unless the specification says what it answers. A call to another",
|
|
163
|
+
" service that fails says what was called - the method and the address - and what came back: the status and",
|
|
164
|
+
" the body. Never a secret: no password, key or token in a log or a message.",
|
|
165
|
+
];
|
|
166
|
+
/** A section's tag - its name in every prompt: `<implementation reqs>`, `<acceptance tests>`. */
|
|
167
|
+
export function sectionTag(key) {
|
|
168
|
+
return key === ACCEPTANCE_TESTS ? "acceptance tests" : key;
|
|
169
|
+
}
|
|
170
|
+
export function section(key, items) {
|
|
171
|
+
if (items.length === 0)
|
|
172
|
+
return null;
|
|
173
|
+
const tag = sectionTag(key);
|
|
174
|
+
return `<${tag}>\n${items.join("\n\n")}\n</${tag}>`;
|
|
175
|
+
}
|
|
176
|
+
export function sectionModules(tree, moduleName) {
|
|
177
|
+
const of = (key) => (tree[key] ?? []).map((item) => item.module ?? moduleName);
|
|
178
|
+
return { [DEFINITIONS]: of(DEFINITIONS), [NON_FUNCTIONAL_REQUIREMENTS]: of(NON_FUNCTIONAL_REQUIREMENTS), [TEST_REQUIREMENTS]: of(TEST_REQUIREMENTS) };
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* A section of the spec, its items grouped under the module each belongs to - the modules in the order they are built on
|
|
182
|
+
* one another, the current one marked - as the functional specs are (`functionalitiesByModule`). Without `modules`, or
|
|
183
|
+
* when they do not match the items one for one, every item is the current module's.
|
|
184
|
+
*/
|
|
185
|
+
export function moduleSection(key, items, moduleName, modules) {
|
|
186
|
+
if (items.length === 0)
|
|
187
|
+
return null;
|
|
188
|
+
const given = modules?.[key];
|
|
189
|
+
const owners = given !== undefined && given.length === items.length ? given : items.map(() => moduleName);
|
|
190
|
+
const groups = [...new Set(owners)].map((module) => `<module name="${module}"${module === moduleName ? ' role="current"' : ""}>\n${items.filter((_, index) => owners[index] === module).join("\n\n")}\n</module>`);
|
|
191
|
+
const tag = sectionTag(key);
|
|
192
|
+
return `<${tag}>\n${groups.join("\n\n")}\n</${tag}>`;
|
|
193
|
+
}
|
|
194
|
+
function linkedResourcesSection(resources) {
|
|
195
|
+
if (resources.length === 0)
|
|
196
|
+
return null;
|
|
197
|
+
// An image is not text: the prompt says where it is - attached to the first message, by number - and what type.
|
|
198
|
+
// A text resource has one home in the codebase, should the code need it: its path, made relative to the codebase.
|
|
199
|
+
const rendered = resources.map((resource) => `<resource name="${resource.text}" path="${resource.target}"${resource.image !== undefined ? ` type="${resource.image.mimeType}"` : isCodeResource(resource.target) ? ' kind="code"' : ` code-copy="${codeCopyOf(resource.target)}"`}>\n${resource.content}\n</resource>`);
|
|
200
|
+
return `<linked resources>\n${rendered.join("\n\n")}\n</linked resources>`;
|
|
201
|
+
}
|
|
202
|
+
/** How to work, for any agent writing code in this codebase from a spec. `mechanical`: the job writes no new tests. */
|
|
203
|
+
function workingRules(hasCodebase, mechanical = false) {
|
|
204
|
+
return [
|
|
205
|
+
...(hasCodebase
|
|
206
|
+
? [
|
|
207
|
+
"- The <codebase> map is exact - the harness wrote it from the files. Start from it: open the",
|
|
208
|
+
" files this functionality touches instead of listing or searching the whole directory.",
|
|
209
|
+
]
|
|
210
|
+
: []),
|
|
211
|
+
...(mechanical ? [] : ["- Write the implementation first, then unit tests covering it.", "- Your tests are unit tests, in the code."]),
|
|
212
|
+
"- Honour every implementation req.",
|
|
213
|
+
"- Keep the build's identity as it is: its name, coordinates, version, package and main class. Change one only",
|
|
214
|
+
" when the specification asks you to - other builds and the conformance tests point at them.",
|
|
215
|
+
// How the tests are run, and which run counts, is the testing scripts' note (test-scripts.ts); how a failure is
|
|
216
|
+
// fixed, INTEGRITY_RULES. What is the coder's alone: which tests it may remove.
|
|
217
|
+
"- A test may be removed only when it asserts nothing meaningful - one from an earlier functionality too - and",
|
|
218
|
+
" you say which test and why.",
|
|
219
|
+
...INTEGRITY_RULES,
|
|
220
|
+
...ERROR_CAUSE_RULES,
|
|
221
|
+
];
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* The specification a review of some work is given: laid out exactly as the coding agent had it - how to read a
|
|
225
|
+
* ***plain spec, the definitions, the implementation reqs, what was already implemented - and then what is new, in
|
|
226
|
+
* a block of its own: the change the work made, section by section. The reviewer reads what the coding agent read,
|
|
227
|
+
* and knows exactly what this work was.
|
|
228
|
+
*/
|
|
229
|
+
export function reviewedSpec(input) {
|
|
230
|
+
const { specifications } = input;
|
|
231
|
+
const functionalities = specifications[FUNCTIONAL_REQUIREMENTS] ?? [];
|
|
232
|
+
const required = input.required ?? [];
|
|
233
|
+
return [
|
|
234
|
+
specPrimer({ testReqs: false }),
|
|
235
|
+
"",
|
|
236
|
+
moduleSection(DEFINITIONS, specifications[DEFINITIONS] ?? [], input.moduleName, input.modules),
|
|
237
|
+
moduleSection(NON_FUNCTIONAL_REQUIREMENTS, specifications[NON_FUNCTIONAL_REQUIREMENTS] ?? [], input.moduleName, input.modules),
|
|
238
|
+
alreadyImplemented(input.moduleName, functionalities, required),
|
|
239
|
+
"<new in this work>",
|
|
240
|
+
specChangeBlock(input.change ?? "(the change is not known)"),
|
|
241
|
+
"</new in this work>",
|
|
242
|
+
]
|
|
243
|
+
.filter((part) => part !== null)
|
|
244
|
+
.join("\n");
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Said above a spec change given as the raw diff of the .plain files - when it could not be told section by section:
|
|
248
|
+
* the one place an agent meets the files' own headings, so it is told which tag each is.
|
|
249
|
+
*/
|
|
250
|
+
const RAW_CHANGE_NOTE = "The change could not be told section by section, so it is the diff of the .plain files, as the person made it. There,\n" +
|
|
251
|
+
"each section is headed by its name in stars: ***definitions*** is <definitions>, ***implementation reqs*** is\n" +
|
|
252
|
+
"<implementation reqs>, ***test reqs*** is <test reqs>, ***functional specs*** is <functional specs>, and\n" +
|
|
253
|
+
"***acceptance tests*** under a functional spec is its <acceptance tests>.";
|
|
254
|
+
/** A change in its tag: told section by section, or - when it could not be - the raw diff, with which tag each heading is. */
|
|
255
|
+
export function specChangeBlock(change) {
|
|
256
|
+
const raw = /^diff --git |^@@ |^[+-]{3} /m.test(change);
|
|
257
|
+
return `<spec change>\n${raw ? `${RAW_CHANGE_NOTE}\n\n` : ""}${change.trim()}\n</spec change>`;
|
|
258
|
+
}
|
|
259
|
+
/** The sections as the change is told, in the order a spec has them. Test reqs are the conformance tests', not the code's. */
|
|
260
|
+
const CHANGE_SECTIONS = [
|
|
261
|
+
{ key: DEFINITIONS, title: "definitions" },
|
|
262
|
+
{ key: NON_FUNCTIONAL_REQUIREMENTS, title: "implementation reqs" },
|
|
263
|
+
{ key: FUNCTIONAL_REQUIREMENTS, title: "functional specs" },
|
|
264
|
+
{ key: "required functional specs", title: "functional specs of the modules this one requires" },
|
|
265
|
+
];
|
|
266
|
+
/** A changed item's text: its markdown, and its acceptance tests under it. */
|
|
267
|
+
function itemText(serialized) {
|
|
268
|
+
if (serialized === undefined)
|
|
269
|
+
return "";
|
|
270
|
+
let item;
|
|
271
|
+
try {
|
|
272
|
+
item = JSON.parse(serialized);
|
|
273
|
+
}
|
|
274
|
+
catch {
|
|
275
|
+
return serialized;
|
|
276
|
+
}
|
|
277
|
+
if (typeof item === "string")
|
|
278
|
+
return item;
|
|
279
|
+
const { markdown, [ACCEPTANCE_TESTS]: tests } = (item ?? {});
|
|
280
|
+
const lines = [markdown ?? JSON.stringify(item)];
|
|
281
|
+
if (Array.isArray(tests) && tests.length > 0)
|
|
282
|
+
lines.push(" <acceptance tests>", ...tests.map((test) => ` ${String(test)}`), " </acceptance tests>");
|
|
283
|
+
return lines.join("\n");
|
|
284
|
+
}
|
|
285
|
+
const indent = (text, by) => text.split("\n").map((line) => `${by}${line}`).join("\n");
|
|
286
|
+
/** A changed item, read back: undefined when its line is not one. */
|
|
287
|
+
const parsed = (serialized) => {
|
|
288
|
+
try {
|
|
289
|
+
return serialized === undefined ? undefined : JSON.parse(serialized);
|
|
290
|
+
}
|
|
291
|
+
catch {
|
|
292
|
+
return undefined;
|
|
293
|
+
}
|
|
294
|
+
};
|
|
295
|
+
/**
|
|
296
|
+
* A spec change as an agent reads it: section by section, what was added, what changed - before and now - and what
|
|
297
|
+
* was removed. A definition taken out and put back reworded, or moved, is one change to it, not two. Every item is
|
|
298
|
+
* named so no other module's can be meant: a functional spec by its module and number, and a definition or req of a
|
|
299
|
+
* module below by its module; `moduleName` is the module whose codebase it is - whose own items carry no module.
|
|
300
|
+
*/
|
|
301
|
+
export function describeSpecChange(changes, moduleName) {
|
|
302
|
+
const conceptOf = (text) => /^-\s+(:[^:\s]+:)/.exec(text)?.[1];
|
|
303
|
+
const blocks = [];
|
|
304
|
+
const known = new Set(CHANGE_SECTIONS.map((section) => section.key));
|
|
305
|
+
const sections = [...CHANGE_SECTIONS, ...[...new Set(changes.map((change) => change.section))].filter((key) => !known.has(key) && key !== TEST_REQUIREMENTS).map((key) => ({ key, title: sectionTag(key) }))];
|
|
306
|
+
for (const { key, title } of sections) {
|
|
307
|
+
const here = changes.filter((change) => change.section === key);
|
|
308
|
+
if (here.length === 0)
|
|
309
|
+
continue;
|
|
310
|
+
/** Which item it is: a functional spec by its module and number, a definition by its concept, and one of a module below with that module. */
|
|
311
|
+
const labelOf = (change, line) => {
|
|
312
|
+
const item = parsed(line);
|
|
313
|
+
const below = item?.module !== undefined && item.module !== moduleName ? ` of module \`${item.module}\`` : "";
|
|
314
|
+
if (key === FUNCTIONAL_REQUIREMENTS)
|
|
315
|
+
return moduleName !== undefined ? functionalityOf(String(change.position), moduleName) : `functionality ${change.position}`;
|
|
316
|
+
if (key === REQUIRED_FUNCTIONAL_SPECS)
|
|
317
|
+
return item?.module !== undefined && item.frid !== undefined ? functionalityOf(item.frid, item.module) : `functionality ${change.position}${below}`;
|
|
318
|
+
if (key === DEFINITIONS)
|
|
319
|
+
return `${conceptOf(itemText(line)) ?? `item ${change.position}`}${below}`;
|
|
320
|
+
return `item ${change.position}${below}`;
|
|
321
|
+
};
|
|
322
|
+
/** Whether an added or removed item is named above its text: a functional spec always, anything else of a module below. */
|
|
323
|
+
const named = (line) => key === FUNCTIONAL_REQUIREMENTS || key === REQUIRED_FUNCTIONAL_SPECS || (() => {
|
|
324
|
+
const item = parsed(line);
|
|
325
|
+
return item?.module !== undefined && item.module !== moduleName;
|
|
326
|
+
})();
|
|
327
|
+
const changed = [];
|
|
328
|
+
// A definition "changed" into another concept is one removed and another added: a definition is itself by its concept.
|
|
329
|
+
const split = here.flatMap((change) => key === DEFINITIONS && change.kind === "change" && conceptOf(itemText(change.before)) !== conceptOf(itemText(change.after))
|
|
330
|
+
? [{ ...change, kind: "remove", after: undefined }, { ...change, kind: "add", before: undefined }]
|
|
331
|
+
: [change]);
|
|
332
|
+
let added = split.filter((change) => change.kind === "add");
|
|
333
|
+
let removed = split.filter((change) => change.kind === "remove");
|
|
334
|
+
// Marked to regenerate again: built, and regenerated again - what it says now, and what it said, where that changed too.
|
|
335
|
+
const again = split.filter((change) => change.kind === "change" && marksRegenerateAgain(parsed(change.before)));
|
|
336
|
+
for (const change of split.filter((item) => item.kind === "change" && !again.includes(item))) {
|
|
337
|
+
const label = labelOf(change, change.after);
|
|
338
|
+
const resources = resourcesChanged(change.before, change.after).map((resource) => resource.target);
|
|
339
|
+
changed.push({ label, before: itemText(change.before), after: itemText(change.after), ...(resources.length > 0 ? { resources } : {}) });
|
|
340
|
+
}
|
|
341
|
+
// A definition removed and added again under the same concept is that definition changed.
|
|
342
|
+
if (key === DEFINITIONS) {
|
|
343
|
+
for (const out of [...removed]) {
|
|
344
|
+
const concept = conceptOf(itemText(out.before));
|
|
345
|
+
const back = concept === undefined ? undefined : added.find((change) => conceptOf(itemText(change.after)) === concept);
|
|
346
|
+
if (back === undefined)
|
|
347
|
+
continue;
|
|
348
|
+
removed = removed.filter((change) => change !== out);
|
|
349
|
+
added = added.filter((change) => change !== back);
|
|
350
|
+
if (itemText(out.before) !== itemText(back.after))
|
|
351
|
+
changed.push({ label: labelOf(back, back.after), before: itemText(out.before), after: itemText(back.after) });
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
const counts = [
|
|
355
|
+
added.length > 0 ? `${added.length} added` : "",
|
|
356
|
+
changed.length > 0 ? `${changed.length} changed` : "",
|
|
357
|
+
again.length > 0 ? `${again.length} to regenerate again` : "",
|
|
358
|
+
removed.length > 0 ? `${removed.length} removed` : "",
|
|
359
|
+
].filter(Boolean);
|
|
360
|
+
if (counts.length === 0)
|
|
361
|
+
continue;
|
|
362
|
+
const lines = [`<${title} change="${counts.join(", ")}">`];
|
|
363
|
+
if (changed.length > 0) {
|
|
364
|
+
lines.push("Changed:");
|
|
365
|
+
for (const change of changed) {
|
|
366
|
+
// What a file it links holds changed: said, since its text may read as it did.
|
|
367
|
+
const held = change.resources?.map((target) => ` what ${target} holds changed - it is in <linked resources> as it is now: make the code match it`) ?? [];
|
|
368
|
+
if (change.before === change.after && held.length > 0)
|
|
369
|
+
lines.push(` ${change.label}`, " its text is as it was:", indent(change.after, " "), ...held);
|
|
370
|
+
else
|
|
371
|
+
lines.push(` ${change.label}`, " before:", indent(change.before, " "), " now:", indent(change.after, " "), ...held);
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
if (again.length > 0) {
|
|
375
|
+
lines.push("To regenerate again - built already, and regenerated again:");
|
|
376
|
+
for (const change of again) {
|
|
377
|
+
const label = labelOf(change, change.after);
|
|
378
|
+
if (itemText(change.before) === itemText(change.after))
|
|
379
|
+
lines.push(` ${label}:`, indent(itemText(change.after), " "));
|
|
380
|
+
else
|
|
381
|
+
lines.push(` ${label}`, " before:", indent(itemText(change.before), " "), " now:", indent(itemText(change.after), " "));
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
if (added.length > 0) {
|
|
385
|
+
lines.push("Added:");
|
|
386
|
+
for (const change of added) {
|
|
387
|
+
if (named(change.after))
|
|
388
|
+
lines.push(` ${labelOf(change, change.after)}:`);
|
|
389
|
+
lines.push(indent(itemText(change.after), " "));
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
if (removed.length > 0) {
|
|
393
|
+
lines.push("Removed:");
|
|
394
|
+
for (const change of removed) {
|
|
395
|
+
if (named(change.before))
|
|
396
|
+
lines.push(` ${labelOf(change, change.before)}:`);
|
|
397
|
+
lines.push(indent(itemText(change.before), " "));
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
lines.push(`</${title}>`);
|
|
401
|
+
blocks.push(lines.join("\n"));
|
|
402
|
+
}
|
|
403
|
+
return blocks.length > 0 ? blocks.join("\n\n") : "Nothing the code is built from changes: the change is only in how the specs are laid out, or in the test reqs.";
|
|
404
|
+
}
|
|
405
|
+
/**
|
|
406
|
+
* Build the prompt for rendering a change to the specs.
|
|
407
|
+
*
|
|
408
|
+
* The change is given as the person made it - a diff of the .plain files - and the specs as
|
|
409
|
+
* they stand after it come with it, so the agent has both what moved and what it must add up
|
|
410
|
+
* to. Additions, changes and removals, in any section, are one task: make the code match.
|
|
411
|
+
*/
|
|
412
|
+
export function buildSpecDiffPrompt(input) {
|
|
413
|
+
const { specifications } = input;
|
|
414
|
+
const all = [
|
|
415
|
+
...input.required,
|
|
416
|
+
...specifications[FUNCTIONAL_REQUIREMENTS].map((spec, index) => ({
|
|
417
|
+
module: input.moduleName,
|
|
418
|
+
frid: String(index + 1),
|
|
419
|
+
spec,
|
|
420
|
+
})),
|
|
421
|
+
];
|
|
422
|
+
const parts = [
|
|
423
|
+
specPrimer({ testReqs: false }),
|
|
424
|
+
`You are changing the \`${input.moduleName}\` codebase to match a change in its specification.`,
|
|
425
|
+
"",
|
|
426
|
+
input.empty
|
|
427
|
+
? "Nothing is built yet: the working directory is empty, and the change is the whole specification."
|
|
428
|
+
: "The working directory holds the code built so far, from the specs below - the specs before the change.",
|
|
429
|
+
"The specification the code is built from follows - its functional specs in <already implemented> - and then",
|
|
430
|
+
"<spec change>, the change to it, section by section: the specification below with the change applied is what the",
|
|
431
|
+
"code has to do once you are done. Your task is to apply the change to the code - everything it adds, changes and",
|
|
432
|
+
"removes - and nothing else.",
|
|
433
|
+
"",
|
|
434
|
+
moduleSection(DEFINITIONS, specifications[DEFINITIONS], input.moduleName, input.modules),
|
|
435
|
+
moduleSection(NON_FUNCTIONAL_REQUIREMENTS, specifications[NON_FUNCTIONAL_REQUIREMENTS], input.moduleName, input.modules),
|
|
436
|
+
all.length === 0 ? null : `<already implemented>\n${functionalitiesByModule(all, input.moduleName, "implemented")}\n</already implemented>`,
|
|
437
|
+
input.codebase ? `<codebase>\n${input.codebase}\n</codebase>` : null,
|
|
438
|
+
linkedResourcesSection(input.linkedResources),
|
|
439
|
+
input.changes !== undefined && input.changes.length > 0
|
|
440
|
+
? specChangeBlock(describeSpecChange(input.changes, input.moduleName))
|
|
441
|
+
: `<spec change>\n${RAW_CHANGE_NOTE}\n\n${input.diff.trim()}\n</spec change>`,
|
|
442
|
+
"",
|
|
443
|
+
...(input.cases !== undefined
|
|
444
|
+
? codeCasesBlock(input.cases)
|
|
445
|
+
: [
|
|
446
|
+
"- Implement what the change adds. Update what it changes. Remove what it removes - its code and its",
|
|
447
|
+
" unit tests - where nothing that remains still needs it.",
|
|
448
|
+
"- A change to a definition or an implementation req reaches every functional spec that relies on it. Follow it there.",
|
|
449
|
+
]),
|
|
450
|
+
"- Everything the change does not touch must keep working, and its tests passing.",
|
|
451
|
+
...workingRules(Boolean(input.codebase), input.cases?.mechanical === true),
|
|
452
|
+
];
|
|
453
|
+
return parts.filter((part) => part !== null).join("\n");
|
|
454
|
+
}
|
|
455
|
+
/**
|
|
456
|
+
* Build the prompt for conformance tests.
|
|
457
|
+
*
|
|
458
|
+
* These are black-box in shape: they invoke the built functionality from outside and check
|
|
459
|
+
* it does what the specification says. The agent can read the implementation - it needs to
|
|
460
|
+
* know how to call the thing - but the prompt leads with the specification, because a test
|
|
461
|
+
* written from the code can only ever agree with it.
|
|
462
|
+
*/
|
|
463
|
+
export function buildConformancePrompt(input) {
|
|
464
|
+
const { specifications } = input;
|
|
465
|
+
const functionalities = specifications[FUNCTIONAL_REQUIREMENTS];
|
|
466
|
+
const forChange = input.diff !== undefined;
|
|
467
|
+
const target = functionalities[functionalities.length - 1];
|
|
468
|
+
if (target === undefined && !forChange)
|
|
469
|
+
throw new Error(`Functionality ${input.frid} has no specification text.`);
|
|
470
|
+
const parts = [
|
|
471
|
+
specPrimer({ testReqs: true }),
|
|
472
|
+
`You are writing conformance tests for the \`${input.moduleName}\` specification.`,
|
|
473
|
+
"",
|
|
474
|
+
"Conformance tests are black box. They invoke the built functionality from the outside -",
|
|
475
|
+
"running its executable or importing its public entry point - and check that it behaves as",
|
|
476
|
+
"the specification says.",
|
|
477
|
+
"",
|
|
478
|
+
...(input.invocation !== undefined
|
|
479
|
+
? [
|
|
480
|
+
...(input.readCode !== undefined
|
|
481
|
+
? [
|
|
482
|
+
`You have read access to the code, in ${input.readCode}, with your file tools - read, grep, find, ls - but`,
|
|
483
|
+
"not write access: you write tests, never code. Read it to see how the software is reached - its routes,",
|
|
484
|
+
"commands, names, how it starts - never for what it should do: that comes from the specification. Your",
|
|
485
|
+
"shell holds your tests folder and the code, the code read only: to build or run the software yourself,",
|
|
486
|
+
`copy the code into a folder of your own under ${modulesScratchDir(input.readCode)}, and build and run it there.`,
|
|
487
|
+
]
|
|
488
|
+
: [
|
|
489
|
+
"You cannot read or view the code. It is not in your sandbox - your file tools and your shell hold your",
|
|
490
|
+
"tests folder and nothing else - so do not look for it: no searching parent folders, absolute paths or /tmp.",
|
|
491
|
+
]),
|
|
492
|
+
"An agent that explored the code wrote down what you need of it, in <how to invoke it>: how it is",
|
|
493
|
+
"built and run, how each functionality is reached from outside, what it needs around it, and a plan for",
|
|
494
|
+
"these tests. Write the conformance tests from this prompt - the specification, that account and its",
|
|
495
|
+
"plan - and what is in your tests folder: the tests already there show how the suite is built and run.",
|
|
496
|
+
"Every assertion comes from the specification, never from what the account says the code does. Where the",
|
|
497
|
+
"code and the specification disagree, the test follows the specification and fails. The plan is a",
|
|
498
|
+
"starting point: where it and the specification differ, the specification is right.",
|
|
499
|
+
"The conformance testing script at the top of this prompt is how your tests are built and run: read what it",
|
|
500
|
+
"builds or installs before your tests run, and so what they can depend on or start to reach the software.",
|
|
501
|
+
"Write the tests' own build - its dependencies, how it starts the software - to fit that script.",
|
|
502
|
+
"",
|
|
503
|
+
`<how to invoke it>\n${input.invocation.trim()}\n</how to invoke it>`,
|
|
504
|
+
...((input.manifests ?? []).length > 0
|
|
505
|
+
? [
|
|
506
|
+
"",
|
|
507
|
+
"The code's build files, read from the code by the harness, as they are. They are the one place to take the",
|
|
508
|
+
"code's exact name, coordinates and version from - whatever your tests' own build has to point at. Copy them",
|
|
509
|
+
"from here; never guess them, and never take them from a folder or module name.",
|
|
510
|
+
"",
|
|
511
|
+
...input.manifests.map((manifest) => `<code build file path="${manifest.path}">\n${manifest.content.trimEnd()}\n</code build file>`),
|
|
512
|
+
]
|
|
513
|
+
: []),
|
|
514
|
+
]
|
|
515
|
+
: [
|
|
516
|
+
"You can read the implementation, and you should, but only to find out how the thing is",
|
|
517
|
+
"invoked: its entry point, its arguments, the shape of its output. Every assertion comes",
|
|
518
|
+
"from the specification. A test that asserts what the code happens to do agrees with the",
|
|
519
|
+
"code by construction and can never catch it being wrong - which is the only thing these",
|
|
520
|
+
"tests are for. Where the code and the specification disagree, the test follows the",
|
|
521
|
+
"specification and fails.",
|
|
522
|
+
]),
|
|
523
|
+
"",
|
|
524
|
+
moduleSection(DEFINITIONS, specifications[DEFINITIONS], input.moduleName, input.modules),
|
|
525
|
+
moduleSection(NON_FUNCTIONAL_REQUIREMENTS, specifications[NON_FUNCTIONAL_REQUIREMENTS], input.moduleName, input.modules),
|
|
526
|
+
moduleSection(TEST_REQUIREMENTS, specifications[TEST_REQUIREMENTS], input.moduleName, input.modules),
|
|
527
|
+
// Context, not the subject: what this functionality can rely on already existing - a
|
|
528
|
+
// user registered before one logs in - so its tests can set that up through the same
|
|
529
|
+
// public entry points. Only the functionality below is under test. For a diff, every
|
|
530
|
+
// functionality is the spec as it now stands.
|
|
531
|
+
alreadyImplemented(input.moduleName, input.diff !== undefined ? functionalities : functionalities.slice(0, -1), input.required ?? []),
|
|
532
|
+
];
|
|
533
|
+
const resources = linkedResourcesSection(input.linkedResources);
|
|
534
|
+
if (resources !== null)
|
|
535
|
+
parts.push(resources);
|
|
536
|
+
parts.push(input.diff !== undefined
|
|
537
|
+
? specChangeBlock(input.diff)
|
|
538
|
+
: `<functionality module="${input.moduleName}" id="${input.frid}">\n${target}\n</functionality>`, input.diff !== undefined
|
|
539
|
+
? touchedAcceptanceTests(input.touched ?? [])
|
|
540
|
+
: numberedAcceptanceTests(input.frid, specifications[ACCEPTANCE_TESTS] ?? []), `<files in the build>\n${input.manifest.join("\n")}\n</files in the build>`, input.existingTests && input.existingTests.length > 0
|
|
541
|
+
? [
|
|
542
|
+
`<conformance tests already here>\n${input.existingTests.join("\n")}\n</conformance tests already here>`,
|
|
543
|
+
"",
|
|
544
|
+
"This directory holds the conformance tests of every functionality built so far - this module's",
|
|
545
|
+
"and those of the modules it requires, copied up with their code. Tests for other functionalities",
|
|
546
|
+
"must keep passing: leave them as they are. If tests for this functionality are among them, from an",
|
|
547
|
+
"earlier version of the specification, update them to it: keep the coverage, change only what the",
|
|
548
|
+
"specification changed.",
|
|
549
|
+
].join("\n")
|
|
550
|
+
: null, "", ...(input.diff !== undefined
|
|
551
|
+
? [
|
|
552
|
+
"The specs were changed as in <spec change>, and the code has been updated to match. Bring the",
|
|
553
|
+
"conformance tests in line with the specs as they now stand.",
|
|
554
|
+
"",
|
|
555
|
+
...(input.cases !== undefined
|
|
556
|
+
? conformanceCasesBlock(input.cases)
|
|
557
|
+
: [
|
|
558
|
+
"- Add tests for behaviour the change adds, change the tests of behaviour it changes, and remove the",
|
|
559
|
+
" tests of behaviour it removes. Leave the tests of everything the change does not touch as they are.",
|
|
560
|
+
"- The change is tested whatever it touches: a definition, an implementation req, the test reqs, as",
|
|
561
|
+
" well as a functional spec. Where it changes what built software must do or how the tests reach it,",
|
|
562
|
+
" the tests check that - with or without a functional spec that uses it yet.",
|
|
563
|
+
]),
|
|
564
|
+
"- A test is removed only because the spec no longer asks for what it checks. Say which, and why.",
|
|
565
|
+
]
|
|
566
|
+
: [
|
|
567
|
+
`Write conformance tests for ${functionalityOf(input.frid, input.moduleName)}.`,
|
|
568
|
+
"",
|
|
569
|
+
`- Test ${functionalityOf(input.frid, input.moduleName)} only. What is listed as already implemented is there to be`,
|
|
570
|
+
" used - to set up the state a test needs - not tested again.",
|
|
571
|
+
]), "- Write them in your working directory - the module's tests folder - and nowhere else: never in the", " code folder, never beside the unit tests. That folder is the conformance suite; the harness runs it,", " and nothing else, as one. When it runs, the tests are put together with a copy of the build, so refer", " to the built files by the paths in <files in the build>, resolved against the current working", " directory rather than against your test file's own location.", "- Organise the tests however suits them - files, folders, shared helpers. Make it clear which", forChange
|
|
572
|
+
? " functionality a test covers, so its tests can be found and updated later."
|
|
573
|
+
: ` functionality a test covers, so the tests of ${functionalityOf(input.frid, input.moduleName)} can be found and updated later.`, ...(forChange ? changeTestKinds() : testKinds(input.frid, (specifications[ACCEPTANCE_TESTS] ?? []).length)), "- Test observable behaviour only: output, exit codes, files produced, values returned by the", " public entry point. Never assert on internal names, private helpers or implementation detail.", "- <test reqs> say how conformance tests must be written and run. Follow them.", ...(input.invocation !== undefined
|
|
574
|
+
? [
|
|
575
|
+
"- Your shell holds the tests folder, and the code read only. Run the tests with run_conformance_tests:",
|
|
576
|
+
" it puts the whole suite together with a copy of the build and runs the project's conformance",
|
|
577
|
+
" testing script exactly as the harness does to decide whether this is done, and that run is the only",
|
|
578
|
+
" one that counts. Call it before you finish.",
|
|
579
|
+
"- You change tests, never code. When the harness's run fails, another agent works out against the",
|
|
580
|
+
" specification whether a test or the code is wrong. When it is a test, you are sent back with why:",
|
|
581
|
+
" correct it to what the specification says. When it is the code, the coding agent fixes that.",
|
|
582
|
+
"- When a run fails because of the software itself - it does not build or start, or does what the",
|
|
583
|
+
" specification says it must not - the tests are not what is wrong: do not work around it in them - no",
|
|
584
|
+
" mocking, no skipping, no changing what they expect, no pointing them elsewhere. Stop, and say in your",
|
|
585
|
+
" report what the software does; the harness has the code fixed.",
|
|
586
|
+
"- You can run the suite three times a turn with run_conformance_tests. The third ends your turn: the harness",
|
|
587
|
+
" then runs it itself, and works out what is wrong. When a run's output is cut, the cut names the id the",
|
|
588
|
+
" whole of it is kept as - read it with read_output before you change anything.",
|
|
589
|
+
]
|
|
590
|
+
: [
|
|
591
|
+
"- Run them however you like while you work - one file, one case, any configuration - with bash.",
|
|
592
|
+
" Before you finish, call run_conformance_tests: it runs the project's conformance testing script on",
|
|
593
|
+
" the whole suite exactly as the harness does to decide whether this is done, and that run is the",
|
|
594
|
+
" only one that counts.",
|
|
595
|
+
"- When a test fails, work out against the specification what is wrong - the test or the code - and",
|
|
596
|
+
" fix that one. A test that misreads the specification is corrected. Code that does not do what the",
|
|
597
|
+
input.codeDir
|
|
598
|
+
? ` specification says is fixed, in ${input.codeDir}; then check its unit tests with run_unit_tests.`
|
|
599
|
+
: " specification says is fixed; then check its unit tests with run_unit_tests.",
|
|
600
|
+
]), "- Never change a test so it agrees with code that contradicts the specification: the tests say what", " the specification requires, and the code is what has to meet it.", "- Never delete, skip or weaken a test to get a pass. A test may be removed only when it asserts", " nothing meaningful, and you must say which test and why.", ...INTEGRITY_RULES, "- Every test fails explicitly - through an assertion or a raised error - when what it checks does not hold,", " and its failure message says what was expected, what came back, and enough of the context to debug it", " from: the failure output is all the next agent, and whoever judges it, gets.");
|
|
601
|
+
return parts.filter((part) => part !== null).join("\n");
|
|
602
|
+
}
|
|
603
|
+
/**
|
|
604
|
+
* For a spec change: the acceptance tests of the functionalities it touches, numbered as their tests
|
|
605
|
+
* are labelled - each implemented as a test of its own, as a functionality's own run has them.
|
|
606
|
+
*/
|
|
607
|
+
function touchedAcceptanceTests(touched) {
|
|
608
|
+
const withTests = touched.filter((item) => item.acceptanceTests.length > 0);
|
|
609
|
+
if (withTests.length === 0)
|
|
610
|
+
return null;
|
|
611
|
+
const rendered = withTests.flatMap((item) => item.acceptanceTests.map((test, index) => `${acceptanceLabel(item.frid, index + 1)}: ${acceptanceText(test)}`));
|
|
612
|
+
return [
|
|
613
|
+
"The acceptance tests of the functional specs the change touches:",
|
|
614
|
+
`<acceptance tests>\n${rendered.join("\n\n")}\n</acceptance tests>`,
|
|
615
|
+
"Each of these is implemented as a test of its own, doing every step it lists and asserting every expectation it",
|
|
616
|
+
"states, and labelled as it is numbered here - bring the ones already there in line with the change.",
|
|
617
|
+
].join("\n");
|
|
618
|
+
}
|
|
619
|
+
/** The functionality's acceptance tests, numbered as their tests are labelled. */
|
|
620
|
+
function numberedAcceptanceTests(frid, items) {
|
|
621
|
+
if (items.length === 0)
|
|
622
|
+
return null;
|
|
623
|
+
const rendered = items.map((item, i) => `${acceptanceLabel(frid, i + 1)}: ${acceptanceText(item)}`);
|
|
624
|
+
return `<acceptance tests>\n${rendered.join("\n\n")}\n</acceptance tests>`;
|
|
625
|
+
}
|
|
626
|
+
/** The two kinds of test, for a spec change: each labelled with what it covers. */
|
|
627
|
+
function changeTestKinds() {
|
|
628
|
+
return [
|
|
629
|
+
"- Conformance tests come from the specs themselves: what they say the software does, its edge cases, its",
|
|
630
|
+
" errors - written as if no acceptance tests had been given. They do not copy the acceptance tests.",
|
|
631
|
+
`- Label them: files whose names say conformance, each with a comment at its top naming the functionality it`,
|
|
632
|
+
` covers - "${conformanceLabel("<id>")}" - or, for one that covers no functional spec yet, "${CHANGE_LABEL}".`,
|
|
633
|
+
"- Acceptance tests - the ones in <acceptance tests> - are implemented separately from the conformance tests,",
|
|
634
|
+
" each as its own test, doing what it says, in files whose names say acceptance.",
|
|
635
|
+
`- Label each acceptance test with a comment right above it: "${acceptanceLabel("<id>", 1)}:" and the text`,
|
|
636
|
+
" of the acceptance test, numbered as the spec lists them.",
|
|
637
|
+
];
|
|
638
|
+
}
|
|
639
|
+
/** What a conformance test covering no functional spec yet - a definition, a req - is labelled. */
|
|
640
|
+
export const CHANGE_LABEL = "conformance tests of the spec";
|
|
641
|
+
/**
|
|
642
|
+
* The two kinds of test, kept apart and labelled (see test-kinds.ts). `acceptance` is how many
|
|
643
|
+
* acceptance tests the functionality has; for a spec change, it is left to the change.
|
|
644
|
+
*/
|
|
645
|
+
function testKinds(frid, acceptance) {
|
|
646
|
+
const conformance = [
|
|
647
|
+
"- Conformance tests come from the functional spec itself: what it says the functionality does, its edge",
|
|
648
|
+
" cases, its errors - written as if no acceptance tests had been given. They do not copy the acceptance",
|
|
649
|
+
" tests, and do not stop where the acceptance tests stop.",
|
|
650
|
+
`- Label them: files whose names say conformance, each with a comment "${conformanceLabel(frid)}" at its top`,
|
|
651
|
+
" (for a spec change, the functionality each covers).",
|
|
652
|
+
];
|
|
653
|
+
if (acceptance === 0)
|
|
654
|
+
return conformance;
|
|
655
|
+
return [
|
|
656
|
+
...conformance,
|
|
657
|
+
acceptance === undefined
|
|
658
|
+
? "- Acceptance tests - the ones in <acceptance tests> - are implemented separately from the"
|
|
659
|
+
: `- Separately from them, implement each of the ${acceptance} acceptance test${acceptance === 1 ? "" : "s"} above as its own test, doing what it`,
|
|
660
|
+
acceptance === undefined
|
|
661
|
+
? " conformance tests, each as its own test, doing what it says, in files whose names say acceptance."
|
|
662
|
+
: " says, in files whose names say acceptance. Never fold one into a conformance test, even where they overlap.",
|
|
663
|
+
`- Label each acceptance test with a comment right above it: "${acceptanceLabel(frid, 1)}:" and the text`,
|
|
664
|
+
" of the acceptance test, numbered as above.",
|
|
665
|
+
];
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Build the prompt for repairing a regression.
|
|
669
|
+
*
|
|
670
|
+
* A regression means the newest functionality broke an older one, so the whole module's
|
|
671
|
+
* conformance suite is the subject. The tests are not editable from here - they state
|
|
672
|
+
* what the specification requires of every functionality built so far.
|
|
673
|
+
*/
|
|
674
|
+
export function buildRegressionFixPrompt(input) {
|
|
675
|
+
const { specifications } = input;
|
|
676
|
+
const parts = [
|
|
677
|
+
specPrimer({ testReqs: false }),
|
|
678
|
+
`The conformance suite for \`${input.moduleName}\` is failing.`,
|
|
679
|
+
"",
|
|
680
|
+
`It covers functionalities ${input.frids.join(", ")} of module \`${input.moduleName}\` together. A functionality that passed`,
|
|
681
|
+
"on its own can still be broken by a later one, which is what this run is for.",
|
|
682
|
+
"",
|
|
683
|
+
moduleSection(DEFINITIONS, specifications[DEFINITIONS], input.moduleName, input.modules),
|
|
684
|
+
moduleSection(NON_FUNCTIONAL_REQUIREMENTS, specifications[NON_FUNCTIONAL_REQUIREMENTS], input.moduleName, input.modules),
|
|
685
|
+
// All of it is in the code, the required modules' functionalities included, and any of it
|
|
686
|
+
// may be what the latest change broke.
|
|
687
|
+
alreadyImplemented(input.moduleName, specifications[FUNCTIONAL_REQUIREMENTS], input.required ?? []),
|
|
688
|
+
`<conformance failure>\n${input.failure}\n</conformance failure>`,
|
|
689
|
+
"",
|
|
690
|
+
"Change the implementation so every functionality listed as already implemented works at the same time.",
|
|
691
|
+
"",
|
|
692
|
+
"- The conformance tests are not in this directory and you cannot edit them. Fix the code.",
|
|
693
|
+
"- Do not fix one functionality by breaking another. They must all hold together.",
|
|
694
|
+
"- Do not special-case the test. Make the behaviour the specification describes actually true.",
|
|
695
|
+
"- Keep the existing unit tests passing. Run any tests you like with bash; before you finish, call",
|
|
696
|
+
" run_conformance_tests and run_unit_tests - the project's scripts, run as the harness runs them.",
|
|
697
|
+
...INTEGRITY_RULES,
|
|
698
|
+
...ERROR_CAUSE_RULES,
|
|
699
|
+
"- Keep the build's identity as it is: its name, coordinates, version, package and main class. Change one only",
|
|
700
|
+
" when the specification asks you to - other builds and the conformance tests point at them.",
|
|
701
|
+
"- Report what you changed and why.",
|
|
702
|
+
];
|
|
703
|
+
return parts.filter((part) => part !== null).join("\n");
|
|
704
|
+
}
|