@golden-frijoles/kit 0.0.0 → 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 +25 -1
- package/bin.mjs +108 -0
- package/dist/LICENSE +202 -0
- package/dist/NOTICE +7 -0
- package/dist/babysit-pr.mjs +213 -0
- package/dist/build-order-sync.mjs +141 -0
- package/dist/build-order.mjs +178 -0
- package/dist/cross-panel.mjs +216 -0
- package/dist/cross-panel.prompt.md +115 -0
- package/dist/doc-format.enforced.json +4 -0
- package/dist/doc-format.mjs +856 -0
- package/dist/doc-hygiene.mjs +279 -0
- package/dist/lib/cross-agent-cli.mjs +695 -0
- package/dist/lib/gh-rest.mjs +229 -0
- package/dist/lib/golden-onboarding.mjs +179 -0
- package/dist/lib/jev.mjs +334 -0
- package/dist/lib/log-branch.mjs +106 -0
- package/dist/lib/pmo-benchmarks.mjs +61 -0
- package/dist/lib/pmo-delivery.mjs +102 -0
- package/dist/lib/pmo-metrics.mjs +200 -0
- package/dist/lib/pmo-templates.mjs +132 -0
- package/dist/lib/pmo-window-log.mjs +99 -0
- package/dist/lib/project-root.mjs +78 -0
- package/dist/lib/prose-brief.mjs +191 -0
- package/dist/lib/prose-guard.mjs +761 -0
- package/dist/lib/prose-writer.mjs +410 -0
- package/dist/lib/report-registry.mjs +469 -0
- package/dist/lib/reporting-config.mjs +266 -0
- package/dist/lib/roadmap-contract.mjs +305 -0
- package/dist/lib/roadmap-status-buckets.mjs +20 -0
- package/dist/lib/standup-deck.mjs +120 -0
- package/dist/lib/telegram-format.mjs +91 -0
- package/dist/live-smoke.mjs +303 -0
- package/dist/pmo/benchmarks.json +77 -0
- package/dist/pmo/templates/metrics-sheet.md +27 -0
- package/dist/pmo/templates/monthly-stakeholder-packet.md +53 -0
- package/dist/pmo/templates/weekly-story-deck.md +66 -0
- package/dist/pmo-report.mjs +338 -0
- package/dist/preflight.mjs +444 -0
- package/dist/prose/cpo-persona.md +108 -0
- package/dist/prose/internal.task.md +48 -0
- package/dist/prose/standup.task.md +38 -0
- package/dist/prose/weekly.task.md +36 -0
- package/dist/prose-draft.mjs +198 -0
- package/dist/prose-lessons.md +59 -0
- package/dist/roadmap-extract.mjs +410 -0
- package/dist/standup/templates/daily-story-deck.md +46 -0
- package/dist/standup.mjs +634 -0
- package/dist/vercel-prune-previews.mjs +164 -0
- package/dist/weekly-recap.mjs +526 -0
- package/package.json +33 -1
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
weekly.task.md — the per-surface task block for the WEEKLY recap.
|
|
3
|
+
|
|
4
|
+
Higher altitude than standup.task.md: themes, not events. Composed after cpo-persona.md and the
|
|
5
|
+
accumulated lessons, before the evidence pack.
|
|
6
|
+
|
|
7
|
+
Written by the Claude Routine in-context (README D1).
|
|
8
|
+
|
|
9
|
+
Everything above the first `---` is stripped before sending.
|
|
10
|
+
-->
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Task: write the WEEKLY RECAP
|
|
15
|
+
|
|
16
|
+
For someone who was not watching this week and wants the through-line, not the log.
|
|
17
|
+
|
|
18
|
+
**Length: up to 160 words.** That is a budget, not a target — and it is emphatically **not**
|
|
19
|
+
permission to enumerate. A list of merges is not a report.
|
|
20
|
+
|
|
21
|
+
The question you are answering: **what is now possible that was not possible on Monday, and what is
|
|
22
|
+
still owed?**
|
|
23
|
+
|
|
24
|
+
- **Group by theme, never by commit or by repo.** Three changes that together made one flow work are
|
|
25
|
+
one theme and one sentence. Repos are an implementation detail the reader does not think in.
|
|
26
|
+
- **Lead with shipped and closed work.** The evidence pack gives you epics whose status flipped this
|
|
27
|
+
week, with their retrospectives — that is the highest-altitude product input available, written in
|
|
28
|
+
product language by the people who did the work. Use it before you touch commit subjects.
|
|
29
|
+
- **Name the decision that shaped the week**, if there was one. Something chosen over an obvious
|
|
30
|
+
alternative, something deliberately declined, something that turned out to be false and changed
|
|
31
|
+
the plan. A week usually has exactly one of these and it is usually the most valuable sentence in
|
|
32
|
+
the report.
|
|
33
|
+
- **State what is still owed** — unverified work, gaps a person has to close by hand, anything
|
|
34
|
+
built but not yet live. The owed ledger is sacred here: a weekly that quietly omits a known gap is
|
|
35
|
+
the one unforgivable error in this house.
|
|
36
|
+
- **A quiet week is a fact.** Report it in a sentence and stop.
|
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// prose-draft.mjs — delegate FILE-DERIVED internal prose to a cheaper, different-family model.
|
|
3
|
+
//
|
|
4
|
+
// The cross-agent rail (cross-review / cross-panel) proved the primitive: pipe context into a
|
|
5
|
+
// foreign CLI, get one advisory pass back. This extends it from *reviewing code* to *drafting
|
|
6
|
+
// prose* — but only for artifacts whose inputs are FILES (epic docs, sprint docs, git log), not
|
|
7
|
+
// the builder's in-head context. That's the honest scope boundary: a PR body is cheapest written
|
|
8
|
+
// by the agent that just built the PR; a retrospective/poster entry is a synthesis of docs that
|
|
9
|
+
// are already on disk, which a cheap fast model drafts well and the coordinating agent edits.
|
|
10
|
+
//
|
|
11
|
+
// Modes (v1):
|
|
12
|
+
// node scripts/prose-draft.mjs --kind retro --epic Roadmap/<area>/<epic-dir>
|
|
13
|
+
// node scripts/prose-draft.mjs --kind poster --epic Roadmap/<area>/<epic-dir>
|
|
14
|
+
// node scripts/prose-draft.mjs --kind sprint-wrap --sprint Roadmap/<area>/<epic>/sprint-N.md
|
|
15
|
+
//
|
|
16
|
+
// Output: the draft on stdout (synchronous flush — LEARNINGS pipe-truncation rule). The caller
|
|
17
|
+
// reviews/edits/commits; this tool NEVER writes repo files itself (editorial control stays with
|
|
18
|
+
// the coordinating agent — same "advisory only" stance as cross-review).
|
|
19
|
+
//
|
|
20
|
+
// Model: agy with a cheap-fast pair by default (prose doesn't need Pro-tier reasoning; the
|
|
21
|
+
// coordinating agent is the editor). Override via PROSE_MODEL / PROSE_FALLBACK_MODEL.
|
|
22
|
+
|
|
23
|
+
import { spawnSync } from 'node:child_process';
|
|
24
|
+
import { readFileSync, readdirSync, existsSync, writeSync } from 'node:fs';
|
|
25
|
+
import { join, dirname, resolve } from 'node:path';
|
|
26
|
+
import { fileURLToPath } from 'node:url';
|
|
27
|
+
import { hasCmd, loadPromptBody, die, need } from './lib/cross-agent-cli.mjs';
|
|
28
|
+
import { writeProse, buildWriterPrompt, loadLessons } from './lib/prose-writer.mjs';
|
|
29
|
+
import { projectAsset, projectRoot } from './lib/project-root.mjs';
|
|
30
|
+
|
|
31
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
32
|
+
const REPO_ROOT = projectRoot(); // D2
|
|
33
|
+
|
|
34
|
+
// The model/router lives in scripts/lib/prose-writer.mjs now — deliberately NOT here.
|
|
35
|
+
//
|
|
36
|
+
// This file used to default to `gemini-3.5-flash-high`. That is the exact constant a consuming
|
|
37
|
+
// project identified as having silently destroyed the register of every report it wrote: the accepted
|
|
38
|
+
// drafts had come from GPT-OSS on the occasions Gemini's quota was exhausted, so the "fallback" was
|
|
39
|
+
// actually the good writer. The rail now pins a single non-Gemini model and routes devin first. Do not
|
|
40
|
+
// reintroduce a model constant here.
|
|
41
|
+
|
|
42
|
+
export const KINDS = ['retro', 'poster', 'sprint-wrap'];
|
|
43
|
+
|
|
44
|
+
// ── mode task blocks (pure) ─────────────────────────────────────────────────────────────────
|
|
45
|
+
|
|
46
|
+
export function taskBlock(kind) {
|
|
47
|
+
switch (kind) {
|
|
48
|
+
case 'retro':
|
|
49
|
+
return `## Task: draft RETROSPECTIVE.md for the epic whose docs follow.
|
|
50
|
+
Shape (exactly these sections): a one-line header with the epic name + close date placeholder
|
|
51
|
+
(_Closed: YYYY-MM-DD_ — leave the placeholder if no date is in the sources); **## What shipped**
|
|
52
|
+
(per sprint, outcome-first, with PR/commit refs from the sources); **## What worked**;
|
|
53
|
+
**## What didn't / incidents** (plain, unsoftened); **## Gaps / follow-ups** (the owed ledger —
|
|
54
|
+
every unverified item, pending migration, human smoke); **## Durable learnings** (only the
|
|
55
|
+
*transferable* rules, each a bold one-liner + why + date/source, written so it can be pasted
|
|
56
|
+
into Roadmap/LEARNINGS.md — dedupe-aware: if a learning sharpens an existing well-known rule,
|
|
57
|
+
say "sharpens: <rule>"). Length budget: 60-90 lines.`;
|
|
58
|
+
case 'poster':
|
|
59
|
+
return `## Task: draft the product-poster entry for this epic (Roadmap/README.md house shape).
|
|
60
|
+
One dated bold-led bullet: **YYYY-MM-DD — <Epic name> (<n> sprint(s); <risk>, <surface>).**
|
|
61
|
+
followed by 3-8 sentences: the problem, what shipped (with PR refs), the load-bearing technical
|
|
62
|
+
decision(s), what's owed. Then one table-row line for the area README:
|
|
63
|
+
| [<slug>](<slug>/) | <one-line what> | ✅ **Shipped YYYY-MM-DD** (<refs + owed note>) |
|
|
64
|
+
Length budget: 12 lines max.`;
|
|
65
|
+
case 'sprint-wrap':
|
|
66
|
+
return `## Task: draft the sprint-wrap terminal summary (SESSION-KICKOFFS §7 shape) for the
|
|
67
|
+
sprint doc that follows. It is a THIN POINTER, never a re-summary: 4-8 lines — sprint name +
|
|
68
|
+
status, the one-line outcome per story (ref'd), the owed list, and "next:" with the next sprint
|
|
69
|
+
or close-out step. No headings, no code fences.`;
|
|
70
|
+
default:
|
|
71
|
+
return die(`unknown --kind "${kind}" (expected: ${KINDS.join(' | ')})`);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// ── source gathering (I/O, thin) ────────────────────────────────────────────────────────────
|
|
76
|
+
|
|
77
|
+
function read(p) {
|
|
78
|
+
return readFileSync(p, 'utf8');
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function gitLogFor(paths) {
|
|
82
|
+
// argv-array spawn, never a shell-built string — a path can't smuggle command substitution.
|
|
83
|
+
const r = spawnSync('git', ['log', '--oneline', '-20', '--', ...paths], {
|
|
84
|
+
cwd: REPO_ROOT,
|
|
85
|
+
encoding: 'utf8',
|
|
86
|
+
});
|
|
87
|
+
return r.status === 0 ? (r.stdout || '').trim() : '(git log unavailable)';
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function gatherEpicSources(epicDir, { readFile = read, listDir = readdirSync, log = gitLogFor } = {}) {
|
|
91
|
+
const files = listDir(epicDir)
|
|
92
|
+
.filter((f) => f.endsWith('.md'))
|
|
93
|
+
.sort();
|
|
94
|
+
if (!files.includes('README.md')) die(`${epicDir} has no README.md — is this an epic directory?`);
|
|
95
|
+
const parts = files.map((f) => `\n\n### FILE: ${f}\n\n${readFile(join(epicDir, f))}`);
|
|
96
|
+
return `## Source material — epic directory ${epicDir}${parts.join('')}\n\n### GIT LOG (epic paths)\n${log([epicDir])}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function gatherSprintSources(
|
|
100
|
+
sprintPath,
|
|
101
|
+
{ readFile = read, log = gitLogFor, exists = existsSync } = {}
|
|
102
|
+
) {
|
|
103
|
+
const epicDir = dirname(sprintPath);
|
|
104
|
+
const readme = join(epicDir, 'README.md');
|
|
105
|
+
let out = `## Source material — sprint doc ${sprintPath}\n\n### FILE: ${sprintPath}\n\n${readFile(sprintPath)}`;
|
|
106
|
+
if (exists(readme)) out += `\n\n### FILE (context): ${readme}\n\n${readFile(readme)}`;
|
|
107
|
+
return `${out}\n\n### GIT LOG (epic paths)\n${log([epicDir])}`;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ── prompt assembly (pure) ──────────────────────────────────────────────────────────────────
|
|
111
|
+
|
|
112
|
+
// The house voice now comes from the SHARED persona + the internal-artifact task file, so a change to
|
|
113
|
+
// the register reaches this surface and the two scheduled reports at once. What is genuinely local to
|
|
114
|
+
// this tool — its three mode shapes — lives in taskBlock. (The old prose-draft.prompt.md is retired;
|
|
115
|
+
// its internal-artifact rules moved into prose/internal.task.md.)
|
|
116
|
+
export function loadStylePrompt() {
|
|
117
|
+
return [
|
|
118
|
+
loadPromptBody(projectAsset('prose/cpo-persona.md')), // a TEMPLATE FILL-IN the project owns
|
|
119
|
+
loadPromptBody(join(__dirname, 'prose', 'internal.task.md')),
|
|
120
|
+
].join('\n\n');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function buildPrompt({ style, kind, sources, lessons = '' }) {
|
|
124
|
+
// buildWriterPrompt owns the style→lessons→task ordering for BOTH backends, so the lessons file
|
|
125
|
+
// cannot reach one surface and silently miss the other.
|
|
126
|
+
return buildWriterPrompt({ style, lessons, task: `${taskBlock(kind)}\n\n${sources}` });
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ── main ────────────────────────────────────────────────────────────────────────────────────
|
|
130
|
+
|
|
131
|
+
async function main() {
|
|
132
|
+
const args = process.argv.slice(2);
|
|
133
|
+
let kind, epic, sprint;
|
|
134
|
+
for (let i = 0; i < args.length; i++) {
|
|
135
|
+
if (args[i] === '--kind') kind = need(args[++i], '--kind');
|
|
136
|
+
else if (args[i] === '--epic') epic = need(args[++i], '--epic');
|
|
137
|
+
else if (args[i] === '--sprint') sprint = need(args[++i], '--sprint');
|
|
138
|
+
else die(`unknown arg ${args[i]}`);
|
|
139
|
+
}
|
|
140
|
+
if (!kind || !KINDS.includes(kind)) die(`--kind is required (${KINDS.join(' | ')})`);
|
|
141
|
+
|
|
142
|
+
if (!hasCmd('devin') && !hasCmd('agy')) {
|
|
143
|
+
die('no prose writer available — this rail needs devin or agy on PATH (see scripts/README.md).');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let sources;
|
|
147
|
+
if (kind === 'sprint-wrap') {
|
|
148
|
+
if (!sprint) die('--sprint <path/to/sprint-N.md> is required for --kind sprint-wrap');
|
|
149
|
+
sources = gatherSprintSources(resolve(REPO_ROOT, sprint));
|
|
150
|
+
} else {
|
|
151
|
+
if (!epic) die(`--epic <path/to/epic-dir> is required for --kind ${kind}`);
|
|
152
|
+
sources = gatherEpicSources(resolve(REPO_ROOT, epic));
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const prompt = buildPrompt({ style: loadStylePrompt(), kind, sources, lessons: loadLessons() });
|
|
156
|
+
|
|
157
|
+
// These are INTERNAL engineering artifacts, so two guard rules that fit a chat-message report would
|
|
158
|
+
// misfire here: refs/dates are required (not invented), and the length budget is a document, not 60
|
|
159
|
+
// words. `allowsBeneficiary` stays TRUE because a retro legitimately discusses merchants and buyers.
|
|
160
|
+
// The size cap is gone with the argv path — devin takes the prompt in a file.
|
|
161
|
+
const result = await writeProse({
|
|
162
|
+
prompt,
|
|
163
|
+
evidence: {
|
|
164
|
+
allowsFixClaim: true,
|
|
165
|
+
allowsBeneficiary: true,
|
|
166
|
+
allowsMarkdown: true,
|
|
167
|
+
maxWords: 4000,
|
|
168
|
+
minWords: 40,
|
|
169
|
+
},
|
|
170
|
+
});
|
|
171
|
+
if (!result.text) die(result.error || 'no prose writer produced a draft.');
|
|
172
|
+
|
|
173
|
+
// Advisory banner: names the writer AND the model that actually ran, plus whether the guard passed
|
|
174
|
+
// clean — so a paste-without-reading stays self-identifying in review.
|
|
175
|
+
const decided = result.guard?.decider ? ` by ${result.guard.decider}` : '';
|
|
176
|
+
const verdict = result.ok
|
|
177
|
+
? `guard: clean${decided}`
|
|
178
|
+
: `guard: FLAGGED${decided} (${result.guard.findings.map((f) => f.code).join(', ')})`;
|
|
179
|
+
writeSync(
|
|
180
|
+
1,
|
|
181
|
+
`<!-- draft: prose-draft.mjs --kind ${kind} · writer ${result.writer}/${result.model} · ${verdict} · EDIT BEFORE COMMITTING -->\n${result.text}\n`
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
if (!result.ok) {
|
|
185
|
+
process.stderr.write(
|
|
186
|
+
`\n⚠ The guard rejected this draft and it did NOT converge after a revision pass:\n` +
|
|
187
|
+
result.guard.findings.map((f) => ` · ${f.code}: ${f.note}`).join('\n') +
|
|
188
|
+
`\n Read it carefully before committing.\n\n`
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const isMain = process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url);
|
|
194
|
+
if (isMain)
|
|
195
|
+
main().catch((e) => {
|
|
196
|
+
process.stderr.write(`prose-draft: ${e?.message || e}\n`);
|
|
197
|
+
process.exit(1);
|
|
198
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
prose-lessons.md — accumulated corrections, injected into EVERY writer prompt on BOTH backends
|
|
3
|
+
(scripts/lib/prose-writer.mjs's loadLessons, and the routine brief for the standup/weekly).
|
|
4
|
+
|
|
5
|
+
HOW TO USE THIS FILE
|
|
6
|
+
Every line below is a mistake a real draft actually made and a human had to catch. When you catch a
|
|
7
|
+
new one, add a line — that is the whole mechanism by which this rail gets better. Two rules:
|
|
8
|
+
|
|
9
|
+
1. Quote the ACTUAL bad sentence. A rule stated abstractly ("be accurate") changes nothing; the
|
|
10
|
+
verbatim failure is what a model can pattern-match against.
|
|
11
|
+
2. If the failure is mechanically detectable, ALSO add it to scripts/lib/prose-guard.mjs with a
|
|
12
|
+
test. A lesson reduces a mistake; a guard catches it. Prefer both.
|
|
13
|
+
|
|
14
|
+
Keep it short. A long file dilutes every line in it, and the guard is where hard enforcement lives.
|
|
15
|
+
|
|
16
|
+
Provenance: the first six lessons are inherited from golden-beans, where they were measured on a
|
|
17
|
+
live rail rather than imagined — a cheap model given a dense engineering commit produced material
|
|
18
|
+
falsehoods on two of its first three runs. They are kept verbatim because the failures they
|
|
19
|
+
describe are model behaviour, not repo-specific. The flag-state lesson is ours.
|
|
20
|
+
-->
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
**Never invent a beneficiary.** A real draft wrote _"Tenants now benefit from a new pure-logic
|
|
25
|
+
unit-test suite"_ about internal test tooling. Tenants do not run our tests. If a change is
|
|
26
|
+
internal, say so plainly and name the real beneficiary — usually whoever builds here next — and the
|
|
27
|
+
real effect: a bug class that can no longer reach production unnoticed, a mistake caught in seconds
|
|
28
|
+
instead of after a deploy.
|
|
29
|
+
|
|
30
|
+
**Never report a fix this change did not make.** A real draft wrote _"the previous backslash bypass
|
|
31
|
+
is blocked, eliminating a potential open-redirect attack"_ about a commit that only ADDED TESTS for
|
|
32
|
+
a fix shipped weeks earlier. Commit messages and roadmap docs here routinely cite a past incident to
|
|
33
|
+
explain why present work matters — that is context, never the outcome. If the verb belongs to an
|
|
34
|
+
earlier change, it is background.
|
|
35
|
+
|
|
36
|
+
**Never claim a capability is live when its flag is off.** In the project these lessons were first
|
|
37
|
+
learned in, five flag-gated epics shipped in a fortnight, every one of them born OFF. "Accounts can
|
|
38
|
+
now be handed to a partner" is FALSE when `partner_handoff_enabled` is off — the correct sentence is that
|
|
39
|
+
the rail is built and stays dark until the flag is flipped. This is our highest-risk falsehood
|
|
40
|
+
because it reads as good news and nobody checks a flag before believing a report.
|
|
41
|
+
|
|
42
|
+
**Never invent a deadline, a due date or a sign-off.** A real standup draft ended _"design sign-off
|
|
43
|
+
on the Sprint 2 layout is owed before tomorrow"_ — no such commitment existed anywhere in the source
|
|
44
|
+
data. Commits and roadmap docs contain no deadlines, so any date you attach is fabricated by
|
|
45
|
+
construction. This is the most corrosive mistake on this list: it reads as perfectly ordinary
|
|
46
|
+
standup language and it makes people chase work nobody agreed to. If something is genuinely owed,
|
|
47
|
+
name what and to whom, with no date.
|
|
48
|
+
|
|
49
|
+
**Never name tools, frameworks or models.** Real drafts leaked _"Playwright"_, _"Gemini"_ and
|
|
50
|
+
_"Antigravity"_ despite the brief forbidding it. The reader either knows the stack or does not need
|
|
51
|
+
it. Describe the effect, not the machinery.
|
|
52
|
+
|
|
53
|
+
**Finish the last sentence.** A real draft ended _"…broader error handling is planned"_ mid-thought
|
|
54
|
+
after running out of room. A trailing fragment reads as a broken tool, not as brevity. Plan the
|
|
55
|
+
ending before starting it; stop at the previous full stop instead.
|
|
56
|
+
|
|
57
|
+
**Say "no user-visible effect" when that is the truth.** It is a correct and useful report. Reaching
|
|
58
|
+
for a vague benefit to avoid an honest "this is internal" is the failure that produces every other
|
|
59
|
+
one on this list.
|