diff options
| author | Lester Caine <lester@lsces.co.uk> | 2026-06-11 14:28:40 +0100 |
|---|---|---|
| committer | Lester Caine <lester@lsces.co.uk> | 2026-06-11 14:28:40 +0100 |
| commit | 079c17e51284796173dd3205fcb20f09fb502f8f (patch) | |
| tree | d7c049a8bc8a3b086e9a15d669b9aaed20a9e2d9 /CLAUDE.md | |
| parent | 8abd6a28d16ed591c133bafd797026a29d45356a (diff) | |
| download | bitweaver-079c17e51284796173dd3205fcb20f09fb502f8f.tar.gz bitweaver-079c17e51284796173dd3205fcb20f09fb502f8f.tar.bz2 bitweaver-079c17e51284796173dd3205fcb20f09fb502f8f.zip | |
CLAUDE.md: document ContactPerson/ContactBusiness plan and xref architecture
- Contact section: add planned ContactPerson/ContactBusiness subclasses note —
dual-guid pattern, $isPerson replacement, upgrade script 5.0.3.php outline,
rainbowdigitalmedia test-first requirement
- Dual-guid xref schema: new section covering package-level vs content-type-level
split with stock as reference implementation and JOIN rule
- LibertyXrefType instance class: document constructor, xrefType() accessor,
static vs instance method split
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'CLAUDE.md')
| -rw-r--r-- | CLAUDE.md | 425 |
1 files changed, 425 insertions, 0 deletions
diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..61f32f6 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,425 @@ +# Bitweaver-lsces Project Context + +## Stack +- PHP 8.6 / Firebird 5 / adodb / Smarty +- One repo per package (bitweaver-lsces organisation) +- webtrees used as an additional package with illuminate-firebird providing Firebird DB driver (personal fork) +- externals/ holds actively-developed third-party dependencies +- No npm, no Node.js — ever + +## Philosophy +- Fix root causes, not symptoms +- Self-hosted, document everything +- Internal stays internal +- Clean diffs matter more than PSR compliance for its own sake + +## Deploy Path +Code is edited and tested in `/srv/website/bitweaver5` (xdebug available). +Each bitweaver package is a self-contained directory under `bitweaver5/`, with its +own **individual git repo** under `~/Development/bitweaver-lsces/<package>/`. +Proven changes are copied to the matching package repo — normally by CC directly, +with BeyondCompare used for manual review when needed. + +Deploy steps: +1. Copy changed files to `~/Development/bitweaver-lsces/<package>/` +2. Commit in that package's git repo +3. `git push` — updates GitHub (publish-only, not part of the deploy chain) +4. `/etc/webstack/scripts/server-pull-all.sh <package>` — pulls to srv9 and srv10 from the desktop's local copy + +Servers do NOT pull from GitHub — they pull from the desktop's local copy. +After pulling on a server, clear the Smarty template cache and restart php-fpm. + +Server configuration (nginx, PHP, Firebird) lives in `/etc/webstack/` — a separate +git repo replicated across desktop, srv9, and srv10. Never look in `/etc/nginx` or +`/etc/php*` — they are not the source of truth. +`/etc/webstack` pushes to the bare repo at `/srv/git/webstack.git` (not GitHub) — always +`git push` there before pulling on servers, otherwise servers see stale state. + +> NOTE: `/srv/git/bitweaver` → nginx is not yet wired up (infrastructure thread — do not action). + +Test all changes on srv9 first (including `zypper dup` before system updates). +srv10 is production — only gets changes proven on srv9. + +## Scope +Focus exclusively on Bitweaver code in the current working package. +Do not roam into other packages unless explicitly asked. + +**Ignore completely:** +- `webtrees/` — separate application, has its own work thread +- `vendor/` — composer-managed, do not touch +- `externals/` — third-party libs, treat as read-only unless explicitly asked +- `~/Development/` — not relevant to in-place editing +- `/etc/nginx`, `/etc/php*` — not the source of truth, see /srv/webstack + +## Patterns & Conventions +- Tabs, not spaces +- Short array syntax `[]` throughout +- Double quotes preferred (interpolation available) +- adodb for all DB access — Firebird 5 +- No composer autoload changes without explicit discussion +- No framework magic — keep it explicit and traceable + +## PHP-CS-Fixer +Config at project root. Rules in use: +- `array_syntax` short +- `indentation_type` (tabs) +- `no_trailing_whitespace`, `no_whitespace_in_blank_line` +- `ternary_to_null_coalescing` — `isset($x) ? $x : $y` → `$x ?? $y` +- `get_class_to_class_keyword` — `get_class($x)` → `$x::class` +- `modernize_types_casting` — `intval()` → `(int)` etc (risky — verify output) +- `use_arrow_functions` — eligible closures → `fn()` (risky — verify scoping) +- `no_unused_imports`, `no_useless_else`, `no_useless_return` +- `trailing_comma_in_multiline` +- `blank_line_after_namespace` +- `no_extra_blank_lines` + +**Removed (noise, no value):** +- `single_quote` — double quotes are fine, interpolation is useful +- `ordered_imports` — pure pedantry, clutters diffs + +Fixer has been run across 741 files. Any future run should use `--dry-run --diff` first. + +## Current Work Threads +stock and contact packages + +### Active +- hauth/facebook login — keep option open; not culling +- JavaScript tidy — other areas beyond util/javascript + +### Pending +- webtrees data/images separation (buried in app, needs separating like bitweaver storage) +- externals/composer halfway-house — ckeditor and util-type dependencies +- `/srv/git/bitweaver` → nginx wiring (infrastructure thread, separate from code work) +- phpsurgery theme: currently using BlueSky — needs a proper site-specific theme tidy +- contact + stock: audit `expunge` permission gating on xref item templates — some delete + actions (expunge=1/-1) push to history rather than hard-delete, so should gate on + `_update` not `_expunge`. Trace `edit_xref.php` for each `expunge` value: if it sets a + deleted flag rather than `DELETE FROM`, move to `p_contact_update` / `p_stock_update`. + Stock xref deletes currently gate on `$xrefAllowEdit` only — need `p_stock_expunge` added. +- icon set — current tango icons for stock/contact menus are placeholder; proper custom + icons needed for assemblies, components, movements, requisitions, add-person, add-business + +## Bitweaver Structure Notes + +### Permission / Role system +Default role_id values (ANONYMOUS_TEAM_ID = -1): +- `1` Administrators — perm_level `admin` +- `2` Editors — perm_level `editors` +- `3` Registered — perm_level `registered` +- `-1` Anonymous — perm_level `basic` + +Permissions assigned in `*/admin/schema_inc.php`. Role assignments stored in +`users_role_permissions`. When writing xref role-filter queries, guard `mRoles` +with `array_keys($gBitUser->mRoles ?? []) ?: [-1]` — Firebird rejects empty `IN()`. + +### Session / Auth cookie +Cookie name = `bit-user-{site_title_stripped}` (lowercase, alphanum only). +Login stores PHP `session_id()` in `users_cnxn.cookie` mapped to `user_id`. +Subsequent requests look up the cookie value in `users_cnxn` to identify the user — +this is separate from PHP's own session mechanism (though they share the same cookie name). + +### Top navbar menu +Each package self-registers via `$gBitSystem->registerAppMenu()` in its `bit_setup_inc.php`. +Rendered by `kernel/templates/top_bar.tpl` — no built-in role gate at the dropdown level. +Config switches (stored in kernel_config, set via Themes > Admin > Menus): +- `menu_$pkg = 'n'` — disable dropdown for all users +- `${pkg}_menu_text` — custom dropdown label +- `${pkg}_menu_position` — sort position +For role-based visibility, override `top_bar.tpl` in `config/themes/merg/kernel/`. + +### CSS load order +`BitThemes::loadStyleData()` loads CSS in this order: +1. Package CSS (around position 300) — each package's `html_head_inc.tpl` +2. Style CSS (`getStyleCssFile()`, position 998) — the active theme's main CSS +3. Browser CSS (`getBrowserStyleCssFile()`, position 999) +4. `themes/css/config.css` — loaded last; use this for global overrides (Bootstrap tweaks etc.) + +Site-specific CSS lives in `/etc/webstack/domains/{site}/themes/{site}/{site}.css` and is +the active theme file for that site (position 998). Site theme images go in +`themes/{site}/images/` within that domain dir — referenced via `{$gBitThemes->getStyleUrl()}images/`. + +### Smarty notes +- `{tr}...{/tr}` for translation in templates; `KernelTools::tra()` in PHP +- `tra` is NOT a Smarty modifier — `"string"|tra` will throw a compiler error +- `{form}` block plugin auto-injects `<input type="hidden" name="tk">` (CSRF ticket) +- **`form-search` hides Bootstrap submit buttons** — Bootstrap 2 `.form-search` suppresses + the submit control. Use `class="minifind"` alone on floaticon filter forms; never add `form-search`. +- `{strip}` removes whitespace between HTML tags; keep `•` separators inside + valid `<li>` elements to avoid detachment +- Per-site footer scripts belong in `kernel/footer_inc.tpl`, NOT `kernel/footer.tpl`. + `footer_inc.tpl` is picked up by the `mAuxFiles` loop in `html.tpl` reliably. + `footer.tpl` as a theme override only loads if the active style matches exactly — + fragile and easy to miss. + +### Module / Layout system +Modules are placed in layout areas via the `THEMES_LAYOUTS` table: +- `LAYOUT_AREA` — column position: `t` (top), `l` (left), `r` (right), `b` (bottom) +- `POS` — sort order within the column +- `MODULE_RSRC` — the Smarty template to render, e.g. `bitpackage:kernel/mod_top_banner.tpl` +- `ROLES` — role_id that can see this module (empty = all users); use `-1` for anonymous-only + +The 't' column is rendered inside `<header id="bw-main-header">` via `displayLayoutColumn('t')`. +Bootstrap grid columns (`col-sm-*`) inside modules MUST be wrapped in a `clearfix` parent; +without it, subsequent modules in the same column float up and overlay earlier ones. + +Managed via Admin > Themes > Layout. Template includes resolve via `bitpackage:` prefix. + +Column visibility is controlled by feature flags in `kernel_config` checked in `BitThemes::loadLayout()`: +- `{display_mode}_hide_{area}_col` (e.g. `edit_hide_left_col`) +- `{package}_hide_{area}_col` +- `{package}_{display_mode}_hide_{area}_col` +The old hardcoded `display_mode != 'edit'` guard in `html.tpl` has been removed — columns now always +follow these flags. All flags off = columns show on all pages including edit pages. + +### Site-specific theme overrides (/etc/webstack/domains) +Each vhosted site has its theme overrides at `/etc/webstack/domains/{site}/themes/{site}/`. +These are symlinked into `bitweaver5/config/themes/{site}` — e.g.: +``` +/srv/website/bitweaver5/config/themes/merg -> /etc/webstack/domains/merg/themes/merg +``` +Typical contents: `kernel/` (top_bar.tpl, top_banner.tpl, etc.), `images/`, site CSS, favicon. +Any template in this path overrides the package default via Smarty's `bitpackage:` resource lookup. +`config/themes/medw` and `config/themes/merg` are both symlinks — never edit the config/themes +path directly; edit the source in `/etc/webstack/domains/`. + +### Contact package — Person vs Business model + +Two distinct contact types, entered via separate pages: +- `add_person.php` — auto-injects `$00` type; name stored pipe-separated (`prefix|forename|surname|suffix`) in `liberty_xref.xkey_ext` of the `$00` record; `lc.title` = surname; redirects to `edit.php` for further detail +- `add_business.php` — no `$00`; user picks from `$02`+ subtypes (Supplier, Manufacturer etc., expandable via DB); `lc.title` = organisation name; redirects to `edit.php` + +Type codes in `liberty_xref_item` (`content_type_guid='contact'`, `x_group='type'`, `sort_order=0`): +- `$00` Person — triggers name fields in edit/display; never shown as a checkbox in UI +- `$01` Organisation — deprecated, not used in new UI +- `$02`+ Business subtypes — shown as checkboxes in `add_business.php` and `edit.php` + +`edit.php` detects person via `contact_types[0].content_id` → `$isPerson` flag: +- Person: name fields shown, Contact Types section hidden, Organisation hidden +- Business: org field shown, Contact Types (`$02`+) shown, name fields hidden + +`display_contact.tpl` heading = "Personal Contact" / "Business Contact" from `contact_types.0.content_id`. +Name loaded from `$00` xref `xkey_ext` via SQL join in `Contact::load()` (`x00.xkey_ext AS name`). + +xref item templates gate dates and edit actions on `{$xrefAllowEdit}` (pass `allow_edit=false` in view, `allow_edit=true` in edit). + +#### Planned: ContactPerson / ContactBusiness subclasses + +The current `$isPerson` detection via `$00` xref presence is a hack. The plan replaces it +with proper subclasses following the dual-guid xref pattern (as per stock): + +- `ContactPerson extends Contact` — `mContentTypeGuid = 'contactperson'`, `mPackageGuid = 'contact'` +- `ContactBusiness extends Contact` — `mContentTypeGuid = 'contactbusiness'`, `mPackageGuid = 'contact'` + +Shared schema (addresses, SCREF etc.) stays at `content_type_guid='contact'`. +Person-specific types (`$00` default, kitelf, committee member etc.) at `contactperson` level. +Business subtypes (`$02`+: Supplier, Manufacturer etc.) at `contactbusiness` level. +`$isPerson` flag disappears — the class IS the distinction. +Template resolution already works via `mContentTypeGuid` path lookup in LibertyContent. + +**Not yet implemented.** Development/testing on `rainbowdigitalmedia` first. +Upgrade script `contact/admin/upgrades/5.0.3.php` will: +1. Register `contactperson` and `contactbusiness` content types +2. `UPDATE liberty_content SET content_type_guid = 'contactperson'` for records with a `$00` xref +3. Remaining `content_type_guid='contact'` records become `contactbusiness` + +**SCREF** — short reference code for a contact, stored in `liberty_xref.xkey` where `item='SCREF'`. +Used as the `from` value in stock movement CSVs to identify the supplier/source contact. +`contact/includes/lookup_contact.php` provides JSON autocomplete searching by `lc.title` or SCREF `xkey`. + +### Stock package — file naming convention +Entry points follow `verb_contenttype.php` pattern: +- `view_assembly.php`, `edit_assembly.php` +- `view_component.php`, `edit_component.php` +- `view_movement.php`, `edit_movement.php` +- `list_assemblies.php`, `list_components.php`, `list_movements.php` +- `add_supplier.php` — specialist add page (no generic add_assembly/add_component yet) +- `add_order.php` — draft ORDER movement from shortages list; pre-populates lines with + shortage qty, supplier autocomplete, editable qty/delete per line before creating movement + +`view.php` and `edit.php` removed. +`list_stock.php` — stock levels from movement xrefs. Shortages filter (`?shortages=1`) works +on both main list (level < 0) and BOM view (remaining < 0). Shortages view has floaticon icons +for Print, CSV export (`?format=csv`, part_number + qty, skips blanks), and Create Order. + +### Firebird GROUP BY strictness +Firebird requires every non-aggregate column in SELECT to appear in GROUP BY — including `lc.data`, `lc.title` etc. Correlated scalar subqueries in SELECT (e.g. `SELECT FIRST 1 ...`) are exempt. MySQL is more lenient; Firebird is not. + +### Stock movement model +Movement = pure `liberty_content` record (`content_type_guid='stockmovement'`). No `stock_movement` table. + +**Direction** inferred from `reference` xref group (x_group='reference', sort_order=1): +- `REQN` = outbound (to kitlocker) +- `TRANS` = inbound from another elf +- `ORDER` = inbound from supplier + +**Status** = `lc.event_time` (BIGINT, Unix seconds) — `0` = placed/open, positive = received/fulfilled. +`StockMovement::markReceived()` sets it to `time()`. `isReceived()` uses `!empty()` so 0 = not received. + +**Reference xref** (x_group='reference', sort_order=1), one row per movement: +- `item` = REQN/TRANS/ORDER +- `xkey` = reference number/key +- `data` = free-text "from" (fallback if no contact linked) +- `xref` = contact content_id (linked supplier/source — looked up via SCREF xkey) +- `start_date` (TIMESTAMP) = order date (from CSV col 3 or manual entry) + +**Items** = `quantity` xref group (x_group='quantity', sort_order=2), `multiple=1`, one xref row per component line: +- `item` = SGL/PCK/SHT/VOL — quantity type +- `xref` = component content_id +- `xkey` = quantity value +- `xorder` = line sequence (managed explicitly, NOT via `fAddXref` — `multiple=0` on these items for other content types) + +**CSV format** (one movement per file): line 1 = `from(SCREF), ref, order_date(dd/mm/yy), received_date(dd/mm/yy optional)`; lines 2+ = `component_title, quantity, [optional qty type]`. Qty type defaults to component's existing xref type if not specified. + +Uploaded CSVs are saved to `STOCK_IMPORT_PATH` (`storage/stock/`) as `<origname>_move_<content_id>.csv` for audit. BOM uploads save as `<origname>_bom_<content_id>.csv`. + +**`storeXref()` always needs a named variable** — it takes `&$pParamHash` by reference; passing a literal array is a fatal error. + +### LibertyXref / xorder +`liberty_xref.xorder` — used for BOM grouping and sort. Must be explicitly selected +in queries; it is not auto-included in standard SELECT lists. + +### LibertyXrefType — instance class +`LibertyXrefType` is an **instance class**, not a bag of statics. Construct with +`new LibertyXrefType( $contentTypeGuid, $packageGuid = null )`. In page/class code, +always access it via `LibertyContent::xrefType()` which lazily creates and caches the +instance. The five runtime query methods (`getDisplayGroups`, `getTypeMarkers`, +`getAvailableItems`, `getTemplateFormats`, `getContentTypeMarkers`) are instance methods. +Admin cross-type queries (`getXrefTypeList`, `getContentTypeGuids`, `getGroupList`) +remain static. + +### Dual-guid xref schema (package-level + content-type-level) +A package with multiple content types can define xref groups/items at two levels: +- **Package-level** — groups shared across all content types in the package, keyed by the package guid +- **Content-type-level** — groups specific to one content type, keyed by the content type guid + +**Stock is the reference implementation:** +- Package-level (`'stock'`): `stgrp`, `supplier`, `kitlocker` — apply to both assemblies and components +- Content-type-level (`'stockcomponent'`): `quantity`, `values`; (`'stockassembly'`): `quantity` + +To support this, pass `$packageGuid` when constructing `LibertyXrefType` or `LibertyXrefInfo` +(both accept it as an optional second argument). The `mPackageGuid` property on `LibertyContent` +is set automatically by `registerContentType()` when `handler_package` differs from the content +type guid — so subclasses get it for free. + +When writing xref JOIN queries that span both levels, always join item↔group on +`t.content_type_guid = s.content_type_guid` (self-consistent); apply the guid `IN()` filter +only in the WHERE clause on `s`. Putting the filter in the JOIN ON instead causes +cross-matching when two guids share an `x_group` name. + +### LibertyXrefGroup display path (contact + stock) + +**PHP pattern** — display and edit pages: +```php +$gContent->loadXrefInfo(); +$gBitSmarty->assign( 'gXrefInfo', $gContent->mXrefInfo ); +``` + +**Template pattern** — view and edit templates: +```smarty +{foreach $gXrefInfo->mGroups as $xrefGroup} + {include file=$gContent->getXrefListTemplate($xrefGroup->mTemplate) + xrefGroup=$xrefGroup allow_edit=false} {* true for edit pages *} +{/foreach} +``` + +Group templates receive `$xrefGroup` (LibertyXrefGroup object). First two lines must be: +```smarty +{assign var=xrefAllowEdit value=$allow_edit|default:false} +{assign var=isHistory value=($xrefGroup->mXGroup eq 'history')} +``` + +Fallback for groups with no specific template → `liberty/list_xref.tpl`. + +**Floaticon placement** — on list pages the floaticon div goes inside the `<div class="floaticon">` +inside the assembly view's header. On view/edit pages it goes inside the `.header` div before +the `<h1>`. Forms in the floaticon use `class="minifind"` for correct spacing (see Smarty notes). +`assembly_icons_inc.tpl` is included in every assembly view layout's header — put assembly-level +icons there rather than in `view_assembly.tpl` directly. + +**Kitlocker tab visibility** — `edit_component.php` and `view_component.php` detect kitlocker +components via a KLID xref presence check and assign `$isKitlocker`. In templates, stash the +`kitlocker` and `stgrp` groups during the normal foreach and render them at the end only when +`$isKitlocker` is true: +```smarty +{assign var=klGroup value=null}{assign var=sgGroup value=null} +{foreach $gXrefInfo->mGroups as $xrefGroup} + {if $xrefGroup->mXGroup eq 'kitlocker'}{assign var=klGroup value=$xrefGroup} + {elseif $xrefGroup->mXGroup eq 'stgrp'}{assign var=sgGroup value=$xrefGroup} + {else}{include file=... xrefGroup=$xrefGroup ...}{/if} +{/foreach} +{if $isKitlocker && $klGroup}{include ...}{/if} +{if $isKitlocker && $sgGroup}{include ...}{/if} +``` + +**movement edit_movement.php** filters 'reference' group in template: +`{if $xrefGroup->mXGroup neq 'reference'}` — reference is rendered directly in the form. + +**CSV import xorder** — `ImportContactCSV.php` explicitly sets xorder: 0 for single items, +1 for #P/#F (multiple=1) - will need to address when more than one record needed. + +**`contact_types` in Contact** — separate from the display path. Populated by +`loadXrefTypeList()` which queries sort_order=0 items (the 'type' group: `$00`, `$02`+). +Used for `$isPerson` detection in `edit.php` and display templates. `loadXrefInfo()` +deliberately excludes sort_order=0, so there is no overlap. + +**Raw LEFT JOINs in `Contact::load()`** — joins `liberty_xref` directly for `$00` +(person name), `#S` (service address), `#L` (location), `IMG` (gallery). `IMG`, `#S`, +`#L` have no live data. + +**Pending: Contact::load() cleanup** — remove the three commented-out dead joins; replace +the active raw xref joins with the proper path: `$00` name from `loadXrefTypeList()` with +`xkey_ext` added; `#S`/`#L`/`ap` from `loadXrefInfo()` address group (postcode join +already present in `LibertyXrefGroup::loadXrefs()`); gallery association needs rethinking +separately from the xref mechanism. + +View pages pass `allow_edit=false` (or omit), edit pages pass `allow_edit=true`. + +### Contact delete / expunge +`edit.php` handles `expunge=1`: checks `p_contact_expunge`, calls `$gContent->expunge()`, +redirects to `list_contacts.php`. `contact_date_bar.tpl` uses +`{smartlink ... ifile="edit.php" expunge=1}`. `Contact::expunge()` deletes liberty_xref rows +then calls `LibertyContent::expunge()`. + +## Infrastructure + +### /etc/webstack +Single git repo replicated across desktop, srv9, and srv10. Push to `/srv/git/webstack.git` +before pulling on servers — servers pull from the desktop copy, not GitHub. +Contains: nginx vhost configs, logrotate, cron scripts, domain theme symlinks, PHP/Firebird config. +`/etc/logrotate.d/nginx` is a **hard link** to `/etc/webstack/logrotate.d/nginx` — one config, +not two competing ones. Never edit via `/etc/logrotate.d/` directly. + +### Nginx log structure +Central combined log: `/var/log/nginx/access.log` — every request from all vhosts, domain name +appended at end of each line by nginx log format. +Per-domain dirs: `/var/log/nginx/{domain}/` — contain `access.log` (filtered, rebuilt nightly), +`80.access.log` (port 80, written directly by nginx), and `error.log`. All are covered by the +domain logrotate stanza (`size=+8M`, `rotate 7`). The filtered `access.log` is always small so +`notifempty` skips it; the stanza exists for `80.access.log` and `error.log`. + +Logrotate on Tumbleweed is a **crontab entry**, not a cron.daily script — ordering tricks +(renaming files in cron.daily) do not affect when logrotate runs relative to nginx-stats. + +Raw log rotation (parent only): `size=+16M`, `rotate 14`, `maxage 30`, `delaycompress`. +At current traffic (~7 MB/day) this gives ~30 days of history across ~14 archives. +Compressed archives use `.xz` or `.gz` depending on when they were created. + +### nginx-stats / goaccess +Script: `/etc/webstack/cron.daily/nginx-stats` + +Runs nightly. Builds `/tmp/nginx-combined.log` from the current `access.log` plus all +parent archives (decompressing `.xz`/`.gz` inline), then greps per domain into +`/var/log/nginx/{domain}/access.log`, runs goaccess, publishes +`/srv/website/{domain}/stats-rep.html`. Cleans up `combined.log` on exit. + +Domain `access.log` files are ephemeral outputs — rebuilt from parent archives each run. +History lives entirely in the parent archive chain; no domain-level archives are created. + +## Session Management +At the end of each productive session, append discoveries, decisions, and completed items to this file. +Use `/clear` to reset context when it gets bloated — this file re-orients the session. + +## CC Limitations +For execution-order bugs and session/config state problems, +use xdebug rather than asking CC to trace — static analysis +cannot follow runtime state reliably.
\ No newline at end of file |
