wizz-method 1.14.0 → 1.16.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/package.json +3 -2
- package/skills-registry.yaml +4 -0
- package/src/bmm-skills/3-solutioning/wizz-architecture/scripts/__pycache__/lint_spine.cpython-313.pyc +0 -0
- package/src/bmm-skills/3-solutioning/wizz-architecture/scripts/tests/__pycache__/test_lint_spine.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/4-implementation/wizz-code-review/SKILL.md +3 -5
- package/src/bmm-skills/4-implementation/wizz-code-review/customize.toml +71 -0
- package/src/bmm-skills/4-implementation/wizz-code-review/references/claims-check.md +14 -0
- package/src/bmm-skills/4-implementation/wizz-code-review/references/deletion-check.md +14 -0
- package/src/bmm-skills/4-implementation/wizz-code-review/review-prompts/edge-case-hunter.md +110 -0
- package/src/bmm-skills/4-implementation/wizz-code-review/review-prompts/verification-gap.md +113 -0
- package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-01-gather-context.md +32 -23
- package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-02-review.md +10 -13
- package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-03-triage.md +16 -22
- package/src/bmm-skills/4-implementation/wizz-code-review/steps/step-04-present.md +4 -2
- package/src/bmm-skills/4-implementation/wizz-quick-dev/customize.toml +76 -0
- package/src/bmm-skills/4-implementation/wizz-quick-dev/references/claims-check.md +14 -0
- package/src/bmm-skills/4-implementation/wizz-quick-dev/references/deletion-check.md +14 -0
- package/src/bmm-skills/4-implementation/wizz-quick-dev/review-prompts/edge-case-hunter.md +110 -0
- package/src/bmm-skills/4-implementation/wizz-quick-dev/review-prompts/verification-gap.md +113 -0
- package/src/bmm-skills/4-implementation/wizz-quick-dev/step-04-review.md +38 -16
- package/src/bmm-skills/4-implementation/wizz-retrospective/SKILL.md +56 -1488
- package/src/bmm-skills/4-implementation/wizz-retrospective/customize.toml +3 -5
- package/src/bmm-skills/4-implementation/wizz-retrospective/references/acceptance-verdict.md +55 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/references/aggregate-views.md +17 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/references/evidence-gathering.md +30 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/references/retro-document.md +84 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/references/team-discussion.md +22 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/__pycache__/sprint_status.cpython-313.pyc +0 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/git_evidence.py +304 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/sprint_status.py +746 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/__pycache__/test_git_evidence.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/__pycache__/test_sprint_status.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/fixtures/sprint-status-template.yaml +71 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/test_git_evidence.py +750 -0
- package/src/bmm-skills/4-implementation/wizz-retrospective/scripts/tests/test_sprint_status.py +1579 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/SKILL.md +39 -296
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/customize.toml +1 -3
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/fix-sprint-status.md +30 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/generate-tracking.md +25 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/readiness-gate.md +20 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/status-view.md +14 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/references/validate.md +10 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/__pycache__/sprint_plan.cpython-313.pyc +0 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/sprint_plan.py +697 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/tests/__pycache__/test_sprint_plan.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/scripts/tests/test_sprint_plan.py +524 -0
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/sprint-status-template.yaml +9 -7
- package/src/bmm-skills/module-help.csv +3 -3
- package/src/core-skills/_shared/handoff-protocol.md +7 -8
- package/src/core-skills/module-help.csv +1 -0
- package/src/core-skills/wizz-advanced-elicitation/SKILL.md +36 -114
- package/src/core-skills/wizz-advanced-elicitation/{methods.csv → assets/methods.csv} +29 -27
- package/src/core-skills/wizz-advanced-elicitation/customize.toml +54 -0
- package/src/core-skills/wizz-advanced-elicitation/scripts/__pycache__/pick_methods.cpython-313.pyc +0 -0
- package/src/core-skills/wizz-advanced-elicitation/scripts/pick_methods.py +233 -0
- package/src/core-skills/wizz-advanced-elicitation/scripts/tests/__pycache__/test_pick_methods.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/core-skills/wizz-advanced-elicitation/scripts/tests/test_pick_methods.py +228 -0
- package/src/core-skills/wizz-brainstorming/SKILL.md +2 -2
- package/src/core-skills/wizz-brainstorming/assets/brain-selector.html +2 -0
- package/src/core-skills/wizz-brainstorming/references/converge.md +1 -1
- package/src/core-skills/wizz-brainstorming/references/finalize.md +1 -1
- package/src/core-skills/wizz-brainstorming/references/headless.md +1 -1
- package/src/core-skills/wizz-brainstorming/references/mode-autonomous.md +1 -1
- package/src/core-skills/wizz-brainstorming/scripts/__pycache__/brain.cpython-313.pyc +0 -0
- package/src/core-skills/wizz-brainstorming/scripts/brain.py +36 -6
- package/src/core-skills/wizz-brainstorming/scripts/tests/__pycache__/test_brain.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/core-skills/wizz-brainstorming/scripts/tests/__pycache__/test_brain.cpython-314.pyc +0 -0
- package/src/core-skills/wizz-brainstorming/scripts/tests/test_brain.py +24 -2
- package/src/core-skills/wizz-customize/scripts/__pycache__/list_customizable_skills.cpython-313.pyc +0 -0
- package/src/core-skills/wizz-customize/scripts/tests/__pycache__/test_list_customizable_skills.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/core-skills/wizz-forge-idea/SKILL.md +107 -0
- package/src/core-skills/wizz-forge-idea/customize.toml +41 -0
- package/src/core-skills/wizz-forge-idea/scripts/__pycache__/resolve_personas.cpython-313.pyc +0 -0
- package/src/core-skills/wizz-forge-idea/scripts/__pycache__/resolve_personas.cpython-314.pyc +0 -0
- package/src/core-skills/wizz-forge-idea/scripts/resolve_personas.py +275 -0
- package/src/core-skills/wizz-forge-idea/scripts/tests/__pycache__/test_resolve_personas.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/core-skills/wizz-forge-idea/scripts/tests/__pycache__/test_resolve_personas.cpython-314.pyc +0 -0
- package/src/core-skills/wizz-forge-idea/scripts/tests/test_resolve_personas.py +138 -0
- package/src/core-skills/wizz-party-mode/SKILL.md +37 -54
- package/src/core-skills/wizz-party-mode/customize.toml +61 -2
- package/src/core-skills/wizz-party-mode/references/create-party.md +8 -3
- package/src/core-skills/wizz-party-mode/references/mode-agent-team.md +3 -1
- package/src/core-skills/wizz-party-mode/references/mode-subagent.md +16 -4
- package/src/core-skills/wizz-party-mode/references/party-memory.md +51 -0
- package/src/core-skills/wizz-party-mode/scripts/__pycache__/resolve_party.cpython-313.pyc +0 -0
- package/src/core-skills/wizz-party-mode/scripts/__pycache__/resolve_party.cpython-314.pyc +0 -0
- package/src/core-skills/wizz-party-mode/scripts/resolve_party.py +22 -7
- package/src/core-skills/wizz-party-mode/scripts/tests/__pycache__/test_resolve_party.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/core-skills/wizz-party-mode/scripts/tests/__pycache__/test_resolve_party.cpython-314.pyc +0 -0
- package/src/core-skills/wizz-party-mode/scripts/tests/{test-resolve_party.py → test_resolve_party.py} +8 -0
- package/src/core-skills/wizz-review-edge-case-hunter/SKILL.md +17 -3
- package/src/modules/wizz/README.md +1 -1
- package/src/modules/wizz/_shared/model-ladder.md +22 -0
- package/src/modules/wizz/_shared/token-economy.md +6 -2
- package/src/modules/wizz/agents/wizz-ads/customize.toml +1 -1
- package/src/modules/wizz/agents/wizz-copy/customize.toml +1 -1
- package/src/modules/wizz/agents/wizz-designer/customize.toml +1 -1
- package/src/modules/wizz/agents/wizz-growth/customize.toml +11 -1
- package/src/modules/wizz/agents/wizz-maestro/customize.toml +1 -1
- package/src/modules/wizz/agents/wizz-memoria/customize.toml +1 -1
- package/src/modules/wizz/agents/wizz-qa/customize.toml +1 -1
- package/src/modules/wizz/agents/wizz-seo/customize.toml +1 -1
- package/src/modules/wizz/agents/wizz-social/customize.toml +1 -1
- package/src/modules/wizz/overrides/wizz-agent-analyst.toml +1 -1
- package/src/modules/wizz/overrides/wizz-agent-architect.toml +1 -1
- package/src/modules/wizz/overrides/wizz-agent-dev.toml +1 -1
- package/src/modules/wizz/overrides/wizz-agent-pm.toml +1 -1
- package/src/modules/wizz/overrides/wizz-agent-tech-writer.toml +1 -1
- package/src/modules/wizz/overrides/wizz-agent-ux-designer.toml +1 -1
- package/src/modules/wizz/subagents/codex/wizz-exec-haiku.toml +16 -0
- package/src/modules/wizz/subagents/codex/wizz-exec-opus.toml +18 -0
- package/src/modules/wizz/subagents/codex/wizz-exec-review.toml +17 -0
- package/src/modules/wizz/subagents/codex/wizz-exec-sonnet.toml +17 -0
- package/src/modules/wizz/subagents/gemini/wizz-exec-haiku.md +15 -0
- package/src/modules/wizz/subagents/gemini/wizz-exec-opus.md +17 -0
- package/src/modules/wizz/subagents/gemini/wizz-exec-review.md +15 -0
- package/src/modules/wizz/subagents/gemini/wizz-exec-sonnet.md +16 -0
- package/src/modules/wizz/subagents/opencode/wizz-exec-haiku.md +14 -0
- package/src/modules/wizz/subagents/opencode/wizz-exec-opus.md +16 -0
- package/src/modules/wizz/subagents/opencode/wizz-exec-review.md +16 -0
- package/src/modules/wizz/subagents/opencode/wizz-exec-sonnet.md +15 -0
- package/src/modules/wizz/subagents/wizz-exec-opus.md +16 -0
- package/src/modules/wizz/subagents/wizz-exec-review.md +15 -0
- package/src/scripts/__pycache__/memlog.cpython-313.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_memlog.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/scripts/tests/__pycache__/test_resolve_customization.cpython-313-pytest-9.1.1.pyc +0 -0
- package/src/skills-lib/pesquisa-de-publico-do-piva/README.md +83 -0
- package/src/skills-lib/pesquisa-de-publico-do-piva/SKILL.md +65 -0
- package/src/skills-lib/pesquisa-de-publico-do-piva/assets/template.html +414 -0
- package/src/skills-lib/pesquisa-de-publico-do-piva/prompts.md +131 -0
- package/src/skills-lib/wizz-offer-forge/README.md +13 -0
- package/src/skills-lib/wizz-offer-forge/SKILL.md +74 -0
- package/src/skills-lib/wizz-offer-forge/references/empilhamento-valor.md +25 -0
- package/src/skills-lib/wizz-offer-forge/references/garantia-reversa.md +22 -0
- package/src/skills-lib/wizz-offer-forge/references/mecanismo-unico.md +28 -0
- package/src/skills-lib/wizz-offer-forge/references/ponte-trafego.md +21 -0
- package/src/skills-lib/wizz-router/references/routing-table-flat.md +2 -0
- package/tools/installer/core/installer.js +57 -0
- package/tools/installer/ide/_config-driven.js +44 -30
- package/tools/installer/ide/platform-codes.yaml +42 -3
- package/tools/installer/modules/external-manager.js +140 -2
- package/tools/installer/modules/official-modules.js +68 -21
- package/tools/installer/prompts.js +45 -103
- package/tools/installer/ui.js +64 -7
- package/wizz-modules.yaml +65 -11
- package/src/bmm-skills/4-implementation/wizz-sprint-planning/checklist.md +0 -34
- package/src/core-skills/wizz-brainstorming/scripts/memlog.py +0 -202
- package/src/core-skills/wizz-brainstorming/scripts/tests/test_memlog.py +0 -265
package/wizz-modules.yaml
CHANGED
|
@@ -17,6 +17,29 @@
|
|
|
17
17
|
# repo changes owner, disappears, or ships something unexpected.
|
|
18
18
|
# type: experimental — same delegated trust, plus unstable by upstream's
|
|
19
19
|
# own definition.
|
|
20
|
+
#
|
|
21
|
+
# deprecated (optional, default false) — when true, the module is hidden from
|
|
22
|
+
# the installer picker UNLESS it is already installed (so existing users can
|
|
23
|
+
# still see/manage it, but new users are not offered it).
|
|
24
|
+
# deprecation-message (optional) — surfaced in the picker hint for a deprecated
|
|
25
|
+
# module that is still installed; use it to point users to the replacement.
|
|
26
|
+
#
|
|
27
|
+
# marketplace-plugin (optional, default false) — when true, the module's
|
|
28
|
+
# installable skills are resolved from its .claude-plugin/marketplace.json via
|
|
29
|
+
# the plugin resolver (instead of copying the single module-definition dir).
|
|
30
|
+
# Use this for modules whose module.yaml does not sit alongside the skill
|
|
31
|
+
# folders. module-definition should still point at the real module.yaml so
|
|
32
|
+
# config/version lookups work.
|
|
33
|
+
#
|
|
34
|
+
# post-install-message (optional) — an "action needed" notice shown to the user
|
|
35
|
+
# after install completes, once for each install run that includes the module.
|
|
36
|
+
# Interactive installs require the user to acknowledge it (press Enter);
|
|
37
|
+
# non-interactive (--yes) installs print it and continue. Use for required
|
|
38
|
+
# follow-up steps (e.g. "run the X setup skill").
|
|
39
|
+
#
|
|
40
|
+
# aliases (optional) — prior `code` values this module was registered under.
|
|
41
|
+
# An existing install recorded under an alias resolves to this entry instead of
|
|
42
|
+
# being orphaned as "no source available" when the code changes.
|
|
20
43
|
|
|
21
44
|
modules:
|
|
22
45
|
bmad-method-test-architecture-enterprise:
|
|
@@ -41,19 +64,33 @@ modules:
|
|
|
41
64
|
npmPackage: bmad-builder
|
|
42
65
|
default_channel: stable
|
|
43
66
|
|
|
44
|
-
|
|
45
|
-
url: https://github.com/bmad-code-org/
|
|
46
|
-
module-definition: skills/module.yaml
|
|
47
|
-
code:
|
|
48
|
-
|
|
49
|
-
|
|
67
|
+
bmad-loop:
|
|
68
|
+
url: https://github.com/bmad-code-org/bmad-loop
|
|
69
|
+
module-definition: src/bmad_loop/data/skills/bmad-loop-setup/assets/module.yaml
|
|
70
|
+
code: bmad-loop
|
|
71
|
+
# bauto é o código pré-rename (o módulo era bmad-auto antes do repo e do
|
|
72
|
+
# conteúdo serem renomeados para bmad-loop); mantém instalações em bauto
|
|
73
|
+
# migrando para a frente em vez de virarem órfãs.
|
|
74
|
+
aliases: [bauto]
|
|
75
|
+
name: "Wizz Loop"
|
|
76
|
+
description: "Builds, verifies, and retros a whole epic unattended"
|
|
50
77
|
defaultSelected: false
|
|
51
|
-
type:
|
|
52
|
-
|
|
53
|
-
|
|
78
|
+
type: upstream-org
|
|
79
|
+
default_channel: stable
|
|
80
|
+
# As skills vivem fora de um diretório único de module.yaml; resolve pelo
|
|
81
|
+
# .claude-plugin/marketplace.json via plugin resolver (ver external-manager).
|
|
82
|
+
marketplace-plugin: true
|
|
83
|
+
post-install-message: |
|
|
84
|
+
Wizz Loop installed. To finish setup, run the bmad-loop-setup skill
|
|
85
|
+
from your agent:
|
|
86
|
+
|
|
87
|
+
> use the bmad-loop-setup skill
|
|
88
|
+
|
|
89
|
+
It installs the bmad-loop orchestrator tool and wires up the per-project
|
|
90
|
+
hooks and policy. The automation skills don't run until setup completes.
|
|
54
91
|
|
|
55
92
|
bmad-creative-intelligence-suite:
|
|
56
|
-
url: https://github.com/bmad-code-org/
|
|
93
|
+
url: https://github.com/bmad-code-org/bmad-module-creative-intelligence-suite
|
|
57
94
|
module-definition: src/module.yaml
|
|
58
95
|
code: cis
|
|
59
96
|
name: "Wizz Creative Intelligence Suite"
|
|
@@ -64,7 +101,7 @@ modules:
|
|
|
64
101
|
default_channel: stable
|
|
65
102
|
|
|
66
103
|
bmad-game-dev-studio:
|
|
67
|
-
url: https://github.com/bmad-code-org/
|
|
104
|
+
url: https://github.com/bmad-code-org/bmad-module-game-dev-studio.git
|
|
68
105
|
module-definition: src/module.yaml
|
|
69
106
|
code: gds
|
|
70
107
|
name: "Wizz Game Dev Studio"
|
|
@@ -74,6 +111,21 @@ modules:
|
|
|
74
111
|
npmPackage: bmad-game-dev-studio
|
|
75
112
|
default_channel: stable
|
|
76
113
|
|
|
114
|
+
# --- Deprecated: hidden from the picker unless already installed ---
|
|
115
|
+
|
|
116
|
+
wizz-automator:
|
|
117
|
+
url: https://github.com/bmad-code-org/bmad-automator
|
|
118
|
+
module-definition: skills/module.yaml
|
|
119
|
+
code: automator
|
|
120
|
+
name: "Wizz Automator Epic Builder Experimental"
|
|
121
|
+
description: "EXPERIMENTAL: only supports claude and codex currently"
|
|
122
|
+
defaultSelected: false
|
|
123
|
+
type: experimental
|
|
124
|
+
npmPackage: bmad-story-automator
|
|
125
|
+
default_channel: next
|
|
126
|
+
deprecated: true
|
|
127
|
+
deprecation-message: "Wizz Automator has been deprecated and is replaced by Wizz Loop (bmad-loop). Install Wizz Loop instead."
|
|
128
|
+
|
|
77
129
|
bmad-method-wds-expansion:
|
|
78
130
|
url: https://github.com/bmad-code-org/bmad-method-wds-expansion
|
|
79
131
|
module-definition: src/module.yaml
|
|
@@ -85,3 +137,5 @@ modules:
|
|
|
85
137
|
type: upstream-org
|
|
86
138
|
npmPackage: bmad-wds
|
|
87
139
|
default_channel: stable
|
|
140
|
+
deprecated: true
|
|
141
|
+
deprecation-message: "Whiteport Design Studio is no longer receiving updates. Its capabilities are being folded into the bmm module as enhanced UX features. Your existing install is kept as-is."
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
# Sprint Planning Validation Checklist
|
|
2
|
-
|
|
3
|
-
## Core Validation
|
|
4
|
-
|
|
5
|
-
### Complete Coverage Check
|
|
6
|
-
|
|
7
|
-
- [ ] Every epic found in epic\*.md files appears in sprint-status.yaml
|
|
8
|
-
- [ ] Every story found in epic\*.md files appears in sprint-status.yaml
|
|
9
|
-
- [ ] Every epic has a corresponding retrospective entry
|
|
10
|
-
- [ ] No development_status items in sprint-status.yaml that don't exist in epic files
|
|
11
|
-
- [ ] action_items section (if it existed) carried over unchanged
|
|
12
|
-
|
|
13
|
-
### Parsing Verification
|
|
14
|
-
|
|
15
|
-
Compare epic files against generated sprint-status.yaml:
|
|
16
|
-
|
|
17
|
-
```
|
|
18
|
-
Epic Files Contains: Sprint Status Contains:
|
|
19
|
-
✓ Epic 1 ✓ epic-1: [status]
|
|
20
|
-
✓ Story 1.1: User Auth ✓ 1-1-user-auth: [status]
|
|
21
|
-
✓ Story 1.2: Account Mgmt ✓ 1-2-account-mgmt: [status]
|
|
22
|
-
✓ Story 1.3: Plant Naming ✓ 1-3-plant-naming: [status]
|
|
23
|
-
✓ epic-1-retrospective: [status]
|
|
24
|
-
✓ Epic 2 ✓ epic-2: [status]
|
|
25
|
-
✓ Story 2.1: Personality Model ✓ 2-1-personality-model: [status]
|
|
26
|
-
✓ Story 2.2: Chat Interface ✓ 2-2-chat-interface: [status]
|
|
27
|
-
✓ epic-2-retrospective: [status]
|
|
28
|
-
```
|
|
29
|
-
|
|
30
|
-
### Final Check
|
|
31
|
-
|
|
32
|
-
- [ ] Total count of epics matches
|
|
33
|
-
- [ ] Total count of stories matches
|
|
34
|
-
- [ ] All items are in the expected order (epic, stories, retrospective)
|
|
@@ -1,202 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
# /// script
|
|
3
|
-
# requires-python = ">=3.10"
|
|
4
|
-
# ///
|
|
5
|
-
"""memlog — an append-only memory log: LLM-optimal working memory for a skill.
|
|
6
|
-
|
|
7
|
-
A memlog is the dense, chronological record of everything that mattered in a piece of
|
|
8
|
-
work — every item the user generated or accepted — kept minimal like human memory: only
|
|
9
|
-
what's important, never bloated. It persists ACROSS sessions, so a fresh session can
|
|
10
|
-
load it and continue. It is NOT a deliverable; downstream artifacts (a brief, a PRD, a
|
|
11
|
-
deck, a report) are *derived* from it on demand. The host skill supplies the vocabulary
|
|
12
|
-
by how it calls `append` — the tool stays neutral.
|
|
13
|
-
|
|
14
|
-
It is a FLAT log: there are no sections or grouping. Every entry is one line, recorded
|
|
15
|
-
at the END in the order it happened. The chronology itself is the structure — an event
|
|
16
|
-
like "started technique X" is just another entry, same as an idea or an insight.
|
|
17
|
-
|
|
18
|
-
Two invariants make it trustworthy:
|
|
19
|
-
|
|
20
|
-
1. Append-only, chronological. Entries land at the end, in the order they happen.
|
|
21
|
-
Nothing is ever inserted backward, reordered, or grouped.
|
|
22
|
-
2. Write-only / blind. Every command is an atomic, context-free write and echoes the
|
|
23
|
-
new state as JSON, so the caller never re-reads the file mid-session. The one time
|
|
24
|
-
the file is read is on resume — and the caller reads it itself, not via this script.
|
|
25
|
-
|
|
26
|
-
The file shape (.memlog.md):
|
|
27
|
-
|
|
28
|
-
---
|
|
29
|
-
topic: Onboarding flow for a budgeting app
|
|
30
|
-
goal: lift week-1 retention
|
|
31
|
-
status: active
|
|
32
|
-
updated: 2026-05-30T14:22
|
|
33
|
-
---
|
|
34
|
-
|
|
35
|
-
- (note) user picked techniques: SCAMPER, then Six Thinking Hats
|
|
36
|
-
- (technique) started SCAMPER
|
|
37
|
-
- (idea) skip the signup wall: let people try with sample data first
|
|
38
|
-
- (idea) auto-import one bank account so the first screen shows real numbers
|
|
39
|
-
- (question) is open-banking consent too heavy for step one?
|
|
40
|
-
- (technique) started Six Thinking Hats
|
|
41
|
-
- (idea) black-hat: imported transactions look scary before they're categorized
|
|
42
|
-
- (insight) the "scary numbers" risk and the "real numbers" idea are one lever: show real data, pre-categorized
|
|
43
|
-
- (direction) user wants to optimize for the anxious first-timer, not the power user
|
|
44
|
-
- (decision) lead with one pre-categorized account; defer multi-account import
|
|
45
|
-
|
|
46
|
-
Each entry may carry an optional `--type` — what KIND it is (idea, insight, question,
|
|
47
|
-
decision, technique, …) — and an optional `--by` naming who it came from (e.g. `user`,
|
|
48
|
-
`coach`), for sessions where authorship matters. Both render into one short inline tag:
|
|
49
|
-
`(idea)`, `(idea by user)`, `(by coach)`. Omit them for a plain note. The host skill
|
|
50
|
-
names the vocabulary; the script does not.
|
|
51
|
-
|
|
52
|
-
Commands:
|
|
53
|
-
init --workspace DIR [--field k=v ...] create the memlog (errors if it exists)
|
|
54
|
-
append --workspace DIR --text STR [--type T] [--by W] append one entry at the end
|
|
55
|
-
set --workspace DIR --key K --value V set/replace a frontmatter field
|
|
56
|
-
|
|
57
|
-
The workspace is the run folder; the memlog is always {workspace}/.memlog.md.
|
|
58
|
-
"""
|
|
59
|
-
import argparse
|
|
60
|
-
import json
|
|
61
|
-
import os
|
|
62
|
-
import sys
|
|
63
|
-
from datetime import datetime
|
|
64
|
-
from pathlib import Path
|
|
65
|
-
|
|
66
|
-
MEMLOG = ".memlog.md"
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def now() -> str:
|
|
70
|
-
return datetime.now().strftime("%Y-%m-%dT%H:%M")
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
def memlog_path(workspace: str) -> Path:
|
|
74
|
-
return Path(workspace) / MEMLOG
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
def split(text: str) -> tuple[dict, str]:
|
|
78
|
-
"""Return (frontmatter dict in source order, body str). Frontmatter is plain key: value.
|
|
79
|
-
|
|
80
|
-
The closing fence is the first line that is *exactly* `---`, so a `---` inside a
|
|
81
|
-
field value (topic/goal are free user text) never truncates the frontmatter.
|
|
82
|
-
"""
|
|
83
|
-
lines = text.splitlines()
|
|
84
|
-
if not lines or lines[0] != "---":
|
|
85
|
-
raise ValueError(".memlog.md has no frontmatter")
|
|
86
|
-
end = next((i for i in range(1, len(lines)) if lines[i] == "---"), None)
|
|
87
|
-
if end is None:
|
|
88
|
-
raise ValueError(".memlog.md frontmatter is not terminated")
|
|
89
|
-
meta: dict[str, str] = {}
|
|
90
|
-
for line in lines[1:end]:
|
|
91
|
-
if ":" in line:
|
|
92
|
-
k, v = line.split(":", 1)
|
|
93
|
-
meta[k.strip()] = v.strip()
|
|
94
|
-
return meta, "\n".join(lines[end + 1:]).lstrip("\n")
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
def render(meta: dict, body: str) -> str:
|
|
98
|
-
# Neutralize newlines in values so a multi-line field can't break the fence on re-read.
|
|
99
|
-
fm = "\n".join(f"{k}: {' '.join(str(v).splitlines())}" for k, v in meta.items())
|
|
100
|
-
return "---\n" + fm + "\n---\n\n" + body.rstrip("\n") + "\n"
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def touch(meta: dict) -> None:
|
|
104
|
-
"""Stamp `updated` and keep it last so the field order stays predictable."""
|
|
105
|
-
meta.pop("updated", None)
|
|
106
|
-
meta["updated"] = now()
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
def write_atomic(path: Path, text: str) -> None:
|
|
110
|
-
tmp = path.with_suffix(path.suffix + ".tmp")
|
|
111
|
-
tmp.write_text(text, encoding="utf-8")
|
|
112
|
-
os.replace(tmp, path)
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
def entry_count(body: str) -> int:
|
|
116
|
-
return sum(1 for ln in body.splitlines() if ln.startswith("- "))
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
def ack(path: Path, meta: dict, body: str) -> None:
|
|
120
|
-
"""Echo new state so the caller never re-reads the file to know where it stands."""
|
|
121
|
-
print(json.dumps({
|
|
122
|
-
"ok": True,
|
|
123
|
-
"memlog": str(path),
|
|
124
|
-
"status": meta.get("status", ""),
|
|
125
|
-
"entries": entry_count(body),
|
|
126
|
-
}))
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
def cmd_init(args) -> int:
|
|
130
|
-
path = memlog_path(args.workspace)
|
|
131
|
-
if path.exists():
|
|
132
|
-
print(f"error: {path} already exists; use append/set to update it", file=sys.stderr)
|
|
133
|
-
return 2
|
|
134
|
-
path.parent.mkdir(parents=True, exist_ok=True)
|
|
135
|
-
meta: dict[str, str] = {}
|
|
136
|
-
for pair in args.field or []:
|
|
137
|
-
if "=" not in pair:
|
|
138
|
-
print(f"error: --field expects key=value, got {pair!r}", file=sys.stderr)
|
|
139
|
-
return 2
|
|
140
|
-
k, v = pair.split("=", 1)
|
|
141
|
-
meta[k.strip()] = v.strip()
|
|
142
|
-
meta.setdefault("status", "active")
|
|
143
|
-
touch(meta)
|
|
144
|
-
write_atomic(path, render(meta, ""))
|
|
145
|
-
ack(path, meta, "")
|
|
146
|
-
return 0
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
def cmd_append(args) -> int:
|
|
150
|
-
path = memlog_path(args.workspace)
|
|
151
|
-
meta, body = split(path.read_text(encoding="utf-8"))
|
|
152
|
-
text = " ".join(args.text.split()) # collapse newlines/runs → one-line entry, no prose bloat
|
|
153
|
-
label = args.type or ""
|
|
154
|
-
if args.by:
|
|
155
|
-
label = f"{label} by {args.by}".strip() # attribution: "(idea by user)" / "(by coach)"
|
|
156
|
-
tag = f"({label}) " if label else ""
|
|
157
|
-
entry = f"- {tag}{text}"
|
|
158
|
-
body = (body.rstrip("\n") + "\n" + entry) if body.strip() else entry # always at the end
|
|
159
|
-
touch(meta)
|
|
160
|
-
write_atomic(path, render(meta, body))
|
|
161
|
-
ack(path, meta, body)
|
|
162
|
-
return 0
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
def cmd_set(args) -> int:
|
|
166
|
-
path = memlog_path(args.workspace)
|
|
167
|
-
meta, body = split(path.read_text(encoding="utf-8"))
|
|
168
|
-
meta[args.key] = args.value
|
|
169
|
-
touch(meta)
|
|
170
|
-
write_atomic(path, render(meta, body))
|
|
171
|
-
ack(path, meta, body)
|
|
172
|
-
return 0
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
def main(argv: list[str] | None = None) -> int:
|
|
176
|
-
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
177
|
-
sub = p.add_subparsers(dest="cmd", required=True)
|
|
178
|
-
|
|
179
|
-
pi = sub.add_parser("init", help="create the memlog")
|
|
180
|
-
pi.add_argument("--workspace", required=True)
|
|
181
|
-
pi.add_argument("--field", action="append", metavar="KEY=VALUE", help="frontmatter field (repeatable)")
|
|
182
|
-
pi.set_defaults(func=cmd_init)
|
|
183
|
-
|
|
184
|
-
pa = sub.add_parser("append", help="append one entry at the end")
|
|
185
|
-
pa.add_argument("--workspace", required=True)
|
|
186
|
-
pa.add_argument("--text", required=True)
|
|
187
|
-
pa.add_argument("--type", help="entry kind, rendered as an inline tag")
|
|
188
|
-
pa.add_argument("--by", help="who the entry came from (e.g. user, coach); rendered into the tag")
|
|
189
|
-
pa.set_defaults(func=cmd_append)
|
|
190
|
-
|
|
191
|
-
pset = sub.add_parser("set", help="set a frontmatter field")
|
|
192
|
-
pset.add_argument("--workspace", required=True)
|
|
193
|
-
pset.add_argument("--key", required=True)
|
|
194
|
-
pset.add_argument("--value", required=True)
|
|
195
|
-
pset.set_defaults(func=cmd_set)
|
|
196
|
-
|
|
197
|
-
args = p.parse_args(argv)
|
|
198
|
-
return args.func(args)
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
if __name__ == "__main__":
|
|
202
|
-
sys.exit(main())
|
|
@@ -1,265 +0,0 @@
|
|
|
1
|
-
# /// script
|
|
2
|
-
# requires-python = ">=3.10"
|
|
3
|
-
# dependencies = ["pytest>=8.0"]
|
|
4
|
-
# ///
|
|
5
|
-
"""Tests for memlog.py. Run: uv run --with pytest pytest scripts/tests/test_memlog.py
|
|
6
|
-
|
|
7
|
-
The spine under test is the flat, append-only, chronological invariant: every entry is
|
|
8
|
-
one line recorded at the end in the order it happened — no sections, no grouping.
|
|
9
|
-
"""
|
|
10
|
-
import json
|
|
11
|
-
import sys
|
|
12
|
-
from pathlib import Path
|
|
13
|
-
|
|
14
|
-
import pytest
|
|
15
|
-
|
|
16
|
-
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
17
|
-
import memlog # noqa: E402
|
|
18
|
-
|
|
19
|
-
MEMLOG = ".memlog.md"
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
@pytest.fixture
|
|
23
|
-
def ws(tmp_path):
|
|
24
|
-
return str(tmp_path)
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
def read(ws):
|
|
28
|
-
return (Path(ws) / MEMLOG).read_text(encoding="utf-8")
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
def body_of(ws):
|
|
32
|
-
return memlog.split(read(ws))[1]
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
def entries(ws):
|
|
36
|
-
return [ln for ln in body_of(ws).splitlines() if ln.startswith("- ")]
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
def init(ws, **fields):
|
|
40
|
-
fields = fields or {"topic": "Reinvent the lunchbox", "goal": "ideas for a pitch"}
|
|
41
|
-
argv = ["init", "--workspace", ws]
|
|
42
|
-
for k, v in fields.items():
|
|
43
|
-
argv += ["--field", f"{k}={v}"]
|
|
44
|
-
assert memlog.main(argv) == 0
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
def append(ws, text, entry_type=None, by=None):
|
|
48
|
-
argv = ["append", "--workspace", ws, "--text", text]
|
|
49
|
-
if entry_type:
|
|
50
|
-
argv += ["--type", entry_type]
|
|
51
|
-
if by:
|
|
52
|
-
argv += ["--by", by]
|
|
53
|
-
assert memlog.main(argv) == 0
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
# --- init ---------------------------------------------------------------
|
|
57
|
-
|
|
58
|
-
def test_init_writes_frontmatter_fields(ws):
|
|
59
|
-
init(ws)
|
|
60
|
-
meta, body = memlog.split(read(ws))
|
|
61
|
-
assert meta["topic"] == "Reinvent the lunchbox"
|
|
62
|
-
assert meta["goal"] == "ideas for a pitch"
|
|
63
|
-
assert meta["status"] == "active"
|
|
64
|
-
assert "updated" in meta
|
|
65
|
-
assert body.strip() == ""
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
def test_init_arbitrary_fields(ws):
|
|
69
|
-
init(ws, topic="T", audience="board")
|
|
70
|
-
meta, _ = memlog.split(read(ws))
|
|
71
|
-
assert meta["audience"] == "board"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
def test_init_refuses_overwrite(ws):
|
|
75
|
-
init(ws)
|
|
76
|
-
assert memlog.main(["init", "--workspace", ws, "--field", "topic=other"]) == 2
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
def test_init_creates_missing_workspace(tmp_path):
|
|
80
|
-
nested = str(tmp_path / "a" / "b")
|
|
81
|
-
assert memlog.main(["init", "--workspace", nested, "--field", "topic=T"]) == 0
|
|
82
|
-
assert (Path(nested) / MEMLOG).is_file()
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
def test_init_rejects_malformed_field(ws):
|
|
86
|
-
assert memlog.main(["init", "--workspace", ws, "--field", "noequals"]) == 2
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
# --- append: flat chronological order is the whole point -----------------
|
|
90
|
-
|
|
91
|
-
def test_append_lands_at_end_in_order(ws):
|
|
92
|
-
init(ws)
|
|
93
|
-
append(ws, "first")
|
|
94
|
-
append(ws, "second")
|
|
95
|
-
append(ws, "third")
|
|
96
|
-
assert entries(ws) == ["- first", "- second", "- third"]
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
def test_no_sections_or_headings_ever(ws):
|
|
100
|
-
init(ws)
|
|
101
|
-
append(ws, "started foo", entry_type="technique")
|
|
102
|
-
append(ws, "an idea", entry_type="idea")
|
|
103
|
-
append(ws, "started bar", entry_type="technique")
|
|
104
|
-
assert "## " not in body_of(ws) # the flat log never grows headings
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
def test_type_renders_as_inline_tag(ws):
|
|
108
|
-
init(ws)
|
|
109
|
-
append(ws, "the earth revolves around the sun", entry_type="idea")
|
|
110
|
-
append(ws, "how do we handle stampede?", entry_type="question")
|
|
111
|
-
body = body_of(ws)
|
|
112
|
-
assert "- (idea) the earth revolves around the sun" in body
|
|
113
|
-
assert "- (question) how do we handle stampede?" in body
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
def test_append_without_type_is_plain_note(ws):
|
|
117
|
-
init(ws)
|
|
118
|
-
append(ws, "bare entry")
|
|
119
|
-
assert entries(ws) == ["- bare entry"]
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
def test_append_collapses_newlines_into_one_line(ws):
|
|
123
|
-
init(ws)
|
|
124
|
-
append(ws, "line one\nline two\n spaced out")
|
|
125
|
-
assert entries(ws) == ["- line one line two spaced out"]
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
def test_revisited_technique_is_just_a_later_entry(ws):
|
|
129
|
-
# the user's model: switching techniques is an entry, not a section to return to
|
|
130
|
-
init(ws)
|
|
131
|
-
append(ws, "started SCAMPER", entry_type="technique")
|
|
132
|
-
append(ws, "magnetic latch", entry_type="idea")
|
|
133
|
-
append(ws, "started Six Hats", entry_type="technique")
|
|
134
|
-
append(ws, "stale data risk", entry_type="idea")
|
|
135
|
-
append(ws, "started SCAMPER", entry_type="technique") # back to SCAMPER — just appended again
|
|
136
|
-
append(ws, "stackable tiers", entry_type="idea")
|
|
137
|
-
assert entries(ws) == [
|
|
138
|
-
"- (technique) started SCAMPER",
|
|
139
|
-
"- (idea) magnetic latch",
|
|
140
|
-
"- (technique) started Six Hats",
|
|
141
|
-
"- (idea) stale data risk",
|
|
142
|
-
"- (technique) started SCAMPER",
|
|
143
|
-
"- (idea) stackable tiers",
|
|
144
|
-
]
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
def test_by_renders_attribution_in_tag(ws):
|
|
148
|
-
# Creative Partner mode must record whose idea each one was
|
|
149
|
-
init(ws)
|
|
150
|
-
append(ws, "magnetic latch lid", entry_type="idea", by="user")
|
|
151
|
-
append(ws, "lid doubles as a plate", entry_type="idea", by="coach")
|
|
152
|
-
body = body_of(ws)
|
|
153
|
-
assert "- (idea by user) magnetic latch lid" in body
|
|
154
|
-
assert "- (idea by coach) lid doubles as a plate" in body
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
def test_by_without_type_renders_alone(ws):
|
|
158
|
-
init(ws)
|
|
159
|
-
append(ws, "off-the-cuff thought", by="coach")
|
|
160
|
-
assert entries(ws) == ["- (by coach) off-the-cuff thought"]
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
def test_heterogeneous_entry_types_coexist(ws):
|
|
164
|
-
init(ws)
|
|
165
|
-
append(ws, "an idea", entry_type="idea")
|
|
166
|
-
append(ws, "an open question", entry_type="question")
|
|
167
|
-
append(ws, "a decision we made", entry_type="decision")
|
|
168
|
-
append(ws, "user wants mobile-first", entry_type="direction")
|
|
169
|
-
body = body_of(ws)
|
|
170
|
-
for tag in ("(idea)", "(question)", "(decision)", "(direction)"):
|
|
171
|
-
assert tag in body
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
# --- set ----------------------------------------------------------------
|
|
175
|
-
|
|
176
|
-
def test_set_flips_status(ws):
|
|
177
|
-
init(ws)
|
|
178
|
-
memlog.main(["set", "--workspace", ws, "--key", "status", "--value", "complete"])
|
|
179
|
-
assert memlog.split(read(ws))[0]["status"] == "complete"
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
def test_set_preserves_body(ws):
|
|
183
|
-
init(ws)
|
|
184
|
-
append(ws, "keep me", entry_type="idea")
|
|
185
|
-
memlog.main(["set", "--workspace", ws, "--key", "status", "--value", "complete"])
|
|
186
|
-
meta, body = memlog.split(read(ws))
|
|
187
|
-
assert meta["status"] == "complete"
|
|
188
|
-
assert "- (idea) keep me" in body
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
def test_set_can_add_new_field(ws):
|
|
192
|
-
init(ws)
|
|
193
|
-
memlog.main(["set", "--workspace", ws, "--key", "owner", "--value", "Wizz"])
|
|
194
|
-
assert memlog.split(read(ws))[0]["owner"] == "Wizz"
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
def test_updated_stays_last(ws):
|
|
198
|
-
init(ws)
|
|
199
|
-
memlog.main(["set", "--workspace", ws, "--key", "owner", "--value", "Wizz"])
|
|
200
|
-
meta = memlog.split(read(ws))[0]
|
|
201
|
-
assert list(meta)[-1] == "updated"
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
# --- robustness ---------------------------------------------------------
|
|
205
|
-
|
|
206
|
-
def test_roundtrip_render_is_stable(ws):
|
|
207
|
-
init(ws)
|
|
208
|
-
append(ws, "one", entry_type="idea")
|
|
209
|
-
first = read(ws)
|
|
210
|
-
meta, body = memlog.split(first)
|
|
211
|
-
assert memlog.render(meta, body) == first
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
def test_commas_in_field_survive(ws):
|
|
215
|
-
init(ws, topic="cars, trains, and planes")
|
|
216
|
-
append(ws, "z", entry_type="idea")
|
|
217
|
-
meta, _ = memlog.split(read(ws))
|
|
218
|
-
assert meta["topic"] == "cars, trains, and planes"
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
def test_triple_dash_in_field_does_not_corrupt_frontmatter(ws):
|
|
222
|
-
# A `---` inside a value must NOT be read as the closing fence: topic stays intact,
|
|
223
|
-
# status survives, and the body never leaks frontmatter text.
|
|
224
|
-
init(ws, topic="Pricing --- tiers --- and add-ons")
|
|
225
|
-
append(ws, "an idea", entry_type="idea")
|
|
226
|
-
meta, body = memlog.split(read(ws))
|
|
227
|
-
assert meta["topic"] == "Pricing --- tiers --- and add-ons"
|
|
228
|
-
assert meta["status"] == "active"
|
|
229
|
-
assert entries(ws) == ["- (idea) an idea"]
|
|
230
|
-
assert "status:" not in body # frontmatter never bled into the body
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
def test_triple_dash_status_survives_in_ack(ws, capsys):
|
|
234
|
-
init(ws, topic="a --- b")
|
|
235
|
-
append(ws, "x", entry_type="idea")
|
|
236
|
-
out = json.loads(capsys.readouterr().out.strip().splitlines()[-1])
|
|
237
|
-
assert out["status"] == "active" # not "" — frontmatter recovered cleanly
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
def test_newline_in_field_is_neutralized(ws):
|
|
241
|
-
# A value carrying a newline can't break the fence on the next round-trip.
|
|
242
|
-
memlog.main(["init", "--workspace", ws, "--field", "topic=line one\nline two"])
|
|
243
|
-
append(ws, "x", entry_type="idea")
|
|
244
|
-
meta, _ = memlog.split(read(ws))
|
|
245
|
-
assert "\n" not in meta["topic"]
|
|
246
|
-
assert meta["status"] == "active"
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
def test_append_emits_json_ack(ws, capsys):
|
|
250
|
-
init(ws)
|
|
251
|
-
append(ws, "x", entry_type="idea")
|
|
252
|
-
out = json.loads(capsys.readouterr().out.strip().splitlines()[-1])
|
|
253
|
-
assert out["ok"] is True
|
|
254
|
-
assert out["status"] == "active"
|
|
255
|
-
assert out["entries"] == 1
|
|
256
|
-
assert out["memlog"].endswith(MEMLOG)
|
|
257
|
-
assert "section" not in out # sections are gone
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
def test_ack_entry_count_climbs(ws, capsys):
|
|
261
|
-
init(ws)
|
|
262
|
-
append(ws, "a")
|
|
263
|
-
append(ws, "b")
|
|
264
|
-
out = json.loads(capsys.readouterr().out.strip().splitlines()[-1])
|
|
265
|
-
assert out["entries"] == 2
|