kimal3 0.6.5 → 0.8.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 +10 -4
- package/claude-plugins/.claude-plugin/marketplace.json +2 -1
- package/claude-plugins/README.md +2 -0
- package/claude-plugins/kimal3-logo/.claude-plugin/plugin.json +1 -1
- package/claude-plugins/kimal3-logo/hooks/app-oeffnen.ts +37 -9
- package/claude-plugins/kimal3-logo/hooks/register.tsx +1 -1
- package/claude-plugins/kimal3-plan/.claude-plugin/plugin.json +7 -0
- package/claude-plugins/kimal3-plan/hooks/erinnerung.ts +83 -0
- package/claude-plugins/kimal3-plan/hooks/fehlerzeile.ts +41 -0
- package/claude-plugins/kimal3-plan/hooks/hooks.json +1 -0
- package/claude-plugins/kimal3-plan/hooks/lauf.ts +143 -0
- package/claude-plugins/kimal3-plan/hooks/lebenszeichen.ts +197 -0
- package/claude-plugins/kimal3-plan/hooks/ordner.ts +141 -0
- package/claude-plugins/kimal3-plan/hooks/register.tsx +22 -0
- package/claude-plugins/kimal3-plan/hooks/spec-text.ts +45 -0
- package/claude-plugins/kimal3-plan/types/index.d.ts +10 -0
- package/coach/de/kimal3-coach.md +41 -1
- package/coach/en/kimal3-coach.md +41 -1
- package/dist/argumente.js +12 -0
- package/dist/atomar.js +22 -0
- package/dist/befehle/einrichten.js +4 -4
- package/dist/befehle/login.js +2 -2
- package/dist/befehle/plan-hoch.js +8 -0
- package/dist/befehle/plan-melde.js +105 -0
- package/dist/befehle/plan-senden.js +149 -0
- package/dist/befehle/plan.js +45 -10
- package/dist/befehle/projekt-verbinden.js +141 -0
- package/dist/befehle/skill-install.js +2 -2
- package/dist/befehle-katalog-en.js +6 -6
- package/dist/befehle-katalog.js +8 -7
- package/dist/befehlswoerter.js +9 -0
- package/dist/einstellungen-schreiben.js +1 -1
- package/dist/kimal3.js +22 -0
- package/dist/meldungen-speicher.js +189 -0
- package/dist/{logo-ablegen.js → mods-ablegen.js} +29 -18
- package/dist/plugins-mischen.js +7 -2
- package/dist/projekt-ordner.js +189 -0
- package/dist/texte/einrichten.js +3 -1
- package/dist/texte/meldungen.js +127 -0
- package/dist/texte/plan.js +42 -22
- package/dist/texte/skill.js +12 -12
- package/package.json +1 -1
- package/skill/commands/k3feierabend.md +29 -0
- package/skill/commands/k3plan.md +167 -213
- package/skill/en/commands/k3plan.md +174 -222
- package/skill/en/commands/k3show.md +3 -1
- package/skill/en/commands/k3wrapup.md +28 -0
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
import type { EngineInterface } from 'claude-code'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Zu welchem Projekt in der App gehoert der Ordner, in dem Claude laeuft? (Spec 2026-10-23-brett-in-echtzeit-design.md,
|
|
5
|
+
* 1.4, 1.7, S-27 bis S-31). DIESELBE Regel wie `cli/src/projekt-ordner.ts` -- der Mod kann `cli/src` nicht importieren,
|
|
6
|
+
* deshalb steht sie hier ein zweites Mal. Den Gleichlauf haelt die gemeinsame Tabelle `scripts/projektordner-faelle.mts`:
|
|
7
|
+
* `check:cli` prueft die CLI, `check:plan-mod` diesen Mod gegen dieselben Faelle. Aendert sich die Regel, an BEIDEN
|
|
8
|
+
* Stellen und in der Tabelle.
|
|
9
|
+
*
|
|
10
|
+
* Einziger Unterschied: Die Platte fragt der Mod ueber `$.fs`, also asynchron -- `gibt` und `lies` sind hier Promises.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export type Ordnerzettel = { format: 1; projekt_id: string; titel: string }
|
|
14
|
+
export type Ordnerlage =
|
|
15
|
+
| { art: 'projekt'; ordner: string; name: string; zettel: Ordnerzettel | null; ausStandJson: boolean }
|
|
16
|
+
| { art: 'kein-projektordner' }
|
|
17
|
+
type Gibt = (pfad: string) => Promise<boolean>
|
|
18
|
+
type Lies = (pfad: string) => Promise<string | null>
|
|
19
|
+
|
|
20
|
+
const ZETTEL_PFAD = 'plan/projekt.json'
|
|
21
|
+
const STAND_PFAD = 'plan/stand.json'
|
|
22
|
+
const BEISPIELE: readonly string[] = ['beispiel-rechnungen-erfassen', 'example-capture-invoices']
|
|
23
|
+
const PROJEKTORDNER: readonly string[] = ['projekte', 'projects']
|
|
24
|
+
const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i
|
|
25
|
+
|
|
26
|
+
export const normiert = (pfad: string) => pfad.replace(/\\/g, '/').replace(/\/+$/, '')
|
|
27
|
+
const nachOben = (pfad: string) => {
|
|
28
|
+
const teile = normiert(pfad).split('/')
|
|
29
|
+
return teile.map((_, i) => teile.slice(0, teile.length - i).join('/')).filter(p => p !== '')
|
|
30
|
+
}
|
|
31
|
+
const istWerkbank = (ordner: string) => ordner.startsWith('werkbank-') || ordner.startsWith('workbench-')
|
|
32
|
+
|
|
33
|
+
/** `werkbank-x/projekte/<name>` irgendwo im Pfad -- der aeusserste Treffer, damit `.claude/worktrees/x` nicht zaehlt. */
|
|
34
|
+
function projektordnerIn(pfad: string): { ordner: string; name: string } | null {
|
|
35
|
+
const teile = normiert(pfad).split('/')
|
|
36
|
+
for (let i = 0; i + 2 < teile.length; i++) {
|
|
37
|
+
if (!istWerkbank(teile[i] ?? '') || !PROJEKTORDNER.includes(teile[i + 1] ?? '')) continue
|
|
38
|
+
const name = teile[i + 2] ?? ''
|
|
39
|
+
return name === '' || BEISPIELE.includes(name) ? null : { ordner: teile.slice(0, i + 3).join('/'), name }
|
|
40
|
+
}
|
|
41
|
+
return null
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function objektAus(text: string | null): Record<string, unknown> | null {
|
|
45
|
+
if (text === null) return null
|
|
46
|
+
try {
|
|
47
|
+
const j: unknown = JSON.parse(text.replace(/^\uFEFF/, ''))
|
|
48
|
+
return j !== null && typeof j === 'object' && !Array.isArray(j) ? (j as Record<string, unknown>) : null
|
|
49
|
+
} catch {
|
|
50
|
+
return null
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Der Zettel, wenn er gueltig ist -- sonst `null` (kein JSON, andere Formatnummer, keine Projektnummer, kein Titel). */
|
|
55
|
+
export function zettelAus(text: string | null): Ordnerzettel | null {
|
|
56
|
+
const o = objektAus(text)
|
|
57
|
+
if (o === null || o.format !== 1 || typeof o.projekt_id !== 'string' || !UUID.test(o.projekt_id)) return null
|
|
58
|
+
if (typeof o.titel !== 'string' || o.titel.trim() === '') return null
|
|
59
|
+
return { format: 1, projekt_id: o.projekt_id, titel: o.titel }
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Aus einer alten `plan/stand.json` nur die Nummer (und der Titel, falls er dasteht). */
|
|
63
|
+
function zettelAusStand(text: string | null, name: string): Ordnerzettel | null {
|
|
64
|
+
const o = objektAus(text)
|
|
65
|
+
if (o === null || typeof o.projekt_id !== 'string' || !UUID.test(o.projekt_id)) return null
|
|
66
|
+
const titel = typeof o.projekt === 'string' && o.projekt.trim() !== '' ? o.projekt : name
|
|
67
|
+
return { format: 1, projekt_id: o.projekt_id, titel }
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/** `.` und `..` in einem Pfad mit `/` aufloesen, ohne die Platte zu fragen (die Wurzel bleibt stehen). */
|
|
71
|
+
function aufgeloest(pfad: string): string {
|
|
72
|
+
const teile: string[] = []
|
|
73
|
+
for (const t of pfad.split('/')) {
|
|
74
|
+
if (t === '.' || (t === '' && teile.length > 0)) continue
|
|
75
|
+
if (t === '..' && teile.length > 1) teile.pop()
|
|
76
|
+
else if (t !== '..') teile.push(t)
|
|
77
|
+
}
|
|
78
|
+
return teile.join('/')
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function naechsteGitWurzel(cwd: string, gibt: Gibt): Promise<string | undefined> {
|
|
82
|
+
for (const o of nachOben(cwd)) if (await gibt(`${o}/.git`)) return o
|
|
83
|
+
return undefined
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Der Hauptordner einer Git-Arbeitskopie (die naechste `.git` ueber `cwd` ist eine Datei `gitdir: …/.git/worktrees/x`). */
|
|
87
|
+
export async function hauptordnerFuer(cwd: string, gibt: Gibt, lies: Lies): Promise<string | null> {
|
|
88
|
+
const wurzel = await naechsteGitWurzel(cwd, gibt)
|
|
89
|
+
if (wurzel === undefined) return null
|
|
90
|
+
const text = await lies(`${wurzel}/.git`)
|
|
91
|
+
const treffer = text === null ? null : /^gitdir:\s*(.+?)[\\/]\.git[\\/]worktrees[\\/][^\\/]+\s*$/.exec(text.trim())
|
|
92
|
+
if (treffer === null) return null
|
|
93
|
+
const ziel = normiert(treffer[1] ?? '')
|
|
94
|
+
return /^([A-Za-z]:)?\//.test(ziel) ? ziel : aufgeloest(`${wurzel}/${ziel}`)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export async function ordnerlage(cwd: string, gibt: Gibt, lies: Lies, hauptordner: string | null): Promise<Ordnerlage> {
|
|
98
|
+
// In einer Arbeitskopie zaehlt das Projekt des Hauptordners; der Zettel gehoert in ihre eigene Wurzel.
|
|
99
|
+
const projekt = projektordnerIn(hauptordner ?? cwd)
|
|
100
|
+
if (projekt === null) return { art: 'kein-projektordner' }
|
|
101
|
+
const ordner = hauptordner === null ? projekt.ordner : (await naechsteGitWurzel(cwd, gibt)) ?? projekt.ordner
|
|
102
|
+
const orte = ordner === projekt.ordner ? [ordner] : [ordner, projekt.ordner]
|
|
103
|
+
for (const o of orte) {
|
|
104
|
+
const zettel = zettelAus(await lies(`${o}/${ZETTEL_PFAD}`))
|
|
105
|
+
if (zettel !== null) return { art: 'projekt', ordner, name: projekt.name, zettel, ausStandJson: false }
|
|
106
|
+
}
|
|
107
|
+
for (const o of orte) {
|
|
108
|
+
const zettel = zettelAusStand(await lies(`${o}/${STAND_PFAD}`), projekt.name)
|
|
109
|
+
if (zettel !== null) return { art: 'projekt', ordner, name: projekt.name, zettel, ausStandJson: true }
|
|
110
|
+
}
|
|
111
|
+
return { art: 'projekt', ordner, name: projekt.name, zettel: null, ausStandJson: false }
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ── Platte über `$.fs` ──────────────────────────────────────────────────────────────────────────────────────────────
|
|
115
|
+
|
|
116
|
+
/** Liest eine Datei; `null`, wenn sie fehlt oder ein Ordner ist (`.git` eines normalen Projekts). */
|
|
117
|
+
export async function liesDatei($: EngineInterface, pfad: string): Promise<string | null> {
|
|
118
|
+
try {
|
|
119
|
+
return await $.fs.read(pfad)
|
|
120
|
+
} catch {
|
|
121
|
+
return null
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Die Lage des Ordners, in dem die Sitzung laeuft. */
|
|
126
|
+
export async function lageJetzt($: EngineInterface): Promise<Ordnerlage> {
|
|
127
|
+
const cwd = normiert(await $.session.cwd())
|
|
128
|
+
const gibt: Gibt = p => $.fs.exists(p).catch(() => false)
|
|
129
|
+
const lies: Lies = p => liesDatei($, p)
|
|
130
|
+
return ordnerlage(cwd, gibt, lies, await hauptordnerFuer(cwd, gibt, lies))
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Der Pfad einer Datei relativ zum Projektordner (`docs/x.md`), oder `null`, wenn sie nicht darin liegt.
|
|
135
|
+
* Gross- und Kleinschreibung zaehlt nicht (Windows); ein relativer Pfad gilt ab dem Startordner der Sitzung.
|
|
136
|
+
*/
|
|
137
|
+
export function relativImOrdner(datei: string, ordner: string, cwd: string): string | null {
|
|
138
|
+
const absolut = /^([A-Za-z]:)?[\\/]/.test(datei) ? normiert(datei) : aufgeloest(`${normiert(cwd)}/${normiert(datei)}`)
|
|
139
|
+
const basis = `${normiert(ordner)}/`
|
|
140
|
+
return absolut.toLowerCase().startsWith(basis.toLowerCase()) && absolut.length > basis.length ? absolut.slice(basis.length) : null
|
|
141
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Register } from 'claude-code'
|
|
2
|
+
import { erinnerung } from './erinnerung'
|
|
3
|
+
import { neuerLauf } from './lauf'
|
|
4
|
+
import { lebenszeichen } from './lebenszeichen'
|
|
5
|
+
import { specText } from './spec-text'
|
|
6
|
+
|
|
7
|
+
// Der Mod `kimal3-plan` (Spec docs/superpowers/specs/2026-10-23-brett-in-echtzeit-design.md, 1.3): haelt das Board der
|
|
8
|
+
// Projektseite aktuell. Drei Aufgaben, je eine Datei: Punkt am Leben halten (lebenszeichen.ts), Spec-Texte hochladen
|
|
9
|
+
// (spec-text.ts), Claude ans Melden erinnern (erinnerung.ts). Dem Menschen sagt er nur etwas, wenn etwas nicht klappt --
|
|
10
|
+
// eine Zeile in der Statuszeile (fehlerzeile.ts). Er startet kein `kimal3` (`$.process` gibt es in der Claude-App nicht)
|
|
11
|
+
// und spricht selbst mit der App, mit dem Geraeteschluessel aus `~/.kimal3/anmeldung.json`.
|
|
12
|
+
|
|
13
|
+
// Die Sprache, wenn `~/.kimal3/sprache.json` fehlt (S-23). `kimal3 einrichten` setzt diese Zeile beim Ablegen in
|
|
14
|
+
// `~/.kimal3/claude-plugins` (`cli/src/mods-ablegen.ts`, `mitSprache`).
|
|
15
|
+
const SPRACHE: 'de' | 'en' = 'de'
|
|
16
|
+
|
|
17
|
+
export const register: Register = on => {
|
|
18
|
+
const lauf = neuerLauf(SPRACHE)
|
|
19
|
+
lebenszeichen(on, lauf)
|
|
20
|
+
erinnerung(on, lauf)
|
|
21
|
+
specText(on, lauf)
|
|
22
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { EngineInterface, On } from 'claude-code'
|
|
2
|
+
import { dateiAus } from './erinnerung'
|
|
3
|
+
import { fehlerSetzen } from './fehlerzeile'
|
|
4
|
+
import { anApp, liesAusweis, type Lauf } from './lauf'
|
|
5
|
+
import { liesDatei, normiert, relativImOrdner } from './ordner'
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Spec-Texte hochladen (Spec 1.3 Aufgabe 1, S-18): Schreibt oder aendert Claude eine `.md`-Datei im Projektordner, laedt
|
|
9
|
+
* der Mod ihren Text hoch (`POST /api/mod/spec-text`), damit die Lesefassung in der App stimmt, ohne dass jemand tippt.
|
|
10
|
+
* 🔑 Hochgeladen wird NUR eine Datei, die die App als Spec des Projekts kennt: Das Lebenszeichen nennt die Liste
|
|
11
|
+
* (`specDateien`, wie `datei` im Stand). Jede andere `.md` -- Notizen, README, Entwuerfe -- verlaesst den Rechner nicht
|
|
12
|
+
* (Datensparsamkeit, Ruling der Fixrunde). Eine brandneue Spec kommt mit ihrer Spec-Meldung (`kimal3 plan melde`).
|
|
13
|
+
* Sagt die App trotzdem `204` (keine Spec), ist das Erfolg ohne Wirkung. Der Pfad geht RELATIV zum Projektordner mit.
|
|
14
|
+
*
|
|
15
|
+
* Hochgeladen wird nach dem Werkzeug und ohne Claude darauf warten zu lassen (`$.clock.after(0, …)`). Was per Skript
|
|
16
|
+
* entsteht (Bash, Python), sieht der Mod nicht; das kommt mit `/k3plan` (S-19).
|
|
17
|
+
*/
|
|
18
|
+
export function specText(on: On, lauf: Lauf): void {
|
|
19
|
+
on('tool.call', { tool: ['Write', 'Edit'] }, async ($, e, next) => {
|
|
20
|
+
const ergebnis = await next(e)
|
|
21
|
+
if (ergebnis.deny !== undefined || ergebnis.isError === true) return ergebnis
|
|
22
|
+
const datei = dateiAus(e)
|
|
23
|
+
const lage = lauf.lage
|
|
24
|
+
if (datei === null || !datei.toLowerCase().endsWith('.md') || lage.art !== 'projekt' || lage.zettel === null) return ergebnis
|
|
25
|
+
const pfad = relativImOrdner(datei, lage.ordner, await $.session.cwd())
|
|
26
|
+
if (pfad === null || !lauf.specDateien.includes(pfad)) return ergebnis
|
|
27
|
+
const projektId = lage.zettel.projekt_id
|
|
28
|
+
$.clock.after(0, () => void hochladen($, lauf, `${normiert(lage.ordner)}/${pfad}`, pfad, projektId))
|
|
29
|
+
return ergebnis
|
|
30
|
+
})
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
async function hochladen($: EngineInterface, lauf: Lauf, datei: string, pfad: string, projektId: string): Promise<void> {
|
|
34
|
+
const text = await liesDatei($, datei)
|
|
35
|
+
const ausweis = await liesAusweis($, lauf)
|
|
36
|
+
// Ohne Ausweis meldet sich die Statuszeile schon ueber das Lebenszeichen (S-11, S-22).
|
|
37
|
+
if (text === null || 'fehler' in ausweis) return
|
|
38
|
+
const antwort = await anApp($, lauf, '/api/mod/spec-text', ausweis.schluessel, { projektId, pfad, text })
|
|
39
|
+
if (antwort?.status === 200 || antwort?.status === 204) return fehlerSetzen($, lauf, { 'nicht-angemeldet': false, 'nicht-erreichbar': false })
|
|
40
|
+
if (antwort?.status === 401) return fehlerSetzen($, lauf, { 'nicht-angemeldet': true })
|
|
41
|
+
if (antwort === null || antwort.status >= 500) return fehlerSetzen($, lauf, { 'nicht-erreichbar': true })
|
|
42
|
+
// `413` (zu gross), `403`, `400`: Die App war da und hat abgelehnt -- kein Fall fuer die Statuszeile, der Text kommt
|
|
43
|
+
// mit `/k3plan`. Nur ins Debug-Protokoll.
|
|
44
|
+
$.ui.log(`kimal3-plan: Spec-Text ${pfad} nicht hochgeladen (${antwort.status})`, { to: 'debug' })
|
|
45
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
declare module 'claude-code' {
|
|
2
|
+
interface PluginState {
|
|
3
|
+
'kimal3-plan': {
|
|
4
|
+
// Wann Claude zuletzt ans Melden erinnert wurde (`$.clock.now()`), `null`: noch nie in dieser Sitzung (hooks/erinnerung.ts, S-26).
|
|
5
|
+
letzteErinnerung: number | null
|
|
6
|
+
// Der Anstoss „Projekt anlegen“ ging in dieser Sitzung schon an Claude (hooks/erinnerung.ts, US-8).
|
|
7
|
+
anstossGegeben: boolean
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
}
|
package/coach/de/kimal3-coach.md
CHANGED
|
@@ -88,7 +88,12 @@ nutze sie, statt vorschnell loszulegen.
|
|
|
88
88
|
|
|
89
89
|
### 3.3 Planen, jetzt konkret
|
|
90
90
|
- Was genau gebaut wird, in welchen Schritten, woran jeder Schritt geprüft wird. Bei Größerem schriftlich: eine Spec,
|
|
91
|
-
also ein Plan mit Nutzergeschichten
|
|
91
|
+
also ein Plan mit Nutzergeschichten. Jede Story steht in vier Kisten, immer in dieser Reihenfolge:
|
|
92
|
+
**User Story** („Als … will ich … damit …“; der Titel nennt das Ziel des Menschen, nicht das Ergebnis) ·
|
|
93
|
+
**Akzeptanzkriterium** (vier Zeilen: *X hat …* · *X tut …* · *Dann muss …* · *Gegenprobe: …* — ein Kriterium, das nicht
|
|
94
|
+
scheitern kann, ist keins) · **So testest du es** (nummerierte Schritte für jemanden ohne Technik: wo, was tun, was dann
|
|
95
|
+
zu sehen ist; darunter die Zeile „Passt nicht, wenn: …“; ein Befehl nur mit Ort, in Backticks) · **Technische Notiz**
|
|
96
|
+
(wo die Sache wohnt, was sie trägt — der Mensch muss sie nicht lesen). Echte Zahlen, echter Text, ein Name statt „der Nutzer“.
|
|
92
97
|
- **Ein abgesegneter Plan rechtfertigt nichts, was frische Messungen widerlegen.** Nie „der Plan sagt, das ist
|
|
93
98
|
hinnehmbar" als Begründung — sondern neu beantworten: *Was ist mit dem, was ich JETZT weiß, das Beste?* Weicht es
|
|
94
99
|
ab: laut sagen, Plan nachziehen.
|
|
@@ -136,6 +141,41 @@ nutze sie, statt vorschnell loszulegen.
|
|
|
136
141
|
- **Sag von selbst, welche Schritte du ausgelassen hast** — auch einen, den ein Skill vorschreibt. Es wird danach
|
|
137
142
|
gefragt, und aufgeflogen wiegt es schwerer als gesagt.
|
|
138
143
|
|
|
144
|
+
### 3.6 Das Board — wann du der Projektseite meldest
|
|
145
|
+
Gilt in jedem Projektordner (ein Ordner direkt in `werkbank-…/projekte/`). Anderswo meldest du nichts und fragst nichts.
|
|
146
|
+
Das Board in der App zeigt nur, was du meldest: Wer nicht meldet, lässt es stundenlang „Offen“ zeigen, während gebaut wird.
|
|
147
|
+
- **Das Projekt gibt es einmal je Ordner:** `kimal3 projekt verbinden`. Es schweigt (`0`), wenn der Ordner schon verbunden ist.
|
|
148
|
+
Legt es ein Projekt an, gibst du den **einen** Satz weiter, den es ausgibt („Ich habe das Projekt ‚…‘ in der App angelegt.“).
|
|
149
|
+
Gibt es zwei gleichnamige Projekte oder wurde es in der App gelöscht, fragt es: Frag den Menschen und wähle nie selbst; `--neu`
|
|
150
|
+
gibt es nur nach seinem Ja. Fehlt das Erfolgsprofil, sag den Satz einmal, arbeite weiter und melde nichts.
|
|
151
|
+
- **Du meldest zu sechs Zeitpunkten**, jedes Mal mit `kimal3 plan melde` (Eingabe: eine Meldung als JSON oder eine Liste,
|
|
152
|
+
z. B. `echo '[…]' | kimal3 plan melde`; Zeit und Sitzung setzt das Programm). Die Schlüssel von Spec und Story liest du mit
|
|
153
|
+
`kimal3 plan` aus der App — erfinde keine.
|
|
154
|
+
1. **Spec angelegt oder ihre Stories geändert:**
|
|
155
|
+
`{"art":"spec","schluessel":"2026-10-24-testmonat","titel":"Testmonat","datei":"docs/superpowers/specs/2026-10-24-testmonat-design.md","stories":[{"schluessel":"US-1","titel":"Der Testmonat startet"}]}`
|
|
156
|
+
2. **Der Bau einer Spec beginnt:** die Stories, die jetzt gebaut werden, auf „in Arbeit“ — **bevor** du den ersten Bau-Schritt oder
|
|
157
|
+
einen Subagenten losschickst:
|
|
158
|
+
`{"art":"story","spec":"2026-10-24-testmonat","story":"US-1","stand":"in_arbeit"}`
|
|
159
|
+
3. **Eine Story ist gebaut und ihre Gegenproben sind gelaufen:** „gebaut“ mit Kriterien und dem `testen`-Text. Den Text nimmst du aus der Kiste „So testest du es“ der Story, wörtlich. Er ist eine Anleitung für jemanden ohne Technik: **eine Zeile je Schritt** (wo man es findet, was man tut, was dann zu sehen ist), **ohne** Kistenüberschrift, und **zuletzt** die Zeile „Passt nicht, wenn: …“. Die Zeilen trennst du im JSON mit `\n` — ein echter Zeilenumbruch im Text bricht das JSON, und `kimal3 plan melde` liest die Meldung nicht. Höchstens 1000 Zeichen: ist er länger, kürze die Schritte, nie mitten im Satz abschneiden. Ein Befehl nur, wenn es keinen anderen Weg gibt — dann sagst du, wo man ihn eingibt (in Claude Code im Projektordner, mit `!` davor), und setzt ihn in Backticks.
|
|
160
|
+
Beispiel:
|
|
161
|
+
`{"art":"story","spec":"2026-10-24-testmonat","story":"US-1","stand":"gebaut","kriterien":[{"text":"Startet Sabine am 1.11., endet er am 30.11.","gegenprobe":"bestanden"}],"testen":"1. Öffne in der App ‚Testmonat‘.\n2. Klick „Starten“.\n3. Darunter steht ‚Endet am 30.11.‘.\nPasst nicht, wenn: dort ein anderes Datum steht."}`
|
|
162
|
+
4. **Etwas fällt auf, das keine Spec verspricht:** `{"art":"thema","schluessel":"T-4","text":"Der Kalender könnte Feiertage zeigen"}`
|
|
163
|
+
5. **`/k3plan`:** Specs gegen den Code prüfen und jede Abweichung einzeln melden — dort steht, wie. **Prüfen ist Arbeit:** Eine Story mit offenen Gegenproben (Spalte „Claude-Test offen“), die du gegen den Code prüfst, meldest du vorher als „in Arbeit“. Stories in „Dein Test“ oder „Fertig“ meldest du beim Abgleich nicht neu.
|
|
164
|
+
6. **Feierabend:** den Tag abgleichen — dort steht, wie.
|
|
165
|
+
|
|
166
|
+
🔑 **Bleibt eine Story unfertig, wenn du aufhörst** (am Ende von `/k3plan` oder im Feierabend), meldest du sie mit `--ohne-sitzung` (`kimal3 plan melde --ohne-sitzung`): Ihr Plan-Stand bleibt `in_arbeit`, aber der Punkt am Projekt leuchtet nicht weiter, obwohl das Fenster offen bleibt — auf dem Board steht sie dann in „Offen“ als angefangen.
|
|
167
|
+
|
|
168
|
+
„Gebaut“ meldest du erst nach einer Gegenprobe, die in dieser Sitzung gelaufen ist, nie aus dem Gedächtnis.
|
|
169
|
+
- **Subagenten melden nie.** Der Mod erinnert nur dich, nicht sie: Du meldest „in Arbeit“ **vor** dem Auftrag an einen Subagenten, der
|
|
170
|
+
baut, und „gebaut“ **nachdem** du sein Ergebnis geprüft hast.
|
|
171
|
+
- **Bei Erfolg schweigst du.** Der Mensch bekommt keine Zeile darüber, dass du gemeldet hast — ständige Einblendungen stören ihn.
|
|
172
|
+
Sagen musst du nur den Satz von `projekt verbinden` und Probleme: Rückgabewert `1` heißt, die App hat abgesagt — lies den
|
|
173
|
+
Satz, behebe es, und gib weiter, was den Menschen betrifft. `3` heißt, die App ist nicht erreichbar: Die Meldung ist gemerkt
|
|
174
|
+
und geht beim nächsten Befehl mit, schick sie nicht noch einmal. `2` heißt nicht angemeldet: Sag ihm `kimal3 login`.
|
|
175
|
+
- **Sätze, die nur du bekommst** (vom Mod `kimal3-plan`, der Mensch sieht sie nicht): die Erinnerung, eine Story als „in Arbeit“
|
|
176
|
+
zu melden oder die Änderung als Thema zu notieren · „leg das Projekt mit `kimal3 projekt verbinden` an“ · „gemerkte Meldungen
|
|
177
|
+
warten — `kimal3 plan senden`“. Handle danach, ohne dem Menschen davon zu erzählen.
|
|
178
|
+
|
|
139
179
|
## 4 · Erlaubnis und Sicherheit
|
|
140
180
|
- **Bevor eine Aktion bestätigt werden muss**, erkläre in Alltagssprache: **was** passiert (die Wirkung, nicht der
|
|
141
181
|
Befehl), **warum** jetzt, **was es bedeutet** (umkehrbar? folgenreich?). Ruhig und klar, nicht alarmierend.
|
package/coach/en/kimal3-coach.md
CHANGED
|
@@ -88,7 +88,12 @@ use them instead of starting off too quickly.
|
|
|
88
88
|
|
|
89
89
|
### 3.3 Planning, now concretely
|
|
90
90
|
- What exactly is built, in which steps, how each step is checked. For bigger things in writing: a spec,
|
|
91
|
-
that is a plan with user stories
|
|
91
|
+
that is a plan with user stories. Every story sits in four boxes, always in this order:
|
|
92
|
+
**User story** ("As … I want … so that …"; the title names the person's goal, not the result) ·
|
|
93
|
+
**Acceptance criterion** (four lines: *X has …* · *X does …* · *Then …must …* · *Counter-check: …* — a criterion that
|
|
94
|
+
cannot fail is none) · **How you test it** (numbered steps for someone without technical knowledge: where, what to do, what
|
|
95
|
+
appears; below them the line "Doesn't work if: …"; a command only with its place, in backticks) · **Technical note**
|
|
96
|
+
(where the thing lives, what carries it — the person need not read it). Real numbers, real text, a name instead of "the user".
|
|
92
97
|
- **An approved plan justifies nothing that fresh measurements refute.** Never "the plan says this is
|
|
93
98
|
acceptable" as a reason — answer anew: *What is the best thing, given what I know NOW?* If it
|
|
94
99
|
differs: say so out loud, update the plan.
|
|
@@ -136,6 +141,41 @@ use them instead of starting off too quickly.
|
|
|
136
141
|
- **Say on your own which steps you skipped** — even one a skill prescribes. They will ask about it,
|
|
137
142
|
and being found out weighs more than saying it.
|
|
138
143
|
|
|
144
|
+
### 3.6 The board — when you report to the project page
|
|
145
|
+
Applies in every project folder (a folder directly inside `workbench-…/projects/`). Elsewhere you report nothing and ask nothing.
|
|
146
|
+
The board in the app shows only what you report: if you don't, it shows "Open" for hours while you build.
|
|
147
|
+
- **There is one project per folder:** `kimal3 project connect`. It stays silent (`0`) if the folder is already connected.
|
|
148
|
+
If it creates a project, you pass on the **one** sentence it prints ("I created the project '…' in the app.").
|
|
149
|
+
If two projects have the same name, or it was deleted in the app, it asks: ask the person and never choose yourself; `--new`
|
|
150
|
+
only after their yes. If the success profile is missing, say the sentence once, keep working and report nothing.
|
|
151
|
+
- **You report at six moments**, each time with `kimal3 plan report` (input: one report as JSON or a list,
|
|
152
|
+
e.g. `echo '[…]' | kimal3 plan report`; the program sets time and session). You read the keys of spec and story from the app
|
|
153
|
+
with `kimal3 plan` — don't invent them.
|
|
154
|
+
1. **A spec is created or its stories change:**
|
|
155
|
+
`{"art":"spec","schluessel":"2026-10-24-test-month","titel":"Test month","datei":"docs/superpowers/specs/2026-10-24-test-month-design.md","stories":[{"schluessel":"US-1","titel":"The test month starts"}]}`
|
|
156
|
+
2. **The build of a spec begins:** the stories being built now go to "in progress" — **before** you take the first build step or
|
|
157
|
+
send off a subagent:
|
|
158
|
+
`{"art":"story","spec":"2026-10-24-test-month","story":"US-1","stand":"in_arbeit"}`
|
|
159
|
+
3. **A story is built and its counter-checks have run:** "built" with criteria and the `testen` text. You take the text from the story's box "How you test it", word for word. It is a guide for someone without technical knowledge: **one line per step** (where to find it, what to do, what must appear), **without** the box heading, and **last** the line "Doesn't work if: …". In the JSON you separate the lines with `\n` — a real line break inside the text breaks the JSON, and `kimal3 plan report` cannot read the report. At most 1000 characters: if it is longer, shorten the steps, never cut off in the middle of a sentence. A command only if there is no other way - then say where to type it (in Claude Code in the project folder, with `!` in front) and put it in backticks.
|
|
160
|
+
Example:
|
|
161
|
+
`{"art":"story","spec":"2026-10-24-test-month","story":"US-1","stand":"gebaut","kriterien":[{"text":"If Sabine starts on 1 Nov, it ends on 30 Nov.","gegenprobe":"bestanden"}],"testen":"1. In the app open 'Test month'.\n2. Click Start.\n3. Below it says 'Ends on 30 Nov'.\nDoesn't work if: it shows another date."}`
|
|
162
|
+
4. **Something comes up that no spec promises:** `{"art":"thema","schluessel":"T-4","text":"The calendar could show public holidays"}`
|
|
163
|
+
5. **`/k3plan`:** check the specs against the code and report every deviation individually — it says how. **Checking is work:** a story with pending counter-checks (column "Claude test pending") that you check against the code, you report as "in progress" beforehand. Stories under "Your test" or "Done" you do not report again during the reconciliation.
|
|
164
|
+
6. **Wrap-up:** reconcile the day — it says how.
|
|
165
|
+
|
|
166
|
+
🔑 **If a story stays unfinished when you stop** (at the end of `/k3plan` or in the wrap-up), you report it with `--no-session` (`kimal3 plan report --no-session`): its plan state stays `in_arbeit`, but the dot at the project stops glowing although the window stays open — on the board it then sits under "Open" as started.
|
|
167
|
+
|
|
168
|
+
You report "built" only after a counter-check that ran in this session, never from memory.
|
|
169
|
+
- **Subagents never report.** The mod reminds only you, not them: you report "in progress" **before** the job to a subagent that
|
|
170
|
+
builds, and "built" **after** you have checked its result.
|
|
171
|
+
- **On success you stay silent.** The person gets no line saying that you reported — constant pop-ups bother them.
|
|
172
|
+
You only say the sentence from `project connect` and problems: exit code `1` means the app refused — read the
|
|
173
|
+
sentence, fix it, and pass on what concerns the person. `3` means the app is not reachable: the report is kept
|
|
174
|
+
and goes along with the next command, don't send it again. `2` means not signed in: tell them `kimal3 login`.
|
|
175
|
+
- **Sentences only you get** (from the mod `kimal3-plan`; the person doesn't see them): the reminder to report a story as "in
|
|
176
|
+
progress" or to note the change as a topic · "create the project with `kimal3 project connect`" · "saved reports are
|
|
177
|
+
waiting — `kimal3 plan send`". Act on them without telling the person.
|
|
178
|
+
|
|
139
179
|
## 4 · Permission and safety
|
|
140
180
|
- **Before an action has to be confirmed**, explain in everyday language: **what** happens (the effect, not the
|
|
141
181
|
command), **why** now, **what it means** (reversible? consequential?). Calm and clear, not alarming.
|
package/dist/argumente.js
CHANGED
|
@@ -143,3 +143,15 @@ export function schalterOhneWert(argv, deutsch) {
|
|
|
143
143
|
const namen = [deutsch, ...Object.keys(SCHALTER_EN).filter((e) => SCHALTER_EN[e] === deutsch)].map((n) => `--${n}`);
|
|
144
144
|
return { gesetzt: argv.some((a) => namen.includes(a)), rest: argv.filter((a) => !namen.includes(a)) };
|
|
145
145
|
}
|
|
146
|
+
/**
|
|
147
|
+
* Was `kimal3 plan melde` / `plan report` hinter dem Befehlswort annimmt: nichts, `--help`/`-h` oder genau
|
|
148
|
+
* `--ohne-sitzung` (`--no-session`). Alles andere -- auch ein Tippfehler wie `--ohne-sitzungg` -- bleibt „unbekannt"
|
|
149
|
+
* wie heute (Spec 2026-10-24 E-8). Die Meldung selbst kommt von der Eingabe, nie aus Argumenten.
|
|
150
|
+
*/
|
|
151
|
+
export function planMeldeAngenommen(rest) {
|
|
152
|
+
if (rest.length === 0)
|
|
153
|
+
return true;
|
|
154
|
+
if (rest.length > 1)
|
|
155
|
+
return false;
|
|
156
|
+
return rest[0] === "--help" || rest[0] === "-h" || schalterOhneWert(rest, "ohne-sitzung").gesetzt;
|
|
157
|
+
}
|
package/dist/atomar.js
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { renameSync, rmSync, writeFileSync } from "node:fs";
|
|
3
|
+
/**
|
|
4
|
+
* Schreibt eine Datei so, dass sie nie halb dasteht: erst eine Nebendatei, dann umbenennen (Board in Echtzeit, Task 7,
|
|
5
|
+
* Fixrunde 1). Ein Absturz oder voller Datentraeger mitten im Schreiben laesst die alte Datei (oder gar keine) zurueck,
|
|
6
|
+
* nie eine abgeschnittene -- eine abgeschnittene gemerkte Meldung oder ein halber Ordnerzettel waere unlesbar.
|
|
7
|
+
* Wirft wie `writeFileSync`; die Nebendatei raeumt sie dabei weg.
|
|
8
|
+
*/
|
|
9
|
+
export function schreibeAtomar(pfad, text) {
|
|
10
|
+
const neben = `${pfad}.${randomBytes(3).toString("hex")}.tmp`; // kein-anzeigetext: Dateiname
|
|
11
|
+
try {
|
|
12
|
+
writeFileSync(neben, text, "utf8");
|
|
13
|
+
renameSync(neben, pfad);
|
|
14
|
+
}
|
|
15
|
+
catch (f) {
|
|
16
|
+
try {
|
|
17
|
+
rmSync(neben, { force: true });
|
|
18
|
+
}
|
|
19
|
+
catch { /* schon weg */ }
|
|
20
|
+
throw f;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
@@ -6,7 +6,7 @@ import { leseAnmeldung } from "../anmeldung.js";
|
|
|
6
6
|
import { alterKursCoachAblage, alterKursCoachPfad, coachDateiLiegt, legeCoachAb, meldeCoach } from "../coach-ablegen.js";
|
|
7
7
|
import { istAlterKursCoach } from "../coach-rechnung.js";
|
|
8
8
|
import { tragePluginsEin } from "../einstellungen-schreiben.js";
|
|
9
|
-
import {
|
|
9
|
+
import { modsAblegen } from "../mods-ablegen.js";
|
|
10
10
|
import { pluginStand } from "../plugins-mischen.js";
|
|
11
11
|
import { ruf } from "../api.js";
|
|
12
12
|
import { FIRMEN_OPTIONEN, fehlendeDateienSchreiben, istUnsichererOrdnername, vorlageHolen, werkbankOrdner, } from "../arbeitsplatz-dateien.js";
|
|
@@ -310,7 +310,7 @@ async function neuAnlegen(pfad, wunsch, email, gespeichert) {
|
|
|
310
310
|
function pluginsEinrichten(git) {
|
|
311
311
|
const s = sprache();
|
|
312
312
|
const t = EINRICHTEN_TEXTE[s];
|
|
313
|
-
const ablage =
|
|
313
|
+
const ablage = modsAblegen(s);
|
|
314
314
|
// Ohne Logo-Ordner kommt superpowers trotzdem (Durchsicht 2, Fund 1): eine Zeile zum Logo, dann weiter.
|
|
315
315
|
if (!ablage.ok)
|
|
316
316
|
console.log(t.logoNotWritten(ablage.detail));
|
|
@@ -328,8 +328,8 @@ function pluginsEinrichten(git) {
|
|
|
328
328
|
return;
|
|
329
329
|
}
|
|
330
330
|
const stand = pluginStand(eintrag.inhalt);
|
|
331
|
-
const namen = { superpowers: "superpowers", logo: t.logoName };
|
|
332
|
-
const mit = (wert) => ["superpowers", "logo"].filter((p) => stand[p] === wert).map((p) => namen[p]);
|
|
331
|
+
const namen = { superpowers: "superpowers", logo: t.logoName, plan: t.planName };
|
|
332
|
+
const mit = (wert) => ["superpowers", "logo", "plan"].filter((p) => stand[p] === wert).map((p) => namen[p]);
|
|
333
333
|
console.log(t.pluginsLine(mit("an"), mit("aus"), eintrag.art === "eingetragen"));
|
|
334
334
|
// Auch wenn superpowers schon von früher drinsteht: ohne Git lädt es nicht (Durchsicht 4, Fund 3).
|
|
335
335
|
if (!git && stand.superpowers !== "aus")
|
package/dist/befehle/login.js
CHANGED
|
@@ -6,7 +6,7 @@ import { anmeldeAdresse, oeffneImBrowser } from "../browser.js";
|
|
|
6
6
|
import { ALLGEMEIN_TEXTE } from "../texte/allgemein.js";
|
|
7
7
|
import { EINRICHTEN_TEXTE } from "../texte/einrichten.js";
|
|
8
8
|
import { frischeCoachAuf } from "../coach-ablegen.js";
|
|
9
|
-
import {
|
|
9
|
+
import { frischeModsAuf } from "../mods-ablegen.js";
|
|
10
10
|
import { stelleSkillUm } from "./skill-tausch.js";
|
|
11
11
|
const warte = (ms) => new Promise((f) => setTimeout(f, ms));
|
|
12
12
|
// 🌐 Die Sprache wird bei JEDER Ausgabe neu gelesen: Die Abhol-Antwort nennt die Kontosprache
|
|
@@ -36,7 +36,7 @@ export async function login() {
|
|
|
36
36
|
// 2026-10-18, L-1, L-8): `einrichten` legt es nach der Anmeldung ohnehin selbst ab.
|
|
37
37
|
if (code === 0) {
|
|
38
38
|
frischeCoachAuf(sprache());
|
|
39
|
-
|
|
39
|
+
frischeModsAuf(sprache());
|
|
40
40
|
}
|
|
41
41
|
return code;
|
|
42
42
|
}
|
|
@@ -50,6 +50,14 @@ export async function planHoch(argv) {
|
|
|
50
50
|
console.error(zerlegt.meldung);
|
|
51
51
|
return 1;
|
|
52
52
|
}
|
|
53
|
+
// 🔴 Seit 0.7.0 (Ruling Task 7, Board in Echtzeit 1.1): Claude meldet jede Aenderung einzeln (`plan melde`). Die ganze
|
|
54
|
+
// Datei geht nur noch mit --ueberschreiben hoch -- fuer eine Planung, die sich anders nicht mehr lesen laesst (die App
|
|
55
|
+
// nimmt das nur an, wenn seit `basis` keine 0.7-Meldung kam, R-6). Ohne den Schalter wuerde eine alte Datei Meldungen
|
|
56
|
+
// anderer Sitzungen wegwischen.
|
|
57
|
+
if (!ueber.gesetzt) {
|
|
58
|
+
console.error(PLAN_TEXTE[s].uploadNotNeeded);
|
|
59
|
+
return 1;
|
|
60
|
+
}
|
|
53
61
|
const datei = planDateiOderAbsage(s);
|
|
54
62
|
if (datei === null)
|
|
55
63
|
return 1;
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { schalterOhneWert } from "../argumente.js";
|
|
2
|
+
import { sprache } from "../sprache.js";
|
|
3
|
+
import { MELDUNGEN_TEXTE } from "../texte/meldungen.js";
|
|
4
|
+
import { merke, meldungFehler, sitzungFuerMeldung } from "../meldungen-speicher.js";
|
|
5
|
+
import { ladeSpecText, sendeUmschlaege } from "./plan-senden.js";
|
|
6
|
+
import { projektDesOrdners } from "./projekt-verbinden.js";
|
|
7
|
+
/*
|
|
8
|
+
`kimal3 plan melde` -- Claude meldet der Projektseite EINE Aenderung (oder eine Liste), statt die ganze Datei
|
|
9
|
+
hochzuladen (Board in Echtzeit, Spec 1.1, 1.2, E-6, S-10, US-1, US-4).
|
|
10
|
+
|
|
11
|
+
Das Projekt kommt vom Ordnerzettel, nie aus einer Wahl (S-12). `entstanden` ist JETZT, `sitzung` die Nummer der
|
|
12
|
+
Claude-Sitzung: Beides setzt das Programm, nicht Claude -- eine von Hand gesetzte Zeit koennte eine fremde,
|
|
13
|
+
neuere Meldung verdraengen (E-2, E-6).
|
|
14
|
+
|
|
15
|
+
Nicht erreichbar -> gemerkt, Rueckgabewert 3; beim naechsten Befehl geht sie mit. Erfolg ist still (US-4).
|
|
16
|
+
*/
|
|
17
|
+
async function leseEingabe() {
|
|
18
|
+
if (process.stdin.isTTY)
|
|
19
|
+
return null;
|
|
20
|
+
const teile = [];
|
|
21
|
+
for await (const teil of process.stdin)
|
|
22
|
+
teile.push(Buffer.from(teil));
|
|
23
|
+
const text = Buffer.concat(teile).toString("utf8").replace(/^/, "");
|
|
24
|
+
return text.trim() === "" ? null : text;
|
|
25
|
+
}
|
|
26
|
+
export async function planMelde(argv) {
|
|
27
|
+
const t = MELDUNGEN_TEXTE[sprache()];
|
|
28
|
+
if (argv.includes("--help") || argv.includes("-h")) {
|
|
29
|
+
console.log(t.reportHelp);
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
const projekt = projektDesOrdners();
|
|
33
|
+
if (projekt === null)
|
|
34
|
+
return 1;
|
|
35
|
+
const text = await leseEingabe();
|
|
36
|
+
if (text === null) {
|
|
37
|
+
console.error(t.noInput);
|
|
38
|
+
return 1;
|
|
39
|
+
}
|
|
40
|
+
let roh;
|
|
41
|
+
try {
|
|
42
|
+
roh = JSON.parse(text);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
console.error(t.inputNoJson);
|
|
46
|
+
return 1;
|
|
47
|
+
}
|
|
48
|
+
const liste = Array.isArray(roh) ? roh : [roh];
|
|
49
|
+
if (liste.length === 0) {
|
|
50
|
+
console.error(t.noInput);
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
for (const [i, m] of liste.entries()) {
|
|
54
|
+
const feld = meldungFehler(m);
|
|
55
|
+
if (feld !== null) {
|
|
56
|
+
console.error(t.reportUnreadable(i + 1, feld));
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
const entstanden = new Date().toISOString();
|
|
61
|
+
// Prüfen ist nicht Arbeiten (Spec 2026-10-24 E-8): `--ohne-sitzung` schickt keine Sitzung mit, also leuchtet kein Punkt.
|
|
62
|
+
const sitzung = schalterOhneWert(argv, "ohne-sitzung").gesetzt ? null : sitzungFuerMeldung(process.env);
|
|
63
|
+
const umschlaege = liste.map((m) => ({ meldung: m, entstanden, sitzung }));
|
|
64
|
+
const sendung = await sendeUmschlaege(projekt.projektId, umschlaege);
|
|
65
|
+
const u = MELDUNGEN_TEXTE[sprache()];
|
|
66
|
+
// Erst die Absagen (je Satz einmal), dann was mit dem Rest geschieht.
|
|
67
|
+
for (const satz of new Set(sendung.je.flatMap((e) => (e.art === "abgesagt" ? [e.meldung] : []))))
|
|
68
|
+
console.error(satz);
|
|
69
|
+
const offen = umschlaege.filter((_, i) => sendung.je[i].art === "offen");
|
|
70
|
+
const p = sendung.problem;
|
|
71
|
+
if (offen.length > 0 && p !== null && p.art !== "absage") {
|
|
72
|
+
// Jede einzeln merken und zaehlen: Scheitert das Merken mittendrin, muss der Satz sagen, wie viele gemerkt sind.
|
|
73
|
+
const gemerkt = offen.filter((x) => merke(projekt.projektId, projekt.ordner, x)).length;
|
|
74
|
+
const satz = gemerkt === offen.length ? (p.art === "anmelden" ? u.storedSignIn : u.stored)
|
|
75
|
+
: gemerkt > 0 ? u.storedPartly(gemerkt, offen.length)
|
|
76
|
+
: p.art === "anmelden" ? p.meldung : u.notStored;
|
|
77
|
+
if (p.art === "anmelden" && gemerkt < offen.length && gemerkt > 0)
|
|
78
|
+
console.error(p.meldung);
|
|
79
|
+
console.error(satz);
|
|
80
|
+
return p.art === "anmelden" ? 2 : 3;
|
|
81
|
+
}
|
|
82
|
+
// Ruling Task 7: Eine Spec-Datei, die VOR ihrer Meldung geschrieben wurde, hat die App noch nicht -- jetzt mitnehmen.
|
|
83
|
+
// Fuer jede ANGENOMMENE Spec-Meldung, auch wenn eine andere der Liste abgesagt wurde.
|
|
84
|
+
let code = sendung.je.some((e) => e.art === "abgesagt") ? 1 : 0;
|
|
85
|
+
for (const [i, x] of umschlaege.entries()) {
|
|
86
|
+
const m = x.meldung;
|
|
87
|
+
if (sendung.je[i].art !== "angenommen" || m.art !== "spec" || m.datei === null)
|
|
88
|
+
continue;
|
|
89
|
+
const text = await ladeSpecText(projekt.projektId, projekt.ordner, m.schluessel, m.datei);
|
|
90
|
+
if (text === "anmelden") {
|
|
91
|
+
console.error(u.specTextSignIn(m.datei));
|
|
92
|
+
return 2;
|
|
93
|
+
}
|
|
94
|
+
if (text === "fehler") {
|
|
95
|
+
console.error(u.specTextFailed(m.datei));
|
|
96
|
+
code = 3;
|
|
97
|
+
}
|
|
98
|
+
else if (text === "zu-gross") {
|
|
99
|
+
console.error(u.specTextTooBig(m.datei));
|
|
100
|
+
if (code === 0)
|
|
101
|
+
code = 1;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
return code;
|
|
105
|
+
}
|