You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Use a product-engineering register: concise, practical, and confident. Write for developers who value clear terminal workflows; prefer precise verbs, concrete outcomes, and short examples over marketing slogans.
6
+
7
+
## Users
8
+
9
+
Developers using Pythinker inside terminal workflows, including PowerShell, Windows Terminal, macOS Terminal, iTerm2, GNOME Terminal, CI logs, SSH sessions, and low-color or no-color shells. They are reviewing code, diagnosing failures, running commands, approving changes, and switching between AI and shell work.
10
+
11
+
## Product Purpose
12
+
13
+
Pythinker Code is a review-first AI engineering CLI. The interface should help users understand what the agent is doing, trust approvals and results, move quickly through repeated terminal actions, and keep context visible without leaving the shell.
14
+
15
+
## Brand Personality
16
+
17
+
Disciplined, terminal-native, precise.
18
+
19
+
## Anti-references
20
+
21
+
Do not make the TUI feel like a neon dashboard, glassmorphism mockup, emoji-heavy chat toy, or novelty terminal skin. Avoid fragile color-only semantics, excessive borders, box-heavy layouts, decorative motion, AI-purple gradients, and UI effects that slow repeated keyboard workflows.
22
+
23
+
## Design Principles
24
+
25
+
1. Standardize first: core structure, labels, states, and colors must behave consistently across shells and terminal capabilities.
26
+
2. Text is the UI: layout, copy, hierarchy, and semantic labels should remain usable in no-color and narrow-width environments.
27
+
3. Speed over spectacle: feedback should be immediate, short, and interruptible; repeated keyboard actions should not animate.
28
+
4. State must be explicit: approvals, background work, errors, warnings, and tool execution need clear shape and wording, not just color.
29
+
5. Compatibility is craft: PowerShell, Windows Terminal, Unix terminals, SSH, CI, and screen readers should all get a coherent experience.
30
+
31
+
## Accessibility & Inclusion
32
+
33
+
Target WCAG-minded contrast within ANSI limitations, colorblind-safe state semantics, reduced-motion behavior, keyboard-first operation, graceful no-color fallback, and responsive rendering from narrow split panes to wide terminals.
-[ ]2. Run it and watch it fail for the right reason (the generator does not exist yet):
653
653
```bash
654
-
/home/ai/.local/bin/uv run pytest tests/test_scoop_manifest.py -vv
654
+
uv run pytest tests/test_scoop_manifest.py -vv
655
655
```
656
656
Expected: collection/import error — `FileNotFoundError`/`spec is None` because `packages/scoop-bucket/generate-manifest.py` does not exist. (Red.)
657
657
@@ -798,13 +798,13 @@ if __name__ == "__main__":
798
798
799
799
-[ ]4. Run the test and watch it pass (Green):
800
800
```bash
801
-
/home/ai/.local/bin/uv run pytest tests/test_scoop_manifest.py -vv
801
+
uv run pytest tests/test_scoop_manifest.py -vv
802
802
```
803
803
Expected: `3 passed`.
804
804
805
805
-[ ]5. Run the repo lint on the new files so the PR's `check` job will be green (matches `make check-pythinker-code`):
806
806
```bash
807
-
/home/ai/.local/bin/uv run ruff check tests/test_scoop_manifest.py
807
+
uv run ruff check tests/test_scoop_manifest.py
808
808
```
809
809
Expected: `All checks passed!` (the generator lives under `packages/scoop-bucket/` which is generator code; if ruff flags it, match the homebrew generator's style — it passes today.)
810
810
@@ -1319,7 +1319,7 @@ Expected: `OK: no versioned strings added`.
1319
1319
1320
1320
-[ ]4. If P1 is merged, run the lockstep test to prove the README edits didn't break it:
1321
1321
```bash
1322
-
/home/ai/.local/bin/uv run pytest tests/test_version_lockstep.py -vv
1322
+
uv run pytest tests/test_version_lockstep.py -vv
1323
1323
```
1324
1324
Expected: `passed` (only run if P1's test exists; if P1 isn't merged yet, skip and note it).
# The strapline and the help line must not share one rendered line.
91
91
assertnotany(
92
-
"then Create."in ln and"Send /help"in ln for ln in out.splitlines()
92
+
"Build with confidence."in ln and"Type /help"in ln for ln in out.splitlines()
93
93
)
94
94
```
95
95
@@ -103,7 +103,7 @@ uv run pytest tests/ui_and_conv/test_shell_welcome_info.py -q -k "footer or pipe
103
103
Expected: all three FAIL.
104
104
-`test_welcome_chip_renders_in_footer_not_header` fails because today the chip is in the header (top), so `lines[-1]` has no "changelog" and `lines[:3]` contains it.
105
105
-`test_welcome_info_grid_has_no_pipe_separator` fails because today the row is `│ Directory │ /tmp/proj │` → 3 pipes, not 2.
106
-
-`test_welcome_strapline_and_help_on_separate_lines` fails because today "then Create." and "Send /help" share one line.
106
+
-`test_welcome_strapline_and_help_on_separate_lines` fails if "Build with confidence." and "Type /help for commands." share one line.
107
107
108
108
-[ ]**Step 3: Confirm the 5 existing tests still pass (no regression introduced by the new tests)**
109
109
@@ -139,9 +139,9 @@ def _print_welcome_info(
139
139
_t = _get_tui_tokens()
140
140
head = Text.from_markup("Welcome to Pythinker — think first, then code.")
141
141
strapline = Text.from_markup(
142
-
f"[{_t.muted}]Review · Secure · Diagnose · then Create.[/]"
142
+
f"[{_t.muted}]Review · Secure · Diagnose · Build with confidence.[/]"
143
143
)
144
-
help_text = Text.from_markup(f"[{_t.muted}]Send /help for help.[/]")
144
+
help_text = Text.from_markup(f"[{_t.muted}]Type /help for commands.[/]")
# Logo on the left; the 3-line text block bottom-aligns against the 5-line
@@ -338,4 +338,3 @@ rm -f /tmp/verify_banner.py
338
338
-**Spec coverage:** §3 changes 1–5 each map to a Task-2 step (chip→footer = `subtitle=`; bottom-align = `vertical="bottom"`; split strapline = three `Text` lines; drop pipe = 2-col info grid; token border = `tui_rich_style`). §5 new tests = Task 1. §6 verification = Task 3.
339
339
-**Placeholder scan:** no TBD/TODO; every code step shows complete code; every command shows expected output.
340
340
-**Type/name consistency:**`WelcomeInfoItem`, `_value_style_for_label`, `_get_tui_tokens`, `tui_rich_style`, `get_version`, `Group`, `RenderableType`, `Table`, `Text`, `Panel`, `box` are all already imported in the target file; no new imports needed. `_print_welcome_info` keeps its exact signature, so the `Shell.run()` call site is unaffected.
│ SSOT = pyproject.toml:3 (the ONLY authoritative version) │
20
20
│ scripts/release.py rewrites all derived files + uv.lock, │
@@ -103,7 +103,8 @@ After P0+P1: **zero long-lived cross-repo PATs** on the release path. The only r
103
103
**Source:**`pyproject.toml:3``[project].version`. The installed package reads `importlib.metadata.version("pythinker-code")` (`constant.py:14`) — there is no `__version__` constant, so this is genuinely the only authoritative store.
104
104
105
105
**The SSOT→site chain (proves distribution + site consume the orchestrator's source):**
**Required code change (don't ship non-self-updating channels without it):**`src/pythinker_code/ui/shell/update.py` — add a `PYTHINKER_MANAGED=<channel>` env read at the top of `_detect_upgrade_command()` (mirrors hermes `HERMES_MANAGED`); Docker/Nix set it, Scoop/WinGet manifests set it → channel-native upgrade hint. **Brew must NOT set `PYTHINKER_MANAGED`** (keep its existing path-sniff so behavior is unchanged); **mandatory regression test** that brew still maps to `['brew','upgrade','pythinker-code']` (the `.pythinker-native` marker means brew also trips `is_native_build()`; precedence is load-bearing). This change ships in P1 as prep so P2 channels are not released non-self-updating.
210
+
**Required code change (don't ship supported non-self-updating channels without it):**`src/pythinker_code/ui/shell/update.py` — add a `PYTHINKER_MANAGED=<channel>` env read at the top of `_detect_upgrade_command()` (mirrors hermes `HERMES_MANAGED`); Docker/Nix set it and Scoop manifests set it → channel-native upgrade hint. **WinGet does not set `PYTHINKER_MANAGED`** because its workflow submits installer metadata only and the WinGet manifest cannot inject a process env var, so it uses the generic native-updater path. **Brew must NOT set `PYTHINKER_MANAGED`** (keep its existing path-sniff so behavior is unchanged); **mandatory regression test** that brew still maps to `['brew','upgrade','pythinker-code']` (the `.pythinker-native` marker means brew also trips `is_native_build()`; precedence is load-bearing). This change ships in P1 as prep so P2 channels are not released non-self-updating where markers are supported.
210
211
211
212
**C4 for new channels:** README install snippets MUST be **version-less** (`scoop install pythinker-code`, `docker run ghcr.io/techmatrix-labs/pythinker-code`, `nix run github:TechMatrix-labs/pythinker-code`) so they never enter the F3 sprawl set.
212
213
@@ -230,13 +231,16 @@ The discriminator that matters in `sync-upstream-products.ts`:
230
231
-**Release-asset download URLs** (`/releases/download/<tag>/...`, plus the deb/rpm/exe URLs + shas in `pythinkerCodeRelease.ts`) **must be built from the live API's `release.tag_name`, never from the payload ref** — a SHA there 404s. The sync already derives release data from `/releases/latest`; this codifies that the payload `tag` is used only for raw-source pinning and **never** for constructing asset URLs.
231
232
232
233
**Deploy chain (the commit-push IS the deploy trigger):**
→ Dokploy build-from-source (nixpacks → bun run server.ts: serves dist/ + bun:sqlite install-counter) → pythinker.com
236
238
```
237
239
238
240
**Deploy resolution (F5):****Canonical = Dokploy build-from-source.** GitHub Pages is **disqualified** — `server.ts` needs a runtime + SQLite + POST endpoint. GHCR+Watchtower path is **provably dead** (no image since `docker.yml` deleted; `deploy/.env.example``SITE_IMAGE` points at the wrong org `mohamed-elkholy95`). **Retire** (after confirming the live host runs Dokploy, not compose): `docker-compose.yml`, `docker-compose.private-ghcr.yml`, `deploy/traefik/`, Watchtower + all GHCR refs, rewrite `deploy/README.md` around Dokploy. **Keep:**`Dockerfile` (documented single-container fallback), `nixpacks.toml`, `server.ts`. **State the dependency:** the deploy chain relies on `pythinker-home` main being unprotected (verified) so the workflow can push; if it's ever protected, exempt `github-actions[bot]` or the chain breaks.
239
241
242
+
**Operational branch-protection note:** if `pythinker-home` main is protected, the sync workflow cannot push the generated site files and Dokploy never sees a new build-from-source commit. Symptom: the sync workflow fails at its `git push main` step; check the `sync-upstream-products.yml` CI logs in `pythinker-home`. Remediation is either (1) remove main-branch protection for this repository, or (2) keep protection but add `github-actions[bot]` as an allowed actor/bypass for pushes from the workflow. Preserve one of those two settings whenever branch protection is changed.
243
+
240
244
**Install-script locations — distinguish code-repo sources from site mirrors (critique item 7):**
241
245
-**Code repo (KEEP — they are dispatch triggers):**`scripts/install.ps1` + `scripts/install-native.sh` are the **source** scripts; `dispatch-pythinker-home-sync.yml` watches them (`paths:` lines 16-17). Do **not** delete these.
242
246
-**Site repo (`git rm` the dead mirrors):** the canonical served pair is **`public/install.sh` + `public/install.ps1`** (the sole Vite-served dir, confirmed kept). The 3 dead, git-tracked mirrors — `scripts/install.ps1`, `web/public/install.ps1`, `docs/public/install.ps1` (all verified tracked + byte-identical) — get `git rm`'d and dropped from `installMirrors[].targetPaths`.
@@ -290,7 +294,7 @@ Add a **`.sha256` sidecar existence check** (right-sized: defense-in-depth for t
290
294
**Risk:** Low-medium (tool runs locally before any push; gates mirror CI; no workflow topology change). **Reversibility:** High (additive; manual bump still works if tool unused). **Effort:**~2–3 days incl. tests + a dry-run release rehearsal.
291
295
292
296
### P2 — Breadth (new channels; broaden distribution)
293
-
**Scope, in order:** (1) Docker/GHCR (`Dockerfile` + `docker.yml`, ancestor-check, `GITHUB_TOKEN`); (2) Scoop (`scoop-pythinker`**org repo** + `pythinker-scoop-publisher`**org App** + `generate-manifest.py` polling the windows zip + `scoop-bucket.yml`); (3) Nix (`apps.default` net-new + `nix build .#default`/`nix run` CI + monthly `update-flake-lock` PR); (4) WinGet (manual `workflow_dispatch`, isolated PAT) — last; AUR deferred. Each new channel: version-less README snippet (C4), `PYTHINKER_MANAGED` set, best-effort (never gates promote).
297
+
**Scope, in order:** (1) Docker/GHCR (`Dockerfile` + `docker.yml`, ancestor-check, `GITHUB_TOKEN`); (2) Scoop (`scoop-pythinker`**org repo** + `pythinker-scoop-publisher`**org App** + `generate-manifest.py` polling the windows zip + `scoop-bucket.yml`); (3) Nix (`apps.default` net-new + `nix build .#default`/`nix run` CI + monthly `update-flake-lock` PR); (4) WinGet (manual `workflow_dispatch`, isolated PAT) — last; AUR deferred. Each new channel: version-less README snippet (C4), best-effort (never gates promote); Docker/Scoop/Nix set `PYTHINKER_MANAGED`, while WinGet's no-marker limitation is documented.
294
298
**Risk:** Medium (new repos/Apps/secrets; all best-effort so they can't worsen F1). **Reversibility:** High (each channel is an independent additive workflow; delete to remove). **Effort:** Docker ~1 day, Scoop ~1.5 days, Nix ~0.5 day, WinGet ~0.5 day — adopt incrementally, one PR each.
295
299
296
300
**Cross-phase invariants:** every PR goes branch → PR → checks pass → CodeRabbit `success` (C2) → merge → (release PRs) tag (C1); no direct main push; no new agent runtime dep (C3); README/badges move with the bump (C4); authored CHANGELOG narrative preserved, contributor footer only as release-notes addendum (C5).
0 commit comments