kimal3 0.8.0 → 0.9.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/claude-plugins/kimal3-plan/.claude-plugin/plugin.json +2 -2
- package/claude-plugins/kimal3-plan/hooks/gespraech.ts +134 -0
- package/claude-plugins/kimal3-plan/hooks/lauf.ts +48 -1
- package/claude-plugins/kimal3-plan/hooks/lebenszeichen.ts +61 -8
- package/claude-plugins/kimal3-plan/hooks/register.tsx +5 -2
- package/claude-plugins/kimal3-plan/hooks/spec-text.ts +8 -3
- package/claude-plugins/kimal3-plan/types/index.d.ts +4 -0
- package/coach/de/kimal3-coach.md +32 -11
- package/coach/en/kimal3-coach.md +30 -11
- package/dist/meldungen-speicher.js +5 -0
- package/dist/texte/meldungen.js +2 -2
- package/package.json +1 -1
- package/skill/commands/k3feierabend.md +2 -2
- package/skill/commands/k3plan.md +14 -8
- package/skill/en/commands/k3plan.md +15 -9
- package/skill/en/commands/k3wrapup.md +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "kimal3-plan",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Hält das Board der Projektseite in der App aktuell: Punkt solange die Sitzung arbeitet, Spec-Texte, hält Claude vor dem ersten Bau-Schritt an · Keeps the board on the app's project page current: dot while the session works, spec texts, stops Claude before the first build step",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Hält das Board der Projektseite in der App aktuell: Punkt solange die Sitzung arbeitet, ob Claude gerade arbeitet oder wartet, „Besprechen“ an Claude weitergeben, Spec-Texte, hält Claude vor dem ersten Bau-Schritt an · Keeps the board on the app's project page current: dot while the session works, whether Claude is working or waiting, passes ‘Discuss’ on to Claude, spec texts, stops Claude before the first build step",
|
|
5
5
|
"author": { "name": "KImal3", "url": "https://kimal3.de" },
|
|
6
6
|
"types": "./types/index.d.ts"
|
|
7
7
|
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { update, type EngineInterface, type On } from 'claude-code'
|
|
2
|
+
import { TEXTE, zugestelltFuer, zustandVon, type Lauf } from './lauf'
|
|
3
|
+
import { naechsteAntwort, sofort } from './lebenszeichen'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* „Im Gespräch“ (Spec docs/superpowers/specs/2026-10-26-im-gespraech-design.md, E-4, E-5, E-8, US-2, US-3; Bauplan Task 4,
|
|
7
|
+
* R-17). Zwei Aufgaben:
|
|
8
|
+
*
|
|
9
|
+
* 1. **Ob Claude gerade arbeitet** sieht der Mod selbst, ohne dass Claude etwas meldet (E-4): Jede Runde, die beginnt
|
|
10
|
+
* (`turn.start`), kommt in eine Menge, jede, die endet (`turn.complete`, jeder Grund, auch Esc = `aborted`), geht
|
|
11
|
+
* wieder hinaus — Hauptrunde UND Subagenten. Gemessen in der Probe (docs/qa/2026-10-26-im-gespraech-probe.md): Die
|
|
12
|
+
* Hauptrunde kann VOR einem Subagenten im Hintergrund enden; „arbeitet“ gilt deshalb, solange irgendeine Runde offen
|
|
13
|
+
* ist (G-14). „Wartet“ (E-5): ab einer Erlaubnis-Abfrage (`classic.PermissionRequest`, gemessen) oder während Claude mit
|
|
14
|
+
* `AskUserQuestion` fragt — bis dieser Werkzeugaufruf endet (`classic.PostToolUse`, `PostToolUseFailure`,
|
|
15
|
+
* `PermissionDenied`, gleiches Werkzeug, gleiche Schleife) oder die Runde seiner Schleife (R-34, R-35). Diese Ereignisse
|
|
16
|
+
* umschließen nichts; ein `tool.call`-Haken um JEDES Werkzeug läge dagegen um den Start eines Subagenten, und „an agent's
|
|
17
|
+
* start skips the hooks its spawn was nested in“ (Typen, `'turn.start'`). Jeder Wechsel löst sofort ein Lebenszeichen aus
|
|
18
|
+
* (`sofort`); der Zustand reist ohnehin mit jedem Takt, so heilt der nächste eine verlorene Meldung (G-27).
|
|
19
|
+
* 2. **Dennis' Klick an Claude weitergeben** (E-8): Beim Abschicken einer Nachricht (`classic.UserPromptSubmit`, gemessen:
|
|
20
|
+
* `additionalContext` kommt an) bekommt Claude den Satz „angeklickt“ bzw. „beendet“, wenn die App ihn für diesen Chat
|
|
21
|
+
* `abholbar` nennt und die Nummer neu ist. Gewartet wird höchstens 1,5 s auf eine frische Antwort der App, sonst gilt
|
|
22
|
+
* die letzte (QA 11: das Abschicken hängt nie 8 s). Die zugestellte Nummer steht in `$.state` und übersteht so ein
|
|
23
|
+
* Neuladen des Mods; nach „angeklickt“ geht SOFORT ein Lebenszeichen mit `abgeholt` hinaus (R-17), damit ein zweiter
|
|
24
|
+
* Chat den Klick nicht auch zustellt. Für „beendet“ kein `abgeholt` (die App meldet es dem tragenden Chat dauerhaft
|
|
25
|
+
* `abholbar`; allein die Nummer in `$.state` verhindert die Wiederholung).
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** Wie lange das Abschicken einer Nachricht höchstens auf eine frische Antwort der App wartet (US-3, QA 11). */
|
|
29
|
+
const FRISCH_MS = 1_500
|
|
30
|
+
|
|
31
|
+
// Geschrieben wird hier, gelesen in `zugestelltFuer` (lauf.ts, auch vom Lebenszeichen, R-33): je Projekt eine Nummer.
|
|
32
|
+
const zugestellt = { plugin: 'kimal3-plan', key: 'zugestellt' } as const
|
|
33
|
+
|
|
34
|
+
/** Ändert `f` den Zustand, erfährt es die App sofort. Kein `await`: Beginn und Ende einer Runde warten auf nichts. */
|
|
35
|
+
function aendere($: EngineInterface, lauf: Lauf, f: () => void): void {
|
|
36
|
+
const vorher = zustandVon(lauf)
|
|
37
|
+
f()
|
|
38
|
+
if (lauf.runden.size === 0) lauf.wartet = null
|
|
39
|
+
if (zustandVon(lauf) !== vorher) sofort($, lauf)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Das Warten auf `werkzeug` in der Schleife `agent` (Hauptrunde: `undefined`) ist vorbei. */
|
|
43
|
+
function wartenVorbei($: EngineInterface, lauf: Lauf, agent: string | undefined, werkzeug: string): void {
|
|
44
|
+
aendere($, lauf, () => {
|
|
45
|
+
if (lauf.wartet !== null && lauf.wartet.agent === agent && lauf.wartet.werkzeug === werkzeug) lauf.wartet = null
|
|
46
|
+
})
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function gespraech(on: On, lauf: Lauf): void {
|
|
50
|
+
// Nach `/clear` ist es ein neuer Chat: Er hat noch nichts zugestellt.
|
|
51
|
+
on('classic.SessionStart', { source: 'clear' }, async ($, e, next) => {
|
|
52
|
+
const ergebnis = await next(e)
|
|
53
|
+
await update($, zugestellt, () => ({}))
|
|
54
|
+
return ergebnis
|
|
55
|
+
})
|
|
56
|
+
on('turn.start', ($, e, next) => {
|
|
57
|
+
aendere($, lauf, () => lauf.runden.add(e.turnId))
|
|
58
|
+
return next(e)
|
|
59
|
+
})
|
|
60
|
+
// Jeder Grund schließt die Runde, auch `aborted` (Esc, G-26) und `error`. Eine Runde, deren Beginn der Mod nie hörte
|
|
61
|
+
// (vor dem Laden begonnen), ändert nichts. Mit ihr endet ein Warten ihrer Schleife (R-34): Endet die Runde, ohne dass
|
|
62
|
+
// das Werkzeug ein Ende meldete (abgelehnt, abgebrochen), wartet dort niemand mehr — auch wenn ein Subagent weiterläuft.
|
|
63
|
+
on('turn.complete', ($, e, next) => {
|
|
64
|
+
aendere($, lauf, () => {
|
|
65
|
+
lauf.runden.delete(e.turnId)
|
|
66
|
+
if (lauf.wartet?.agent === e.agentId) lauf.wartet = null
|
|
67
|
+
})
|
|
68
|
+
return next(e)
|
|
69
|
+
})
|
|
70
|
+
// Nur beobachten: Die Abfrage selbst entscheidet der Mensch, der Haken antwortet nie mit `decision`.
|
|
71
|
+
on('classic.PermissionRequest', ($, e, next) => {
|
|
72
|
+
aendere($, lauf, () => {
|
|
73
|
+
if (lauf.runden.size > 0) lauf.wartet = { agent: e.agent_id, werkzeug: e.tool_name }
|
|
74
|
+
})
|
|
75
|
+
return next(e)
|
|
76
|
+
})
|
|
77
|
+
// Das Fragefenster: Nur dieser Haken umschließt den Aufruf (`next`), alle anderen Ereignisse hier beobachten nur.
|
|
78
|
+
on('tool.call', { tool: 'AskUserQuestion' }, async ($, e, next) => {
|
|
79
|
+
aendere($, lauf, () => {
|
|
80
|
+
if (lauf.runden.size > 0) lauf.wartet = { agent: e.agentId, werkzeug: e.tool }
|
|
81
|
+
})
|
|
82
|
+
try {
|
|
83
|
+
return await next(e)
|
|
84
|
+
} finally {
|
|
85
|
+
wartenVorbei($, lauf, e.agentId, e.tool)
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
// Das Werkzeug, auf das gewartet wurde, ist fertig, gescheitert oder abgelehnt (gleiches Werkzeug, gleiche Schleife). Ein
|
|
89
|
+
// Aufruf eines Subagenten, der nebenher endet, beendet das Warten der Hauptrunde nicht.
|
|
90
|
+
on('classic.PostToolUse', ($, e, next) => {
|
|
91
|
+
wartenVorbei($, lauf, e.agent_id, e.tool_name)
|
|
92
|
+
return next(e)
|
|
93
|
+
})
|
|
94
|
+
on('classic.PostToolUseFailure', ($, e, next) => {
|
|
95
|
+
wartenVorbei($, lauf, e.agent_id, e.tool_name)
|
|
96
|
+
return next(e)
|
|
97
|
+
})
|
|
98
|
+
on('classic.PermissionDenied', ($, e, next) => {
|
|
99
|
+
wartenVorbei($, lauf, e.agent_id, e.tool_name)
|
|
100
|
+
return next(e)
|
|
101
|
+
})
|
|
102
|
+
// R-36: zugestellt ist der Satz erst, wenn die Nachricht wirklich an Claude geht — nicht bei einer Nachricht, die ein
|
|
103
|
+
// anderer Haken anhält (`block`, `preventContinuation`), nicht beim Vorab-Durchgang eines Ereignisses (`poll_event`, die
|
|
104
|
+
// Typen: „before its delivery ack exists“), nicht in einem Subagenten.
|
|
105
|
+
on('classic.UserPromptSubmit', async ($, e, next) => {
|
|
106
|
+
const z = e.agent_id === undefined && e.source !== 'poll_event' ? await zustellbar($, lauf) : null
|
|
107
|
+
const ergebnis = await next(e)
|
|
108
|
+
if (z === null || ergebnis.block !== undefined || ergebnis.preventContinuation === true) return ergebnis
|
|
109
|
+
await update($, zugestellt, m => ({ ...m, [z.projekt]: Math.max(m?.[z.projekt] ?? 0, z.nummer) }))
|
|
110
|
+
if (z.aktiv) {
|
|
111
|
+
lauf.abholen = { projekt: z.projekt, nummer: z.nummer }
|
|
112
|
+
sofort($, lauf)
|
|
113
|
+
}
|
|
114
|
+
return { ...ergebnis, additionalContext: [...(ergebnis.additionalContext ?? []), z.satz] }
|
|
115
|
+
})
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Der Satz, den dieser Chat jetzt zustellen darf, mit Projekt, Nummer und ob es „angeklickt“ ist — oder `null`. */
|
|
119
|
+
async function zustellbar($: EngineInterface, lauf: Lauf): Promise<{ satz: string; projekt: string; nummer: number; aktiv: boolean } | null> {
|
|
120
|
+
// Ohne laufendes Lebenszeichen (kein Projektordner, kein Zettel, beendet) gibt es nichts abzuholen und nichts zu warten.
|
|
121
|
+
if (lauf.takt === null) return null
|
|
122
|
+
await new Promise<void>(fertig => {
|
|
123
|
+
const frist = $.clock.after(FRISCH_MS, fertig)
|
|
124
|
+
naechsteAntwort($, lauf).then(() => {
|
|
125
|
+
frist.cancel()
|
|
126
|
+
fertig()
|
|
127
|
+
})
|
|
128
|
+
})
|
|
129
|
+
const g = lauf.gespraech
|
|
130
|
+
if (g === null || !g.abholbar || g.quelle !== 'dennis' || g.nummer <= (await zugestelltFuer($, g.projekt))) return null
|
|
131
|
+
const texte = TEXTE[lauf.sprache]
|
|
132
|
+
const satz = g.aktiv ? texte.angeklickt(g.titel, g.datei) : texte.beendet(g.titel)
|
|
133
|
+
return { satz, projekt: g.projekt, nummer: g.nummer, aktiv: g.aktiv }
|
|
134
|
+
}
|
|
@@ -37,16 +37,58 @@ export type Lauf = {
|
|
|
37
37
|
/** Beim naechsten erfolgreichen Lebenszeichen nach gemerkten Meldungen sehen (R-4): beim Start und nach jedem Ausfall. */
|
|
38
38
|
meldungenPruefen: boolean
|
|
39
39
|
fehler: Set<Fehler>
|
|
40
|
+
/** Die offenen Runden (`turnId`), Hauptrunde und Subagenten: nicht leer = Claude arbeitet (hooks/gespraech.ts, E-4). */
|
|
41
|
+
runden: Set<string>
|
|
42
|
+
/** Worauf Claude gerade wartet (Erlaubnis, Fragefenster) und in welcher Schleife; `null`: auf nichts (E-5). */
|
|
43
|
+
wartet: { agent: string | undefined; werkzeug: string } | null
|
|
44
|
+
/** Das Gespräch aus der letzten Antwort der App; `null`: keins, oder eine App ohne „Im Gespräch“ (R-18). */
|
|
45
|
+
gespraech: (GespraechFuerMod & { projekt: string }) | null
|
|
46
|
+
/** Diese Nummer („angeklickt“) ist Claude zugestellt, die App weiß es noch nicht: Das nächste Lebenszeichen DIESES Projekts sagt `abgeholt`. */
|
|
47
|
+
abholen: { projekt: string; nummer: number } | null
|
|
48
|
+
/** Ein sofortiger Takt fiel aus, weil einer unterwegs war: gleich danach noch einer (der Nachzügler). */
|
|
49
|
+
nachzuegler: boolean
|
|
50
|
+
/** Wer auf die Antwort des nächsten Takts wartet (`naechsteAntwort`); ein Takt nimmt beim Start alle mit. */
|
|
51
|
+
wartende: (() => void)[]
|
|
40
52
|
/** Was gerade in der Statuszeile steht (`undefined`: nichts; `null`: noch nie gesetzt -- die erste Auswertung nach dem
|
|
41
53
|
* Laden ruft `$.ui.status` in jedem Fall und raeumt so eine Zeile ab, die ein frueherer Lauf stehen liess). */
|
|
42
54
|
zeile: string | undefined | null
|
|
43
55
|
}
|
|
44
56
|
|
|
57
|
+
export type Zustand = 'still' | 'arbeitet' | 'wartet'
|
|
58
|
+
|
|
59
|
+
/** Was das Lebenszeichen über das Gespräch sagt (`gespraechFuerMod` in `src/lib/planung/gespraech.ts`, Bauplan R-18). */
|
|
60
|
+
export type GespraechFuerMod = { nummer: number; spec: string; titel: string; datei: string | null; aktiv: boolean; quelle: string; abholbar: boolean }
|
|
61
|
+
|
|
62
|
+
/** Arbeitet Claude, wartet es, oder ist es fertig (hooks/gespraech.ts, E-4, E-5)? */
|
|
63
|
+
export function zustandVon(lauf: Lauf): Zustand {
|
|
64
|
+
if (lauf.runden.size === 0) return 'still'
|
|
65
|
+
return lauf.wartet === null ? 'arbeitet' : 'wartet'
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** `gespraech` aus der Antwort; fehlt es (alte App, alter Rumpf) oder ist es kaputt: `null` — beides gleich (R-18). */
|
|
69
|
+
export function gespraechAus(o: unknown): GespraechFuerMod | null {
|
|
70
|
+
const g = o as Partial<Record<keyof GespraechFuerMod, unknown>> | null | undefined
|
|
71
|
+
if (g === null || typeof g !== 'object') return null
|
|
72
|
+
const { nummer, spec, titel, datei, aktiv, quelle, abholbar } = g
|
|
73
|
+
if (!Number.isSafeInteger(nummer) || (nummer as number) < 1 || typeof spec !== 'string' || typeof titel !== 'string') return null
|
|
74
|
+
if (!(datei === null || typeof datei === 'string') || typeof aktiv !== 'boolean' || typeof quelle !== 'string' || typeof abholbar !== 'boolean') return null
|
|
75
|
+
return { nummer: nummer as number, spec, titel, datei, aktiv, quelle, abholbar }
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Welche Nummer Claude je Projekt schon bekam (R-37): je `projekt_id`, damit ein Chat, der das Projekt wechselt, dort keinen
|
|
80
|
+
* Satz verschluckt. In `$.state`, damit ein Neuladen des Mods denselben Satz nicht noch einmal zustellt (US-3).
|
|
81
|
+
*/
|
|
82
|
+
export async function zugestelltFuer($: EngineInterface, projekt: string): Promise<number> {
|
|
83
|
+
const { value } = await $.state.get({ plugin: 'kimal3-plan', key: 'zugestellt' })
|
|
84
|
+
return value?.[projekt] ?? 0
|
|
85
|
+
}
|
|
86
|
+
|
|
45
87
|
export function neuerLauf(vorgabe: Sprache): Lauf {
|
|
46
88
|
return {
|
|
47
89
|
vorgabe, sprache: vorgabe, heim: null, basis: APP, lage: { art: 'kein-projektordner' }, inArbeit: null, meldungen: 0, specDateien: [],
|
|
48
90
|
takt: null, startet: false, generation: 0, beendet: false, abgelehnt: null, gemeldetFuer: null, meldungenPruefen: true,
|
|
49
|
-
fehler: new Set(), zeile: null,
|
|
91
|
+
fehler: new Set(), zeile: null, runden: new Set(), wartet: null, gespraech: null, abholen: null, nachzuegler: false, wartende: [],
|
|
50
92
|
}
|
|
51
93
|
}
|
|
52
94
|
|
|
@@ -59,6 +101,9 @@ export const TEXTE = {
|
|
|
59
101
|
erinnerung: (titel: string) => `Halt, die Datei ist noch nicht geändert: Du arbeitest im Projekt ${titel}, aber diese Sitzung hat keine Story ‚in Arbeit‘ gemeldet. Melde zuerst die Story, an der du arbeitest, und ändere die Datei danach. Gehört die Änderung zu keiner Story, notiere sie als Thema oder versuch es einfach noch einmal.`,
|
|
60
102
|
anstoss: 'Für diesen Projektordner gibt es noch kein Projekt in der App — leg es mit `kimal3 projekt verbinden` an.',
|
|
61
103
|
gemerkt: 'Gemerkte Meldungen warten — führe `kimal3 plan senden` aus.',
|
|
104
|
+
// Im Gespräch, E-8 (Wortlaute der Spec ohne Namen: Der Mod kennt den Vornamen des Eigentümers nicht).
|
|
105
|
+
angeklickt: (titel: string, datei: string | null) => `Auf dem Board wurde ‚${titel}‘${datei === null ? '' : ` (${datei})`} zum Besprechen angeklickt — darüber wird jetzt gesprochen.`,
|
|
106
|
+
beendet: (titel: string) => `Auf dem Board wurde das Gespräch über ‚${titel}‘ beendet.`,
|
|
62
107
|
fehler: {
|
|
63
108
|
'nicht-erreichbar': 'Die Projektseite ist gerade nicht erreichbar — ich versuche es gleich wieder.',
|
|
64
109
|
'nicht-angemeldet': 'Du bist bei kimal3 nicht mehr angemeldet — `kimal3 login`.',
|
|
@@ -69,6 +114,8 @@ export const TEXTE = {
|
|
|
69
114
|
erinnerung: (titel: string) => `Stop, the file has not been changed: You are working in the project ${titel}, but this session hasn't reported a story as ‘in progress’. Report the story you're working on first, then change the file. If the change belongs to no story, note it as a topic or simply try again.`,
|
|
70
115
|
anstoss: 'There is no project in the app for this project folder yet — create it with `kimal3 project connect`.',
|
|
71
116
|
gemerkt: 'Saved reports are waiting — run `kimal3 plan send`.',
|
|
117
|
+
angeklickt: (titel: string, datei: string | null) => `'${titel}'${datei === null ? '' : ` (${datei})`} was selected on the board to discuss — that's what this conversation is about now.`,
|
|
118
|
+
beendet: (titel: string) => `The discussion of '${titel}' was ended on the board.`,
|
|
72
119
|
fehler: {
|
|
73
120
|
'nicht-erreichbar': "The project page can't be reached right now — I'll try again shortly.",
|
|
74
121
|
'nicht-angemeldet': "You're no longer signed in to kimal3 — `kimal3 login`.",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { EngineInterface, On } from 'claude-code'
|
|
2
2
|
import { anstossOhneZettel, gemerkteMeldungenAnstossen, sitzungNeu } from './erinnerung'
|
|
3
3
|
import { fehlerSind } from './fehlerzeile'
|
|
4
|
-
import { anApp, liesAusweis, umgebungLesen, type Lauf } from './lauf'
|
|
4
|
+
import { anApp, gespraechAus, liesAusweis, umgebungLesen, zugestelltFuer, zustandVon, type GespraechFuerMod, type Lauf } from './lauf'
|
|
5
5
|
import { lageJetzt } from './ordner'
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -13,6 +13,11 @@ import { lageJetzt } from './ordner'
|
|
|
13
13
|
*
|
|
14
14
|
* Jeder Takt liest Ordner, Zettel und Ausweis neu: So kommt ein Zettel, den Claude nach dem Anstoss anlegt (US-8), und ein
|
|
15
15
|
* `kimal3 login` beim naechsten Takt an, ohne Neustart.
|
|
16
|
+
*
|
|
17
|
+
* „Im Gespraech“ (Spec 2026-10-26-im-gespraech-design.md, US-2, US-3; hooks/gespraech.ts): Jedes Lebenszeichen traegt den
|
|
18
|
+
* Zustand (`still`/`arbeitet`/`wartet`), `faehig: true` und nach einer Zustellung `abgeholt`; die Antwort `gespraech` merkt
|
|
19
|
+
* sich der Mod. Ein Wechsel des Zustands loest einen sofortigen Takt aus (`sofort`); ist gerade einer unterwegs, kommt
|
|
20
|
+
* gleich danach ein Nachzuegler -- sonst fiele die Meldung bis zum naechsten 10-Sekunden-Takt aus.
|
|
16
21
|
*/
|
|
17
22
|
const TAKT_MS = 10_000
|
|
18
23
|
const MELDUNG = /\bplan\s+(melde|report|senden|send)\b/
|
|
@@ -47,6 +52,7 @@ export function lebenszeichen(on: On, lauf: Lauf): void {
|
|
|
47
52
|
const ergebnis = await next(e)
|
|
48
53
|
if (e.source === 'clear') {
|
|
49
54
|
Object.assign(lauf, { beendet: false, inArbeit: null, specDateien: [], abgelehnt: null, gemeldetFuer: null, meldungenPruefen: true })
|
|
55
|
+
Object.assign(lauf, { runden: new Set(), wartet: null, gespraech: null, abholen: null, nachzuegler: false })
|
|
50
56
|
await sitzungNeu($)
|
|
51
57
|
await sitzungFuerProgramm($)
|
|
52
58
|
await starte($, lauf)
|
|
@@ -75,6 +81,7 @@ export function lebenszeichen(on: On, lauf: Lauf): void {
|
|
|
75
81
|
lauf.takt?.cancel()
|
|
76
82
|
lauf.takt = null
|
|
77
83
|
lauf.inArbeit = null
|
|
84
|
+
Object.assign(lauf, { runden: new Set(), wartet: null, gespraech: null, abholen: null })
|
|
78
85
|
// Die ganze Kette von `session.end` hat ~1,5 s (gemessen M-2); der Abruf endet vorher, damit Claude Code nicht
|
|
79
86
|
// mitten in ihm abschneidet und die Hooks nach uns ihre Zeit behalten.
|
|
80
87
|
const grenze = next.budget.remainingMs - 100
|
|
@@ -84,6 +91,20 @@ export function lebenszeichen(on: On, lauf: Lauf): void {
|
|
|
84
91
|
})
|
|
85
92
|
}
|
|
86
93
|
|
|
94
|
+
/** Ein Lebenszeichen sofort (hooks/gespraech.ts) -- nur, wenn der Zeitgeber laeuft (Projektordner, nicht beendet). */
|
|
95
|
+
export function sofort($: EngineInterface, lauf: Lauf): void {
|
|
96
|
+
if (lauf.takt !== null) $.clock.after(0, () => void takt($, lauf, true))
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Erfuellt sich, sobald ein Takt, der ab jetzt beginnt, seine Antwort hat (oder gleich, wenn keiner laeuft). */
|
|
100
|
+
export function naechsteAntwort($: EngineInterface, lauf: Lauf): Promise<void> {
|
|
101
|
+
if (lauf.takt === null) return Promise.resolve()
|
|
102
|
+
return new Promise(fertig => {
|
|
103
|
+
lauf.wartende.push(fertig)
|
|
104
|
+
sofort($, lauf)
|
|
105
|
+
})
|
|
106
|
+
}
|
|
107
|
+
|
|
87
108
|
async function starte($: EngineInterface, lauf: Lauf): Promise<void> {
|
|
88
109
|
// 🔴 `startet` steht VOR dem ersten `await`: `session.start` und `session.attach` kommen fast gleichzeitig, und
|
|
89
110
|
// beide bestanden sonst die Pruefung auf `takt` -- zwei Zeitgeber, einer davon nie angehalten (Befund der Durchsicht).
|
|
@@ -106,15 +127,27 @@ async function starte($: EngineInterface, lauf: Lauf): Promise<void> {
|
|
|
106
127
|
/** Die Sitzung (`generation`), fuer die gerade ein Lebenszeichen unterwegs ist -- je Sitzung hoechstens eines. */
|
|
107
128
|
let unterwegs: number | null = null
|
|
108
129
|
|
|
109
|
-
async function takt($: EngineInterface, lauf: Lauf): Promise<void> {
|
|
110
|
-
// Ein Takt, dessen Vorgaenger noch auf die App wartet (langsames Netz), faellt aus, statt sich zu stapeln.
|
|
130
|
+
async function takt($: EngineInterface, lauf: Lauf, sofort = false): Promise<void> {
|
|
131
|
+
// Ein Takt, dessen Vorgaenger noch auf die App wartet (langsames Netz), faellt aus, statt sich zu stapeln. Ein
|
|
132
|
+
// sofortiger (Zustand gewechselt, Satz zugestellt) merkt sich, dass gleich danach einer kommen muss -- hoechstens einer.
|
|
111
133
|
const generation = lauf.generation
|
|
112
|
-
if (lauf.beendet
|
|
134
|
+
if (lauf.beendet) return
|
|
135
|
+
if (unterwegs === generation) {
|
|
136
|
+
if (sofort) lauf.nachzuegler = true
|
|
137
|
+
return
|
|
138
|
+
}
|
|
113
139
|
unterwegs = generation
|
|
140
|
+
// Wer jetzt wartet, bekommt die Antwort DIESES Takts; wer erst waehrend des Abrufs dazukommt, die des Nachzueglers.
|
|
141
|
+
const wartende = lauf.wartende.splice(0)
|
|
114
142
|
try {
|
|
115
143
|
await einTakt($, lauf, generation)
|
|
116
144
|
} finally {
|
|
117
145
|
if (unterwegs === generation) unterwegs = null
|
|
146
|
+
for (const fertig of wartende) fertig()
|
|
147
|
+
if (lauf.nachzuegler && !lauf.beendet && lauf.generation === generation) {
|
|
148
|
+
lauf.nachzuegler = false
|
|
149
|
+
$.clock.after(0, () => void takt($, lauf, true))
|
|
150
|
+
}
|
|
118
151
|
}
|
|
119
152
|
}
|
|
120
153
|
|
|
@@ -141,7 +174,11 @@ async function einTakt($: EngineInterface, lauf: Lauf, generation: number): Prom
|
|
|
141
174
|
// Kam waehrend des Abrufs eine Meldung, ist seine Antwort zu „in Arbeit“ womoeglich von davor: sie zaehlt nicht.
|
|
142
175
|
const meldungen = lauf.meldungen
|
|
143
176
|
lauf.gemeldetFuer = zettel.projekt_id
|
|
144
|
-
|
|
177
|
+
// Der Zustand im Augenblick des Abschickens; aendert er sich waehrend des Abrufs, kommt der Nachzuegler (`takt`).
|
|
178
|
+
// `abgeholt` nur an das Projekt, dessen Klick zugestellt wurde (R-37: der Chat kann den Ordner gewechselt haben).
|
|
179
|
+
const abgeholt = lauf.abholen?.projekt === zettel.projekt_id ? lauf.abholen.nummer : null
|
|
180
|
+
const rumpf = { projektId: zettel.projekt_id, sitzung, zustand: zustandVon(lauf), faehig: true, ...(abgeholt === null ? {} : { abgeholt }) }
|
|
181
|
+
const antwort = await anApp($, lauf, '/api/mod/lebenszeichen', ausweis.schluessel, rumpf)
|
|
145
182
|
if (vorbei()) return
|
|
146
183
|
|
|
147
184
|
if (antwort?.status === 200) {
|
|
@@ -150,6 +187,16 @@ async function einTakt($: EngineInterface, lauf: Lauf, generation: number): Prom
|
|
|
150
187
|
if (gelesen === null) return fehlerSind($, lauf, ['nicht-erreichbar'])
|
|
151
188
|
if (lauf.meldungen === meldungen) lauf.inArbeit = gelesen.inArbeit
|
|
152
189
|
lauf.specDateien = gelesen.specDateien
|
|
190
|
+
const g = gelesen.gespraech
|
|
191
|
+
lauf.gespraech = g === null ? null : { ...g, projekt: zettel.projekt_id }
|
|
192
|
+
if (abgeholt !== null && lauf.abholen?.nummer === abgeholt) lauf.abholen = null
|
|
193
|
+
// R-33: Nennt die App einen Klick, den dieser Chat schon zugestellt hat, noch `abholbar`, hat das Eintragen nicht
|
|
194
|
+
// geklappt (oder die Antwort war von davor) -- das naechste Lebenszeichen sagt `abgeholt` noch einmal. Doppeltes
|
|
195
|
+
// Abholen ist harmlos (wer zuerst kommt, `abholen` in src/lib/planung/gespraech.ts); nie fuer „beendet“ (R-17).
|
|
196
|
+
if (g !== null && g.aktiv && g.abholbar && g.quelle === 'dennis' && g.nummer === (await zugestelltFuer($, zettel.projekt_id))) {
|
|
197
|
+
if (vorbei()) return
|
|
198
|
+
lauf.abholen = { projekt: zettel.projekt_id, nummer: g.nummer }
|
|
199
|
+
}
|
|
153
200
|
fehlerSind($, lauf, [])
|
|
154
201
|
if (lauf.meldungenPruefen) {
|
|
155
202
|
lauf.meldungenPruefen = false
|
|
@@ -163,6 +210,7 @@ async function einTakt($: EngineInterface, lauf: Lauf, generation: number): Prom
|
|
|
163
210
|
if (antwort?.status === 403) {
|
|
164
211
|
lauf.abgelehnt = zettel.projekt_id
|
|
165
212
|
lauf.inArbeit = null
|
|
213
|
+
lauf.gespraech = null
|
|
166
214
|
return fehlerSind($, lauf, [])
|
|
167
215
|
}
|
|
168
216
|
// `410`: diese Sitzung ist schon abgemeldet -- nie wiederbeleben, sonst kaeme der Punkt zurueck (Review Focus 5).
|
|
@@ -170,19 +218,24 @@ async function einTakt($: EngineInterface, lauf: Lauf, generation: number): Prom
|
|
|
170
218
|
lauf.takt?.cancel()
|
|
171
219
|
lauf.takt = null
|
|
172
220
|
lauf.inArbeit = null
|
|
221
|
+
lauf.gespraech = null
|
|
173
222
|
return fehlerSind($, lauf, [])
|
|
174
223
|
}
|
|
175
224
|
fehlerSind($, lauf, ['nicht-erreichbar'])
|
|
176
225
|
}
|
|
177
226
|
|
|
178
|
-
/**
|
|
179
|
-
|
|
227
|
+
/**
|
|
228
|
+
* `{ inArbeit, specDateien, gespraech }` aus der Antwort; ohne gueltiges `inArbeit` `null`. Fehlt `specDateien` (aeltere
|
|
229
|
+
* App): leer. Fehlt `gespraech` oder ist es `null` (App vor „Im Gespraech“): kein Gespraech -- das bricht nichts.
|
|
230
|
+
*/
|
|
231
|
+
function antwortAus(text: string): { inArbeit: boolean; specDateien: string[]; gespraech: GespraechFuerMod | null } | null {
|
|
180
232
|
try {
|
|
181
233
|
const o: unknown = JSON.parse(text)
|
|
182
234
|
const inArbeit = (o as { inArbeit?: unknown } | null)?.inArbeit
|
|
183
235
|
const dateien = (o as { specDateien?: unknown } | null)?.specDateien
|
|
184
236
|
if (typeof inArbeit !== 'boolean') return null
|
|
185
|
-
|
|
237
|
+
const gespraech = gespraechAus((o as { gespraech?: unknown }).gespraech)
|
|
238
|
+
return { inArbeit, specDateien: Array.isArray(dateien) ? dateien.filter((d): d is string => typeof d === 'string') : [], gespraech }
|
|
186
239
|
} catch {
|
|
187
240
|
return null
|
|
188
241
|
}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import type { Register } from 'claude-code'
|
|
2
2
|
import { erinnerung } from './erinnerung'
|
|
3
|
+
import { gespraech } from './gespraech'
|
|
3
4
|
import { neuerLauf } from './lauf'
|
|
4
5
|
import { lebenszeichen } from './lebenszeichen'
|
|
5
6
|
import { specText } from './spec-text'
|
|
6
7
|
|
|
7
8
|
// 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.
|
|
9
|
-
// (spec-text.ts), Claude ans Melden erinnern (erinnerung.ts)
|
|
9
|
+
// Projektseite aktuell. Vier Aufgaben, je eine Datei: Punkt am Leben halten (lebenszeichen.ts), Spec-Texte hochladen
|
|
10
|
+
// (spec-text.ts), Claude ans Melden erinnern (erinnerung.ts), „Im Gespraech“ -- ob Claude arbeitet, und Dennis' Klick auf
|
|
11
|
+
// dem Board an Claude weitergeben (gespraech.ts, Spec 2026-10-26-im-gespraech-design.md). Dem Menschen sagt er nur etwas, wenn etwas nicht klappt --
|
|
10
12
|
// eine Zeile in der Statuszeile (fehlerzeile.ts). Er startet kein `kimal3` (`$.process` gibt es in der Claude-App nicht)
|
|
11
13
|
// und spricht selbst mit der App, mit dem Geraeteschluessel aus `~/.kimal3/anmeldung.json`.
|
|
12
14
|
|
|
@@ -17,6 +19,7 @@ const SPRACHE: 'de' | 'en' = 'de'
|
|
|
17
19
|
export const register: Register = on => {
|
|
18
20
|
const lauf = neuerLauf(SPRACHE)
|
|
19
21
|
lebenszeichen(on, lauf)
|
|
22
|
+
gespraech(on, lauf)
|
|
20
23
|
erinnerung(on, lauf)
|
|
21
24
|
specText(on, lauf)
|
|
22
25
|
}
|
|
@@ -14,6 +14,10 @@ import { liesDatei, normiert, relativImOrdner } from './ordner'
|
|
|
14
14
|
*
|
|
15
15
|
* Hochgeladen wird nach dem Werkzeug und ohne Claude darauf warten zu lassen (`$.clock.after(0, …)`). Was per Skript
|
|
16
16
|
* entsteht (Bash, Python), sieht der Mod nicht; das kommt mit `/k3plan` (S-19).
|
|
17
|
+
*
|
|
18
|
+
* „Im Gespraech“ (Spec 2026-10-26-im-gespraech-design.md, E-2 Rang 3, G-8, G-13): Schreibt die HAUPTSITZUNG die Spec,
|
|
19
|
+
* geht ihre Kennung mit (`sitzung`) -- dann kommt die Spec von selbst ins Gespraech, wenn gerade keins laeuft. Ein Subagent
|
|
20
|
+
* (`e.agentId`) laedt den Text hoch, aber ohne Kennung: Er setzt nie ein Gespraech.
|
|
17
21
|
*/
|
|
18
22
|
export function specText(on: On, lauf: Lauf): void {
|
|
19
23
|
on('tool.call', { tool: ['Write', 'Edit'] }, async ($, e, next) => {
|
|
@@ -25,17 +29,18 @@ export function specText(on: On, lauf: Lauf): void {
|
|
|
25
29
|
const pfad = relativImOrdner(datei, lage.ordner, await $.session.cwd())
|
|
26
30
|
if (pfad === null || !lauf.specDateien.includes(pfad)) return ergebnis
|
|
27
31
|
const projektId = lage.zettel.projekt_id
|
|
28
|
-
|
|
32
|
+
const sitzung = e.agentId === undefined ? await $.session.id() : null
|
|
33
|
+
$.clock.after(0, () => void hochladen($, lauf, `${normiert(lage.ordner)}/${pfad}`, pfad, projektId, sitzung))
|
|
29
34
|
return ergebnis
|
|
30
35
|
})
|
|
31
36
|
}
|
|
32
37
|
|
|
33
|
-
async function hochladen($: EngineInterface, lauf: Lauf, datei: string, pfad: string, projektId: string): Promise<void> {
|
|
38
|
+
async function hochladen($: EngineInterface, lauf: Lauf, datei: string, pfad: string, projektId: string, sitzung: string | null): Promise<void> {
|
|
34
39
|
const text = await liesDatei($, datei)
|
|
35
40
|
const ausweis = await liesAusweis($, lauf)
|
|
36
41
|
// Ohne Ausweis meldet sich die Statuszeile schon ueber das Lebenszeichen (S-11, S-22).
|
|
37
42
|
if (text === null || 'fehler' in ausweis) return
|
|
38
|
-
const antwort = await anApp($, lauf, '/api/mod/spec-text', ausweis.schluessel, { projektId, pfad, text })
|
|
43
|
+
const antwort = await anApp($, lauf, '/api/mod/spec-text', ausweis.schluessel, { projektId, pfad, text, ...(sitzung === null ? {} : { sitzung }) })
|
|
39
44
|
if (antwort?.status === 200 || antwort?.status === 204) return fehlerSetzen($, lauf, { 'nicht-angemeldet': false, 'nicht-erreichbar': false })
|
|
40
45
|
if (antwort?.status === 401) return fehlerSetzen($, lauf, { 'nicht-angemeldet': true })
|
|
41
46
|
if (antwort === null || antwort.status >= 500) return fehlerSetzen($, lauf, { 'nicht-erreichbar': true })
|
|
@@ -5,6 +5,10 @@ declare module 'claude-code' {
|
|
|
5
5
|
letzteErinnerung: number | null
|
|
6
6
|
// Der Anstoss „Projekt anlegen“ ging in dieser Sitzung schon an Claude (hooks/erinnerung.ts, US-8).
|
|
7
7
|
anstossGegeben: boolean
|
|
8
|
+
// Je Projekt (`projekt_id`) die hoechste Nummer eines Gespraechs, deren Satz („angeklickt“/„beendet“) Claude in dieser
|
|
9
|
+
// Sitzung bekam; fehlt das Projekt: noch keine. In `$.state`, damit ein Neuladen des Mods denselben Satz nicht noch
|
|
10
|
+
// einmal zustellt (hooks/gespraech.ts, US-3, R-37).
|
|
11
|
+
zugestellt: Partial<Record<string, number>>
|
|
8
12
|
}
|
|
9
13
|
}
|
|
10
14
|
}
|
package/coach/de/kimal3-coach.md
CHANGED
|
@@ -148,20 +148,39 @@ Das Board in der App zeigt nur, was du meldest: Wer nicht meldet, lässt es stun
|
|
|
148
148
|
Legt es ein Projekt an, gibst du den **einen** Satz weiter, den es ausgibt („Ich habe das Projekt ‚…‘ in der App angelegt.“).
|
|
149
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
150
|
gibt es nur nach seinem Ja. Fehlt das Erfolgsprofil, sag den Satz einmal, arbeite weiter und melde nichts.
|
|
151
|
-
- **Du meldest zu
|
|
151
|
+
- **Du meldest zu sieben Zeitpunkten**, jedes Mal mit `kimal3 plan melde` (Eingabe: eine Meldung als JSON oder eine Liste,
|
|
152
152
|
z. B. `echo '[…]' | kimal3 plan melde`; Zeit und Sitzung setzt das Programm). Die Schlüssel von Spec und Story liest du mit
|
|
153
153
|
`kimal3 plan` aus der App — erfinde keine.
|
|
154
|
-
1. **Spec
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
1. **Eine Spec entsteht oder ihre Stories ändern sich:** Eine neue Spec meldest du, **sobald der Mensch sagt, dass eine Spec
|
|
155
|
+
entstehen soll** (oder deinem Vorschlag dazu zustimmt) — vor der ersten Rückfrage, vor dem ersten geschriebenen Satz, auch
|
|
156
|
+
ohne Stories. Schlüssel ist der Dateiname, den sie bekommt, ohne `.md`. Legst du sie dem Menschen zum Lesen vor (nach deinen
|
|
157
|
+
eigenen Prüfrunden), meldest du sie mit ihren Stories und `"wartet_auf_lesen":true` — an der Karte steht dann „Wartet auf
|
|
158
|
+
dein Lesen“. Sagt er „Passt“, meldest du sie mit `"wartet_auf_lesen":false`; sagt er, was nicht passt, bleibt das Etikett,
|
|
159
|
+
bis du überarbeitet hast und er „Passt“ sagt. Überarbeitest du sie nach „Passt“ so, dass er sie neu lesen muss, setzt du es
|
|
160
|
+
wieder. 🔑 **Eine Spec ist eine Karte:** keine Nebenkarten wie „Spec schreiben: …“ oder „Bauplan: …“, weder als
|
|
161
|
+
Spec noch als Thema.
|
|
162
|
+
`{"art":"spec","schluessel":"2026-10-24-testmonat-design","titel":"Testmonat","datei":"docs/superpowers/specs/2026-10-24-testmonat-design.md","stories":[]}`
|
|
163
|
+
`{"art":"spec","schluessel":"2026-10-24-testmonat-design","titel":"Testmonat","datei":"docs/superpowers/specs/2026-10-24-testmonat-design.md","stories":[{"schluessel":"US-1","titel":"Der Testmonat startet"}],"wartet_auf_lesen":true}`
|
|
164
|
+
2. **Das Thema im Chat ist eine Spec** (der Mensch spricht über eine, oder du legst eine an): wann immer das Thema zu einer
|
|
165
|
+
anderen Spec wechselt, nicht bei jeder Nachricht; im Zweifel melden — doppelt schadet nicht. An der Karte steht dann „Im
|
|
166
|
+
Gespräch“. Bei einer neuen Spec beides in einer Liste, die Spec zuerst. Sagt der Mensch im Chat, er sei damit fertig,
|
|
167
|
+
meldest du das Ende mit `"spec":null` und nennst in `"war"` immer die Spec, über die ihr gesprochen habt — hat der Mensch
|
|
168
|
+
inzwischen eine andere Karte angeklickt, beendet deine Meldung dann nicht deren Gespräch. Immer mit Sitzung, also ohne
|
|
169
|
+
`--ohne-sitzung`. Sagt die App „Über diese Karte spricht gerade ein anderer Chat.“: nichts tun, das Gespräch trägt ein
|
|
170
|
+
anderer Chat. Sagt sie „… braucht den Chat, aus dem sie kommt.“: mit Sitzung noch einmal melden, nicht mit
|
|
171
|
+
`--ohne-sitzung`. Sagt sie „Dieser Chat hat den alten Board-Mod.“: dem Menschen sagen, er soll `/reload-plugins` tippen
|
|
172
|
+
oder Claude neu starten, danach noch einmal melden. Sagt sie „Dieser Chat ist nicht mehr offen“ (eine gemerkte Meldung kam
|
|
173
|
+
erst später an): nichts tun — gilt das Thema noch, meldest du es aus dem Chat, in dem ihr gerade sprecht.
|
|
174
|
+
`{"art":"gespraech","spec":"2026-10-24-testmonat-design"}` · `{"art":"gespraech","spec":null,"war":"2026-10-24-testmonat-design"}`
|
|
175
|
+
3. **Der Bau einer Spec beginnt:** die Stories, die jetzt gebaut werden, auf „in Arbeit“ — **bevor** du den ersten Bau-Schritt oder
|
|
157
176
|
einen Subagenten losschickst:
|
|
158
|
-
`{"art":"story","spec":"2026-10-24-testmonat","story":"US-1","stand":"in_arbeit"}`
|
|
159
|
-
|
|
177
|
+
`{"art":"story","spec":"2026-10-24-testmonat-design","story":"US-1","stand":"in_arbeit"}`
|
|
178
|
+
4. **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
179
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
180
|
+
`{"art":"story","spec":"2026-10-24-testmonat-design","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."}`
|
|
181
|
+
5. **Etwas fällt auf, das keine Spec verspricht:** `{"art":"thema","schluessel":"T-4","text":"Der Kalender könnte Feiertage zeigen"}`
|
|
182
|
+
6. **`/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“, mit `"pruefen":true` — so kommt die Karte dabei nicht ins Gespräch. Stories in „Dein Test“ oder „Fertig“ meldest du beim Abgleich nicht neu.
|
|
183
|
+
7. **Feierabend:** den Tag abgleichen — dort steht, wie.
|
|
165
184
|
|
|
166
185
|
🔑 **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
186
|
|
|
@@ -174,7 +193,9 @@ Das Board in der App zeigt nur, was du meldest: Wer nicht meldet, lässt es stun
|
|
|
174
193
|
und geht beim nächsten Befehl mit, schick sie nicht noch einmal. `2` heißt nicht angemeldet: Sag ihm `kimal3 login`.
|
|
175
194
|
- **Sätze, die nur du bekommst** (vom Mod `kimal3-plan`, der Mensch sieht sie nicht): die Erinnerung, eine Story als „in Arbeit“
|
|
176
195
|
zu melden oder die Änderung als Thema zu notieren · „leg das Projekt mit `kimal3 projekt verbinden` an“ · „gemerkte Meldungen
|
|
177
|
-
warten — `kimal3 plan senden
|
|
196
|
+
warten — `kimal3 plan senden`“ · der Mensch hat auf dem Board eine Karte zum Besprechen angeklickt oder das Gespräch beendet
|
|
197
|
+
(dann sprichst du über diese Spec bzw. nicht mehr; ein `gespraech` meldest du dafür nicht). Handle danach, ohne dem Menschen
|
|
198
|
+
davon zu erzählen.
|
|
178
199
|
|
|
179
200
|
## 4 · Erlaubnis und Sicherheit
|
|
180
201
|
- **Bevor eine Aktion bestätigt werden muss**, erkläre in Alltagssprache: **was** passiert (die Wirkung, nicht der
|
package/coach/en/kimal3-coach.md
CHANGED
|
@@ -148,20 +148,38 @@ The board in the app shows only what you report: if you don't, it shows "Open" f
|
|
|
148
148
|
If it creates a project, you pass on the **one** sentence it prints ("I created the project '…' in the app.").
|
|
149
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
150
|
only after their yes. If the success profile is missing, say the sentence once, keep working and report nothing.
|
|
151
|
-
- **You report at
|
|
151
|
+
- **You report at seven moments**, each time with `kimal3 plan report` (input: one report as JSON or a list,
|
|
152
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
153
|
with `kimal3 plan` — don't invent them.
|
|
154
|
-
1. **A spec
|
|
155
|
-
|
|
156
|
-
|
|
154
|
+
1. **A spec comes into being or its stories change:** you report a new spec **as soon as the person says a spec should be
|
|
155
|
+
written** (or agrees to your suggestion of one) — before the first question back, before the first written sentence, even
|
|
156
|
+
without stories. The key is the file name it will get, without `.md`. When you hand it to the person to read (after your own
|
|
157
|
+
review rounds), you report it with its stories and `"wartet_auf_lesen":true` — the card then shows "Waiting for you to
|
|
158
|
+
read". If they say it works, you report it with `"wartet_auf_lesen":false`; if they say what doesn't work, the label stays
|
|
159
|
+
until you have revised it and they say it works. If you revise it after "works" so that they have to read it again, you set
|
|
160
|
+
it again. 🔑 **A spec is one card:** no side cards like "Write spec: …" or "Build plan: …", neither as a spec nor as a topic.
|
|
161
|
+
`{"art":"spec","schluessel":"2026-10-24-test-month-design","titel":"Test month","datei":"docs/superpowers/specs/2026-10-24-test-month-design.md","stories":[]}`
|
|
162
|
+
`{"art":"spec","schluessel":"2026-10-24-test-month-design","titel":"Test month","datei":"docs/superpowers/specs/2026-10-24-test-month-design.md","stories":[{"schluessel":"US-1","titel":"The test month starts"}],"wartet_auf_lesen":true}`
|
|
163
|
+
2. **The topic in the chat is a spec** (the person talks about one, or you are creating one): whenever the topic switches to
|
|
164
|
+
another spec, not with every message; when in doubt, report — sending twice does no harm. The card then shows "In
|
|
165
|
+
discussion". For a new spec, both in one list, the spec first. If the person says in the chat that they are done with it,
|
|
166
|
+
you report the end with `"spec":null` and always name the spec you were discussing in `"war"` — if the person has clicked
|
|
167
|
+
another card in the meantime, your report then doesn't end that card's discussion. Always with the session, so without
|
|
168
|
+
`--no-session`. If the app says "Another chat is discussing this card right now.": do nothing, another chat carries the
|
|
169
|
+
discussion. If it says "… needs the chat it comes from.": report again with the session, not with `--no-session`. If it
|
|
170
|
+
says "This chat has the old board mod.": tell the person to type `/reload-plugins` or restart Claude, then report again.
|
|
171
|
+
If it says "This chat is no longer open" (a stored report arrived later): do nothing — if the topic still applies,
|
|
172
|
+
report it from the chat you are talking in now.
|
|
173
|
+
`{"art":"gespraech","spec":"2026-10-24-test-month-design"}` · `{"art":"gespraech","spec":null,"war":"2026-10-24-test-month-design"}`
|
|
174
|
+
3. **The build of a spec begins:** the stories being built now go to "in progress" — **before** you take the first build step or
|
|
157
175
|
send off a subagent:
|
|
158
|
-
`{"art":"story","spec":"2026-10-24-test-month","story":"US-1","stand":"in_arbeit"}`
|
|
159
|
-
|
|
176
|
+
`{"art":"story","spec":"2026-10-24-test-month-design","story":"US-1","stand":"in_arbeit"}`
|
|
177
|
+
4. **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
178
|
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
|
-
|
|
163
|
-
|
|
164
|
-
|
|
179
|
+
`{"art":"story","spec":"2026-10-24-test-month-design","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."}`
|
|
180
|
+
5. **Something comes up that no spec promises:** `{"art":"thema","schluessel":"T-4","text":"The calendar could show public holidays"}`
|
|
181
|
+
6. **`/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, with `"pruefen":true` — so the card doesn't come into discussion meanwhile. Stories under "Your test" or "Done" you do not report again during the reconciliation.
|
|
182
|
+
7. **Wrap-up:** reconcile the day — it says how.
|
|
165
183
|
|
|
166
184
|
🔑 **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
185
|
|
|
@@ -174,7 +192,8 @@ The board in the app shows only what you report: if you don't, it shows "Open" f
|
|
|
174
192
|
and goes along with the next command, don't send it again. `2` means not signed in: tell them `kimal3 login`.
|
|
175
193
|
- **Sentences only you get** (from the mod `kimal3-plan`; the person doesn't see them): the reminder to report a story as "in
|
|
176
194
|
progress" or to note the change as a topic · "create the project with `kimal3 project connect`" · "saved reports are
|
|
177
|
-
waiting — `kimal3 plan send`"
|
|
195
|
+
waiting — `kimal3 plan send`" · the person clicked a card on the board to discuss it, or ended the discussion (then you talk
|
|
196
|
+
about that spec, or no longer; you don't report a `gespraech` for it). Act on them without telling the person.
|
|
178
197
|
|
|
179
198
|
## 4 · Permission and safety
|
|
180
199
|
- **Before an action has to be confirmed**, explain in everyday language: **what** happens (the effect, not the
|
|
@@ -40,6 +40,8 @@ export function meldungFehler(m) {
|
|
|
40
40
|
case "spec":
|
|
41
41
|
if (!istText(m.schluessel) || !istText(m.titel) || !istTextOderNull(m.datei) || !Array.isArray(m.stories))
|
|
42
42
|
return "meldung";
|
|
43
|
+
if (m.wartet_auf_lesen !== undefined && typeof m.wartet_auf_lesen !== "boolean")
|
|
44
|
+
return "meldung";
|
|
43
45
|
return m.stories.every((s) => istObjekt(s) && istText(s.schluessel) && istText(s.titel)) ? null : "meldung";
|
|
44
46
|
case "story":
|
|
45
47
|
if (!istText(m.spec) || !istText(m.story) || typeof m.stand !== "string" || !STAENDE.includes(m.stand))
|
|
@@ -50,10 +52,13 @@ export function meldungFehler(m) {
|
|
|
50
52
|
return "meldung";
|
|
51
53
|
if (m.befunde !== undefined && !Array.isArray(m.befunde))
|
|
52
54
|
return "meldung";
|
|
55
|
+
if (m.pruefen !== undefined && typeof m.pruefen !== "boolean")
|
|
56
|
+
return "meldung";
|
|
53
57
|
return (m.testen === undefined || istTextOderNull(m.testen)) && (m.grund === undefined || istTextOderNull(m.grund)) ? null : "meldung";
|
|
54
58
|
case "thema": return istText(m.schluessel) && istText(m.text) ? null : "meldung";
|
|
55
59
|
case "uebernommen": return Number.isInteger(m.rueckmeldung) && istText(m.antwort) ? null : "meldung";
|
|
56
60
|
case "eingeordnet": return istText(m.schluessel) && istText(m.text) && istText(m.antwort) ? null : "meldung";
|
|
61
|
+
case "gespraech": return istTextOderNull(m.spec) && (m.war === undefined || (istText(m.war) && m.spec === null)) ? null : "meldung";
|
|
57
62
|
default: return "art";
|
|
58
63
|
}
|
|
59
64
|
}
|
package/dist/texte/meldungen.js
CHANGED
|
@@ -29,7 +29,7 @@ const de = {
|
|
|
29
29
|
Meldet der Projektseite eine Änderung: liest eine Meldung oder eine Liste
|
|
30
30
|
von Meldungen als JSON von der Eingabe, zum Beispiel
|
|
31
31
|
echo '{"art":"story","spec":"testmonat","story":"US-2","stand":"in_arbeit"}' | kimal3 plan melde
|
|
32
|
-
Arten: spec, story, thema, uebernommen, eingeordnet.
|
|
32
|
+
Arten: spec, story, thema, uebernommen, eingeordnet, gespraech.
|
|
33
33
|
|
|
34
34
|
--ohne-sitzung Meldet ohne die Nummer der Claude-Sitzung (für das
|
|
35
35
|
Prüfen von Specs): Auf dem Board leuchtet kein Punkt.
|
|
@@ -91,7 +91,7 @@ const en = {
|
|
|
91
91
|
Reports a change to the project page: reads one report or a list of
|
|
92
92
|
reports as JSON from the input, for example
|
|
93
93
|
echo '{"art":"story","spec":"testmonat","story":"US-2","stand":"in_arbeit"}' | kimal3 plan report
|
|
94
|
-
Kinds (field "art"): spec, story, thema, uebernommen, eingeordnet.
|
|
94
|
+
Kinds (field "art"): spec, story, thema, uebernommen, eingeordnet, gespraech.
|
|
95
95
|
|
|
96
96
|
--no-session Reports without the Claude session number (for checking
|
|
97
97
|
specs): no dot lights up on the board.
|
package/package.json
CHANGED
|
@@ -28,7 +28,7 @@ und überspring ihn — kein Leerlauf.
|
|
|
28
28
|
`~/.claude/skills/kimal3/SKILL.md`):
|
|
29
29
|
|
|
30
30
|
```json
|
|
31
|
-
[ { "art": "story", "spec": "2026-10-24-testmonat", "story": "US-1", "stand": "gebaut",
|
|
31
|
+
[ { "art": "story", "spec": "2026-10-24-testmonat-design", "story": "US-1", "stand": "gebaut",
|
|
32
32
|
"kriterien": [ { "text": "Startet Sabine am 1.11., endet er am 30.11.", "gegenprobe": "bestanden" } ],
|
|
33
33
|
"testen": "1. Öffne /testmonat.\n2. Klick „Starten“.\n3. Darunter steht „Endet am 30.11.“.\nPasst nicht, wenn: dort ein anderes Datum steht." } ]
|
|
34
34
|
```
|
|
@@ -36,7 +36,7 @@ und überspring ihn — kein Leerlauf.
|
|
|
36
36
|
Und was unfertig bleibt, getrennt davon, mit dem Schalter:
|
|
37
37
|
|
|
38
38
|
```json
|
|
39
|
-
[ { "art": "story", "spec": "2026-10-24-testmonat", "story": "US-2", "stand": "in_arbeit" } ]
|
|
39
|
+
[ { "art": "story", "spec": "2026-10-24-testmonat-design", "story": "US-2", "stand": "in_arbeit" } ]
|
|
40
40
|
```
|
|
41
41
|
|
|
42
42
|
```
|
package/skill/commands/k3plan.md
CHANGED
|
@@ -43,7 +43,7 @@ Du löschst sie nicht selbst.
|
|
|
43
43
|
**Was die Ausgabe heißt** (Beispiel, erfundene Werte):
|
|
44
44
|
|
|
45
45
|
```
|
|
46
|
-
Spec 2026-09-23-content-planer · Content-Planer als Seite (plan/2026-09-23-content-planer
|
|
46
|
+
Spec 2026-09-23-content-planer · Content-Planer als Seite (plan/2026-09-23-content-planer.md)
|
|
47
47
|
US-1 · gebaut · Runde 1 · Beiträge anlegen
|
|
48
48
|
US-2 · in Arbeit · Runde 1 · Beiträge im Kalender verschieben
|
|
49
49
|
Notiertes Thema T-3: Export als PDF
|
|
@@ -130,7 +130,7 @@ Gegenprobe laufen kann.
|
|
|
130
130
|
|
|
131
131
|
**Der `testen`-Text** (bei jeder `gebaut`-Story, höchstens 1000 Zeichen) ist eine Anleitung für jemanden ohne Technik. Steht die Story in einer Spec mit der Kiste „So testest du es“, nimmst du ihn von dort, wörtlich; fehlt die Kiste, schreibst du ihn selbst. **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: …“. Im JSON trennst du die Zeilen mit `\n`: Ein echter Zeilenumbruch im Text bricht das JSON, und `kimal3 plan melde` liest die Meldung nicht. Ist der Text länger als 1000 Zeichen, kürze die Schritte — nie mitten im Satz abschneiden, die Zeile „Passt nicht, wenn: …“ muss am Ende stehen bleiben. 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. Fehlt der Text, zeigt die App: „Claude hat nicht aufgeschrieben, wie man das testet".
|
|
132
132
|
|
|
133
|
-
**Prüfen ist Arbeit.** Bevor du eine Story mit offenen Gegenproben (Spalte „Claude-Test offen“) gegen den Code prüfst, meldest du sie als `in_arbeit` **mit** der Sitzung (also ohne `--ohne-sitzung`): Solange du prüfst, leuchtet der Punkt am Projekt. Nach der Prüfung meldest du das Ergebnis (`gebaut`, oder `in_arbeit` mit Befund). Stories in „Dein Test“ oder „Fertig“ meldest du beim Abgleich **nicht** neu, auch wenn du sie durchsiehst: `in_arbeit` und danach `gebaut` setzt ihr Baudatum auf heute, und jede Meldung zeigt das Etikett „geändert“ und bei zwei Sitzungen das ⚠ „gleichzeitig geändert“. Was am Ende von `/k3plan` unfertig bleibt, meldest du mit `--ohne-sitzung` (Abschnitt 6).
|
|
133
|
+
**Prüfen ist Arbeit.** Bevor du eine Story mit offenen Gegenproben (Spalte „Claude-Test offen“) gegen den Code prüfst, meldest du sie als `in_arbeit` **mit** der Sitzung (also ohne `--ohne-sitzung`) **und mit `"pruefen": true`**: Solange du prüfst, leuchtet der Punkt am Projekt, aber die Karte kommt dadurch nicht ins Gespräch — ohne das Feld wanderte der blaue Rahmen durch jede Spec, die du prüfst. Nach der Prüfung meldest du das Ergebnis (`gebaut`, oder `in_arbeit` mit Befund — jede `in_arbeit`-Meldung aus `/k3plan` trägt `"pruefen": true`). Stories in „Dein Test“ oder „Fertig“ meldest du beim Abgleich **nicht** neu, auch wenn du sie durchsiehst: `in_arbeit` und danach `gebaut` setzt ihr Baudatum auf heute, und jede Meldung zeigt das Etikett „geändert“ und bei zwei Sitzungen das ⚠ „gleichzeitig geändert“. Was am Ende von `/k3plan` unfertig bleibt, meldest du mit `--ohne-sitzung` (Abschnitt 6).
|
|
134
134
|
|
|
135
135
|
**Wie eine Spec aufgebaut ist.** Findest du beim Prüfen eine Spec, die nicht so gebaut ist, sag es im Bericht — du schreibst sie nicht um. Eine Story steht darin in vier Kisten: **User Story** (der Titel nennt das Ziel des Menschen) · **Akzeptanzkriterium** (mit Gegenprobe) · **So testest du es** (nummerierte Schritte, darunter „Passt nicht, wenn: …“) · **Technische Notiz**.
|
|
136
136
|
|
|
@@ -165,7 +165,7 @@ Die App zeigt sie dem Menschen in genau diesem Wortlaut („So bleibt die Planun
|
|
|
165
165
|
**Was du danach tust** (jedes Mal eine Meldung, Abschnitt 6):
|
|
166
166
|
|
|
167
167
|
- **Regel 1 — jedes `passt nicht` aus `kimal3 plan`:** Gibt es die Story noch, melde sie mit dem Satz
|
|
168
|
-
des Menschen als Befund (`quelle: "mensch"`, `seit: "<heute>"`)
|
|
168
|
+
des Menschen als Befund (`quelle: "mensch"`, `seit: "<heute>"`), `stand: "in_arbeit"` und `"pruefen": true`, und
|
|
169
169
|
gleich dahinter in derselben Liste `uebernommen` mit der Nummer: `{ "art": "uebernommen",
|
|
170
170
|
"rueckmeldung": 12, "antwort": "Befund an US-2" }`. Ist sie seitdem schon nachgebessert,
|
|
171
171
|
stattdessen `runde` + 1, `befunde: []` und `stand: "gebaut"` (Abschnitt 4). Gibt es die Story
|
|
@@ -207,7 +207,7 @@ Die App zeigt sie dem Menschen in genau diesem Wortlaut („So bleibt die Planun
|
|
|
207
207
|
|
|
208
208
|
Jede Meldung ist ein JSON-Objekt, mehrere eine Liste. Sie geht über die Eingabe an
|
|
209
209
|
`kimal3 plan melde`; das Programm setzt die Zeit selbst, das Projekt kommt vom Ordnerzettel
|
|
210
|
-
`plan/projekt.json`. Prüfen ist Arbeit — mit der Sitzung gemeldet leuchtet auf dem Board der Punkt, solange du prüfst.
|
|
210
|
+
`plan/projekt.json`. Prüfen ist Arbeit — mit der Sitzung gemeldet leuchtet auf dem Board der Punkt, solange du prüfst; `"pruefen": true` an jeder `in_arbeit`-Meldung hält die Karte dabei aus dem Gespräch.
|
|
211
211
|
|
|
212
212
|
```
|
|
213
213
|
echo '[ … ]' | kimal3 plan melde
|
|
@@ -229,17 +229,21 @@ echo '[ … ]' | kimal3 plan melde --ohne-sitzung
|
|
|
229
229
|
**Eine Spec** — beim Prüfen und wenn Stories dazukommen:
|
|
230
230
|
|
|
231
231
|
```json
|
|
232
|
-
{ "art": "spec", "schluessel": "2026-10-24-testmonat", "titel": "Testmonat",
|
|
232
|
+
{ "art": "spec", "schluessel": "2026-10-24-testmonat-design", "titel": "Testmonat",
|
|
233
233
|
"datei": "docs/superpowers/specs/2026-10-24-testmonat-design.md",
|
|
234
234
|
"stories": [ { "schluessel": "US-1", "titel": "Der Testmonat startet" },
|
|
235
235
|
{ "schluessel": "US-2", "titel": "Der Testmonat endet" },
|
|
236
236
|
{ "schluessel": "US-3", "titel": "Export des Testmonats" } ] }
|
|
237
237
|
```
|
|
238
238
|
|
|
239
|
+
Lass dabei `wartet_auf_lesen` weg: Fehlt das Feld, bleibt das Etikett „Wartet auf dein Lesen“, wie es ist. Gesetzt und
|
|
240
|
+
weggenommen wird es, wenn du eine Spec dem Menschen zum Lesen vorlegst und er sie freigibt (Coach, Abschnitt 3.6) — genauso die Meldung
|
|
241
|
+
`gespraech`; `/k3plan` schickt keine.
|
|
242
|
+
|
|
239
243
|
**Eine Story** — eine Abweichung, ein Eintrag. Gebaut, mit Gegenprobe:
|
|
240
244
|
|
|
241
245
|
```json
|
|
242
|
-
{ "art": "story", "spec": "2026-10-24-testmonat", "story": "US-1", "stand": "gebaut",
|
|
246
|
+
{ "art": "story", "spec": "2026-10-24-testmonat-design", "story": "US-1", "stand": "gebaut",
|
|
243
247
|
"kriterien": [ { "text": "Startet Sabine am 1.11., endet der Testmonat am 30.11.", "gegenprobe": "bestanden" } ],
|
|
244
248
|
"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." }
|
|
245
249
|
```
|
|
@@ -247,14 +251,14 @@ echo '[ … ]' | kimal3 plan melde --ohne-sitzung
|
|
|
247
251
|
Mit einem offenen Befund (hier von dir; `quelle: "mensch"` nur für den Satz des Menschen):
|
|
248
252
|
|
|
249
253
|
```json
|
|
250
|
-
{ "art": "story", "spec": "2026-10-24-testmonat", "story": "US-2", "stand": "in_arbeit",
|
|
254
|
+
{ "art": "story", "spec": "2026-10-24-testmonat-design", "story": "US-2", "stand": "in_arbeit", "pruefen": true,
|
|
251
255
|
"befunde": [ { "text": "Am 30.11. fehlt die Erinnerungsmail, die die Spec verspricht.", "quelle": "claude", "seit": "2026-10-23" } ] }
|
|
252
256
|
```
|
|
253
257
|
|
|
254
258
|
Bewusst weggelassen (`grund` ist Pflicht):
|
|
255
259
|
|
|
256
260
|
```json
|
|
257
|
-
{ "art": "story", "spec": "2026-10-24-testmonat", "story": "US-3", "stand": "weggelassen", "grund": "Die Spec streicht den Export." }
|
|
261
|
+
{ "art": "story", "spec": "2026-10-24-testmonat-design", "story": "US-3", "stand": "weggelassen", "grund": "Die Spec streicht den Export." }
|
|
258
262
|
```
|
|
259
263
|
|
|
260
264
|
**Ein Thema** (Regel 2), **ein Thema von dir einordnen** (Regel 3), **eine Rückmeldung des Menschen
|
|
@@ -284,6 +288,8 @@ angemeldet: Sag dem Menschen `kimal3 login`; die Meldung ist gemerkt.
|
|
|
284
288
|
| `Rückmeldung 12 gibt es in diesem Projekt nicht oder steht zweimal …` | Nur Nummern aus `kimal3 plan`, jede einmal. |
|
|
285
289
|
| `Rückmeldung 12 („…“) ist als übernommen gemeldet, aber US-2 hat keinen Befund von dir …` | Erst die Story mit dem Befund `quelle: "mensch"` melden (Regel 1) — oder `runde` + 1, wenn nachgebessert ist — dann `uebernommen`. |
|
|
286
290
|
| `Der Plan ist nicht lesbar: <Ort> …` | Die genannte Stelle der Meldung berichtigen (Länge, `grund` bei `weggelassen`, doppelter Schlüssel). |
|
|
291
|
+
| `Die Meldung „Im Gespräch“ braucht den Chat, aus dem sie kommt.` | Mit Sitzung noch einmal melden, nicht mit `--ohne-sitzung`. |
|
|
292
|
+
| `Über diese Karte spricht gerade ein anderer Chat.` | Nichts tun: Das Gespräch trägt ein anderer Chat. |
|
|
287
293
|
| `Die Meldung ist angekommen, der Text von <Datei> aber nicht …` | Die Meldung noch einmal schicken — doppelt schadet nicht. Passt der Text nicht mehr (über 300 KB), sag es dem Menschen. |
|
|
288
294
|
| `Die Meldungen sind gespeichert, aber noch nicht ganz vermerkt.` | Noch einmal schicken — doppelt schadet nicht. |
|
|
289
295
|
| `Die gespeicherte Planung in der App ist beschädigt …` | Sag es dem Menschen. `kimal3 plan hoch --ueberschreiben` ersetzt die Planung durch eine ganze Datei `plan/stand.json` — **nur nach seinem ausdrücklichen Ja.** |
|
|
@@ -41,7 +41,7 @@ not needed any more — the state is in the app. You can delete it." You do not
|
|
|
41
41
|
**What the output means** (example, made-up values):
|
|
42
42
|
|
|
43
43
|
```
|
|
44
|
-
Spec 2026-09-23-content-planner · Content planner as a page (plan/2026-09-23-content-planner
|
|
44
|
+
Spec 2026-09-23-content-planner · Content planner as a page (plan/2026-09-23-content-planner.md)
|
|
45
45
|
US-1 · built · round 1 · Create posts
|
|
46
46
|
US-2 · in progress · round 1 · Move posts in the calendar
|
|
47
47
|
Noted topic T-3: Export as PDF
|
|
@@ -125,7 +125,7 @@ run.
|
|
|
125
125
|
|
|
126
126
|
**The `testen` text** (on every `gebaut` story, at most 1000 characters) is a guide for someone without technical knowledge. If the story sits in a spec with the box "How you test it", you take it from there, word for word; if the box is missing, you write it yourself. **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. If the text is longer than 1000 characters, shorten the steps — never cut off in the middle of a sentence; the line "Doesn't work if: …" must stay at the end. 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. If the text is missing, the app shows: "Claude didn't write down how to test this".
|
|
127
127
|
|
|
128
|
-
**Checking is work.** Before you check a story with pending counter-checks (column "Claude test pending") against the code, you report it as `in_arbeit` **with** the session (so without `--no-session`): while you check, the dot at the project glows. After the check you report the result (`gebaut`, or `in_arbeit` with a finding). Stories under "Your test" or "Done" you do **not** report again during the reconciliation, even if you look through them: `in_arbeit` followed by `gebaut` sets their build date to today, and every report shows the label "changed" and, with two sessions, the ⚠ "changed at the same time". What stays unfinished at the end of `/k3plan`, you report with `--no-session` (section 6).
|
|
128
|
+
**Checking is work.** Before you check a story with pending counter-checks (column "Claude test pending") against the code, you report it as `in_arbeit` **with** the session (so without `--no-session`) **and with `"pruefen": true`**: while you check, the dot at the project glows, but the card doesn't come into discussion — without the field the blue frame would wander through every spec you check. After the check you report the result (`gebaut`, or `in_arbeit` with a finding — every `in_arbeit` report from `/k3plan` carries `"pruefen": true`). Stories under "Your test" or "Done" you do **not** report again during the reconciliation, even if you look through them: `in_arbeit` followed by `gebaut` sets their build date to today, and every report shows the label "changed" and, with two sessions, the ⚠ "changed at the same time". What stays unfinished at the end of `/k3plan`, you report with `--no-session` (section 6).
|
|
129
129
|
|
|
130
130
|
**How a spec is built.** If you find a spec while checking that is not built like this, say so in the report — you do not rewrite it. A story in it sits in four boxes: **User story** (the title names the person's goal) · **Acceptance criterion** (with counter-check) · **How you test it** (numbered steps, below them "Doesn't work if: …") · **Technical note**.
|
|
131
131
|
|
|
@@ -160,8 +160,8 @@ The app shows them to the person in exactly this wording ("How planning stays sh
|
|
|
160
160
|
**What you do with them** (every time a report, section 6):
|
|
161
161
|
|
|
162
162
|
- **Rule 1 — every `doesn't work` from `kimal3 plan`:** if the story still exists, report it with
|
|
163
|
-
the person's sentence as a finding (`quelle: "mensch"`, `seit: "<today>"`)
|
|
164
|
-
`stand: "in_arbeit"`, and right behind it in the same list `uebernommen` with the number:
|
|
163
|
+
the person's sentence as a finding (`quelle: "mensch"`, `seit: "<today>"`),
|
|
164
|
+
`stand: "in_arbeit"` and `"pruefen": true`, and right behind it in the same list `uebernommen` with the number:
|
|
165
165
|
`{ "art": "uebernommen", "rueckmeldung": 12, "antwort": "Finding on US-2" }`. If it has already
|
|
166
166
|
been fixed since, raise `runde` by 1 instead, with `befunde: []` and `stand: "gebaut"`
|
|
167
167
|
(section 4). If the story no longer exists, the `antwort` says why. ⚠️ Without a finding from
|
|
@@ -202,7 +202,7 @@ The app shows them to the person in exactly this wording ("How planning stays sh
|
|
|
202
202
|
|
|
203
203
|
A report is a JSON object, several are a list. It goes through the input to `kimal3 plan report`;
|
|
204
204
|
the program sets the time itself, and the project comes from the folder note
|
|
205
|
-
`plan/projekt.json`. Checking is work — reported with the session, the dot on the board glows while you check.
|
|
205
|
+
`plan/projekt.json`. Checking is work — reported with the session, the dot on the board glows while you check; `"pruefen": true` on every `in_arbeit` report keeps the card out of discussion meanwhile.
|
|
206
206
|
|
|
207
207
|
```
|
|
208
208
|
echo '[ … ]' | kimal3 plan report
|
|
@@ -224,17 +224,21 @@ echo '[ … ]' | kimal3 plan report --no-session
|
|
|
224
224
|
**A spec** — when checking, and when stories are added:
|
|
225
225
|
|
|
226
226
|
```json
|
|
227
|
-
{ "art": "spec", "schluessel": "2026-10-24-test-month", "titel": "Test month",
|
|
227
|
+
{ "art": "spec", "schluessel": "2026-10-24-test-month-design", "titel": "Test month",
|
|
228
228
|
"datei": "docs/superpowers/specs/2026-10-24-test-month-design.md",
|
|
229
229
|
"stories": [ { "schluessel": "US-1", "titel": "The test month starts" },
|
|
230
230
|
{ "schluessel": "US-2", "titel": "The test month ends" },
|
|
231
231
|
{ "schluessel": "US-3", "titel": "Export of the test month" } ] }
|
|
232
232
|
```
|
|
233
233
|
|
|
234
|
+
Leave out `wartet_auf_lesen` here: if the field is missing, the label "Waiting for you to read" stays as it is. It is set
|
|
235
|
+
and removed when you hand a spec to the person to read and they approve it (coach, section 3.6) — the same goes for the report
|
|
236
|
+
`gespraech`; `/k3plan` sends none.
|
|
237
|
+
|
|
234
238
|
**A story** — one deviation, one entry. Built, with a counter-check:
|
|
235
239
|
|
|
236
240
|
```json
|
|
237
|
-
{ "art": "story", "spec": "2026-10-24-test-month", "story": "US-1", "stand": "gebaut",
|
|
241
|
+
{ "art": "story", "spec": "2026-10-24-test-month-design", "story": "US-1", "stand": "gebaut",
|
|
238
242
|
"kriterien": [ { "text": "If Sabine starts on 1 Nov, the test month ends on 30 Nov.", "gegenprobe": "bestanden" } ],
|
|
239
243
|
"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." }
|
|
240
244
|
```
|
|
@@ -242,14 +246,14 @@ echo '[ … ]' | kimal3 plan report --no-session
|
|
|
242
246
|
With an open finding (here from you; `quelle: "mensch"` only for the person's sentence):
|
|
243
247
|
|
|
244
248
|
```json
|
|
245
|
-
{ "art": "story", "spec": "2026-10-24-test-month", "story": "US-2", "stand": "in_arbeit",
|
|
249
|
+
{ "art": "story", "spec": "2026-10-24-test-month-design", "story": "US-2", "stand": "in_arbeit", "pruefen": true,
|
|
246
250
|
"befunde": [ { "text": "On 30 Nov the reminder mail the spec promises is missing.", "quelle": "claude", "seit": "2026-10-23" } ] }
|
|
247
251
|
```
|
|
248
252
|
|
|
249
253
|
Deliberately left out (`grund` is required):
|
|
250
254
|
|
|
251
255
|
```json
|
|
252
|
-
{ "art": "story", "spec": "2026-10-24-test-month", "story": "US-3", "stand": "weggelassen", "grund": "The spec drops the export." }
|
|
256
|
+
{ "art": "story", "spec": "2026-10-24-test-month-design", "story": "US-3", "stand": "weggelassen", "grund": "The spec drops the export." }
|
|
253
257
|
```
|
|
254
258
|
|
|
255
259
|
**A topic** (rule 2), **sorting a topic of yours** (rule 3), **handing on a response from the
|
|
@@ -278,6 +282,8 @@ send it again. `2` — no longer signed in: tell the person `kimal3 login`; the
|
|
|
278
282
|
| `Response 12 doesn't exist in this project or is listed twice …` | Only numbers from `kimal3 plan`, each once. |
|
|
279
283
|
| `Response 12 ("…") is marked as handled, but US-2 has no finding from you …` | Report the story with the finding `quelle: "mensch"` first (rule 1) — or `runde` + 1 if it has been fixed — then `uebernommen`. |
|
|
280
284
|
| `The plan can't be read: <place> …` | Correct the named place in the report (length, `grund` on `weggelassen`, duplicate key). |
|
|
285
|
+
| `The “in discussion” report needs the chat it comes from.` | Report again with the session, not with `--no-session`. |
|
|
286
|
+
| `Another chat is discussing this card right now.` | Do nothing: another chat carries the discussion. |
|
|
281
287
|
| `The report arrived, but the text of <file> didn't …` | Send the report again — sending twice does no harm. If the text no longer fits (over 300 KB), tell the person. |
|
|
282
288
|
| `The reports are saved, but not fully recorded yet.` | Send again — sending twice does no harm. |
|
|
283
289
|
| `The planning saved in the app is damaged …` | Tell the person. `kimal3 plan upload --overwrite` replaces the planning with a whole file `plan/stand.json` — **only after their explicit yes.** |
|
|
@@ -27,7 +27,7 @@ and skip it — no filler.
|
|
|
27
27
|
`/k3plan`, section 6; what an exit code means is in `~/.claude/skills/kimal3/SKILL.md`):
|
|
28
28
|
|
|
29
29
|
```json
|
|
30
|
-
[ { "art": "story", "spec": "2026-10-24-test-month", "story": "US-1", "stand": "gebaut",
|
|
30
|
+
[ { "art": "story", "spec": "2026-10-24-test-month-design", "story": "US-1", "stand": "gebaut",
|
|
31
31
|
"kriterien": [ { "text": "If Sabine starts on 1 Nov, it ends on 30 Nov.", "gegenprobe": "bestanden" } ],
|
|
32
32
|
"testen": "1. Open /test-month.\n2. Click Start.\n3. Below it says Ends on 30 Nov.\nDoesn't work if: it shows another date." } ]
|
|
33
33
|
```
|
|
@@ -35,7 +35,7 @@ and skip it — no filler.
|
|
|
35
35
|
And what stays unfinished, separately, with the flag:
|
|
36
36
|
|
|
37
37
|
```json
|
|
38
|
-
[ { "art": "story", "spec": "2026-10-24-test-month", "story": "US-2", "stand": "in_arbeit" } ]
|
|
38
|
+
[ { "art": "story", "spec": "2026-10-24-test-month-design", "story": "US-2", "stand": "in_arbeit" } ]
|
|
39
39
|
```
|
|
40
40
|
|
|
41
41
|
```
|