@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,300 @@
|
|
|
1
|
+
import { wrapTextWithAnsi } from "@earendil-works/pi-tui";
|
|
2
|
+
import { describeAgents } from "./activity.js";
|
|
3
|
+
import { didNotPass, finishedMark, pulseCost, spinner } from "./job-panel.js";
|
|
4
|
+
import { formatDuration, pulseName } from "./jobs.js";
|
|
5
|
+
import { diffFiles, fileHeader, PLAIN_PAINT } from "./diff-view.js";
|
|
6
|
+
export const SECTIONS = ["running", "queued", "finished"];
|
|
7
|
+
export function createJobsTreeState() {
|
|
8
|
+
return { open: { running: true, queued: true, finished: false }, openJobs: new Map(), openFiles: new Map(), cursor: 0, top: 0 };
|
|
9
|
+
}
|
|
10
|
+
export function sectionOf(job) {
|
|
11
|
+
return job.status === "running" ? "running" : job.status === "queued" ? "queued" : "finished";
|
|
12
|
+
}
|
|
13
|
+
/** The files of a job's diff, in the order they are shown - read once for each diff, not on every frame. */
|
|
14
|
+
const filesSeen = new Map();
|
|
15
|
+
function filesOf(job) {
|
|
16
|
+
if (job.diff === undefined || job.diff.trim() === "")
|
|
17
|
+
return [];
|
|
18
|
+
const seen = filesSeen.get(job.id);
|
|
19
|
+
if (seen !== undefined && seen.diff === job.diff)
|
|
20
|
+
return seen.files;
|
|
21
|
+
const files = diffFiles(job.diff, 80, undefined, job.diffWhere).map((file) => file.file);
|
|
22
|
+
filesSeen.set(job.id, { diff: job.diff, files });
|
|
23
|
+
return files;
|
|
24
|
+
}
|
|
25
|
+
/** Whether a file of a job's diff is open: as it was opened or closed, else open when it is the file being rendered - the first. */
|
|
26
|
+
function isFileOpen(state, row) {
|
|
27
|
+
return state.openFiles.get(row.job.id)?.get(row.file) ?? row.first;
|
|
28
|
+
}
|
|
29
|
+
function setFileOpen(state, job, file, open) {
|
|
30
|
+
const files = state.openFiles.get(job.id) ?? new Map();
|
|
31
|
+
files.set(file, open);
|
|
32
|
+
state.openFiles.set(job.id, files);
|
|
33
|
+
}
|
|
34
|
+
/** Whether a job is open where it is now: one opened in another section, it closed when it left it. */
|
|
35
|
+
function isOpen(state, job) {
|
|
36
|
+
const opened = state.openJobs.get(job.id);
|
|
37
|
+
if (opened === undefined)
|
|
38
|
+
return false;
|
|
39
|
+
if (opened === sectionOf(job))
|
|
40
|
+
return true;
|
|
41
|
+
state.openJobs.delete(job.id);
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
/** The rows, top to bottom: each section's header, and its jobs when it is open - finished ones newest first. */
|
|
45
|
+
/** The pulse list put on one pulse: its section open, the pulse opened - its stages, its agents, its log - and the cursor on it. */
|
|
46
|
+
export function selectJob(jobs, state, id) {
|
|
47
|
+
const job = jobs.find((entry) => entry.id === id);
|
|
48
|
+
if (job === undefined)
|
|
49
|
+
return false;
|
|
50
|
+
const section = job.status === "running" ? "running" : job.status === "queued" ? "queued" : "finished";
|
|
51
|
+
state.open[section] = true;
|
|
52
|
+
state.openJobs.set(id, section);
|
|
53
|
+
const at = jobsTreeRows(jobs, state).findIndex((row) => row.kind === "job" && row.job.id === id);
|
|
54
|
+
if (at !== -1)
|
|
55
|
+
state.cursor = at;
|
|
56
|
+
return at !== -1;
|
|
57
|
+
}
|
|
58
|
+
export function jobsTreeRows(jobs, state) {
|
|
59
|
+
// A job queued again as another is that other one: it is not listed apart.
|
|
60
|
+
const bySequence = jobs.filter((job) => job.replacedBy === undefined).sort((a, b) => a.sequence - b.sequence);
|
|
61
|
+
const of = (section) => {
|
|
62
|
+
const inSection = bySequence.filter((job) => sectionOf(job) === section);
|
|
63
|
+
return section === "finished" ? inSection.sort((a, b) => (b.finishedAt ?? b.sequence) - (a.finishedAt ?? a.sequence) || b.sequence - a.sequence) : inSection;
|
|
64
|
+
};
|
|
65
|
+
// Forget what is open for jobs that moved on - they close as they leave.
|
|
66
|
+
for (const job of jobs)
|
|
67
|
+
isOpen(state, job);
|
|
68
|
+
return SECTIONS.flatMap((section) => {
|
|
69
|
+
const inSection = of(section);
|
|
70
|
+
const jobRows = (job) => [
|
|
71
|
+
{ kind: "job", section, job },
|
|
72
|
+
// An open job's diff, a row a file: each opens and closes on its own.
|
|
73
|
+
...(isOpen(state, job) ? filesOf(job).map((file, index) => ({ kind: "file", section, job, file, first: index === 0 })) : []),
|
|
74
|
+
];
|
|
75
|
+
return [{ kind: "section", section, count: inSection.length }, ...(state.open[section] ? inSection.flatMap(jobRows) : [])];
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* A job's status beside its name. Running is the panel's spinner, not a play mark: a `▶` next to the `▸` that opens the
|
|
80
|
+
* job reads as a second arrow to pick.
|
|
81
|
+
*/
|
|
82
|
+
const MARK = { running: spinner, queued: () => "·", succeeded: () => "✓", failed: () => "✗", stopped: () => "■" };
|
|
83
|
+
/** A pulse's mark: running or queued as it is; finished, in the colour of how it ended - green it passed, red it failed or did not pass, stopped amber. */
|
|
84
|
+
function markOf(job, style) {
|
|
85
|
+
if (sectionOf(job) !== "finished")
|
|
86
|
+
return MARK[job.status]?.(Date.now()) ?? "·";
|
|
87
|
+
return finishedMark(job, { success: style.success ?? style.added, error: style.error, warning: style.warning ?? style.accent });
|
|
88
|
+
}
|
|
89
|
+
function wrap(text, width, indent, colour = (t) => t) {
|
|
90
|
+
return text
|
|
91
|
+
.trimEnd()
|
|
92
|
+
.split("\n")
|
|
93
|
+
.flatMap((line) => (line === "" ? [""] : wrapTextWithAnsi(line, Math.max(10, width - indent.length))))
|
|
94
|
+
.map((line) => colour(`${indent}${line}`));
|
|
95
|
+
}
|
|
96
|
+
/** The spec diffs a module's render planned and queued, from its details. */
|
|
97
|
+
function plannedOf(job) {
|
|
98
|
+
const planned = job.details?.planned;
|
|
99
|
+
return Array.isArray(planned) ? planned : [];
|
|
100
|
+
}
|
|
101
|
+
/** A job named for the one reading about another: what it was, and its id. */
|
|
102
|
+
function named(jobs, id) {
|
|
103
|
+
const job = jobs.find((other) => other.id === id);
|
|
104
|
+
return job !== undefined ? `${pulseName(job)} (${job.module === undefined ? `pulse ${id}, ` : ""}${job.status})` : `pulse ${id}`;
|
|
105
|
+
}
|
|
106
|
+
/** All of a job, opened: what it builds, and - running - what it is doing; waiting, what it waits on; done, how it ended. */
|
|
107
|
+
function detailLines(job, jobs, width, style) {
|
|
108
|
+
const indent = " ";
|
|
109
|
+
const lines = [];
|
|
110
|
+
// A diff is shown a file at a time, in rows of their own after these.
|
|
111
|
+
if ((job.diff === undefined || job.diff.trim() === "") && job.building !== undefined) {
|
|
112
|
+
lines.push(style.muted(`${indent}${job.building.label}:`), ...wrap(job.building.text, width, `${indent} `, style.accent));
|
|
113
|
+
}
|
|
114
|
+
if (job.follows !== undefined)
|
|
115
|
+
lines.push(style.muted(`${indent}the conformance of ${named(jobs, job.follows)}`));
|
|
116
|
+
// The job that planned it, for a spec diff a module's render queued; what a planning job planned, in order.
|
|
117
|
+
const planner = jobs.find((other) => plannedOf(other).some((step) => step.jobId === job.id));
|
|
118
|
+
if (planner !== undefined)
|
|
119
|
+
lines.push(style.muted(`${indent}planned by ${named(jobs, planner.id)}`));
|
|
120
|
+
plannedOf(job).forEach((step, index, all) => {
|
|
121
|
+
if (index === 0)
|
|
122
|
+
lines.push(style.muted(`${indent}planned ${all.length} spec diff${all.length === 1 ? "" : "s"}:`));
|
|
123
|
+
lines.push(style.muted(`${indent} ${index + 1}. ${step.title} → ${named(jobs, step.jobId)}`));
|
|
124
|
+
});
|
|
125
|
+
if (job.status === "queued" && job.waitingOn.length > 0)
|
|
126
|
+
lines.push(style.muted(`${indent}waits for ${job.waitingOn.map((id) => `pulse ${id}`).join(", ")}`));
|
|
127
|
+
if (job.note !== undefined)
|
|
128
|
+
lines.push(...wrap(job.note, width, indent, style.accent));
|
|
129
|
+
if (job.status === "running") {
|
|
130
|
+
for (const line of job.activity)
|
|
131
|
+
lines.push(...wrap(line, width, indent, style.muted));
|
|
132
|
+
if (job.now !== undefined)
|
|
133
|
+
lines.push(style.muted(`${indent}… ${job.now.label} · ${Math.max(0, Math.round((Date.now() - job.now.since) / 1000))}s`));
|
|
134
|
+
}
|
|
135
|
+
if (job.status === "failed" && job.error !== undefined)
|
|
136
|
+
lines.push(...wrap(job.error.split("\n").slice(0, 6).join("\n"), width, indent, style.error));
|
|
137
|
+
if (sectionOf(job) === "finished")
|
|
138
|
+
lines.push(...whatWentOn(job, width, style));
|
|
139
|
+
return lines;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* What went on in a finished pulse, opened: its agents and what each spent, and whether it passed - its report is
|
|
143
|
+
* the chat's, and its log has the rest. Its diff follows, a file a row.
|
|
144
|
+
*/
|
|
145
|
+
function whatWentOn(job, width, style) {
|
|
146
|
+
const indent = " ";
|
|
147
|
+
const lines = [];
|
|
148
|
+
// Each agent's turns, tokens, cost and model - without its label, which says again what the pulse's line says.
|
|
149
|
+
for (const agent of describeAgents(job.agents.map((tally) => ({ ...tally, label: "" }))))
|
|
150
|
+
lines.push(...wrap(agent.replace(/^ · /, ""), width, indent, style.muted));
|
|
151
|
+
if (didNotPass(job))
|
|
152
|
+
lines.push(style.error(`${indent}did not pass`));
|
|
153
|
+
if (job.logFile !== undefined)
|
|
154
|
+
lines.push(style.muted(`${indent}log: ${job.logFile}`));
|
|
155
|
+
return lines;
|
|
156
|
+
}
|
|
157
|
+
/** The rows as lines, the cursor's marked, and where each row starts - for scrolling to it. */
|
|
158
|
+
export function jobsTreeLines(jobs, state, width, style) {
|
|
159
|
+
const rows = jobsTreeRows(jobs, state);
|
|
160
|
+
state.cursor = Math.max(0, Math.min(state.cursor, rows.length - 1));
|
|
161
|
+
const lines = [];
|
|
162
|
+
const rowStarts = [];
|
|
163
|
+
rows.forEach((row, index) => {
|
|
164
|
+
rowStarts.push(lines.length);
|
|
165
|
+
const here = index === state.cursor;
|
|
166
|
+
const pointer = here ? "›" : " ";
|
|
167
|
+
if (row.kind === "section") {
|
|
168
|
+
if (index > 0) {
|
|
169
|
+
// A blank line between sections, kept off the cursor's row.
|
|
170
|
+
lines.push("");
|
|
171
|
+
rowStarts[index] = lines.length;
|
|
172
|
+
}
|
|
173
|
+
const header = `${pointer} ${state.open[row.section] ? "▾" : "▸"} ${row.section} (${row.count})`;
|
|
174
|
+
lines.push(here ? style.heading(header) : style.muted(header));
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
if (row.kind === "file") {
|
|
178
|
+
const open = isFileOpen(state, row);
|
|
179
|
+
const paint = style.diff ?? { ...PLAIN_PAINT, added: style.added, removed: style.removed, muted: style.muted };
|
|
180
|
+
const shown = diffFiles(row.job.diff, Math.max(10, width - FILE_INDENT.length), paint, row.job.diffWhere).find((file) => file.file === row.file);
|
|
181
|
+
if (shown === undefined)
|
|
182
|
+
return;
|
|
183
|
+
const header = `${pointer}${FILE_INDENT.slice(1)}${fileHeader(shown, paint, open)}`;
|
|
184
|
+
lines.push(here ? style.heading(header) : header);
|
|
185
|
+
if (open)
|
|
186
|
+
lines.push(...shown.rows.map((line) => `${FILE_INDENT}${line}`));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
const { job } = row;
|
|
190
|
+
const open = isOpen(state, job);
|
|
191
|
+
// A line says which module, which pulse, how long and what it cost - and the mark where it is, or how it ended; the rest - what it regenerates too - is in it, opened.
|
|
192
|
+
// A queued pulse has not started: it has no time to show.
|
|
193
|
+
const time = job.status === "queued" ? "" : ` · ${formatDuration(job)}`;
|
|
194
|
+
const head = `${pointer} ${open ? "▾" : "▸"} ${markOf(job, style)} ${pulseName(job)}${time}${pulseCost(job)}`;
|
|
195
|
+
const [first = "", ...rest] = wrapTextWithAnsi(head, Math.max(10, width));
|
|
196
|
+
lines.push(here ? style.heading(first) : first, ...rest.map((line) => ` ${line}`));
|
|
197
|
+
if (open)
|
|
198
|
+
lines.push(...detailLines(job, jobs, width, style));
|
|
199
|
+
});
|
|
200
|
+
return { lines, rowStarts, rows };
|
|
201
|
+
}
|
|
202
|
+
/** How far a job's files, and their lines, are indented under it. */
|
|
203
|
+
const FILE_INDENT = " ";
|
|
204
|
+
/** Keep the cursor's row in sight: scroll only as far as that takes. */
|
|
205
|
+
export function keepCursorInView(state, rowStarts, lineCount, height) {
|
|
206
|
+
const start = rowStarts[state.cursor] ?? 0;
|
|
207
|
+
const end = rowStarts[state.cursor + 1] ?? lineCount;
|
|
208
|
+
// A line of its row in sight is enough: a job opened longer than the screen is read by scrolling through it.
|
|
209
|
+
const inSight = start < state.top + height && end > state.top;
|
|
210
|
+
if (!inSight && start < state.top)
|
|
211
|
+
state.top = start;
|
|
212
|
+
if (!inSight && start >= state.top + height)
|
|
213
|
+
state.top = start - height + 1;
|
|
214
|
+
state.top = Math.max(0, Math.min(state.top, Math.max(0, lineCount - height)));
|
|
215
|
+
}
|
|
216
|
+
/** Scroll the view by lines, the cursor coming along to the first row in sight. */
|
|
217
|
+
export function scrollTree(state, by, rowStarts, lineCount, height) {
|
|
218
|
+
state.top = Math.max(0, Math.min(state.top + by, Math.max(0, lineCount - height)));
|
|
219
|
+
const end = (row) => rowStarts[row + 1] ?? lineCount;
|
|
220
|
+
// The cursor's row still has a line in sight - a job opened longer than the screen, read as it scrolls by: it stays.
|
|
221
|
+
if ((rowStarts[state.cursor] ?? 0) < state.top + height && end(state.cursor) > state.top)
|
|
222
|
+
return;
|
|
223
|
+
// Otherwise onto the first row with a line in sight.
|
|
224
|
+
const first = rowStarts.findIndex((start, row) => start < state.top + height && end(row) > state.top);
|
|
225
|
+
state.cursor = Math.max(0, first);
|
|
226
|
+
}
|
|
227
|
+
/** A key in the Jobs view, and what it does to the tree. */
|
|
228
|
+
export function treeKey(state, rows, key) {
|
|
229
|
+
const row = rows[state.cursor];
|
|
230
|
+
switch (key) {
|
|
231
|
+
case "up":
|
|
232
|
+
state.cursor = Math.max(0, state.cursor - 1);
|
|
233
|
+
return { kind: "handled" };
|
|
234
|
+
case "down":
|
|
235
|
+
state.cursor = Math.min(rows.length - 1, state.cursor + 1);
|
|
236
|
+
return { kind: "handled" };
|
|
237
|
+
case "toggle":
|
|
238
|
+
if (row === undefined)
|
|
239
|
+
return { kind: "none" };
|
|
240
|
+
if (row.kind === "section")
|
|
241
|
+
state.open[row.section] = !state.open[row.section];
|
|
242
|
+
else if (row.kind === "file")
|
|
243
|
+
setFileOpen(state, row.job, row.file, !isFileOpen(state, row));
|
|
244
|
+
else if (state.openJobs.has(row.job.id))
|
|
245
|
+
state.openJobs.delete(row.job.id);
|
|
246
|
+
else
|
|
247
|
+
state.openJobs.set(row.job.id, row.section);
|
|
248
|
+
return { kind: "handled" };
|
|
249
|
+
case "open":
|
|
250
|
+
if (row === undefined)
|
|
251
|
+
return { kind: "none" };
|
|
252
|
+
if (row.kind === "section")
|
|
253
|
+
state.open[row.section] = true;
|
|
254
|
+
else if (row.kind === "file")
|
|
255
|
+
setFileOpen(state, row.job, row.file, true);
|
|
256
|
+
else
|
|
257
|
+
state.openJobs.set(row.job.id, row.section);
|
|
258
|
+
return { kind: "handled" };
|
|
259
|
+
case "close":
|
|
260
|
+
if (row === undefined)
|
|
261
|
+
return { kind: "none" };
|
|
262
|
+
if (row.kind === "section")
|
|
263
|
+
state.open[row.section] = false;
|
|
264
|
+
else if (row.kind === "file") {
|
|
265
|
+
// Open, it closes; closed, the cursor goes up to its job.
|
|
266
|
+
if (isFileOpen(state, row))
|
|
267
|
+
setFileOpen(state, row.job, row.file, false);
|
|
268
|
+
else
|
|
269
|
+
state.cursor = rows.findLastIndex((other, index) => index < state.cursor && other.kind === "job" && other.job.id === row.job.id);
|
|
270
|
+
}
|
|
271
|
+
else if (state.openJobs.has(row.job.id))
|
|
272
|
+
state.openJobs.delete(row.job.id);
|
|
273
|
+
else
|
|
274
|
+
state.cursor = rows.findLastIndex((other, index) => index < state.cursor && other.kind === "section");
|
|
275
|
+
return { kind: "handled" };
|
|
276
|
+
case "openAll":
|
|
277
|
+
case "closeAll": {
|
|
278
|
+
if (row === undefined)
|
|
279
|
+
return { kind: "none" };
|
|
280
|
+
// On a file: every file of its job, to go through them quickly.
|
|
281
|
+
if (row.kind === "file") {
|
|
282
|
+
for (const other of rows)
|
|
283
|
+
if (other.kind === "file" && other.job.id === row.job.id)
|
|
284
|
+
setFileOpen(state, other.job, other.file, key === "openAll");
|
|
285
|
+
return { kind: "handled" };
|
|
286
|
+
}
|
|
287
|
+
const section = row.section;
|
|
288
|
+
state.open[section] = true;
|
|
289
|
+
for (const other of rows) {
|
|
290
|
+
if (other.kind !== "job" || other.section !== section)
|
|
291
|
+
continue;
|
|
292
|
+
if (key === "openAll")
|
|
293
|
+
state.openJobs.set(other.job.id, section);
|
|
294
|
+
else
|
|
295
|
+
state.openJobs.delete(other.job.id);
|
|
296
|
+
}
|
|
297
|
+
return { kind: "handled" };
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|