@turri-dev/cli 0.1.27 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/advisor-chat.d.ts +63 -0
- package/dist/advisor-chat.js +290 -0
- package/dist/background-skills.d.ts +53 -0
- package/dist/background-skills.js +121 -0
- package/dist/bedrock-region.d.ts +66 -0
- package/dist/bedrock-region.js +144 -0
- package/dist/btw.d.ts +1 -1
- package/dist/btw.js +1 -1
- package/dist/change-plain-cli.js +20 -13
- package/dist/chat-idle.d.ts +7 -0
- package/dist/chat-idle.js +20 -0
- package/dist/chats.d.ts +10 -0
- package/dist/chats.js +23 -0
- package/dist/cli-options.d.ts +1 -2
- package/dist/cli-options.js +2 -7
- package/dist/cli.js +11 -3
- package/dist/config-command.d.ts +14 -0
- package/dist/config-command.js +35 -15
- package/dist/config-edit.d.ts +3 -3
- package/dist/config-edit.js +16 -12
- package/dist/config.d.ts +54 -34
- package/dist/config.js +40 -44
- package/dist/conform-cli.js +12 -10
- package/dist/conformance-switch.d.ts +34 -0
- package/dist/conformance-switch.js +108 -0
- package/dist/cultivate-cli.js +15 -5
- package/dist/drift-cli.js +2 -0
- package/dist/extension.d.ts +15 -0
- package/dist/extension.js +260 -83
- package/dist/host-session.d.ts +26 -0
- package/dist/host-session.js +30 -2
- package/dist/host.d.ts +5 -2
- package/dist/host.js +7 -1
- package/dist/init-config.d.ts +1 -1
- package/dist/init-config.js +20 -11
- package/dist/loop-cli.d.ts +3 -3
- package/dist/loop-cli.js +13 -16
- package/dist/loop-tools.js +18 -10
- package/dist/model-check.d.ts +8 -3
- package/dist/model-check.js +13 -8
- package/dist/new-project.js +12 -8
- package/dist/{validate-cli.js → parse-cli.js} +11 -5
- package/dist/parse-tool.d.ts +9 -0
- package/dist/parse-tool.js +33 -0
- package/dist/pi-launch.js +1 -1
- package/dist/plain/check.d.ts +53 -6
- package/dist/plain/check.js +217 -24
- package/dist/plain/concepts.d.ts +8 -0
- package/dist/plain/concepts.js +66 -23
- package/dist/plain/cycles.d.ts +21 -0
- package/dist/plain/cycles.js +190 -0
- package/dist/plain/errors.d.ts +4 -0
- package/dist/plain/errors.js +7 -0
- package/dist/plain/files.d.ts +18 -1
- package/dist/plain/files.js +39 -2
- package/dist/plain/graph.d.ts +33 -4
- package/dist/plain/graph.js +60 -12
- package/dist/plain/highlight.d.ts +3 -2
- package/dist/plain/highlight.js +4 -3
- package/dist/plain/index.d.ts +8 -5
- package/dist/plain/index.js +8 -5
- package/dist/plain/liquid.js +1 -6
- package/dist/plain/markdown.d.ts +32 -3
- package/dist/plain/markdown.js +146 -24
- package/dist/plain/module-concepts.js +2 -2
- package/dist/plain/parser.d.ts +83 -28
- package/dist/plain/parser.js +381 -260
- package/dist/plain/plain.tmLanguage.json +29 -3
- package/dist/plain/project.d.ts +48 -0
- package/dist/plain/project.js +155 -0
- package/dist/plain/spec.d.ts +90 -28
- package/dist/plain/spec.js +107 -133
- package/dist/plain/test-plain-file.d.ts +59 -0
- package/dist/plain/test-plain-file.js +232 -0
- package/dist/regenerate/activity.d.ts +20 -9
- package/dist/regenerate/activity.js +11 -4
- package/dist/regenerate/advisor-records.d.ts +93 -0
- package/dist/regenerate/advisor-records.js +110 -0
- package/dist/regenerate/advisor-runs.d.ts +87 -0
- package/dist/regenerate/advisor-runs.js +327 -0
- package/dist/regenerate/base-folder.d.ts +68 -0
- package/dist/regenerate/base-folder.js +168 -0
- package/dist/regenerate/cases.d.ts +9 -6
- package/dist/regenerate/cases.js +70 -14
- package/dist/regenerate/change-view.d.ts +8 -3
- package/dist/regenerate/change-view.js +71 -22
- package/dist/regenerate/chunk-plan.d.ts +36 -15
- package/dist/regenerate/chunk-plan.js +165 -59
- package/dist/regenerate/conform.d.ts +3 -0
- package/dist/regenerate/conform.js +6 -5
- package/dist/regenerate/conformance-agents.d.ts +18 -39
- package/dist/regenerate/conformance-agents.js +60 -88
- package/dist/regenerate/conformance.d.ts +23 -13
- package/dist/regenerate/conformance.js +119 -82
- package/dist/regenerate/diff-view.d.ts +1 -1
- package/dist/regenerate/diff-view.js +20 -4
- package/dist/regenerate/drift.d.ts +47 -15
- package/dist/regenerate/drift.js +101 -30
- package/dist/regenerate/jobs.d.ts +2 -2
- package/dist/regenerate/jobs.js +1 -1
- package/dist/regenerate/ledger.d.ts +19 -0
- package/dist/regenerate/loop.d.ts +62 -43
- package/dist/regenerate/loop.js +472 -341
- package/dist/regenerate/paths.d.ts +4 -0
- package/dist/regenerate/paths.js +7 -1
- package/dist/regenerate/plain-palette.js +2 -0
- package/dist/regenerate/prompt.d.ts +57 -19
- package/dist/regenerate/prompt.js +157 -122
- package/dist/regenerate/records.d.ts +43 -12
- package/dist/regenerate/records.js +102 -88
- package/dist/regenerate/regenerate.js +29 -11
- package/dist/regenerate/regression.d.ts +4 -4
- package/dist/regenerate/regression.js +14 -10
- package/dist/regenerate/renames.d.ts +42 -0
- package/dist/regenerate/renames.js +185 -0
- package/dist/regenerate/requires.d.ts +1 -1
- package/dist/regenerate/requires.js +10 -3
- package/dist/regenerate/resources.d.ts +5 -3
- package/dist/regenerate/resources.js +4 -4
- package/dist/regenerate/scripts-being-written.d.ts +19 -0
- package/dist/regenerate/scripts-being-written.js +30 -0
- package/dist/regenerate/spec-map-view.d.ts +2 -2
- package/dist/regenerate/spec-map-view.js +46 -15
- package/dist/regenerate/spec-map.d.ts +27 -5
- package/dist/regenerate/spec-map.js +92 -60
- package/dist/regenerate/spec-steps.d.ts +48 -5
- package/dist/regenerate/spec-steps.js +213 -28
- package/dist/regenerate/state.d.ts +4 -3
- package/dist/regenerate/stop-report.d.ts +1 -1
- package/dist/regenerate/stop-report.js +10 -10
- package/dist/regenerate/test-advisor.d.ts +101 -0
- package/dist/regenerate/test-advisor.js +379 -0
- package/dist/regenerate/test-kinds.d.ts +71 -28
- package/dist/regenerate/test-kinds.js +110 -63
- package/dist/regenerate/test-scripts.d.ts +2 -0
- package/dist/regenerate/test-scripts.js +4 -0
- package/dist/regenerate/testing-steps.d.ts +41 -0
- package/dist/regenerate/testing-steps.js +110 -0
- package/dist/regenerate/testing.d.ts +1 -1
- package/dist/regenerate/testing.js +5 -4
- package/dist/regenerate/unit-review.d.ts +1 -1
- package/dist/regenerate/unit-review.js +3 -3
- package/dist/regenerate-cli.js +11 -12
- package/dist/release-agent.js +4 -0
- package/dist/release-cli.js +2 -0
- package/dist/release-tools.js +2 -0
- package/dist/run-report.d.ts +2 -2
- package/dist/run-report.js +4 -3
- package/dist/self-invocation.d.ts +1 -1
- package/dist/self-invocation.js +1 -1
- package/dist/sentinel.js +1 -1
- package/dist/setup.d.ts +10 -0
- package/dist/setup.js +52 -4
- package/dist/skill.d.ts +10 -0
- package/dist/skill.js +6 -0
- package/dist/skills.js +3 -1
- package/dist/spec-tools.js +21 -31
- package/dist/test-plain-files.d.ts +41 -0
- package/dist/test-plain-files.js +177 -0
- package/docs/README.md +3 -1
- package/docs/authoring-plain.md +46 -13
- package/docs/chats.md +3 -1
- package/docs/config.md +20 -15
- package/docs/deploy.md +1 -1
- package/docs/development.md +2 -1
- package/docs/headless.md +15 -18
- package/docs/hosting.md +28 -12
- package/docs/installation.md +1 -0
- package/docs/parser.md +88 -27
- package/docs/regenerating.md +87 -52
- package/docs/sentinel.md +1 -1
- package/docs/skills-and-gates.md +3 -1
- package/docs/test-advisor.md +110 -0
- package/docs/theming.md +1 -1
- package/docs/using.md +17 -15
- package/genome/rules/bullet-continuation.md +8 -8
- package/genome/rules/concise-specs-examples.md +111 -80
- package/genome/rules/concise-specs-integration-examples.md +56 -41
- package/genome/rules/concise-specs.md +17 -16
- package/genome/rules/definitions.md +5 -5
- package/genome/rules/examples/integration-embedded-testing/run_conformance_tests_java.sh +1 -1
- package/genome/rules/func-specs.md +69 -74
- package/genome/rules/impl-reqs.md +4 -4
- package/genome/rules/integration-embedded-testing.md +20 -20
- package/genome/rules/integration-embedded.md +31 -31
- package/genome/rules/integration-standalone.md +9 -9
- package/genome/rules/integrations.md +18 -18
- package/genome/rules/linked-resources.md +19 -16
- package/genome/rules/module-structure.md +21 -14
- package/genome/rules/project-model.md +18 -17
- package/genome/rules/required-concepts.md +1 -1
- package/genome/rules/requires-modules.md +6 -5
- package/genome/rules/semantic-ids.md +49 -0
- package/genome/rules/stdlib.md +4 -5
- package/genome/rules/test-plain-files.md +130 -0
- package/genome/rules/test-reqs.md +8 -8
- package/genome/rules/turri-regeneration.md +23 -8
- package/genome/skills/analyze-2-func-specs/SKILL.md +36 -21
- package/genome/skills/analyze-func-specs/SKILL.md +46 -32
- package/genome/skills/break-down-func-spec/SKILL.md +91 -67
- package/genome/skills/consolidate-concepts/SKILL.md +10 -10
- package/genome/skills/convert-old-syntax/SKILL.md +68 -0
- package/genome/skills/convert-old-syntax/references/rubric.md +11 -0
- package/genome/skills/debug-specs/SKILL.md +62 -56
- package/genome/skills/implement-conformance-testing-script/SKILL.md +19 -15
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_java.sh +2 -2
- package/genome/skills/implement-conformance-testing-script/assets/run_conformance_tests_python.sh +2 -2
- package/genome/skills/implement-unit-testing-script/SKILL.md +14 -10
- package/genome/skills/refactor-module/SKILL.md +35 -35
- package/genome/skills/resolve-section-ownership/SKILL.md +9 -9
- package/genome/skills/resolve-spec-conflict/SKILL.md +35 -29
- package/package.json +7 -6
- package/dist/regenerate/choose-conformance.d.ts +0 -32
- package/dist/regenerate/choose-conformance.js +0 -117
- /package/dist/{validate-cli.d.ts → parse-cli.d.ts} +0 -0
|
@@ -44,10 +44,16 @@ export interface ActivitySink {
|
|
|
44
44
|
/** The functional spec being built now, or nothing when none is. */
|
|
45
45
|
spec?(spec: BuildingSpec | undefined): void;
|
|
46
46
|
}
|
|
47
|
-
/** A functional spec as it is being built: where it sits, and its text as the spec has it. */
|
|
47
|
+
/** A functional spec as it is being built: which it is, where it sits, and its text as the spec has it. */
|
|
48
48
|
export interface BuildingSpec {
|
|
49
|
-
/**
|
|
49
|
+
/** The functional spec, as data: for a host to light it without reading the label. */
|
|
50
|
+
spec: {
|
|
51
|
+
module: string;
|
|
52
|
+
funcId: string;
|
|
53
|
+
};
|
|
54
|
+
/** For a person: "core:add-task, functional spec of 3". */
|
|
50
55
|
label: string;
|
|
56
|
+
/** Its text, with its scenarios after it. */
|
|
51
57
|
text: string;
|
|
52
58
|
/**
|
|
53
59
|
* When work on it began - set by the job, and kept while the same one goes on to its next stage
|
|
@@ -66,12 +72,12 @@ export interface SpecConflict {
|
|
|
66
72
|
moduleName: string;
|
|
67
73
|
plainFile: string;
|
|
68
74
|
/**
|
|
69
|
-
* The two
|
|
70
|
-
* id there, the .plain file it is in, and its functional spec as that file has it.
|
|
75
|
+
* The two functional specs - in that module, or in one it requires - each with its module, its
|
|
76
|
+
* func-id there, the .plain file it is in, and its functional spec as that file has it.
|
|
71
77
|
*/
|
|
72
78
|
specs: Array<{
|
|
73
79
|
module: string;
|
|
74
|
-
|
|
80
|
+
funcId: string;
|
|
75
81
|
plainFile: string;
|
|
76
82
|
text: string;
|
|
77
83
|
}>;
|
|
@@ -134,10 +140,10 @@ export interface UsageReport {
|
|
|
134
140
|
cost: number;
|
|
135
141
|
}
|
|
136
142
|
/**
|
|
137
|
-
* What a stage is about, said so the person knows what the name is: "module backend", or "
|
|
138
|
-
* backend
|
|
143
|
+
* What a stage is about, said so the person knows what the name is: "module backend", or "functional spec
|
|
144
|
+
* backend:add-task". A stage leads with it: `${unitOf(moduleName, funcId)}: running the tests`.
|
|
139
145
|
*/
|
|
140
|
-
export declare function unitOf(moduleName: string,
|
|
146
|
+
export declare function unitOf(moduleName: string, funcId?: string): string;
|
|
141
147
|
export declare function reportUsage(usage: UsageReport): void;
|
|
142
148
|
/** What one agent of a job did, added to what it did before: counted by its label. */
|
|
143
149
|
export interface AgentReport {
|
|
@@ -163,7 +169,7 @@ export declare function describeAgents(tallies: AgentTally[]): string[];
|
|
|
163
169
|
/** "1.2k tok · $0.03", or "" when nothing has been spent yet. */
|
|
164
170
|
export declare function describeUsage(usage: UsageReport | undefined): string;
|
|
165
171
|
/**
|
|
166
|
-
* Something an agent did with its model, wherever it ran - in a pulse or not, the planner
|
|
172
|
+
* Something an agent did with its model, wherever it ran - in a pulse or not, the planner too: what a
|
|
167
173
|
* headless run's report adds up (run-report.ts). A call is one model response; a turn, one request of the harness's
|
|
168
174
|
* to an agent, as many calls as it takes; a compaction, its work summed up - a call of its own. A provider retry is a
|
|
169
175
|
* request the provider failed for a moment, waited out and tried again - by pi, or by turri after pi gave up; a provider
|
|
@@ -199,6 +205,11 @@ export declare function onModelEvent(listener: (event: ModelEvent) => void): ()
|
|
|
199
205
|
export declare function describeTurnIncident(event: ModelEvent): string | undefined;
|
|
200
206
|
export declare function reportModelEvent(event: Omit<ModelEvent, "job">): void;
|
|
201
207
|
export declare function withActivity<T>(sink: ActivitySink, run: () => Promise<T>): Promise<T>;
|
|
208
|
+
/**
|
|
209
|
+
* Run work for no job, whatever job the code that starts it runs for - and clear of any stop signal: the test
|
|
210
|
+
* advisor's, started as the loop looks at a codebase, perhaps as a pulse ends. A pulse's panel, log and stop never reach it.
|
|
211
|
+
*/
|
|
212
|
+
export declare function outsideJobs<T>(run: () => T): T;
|
|
202
213
|
export declare function reportStage(label: string): void;
|
|
203
214
|
export declare function reportActivity(line: string): void;
|
|
204
215
|
/** Say something about the machinery under the work: shown as activity, and kept in the job's log, which the activity is not. */
|
|
@@ -58,11 +58,11 @@ export function reportTranscript(entry) {
|
|
|
58
58
|
store.getStore()?.transcript(entry);
|
|
59
59
|
}
|
|
60
60
|
/**
|
|
61
|
-
* What a stage is about, said so the person knows what the name is: "module backend", or "
|
|
62
|
-
* backend
|
|
61
|
+
* What a stage is about, said so the person knows what the name is: "module backend", or "functional spec
|
|
62
|
+
* backend:add-task". A stage leads with it: `${unitOf(moduleName, funcId)}: running the tests`.
|
|
63
63
|
*/
|
|
64
|
-
export function unitOf(moduleName,
|
|
65
|
-
return
|
|
64
|
+
export function unitOf(moduleName, funcId) {
|
|
65
|
+
return funcId === undefined ? `module ${moduleName}` : `functional spec ${moduleName}:${funcId}`;
|
|
66
66
|
}
|
|
67
67
|
export function reportUsage(usage) {
|
|
68
68
|
store.getStore()?.usage(usage);
|
|
@@ -159,6 +159,13 @@ export function reportModelEvent(event) {
|
|
|
159
159
|
export function withActivity(sink, run) {
|
|
160
160
|
return store.run(sink, run);
|
|
161
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Run work for no job, whatever job the code that starts it runs for - and clear of any stop signal: the test
|
|
164
|
+
* advisor's, started as the loop looks at a codebase, perhaps as a pulse ends. A pulse's panel, log and stop never reach it.
|
|
165
|
+
*/
|
|
166
|
+
export function outsideJobs(run) {
|
|
167
|
+
return store.exit(() => stops.exit(run));
|
|
168
|
+
}
|
|
162
169
|
export function reportStage(label) {
|
|
163
170
|
store.getStore()?.stage(label);
|
|
164
171
|
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The test advisor's file: `.turri/advisor/<module>.json` in the project - what it made of each functional spec of the
|
|
3
|
+
* module it looked at, by func-id, keyed by a hash of its text (docs/test-advisor.md). Local to this machine: `.turri`
|
|
4
|
+
* ignores itself, so it is never committed - another machine starts with none, and classifies only what changes there.
|
|
5
|
+
*
|
|
6
|
+
* An entry is current while its hash is the functional spec's text's: a text that changes makes it stale, and the
|
|
7
|
+
* functional spec is looked at again. `pending` is written the moment the advisor sees a functional spec, so quitting
|
|
8
|
+
* loses nothing: the next start in the chat or a host picks it up.
|
|
9
|
+
*/
|
|
10
|
+
export declare const ADVISOR_DIR = "advisor";
|
|
11
|
+
/** The four questions, each a yes or a no: could it fail silently, be read two ways, be built on later, cost much when wrong. */
|
|
12
|
+
export declare const ADVISOR_QUESTIONS: readonly ["silent-failure", "divergence", "revisited", "cost"];
|
|
13
|
+
export type AdvisorQuestion = (typeof ADVISOR_QUESTIONS)[number];
|
|
14
|
+
/** A question that was a yes, with its evidence in a line: the wrong output, the input read two ways, what would change, the consequence. */
|
|
15
|
+
export interface AdvisorAnswer {
|
|
16
|
+
question: AdvisorQuestion;
|
|
17
|
+
evidence: string;
|
|
18
|
+
}
|
|
19
|
+
/** An input to cover: one the readers agreed on, with its output, or one they split on, with each reading the verifier kept. */
|
|
20
|
+
export type AdvisorInput = {
|
|
21
|
+
input: string;
|
|
22
|
+
agreed: string;
|
|
23
|
+
} | {
|
|
24
|
+
input: string;
|
|
25
|
+
readings: string[];
|
|
26
|
+
};
|
|
27
|
+
/** What the advisor made of a functional spec: its verdict, the questions that were a yes, and on a yes the inputs to cover. */
|
|
28
|
+
export interface AdvisorReport {
|
|
29
|
+
verdict: "yes" | "no";
|
|
30
|
+
questions: AdvisorAnswer[];
|
|
31
|
+
inputs: AdvisorInput[];
|
|
32
|
+
}
|
|
33
|
+
/** Where an entry is: seen and not finished, classified yes or no, or failed - tried again only once its text changes. */
|
|
34
|
+
export type AdvisorState = "pending" | "yes" | "no" | "failed";
|
|
35
|
+
export interface AdvisorEntry {
|
|
36
|
+
funcId: string;
|
|
37
|
+
/** The hash of the functional spec's text the entry is about (textHash). */
|
|
38
|
+
hash: string;
|
|
39
|
+
state: AdvisorState;
|
|
40
|
+
report?: AdvisorReport;
|
|
41
|
+
/** The model the advisor ran on. */
|
|
42
|
+
model?: string;
|
|
43
|
+
/** When the entry was last written, ISO. */
|
|
44
|
+
at: string;
|
|
45
|
+
/** Why it failed, for a failed one. */
|
|
46
|
+
reason?: string;
|
|
47
|
+
/** The tip's .plain file, from the project: where the chain's scenarios are tested. */
|
|
48
|
+
tip?: string;
|
|
49
|
+
/** A yes not yet handed to a chat: delivered at the next start, as `pending` work is picked up. */
|
|
50
|
+
undelivered?: boolean;
|
|
51
|
+
/** Pending at the chat's asking (evaluate_tests), with why: picked up at the next start whatever `test-advisor` says. */
|
|
52
|
+
asked?: {
|
|
53
|
+
reason?: string;
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
export interface AdvisorFile {
|
|
57
|
+
module: string;
|
|
58
|
+
/** The module's .plain file, from the project. */
|
|
59
|
+
plainFile: string;
|
|
60
|
+
entries: Record<string, AdvisorEntry>;
|
|
61
|
+
}
|
|
62
|
+
/** The hash a functional spec's text is known by: its text, trimmed. */
|
|
63
|
+
export declare function textHash(text: string): string;
|
|
64
|
+
/** The folder of a project's advisor files. */
|
|
65
|
+
export declare function advisorDir(project: string): string;
|
|
66
|
+
/** A module's advisor file. */
|
|
67
|
+
export declare function advisorPath(project: string, module: string): string;
|
|
68
|
+
/** A module's advisor file, or nothing when there is none or it does not read. */
|
|
69
|
+
export declare function readAdvisor(project: string, module: string): AdvisorFile | undefined;
|
|
70
|
+
/** A functional spec's entry, or nothing. */
|
|
71
|
+
export declare function advisorEntry(project: string, module: string, funcId: string): AdvisorEntry | undefined;
|
|
72
|
+
/** Whether an entry is about the text as it is now: one that is not, or none, is stale, and the functional spec is looked at again. */
|
|
73
|
+
export declare function isCurrent(entry: AdvisorEntry | undefined, hash: string): entry is AdvisorEntry;
|
|
74
|
+
/** Write a functional spec's entry, in place of the one it had. `plainFile` is the module's, from the project. */
|
|
75
|
+
export declare function putAdvisorEntry(project: string, module: string, plainFile: string, entry: Omit<AdvisorEntry, "at"> & {
|
|
76
|
+
at?: string;
|
|
77
|
+
}): AdvisorEntry;
|
|
78
|
+
/** Take a functional spec's entry out - its functional spec is gone. */
|
|
79
|
+
export declare function removeAdvisorEntry(project: string, module: string, funcId: string): void;
|
|
80
|
+
/** Every pending entry of a project's advisor files, with the module and its .plain file - what a start picks up. */
|
|
81
|
+
export declare function pendingEntries(project: string): Array<{
|
|
82
|
+
module: string;
|
|
83
|
+
plainFile: string;
|
|
84
|
+
entry: AdvisorEntry;
|
|
85
|
+
}>;
|
|
86
|
+
/** Every yes of a project's advisor files not yet handed to a chat - what a start delivers. */
|
|
87
|
+
export declare function undeliveredEntries(project: string): Array<{
|
|
88
|
+
module: string;
|
|
89
|
+
plainFile: string;
|
|
90
|
+
entry: AdvisorEntry;
|
|
91
|
+
}>;
|
|
92
|
+
/** A yes handed to a chat: the entry for that text no longer kept for the next start. */
|
|
93
|
+
export declare function markDelivered(project: string, module: string, funcId: string, hash: string): void;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { existsSync, readdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
|
|
3
|
+
import { join, resolve } from "node:path";
|
|
4
|
+
import { ensureInScratch } from "./paths.js";
|
|
5
|
+
/**
|
|
6
|
+
* The test advisor's file: `.turri/advisor/<module>.json` in the project - what it made of each functional spec of the
|
|
7
|
+
* module it looked at, by func-id, keyed by a hash of its text (docs/test-advisor.md). Local to this machine: `.turri`
|
|
8
|
+
* ignores itself, so it is never committed - another machine starts with none, and classifies only what changes there.
|
|
9
|
+
*
|
|
10
|
+
* An entry is current while its hash is the functional spec's text's: a text that changes makes it stale, and the
|
|
11
|
+
* functional spec is looked at again. `pending` is written the moment the advisor sees a functional spec, so quitting
|
|
12
|
+
* loses nothing: the next start in the chat or a host picks it up.
|
|
13
|
+
*/
|
|
14
|
+
export const ADVISOR_DIR = "advisor";
|
|
15
|
+
/** The four questions, each a yes or a no: could it fail silently, be read two ways, be built on later, cost much when wrong. */
|
|
16
|
+
export const ADVISOR_QUESTIONS = ["silent-failure", "divergence", "revisited", "cost"];
|
|
17
|
+
/** The hash a functional spec's text is known by: its text, trimmed. */
|
|
18
|
+
export function textHash(text) {
|
|
19
|
+
return createHash("sha256").update(text.trim()).digest("hex").slice(0, 16);
|
|
20
|
+
}
|
|
21
|
+
/** The folder of a project's advisor files. */
|
|
22
|
+
export function advisorDir(project) {
|
|
23
|
+
return join(resolve(project), ".turri", ADVISOR_DIR);
|
|
24
|
+
}
|
|
25
|
+
/** A module's advisor file. */
|
|
26
|
+
export function advisorPath(project, module) {
|
|
27
|
+
return join(advisorDir(project), `${module}.json`);
|
|
28
|
+
}
|
|
29
|
+
/** A module's advisor file, or nothing when there is none or it does not read. */
|
|
30
|
+
export function readAdvisor(project, module) {
|
|
31
|
+
try {
|
|
32
|
+
const parsed = JSON.parse(readFileSync(advisorPath(project, module), "utf8"));
|
|
33
|
+
return parsed !== null && typeof parsed === "object" && typeof parsed.entries === "object" && parsed.entries !== null ? parsed : undefined;
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
/** A functional spec's entry, or nothing. */
|
|
40
|
+
export function advisorEntry(project, module, funcId) {
|
|
41
|
+
return readAdvisor(project, module)?.entries[funcId];
|
|
42
|
+
}
|
|
43
|
+
/** Whether an entry is about the text as it is now: one that is not, or none, is stale, and the functional spec is looked at again. */
|
|
44
|
+
export function isCurrent(entry, hash) {
|
|
45
|
+
return entry !== undefined && entry.hash === hash;
|
|
46
|
+
}
|
|
47
|
+
/** Write a functional spec's entry, in place of the one it had. `plainFile` is the module's, from the project. */
|
|
48
|
+
export function putAdvisorEntry(project, module, plainFile, entry) {
|
|
49
|
+
const file = readAdvisor(project, module) ?? { module, plainFile, entries: {} };
|
|
50
|
+
const written = { ...entry, at: entry.at ?? new Date().toISOString() };
|
|
51
|
+
file.plainFile = plainFile;
|
|
52
|
+
file.entries[entry.funcId] = written;
|
|
53
|
+
writeAdvisor(project, file);
|
|
54
|
+
return written;
|
|
55
|
+
}
|
|
56
|
+
/** Take a functional spec's entry out - its functional spec is gone. */
|
|
57
|
+
export function removeAdvisorEntry(project, module, funcId) {
|
|
58
|
+
const file = readAdvisor(project, module);
|
|
59
|
+
if (file === undefined || file.entries[funcId] === undefined)
|
|
60
|
+
return;
|
|
61
|
+
delete file.entries[funcId];
|
|
62
|
+
writeAdvisor(project, file);
|
|
63
|
+
}
|
|
64
|
+
function writeAdvisor(project, file) {
|
|
65
|
+
const scratch = join(resolve(project), ".turri");
|
|
66
|
+
ensureInScratch(scratch, advisorDir(project));
|
|
67
|
+
const path = advisorPath(project, file.module);
|
|
68
|
+
// Written whole, then moved in place: a turri killed part-way never leaves half a file.
|
|
69
|
+
const temporary = `${path}.${process.pid}.tmp`;
|
|
70
|
+
writeFileSync(temporary, `${JSON.stringify(file, null, 2)}\n`);
|
|
71
|
+
renameSync(temporary, path);
|
|
72
|
+
}
|
|
73
|
+
/** Every pending entry of a project's advisor files, with the module and its .plain file - what a start picks up. */
|
|
74
|
+
export function pendingEntries(project) {
|
|
75
|
+
return entriesWhere(project, (entry) => entry.state === "pending");
|
|
76
|
+
}
|
|
77
|
+
/** Every yes of a project's advisor files not yet handed to a chat - what a start delivers. */
|
|
78
|
+
export function undeliveredEntries(project) {
|
|
79
|
+
return entriesWhere(project, (entry) => entry.state === "yes" && entry.undelivered === true);
|
|
80
|
+
}
|
|
81
|
+
/** A yes handed to a chat: the entry for that text no longer kept for the next start. */
|
|
82
|
+
export function markDelivered(project, module, funcId, hash) {
|
|
83
|
+
const file = readAdvisor(project, module);
|
|
84
|
+
const entry = file?.entries[funcId];
|
|
85
|
+
if (file === undefined || entry === undefined || entry.hash !== hash || entry.undelivered !== true)
|
|
86
|
+
return;
|
|
87
|
+
const { undelivered: _delivered, ...rest } = entry;
|
|
88
|
+
file.entries[funcId] = rest;
|
|
89
|
+
writeAdvisor(project, file);
|
|
90
|
+
}
|
|
91
|
+
function entriesWhere(project, wanted) {
|
|
92
|
+
const dir = advisorDir(project);
|
|
93
|
+
if (!existsSync(dir))
|
|
94
|
+
return [];
|
|
95
|
+
let names;
|
|
96
|
+
try {
|
|
97
|
+
names = readdirSync(dir).filter((name) => name.endsWith(".json"));
|
|
98
|
+
}
|
|
99
|
+
catch {
|
|
100
|
+
return [];
|
|
101
|
+
}
|
|
102
|
+
return names.flatMap((name) => {
|
|
103
|
+
const file = readAdvisor(project, name.slice(0, -".json".length));
|
|
104
|
+
if (file === undefined)
|
|
105
|
+
return [];
|
|
106
|
+
return Object.values(file.entries)
|
|
107
|
+
.filter(wanted)
|
|
108
|
+
.map((entry) => ({ module: file.module, plainFile: file.plainFile, entry }));
|
|
109
|
+
});
|
|
110
|
+
}
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { type AdvisorReport } from "./advisor-records.ts";
|
|
2
|
+
import type { SeenSpec } from "./loop.ts";
|
|
3
|
+
import { type AdvisorCallOptions, type AdvisorSubject } from "./test-advisor.ts";
|
|
4
|
+
/**
|
|
5
|
+
* When the test advisor runs, and on what (docs/test-advisor.md). It watches what the work loop looks at - every
|
|
6
|
+
* ***plain change the loop sees, as it sees it - and takes each functional spec that is new or whose text changed since
|
|
7
|
+
* its code was built (its code record), has no scenarios, and has no entry in the advisor's file for that text. A
|
|
8
|
+
* project already built, with no advisor file, is looked at not at all: only what changes from now on is.
|
|
9
|
+
*
|
|
10
|
+
* Each is marked `pending` in the advisor's file then and there, and judged in the background - two at a time at most,
|
|
11
|
+
* outside `parallel-jobs` and the lanes: no pulse waits for it, and it never steers or stops one. Its work is clear of
|
|
12
|
+
* any pulse: no panel, no log, no stop of a pulse's reaches it. A functional spec whose text changes while it is judged
|
|
13
|
+
* has that work thrown away - whoever changed it - and the new text starts again. What `pending` is left as turri quits,
|
|
14
|
+
* the next start in the chat or a host picks up.
|
|
15
|
+
*
|
|
16
|
+
* The advisor decides and writes nothing in the ***plain: what it made of a functional spec is kept in its file and
|
|
17
|
+
* handed on (`onVerdict`) to the chat the person looks at, which writes the scenarios on a yes. A yes not yet handed to a
|
|
18
|
+
* chat stays marked undelivered, and is handed on again at the next start.
|
|
19
|
+
*
|
|
20
|
+
* It is made only where the session starts in the chat or a host (extension.ts) - never for a headless command, nor
|
|
21
|
+
* pi's print, json and rpc modes - and does nothing in a project whose `test-advisor:` is false.
|
|
22
|
+
*/
|
|
23
|
+
/** How many functional specs the advisor works on at once. */
|
|
24
|
+
export declare const ADVISOR_PARALLEL = 2;
|
|
25
|
+
/** A finished verdict, handed on to the chat: the functional spec, what was made of it, and where its scenarios go. */
|
|
26
|
+
export interface AdvisorVerdict {
|
|
27
|
+
project: string;
|
|
28
|
+
module: string;
|
|
29
|
+
funcId: string;
|
|
30
|
+
/** The module's .plain file. */
|
|
31
|
+
plainFile: string;
|
|
32
|
+
/** The tip's .plain file: where the chain's scenarios are tested, and its test reqs and conformance testing script are. */
|
|
33
|
+
tip: string;
|
|
34
|
+
/** The test plain file its scenarios go in, from the project: `<test-dir>/<module>/<func-id>.plain`. */
|
|
35
|
+
testFile: string;
|
|
36
|
+
/** The hash of the text it judged. */
|
|
37
|
+
hash: string;
|
|
38
|
+
report: AdvisorReport;
|
|
39
|
+
/** Whether it is still about the functional spec as it is now: its text the same, and still with no scenarios. */
|
|
40
|
+
current(): boolean;
|
|
41
|
+
/** Handed to a chat: no longer kept for the next start. */
|
|
42
|
+
delivered(): void;
|
|
43
|
+
}
|
|
44
|
+
export interface TestAdvisorOptions {
|
|
45
|
+
/** Whether a project wants the advisor now. Default: its `test-advisor:` is not false. */
|
|
46
|
+
enabled?: (project: string) => boolean;
|
|
47
|
+
/** The judgement of a functional spec. Default: the model calls of test-advisor.ts. */
|
|
48
|
+
advise?: (subject: AdvisorSubject, options: AdvisorCallOptions) => Promise<AdvisorReport>;
|
|
49
|
+
/** The conversation's own words on what matters for a module - its steering. */
|
|
50
|
+
context?: (plainFile: string) => string[];
|
|
51
|
+
/** A verdict finished - or a yes left undelivered, at the next start: for the chat the person looks at. Not given: only kept. */
|
|
52
|
+
onVerdict?: (verdict: AdvisorVerdict) => void;
|
|
53
|
+
/** How many functional specs at once. Default: ADVISOR_PARALLEL. */
|
|
54
|
+
parallel?: number;
|
|
55
|
+
}
|
|
56
|
+
export interface TestAdvisor {
|
|
57
|
+
/** What the loop looked at: the functional specs to take, and the work whose text changed under it to drop. */
|
|
58
|
+
seen(look: SeenSpec): void;
|
|
59
|
+
/** Pick up what a project's advisor file has pending - the work turri quit in the middle of - and hand on its undelivered yeses. */
|
|
60
|
+
resume(project: string): void;
|
|
61
|
+
/** Resolves once nothing is queued or running. */
|
|
62
|
+
idle(): Promise<void>;
|
|
63
|
+
/** Stop everything, leaving what was running pending. */
|
|
64
|
+
stop(): void;
|
|
65
|
+
/** The functional specs it is judging for now, or about to - `module:func-id`, a project's alone when named. */
|
|
66
|
+
taking(project?: string): string[];
|
|
67
|
+
/**
|
|
68
|
+
* The chat's own way in (evaluate_tests): a functional spec evaluated now - even a text already judged, and whatever
|
|
69
|
+
* `test-advisor` says - its verdict handed on as any is. `already`: it is being evaluated now; `scenarios`: it has some,
|
|
70
|
+
* and is not evaluated; `missing`: its module has no such functional spec.
|
|
71
|
+
*/
|
|
72
|
+
evaluate(asked: EvaluateRequest): "started" | "already" | "scenarios" | "missing";
|
|
73
|
+
}
|
|
74
|
+
/** A functional spec the chat asks the advisor to evaluate. */
|
|
75
|
+
export interface EvaluateRequest {
|
|
76
|
+
module: string;
|
|
77
|
+
funcId: string;
|
|
78
|
+
/** The module's .plain file. */
|
|
79
|
+
plainFile: string;
|
|
80
|
+
/** The tip its scenarios are tested in. Default: the one the advisor last knew, else the module at the top of a chain it is in, else itself. */
|
|
81
|
+
tip?: string;
|
|
82
|
+
/** Why the chat asks - "the coding agent failed three times to implement it" - for the classifier, as context. */
|
|
83
|
+
reason?: string;
|
|
84
|
+
}
|
|
85
|
+
/** Whether a functional spec has scenarios of any kind: then it is tested as the person wants, and today's review covers it. */
|
|
86
|
+
export declare function hasScenarios(item: unknown): boolean;
|
|
87
|
+
export declare function createTestAdvisor(options?: TestAdvisorOptions): TestAdvisor;
|