chore: checkpoint all prior work for clean state

Captures all uncommitted work from ha-config-bootstrap,
ha-heartbeat-liveness, and associated spec/tooling updates:

- src: ha.c, ha.h, cluster.c, config_repl.c, myapp.c, raft.c —
  heartbeat liveness, RDHUP integration, committing phase fixes
- tests: test_ha_role.c, test_config_repl.c, test_ha_rdhup_integration.c,
  test_p1_integration.c, test_p3_hardening.c, scheduler test
- specs: election-iomgr-refactor, ha-heartbeat-liveness,
  ha-peer-connection-reuse, raft-library (new); ha-config-bootstrap
  and election-iomgr-refactor task/research updates
- tooling: .wolf/ state, ha/data cluster fixtures, raft/raft.tla,
  src/build symlink, xce-build skill update
This commit is contained in:
Rongsong Shen 2026-06-04 13:04:16 +00:00
parent 15bd291bdd
commit e31673dd88
66 changed files with 122422 additions and 860 deletions

135
.wolf/OPENWOLF.md Normal file
View file

@ -0,0 +1,135 @@
# OpenWolf Operating Protocol
You are working in an OpenWolf-managed project. These rules apply every turn.
## File Navigation
1. Check `.wolf/anatomy.md` BEFORE reading any file. It has a 2-3 line description and token estimate for every file in the project.
2. If the description in anatomy.md is sufficient for your task, do NOT read the full file.
3. If a file is not in anatomy.md, search with Grep/Glob, then update anatomy.md with the new entry.
## Code Generation
1. Before generating code, read `.wolf/cerebrum.md` and respect every entry.
2. Check the `## Do-Not-Repeat` section — these are past mistakes that must not recur.
3. Follow all conventions in `## Key Learnings` and `## User Preferences`.
## After Actions
1. After every significant action, append a one-line entry to `.wolf/memory.md`:
`| HH:MM | description | file(s) | outcome | ~tokens |`
2. After creating, deleting, or renaming files: update `.wolf/anatomy.md`.
## Cerebrum Learning (MANDATORY — every session)
OpenWolf's value comes from learning across sessions. You MUST update `.wolf/cerebrum.md` whenever you learn something useful. This is not optional.
**Update `## User Preferences` when the user:**
- Corrects your approach ("no, do it this way instead")
- Expresses a style preference (naming, structure, formatting)
- Shows a preferred workflow or tool choice
- Rejects a suggestion — record what they preferred instead
- Asks for more/less detail, verbosity, explanation
**Update `## Key Learnings` when you discover:**
- A project convention not obvious from the code (e.g., "tests go in __tests__/ not test/")
- A framework-specific pattern this project uses
- An API behavior that surprised you
- A dependency quirk or version constraint
- How modules connect or data flows through the system
**Update `## Do-Not-Repeat` (with date) when:**
- The user corrects a mistake you made
- You try something that fails and find the right approach
- You discover a gotcha that would trip up a fresh session
**Update `## Decision Log` when:**
- A significant architectural or technical choice is made
- The user explains why they chose approach A over B
- A trade-off is explicitly discussed
**The bar is LOW.** If in doubt, add it. A cerebrum entry that's slightly redundant costs nothing. A missing entry means the next session repeats the same discovery process.
## Bug Logging (MANDATORY)
**Log a bug to `.wolf/buglog.json` whenever ANY of these happen:**
- The user reports an error, bug, or problem
- A test fails or a command produces an error
- You fix something that was broken
- You edit a file more than twice to get it right
- An import, module, or dependency is missing or wrong
- A runtime error, type error, or syntax error occurs
- A build or lint command fails
- A feature doesn't work as expected
- You change error handling, try/catch blocks, or validation logic
- The user says something "doesn't work", "is broken", or "shows wrong X"
**Before fixing:** Read `.wolf/buglog.json` first — the fix may already be known.
**After fixing:** ALWAYS append to `.wolf/buglog.json` with this structure:
```json
{
"id": "bug-NNN",
"timestamp": "ISO date",
"error_message": "exact error or user complaint",
"file": "file that was fixed",
"root_cause": "why it broke",
"fix": "what you changed to fix it",
"tags": ["relevant", "keywords"],
"related_bugs": [],
"occurrences": 1,
"last_seen": "ISO date"
}
```
**The threshold is LOW.** When in doubt, log it. A false positive in the bug log costs nothing. A missed bug means repeating the same mistake later.
## Token Discipline
- Never re-read a file already read this session unless it was modified since.
- Prefer anatomy.md descriptions over full file reads when possible.
- Prefer targeted Grep over full file reads when searching for specific code.
- If appending to a file, do not read the entire file first.
## Design QC
When the user asks you to check, evaluate, or improve the design/UI of their app:
1. Run `openwolf designqc` via Bash to capture screenshots.
- The command auto-detects a running dev server, or starts one from package.json if needed
- Use `--url <url>` only if auto-detection fails
- The command saves compressed JPEG screenshots to `.wolf/designqc-captures/`
- Full pages are captured as sectioned viewport-height images (top, section2, ..., bottom)
2. Read the captured screenshot images from `.wolf/designqc-captures/` using the Read tool.
3. Evaluate the design against modern standards (Shadcn UI, Tailwind, clean React patterns):
- Spacing and whitespace consistency
- Typography hierarchy and readability
- Color contrast and accessibility (WCAG)
- Visual hierarchy and focal points
- Component consistency
- Whether the design looks "dull" or "white-coded" (generic, no personality)
4. Provide specific, actionable feedback with fix suggestions.
5. If the user approves, implement the fixes directly in their code.
6. After fixes, re-run `openwolf designqc` to capture new screenshots and verify improvement.
**Token awareness:** Each screenshot costs ~2500 tokens. The command compresses images (JPEG quality 70, max width 1200px) to minimize cost. For large apps, use `--routes / /specific-page` to limit captures.
## Reframe — UI Framework Selection
When the user asks to change, pick, migrate, or "reframe" their project's UI framework:
1. Read `.wolf/reframe-frameworks.md` for the full framework knowledge base.
2. Ask the user the decision questions from the file (current stack, priority, Tailwind usage, theme preference, app type). Stop early once the choice narrows to 1-2 options.
3. Present a recommendation with reasoning based on the comparison matrix.
4. Once the user confirms, use the selected framework's prompt from the file — **adapted to the actual project** using `.wolf/anatomy.md` for real file paths, routes, and components.
5. Execute the migration: install dependencies, update config, refactor components.
6. After migration, run `openwolf designqc` to verify the new look.
**Do NOT read the entire reframe-frameworks.md into context upfront.** Read the decision questions and comparison matrix first (~50 lines). Only read the specific framework's prompt section after the user chooses.
## Session End
Before ending or when asked to wrap up:
1. Write a session summary to `.wolf/memory.md`.
2. Review the session: did you learn anything? Did the user correct you? Did you fix a bug? If yes, update `.wolf/cerebrum.md` and/or `.wolf/buglog.json`.

992
.wolf/anatomy.md Normal file
View file

@ -0,0 +1,992 @@
# anatomy.md
> Auto-maintained by OpenWolf. Last scanned: 2026-06-04T12:21:31.476Z
> Files: 589 tracked | Anatomy hits: 0 | Misses: 0
## ../../../../../claude/.claude/plans/
- `functional-tumbling-squirrel.md` — Plan: Address ha-repl-hardening Design Review Issues (~924 tok)
- `hazy-exploring-swan.md` — Plan: HA Replication Hardening (~1100 tok)
- `prancy-herding-nest.md` — Plan: HA Election + Config Bootstrap Enhancement (~13241 tok)
## ../../../../../claude/.claude/projects/-home-rshen-projects-xce-xce/memory/
- `MEMORY.md` — Memory Index (~41 tok)
- `project_ha_bootstrap_progress.md` (~363 tok)
## ./
- `.clang-format` (~226 tok)
- `.gitignore` — Git ignore rules (~88 tok)
- `CLAUDE.md` — OpenWolf (~2900 tok)
- `README.md` — Project documentation (~612 tok)
## .claude/
- `settings.json` (~441 tok)
- `settings.local.json` (~30 tok)
## .claude/rules/
- `openwolf.md` (~313 tok)
## .claude/skills/kiro-debug/
- `SKILL.md` — kiro-debug (~1322 tok)
## .claude/skills/kiro-discovery/
- `SKILL.md` — kiro-discovery Skill (~3387 tok)
## .claude/skills/kiro-impl/
- `SKILL.md` — kiro-impl Skill (~4048 tok)
## .claude/skills/kiro-impl/templates/
- `debugger-prompt.md` — Debug Investigator (~812 tok)
- `implementer-prompt.md` — TDD Task Implementer (~1659 tok)
- `reviewer-prompt.md` — Task Implementation Reviewer (~1352 tok)
## .claude/skills/kiro-review/
- `SKILL.md` — kiro-review (~1756 tok)
## .claude/skills/kiro-spec-batch/
- `SKILL.md` — kiro-spec-batch Skill (~2293 tok)
## .claude/skills/kiro-spec-design/
- `SKILL.md` — kiro-spec-design Skill (~2881 tok)
## .claude/skills/kiro-spec-design/rules/
- `design-discovery-full.md` — Full Discovery Process for Technical Design (~949 tok)
- `design-discovery-light.md` — Light Discovery Process for Extensions (~389 tok)
- `design-principles.md` — Technical Design Rules and Principles (~2982 tok)
- `design-review-gate.md` — Design Review Gate (~1016 tok)
- `design-synthesis.md` — Design Synthesis (~464 tok)
## .claude/skills/kiro-spec-design/templates/specs/
- `design.md` — Design Document Template (~3303 tok)
- `research.md` — Research & Design Decisions Template (~586 tok)
## .claude/skills/kiro-spec-init/
- `SKILL.md` — Spec Initialization (~790 tok)
## .claude/skills/kiro-spec-init/templates/specs/
- `init.json` (~125 tok)
- `requirements-init.md` — Requirements Document (~40 tok)
## .claude/skills/kiro-spec-quick/
- `SKILL.md` — Quick Spec Generator (~1995 tok)
## .claude/skills/kiro-spec-quick/templates/specs/
- `init.json` (~125 tok)
- `requirements-init.md` — Requirements Document (~40 tok)
## .claude/skills/kiro-spec-requirements/
- `SKILL.md` — kiro-spec-requirements Skill (~2125 tok)
## .claude/skills/kiro-spec-requirements/rules/
- `ears-format.md` — EARS Format Guidelines (~739 tok)
- `requirements-review-gate.md` — Requirements Review Gate (~924 tok)
## .claude/skills/kiro-spec-requirements/templates/specs/
- `requirements.md` — Requirements Document (~325 tok)
## .claude/skills/kiro-spec-status/
- `SKILL.md` — kiro-spec-status Skill (~746 tok)
## .claude/skills/kiro-spec-tasks/
- `SKILL.md` — kiro-spec-tasks Skill (~2473 tok)
## .claude/skills/kiro-spec-tasks/rules/
- `tasks-generation.md` — Task Generation Rules (~2773 tok)
- `tasks-parallel-analysis.md` — Parallel Task Analysis Rules (~633 tok)
## .claude/skills/kiro-spec-tasks/templates/specs/
- `tasks.md` — Implementation Plan (~318 tok)
## .claude/skills/kiro-steering-custom/
- `SKILL.md` — kiro-steering-custom Skill (~1230 tok)
## .claude/skills/kiro-steering-custom/rules/
- `steering-principles.md` — Steering Principles (~593 tok)
## .claude/skills/kiro-steering-custom/templates/steering-custom/
- `api-standards.md` — API Standards (~415 tok)
- `authentication.md` — Authentication & Authorization Standards (~523 tok)
- `database.md` — Database Standards (~377 tok)
- `deployment.md` — Deployment Standards (~441 tok)
- `error-handling.md` — Error Handling Standards (~549 tok)
- `security.md` — Security Standards (~523 tok)
- `testing.md` — Testing Standards (~325 tok)
## .claude/skills/kiro-steering/
- `SKILL.md` — kiro-steering Skill (~1191 tok)
## .claude/skills/kiro-steering/rules/
- `steering-principles.md` — Steering Principles (~593 tok)
## .claude/skills/kiro-steering/templates/steering/
- `product.md` — Product Overview (~89 tok)
- `structure.md` — Project Structure (~218 tok)
- `tech.md` — Technology Stack (~198 tok)
## .claude/skills/kiro-validate-design/
- `SKILL.md` — kiro-validate-design Skill (~1296 tok)
## .claude/skills/kiro-validate-design/rules/
- `design-review.md` — Design Review Process (~1090 tok)
## .claude/skills/kiro-validate-gap/
- `SKILL.md` — kiro-validate-gap Skill (~1385 tok)
## .claude/skills/kiro-validate-gap/rules/
- `gap-analysis.md` — Gap Analysis Process (~1266 tok)
## .claude/skills/kiro-validate-impl/
- `SKILL.md` — kiro-validate-impl Skill (~2826 tok)
## .claude/skills/kiro-verify-completion/
- `SKILL.md` — kiro-verify-completion (~972 tok)
## .claude/skills/xce-build/
- `SKILL.md` — build xce source code (~112 tok)
## .claude/skills/xce-deploy-ha/
- `SKILL.md` — XCE HA Cluster Deployment (~1286 tok)
## .kiro/settings/
- `mcp.json` (~8 tok)
## .kiro/settings/rules/
- `design-discovery-full.md` — Full Discovery Process for Technical Design (~949 tok)
- `design-discovery-light.md` — Light Discovery Process for Extensions (~389 tok)
- `design-principles.md` — Technical Design Rules and Principles (~2982 tok)
- `design-review-gate.md` — Design Review Gate (~1016 tok)
- `design-review.md` — Design Review Process (~1090 tok)
- `design-synthesis.md` — Design Synthesis (~464 tok)
- `ears-format.md` — EARS Format Guidelines (~739 tok)
- `gap-analysis.md` — Gap Analysis Process (~1266 tok)
- `requirements-review-gate.md` — Requirements Review Gate (~924 tok)
- `steering-principles.md` — Steering Principles (~593 tok)
- `tasks-generation.md` — Task Generation Rules (~2773 tok)
- `tasks-parallel-analysis.md` — Parallel Task Analysis Rules (~633 tok)
## .kiro/settings/templates/specs/
- `design.md` — Design Document Template (~3303 tok)
- `init.json` (~125 tok)
- `requirements-init.md` — Requirements Document (~40 tok)
- `requirements.md` — Requirements Document (~325 tok)
- `research.md` — Research & Design Decisions Template (~586 tok)
- `tasks.md` — Implementation Plan (~318 tok)
## .kiro/settings/templates/steering-custom/
- `api-standards.md` — API Standards (~415 tok)
- `authentication.md` — Authentication & Authorization Standards (~523 tok)
- `database.md` — Database Standards (~377 tok)
- `deployment.md` — Deployment Standards (~441 tok)
- `error-handling.md` — Error Handling Standards (~549 tok)
- `security.md` — Security Standards (~523 tok)
- `testing.md` — Testing Standards (~325 tok)
## .kiro/settings/templates/steering/
- `product.md` — Product Overview (~89 tok)
- `structure.md` — Project Structure (~218 tok)
- `tech.md` — Technology Stack (~198 tok)
## .kiro/specs/election-iomgr-refactor/
- `design.md` — Design Document: election-iomgr-refactor (~6671 tok)
- `requirements.md` — Requirements Document (~2695 tok)
- `research.md` — Research & Design Decisions (~5073 tok)
- `spec.json` (~129 tok)
- `tasks.md` — Implementation Plan (~3693 tok)
## .kiro/specs/ha-config-bootstrap/
- `brief.md` — Brief: ha-election-raft-config (~16310 tok)
- `design.md` — Design: HA Config Bootstrap & Raft-Style Quorum Election (~11439 tok)
- `election-trace.md` — Election Trace: 3-Node Cold Start (2026-06-04 00:15:58) (~2907 tok)
- `manual-tests.md` — Manual Integration Tests: ha-config-bootstrap (~3677 tok)
- `requirements.md` — Requirements: HA Config Bootstrap & Raft-Style Quorum Election (~3890 tok)
- `research.md` — Cross-Spec Gap Audit (2026-06-03) (~8150 tok)
- `spec.json` (~198 tok)
- `tasks.md` — Implementation Plan: HA Config Bootstrap & Raft-Style Quorum Election (~5619 tok)
## .kiro/specs/ha-heartbeat-liveness/
- `design.md` — Design Document: ha-heartbeat-liveness (~5409 tok)
- `manual-tests.md` — Manual Integration Tests: ha-heartbeat-liveness — Task 4.5 (~1726 tok)
- `requirements.md` — Requirements Document (~2054 tok)
- `research.md` — Gap Analysis: ha-heartbeat-liveness (~2001 tok)
- `spec.json` (~129 tok)
- `tasks.md` — Implementation Plan (~2227 tok)
## .kiro/specs/ha-peer-connection-reuse/
- `design.md` — Design Document: ha-peer-connection-reuse (~5369 tok)
- `requirements.md` — Requirements Document (~1605 tok)
- `research.md` — Research & Design Decisions: ha-peer-connection-reuse (~3517 tok)
- `spec.json` (~130 tok)
- `tasks.md` — Implementation Plan: ha-peer-connection-reuse (~2022 tok)
## .kiro/specs/ha-repl-hardening/
- `design.md` — Design: HA Replication Hardening (~5514 tok)
- `raft-tla-audit.md` — Raft TLA+ Specification Audit (~1591 tok)
- `requirements.md` — Requirements Document (~2817 tok)
- `research.md` — Gap Analysis: HA Replication Hardening (~2381 tok)
- `spec.json` (~128 tok)
- `tasks.md` — Implementation Plan: HA Replication Hardening (~3502 tok)
## .kiro/specs/raft-library/
- `design.md` — Design Document: raft-library (~7756 tok)
- `requirements.md` — Requirements Document (~2115 tok)
- `research.md` — Research & Design Decisions: raft-library (~579 tok)
- `spec.json` (~127 tok)
- `tasks.md` — Implementation Plan (~4312 tok)
## .kiro/specs/resval-id-decoupling/
- `design.md` — Design: resval-id-decoupling (~3914 tok)
- `requirements.md` — Requirements: resval-id-decoupling (~2360 tok)
- `research.md` — Gap Analysis: resval-id-decoupling (~3180 tok)
- `spec.json` — Declares is (~229 tok)
- `tasks.md` — Implementation Tasks: resval-id-decoupling (~2424 tok)
## .kiro/specs/scheduler-controller-msg-gap/
- `design.md` — Design: scheduler-controller-msg-gap (~4756 tok)
- `requirements.md` — Requirements: scheduler-controller-msg-gap (~2712 tok)
- `research.md` — Research Log: scheduler-controller-msg-gap (~1540 tok)
- `spec.json` (~184 tok)
- `tasks.md` — Tasks: scheduler-controller-msg-gap (~2324 tok)
## .kiro/specs/worker-join-refactor/
- `design.md` — Design Document: worker-join-refactor (~4709 tok)
- `requirements.md` — Requirements Document (~1401 tok)
- `research.md` — Research Log: worker-join-refactor (~3079 tok)
- `spec.json` (~129 tok)
- `tasks.md` — Implementation Plan (~1658 tok)
## .kiro/steering/
- `error-handling.md` — Error Handling Standards (~782 tok)
- `product.md` — Product Overview (~836 tok)
- `security.md` — Security Standards (~804 tok)
- `structure.md` — Project Structure (~1139 tok)
- `tech.md` — Technology Stack (~1041 tok)
- `testing.md` — Testing Standards (~741 tok)
## docs/superpowers/plans/
- `2026-05-07-p0-single-controller-persistence.md` — P0: Single-Controller Persistence — Implementation Plan (~13327 tok)
- `2026-05-07-p1-ha-foundation.md` — P1: HA Foundation — Implementation Plan (~13491 tok)
- `2026-05-07-p2-replication.md` — P2: Replication (Fan-Out TCP) — TDD Implementation Plan (~12797 tok)
- `2026-05-07-p25-discovery.md` — P2.5: libxce Smart Discovery — TDD Implementation Plan (~10344 tok)
- `2026-05-07-p3-hardening.md` — P3: Integration & Hardening — TDD Implementation Plan (~9628 tok)
- `2026-05-07-persistence-ha-overview.md` — XCE Persistence & HA — Implementation Overview (~558 tok)
- `2026-05-13-config-replication.md` — Config Replication Implementation Plan (~21611 tok)
- `2026-05-20-config-repl-wire-protocol.md` — Config Replication — Wire Protocol Completion + Bug Fixes (~5134 tok)
## src/
- `flake.nix` (~571 tok)
- `meson.build` (~623 tok)
- `meson.options` (~90 tok)
## src/.claude/
- `ucai-formatter-cache.local.json` (~17 tok)
## src/autoscaler/
- `meson.build` (~163 tok)
## src/autoscaler/providers/
- `meson.build` (~342 tok)
## src/autoscaler/providers/baidu/
- `BccTool.env` (~64 tok)
## src/autoscaler/providers/baidu/lsf/conf/
- `baiduprov_config.json` (~44 tok)
- `baiduprov_templates.json` (~431 tok)
- `credentials` (~25 tok)
- `logger.yaml` (~196 tok)
## src/autoscaler/providers/baidu/postprovision/lsf/
- `example_user_data.sh` (~486 tok)
## src/autoscaler/providers/baidu/scripts/
- `getAvailableMachines.sh` — This script: (~112 tok)
- `getAvailableTemplates.sh` — This script: (~110 tok)
- `getRequestStatus.sh` — This script: (~110 tok)
- `getReturnRequests.sh` — This script: (~110 tok)
- `requestMachines.sh` — This script: (~110 tok)
- `requestReturnMachines.sh` — This script: (~110 tok)
## src/autoscaler/providers/baidu/src/bcctool/
- `BccTool.env` (~64 tok)
- `go.mod` — Go module definition (~136 tok)
- `go.sum` — Go dependency checksums (~566 tok)
- `main.go` (~355 tok)
## src/autoscaler/providers/baidu/src/bcctool/client/
- `bccClient.go` — ProvCloudClient (63 fields); methods: CreateInstances, ReleaseInstances, ListInstancesByTag, ListAllInstances (~3781 tok)
## src/autoscaler/providers/baidu/src/bcctool/enums/
- `provCode.go` — All error codes used in the Baidu Cloud provider plugin (~1198 tok)
## src/autoscaler/providers/baidu/src/bcctool/model/
- `types.go` — ClientCredential (149 fields); methods: IsBidTemplate, GetNcpus, GetMemory, String (~4213 tok)
## src/autoscaler/providers/baidu/src/bcctool/provconst/
- `bdConst.go` — Declares var (~311 tok)
## src/autoscaler/providers/baidu/src/bcctool/provservices/
- `provService.go` — GetAvailableTemplates, CreateInstancesFromFile, DeleteInstancesFromFile, SyncInstancesStatusFromFile, GetRequestStatusFromFile (~3661 tok)
## src/autoscaler/providers/baidu/src/bcctool/utils/
- `envvars.go` — InitEnvVars (~696 tok)
- `jsonUtils.go` — LoadProvTemplates, LoadProvConfig, LoadNodeCreationInputFile, LoadGetReturnRequestInputFile + 6 more (~2660 tok)
- `logger.go` — Config (50 fields) (~1649 tok)
- `provUtils.go` — InitProv, ExitWithError, Result, CreationTimeout, TerminationTimeout (~718 tok)
## src/autoscaler/scaler/
- `obj_mgr.c` — include <errno.h> (~3260 tok)
- `plug_mgr.c` — Main functions: (~4412 tok)
- `prov_conf.c` — include "prov_conf.h" (~2721 tok)
- `scaler_ctx.c` — include <string.h> (~547 tok)
- `scaler_json.c` — include <stdio.h> (~3716 tok)
- `scaler_misc.c` — include <math.h> (~1482 tok)
- `scaler_obj.c` — include "scaler_obj.h" (~2462 tok)
- `scaler.c` — include <errno.h> (~5076 tok)
## src/autoscaler/scaler/conf/
- `hostProviders.json` (~60 tok)
- `hostProviders.yaml` (~35 tok)
## src/autoscaler/scaler/include/
- `obj_mgr.h` — ifndef _OBJ_MGR_H_ (~492 tok)
- `plug_mgr.h` — ifndef _PLUG_MGR_H_ (~119 tok)
- `prov_conf.h` — ifndef _SCALER_CONF_H_ (~132 tok)
- `scaler_ctx.h` — ifndef _SCALER_CTX_H_ (~73 tok)
- `scaler_json.h` — ifndef __JSON_UTILS_H__ (~267 tok)
- `scaler_misc.h` — ifndef _SCALER_MISC_H_ (~203 tok)
- `scaler_obj.h` — ifndef _SCALER_OBJ_H_ (~273 tok)
- `scaler.h` — ifndef _SCALER_H_ (~384 tok)
## src/autoscaler/scaler/include/scaler/
- `types.h` — ifndef _SCALER_TYPES_H_ (~1091 tok)
## src/autoscaler/scaler/testsuite/
- `test_plug_mgr.c` — include <stdlib.h> (~1794 tok)
- `test_scale_misc.c` — include <stdio.h> (~1258 tok)
- `test.c` — include <stdio.h> (~97 tok)
## src/autoscaler/scaler/testsuite/scripts/
- `getAvailableTemplates.sh` (~242 tok)
- `getRequestStatus.sh` — Parse command line arguments (~644 tok)
- `getReturnRequests.sh` (~44 tok)
- `requestMachines.sh` (~20 tok)
- `requestReturnMachines.sh` (~38 tok)
## src/build/lib/
- `controller.capnp.c` — include "controller.capnp.h" (~134780 tok)
## src/controller/
- `apiserver.c` — include <errno.h> (~5303 tok)
- `CLAUDE.md` — XCE Controller (~2625 tok)
- `cluster.c` — include "config.h" (~11414 tok)
- `config_repl.c` — define _GNU_SOURCE (~13868 tok)
- `config_repl.h` — pragma once (~2069 tok)
- `ctrl_metrics.c` — include "ctrl_metrics.h" (~767 tok)
- `dataex.c` — include <errno.h> (~2466 tok)
- `ha.c` — define _GNU_SOURCE (~30494 tok)
- `ha.h` — ifndef HA_H (~6474 tok)
- `job.c` — include <errno.h> (~8259 tok)
- `join.c` — include "config.h" (~2347 tok)
- `main.c` — include <argp.h> (~1996 tok)
- `meson.build` (~1080 tok)
- `misc.c` — include <errno.h> (~1359 tok)
- `myapp.c` — define _GNU_SOURCE (~3209 tok)
- `myconf.c` — define _GNU_SOURCE (~1100 tok)
- `opget.c` — include "config.h" (~1725 tok)
- `opupd.c` — include "config.h" (~714 tok)
- `persist.c` — define _GNU_SOURCE (~13035 tok)
- `queue.c` — include <errno.h> (~1260 tok)
- `repl.c` — include <assert.h> (~3964 tok)
- `reply.c` — include "config.h" (~590 tok)
- `resources.c` — include <errno.h> (~50 tok)
- `sched.c` — include <errno.h> (~5230 tok)
- `startup_validation.c` — define _GNU_SOURCE (~1423 tok)
- `task.c` — include <errno.h> (~1376 tok)
- `worker.c` — include <errno.h> (~5201 tok)
## src/controller/include/
- `cluster.h` — if !defined(_CLUSTER_H_) (~1669 tok)
- `worker.h` — if !defined(_WORKER_H_) (~190 tok)
## src/controller/testsuite/
- `controller_test.c` — include <argp.h> (~1305 tok)
- `test_cluster_persist.c` — define _GNU_SOURCE (~1062 tok)
- `test_config_repl.c` — define _GNU_SOURCE (~8338 tok)
- `test_ctrl_metrics.c` — define _GNU_SOURCE (~940 tok)
- `test_e2e_capnp.c` — include <stdint.h> (~1690 tok)
- `test_e2e_checkpoint.c` — define _GNU_SOURCE (~1793 tok)
- `test_e2e_gc.c` — define _GNU_SOURCE (~1665 tok)
- `test_e2e_hooks.c` — define _GNU_SOURCE (~2346 tok)
- `test_e2e_wal.c` — define _GNU_SOURCE (~2546 tok)
- `test_gc_retention.c` — define _GNU_SOURCE (~2447 tok)
- `test_ha_bootstrap_support.c` — ifdef XCE_TESTING (~1008 tok)
- `test_ha_bootstrap.c` — does: defer_config_fetch (~5828 tok)
- `test_ha_cluster.c` — include <stdatomic.h> (~1351 tok)
- `test_ha_connect.c` — define _GNU_SOURCE (~1882 tok)
- `test_ha_connection_reuse.c` — define _GNU_SOURCE (~6304 tok)
- `test_ha_ctrlhello.c` (~1322 tok)
- `test_ha_durability.c` — Declares char (~4075 tok)
- `test_ha_election.c` — Declares char (~11144 tok)
- `test_ha_evaluate_committing.c` — Declares checks (~4401 tok)
- `test_ha_failover.c` — define _GNU_SOURCE (~3781 tok)
- `test_ha_liveness_sigstop.sh` — test_ha_liveness_sigstop.sh — Task 4.5 integration test: missed-heartbeat (~2606 tok)
- `test_ha_liveness.c` — Declares directly (~6006 tok)
- `test_ha_rdhup_integration.c` — Declares char (~4347 tok)
- `test_ha_redirect.c` — include <stdatomic.h> (~905 tok)
- `test_ha_repl_ack.c` (~3093 tok)
- `test_ha_repl_push.c` — Declares is (~2818 tok)
- `test_ha_role.c` — include <signal.h> (~1962 tok)
- `test_ha_start_election_async.c` — Declares returns (~1957 tok)
- `test_ha_startup.c` — Declares char (~8594 tok)
- `test_ha_vote_gate.c` — define _GNU_SOURCE (~2679 tok)
- `test_ha_vote_send.c` — define _GNU_SOURCE (~5635 tok)
- `test_ha_vote_send.c` — define _GNU_SOURCE (~3212 tok)
- `test_hooks.c` — include <stdio.h> (~365 tok)
- `test_p1_integration.c` — include <stdatomic.h> (~1496 tok)
- `test_p2_integration.c` — include <errno.h> (~2096 tok)
- `test_p3_hardening.c` — define _GNU_SOURCE (~5279 tok)
- `test_p3_manual_failover.c` — include <stdatomic.h> (~984 tok)
- `test_persist_checkpoint.c` — define _GNU_SOURCE (~1815 tok)
- `test_persist_hooks.c` — define _GNU_SOURCE (~4222 tok)
- `test_persist_recovery.c` — define _GNU_SOURCE (~2290 tok)
- `test_persist_repl.c` — include <errno.h> (~1396 tok)
- `test_persist_schema.c` — include <stdint.h> (~1190 tok)
- `test_persist_truncate.c` — define _GNU_SOURCE (~1973 tok)
- `test_persist_types.c` — include <stddef.h> (~697 tok)
- `test_persist_wal.c` — define _GNU_SOURCE (~1708 tok)
- `test_repl_apply.c` — include <stdio.h> (~1862 tok)
- `test_repl_catchup.c` — include <errno.h> (~1183 tok)
- `test_repl_majority.c` — include <errno.h> (~2108 tok)
- `test_repl_unit.c` — include <assert.h> (~1662 tok)
- `test_worker_join.c` — include <rtti.h> (~4851 tok)
- `test.c` — include <argp.h> (~18744 tok)
## src/docs/
- `apiserver-backend-enhancement-plan.md` — API Server Backend Enhancement Plan (~18355 tok)
- `apiserver-backend-gaps.md` — API Server Backend Gap Analysis (~3048 tok)
- `apiserver-backend-integration-test-plan.md` — Integration Test Plan — P0 + P1 Features (~5693 tok)
- `apiserver-backend-reviews.md` — Final Cross-Perspective Review — API Server Backend Enhancement Plan (~6046 tok)
- `apiserver-backend-tasks.md` — API Server Backend Enhancement — Task Tracker (~2318 tok)
- `config-replication-design.md` — XCE Controller: Configuration Replication Design (~31261 tok)
- `config-replication-gap-analysis.md` — Config Replication — Gap Analysis: Design vs Implementation (~2674 tok)
- `config-replication-impl-status.md` — Config Replication — Implementation Status (~2258 tok)
- `FUTURE_WORK.md` — XCE Controller: Future Work Items (~1087 tok)
- `p1-2-host-select.md` — P1-2: Host Resource Requirement Select — Investigation (~848 tok)
- `persistence-and-ha-design.md` — XCE Controller: State Persistence & High Availability Design (~98184 tok)
- `phase6-audit.md` — Phase 6 Pre-Audit: Single-Threaded Invariant and Stub Locations (~263 tok)
- `phase6-scheduler-integration-plan.md` — Phase 6 — Scheduler Integration Implementation Plan (~10358 tok)
- `resreq-ast-cse-design.md` — XCE Resource Requirement AST & CSE Design (~47536 tok)
- `resreq-ast-cse-impl-plan.md` — Resreq AST + CSE Implementation Plan (~5843 tok)
- `xce.md` — controller (~192 tok)
## src/frontend/
- `CLAUDE.md` — XCE Frontend (API Server + CLI) (~790 tok)
- `Makefile` — Make build targets (~408 tok)
- `meson.build` (~10 tok)
## src/frontend/apiserver/
- `go.mod` — Go module definition (~844 tok)
- `go.sum` — Go dependency checksums (~5760 tok)
- `meson.build` — frontend/apiserver — Go REST API server with CGO backend integration. (~563 tok)
- `README.md` — Project documentation (~42 tok)
## src/frontend/apiserver/backend/
- `clib.go` — go:build cgo (~3465 tok)
- `config.go` — BackendConfig holds connection parameters for the XCE controller. (~200 tok)
- `control_host_test.go` — go:build !cgo (~211 tok)
- `convert_pure.go` — rawStartInfo (93 fields) (~3096 tok)
- `convert_test.go` — TestRawJobToModel_BasicFields, TestRawJobToModel_NilSubmit, TestRawJobToModel_Timestamps, TestRawWorkerToModel_BasicFields + 14 more (~6770 tok)
- `convert.go` — go:build cgo (~3126 tok)
- `handler_test.go` — TestOnHostConfigUpdated_UnregisteredHostIgnored, TestOnHostConfigUpdated_DefaultPseudoHostIgnored, TestOnHostConfigUpdated_PartialMerge, TestOnClus... (~2994 tok)
- `handler.go` — DefaultHandler (8 fields); methods: OnJobCreated, OnJobUpdated, OnJobDeleted, OnJobFinished (~1054 tok)
- `heartbeat_test.go` — TestSetHeartbeatInterval_ValidValue, TestSetHeartbeatInterval_ZeroIgnored, TestSetHeartbeatInterval_NegativeIgnored, TestRunHeartbeat_CallsFunction... (~593 tok)
- `heartbeat.go` — SetHeartbeatInterval (~289 tok)
- `integration_test.go` — go:build !cgo (~4808 tok)
- `notification.go` — NotificationHandler defines callbacks for backend push events. (~241 tok)
- `peers_test.go` — TestParsePeer (~525 tok)
- `peers.go` (~180 tok)
- `stub_funcs_test.go` — go:build !cgo (~513 tok)
- `stub.go` — go:build !cgo (~455 tok)
- `val_helpers.h` — ifndef VAL_HELPERS_H (~260 tok)
## src/frontend/apiserver/backend/auth/
- `config_file_test.go` — TestConfigFileProvider_Authenticate, TestConfigFileProvider_GetUserRoles, TestConfigFileProvider_Name, TestNewConfigFileProvider_Errors + 3 more (~935 tok)
- `config_file.go` — AuthFileConfig (21 fields); methods: Authenticate, GetUserRoles, Name, AllUsers (~650 tok)
- `init.go` — InitAuthProvider (~280 tok)
- `ldap.go` — LDAPProvider is a stub for future LDAP authentication. (~156 tok)
- `oauth2.go` — OAuth2Provider is a stub for future OAuth2/OIDC authentication. (~162 tok)
- `pam_linux.go` — go:build linux && cgo (~973 tok)
- `pam_stub.go` — go:build !linux || !cgo (~100 tok)
- `pam.go` — PAMProvider authenticates users via the system PAM stack. (~106 tok)
- `provider.go` — UserRoleSeeder is an optional interface for providers that can enumerate all users. (~126 tok)
## src/frontend/apiserver/cmd/
- `main.go` (~973 tok)
## src/frontend/apiserver/common/
- `common.go` (~798 tok)
## src/frontend/apiserver/config/
- `app.yaml` (~519 tok)
- `auth.yaml` — Authentication provider: config_file | pam | ldap | oauth2 (~183 tok)
- `auth.yaml.example` — Authentication provider: config_file | pam | ldap | oauth2 (~151 tok)
- `logger.yaml` (~198 tok)
- `nginx.http.conf` — Declares application (~495 tok)
- `nginx.https.conf` — Declares application (~564 tok)
- `rbac_template.yaml` (~1339 tok)
- `rbac.yaml` (~1087 tok)
## src/frontend/apiserver/config/schema/
- `app_schema.json` (~1044 tok)
- `logger_schema.json` (~905 tok)
## src/frontend/apiserver/config/ssl/
- `openssl.cnf` (~99 tok)
- `server.crt` — ---BEGIN CERTIFICATE----- (~330 tok)
- `server.key` — ---BEGIN PRIVATE KEY----- (~455 tok)
## src/frontend/apiserver/controllers/
- `apikey.go` — APIKeyController (9 fields); methods: GenerateAPIKey (~382 tok)
- `auth.go` — AuthController (43 fields); methods: Logon, Logout (~1898 tok)
- `clusters.go` — ClusterController (31 fields); methods: GetClustersInfo (~1995 tok)
- `files.go` — FilesController (48 fields); methods: UploadFiles, DeleteFiles, GetFileLists (~4864 tok)
- `host_control_test.go` — TestPreCheckHostControlRequest_AllFlag, TestPreCheckHostControlRequest_NotAll, TestPreCheckHostControlRequest_NoRace, TestPreCheckHostControlReques... (~974 tok)
- `hosts.go` — HostHandler (54 fields); methods: GetHostsInfo, GetHostByHostname, FilterHostByResReqSelect, ControlHost (~5390 tok)
- `integration_test.go` — go:build !cgo (~5676 tok)
- `jobs_test.go` — TestPreCheckJobRequest_LostAndFound, TestPreCheckJobRequest_NormalQueue, TestPreCheckJobRequest_EmptyQueue (~640 tok)
- `jobs.go` — JobController (49 fields); methods: GetJobsInfo (~32939 tok)
- `permissions.go` — PermController (2 fields); methods: GetPermsInfo (~167 tok)
- `queue_control_test.go` — TestPreCheckQueueControlRequest_AllFlag, TestPreCheckQueueControlRequest_NotAll, TestPreCheckQueueControlRequest_NoRace, TestPreCheckQueueControlRe... (~984 tok)
- `queue_filter_test.go` — TestQueueFilter_AddedOnce, TestQueueFilter_NotAddedWhenUserSpecified, TestQueueFilter_NoQueues, TestQueueFilter_SingleFilter (~601 tok)
- `queues.go` — QueueController (51 fields); methods: GetQueuesInfo, GetQueueByName (~5525 tok)
- `rbac_check_test.go` — TestLoadRBACConfig_RolesPopulated, TestLoadRBACConfig_UserRoleMappings, TestLoadRBACConfig_RoleIncludes, TestLoadRBACConfig_EmptyRoles + 5 more (~2482 tok)
- `rbac_check.go` — RBACConfig (95 fields) (~3448 tok)
- `rbac_convention_test.go` — TestGetRequireResourcePermission_GoConvention, TestGetRequireResourcePermission_FoundPermission, TestDomainTypeConstants, TestGetAccessQueues_AllDo... (~945 tok)
- `resources.go` — ResourceController (59 fields); methods: GetResourcesDistributionInfo, GetResourcesDefinitionInfo (~3386 tok)
- `response.go` — JsonStruct (13 fields) (~610 tok)
- `roles.go` — RoleController (59 fields); methods: GetRolesInfo, GetRoleByRolename (~6979 tok)
- `users.go` — UserController (57 fields); methods: GetUserPermInfo, GetCurrentUserInfo, GetUserRolesInfo, DeleteRolesForUser (~4662 tok)
## src/frontend/apiserver/docs/
- `docs.go` — Code generated by swaggo/swag. DO NOT EDIT (~2852 tok)
- `swagger.json` (~2652 tok)
- `swagger.yaml` — Declares required (~1177 tok)
## src/frontend/apiserver/middleware/
- `cors_test.go` — TestCORSMiddleware_SetsHeaders, TestCORSMiddleware_Preflight, TestCORSMiddleware_Disabled (~458 tok)
- `cors.go` — CORSMiddleware, CORSOriginsFromConfig (~405 tok)
- `load_test.go` — AuthResponse (28 fields) (~728 tok)
- `rate_limit.go` — RateLimiter (22 fields); methods: Limit (~1044 tok)
- `README` (~7 tok)
- `startserver.go` — StartServer, GetIPAddresses (~813 tok)
- `test_rate_limit.bat` (~59 tok)
- `validatehandler.go` — ValidateHandler (~1013 tok)
- `whitelist_test.go` — TestPathMatchesPattern (~277 tok)
- `whitelist.go` — WhitelistURLMiddleware, WhitelistIPMiddleware (~1369 tok)
## src/frontend/apiserver/models/
- `apikey.go` — APIKeyController (10 fields) (~129 tok)
- `auth.go` — LogonRequest (19 fields) (~224 tok)
- `clusters_test.go` — TestClusterInfo_NewFields, TestClusterInfo_ExistingFieldsUnchanged (~1008 tok)
- `clusters.go` — ClusterInfo (18 fields) (~345 tok)
- `common.go` — ActionControl (6 fields) (~107 tok)
- `files.go` — FileUploadRequest (3 fields) (~42 tok)
- `hosts_test.go` — TestHostStateStr (~424 tok)
- `hosts.go` — HostInfo (39 fields) (~996 tok)
- `jobs_test.go` — TestJobStateStr, TestJobStatMapValues (~623 tok)
- `jobs.go` — JobArrayID (124 fields); methods: IsZero, MarshalJSON, Tm, String (~7102 tok)
- `queues.go` — QueueInfo (21 fields) (~523 tok)
- `resources.go` — ResourceInfo (45 fields); methods: String, String, ToJSON (~801 tok)
- `roles.go` — RoleFields (21 fields) (~652 tok)
- `users.go` — AddRoleForUserRequest (12 fields) (~178 tok)
## src/frontend/apiserver/router/
- `routers.go` — Router (~1891 tok)
## src/frontend/apiserver/services/
- `data_service.go` — QueryData, InsertEntity, DeleteEntity (~1002 tok)
- `job_service.go` — SubmitJob (~73 tok)
- `rbac_service.go` — GetUserPermissions (~225 tok)
## src/frontend/apiserver/store/
- `audit_test.go` — TestNoDanglingReferences, TestNoDBImportsInProductionCode (~1182 tok)
- `concurrency_test.go` — TestIntegration_ConcurrentConfigPushes, TestIntegration_ConcurrentHostControlAndConfigPush, TestIntegration_ConcurrentResourceValuePushes (~832 tok)
- `convert.go` — StructToOrderMap, StructsToOrderMaps, StoreToOrderMaps (~344 tok)
- `query.go` — ExecuteQuery, MatchesCondition, MatchesBitmaskCondition, IsBitmaskField + 4 more (~2568 tok)
- `store_test.go` — TestDataStore_BasicOperations, TestDataStore_ListKeysCount, TestDataStore_Filter, TestDataStore_Range, TestDataStore_ConcurrentAccess (~731 tok)
- `store.go` — DataStore is a generic thread-safe in-memory key-value store. (~572 tok)
- `stores.go` — Global store instances. (~1277 tok)
## src/frontend/apiserver/tools/encrypt/
- `crypter.go` — Encrypt, Decrypt (~792 tok)
## src/frontend/apiserver/utils/
- `cgo_stub.go` — This file is reserved for future CGo bindings to the backend C library. (~70 tok)
- `config_test.go` — TestResolveJWTSecret_EnvOverridesConfig, TestResolveJWTSecret_FallsBackToConfig, TestResolveJWTSecret_EmptyEnvUsesConfig (~224 tok)
- `config.go` — BackendConfig (79 fields) (~2434 tok)
- `crypto.go` — Encrypt, Decrypt (~425 tok)
- `logger.go` — Config (50 fields) (~1608 tok)
- `types.go` — Value (140 fields) (~4160 tok)
- `utils_test.go` — TestIntOrInt64 (~256 tok)
- `utils.go` — FilterCondition (142 fields) (~4853 tok)
## src/frontend/docs/
- `analysis.md` — API Server & Client Analysis (~2870 tok)
- `improvement_suggestions.md` — Code Improvement Suggestions (~1408 tok)
- `improvement_tasks.md` — Code Improvement Tasks (~370 tok)
- `integration_test.md` — Integration Test Report: xcecli ↔ apiserver (~3767 tok)
- `plan_t9_f1_f4.md` — Implementation Plan: T9, F1, F4 (~1039 tok)
- `status.md` — Project Status (~1169 tok)
## src/frontend/tests/regression/
- `run.sh` — Regression test suite: xcecli ↔ apiserver (~1787 tok)
## src/frontend/xcecli/
- `go.mod` — Go module definition (~316 tok)
- `go.sum` — Go dependency checksums (~1783 tok)
- `meson.build` — frontend/xcecli — Go CLI tool (~145 tok)
## src/frontend/xcecli/cmd/
- `main.go` (~561 tok)
## src/frontend/xcecli/cmd/auth/
- `list.go` — NewListCmd (~310 tok)
- `logon.go` — NewLogonCmd (~1692 tok)
- `logout_test.go` — TestRunLogout_WithURL, TestRunLogout_DefaultsToActiveServer, TestRunLogout_NotFound (~435 tok)
- `logout.go` — NewLogoutCmd (~512 tok)
## src/frontend/xcecli/cmd/auth/cert/
- `cert.go` — GeneratorCert, GetCertPath (~639 tok)
- `parser.go` (~221 tok)
## src/frontend/xcecli/cmd/clusters/
- `list.go` — NewClustersCmd (~558 tok)
## src/frontend/xcecli/cmd/hosts/
- `controller.go` — HostController (41 fields); methods: RunHostsControl, ParseCmdArgs (~1382 tok)
- `hosts.go` — NewHostsCmd (~233 tok)
- `list.go` — HostsQuerier (43 fields); methods: ParseCmdArgs, RunHostsList (~1627 tok)
- `select_test.go` — TestParseSelectArgs_RequiresResReq, TestParseSelectArgs_MaxLength, TestParseSelectArgs_Valid (~172 tok)
- `select.go` — HostSelector (24 fields) (~654 tok)
## src/frontend/xcecli/cmd/jobs/
- `controller.go` — JobController (75 fields); methods: RunJobsControl, ParseCmdArgs (~2610 tok)
- `files_test.go` — TestParseFileTransfer_ExplicitOperator, TestParseFileTransfer_PlainPath, TestParseFileTransfer_PlainFilename (~318 tok)
- `files.go` — fileTransfer (28 fields) (~1050 tok)
- `jobs.go` — NewJobsCmd (~307 tok)
- `list.go` — JobsQuerier (99 fields); methods: ParseCmdArgs, RunJobsList (~3826 tok)
- `restart.go` — JobRestart (18 fields); methods: ParseCmdArgs, RunJobsRestart (~570 tok)
- `submit.go` — JobSubmitter (135 fields); methods: ParseCmdArgs, BuildJobRequest, RunJobsSubmit (~6928 tok)
## src/frontend/xcecli/cmd/permissions/
- `list.go` — PermsQuerier (29 fields); methods: ParseCmdArgs, RunPermsList (~954 tok)
- `permissions.go` — NewPermsCmd (~168 tok)
## src/frontend/xcecli/cmd/queues/
- `controller.go` — QueueController (58 fields); methods: RunQueuesControl, ParseCmdArgs (~1933 tok)
- `list.go` — QueuesQuerier (45 fields); methods: ParseCmdArgs, RunQueuesList (~1570 tok)
- `queues.go` — NewQueuesCmd (~247 tok)
## src/frontend/xcecli/cmd/resources/
- `list.go` — ResourcesDistQuerier (79 fields); methods: ParseCmdArgs, ParseCmdArgs, RunResourcesDefList, RunResourcesDistList (~2860 tok)
- `resources.go` — NewResourcesCmd (~478 tok)
## src/frontend/xcecli/cmd/roles/
- `addrole.go` — RoleSubmitter (72 fields); methods: ParseCmdArgs, RunRolesAdd, RunPermsAdd, BuildRoleRequest (~2311 tok)
- `deleterole.go` — DeleteRole (20 fields); methods: ParseCmdArgs, RunDeleteRoleInfo (~642 tok)
- `list_test.go` — TestUniqueRoleNames_NoDuplicates, TestUniqueRoleNames_WithDuplicates, TestUniqueRoleNames_Empty, TestUniqueRoleNames_CrossPage, TestUserRoleNameCol... (~673 tok)
- `list.go` — RolesQuerier (53 fields); methods: ParseCmdArgs, RunRolesInfo (~1767 tok)
- `roles.go` — NewRolesCmd (~234 tok)
- `view.go` — NewViewRolesCmd (~434 tok)
## src/frontend/xcecli/cmd/users/
- `assignrole.go` — UserSubmitter (28 fields); methods: ParseCmdArgs, RunUserAssignRole, BuildUserRequest (~941 tok)
- `list_test.go` — TestUniqueUserNames_NoDuplicates, TestUniqueUserNames_WithDuplicates, TestUniqueUserNames_Empty, TestUniqueUserNames_CrossPage, TestUserRoleNameCol... (~668 tok)
- `list.go` — UserRolesQuerier (62 fields); methods: ParseCmdArgs, RunCurrentUserInfo, RunUserRolesInfo (~2090 tok)
- `perm4user.go` — Perms4User (35 fields); methods: ParseCmdArgs, RunUserPermsInfo (~1149 tok)
- `unassignrole.go` — UnassignUser (25 fields); methods: ParseCmdArgs, RunDeleteUserRoleInfo (~780 tok)
- `users.go` — NewUsersCmd (~305 tok)
- `view.go` — NewViewUserRolesCmd (~435 tok)
## src/frontend/xcecli/common/format/
- `jobid_test.go` — TestFormatJobIDRegular, TestFormatJobIDArray, TestParseJobIDRegular, TestParseJobIDArray + 5 more (~832 tok)
- `jobid.go` — FormatJobID, IsArrayJob, DecomposeJobID, MakeJobID + 4 more (~1758 tok)
## src/frontend/xcecli/common/output/
- `output_clusters.go` — Output (9 fields); methods: SetVerbose, SetNoHeader, SetWideFormat, ClusterPrintData (~317 tok)
- `page_print_array_elements_jobs.go` — ArrayElementsPagePrinter (44 fields); methods: PrepareForPrinting, PrintPage (~1818 tok)
- `page_print_array_jobs.go` — ArrayJobsPagePrinter (42 fields); methods: PrepareForPrinting, PrintPage (~1735 tok)
- `page_print_hosts_test.go` — TestFormatRows_UnavailShowsDashes, TestFormatRows_AvailableShowsValues (~670 tok)
- `page_print_hosts.go` — HostsPagePrinter (75 fields); methods: PrepareForPrinting, PrintPage (~2819 tok)
- `page_print_jobs.go` — JobsPagePrinter (89 fields); methods: PrepareForPrinting, PrintPage (~4288 tok)
- `page_print_queues.go` — QueuesPagePrinter (59 fields); methods: PrepareForPrinting, PrintPage (~2208 tok)
- `page_print_resources.go` — ResourcesPagePrinter (87 fields); methods: PrepareForPrinting, PrintPage (~3232 tok)
- `page_print_roles.go` — RolesPagePrinter (34 fields); methods: PrepareForPrinting, PrintPage (~1440 tok)
- `page_print_users.go` — UserRolesPagePrinter (26 fields); methods: PrepareForPrinting, PrintPage (~1038 tok)
- `page_print.go` — Interface: PagePrinter (12 methods) (~2439 tok)
- `time_test.go` — TestParseJobTime_RFC3339, TestParseJobTime_SpaceFormat, TestParseJobTime_Empty, TestFormatJobTime (~244 tok)
- `time.go` (~334 tok)
## src/frontend/xcecli/common/request/
- `request_test.go` — TestSetRootCAs_HTTPNoOp verifies HTTP clients are not affected. (~429 tok)
- `request.go` — APIResponse (97 fields); methods: SetRootCAs, Logon, GetRequestFromAPIServer, SubmitJob (~2977 tok)
## src/frontend/xcecli/common/utils/
- `utils.go` — SystemLimits (57 fields); methods: GetCPULimit, GetFileLimit, GetDataLimit, GetStackLimit (~2057 tok)
## src/frontend/xcecli/config/
- `config.go` — APIServerInfo (64 fields); methods: Len, Swap, Less, GetConfig (~1607 tok)
- `logger.go` — LoggerConfig (48 fields) (~1877 tok)
- `logger.yaml` (~205 tok)
- `schema.go` — ValidateYamlFormat, ValidateYAML (~753 tok)
## src/frontend/xcecli/config/schema/
- `logger_schema.json` (~930 tok)
## src/frontend/xcecli/filters/
- `host_filter.go` — HostsFilter (7 fields); methods: HandleHostFilter (~166 tok)
- `job_filter.go` — JobsFilter (61 fields); methods: HandleJobFilter (~1511 tok)
- `queue_filter.go` — QueuesFilter (7 fields); methods: HandleQueueFilter (~119 tok)
- `resource_filter.go` — ResourceDistFilter (13 fields); methods: HandleResourceDistFilter, HandleResourceDefFilter (~271 tok)
- `role_filter.go` — RolesFilter (9 fields); methods: HandleRoleFilter (~149 tok)
- `user_filter.go` — UserRolesFilter (8 fields); methods: HandleUserRoleFilter (~149 tok)
## src/frontend/xcecli/models/
- `cluster.go` — ClusterResponse (15 fields) (~201 tok)
- `exitstatus_test.go` — TestQueryJob_ExitStatus_Null, TestQueryJob_ExitStatus_String, TestQueryJob_ExitStatus_Zero (~292 tok)
- `hosts.go` — HostsResponse (56 fields); methods: UnmarshalJSON (~1288 tok)
- `jobs.go` — JobsResponse (149 fields); methods: UnmarshalJSON, String, UnmarshalJSON (~5415 tok)
- `logon.go` — LogonData define the data structure in the logon response (~168 tok)
- `ordermap.go` — UnmarshalOrderMapRow extracts the Data sub-object from the server's OrderMap wire format (~166 tok)
- `permissions.go` — PermsResponse (6 fields) (~98 tok)
- `queues.go` — QueuesResponse (33 fields); methods: UnmarshalJSON (~1072 tok)
- `resource.go` — ResourceValue (66 fields); methods: UnmarshalJSON, String, UnmarshalJSON, String (~1674 tok)
- `response_test.go` — TestHostsResponse_FullUnmarshal, TestQueuesResponse_FullUnmarshal, TestJobsResponse_FullUnmarshal, TestRolesResponse_FullUnmarshal + 3 more (~1644 tok)
- `roles.go` — CreateRoleRequest (25 fields); methods: UnmarshalJSON (~361 tok)
- `users.go` — AddRoleForUserRequest (26 fields); methods: UnmarshalJSON (~343 tok)
## src/frontend/xcecli/router/
- `router.go` — Execute (~919 tok)
## src/frontend/xcecli/test/
- `t.sh` (~118 tok)
## src/include/
- `attrs.h` — if !defined(_ATTRS_H_) (~26 tok)
- `bitset.h` — if !defined(_BITSET_H_) (~71 tok)
- `byteorder.h` — pragma once (~247 tok)
- `client.h` — if !defined(_CLIENT_H_) (~1023 tok)
- `clusterconfig.h` — if !defined(_CLUSTERCONFIG_H_) (~474 tok)
- `config.h` — if !defined(_CONFIG_H_) (~993 tok)
- `configobj.h` — if !defined(_CONFIGOBJ_H_) (~46 tok)
- `ctrl_metrics.h` — pragma once (~539 tok)
- `dataq.h` — if !defined(_DATAQ_H_) (~190 tok)
- `delay.h` — public delay API; adds delay_task_ms() and nextrun_ts field to delay_task_t (~320 tok)
- `group.h` — if !defined(_GROUP_H_) (~227 tok)
- `helper.h` — if !defined(_HELPER_H_) (~207 tok)
- `hook.h` — if !defined(__HOOK_H__) (~195 tok)
- `htab.h` — if !defined(_HTAB_H_) (~268 tok)
- `idgen.h` — if !defined(_IDGEN_H_) (~117 tok)
- `jobj.h` — if !defined(_JOBJ_H_) (~103 tok)
- `kv.h` — if !defined(_KV_H_) (~407 tok)
- `kvmgr.h` — if !defined(__KVMGR_H__) (~306 tok)
- `misc.h` — if !defined(_MISC_H_) (~88 tok)
- `myconf.h` — if !defined(_MYCONF_H_) (~458 tok)
- `mylock.h` — if !defined(_MYLOCK_H_) (~281 tok)
- `netio.h` — if !defined(_NETIO_H_) (~1293 tok)
- `objs.h` — if !defined(_OBJS_H_) (~36 tok)
- `ordq.h` — if !defined(_ORDQ_H_) (~266 tok)
- `persist.h` — pragma once (~1837 tok)
- `preason.h` — if !defined(_PREASON_H_) (~108 tok)
- `qobj.h` — if !defined(_QOBJ_H_) (~41 tok)
- `repl.h` — pragma once (~979 tok)
- `request.h` — if !defined(_REQUEST_H_) (~2108 tok)
- `resobj.h` — if !defined(_RESOBH_H_) (~46 tok)
- `resreq.h` — if !defined(_RESREQ_H_) (~3223 tok)
- `typemap.h` — if !defined(_TYPEMAP_H_) (~3378 tok)
- `utils.h` — if !defined(_UTILS_H_) (~1574 tok)
- `vtaskmgr.h` — if !defined(_VTASKMGR_H_) (~442 tok)
- `wal_types.h` — pragma once (~679 tok)
- `wobj.h` — if !defined(_WOBBJ_H_) (~42 tok)
- `xcealloc.h` — if !defined(_XCEALLOC_H_) (~146 tok)
- `xceapi.h` — if !defined(_XCEAPI_H_) (~436 tok)
- `xcectx.h` — if !defined(_XCECTX_H_) (~388 tok)
- `xcelimit.h` — if !defined(_XCELIMIT_H_) (~281 tok)
- `xcelog.h` — ifndef _XCELOG_H_ (~819 tok)
- `xlist.h` — if !defined(__XLIST_H__) (~193 tok)
- `xstring.h` — if !defined(__XSTRING_H__) (~54 tok)
- `xvec.h` — if !defined(_XVEC_H_) (~293 tok)
- `zarray.h` — if !defined(_ZARRAY_H_) (~384 tok)
## src/include/xce/
- `cluster_def.h` — ifndef XCE_CLUSTER_DEF_H (~1178 tok)
- `cluster.h` — A single controller candidate. (~1211 tok)
- `errnox.h` — if !defined(__XCE_ERRNOX_H__) (~262 tok)
- `types.h` — < NULL. (~5454 tok)
## src/lib/
- `delay.c` — include <errno.h> (~1619 tok)
- `helper.c` — include <errno.h> (~2956 tok)
- `meson.build` (~1513 tok)
- `netio.c` — include <arpa/inet.h> (~5716 tok)
- `request.c` — include <errno.h> (~12892 tok)
## src/lib/raft/
- `raft_sim.c` — ifdef XCE_TESTING (~1627 tok)
- `raft.c` — include "raft.h" (~2452 tok)
- `raft.h` — ifndef RAFT_H (~3049 tok)
## src/lib/testsuite/
- `test_delay.c` — delay unit tests; adds test_delay_task_ms_fires_within_tolerance and test_delay_task_ms_invalid_args (~1600 tok)
- `test_libxce_retry.c` — define _GNU_SOURCE (~2741 tok)
- `test_netio_on_connected.c` — define _GNU_SOURCE (~1431 tok)
- `test_raft.c` — include <errno.h> (~25610 tok)
- `test.c` — include <argp.h> (~971 tok)
## src/scheduler/testsuite/
- `test.c` — define UNIT_TEST 1 (~4290 tok)
## src/schema/
- `controller.capnp` — Declares OPCode (~7861 tok)
## src/tools/
- `deploy-ha.sh` (~505 tok)
- `test-heartbeat-tolerance.sh` — test-heartbeat-tolerance.sh — Task 4.5: missed-heartbeat tolerance via SIGSTOP/SIGCONT (~2377 tok)
## src/xce.build/
- `build.ninja` — This is the build file for project "xce" (~75089 tok)
## src/xce.build/lib/
- `controller.capnp.c` — include "controller.capnp.h" (~134780 tok)

4474
.wolf/buglog.json Normal file

File diff suppressed because it is too large Load diff

41
.wolf/cerebrum.md Normal file
View file

@ -0,0 +1,41 @@
# Cerebrum
> OpenWolf's learning memory. Updated automatically as the AI learns from interactions.
> Do not edit manually unless correcting an error.
> Last updated: 2026-05-24
## User Preferences
<!-- How the user likes things done. Code style, tools, patterns, communication. -->
## Key Learnings
- Use `clang-format` for C/C++ formatting; shared config lives at `xce/.clang-format`.
- `msg_handler_t` in netio.h is `int (*)(void*)` — NOT `int (*)(endpoint_t*)`. Functions assigned to `ep->msg_handler` must use `void *ep` parameter. (Task 2.2, 2026-05-30)
- `xce_request()` is defined in request.c but not declared in request.h; files that need it must add `extern int xce_request(endpoint_t *ep, xce_request_t *req);` locally (pattern from config_repl.c).
- Test seam pattern for ha.c XCE_TESTING: declare `int (*hook)(ep, req) = NULL` under `#ifdef XCE_TESTING`, expose as extern in test file, test sets it before calling function under test. Mirrors `xce_retry_send_hook` in request.c.
- capnp UAF fix pattern (Task 2.3): INVOKE_MSG_HANDLER fires ep->msg_handler(ep) directly from iomgr thread; packet is in ep->fsm_in.pkt. Do raw capn_init_mem decode, read_RequestVoteReply into local struct BEFORE capn_free, then capn_free + free_packet + free(pkt). Use local scalars (not capnp pointers) after free. (2026-05-30)
- capnp serialization for tests: use new_RequestVoteReply(seg) / new_RequestReply(seg) / new_Request(seg) — NOT raw capn_new_struct. Follow pattern from xce_encode / xce_replconfigpush_req_raw in request.c.
- To avoid C99 "goto jumps over declaration" error: declare all local vars at top of function (or in a sub-block), not after the first goto target label.
- hook seam forward-declaration order: declare the hook variable BEFORE the function that references it (even within the same XCE_TESTING block). Use a dispatch helper static function to decouple seam from production path.
- term fencing must live in the handler, not the dispatch path (2026-05-31): vote_reply_msg_handler delegates to vote_reply_dispatch which calls either xce_vote_reply_hook (XCE_TESTING) or raft_on_vote_reply (production). If term fencing only happens inside raft_cb_become_follower (production), it is silently skipped in the test hook path. Pattern: do explicit CAS loop (atomic_compare_exchange_weak) on ha->term before calling vote_reply_dispatch, so the update applies regardless of which dispatch path runs.
- ha_start_election_async ownership rule (Task 2.4, 2026-05-30): in the production path (c != NULL), ha_elect_ctx_t is owned by election_timeout_fn which frees it on fire. In the test-mode path (c == NULL, peers == NULL), no delaymgr task owns ctx — must free ctx inline after ha_elect_evaluate_and_signal fires. ASAN will catch the leak if forgotten.
- ha_start_election_async single-node shortcircuit (Task 2.4): n_peers==1 means n_other=0 — allocate ha_elect_ctx_t with 0 flexible entries (no votes[] slots needed), set votes_granted=1, call evaluate directly, then free ctx immediately. No iomgr or delaymgr involvement for single-node.
- election_timeout_fn must lock ctx->lock before calling ha_elect_evaluate_and_signal (Task 2.4): the evaluate function has a done_posted guard but its callers in the reply path hold ctx->lock; timeout fn must also hold the lock to avoid races with concurrent reply processing.
- single-node shortcircuit enters CTRL_PRIMARY_COMMITTING, NOT CTRL_PRIMARY (Task 2.4 review fix): task 4.4 committing timeout promotes to PRIMARY for degraded/single-node mode (Req 8.4). Tests must assert CTRL_PRIMARY_COMMITTING.
- ha_elect_evaluate_and_signal out-of-bounds guard (Task 2.4 review fix): ctx->votes[] is a C99 flexible array with n_peers entries. When n_peers==0 (single-node), votes[0] is out-of-bounds. Must check n_peers==0 FIRST and short-circuit to PRIMARY (quorum=1, self-vote=1 always wins). Guard added before any votes[0] access.
- client_ssl_ctx for outgoing vote connections (Task 2.4 review fix): endpoint_connect() creates SSL from iomgr->ssl_ctx (server context). After connect returns, replace ep->ssl with SSL_new(client_ssl_ctx), SSL_set_fd to the same fd, SSL_set_connect_state. If SSL_new fails, fall through (handshake fails → vote denied via err_handler).
- vote_ep_ctx_t lifetime (Task 2.4 review fix): do NOT free vctx in vote_ep_err_handler. ep->data (vctx) is also used by vote_reply_msg_handler. Cleanup deferred to ha_election_cleanup() (task 5.1). Freeing in err_handler causes UAF.
- timeout_ctx field in ha_elect_ctx_t (Task 2.4 review fix): added void *timeout_ctx field pointing to election_timeout_ctx_t. Set after delay_task_ms succeeds. Allows task 4.1 (quorum-reached path) to cancel the watchdog by setting timeout_ctx->cancelled=1 atomically.
## Do-Not-Repeat
<!-- Mistakes made and corrected. Each entry prevents the same mistake recurring. -->
<!-- Format: [YYYY-MM-DD] Description of what went wrong and what to do instead. -->
## Decision Log
<!-- Significant technical decisions with rationale. Why X was chosen over Y. -->
- Task 4.1 (ha_elect_evaluate_and_signal COMMITTING path): Added cluster_t *c field to ha_elect_ctx_t so evaluate can call cluster_init_phase2 without signature change. election_timeout_ctx_t and committing_timeout_ctx_t must be defined BEFORE ha_elect_evaluate_and_signal. cluster_init_phase2 wrapped in #ifndef XCE_TESTING to avoid linker error (cluster.c not in unit-test binary). ha_phase2_hook seam used in XCE_TESTING builds. On PRIMARY: cancel election timeout watchdog, call phase2 if first_boot, set CTRL_PRIMARY_COMMITTING, reset repl_ack_count=0, arm committing_timeout_fn. On STANDBY: call phase2 if first_boot, set CTRL_STANDBY.

73
.wolf/config.json Normal file
View file

@ -0,0 +1,73 @@
{
"version": 1,
"openwolf": {
"enabled": true,
"anatomy": {
"auto_scan_on_init": true,
"rescan_interval_hours": 6,
"max_description_length": 100,
"max_files": 500,
"exclude_patterns": [
"node_modules",
".git",
"dist",
"build",
".wolf",
".next",
".nuxt",
"coverage",
"__pycache__",
".cache",
"target",
".vscode",
".idea",
".turbo",
".vercel",
".netlify",
".output",
"*.min.js",
"*.min.css"
]
},
"token_audit": {
"enabled": true,
"report_frequency": "weekly",
"waste_threshold_percent": 15,
"chars_per_token_code": 3.5,
"chars_per_token_prose": 4.0
},
"cron": {
"enabled": true,
"max_retry_attempts": 3,
"dead_letter_enabled": true,
"heartbeat_interval_minutes": 30,
"use_claude_p": true,
"api_key_env": null
},
"memory": {
"consolidation_after_days": 7,
"max_entries_before_consolidation": 200
},
"cerebrum": {
"max_tokens": 2000,
"reflection_frequency": "weekly"
},
"daemon": {
"port": 18790,
"log_level": "info"
},
"dashboard": {
"enabled": true,
"port": 18791
},
"designqc": {
"enabled": true,
"viewports": [
{ "name": "desktop", "width": 1440, "height": 900 },
{ "name": "mobile", "width": 375, "height": 812 }
],
"max_screenshots": 6,
"chrome_path": null
}
}
}

97
.wolf/cron-manifest.json Normal file
View file

@ -0,0 +1,97 @@
{
"version": 1,
"tasks": [
{
"id": "anatomy-rescan",
"name": "Full anatomy rescan",
"schedule": "0 */6 * * *",
"description": "Re-scans project filesystem and reconciles anatomy.md",
"action": { "type": "scan_project" },
"retry": {
"max_attempts": 3,
"backoff": "exponential",
"base_delay_seconds": 30
},
"failsafe": {
"on_failure": "log_and_continue",
"alert_after_consecutive_failures": 2,
"dead_letter": true
},
"enabled": true
},
{
"id": "memory-consolidation",
"name": "Consolidate old memory",
"schedule": "0 2 * * *",
"description": "Compress memory.md entries older than 7 days",
"action": {
"type": "consolidate_memory",
"params": { "older_than_days": 7 }
},
"retry": {
"max_attempts": 2,
"backoff": "exponential",
"base_delay_seconds": 60
},
"failsafe": {
"on_failure": "skip_and_retry_next_cycle",
"dead_letter": false
},
"enabled": true
},
{
"id": "token-audit",
"name": "Token audit report",
"schedule": "0 0 * * 1",
"description": "Weekly waste pattern detection",
"action": { "type": "generate_token_report" },
"retry": {
"max_attempts": 2,
"backoff": "linear",
"base_delay_seconds": 60
},
"failsafe": { "on_failure": "log_and_continue", "dead_letter": true },
"enabled": true
},
{
"id": "cerebrum-reflection",
"name": "Cerebrum reflection",
"schedule": "0 3 * * 0",
"description": "Weekly AI review of cerebrum.md — prune stale entries, consolidate duplicates",
"action": {
"type": "ai_task",
"params": {
"prompt": "Review this cerebrum.md. Remove duplicate preferences (keep newer). Remove Do-Not-Repeat entries older than 90 days if no longer relevant. Consolidate Key Learnings that overlap. Keep the file under 2000 tokens. Return the cleaned file content only.",
"context_files": [".wolf/cerebrum.md"]
}
},
"retry": {
"max_attempts": 1,
"backoff": "none",
"base_delay_seconds": 0
},
"failsafe": { "on_failure": "log_and_continue", "dead_letter": false },
"enabled": true
},
{
"id": "project-suggestions",
"name": "AI suggestions",
"schedule": "0 4 * * 1",
"description": "Weekly AI analysis with project improvement suggestions",
"action": {
"type": "ai_task",
"params": {
"prompt": "Based on the recent memory entries and current project structure, provide: 1) Key achievements this week, 2) Code improvements to consider, 3) Logical next tasks, 4) Technical debt or risks. Be specific and actionable. Return as JSON: {achievements:[], improvements:[], next_tasks:[], risks:[]}",
"context_files": [".wolf/memory.md", ".wolf/anatomy.md"]
}
},
"retry": {
"max_attempts": 1,
"backoff": "none",
"base_delay_seconds": 0
},
"failsafe": { "on_failure": "log_and_continue", "dead_letter": false },
"enabled": true
}
]
}

168
.wolf/cron-state.json Normal file
View file

@ -0,0 +1,168 @@
{
"last_heartbeat": "2026-06-02T10:04:11.396Z",
"engine_status": "running",
"execution_log": [
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-21T07:00:00.623Z",
"duration_ms": 37
},
{
"task_id": "project-suggestions",
"status": "failed",
"timestamp": "2026-05-21T08:54:59.810Z",
"duration_ms": 120927,
"error": "claude -p failed: spawnSync /bin/sh ETIMEDOUT"
},
{
"task_id": "project-suggestions",
"status": "failed",
"timestamp": "2026-05-21T08:59:01.535Z",
"duration_ms": 241724,
"error": "claude -p failed: spawnSync /bin/sh ETIMEDOUT"
},
{
"task_id": "project-suggestions",
"status": "failed",
"timestamp": "2026-05-21T08:59:01.536Z",
"duration_ms": 120843,
"error": "claude -p failed: spawnSync /bin/sh ETIMEDOUT"
},
{
"task_id": "memory-consolidation",
"status": "success",
"timestamp": "2026-05-21T09:00:00.558Z",
"duration_ms": 1
},
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-21T13:00:00.039Z",
"duration_ms": 32
},
{
"task_id": "project-suggestions",
"status": "failed",
"timestamp": "2026-05-21T13:48:25.789Z",
"duration_ms": 120850,
"error": "claude -p failed: spawnSync /bin/sh ETIMEDOUT"
},
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-21T19:00:00.445Z",
"duration_ms": 29
},
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-22T01:00:00.198Z",
"duration_ms": 31
},
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-22T07:00:00.675Z",
"duration_ms": 31
},
{
"task_id": "memory-consolidation",
"status": "success",
"timestamp": "2026-05-22T09:00:00.789Z",
"duration_ms": 3
},
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-22T13:00:00.217Z",
"duration_ms": 29
},
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-22T19:00:00.644Z",
"duration_ms": 25
},
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-23T01:00:00.097Z",
"duration_ms": 20
},
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-23T07:00:00.825Z",
"duration_ms": 24
},
{
"task_id": "memory-consolidation",
"status": "success",
"timestamp": "2026-05-23T09:00:00.492Z",
"duration_ms": 0
},
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-23T13:00:00.641Z",
"duration_ms": 29
},
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-23T19:00:00.942Z",
"duration_ms": 27
},
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-24T01:00:00.488Z",
"duration_ms": 23
},
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-24T07:00:00.783Z",
"duration_ms": 33
},
{
"task_id": "memory-consolidation",
"status": "success",
"timestamp": "2026-05-24T09:00:00.882Z",
"duration_ms": 1
},
{
"task_id": "cerebrum-reflection",
"status": "success",
"timestamp": "2026-05-24T10:01:38.148Z",
"duration_ms": 97165
},
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-24T13:00:00.732Z",
"duration_ms": 26
},
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-24T19:00:00.183Z",
"duration_ms": 25
},
{
"task_id": "anatomy-rescan",
"status": "success",
"timestamp": "2026-05-25T01:00:00.439Z",
"duration_ms": 22
},
{
"task_id": "memory-consolidation",
"status": "success",
"timestamp": "2026-06-02T09:00:00.411Z",
"duration_ms": 3
}
],
"dead_letter_queue": [],
"upcoming": []
}

83
.wolf/daemon.log Normal file
View file

@ -0,0 +1,83 @@
[2026-05-21T04:35:43.991Z] [INFO] Scheduled task: Full anatomy rescan (0 */6 * * *)
[2026-05-21T04:35:43.992Z] [INFO] Scheduled task: Consolidate old memory (0 2 * * *)
[2026-05-21T04:35:43.992Z] [INFO] Scheduled task: Token audit report (0 0 * * 1)
[2026-05-21T04:35:43.992Z] [INFO] Scheduled task: Cerebrum reflection (0 3 * * 0)
[2026-05-21T04:35:43.993Z] [INFO] Scheduled task: AI suggestions (0 4 * * 1)
[2026-05-21T04:35:43.994Z] [INFO] File watcher started on .wolf/
[2026-05-21T04:35:43.994Z] [INFO] OpenWolf daemon started
[2026-05-21T04:35:43.995Z] [INFO] Dashboard server listening on port 18791
[2026-05-21T04:36:03.472Z] [INFO] WebSocket client connected
[2026-05-21T04:38:32.931Z] [INFO] WebSocket client connected
[2026-05-21T04:42:03.306Z] [INFO] WebSocket client connected
[2026-05-21T07:00:00.586Z] [INFO] Executing task: Full anatomy rescan
[2026-05-21T07:00:00.623Z] [INFO] Task Full anatomy rescan completed in 37ms
[2026-05-21T08:52:58.883Z] [INFO] Executing task: AI suggestions
[2026-05-21T08:54:59.810Z] [ERROR] Task AI suggestions failed (attempt 1): claude -p failed: spawnSync /bin/sh ETIMEDOUT
[2026-05-21T08:54:59.811Z] [INFO] Executing task: AI suggestions
[2026-05-21T08:57:00.693Z] [INFO] Executing task: AI suggestions
[2026-05-21T08:59:01.535Z] [ERROR] Task AI suggestions failed (attempt 1): claude -p failed: spawnSync /bin/sh ETIMEDOUT
[2026-05-21T08:59:01.536Z] [ERROR] Task AI suggestions failed (attempt 1): claude -p failed: spawnSync /bin/sh ETIMEDOUT
[2026-05-21T09:00:00.557Z] [INFO] Executing task: Consolidate old memory
[2026-05-21T09:00:00.558Z] [INFO] Task Consolidate old memory completed in 1ms
[2026-05-21T12:40:53.745Z] [INFO] WebSocket client connected
[2026-05-21T13:00:00.007Z] [INFO] Executing task: Full anatomy rescan
[2026-05-21T13:00:00.039Z] [INFO] Task Full anatomy rescan completed in 32ms
[2026-05-21T13:46:24.939Z] [INFO] Executing task: AI suggestions
[2026-05-21T13:48:25.789Z] [ERROR] Task AI suggestions failed (attempt 1): claude -p failed: spawnSync /bin/sh ETIMEDOUT
[2026-05-21T19:00:00.416Z] [INFO] Executing task: Full anatomy rescan
[2026-05-21T19:00:00.445Z] [INFO] Task Full anatomy rescan completed in 29ms
[2026-05-22T01:00:00.167Z] [INFO] Executing task: Full anatomy rescan
[2026-05-22T01:00:00.198Z] [INFO] Task Full anatomy rescan completed in 31ms
[2026-05-22T07:00:00.644Z] [INFO] Executing task: Full anatomy rescan
[2026-05-22T07:00:00.675Z] [INFO] Task Full anatomy rescan completed in 31ms
[2026-05-22T09:00:00.786Z] [INFO] Executing task: Consolidate old memory
[2026-05-22T09:00:00.789Z] [INFO] Task Consolidate old memory completed in 3ms
[2026-05-22T13:00:00.188Z] [INFO] Executing task: Full anatomy rescan
[2026-05-22T13:00:00.217Z] [INFO] Task Full anatomy rescan completed in 29ms
[2026-05-22T19:00:00.619Z] [INFO] Executing task: Full anatomy rescan
[2026-05-22T19:00:00.644Z] [INFO] Task Full anatomy rescan completed in 25ms
[2026-05-23T01:00:00.077Z] [INFO] Executing task: Full anatomy rescan
[2026-05-23T01:00:00.097Z] [INFO] Task Full anatomy rescan completed in 20ms
[2026-05-23T07:00:00.801Z] [INFO] Executing task: Full anatomy rescan
[2026-05-23T07:00:00.826Z] [INFO] Task Full anatomy rescan completed in 24ms
[2026-05-23T09:00:00.492Z] [INFO] Executing task: Consolidate old memory
[2026-05-23T09:00:00.493Z] [INFO] Task Consolidate old memory completed in 0ms
[2026-05-23T13:00:00.613Z] [INFO] Executing task: Full anatomy rescan
[2026-05-23T13:00:00.641Z] [INFO] Task Full anatomy rescan completed in 29ms
[2026-05-23T19:00:00.915Z] [INFO] Executing task: Full anatomy rescan
[2026-05-23T19:00:00.942Z] [INFO] Task Full anatomy rescan completed in 27ms
[2026-05-24T01:00:00.465Z] [INFO] Executing task: Full anatomy rescan
[2026-05-24T01:00:00.488Z] [INFO] Task Full anatomy rescan completed in 23ms
[2026-05-24T07:00:00.750Z] [INFO] Executing task: Full anatomy rescan
[2026-05-24T07:00:00.783Z] [INFO] Task Full anatomy rescan completed in 33ms
[2026-05-24T09:00:00.881Z] [INFO] Executing task: Consolidate old memory
[2026-05-24T09:00:00.882Z] [INFO] Task Consolidate old memory completed in 1ms
[2026-05-24T10:00:00.983Z] [INFO] Executing task: Cerebrum reflection
[2026-05-24T10:01:38.148Z] [INFO] Task Cerebrum reflection completed in 97165ms
[2026-05-24T13:00:00.706Z] [INFO] Executing task: Full anatomy rescan
[2026-05-24T13:00:00.733Z] [INFO] Task Full anatomy rescan completed in 26ms
[2026-05-24T19:00:00.158Z] [INFO] Executing task: Full anatomy rescan
[2026-05-24T19:00:00.183Z] [INFO] Task Full anatomy rescan completed in 25ms
[2026-05-25T01:00:00.417Z] [INFO] Executing task: Full anatomy rescan
[2026-05-25T01:00:00.439Z] [INFO] Task Full anatomy rescan completed in 22ms
[2026-06-02T04:08:51.473Z] [INFO] Scheduled task: Full anatomy rescan (0 */6 * * *)
[2026-06-02T04:08:51.474Z] [INFO] Scheduled task: Consolidate old memory (0 2 * * *)
[2026-06-02T04:08:51.475Z] [INFO] Scheduled task: Token audit report (0 0 * * 1)
[2026-06-02T04:08:51.475Z] [INFO] Scheduled task: Cerebrum reflection (0 3 * * 0)
[2026-06-02T04:08:51.475Z] [INFO] Scheduled task: AI suggestions (0 4 * * 1)
[2026-06-02T04:08:51.476Z] [INFO] File watcher started on .wolf/
[2026-06-02T04:08:51.477Z] [INFO] OpenWolf daemon started
[2026-06-02T04:08:51.477Z] [INFO] Dashboard server listening on port 18791
[2026-06-02T04:09:36.978Z] [INFO] WebSocket client connected
[2026-06-02T04:18:54.844Z] [INFO] WebSocket client connected
[2026-06-02T07:04:11.391Z] [INFO] Scheduled task: Full anatomy rescan (0 */6 * * *)
[2026-06-02T07:04:11.392Z] [INFO] Scheduled task: Consolidate old memory (0 2 * * *)
[2026-06-02T07:04:11.392Z] [INFO] Scheduled task: Token audit report (0 0 * * 1)
[2026-06-02T07:04:11.392Z] [INFO] Scheduled task: Cerebrum reflection (0 3 * * 0)
[2026-06-02T07:04:11.392Z] [INFO] Scheduled task: AI suggestions (0 4 * * 1)
[2026-06-02T07:04:11.393Z] [INFO] File watcher started on .wolf/
[2026-06-02T07:04:11.394Z] [INFO] OpenWolf daemon started
[2026-06-02T07:04:11.394Z] [INFO] Dashboard server listening on port 18791
[2026-06-02T07:58:22.561Z] [INFO] WebSocket client connected
[2026-06-02T09:00:00.408Z] [INFO] Executing task: Consolidate old memory
[2026-06-02T09:00:00.412Z] [INFO] Task Consolidate old memory completed in 3ms

View file

@ -0,0 +1,6 @@
{
"captured_at": null,
"captures": [],
"total_size_kb": 0,
"estimated_tokens": 0
}

158
.wolf/hooks/_session.json Normal file
View file

@ -0,0 +1,158 @@
{
"session_id": "session-2026-06-04-0446",
"started": "2026-06-04T11:46:27.379Z",
"files_read": {
"/home/rshen/projects/xce/worktrees/persistent_ha/.kiro/specs/ha-repl-hardening": {
"count": 1,
"tokens": 0,
"first_read": "2026-06-04T11:47:59.019Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/.claude/skills/kiro-validate-design/rules/design-review.md": {
"count": 1,
"tokens": 1090,
"first_read": "2026-06-04T11:47:59.216Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/.kiro/specs/ha-repl-hardening/spec.json": {
"count": 2,
"tokens": 128,
"first_read": "2026-06-04T11:48:22.897Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/.kiro/specs/ha-repl-hardening/requirements.md": {
"count": 2,
"tokens": 2817,
"first_read": "2026-06-04T11:48:23.086Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/.kiro/specs/ha-repl-hardening/design.md": {
"count": 5,
"tokens": 5514,
"first_read": "2026-06-04T11:48:34.750Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/.kiro/specs/ha-repl-hardening/tasks.md": {
"count": 2,
"tokens": 2749,
"first_read": "2026-06-04T11:49:26.515Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/.claude/skills/kiro-spec-tasks/rules/tasks-generation.md": {
"count": 1,
"tokens": 2773,
"first_read": "2026-06-04T12:10:52.924Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/.claude/skills/kiro-spec-tasks/rules/tasks-parallel-analysis.md": {
"count": 1,
"tokens": 633,
"first_read": "2026-06-04T12:10:53.319Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/.claude/skills/kiro-spec-tasks/templates/specs/tasks.md": {
"count": 1,
"tokens": 318,
"first_read": "2026-06-04T12:10:53.806Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/.claude/skills/xce-build/SKILL.md": {
"count": 1,
"tokens": 112,
"first_read": "2026-06-04T12:20:51.454Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/.claude/skills/kiro-impl/templates/implementer-prompt.md": {
"count": 1,
"tokens": 1659,
"first_read": "2026-06-04T12:20:52.075Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/.claude/skills/kiro-impl/templates/reviewer-prompt.md": {
"count": 1,
"tokens": 1352,
"first_read": "2026-06-04T12:20:52.620Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/src/schema/controller.capnp": {
"count": 28,
"tokens": 7861,
"first_read": "2026-06-04T12:22:19.810Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/src/xce.build/lib/controller.capnp.h": {
"count": 1,
"tokens": 0,
"first_read": "2026-06-04T12:25:41.822Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/src/xce.build/lib/controller.capnp.c": {
"count": 4,
"tokens": 134780,
"first_read": "2026-06-04T12:26:31.693Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/src/include/typemap.h": {
"count": 6,
"tokens": 3378,
"first_read": "2026-06-04T12:28:56.960Z"
},
"/home/rshen/projects/xce/worktrees/persistent_ha/src/schema/c.capnp": {
"count": 1,
"tokens": 0,
"first_read": "2026-06-04T12:30:22.347Z"
}
},
"files_written": [
{
"file": "/home/rshen/projects/xce/worktrees/persistent_ha/.kiro/specs/ha-repl-hardening/design.md",
"action": "edit",
"tokens": 194,
"at": "2026-06-04T11:54:56.112Z"
},
{
"file": "/home/claude/.claude/plans/functional-tumbling-squirrel.md",
"action": "create",
"tokens": 1056,
"at": "2026-06-04T11:55:29.391Z"
},
{
"file": "/home/rshen/projects/xce/worktrees/persistent_ha/.kiro/specs/ha-repl-hardening/design.md",
"action": "edit",
"tokens": 99,
"at": "2026-06-04T11:56:57.713Z"
},
{
"file": "/home/rshen/projects/xce/worktrees/persistent_ha/.kiro/specs/ha-repl-hardening/design.md",
"action": "edit",
"tokens": 161,
"at": "2026-06-04T11:57:25.385Z"
},
{
"file": "/home/rshen/projects/xce/worktrees/persistent_ha/.kiro/specs/ha-repl-hardening/design.md",
"action": "edit",
"tokens": 157,
"at": "2026-06-04T11:57:49.314Z"
},
{
"file": "/home/rshen/projects/xce/worktrees/persistent_ha/.kiro/specs/ha-repl-hardening/design.md",
"action": "edit",
"tokens": 117,
"at": "2026-06-04T11:58:02.934Z"
},
{
"file": "/home/rshen/projects/xce/worktrees/persistent_ha/.kiro/specs/ha-repl-hardening/tasks.md",
"action": "create",
"tokens": 4002,
"at": "2026-06-04T12:14:41.929Z"
},
{
"file": "/home/rshen/projects/xce/worktrees/persistent_ha/.kiro/specs/ha-repl-hardening/spec.json",
"action": "create",
"tokens": 128,
"at": "2026-06-04T12:15:05.654Z"
},
{
"file": "/home/rshen/projects/xce/worktrees/persistent_ha/.kiro/specs/ha-repl-hardening/spec.json",
"action": "create",
"tokens": 128,
"at": "2026-06-04T12:21:31.484Z"
}
],
"edit_counts": {
".kiro/specs/ha-repl-hardening/design.md": 5,
"../../../../../claude/.claude/plans/functional-tumbling-squirrel.md": 1,
".kiro/specs/ha-repl-hardening/tasks.md": 1,
".kiro/specs/ha-repl-hardening/spec.json": 2
},
"anatomy_hits": 14,
"anatomy_misses": 3,
"repeated_reads_warned": 42,
"cerebrum_warnings": 0,
"stop_count": 4
}

3
.wolf/hooks/package.json Normal file
View file

@ -0,0 +1,3 @@
{
"type": "module"
}

69
.wolf/hooks/post-read.js Normal file
View file

@ -0,0 +1,69 @@
import * as path from "node:path";
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, readMarkdown, parseAnatomy, estimateTokens, readStdin, normalizePath } from "./shared.js";
async function main() {
ensureWolfDir();
const wolfDir = getWolfDir();
const hooksDir = path.join(wolfDir, "hooks");
const sessionFile = path.join(hooksDir, "_session.json");
const raw = await readStdin();
let input;
try {
input = JSON.parse(raw);
}
catch {
process.exit(0);
return;
}
const filePath = input.tool_input?.file_path ?? input.tool_input?.path ?? "";
const content = input.tool_output?.content ?? "";
if (!filePath) {
process.exit(0);
return;
}
const normalizedFile = normalizePath(filePath);
// Skip tracking for .wolf/ internal files — consistent with pre-read
const projectDir = normalizePath(process.env.CLAUDE_PROJECT_DIR || process.cwd());
const relToProject = normalizedFile.startsWith(projectDir)
? normalizedFile.slice(projectDir.length).replace(/^\//, "")
: "";
if (relToProject.startsWith(".wolf/") || relToProject.startsWith(".wolf\\")) {
process.exit(0);
return;
}
const ext = path.extname(filePath).toLowerCase();
const codeExts = new Set([".ts", ".js", ".tsx", ".jsx", ".py", ".rs", ".go", ".java", ".c", ".cpp", ".css", ".json", ".yaml", ".yml"]);
const proseExts = new Set([".md", ".txt", ".rst"]);
const type = codeExts.has(ext) ? "code" : proseExts.has(ext) ? "prose" : "mixed";
let tokens = content ? estimateTokens(content, type) : 0;
// Fallback: if tool_output had no content, use anatomy token estimate
if (tokens === 0) {
const anatomyContent = readMarkdown(path.join(wolfDir, "anatomy.md"));
const sections = parseAnatomy(anatomyContent);
for (const [sectionKey, entries] of sections) {
for (const entry of entries) {
const entryRelPath = normalizePath(path.join(sectionKey, entry.file));
if (normalizedFile.endsWith(entryRelPath) || normalizedFile.endsWith("/" + entryRelPath)) {
tokens = entry.tokens;
break;
}
}
if (tokens > 0)
break;
}
}
const session = readJSON(sessionFile, { files_read: {} });
if (session.files_read[normalizedFile]) {
session.files_read[normalizedFile].tokens = tokens;
}
else {
session.files_read[normalizedFile] = {
count: 1,
tokens,
first_read: new Date().toISOString(),
};
}
writeJSON(sessionFile, session);
process.exit(0);
}
main().catch(() => process.exit(0));
//# sourceMappingURL=post-read.js.map

503
.wolf/hooks/post-write.js Normal file
View file

@ -0,0 +1,503 @@
import * as fs from "node:fs";
import * as path from "node:path";
import * as crypto from "node:crypto";
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, parseAnatomy, serializeAnatomy, extractDescription, estimateTokens, appendMarkdown, timeShort, readStdin, normalizePath } from "./shared.js";
async function main() {
ensureWolfDir();
const wolfDir = getWolfDir();
const hooksDir = path.join(wolfDir, "hooks");
const sessionFile = path.join(hooksDir, "_session.json");
const projectRoot = process.env.CLAUDE_PROJECT_DIR || process.cwd();
const raw = await readStdin();
let input;
try {
input = JSON.parse(raw);
}
catch {
process.exit(0);
return;
}
const toolName = input.tool_name ?? "Write";
const filePath = input.tool_input?.file_path ?? input.tool_input?.path ?? "";
if (!filePath) {
process.exit(0);
return;
}
const absolutePath = path.isAbsolute(filePath) ? filePath : path.join(projectRoot, filePath);
// Skip processing for .wolf/ internal files to avoid slow self-referential updates
const relPath = normalizePath(path.relative(projectRoot, absolutePath));
if (relPath.startsWith(".wolf/")) {
process.exit(0);
return;
}
// Never track .env files in anatomy — they contain secrets
const baseName = path.basename(absolutePath);
if (baseName === ".env" || baseName.startsWith(".env.")) {
process.exit(0);
return;
}
const oldStr = input.tool_input?.old_string ?? "";
const newStr = input.tool_input?.new_string ?? "";
// 1. Update anatomy.md
try {
const anatomyPath = path.join(wolfDir, "anatomy.md");
let anatomyContent;
try {
anatomyContent = fs.readFileSync(anatomyPath, "utf-8");
}
catch {
anatomyContent = "# anatomy.md\n\n> Auto-maintained by OpenWolf.\n";
}
const sections = parseAnatomy(anatomyContent);
const relPathLocal = normalizePath(path.relative(projectRoot, absolutePath));
const dir = path.dirname(relPathLocal);
const fileName = path.basename(relPathLocal);
const sectionKey = dir === "." ? "./" : dir + "/";
let fileContent = "";
try {
fileContent = fs.readFileSync(absolutePath, "utf-8");
}
catch {
fileContent = input.tool_input?.content ?? "";
}
const desc = extractDescription(absolutePath).slice(0, 100);
const ext = path.extname(absolutePath).toLowerCase();
const codeExts = new Set([".ts", ".js", ".tsx", ".jsx", ".py", ".json", ".yaml", ".yml", ".css"]);
const proseExts = new Set([".md", ".txt", ".rst"]);
const type = codeExts.has(ext) ? "code" : proseExts.has(ext) ? "prose" : "mixed";
const tokens = estimateTokens(fileContent, type);
if (!sections.has(sectionKey))
sections.set(sectionKey, []);
const entries = sections.get(sectionKey);
const idx = entries.findIndex((e) => e.file === fileName);
if (idx !== -1) {
entries[idx] = { file: fileName, description: desc, tokens };
}
else {
entries.push({ file: fileName, description: desc, tokens });
}
let fileCount = 0;
for (const [, list] of sections)
fileCount += list.length;
const serialized = serializeAnatomy(sections, {
lastScanned: new Date().toISOString(),
fileCount,
hits: 0,
misses: 0,
});
const tmp = anatomyPath + "." + crypto.randomBytes(4).toString("hex") + ".tmp";
try {
fs.writeFileSync(tmp, serialized, "utf-8");
fs.renameSync(tmp, anatomyPath);
}
catch {
try {
fs.writeFileSync(anatomyPath, serialized, "utf-8");
}
catch { }
try {
fs.unlinkSync(tmp);
}
catch { }
}
}
catch { }
// 2. Append richer entry to memory.md
try {
const action = toolName === "Write" ? "Created" : toolName === "MultiEdit" ? "Multi-edited" : "Edited";
const relFile = normalizePath(path.relative(projectRoot, absolutePath));
const fileContent = input.tool_input?.content ?? "";
const ext = path.extname(absolutePath).toLowerCase();
const codeExts = new Set([".ts", ".js", ".tsx", ".jsx", ".py", ".json", ".yaml", ".yml", ".css"]);
const type = codeExts.has(ext) ? "code" : "mixed";
const writeTokens = estimateTokens(fileContent || newStr, type);
let changeDesc = "";
if (oldStr && newStr) {
changeDesc = summarizeEdit(oldStr, newStr, baseName);
}
const memoryPath = path.join(wolfDir, "memory.md");
const outcome = changeDesc || "—";
appendMarkdown(memoryPath, `| ${timeShort()} | ${action} ${relFile} | ${outcome} | ~${writeTokens} |\n`);
}
catch { }
// 3. Record in session tracker + track edit counts
try {
const session = readJSON(sessionFile, { files_written: [], edit_counts: {} });
if (!session.edit_counts)
session.edit_counts = {};
const normalizedFile = normalizePath(filePath);
const action = toolName === "Write" ? "create" : "edit";
const fileContent = input.tool_input?.content ?? "";
const tokens = estimateTokens(fileContent || newStr, "code");
session.files_written.push({
file: normalizedFile,
action,
tokens,
at: new Date().toISOString(),
});
const editKey = normalizePath(path.relative(projectRoot, absolutePath));
session.edit_counts[editKey] = (session.edit_counts[editKey] || 0) + 1;
writeJSON(sessionFile, session);
if (session.edit_counts[editKey] >= 3) {
process.stderr.write(`⚠️ OpenWolf: ${baseName} has been edited ${session.edit_counts[editKey]} times this session. If you're fixing a bug, remember to log it to .wolf/buglog.json.\n`);
}
}
catch { }
// 4. Auto-detect bug-fix patterns and log them
try {
if (oldStr && newStr) {
autoDetectBugFix(wolfDir, absolutePath, projectRoot, oldStr, newStr);
}
}
catch { }
process.exit(0);
}
// ─── Edit Summarizer ─────────────────────────────────────────────
function summarizeEdit(oldStr, newStr, filename) {
const oldLines = oldStr.split("\n");
const newLines = newStr.split("\n");
const oldCount = oldLines.length;
const newCount = newLines.length;
const ext = path.extname(filename).toLowerCase();
// --- Structural fixes ---
if (newStr.includes("try") && newStr.includes("catch") && !oldStr.includes("catch")) {
return "added error handling";
}
if (newStr.includes("?.") && !oldStr.includes("?."))
return "added optional chaining";
if (newStr.includes("?? ") && !oldStr.includes("?? "))
return "added nullish coalescing";
// --- Deleted code ---
if (!newStr.trim() || newStr.trim().length < oldStr.trim().length * 0.2) {
return `removed ${oldCount} lines`;
}
// --- Import changes ---
const oldImports = oldLines.filter(l => /^\s*(import|require|use |from )/.test(l)).length;
const newImports = newLines.filter(l => /^\s*(import|require|use |from )/.test(l)).length;
if (newImports > oldImports && Math.abs(newCount - oldCount) <= newImports - oldImports + 1) {
return `added ${newImports - oldImports} import(s)`;
}
// --- Value/string replacement (common bug fix: wrong value) ---
if (oldCount === 1 && newCount === 1) {
const o = oldStr.trim();
const n = newStr.trim();
// String literal change
const oStr = o.match(/['"`]([^'"`]+)['"`]/);
const nStr = n.match(/['"`]([^'"`]+)['"`]/);
if (oStr && nStr && oStr[1] !== nStr[1]) {
return `"${oStr[1].slice(0, 25)}" → "${nStr[1].slice(0, 25)}"`;
}
// Number change
const oNum = o.match(/\b(\d+\.?\d*)\b/);
const nNum = n.match(/\b(\d+\.?\d*)\b/);
if (oNum && nNum && oNum[1] !== nNum[1] && o.replace(oNum[1], "") === n.replace(nNum[1], "")) {
return `${oNum[1]} → ${nNum[1]}`;
}
return "inline fix";
}
// --- Method/function call changes ---
const oldCalls = extractCalls(oldStr);
const newCalls = extractCalls(newStr);
const addedCalls = newCalls.filter(c => !oldCalls.includes(c));
const removedCalls = oldCalls.filter(c => !newCalls.includes(c));
if (removedCalls.length === 1 && addedCalls.length === 1) {
return `${removedCalls[0]}() → ${addedCalls[0]}()`;
}
// --- CSS/style changes ---
if (ext === ".css" || ext === ".scss" || ext === ".vue" || ext === ".tsx" || ext === ".jsx") {
const oldProps = (oldStr.match(/[\w-]+\s*:/g) || []).map(p => p.replace(/\s*:/, ""));
const newProps = (newStr.match(/[\w-]+\s*:/g) || []).map(p => p.replace(/\s*:/, ""));
const changed = newProps.filter(p => !oldProps.includes(p));
if (changed.length > 0 && changed.length <= 3) {
return `CSS: ${changed.join(", ")}`;
}
}
// --- Condition changes ---
const oldConds = (oldStr.match(/if\s*\(([^)]+)\)/g) || []);
const newConds = (newStr.match(/if\s*\(([^)]+)\)/g) || []);
if (newConds.length > oldConds.length) {
return `added ${newConds.length - oldConds.length} condition(s)`;
}
// --- Function modified ---
const fnMatch = newStr.match(/(?:function|def|fn|func|async\s+function)\s+(\w+)/);
if (fnMatch) {
return `modified ${fnMatch[1]}()`;
}
// --- Class/method context ---
const methodMatch = newStr.match(/(?:public|private|protected)?\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*[:{]/);
if (methodMatch) {
return `modified ${methodMatch[1]}()`;
}
// --- Size-based fallback ---
if (newCount > oldCount + 5)
return `expanded (+${newCount - oldCount} lines)`;
if (oldCount > newCount + 5)
return `reduced (-${oldCount - newCount} lines)`;
return `${oldCount}→${newCount} lines`;
}
function extractCalls(code) {
return [...new Set((code.match(/(\w+)\s*\(/g) || [])
.map(m => m.match(/(\w+)/)?.[1] || "")
.filter(n => n.length > 2 && !["if", "for", "while", "switch", "catch", "function", "return", "new", "typeof", "instanceof", "const", "let", "var"].includes(n)))];
}
// ─── Auto Bug Detection ──────────────────────────────────────────
function autoDetectBugFix(wolfDir, absolutePath, projectRoot, oldStr, newStr) {
const bugLogPath = path.join(wolfDir, "buglog.json");
const bugLog = readJSON(bugLogPath, { version: 1, bugs: [] });
const relFile = normalizePath(path.relative(projectRoot, absolutePath));
const basename = path.basename(absolutePath);
const ext = path.extname(basename).toLowerCase();
// Detect what kind of fix this is
const detection = detectFixPattern(oldStr, newStr, ext);
if (!detection)
return;
// Check for recent duplicate (same file + same category within 5 min)
const recentDupe = bugLog.bugs.find(b => {
if (path.basename(b.file) !== basename)
return false;
if (!b.tags.includes("auto-detected"))
return false;
if (!b.tags.includes(detection.category))
return false;
const bugTime = new Date(b.last_seen).getTime();
return (Date.now() - bugTime) < 5 * 60 * 1000;
});
if (recentDupe) {
recentDupe.occurrences++;
recentDupe.last_seen = new Date().toISOString();
// Append additional context
if (detection.context && !recentDupe.fix.includes(detection.context)) {
recentDupe.fix += ` | Also: ${detection.context}`;
}
writeJSON(bugLogPath, bugLog);
return;
}
const nextId = `bug-${String(bugLog.bugs.length + 1).padStart(3, "0")}`;
bugLog.bugs.push({
id: nextId,
timestamp: new Date().toISOString(),
error_message: detection.summary,
file: relFile,
root_cause: detection.rootCause,
fix: detection.fix,
tags: ["auto-detected", detection.category, ext.replace(".", "") || "unknown"],
related_bugs: [],
occurrences: 1,
last_seen: new Date().toISOString(),
});
writeJSON(bugLogPath, bugLog);
}
function detectFixPattern(oldStr, newStr, ext) {
const oldLines = oldStr.split("\n");
const newLines = newStr.split("\n");
// --- Error handling added ---
if (newStr.includes("catch") && !oldStr.includes("catch")) {
const fn = newStr.match(/(?:function|def|async)\s+(\w+)/)?.[1] || "unknown";
return {
category: "error-handling",
summary: `Missing error handling in ${path.basename(fn)}`,
rootCause: "Code path had no error handling — exceptions would propagate uncaught",
fix: `Added try/catch block`,
context: extractChangedLines(oldStr, newStr),
};
}
// --- Null/undefined safety ---
if ((newStr.includes("?.") && !oldStr.includes("?.")) ||
(newStr.includes("?? ") && !oldStr.includes("?? ")) ||
(/!==?\s*(null|undefined)/.test(newStr) && !/!==?\s*(null|undefined)/.test(oldStr))) {
return {
category: "null-safety",
summary: `Null/undefined access in ${path.basename(path.basename(""))}`,
rootCause: "Property access on potentially null/undefined value",
fix: `Added null safety (optional chaining or null check)`,
context: extractChangedLines(oldStr, newStr),
};
}
// --- Guard clause / early return added ---
if (/if\s*\([^)]*\)\s*(return|throw|continue|break)/.test(newStr) &&
!/if\s*\([^)]*\)\s*(return|throw|continue|break)/.test(oldStr)) {
const condition = newStr.match(/if\s*\(([^)]+)\)/)?.[1]?.trim().slice(0, 60) || "condition";
return {
category: "guard-clause",
summary: `Missing guard clause`,
rootCause: `No early return/throw for edge case: ${condition}`,
fix: `Added guard clause: if (${condition.slice(0, 40)})`,
};
}
// --- Wrong value / string fix (very common bug) ---
if (oldLines.length <= 3 && newLines.length <= 3) {
const oldJoined = oldStr.trim();
const newJoined = newStr.trim();
// String literal changed
const oStrs = oldJoined.match(/['"`]([^'"`]{2,})['"`]/g) || [];
const nStrs = newJoined.match(/['"`]([^'"`]{2,})['"`]/g) || [];
if (oStrs.length > 0 && nStrs.length > 0) {
for (let i = 0; i < Math.min(oStrs.length, nStrs.length); i++) {
if (oStrs[i] !== nStrs[i]) {
return {
category: "wrong-value",
summary: `Incorrect value in code`,
rootCause: `Had ${oStrs[i].slice(0, 50)}`,
fix: `Changed to ${nStrs[i].slice(0, 50)}`,
};
}
}
}
// Variable name / method call changed
const oldTokens = tokenizeCode(oldJoined);
const newTokens = tokenizeCode(newJoined);
const changed = [];
for (let i = 0; i < Math.min(oldTokens.length, newTokens.length); i++) {
if (oldTokens[i] !== newTokens[i]) {
changed.push([oldTokens[i], newTokens[i]]);
}
}
if (changed.length === 1 && changed[0][0].length > 2) {
return {
category: "wrong-reference",
summary: `Wrong reference: ${changed[0][0]} should be ${changed[0][1]}`,
rootCause: `Used "${changed[0][0]}" instead of "${changed[0][1]}"`,
fix: `Changed ${changed[0][0]} → ${changed[0][1]}`,
};
}
}
// --- Logic fix (condition changed) ---
const oldCond = oldStr.match(/if\s*\(([^)]+)\)/)?.[1];
const newCond = newStr.match(/if\s*\(([^)]+)\)/)?.[1];
if (oldCond && newCond && oldCond !== newCond && oldLines.length <= 5) {
return {
category: "logic-fix",
summary: `Wrong condition in logic`,
rootCause: `Condition was: if (${oldCond.slice(0, 50)})`,
fix: `Changed to: if (${newCond.slice(0, 50)})`,
};
}
// --- Operator fix (=== vs ==, > vs >=, etc.) ---
const opChange = findOperatorChange(oldStr, newStr);
if (opChange) {
return {
category: "operator-fix",
summary: `Wrong operator: ${opChange.old} should be ${opChange.new}`,
rootCause: `Used "${opChange.old}" instead of "${opChange.new}"`,
fix: `Changed operator ${opChange.old} → ${opChange.new}`,
};
}
// --- Missing import/require ---
const oldImports = new Set((oldStr.match(/(?:import|require)\s*\(?['"]([^'"]+)['"]\)?/g) || []).map(m => m));
const newImports = (newStr.match(/(?:import|require)\s*\(?['"]([^'"]+)['"]\)?/g) || []);
const addedImports = newImports.filter(i => !oldImports.has(i));
if (addedImports.length > 0 && newLines.length - oldLines.length <= addedImports.length + 2) {
const modules = addedImports.map(i => i.match(/['"]([^'"]+)['"]/)?.[1] || "").filter(Boolean);
return {
category: "missing-import",
summary: `Missing import: ${modules.join(", ")}`,
rootCause: `Module(s) not imported: ${modules.join(", ")}`,
fix: `Added import(s) for ${modules.join(", ")}`,
};
}
// --- Return value fix ---
const oldReturn = oldStr.match(/return\s+(.+)/)?.[1]?.trim();
const newReturn = newStr.match(/return\s+(.+)/)?.[1]?.trim();
if (oldReturn && newReturn && oldReturn !== newReturn && oldLines.length <= 5) {
return {
category: "return-value",
summary: `Wrong return value`,
rootCause: `Was returning: ${oldReturn.slice(0, 50)}`,
fix: `Now returns: ${newReturn.slice(0, 50)}`,
};
}
// --- Async/await fix ---
if (newStr.includes("await ") && !oldStr.includes("await ")) {
return {
category: "async-fix",
summary: `Missing await`,
rootCause: `Async call without await — returned Promise instead of value`,
fix: `Added await to async call`,
context: extractChangedLines(oldStr, newStr),
};
}
if (newStr.includes("async ") && !oldStr.includes("async ")) {
return {
category: "async-fix",
summary: `Function not marked async`,
rootCause: `Function uses await but wasn't declared async`,
fix: `Added async modifier`,
};
}
// --- Type annotation/cast fix ---
if (ext === ".ts" || ext === ".tsx") {
if ((newStr.includes(" as ") && !oldStr.includes(" as ")) ||
(newStr.includes(": ") && !oldStr.includes(": ") && oldLines.length <= 3)) {
return {
category: "type-fix",
summary: `Type error`,
rootCause: `Missing or incorrect type annotation`,
fix: `Added type assertion/annotation`,
context: extractChangedLines(oldStr, newStr),
};
}
}
// --- CSS/style fix ---
if (ext === ".css" || ext === ".scss" || ext === ".vue" || ext === ".tsx" || ext === ".jsx") {
const oldProps = extractCSSProps(oldStr);
const newProps = extractCSSProps(newStr);
const changedProps = [...newProps.entries()].filter(([k, v]) => oldProps.get(k) !== v && oldProps.has(k));
if (changedProps.length > 0 && changedProps.length <= 3) {
const desc = changedProps.map(([k, v]) => `${k}: ${oldProps.get(k)} → ${v}`).join("; ");
return {
category: "style-fix",
summary: `CSS fix: ${changedProps.map(([k]) => k).join(", ")}`,
rootCause: desc,
fix: `Changed ${desc}`,
};
}
}
// --- Significant diff (catch-all for substantial edits) ---
const diffRatio = Math.abs(newStr.length - oldStr.length) / Math.max(oldStr.length, 1);
if (diffRatio > 0.3 && oldLines.length >= 3 && newLines.length >= 3) {
// Only log if there's meaningful structural change, not just additions
const removedLines = oldLines.filter(l => l.trim() && !newLines.some(nl => nl.trim() === l.trim()));
if (removedLines.length >= 2) {
return {
category: "refactor",
summary: `Significant refactor of ${path.basename("")}`,
rootCause: `${removedLines.length} lines replaced/restructured`,
fix: `Rewrote ${oldLines.length}→${newLines.length} lines (${removedLines.length} removed)`,
context: removedLines.slice(0, 2).map(l => l.trim().slice(0, 50)).join("; "),
};
}
}
return null;
}
function extractChangedLines(oldStr, newStr) {
const oldLines = new Set(oldStr.split("\n").map(l => l.trim()).filter(Boolean));
const newLines = newStr.split("\n").map(l => l.trim()).filter(Boolean);
const added = newLines.filter(l => !oldLines.has(l));
return added.slice(0, 2).map(l => l.slice(0, 60)).join("; ");
}
function tokenizeCode(code) {
return code.replace(/[^\w$]/g, " ").split(/\s+/).filter(t => t.length > 0);
}
function findOperatorChange(oldStr, newStr) {
const operators = ["===", "!==", "==", "!=", ">=", "<=", ">>", "<<", "&&", "||", "??"];
for (const op of operators) {
if (oldStr.includes(op) && !newStr.includes(op)) {
for (const op2 of operators) {
if (op2 !== op && newStr.includes(op2) && !oldStr.includes(op2)) {
return { old: op, new: op2 };
}
}
}
}
return null;
}
function extractCSSProps(code) {
const props = new Map();
const matches = code.matchAll(/([\w-]+)\s*:\s*([^;}\n]+)/g);
for (const m of matches) {
props.set(m[1].trim(), m[2].trim());
}
return props;
}
main().catch(() => process.exit(0));
//# sourceMappingURL=post-write.js.map

80
.wolf/hooks/pre-read.js Normal file
View file

@ -0,0 +1,80 @@
import * as path from "node:path";
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, readMarkdown, parseAnatomy, readStdin, normalizePath } from "./shared.js";
async function main() {
ensureWolfDir();
const wolfDir = getWolfDir();
const hooksDir = path.join(wolfDir, "hooks");
const sessionFile = path.join(hooksDir, "_session.json");
const raw = await readStdin();
let input;
try {
input = JSON.parse(raw);
}
catch {
process.exit(0);
return;
}
const filePath = input.tool_input?.file_path ?? input.tool_input?.path ?? "";
if (!filePath) {
process.exit(0);
return;
}
const normalizedFile = normalizePath(filePath);
// Skip tracking for .wolf/ internal files — they're infrastructure, not project files.
// Counting them inflates anatomy miss rates since .wolf/ is excluded from anatomy scanning.
const projectDir = normalizePath(process.env.CLAUDE_PROJECT_DIR || process.cwd());
const relToProject = normalizedFile.startsWith(projectDir)
? normalizedFile.slice(projectDir.length).replace(/^\//, "")
: "";
if (relToProject.startsWith(".wolf/") || relToProject.startsWith(".wolf\\")) {
process.exit(0);
return;
}
const session = readJSON(sessionFile, {
session_id: "", files_read: {}, anatomy_hits: 0, anatomy_misses: 0,
repeated_reads_warned: 0,
});
// Check if already read this session
if (session.files_read[normalizedFile]) {
const prev = session.files_read[normalizedFile];
process.stderr.write(`⚡ OpenWolf: ${path.basename(normalizedFile)} was already read this session (~${prev.tokens} tokens). Consider using your existing knowledge of this file.\n`);
session.files_read[normalizedFile].count++;
session.repeated_reads_warned++;
writeJSON(sessionFile, session);
process.exit(0);
return;
}
// Check anatomy.md for this file
const anatomyContent = readMarkdown(path.join(wolfDir, "anatomy.md"));
const sections = parseAnatomy(anatomyContent);
let found = false;
for (const [sectionKey, entries] of sections) {
for (const entry of entries) {
// Build the full relative path from the section key + filename for accurate matching
const entryRelPath = normalizePath(path.join(sectionKey, entry.file));
if (normalizedFile.endsWith(entryRelPath) || normalizedFile.endsWith("/" + entryRelPath)) {
process.stderr.write(`📋 OpenWolf anatomy: ${entry.file} — ${entry.description} (~${entry.tokens} tok)\n`);
found = true;
break;
}
}
if (found)
break;
}
if (found) {
session.anatomy_hits++;
}
else {
session.anatomy_misses++;
}
// Record initial read entry (tokens will be updated in post-read)
session.files_read[normalizedFile] = {
count: 1,
tokens: 0,
first_read: new Date().toISOString(),
};
writeJSON(sessionFile, session);
process.exit(0);
}
main().catch(() => process.exit(0));
//# sourceMappingURL=pre-read.js.map

121
.wolf/hooks/pre-write.js Normal file
View file

@ -0,0 +1,121 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { getWolfDir, ensureWolfDir, readJSON, readMarkdown, readStdin } from "./shared.js";
async function main() {
ensureWolfDir();
const wolfDir = getWolfDir();
const raw = await readStdin();
let input;
try {
input = JSON.parse(raw);
}
catch {
process.exit(0);
return;
}
// For Edit tool, the meaningful content is old_string + new_string
const content = input.tool_input?.content ?? "";
const oldStr = input.tool_input?.old_string ?? "";
const newStr = input.tool_input?.new_string ?? "";
const filePath = input.tool_input?.file_path ?? input.tool_input?.path ?? "";
const allContent = [content, oldStr, newStr].join("\n");
if (!allContent.trim()) {
process.exit(0);
return;
}
// 1. Cerebrum Do-Not-Repeat check
checkCerebrum(wolfDir, allContent);
// 2. Bug log: search for similar past bugs when editing code
// This fires when Claude is about to edit a file — if the edit looks like a fix
// (changing error handling, modifying catch blocks, etc.), check the bug log
if (filePath && (oldStr || content)) {
checkBugLog(wolfDir, filePath, oldStr, newStr, content);
}
process.exit(0);
}
function checkCerebrum(wolfDir, content) {
const cerebrumContent = readMarkdown(path.join(wolfDir, "cerebrum.md"));
const doNotRepeatSection = cerebrumContent.split("## Do-Not-Repeat")[1];
if (!doNotRepeatSection)
return;
const entries = doNotRepeatSection.split("## ")[0];
const lines = entries.split("\n").filter((l) => l.trim().startsWith("[") || l.trim().startsWith("-"));
for (const line of lines) {
const trimmed = line.trim().replace(/^[-*]\s*/, "").replace(/^\[[\d-]+\]\s*/, "");
if (!trimmed)
continue;
const patterns = [];
const quotedMatches = trimmed.match(/"([^"]+)"/g) || trimmed.match(/'([^']+)'/g) || trimmed.match(/`([^`]+)`/g);
if (quotedMatches) {
for (const qm of quotedMatches) {
patterns.push(qm.replace(/["'`]/g, ""));
}
}
const neverMatch = trimmed.match(/(?:never use|avoid|don't use|do not use)\s+(\w+)/i);
if (neverMatch)
patterns.push(neverMatch[1]);
for (const pattern of patterns) {
try {
const regex = new RegExp(`\\b${pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")}\\b`, "i");
if (regex.test(content)) {
process.stderr.write(`⚠️ OpenWolf cerebrum warning: "${trimmed}" — check your code before proceeding.\n`);
}
}
catch { }
}
}
}
// Common words that appear in most code — must be excluded from similarity matching
const STOP_WORDS = new Set([
"error", "function", "return", "const", "this", "that", "with", "from",
"import", "export", "class", "interface", "type", "undefined", "null",
"true", "false", "string", "number", "object", "array", "value",
"file", "path", "name", "data", "response", "request", "result",
"should", "must", "does", "have", "been", "will", "would", "could",
"when", "then", "else", "each", "some", "every", "only",
]);
function checkBugLog(wolfDir, filePath, oldStr, newStr, content) {
const bugLogPath = path.join(wolfDir, "buglog.json");
if (!fs.existsSync(bugLogPath))
return;
const bugLog = readJSON(bugLogPath, { version: 1, bugs: [] });
if (bugLog.bugs.length === 0)
return;
const basename = path.basename(filePath);
// ONLY surface bugs that match the SAME file being edited.
// Cross-file matching is too noisy and risks misdirecting Claude.
const fileMatches = bugLog.bugs.filter(b => {
const bugBasename = path.basename(b.file);
return bugBasename === basename;
});
if (fileMatches.length === 0)
return;
// Further filter: require tag or error_message overlap with the edit content
const editText = (oldStr + " " + newStr + " " + content).toLowerCase();
const editTokens = tokenize(editText);
const relevant = fileMatches.filter(bug => {
// Check if any bug tag appears in the edit content
const tagHit = bug.tags.some(t => editText.includes(t.toLowerCase()));
if (tagHit)
return true;
// Check meaningful word overlap (excluding stop words)
const bugTokens = tokenize(bug.error_message + " " + bug.root_cause);
const overlap = [...editTokens].filter(t => bugTokens.has(t));
// Require at least 3 meaningful overlapping words
return overlap.length >= 3;
});
if (relevant.length === 0)
return;
// Surface as a FYI, not a directive — Claude should evaluate, not blindly apply
process.stderr.write(`📋 OpenWolf buglog: ${relevant.length} past bug(s) found for ${basename} — review for context, do NOT apply blindly:\n`);
for (const bug of relevant.slice(0, 2)) {
process.stderr.write(` [${bug.id}] "${bug.error_message.slice(0, 70)}"\n Cause: ${bug.root_cause.slice(0, 80)}\n Fix: ${bug.fix.slice(0, 80)}\n`);
}
}
function tokenize(text) {
return new Set(text.replace(/[^\w\s]/g, " ").split(/\s+/)
.filter(w => w.length > 3 && !STOP_WORDS.has(w.toLowerCase()))
.map(w => w.toLowerCase()));
}
main().catch(() => process.exit(0));
//# sourceMappingURL=pre-write.js.map

View file

@ -0,0 +1,77 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { getWolfDir, ensureWolfDir, writeJSON, appendMarkdown, readJSON, timestamp, timeShort } from "./shared.js";
async function main() {
ensureWolfDir();
const wolfDir = getWolfDir();
// Clean up stale .tmp files left from failed atomic writes
try {
const files = fs.readdirSync(wolfDir);
for (const f of files) {
if (f.endsWith(".tmp")) {
try {
fs.unlinkSync(path.join(wolfDir, f));
}
catch { }
}
}
}
catch { }
const hooksDir = path.join(wolfDir, "hooks");
const sessionFile = path.join(hooksDir, "_session.json");
const now = new Date();
const sessionId = `session-${now.toISOString().slice(0, 10)}-${String(now.getHours()).padStart(2, "0")}${String(now.getMinutes()).padStart(2, "0")}`;
// Create fresh session state
writeJSON(sessionFile, {
session_id: sessionId,
started: timestamp(),
files_read: {},
files_written: [],
edit_counts: {},
anatomy_hits: 0,
anatomy_misses: 0,
repeated_reads_warned: 0,
cerebrum_warnings: 0,
stop_count: 0,
});
// Append session header to memory.md
const memoryPath = path.join(wolfDir, "memory.md");
const header = `\n## Session: ${now.toISOString().slice(0, 10)} ${timeShort()}\n\n| Time | Action | File(s) | Outcome | ~Tokens |\n|------|--------|---------|---------|--------|\n`;
appendMarkdown(memoryPath, header);
// Check cerebrum freshness — remind Claude to learn
try {
const cerebrumPath = path.join(wolfDir, "cerebrum.md");
const cerebrumContent = fs.readFileSync(cerebrumPath, "utf-8");
const stat = fs.statSync(cerebrumPath);
const daysSinceUpdate = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60 * 24);
// Count actual entries (non-comment, non-empty lines in content sections)
const entryLines = cerebrumContent.split("\n").filter(l => {
const t = l.trim();
return t.startsWith("- ") || t.startsWith("* ") || (t.startsWith("[") && t.includes("]"));
});
if (entryLines.length < 3) {
process.stderr.write(`💡 OpenWolf: cerebrum.md has only ${entryLines.length} entries. Learn from this session — record user preferences, project conventions, and mistakes to .wolf/cerebrum.md.\n`);
}
else if (daysSinceUpdate > 3) {
process.stderr.write(`💡 OpenWolf: cerebrum.md hasn't been updated in ${Math.floor(daysSinceUpdate)} days. Look for opportunities to add learnings this session.\n`);
}
}
catch { }
// Check buglog — remind if empty
try {
const buglogPath = path.join(wolfDir, "buglog.json");
const buglog = readJSON(buglogPath, { bugs: [] });
if (buglog.bugs.length === 0) {
process.stderr.write(`📋 OpenWolf: buglog.json is empty. If you encounter or fix any bugs, errors, or failed tests this session, log them to .wolf/buglog.json.\n`);
}
}
catch { }
// Increment total_sessions in token-ledger
const ledgerPath = path.join(wolfDir, "token-ledger.json");
const ledger = readJSON(ledgerPath, { version: 1, lifetime: { total_sessions: 0 } });
ledger.lifetime.total_sessions++;
writeJSON(ledgerPath, ledger);
process.exit(0);
}
main().catch(() => process.exit(0));
//# sourceMappingURL=session-start.js.map

614
.wolf/hooks/shared.js Normal file
View file

@ -0,0 +1,614 @@
import * as fs from "node:fs";
import * as path from "node:path";
import * as crypto from "node:crypto";
export function getWolfDir() {
// Prefer CLAUDE_PROJECT_DIR so hooks work even if CWD changes during a session
const projectDir = process.env.CLAUDE_PROJECT_DIR || process.cwd();
return path.join(projectDir, ".wolf");
}
/**
* Bail out silently if .wolf/ directory doesn't exist in the current project.
* Call this at the top of every hook to avoid crashes in non-OpenWolf projects.
*/
export function ensureWolfDir() {
const wolfDir = getWolfDir();
if (!fs.existsSync(wolfDir)) {
process.exit(0);
}
}
export function readJSON(filePath, fallback) {
try {
return JSON.parse(fs.readFileSync(filePath, "utf-8"));
}
catch {
return fallback;
}
}
export function writeJSON(filePath, data) {
const dir = path.dirname(filePath);
if (!fs.existsSync(dir))
fs.mkdirSync(dir, { recursive: true });
const tmp = filePath + "." + crypto.randomBytes(4).toString("hex") + ".tmp";
try {
fs.writeFileSync(tmp, JSON.stringify(data, null, 2), "utf-8");
fs.renameSync(tmp, filePath);
}
catch {
// On Windows, rename can fail if another process holds a handle.
// Fall back to direct write and clean up the tmp file.
try {
fs.writeFileSync(filePath, JSON.stringify(data, null, 2), "utf-8");
}
catch { }
try {
fs.unlinkSync(tmp);
}
catch { }
}
}
export function readMarkdown(filePath) {
try {
return fs.readFileSync(filePath, "utf-8");
}
catch {
return "";
}
}
export function appendMarkdown(filePath, line) {
const dir = path.dirname(filePath);
if (!fs.existsSync(dir))
fs.mkdirSync(dir, { recursive: true });
fs.appendFileSync(filePath, line, "utf-8");
}
export function parseAnatomy(content) {
const sections = new Map();
let currentSection = "";
for (const line of content.split("\n")) {
const sm = line.match(/^## (.+)/);
if (sm) {
currentSection = sm[1].trim();
if (!sections.has(currentSection))
sections.set(currentSection, []);
continue;
}
if (!currentSection)
continue;
const em = line.match(/^- `([^`]+)`(?:\s+—\s+(.+?))?\s*\(~(\d+)\s+tok\)$/);
if (em) {
sections.get(currentSection).push({
file: em[1],
description: em[2] || "",
tokens: parseInt(em[3], 10),
});
}
}
return sections;
}
export function serializeAnatomy(sections, metadata) {
const lines = [
"# anatomy.md",
"",
`> Auto-maintained by OpenWolf. Last scanned: ${metadata.lastScanned}`,
`> Files: ${metadata.fileCount} tracked | Anatomy hits: ${metadata.hits} | Misses: ${metadata.misses}`,
"",
];
const keys = [...sections.keys()].sort();
for (const key of keys) {
lines.push(`## ${key}`);
lines.push("");
const entries = sections.get(key).sort((a, b) => a.file.localeCompare(b.file));
for (const e of entries) {
const desc = e.description ? ` — ${e.description}` : "";
lines.push(`- \`${e.file}\`${desc} (~${e.tokens} tok)`);
}
lines.push("");
}
return lines.join("\n");
}
export function extractDescription(filePath) {
const MAX_DESC = 150;
const basename = path.basename(filePath);
const ext = path.extname(basename).toLowerCase();
const known = {
"package.json": "Node.js package manifest",
"tsconfig.json": "TypeScript configuration",
".gitignore": "Git ignore rules",
"README.md": "Project documentation",
"composer.json": "PHP package manifest",
"requirements.txt": "Python dependencies",
"schema.sql": "Database schema",
"Dockerfile": "Docker container definition",
"docker-compose.yml": "Docker Compose services",
"Cargo.toml": "Rust package manifest",
"go.mod": "Go module definition",
"Gemfile": "Ruby dependencies",
"pubspec.yaml": "Dart/Flutter package manifest",
};
if (known[basename])
return known[basename];
let content;
try {
const fd = fs.openSync(filePath, "r");
const buf = Buffer.alloc(12288); // 12KB
const n = fs.readSync(fd, buf, 0, 12288, 0);
fs.closeSync(fd);
content = buf.subarray(0, n).toString("utf-8");
}
catch {
return "";
}
if (!content.trim())
return "";
const cap = (s) => s.length <= MAX_DESC ? s : s.slice(0, MAX_DESC - 3) + "...";
// Markdown heading
if (ext === ".md" || ext === ".mdx") {
const m = content.match(/^#{1,2}\s+(.+)$/m);
if (m)
return cap(m[1].trim());
}
// HTML title
if (ext === ".html" || ext === ".htm") {
const m = content.match(/<title[^>]*>([^<]+)<\/title>/i);
if (m)
return cap(m[1].trim());
}
// JSDoc / PHPDoc / Javadoc — first meaningful line
const jm = content.match(/\/\*\*\s*\n?\s*\*?\s*(.+)/);
if (jm) {
const l = jm[1].replace(/\*\/$/, "").trim();
if (l && !l.startsWith("@") && l.length > 5)
return cap(l);
}
// Python docstring
if (ext === ".py") {
const dm = content.match(/^(?:#[^\n]*\n)*\s*(?:"""(.+?)"""|'''(.+?)''')/s);
if (dm) {
const first = (dm[1] || dm[2]).split("\n")[0].trim();
if (first && first.length > 3)
return cap(first);
}
}
// Rust doc comments
if (ext === ".rs") {
const lines = content.split("\n");
for (const line of lines.slice(0, 20)) {
const m = line.match(/^\s*(?:\/\/\/|\/\/!)\s*(.+)/);
if (m && m[1].length > 5)
return cap(m[1].trim());
}
}
// Go package comment
if (ext === ".go") {
const m = content.match(/\/\/\s*Package\s+\w+\s+(.*)/);
if (m)
return cap(m[1].trim());
}
// C# XML doc
if (ext === ".cs") {
const m = content.match(/<summary>\s*([\s\S]*?)\s*<\/summary>/);
if (m) {
const text = m[1].replace(/\/\/\/\s*/g, "").replace(/\s+/g, " ").trim();
if (text.length > 5)
return cap(text);
}
}
// Elixir @moduledoc
if (ext === ".ex" || ext === ".exs") {
const m = content.match(/@moduledoc\s+"""\s*\n\s*(.*)/);
if (m)
return cap(m[1].trim());
}
// Header comment (skip generic ones)
const hdrLines = content.split("\n");
for (const line of hdrLines.slice(0, 15)) {
const t = line.trim();
if (!t || t === "<?php" || t.startsWith("#!") || t.startsWith("namespace") || t.startsWith("use ") || t.startsWith("import ") || t.startsWith("from ") || t.startsWith("require") || t.startsWith("module "))
continue;
const cm = t.match(/^(?:\/\/|#|--)\s*(.+)/);
if (cm) {
const text = cm[1].trim();
const lower = text.toLowerCase();
if (text.length > 5 && !lower.startsWith("copyright") && !lower.startsWith("license") && !lower.startsWith("@") && !lower.startsWith("strict") && !lower.startsWith("generated") && !lower.startsWith("eslint-") && !lower.startsWith("nolint")) {
return cap(text);
}
}
if (!t.startsWith("//") && !t.startsWith("#") && !t.startsWith("/*") && !t.startsWith("*") && !t.startsWith("--"))
break;
}
// ─── PHP / Laravel ───────────────────────────────────────
if (ext === ".php") {
if (basename.endsWith(".blade.php")) {
const ext2 = content.match(/@extends\(\s*['"]([^'"]+)['"]\s*\)/);
const sections = (content.match(/@section\(\s*['"](\w+)['"]/g) || []).map(s => s.match(/['"](\w+)['"]/)?.[1]).filter(Boolean);
const parts = [];
if (ext2)
parts.push(`extends ${ext2[1]}`);
if (sections.length)
parts.push(`sections: ${sections.join(", ")}`);
return cap(parts.length ? `Blade: ${parts.join(", ")}` : "Blade template");
}
const classM = content.match(/class\s+(\w+)(?:\s+extends\s+(\w+))?/);
const className = classM?.[1] || "";
const parent = classM?.[2] || "";
const pubMethods = (content.match(/public\s+function\s+(\w+)/g) || [])
.map(m => m.match(/public\s+function\s+(\w+)/)?.[1])
.filter(n => n && n !== "__construct" && n !== "middleware");
if (basename.endsWith("Controller.php") || parent === "Controller") {
if (pubMethods.length > 0) {
const display = pubMethods.slice(0, 5).join(", ");
return cap(pubMethods.length > 5 ? `${display} + ${pubMethods.length - 5} more` : display);
}
}
if (parent === "Model" || parent === "Authenticatable") {
const parts = [];
const tbl = content.match(/\$table\s*=\s*['"]([^'"]+)['"]/);
if (tbl)
parts.push(`table: ${tbl[1]}`);
const fill = content.match(/\$fillable\s*=\s*\[([^\]]*)\]/s);
if (fill) {
const c = (fill[1].match(/['"]/g) || []).length / 2;
parts.push(`${Math.floor(c)} fields`);
}
const rels = (content.match(/\$this->(hasMany|hasOne|belongsTo|belongsToMany|morphMany|morphTo)\(/g) || []).length;
if (rels)
parts.push(`${rels} rels`);
return cap(parts.length ? `Model — ${parts.join(", ")}` : `Model: ${className}`);
}
if (basename.match(/^\d{4}_\d{2}_\d{2}/)) {
const create = content.match(/Schema::create\(\s*['"]([^'"]+)['"]/);
if (create)
return `Migration: create ${create[1]} table`;
const alter = content.match(/Schema::table\(\s*['"]([^'"]+)['"]/);
if (alter)
return `Migration: alter ${alter[1]} table`;
return "Database migration";
}
if (className && pubMethods.length > 0) {
const display = pubMethods.slice(0, 4).join(", ");
return cap(pubMethods.length > 4 ? `${className}: ${display} + ${pubMethods.length - 4} more` : `${className}: ${display}`);
}
}
// ─── TS/JS/React/Next.js ─────────────────────────────────
if (ext === ".ts" || ext === ".tsx" || ext === ".js" || ext === ".jsx" || ext === ".mjs" || ext === ".cjs") {
// React component
if (ext === ".tsx" || ext === ".jsx") {
const comp = content.match(/(?:export\s+(?:default\s+)?)?(?:function|const)\s+(\w+)/);
const parts = [];
if (comp)
parts.push(comp[1]);
const renders = [];
if (/<(?:form|Form)/i.test(content))
renders.push("form");
if (/<(?:table|Table|DataTable)/i.test(content))
renders.push("table");
if (/<(?:dialog|Dialog|Modal|Drawer)/i.test(content))
renders.push("modal");
if (renders.length)
parts.push(`renders ${renders.join(", ")}`);
if (parts.length)
return cap(parts.join(" — "));
}
// Next.js conventions
if (basename === "page.tsx" || basename === "page.js")
return "Next.js page component";
if (basename === "layout.tsx" || basename === "layout.js")
return "Next.js layout";
if (basename === "route.ts" || basename === "route.js") {
const methods = [...new Set((content.match(/export\s+(?:async\s+)?function\s+(GET|POST|PUT|PATCH|DELETE)/g) || [])
.map(m => m.match(/(GET|POST|PUT|PATCH|DELETE)/)?.[1]))].filter(Boolean);
return methods.length ? `Next.js API route: ${methods.join(", ")}` : "Next.js API route";
}
// Express/Fastify routes
const routeHits = content.match(/\.(get|post|put|patch|delete)\s*\(\s*['"`]/g);
if (routeHits && routeHits.length > 0) {
const methods = [...new Set(routeHits.map(r => r.match(/\.(get|post|put|patch|delete)/)?.[1]?.toUpperCase()))];
return cap(`API routes: ${methods.join(", ")} (${routeHits.length} endpoints)`);
}
// tRPC router
if (content.includes("createTRPCRouter") || content.includes("publicProcedure")) {
const procs = (content.match(/\.(query|mutation|subscription)\s*\(/g) || []).length;
return procs ? `tRPC router: ${procs} procedures` : "tRPC router";
}
// Zod schemas
if (content.includes("z.object") || content.includes("z.string")) {
const schemas = (content.match(/(?:export\s+)?(?:const|let)\s+(\w+)\s*=\s*z\./g) || [])
.map(s => s.match(/(?:const|let)\s+(\w+)/)?.[1]).filter(Boolean);
if (schemas.length)
return cap(`Zod schemas: ${schemas.slice(0, 4).join(", ")}${schemas.length > 4 ? ` + ${schemas.length - 4} more` : ""}`);
}
// Exports summary
const exports = (content.match(/export\s+(?:async\s+)?(?:function|class|const|interface|type|enum)\s+(\w+)/g) || [])
.map(e => e.match(/(\w+)$/)?.[1]).filter(Boolean);
if (exports.length > 0 && exports.length <= 5)
return `Exports ${exports.join(", ")}`;
if (exports.length > 5)
return cap(`Exports ${exports.slice(0, 4).join(", ")} + ${exports.length - 4} more`);
}
// ─── Python / Django / FastAPI / Flask ────────────────────
if (ext === ".py") {
// Django model
if (content.includes("models.Model")) {
const cls = content.match(/class\s+(\w+)\(.*models\.Model\)/);
const fields = (content.match(/^\s+\w+\s*=\s*models\.\w+/gm) || []).length;
return cap(`Model: ${cls?.[1] || "unknown"}, ${fields} fields`);
}
// FastAPI/Flask routes
if (content.includes("@router.") || content.includes("@app.")) {
const routes = (content.match(/@(?:router|app)\.(get|post|put|patch|delete)\s*\(/g) || []);
return cap(routes.length ? `API: ${routes.length} endpoints` : "API router");
}
// Pydantic
if (content.includes("BaseModel") && content.includes("Field(")) {
const cls = content.match(/class\s+(\w+)\(.*BaseModel\)/);
return cls ? `Pydantic: ${cls[1]}` : "Pydantic model";
}
// Celery
if (content.includes("@shared_task") || content.includes("@app.task")) {
const tasks = (content.match(/def\s+(\w+)/g) || []).map(m => m.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
return cap(tasks.length ? `Celery tasks: ${tasks.join(", ")}` : "Celery task");
}
// Generic
const pyClass = content.match(/class\s+(\w+)/);
const funcs = (content.match(/def\s+(\w+)/g) || []).map(f => f.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
if (pyClass && funcs.length > 0)
return cap(funcs.length > 4 ? `${pyClass[1]}: ${funcs.slice(0, 4).join(", ")} + ${funcs.length - 4} more` : `${pyClass[1]}: ${funcs.join(", ")}`);
if (funcs.length > 0)
return cap(funcs.slice(0, 4).join(", "));
}
// ─── Go ──────────────────────────────────────────────────
if (ext === ".go") {
const handlers = (content.match(/func\s+(\w+)\s*\(\s*\w+\s+http\.ResponseWriter/g) || [])
.map(m => m.match(/func\s+(\w+)/)?.[1]).filter(Boolean);
if (handlers.length)
return cap(`HTTP handlers: ${handlers.slice(0, 5).join(", ")}`);
const iface = content.match(/type\s+(\w+)\s+interface\s*\{/);
if (iface)
return `Interface: ${iface[1]}`;
const structM = content.match(/type\s+(\w+)\s+struct\s*\{/);
if (structM)
return `Struct: ${structM[1]}`;
const funcs = (content.match(/^func\s+(\w+)/gm) || []).map(m => m.match(/func\s+(\w+)/)?.[1]).filter(n => n && n[0] === n[0].toUpperCase());
if (funcs.length)
return cap(funcs.slice(0, 5).join(", "));
}
// ─── Rust ────────────────────────────────────────────────
if (ext === ".rs") {
const structM = content.match(/pub\s+struct\s+(\w+)/);
if (structM) {
const methods = (content.match(/pub\s+(?:async\s+)?fn\s+(\w+)/g) || []).map(m => m.match(/fn\s+(\w+)/)?.[1]).filter(Boolean);
return cap(methods.length ? `${structM[1]}: ${methods.slice(0, 4).join(", ")}` : `Struct: ${structM[1]}`);
}
const traitM = content.match(/pub\s+trait\s+(\w+)/);
if (traitM)
return `Trait: ${traitM[1]}`;
const enumM = content.match(/pub\s+enum\s+(\w+)/);
if (enumM)
return `Enum: ${enumM[1]}`;
const fns = (content.match(/pub\s+(?:async\s+)?fn\s+(\w+)/g) || []).map(m => m.match(/fn\s+(\w+)/)?.[1]).filter(Boolean);
if (fns.length)
return cap(fns.slice(0, 5).join(", "));
}
// ─── Java / Spring ───────────────────────────────────────
if (ext === ".java") {
const cls = content.match(/(?:public\s+)?class\s+(\w+)/);
const className = cls?.[1] || basename.replace(".java", "");
const annotations = (content.match(/@(RestController|Controller|Service|Repository|Component|Entity|Configuration)/g) || []).map(a => a.slice(1));
const mappings = (content.match(/@(?:Get|Post|Put|Patch|Delete|Request)Mapping/g) || []).length;
if (mappings)
return cap(`${annotations[0] || "Spring"}: ${className} (${mappings} endpoints)`);
if (annotations.length)
return `${annotations[0]}: ${className}`;
if (content.includes("@Entity"))
return `Entity: ${className}`;
const methods = (content.match(/public\s+(?:static\s+)?(?:\w+(?:<[\w,\s]+>)?)\s+(\w+)\s*\(/g) || [])
.map(m => m.match(/(\w+)\s*\(/)?.[1]).filter(n => n && n !== className);
if (methods.length)
return cap(`${className}: ${methods.slice(0, 4).join(", ")}`);
return className ? `Class: ${className}` : "";
}
// ─── Kotlin ──────────────────────────────────────────────
if (ext === ".kt" || ext === ".kts") {
const cls = content.match(/(?:data\s+)?class\s+(\w+)/);
if (content.match(/data\s+class/))
return `Data class: ${cls?.[1] || basename.replace(/\.kts?$/, "")}`;
if (content.includes("routing {"))
return "Ktor routing";
const fns = (content.match(/fun\s+(\w+)/g) || []).map(m => m.match(/fun\s+(\w+)/)?.[1]).filter(Boolean);
if (cls && fns.length)
return cap(`${cls[1]}: ${fns.slice(0, 4).join(", ")}`);
if (fns.length)
return cap(fns.slice(0, 5).join(", "));
}
// ─── C# / .NET ───────────────────────────────────────────
if (ext === ".cs") {
const cls = content.match(/(?:public\s+)?(?:partial\s+)?class\s+(\w+)(?:\s*:\s*(\w+))?/);
const className = cls?.[1] || basename.replace(".cs", "");
const parent = cls?.[2] || "";
if (parent === "Controller" || parent === "ControllerBase" || content.includes("[ApiController]")) {
const actions = (content.match(/\[Http(Get|Post|Put|Patch|Delete)\]/g) || []).map(a => a.match(/Http(\w+)/)?.[1]).filter(Boolean);
return cap(actions.length ? `API Controller: ${className} (${[...new Set(actions)].join(", ")})` : `Controller: ${className}`);
}
if (parent === "DbContext" || content.includes("DbSet<")) {
const sets = (content.match(/DbSet<(\w+)>/g) || []).map(s => s.match(/<(\w+)>/)?.[1]).filter(Boolean);
return cap(sets.length ? `DbContext: ${sets.join(", ")}` : `DbContext: ${className}`);
}
return className ? `Class: ${className}` : "";
}
// ─── Ruby / Rails ────────────────────────────────────────
if (ext === ".rb") {
const cls = content.match(/class\s+(\w+)(?:\s*<\s*(\w+(?:::\w+)?))?/);
const className = cls?.[1] || "";
const parent = cls?.[2] || "";
if (parent?.includes("Controller")) {
const actions = (content.match(/def\s+(index|show|new|create|edit|update|destroy|\w+)/g) || [])
.map(m => m.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
return cap(actions.length ? `Controller: ${actions.join(", ")}` : `Controller: ${className}`);
}
if (parent === "ApplicationRecord" || parent === "ActiveRecord::Base")
return `Model: ${className}`;
if (basename.match(/^\d{14}_/)) {
const create = content.match(/create_table\s+:(\w+)/);
return create ? `Migration: create ${create[1]}` : "Database migration";
}
const methods = (content.match(/def\s+(\w+)/g) || []).map(m => m.match(/def\s+(\w+)/)?.[1]).filter(n => n && !n.startsWith("_"));
if (cls && methods.length)
return cap(`${className}: ${methods.slice(0, 4).join(", ")}`);
}
// ─── Swift ───────────────────────────────────────────────
if (ext === ".swift") {
if (content.includes(": View") || content.includes("some View")) {
const name = content.match(/struct\s+(\w+)\s*:\s*View/);
return name ? `SwiftUI view: ${name[1]}` : "SwiftUI view";
}
const proto = content.match(/protocol\s+(\w+)/);
if (proto)
return `Protocol: ${proto[1]}`;
const struct = content.match(/(?:public\s+)?struct\s+(\w+)/);
const cls = content.match(/(?:public\s+)?class\s+(\w+)/);
const name = struct?.[1] || cls?.[1] || "";
if (name)
return `${struct ? "Struct" : "Class"}: ${name}`;
}
// ─── Dart / Flutter ──────────────────────────────────────
if (ext === ".dart") {
if (content.includes("StatefulWidget") || content.includes("StatelessWidget")) {
const name = content.match(/class\s+(\w+)\s+extends\s+(?:Stateful|Stateless)Widget/);
return name ? `${content.includes("StatefulWidget") ? "Stateful" : "Stateless"} widget: ${name[1]}` : "Flutter widget";
}
const cls = content.match(/class\s+(\w+)/);
if (cls)
return `Class: ${cls[1]}`;
}
// ─── Vue / Svelte / Astro ────────────────────────────────
if (ext === ".vue") {
const name = content.match(/name:\s*['"]([^'"]+)['"]/);
const setup = content.includes("<script setup");
const parts = [];
if (name)
parts.push(name[1]);
if (setup)
parts.push("setup");
return cap(parts.length ? `Vue: ${parts.join(", ")}` : "Vue component");
}
if (ext === ".svelte")
return `Svelte: ${basename.replace(".svelte", "")}`;
if (ext === ".astro")
return `Astro: ${basename.replace(".astro", "")}`;
// ─── CSS / SCSS / Less ───────────────────────────────────
if (ext === ".css" || ext === ".scss" || ext === ".less") {
const rules = (content.match(/^[.#@][^\n{]+/gm) || []).length;
const vars = (content.match(/--[\w-]+\s*:/g) || []).length;
const parts = [];
if (rules)
parts.push(`${rules} rules`);
if (vars)
parts.push(`${vars} vars`);
return cap(parts.length ? `Styles: ${parts.join(", ")}` : "Stylesheet");
}
// ─── SQL ─────────────────────────────────────────────────
if (ext === ".sql") {
const creates = (content.match(/CREATE\s+TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?[`"']?(\w+)/gi) || [])
.map(m => m.match(/(?:TABLE\s+(?:IF\s+NOT\s+EXISTS\s+)?)([`"']?\w+)/i)?.[1]?.replace(/[`"']/g, "")).filter(Boolean);
if (creates.length)
return cap(`SQL: tables: ${creates.slice(0, 4).join(", ")}`);
}
// ─── Proto / GraphQL ─────────────────────────────────────
if (ext === ".proto") {
const msgs = (content.match(/message\s+(\w+)/g) || []).map(m => m.match(/message\s+(\w+)/)?.[1]).filter(Boolean);
const services = (content.match(/service\s+(\w+)/g) || []).map(m => m.match(/service\s+(\w+)/)?.[1]).filter(Boolean);
const parts = [];
if (msgs.length)
parts.push(`messages: ${msgs.slice(0, 3).join(", ")}`);
if (services.length)
parts.push(`services: ${services.join(", ")}`);
return cap(parts.length ? `Proto: ${parts.join(", ")}` : "");
}
if (ext === ".graphql" || ext === ".gql") {
const types = (content.match(/type\s+(\w+)/g) || []).map(m => m.match(/type\s+(\w+)/)?.[1]).filter(Boolean);
return cap(types.length ? `GraphQL: types: ${types.slice(0, 4).join(", ")}` : "GraphQL schema");
}
// ─── YAML ────────────────────────────────────────────────
if (ext === ".yaml" || ext === ".yml") {
if (content.includes("runs-on:")) {
const name = content.match(/^name:\s*(.+)$/m);
return cap(name ? `CI: ${name[1].trim()}` : "GitHub Actions workflow");
}
if (content.includes("apiVersion:") && content.includes("kind:")) {
const kind = content.match(/kind:\s*(\w+)/);
return cap(kind ? `K8s ${kind[1]}` : "Kubernetes manifest");
}
if (content.includes("services:") && (basename.includes("docker") || basename.includes("compose"))) {
const services = (content.match(/^\s{2}\w+:/gm) || []).length;
return `Docker Compose: ${services} services`;
}
}
// ─── TOML ────────────────────────────────────────────────
if (ext === ".toml") {
const desc = content.match(/^description\s*=\s*"([^"]+)"/m);
if (desc)
return cap(desc[1]);
}
// ─── Elixir ──────────────────────────────────────────────
if (ext === ".ex" || ext === ".exs") {
const mod = content.match(/defmodule\s+([\w.]+)/);
if (content.includes("Phoenix.LiveView"))
return cap(mod ? `LiveView: ${mod[1]}` : "Phoenix LiveView");
if (content.includes("Controller"))
return cap(mod ? `Phoenix controller: ${mod[1]}` : "Phoenix controller");
const fns = (content.match(/def\s+(\w+)/g) || []).map(m => m.match(/def\s+(\w+)/)?.[1]).filter(Boolean);
if (mod && fns.length)
return cap(`${mod[1]}: ${fns.slice(0, 4).join(", ")}`);
if (mod)
return mod[1];
}
// ─── Lua ─────────────────────────────────────────────────
if (ext === ".lua") {
const fns = (content.match(/function\s+(?:\w+[.:])?(\w+)/g) || []).map(m => m.match(/(\w+)\s*$/)?.[1]).filter(Boolean);
if (fns.length)
return cap(fns.slice(0, 5).join(", "));
}
// ─── Zig ─────────────────────────────────────────────────
if (ext === ".zig") {
const fns = (content.match(/pub\s+fn\s+(\w+)/g) || []).map(m => m.match(/fn\s+(\w+)/)?.[1]).filter(Boolean);
if (fns.length)
return cap(fns.slice(0, 5).join(", "));
}
// Last resort
const declM = content.match(/(?:function|class|const|interface|type|enum)\s+(\w+)/);
if (declM) {
const name = declM[1];
const methods = (content.match(/(?:public\s+)?(?:async\s+)?(?:function\s+|(?:get|set)\s+)(\w+)\s*\(/g) || [])
.map(m => m.match(/(\w+)\s*\(/)?.[1]).filter(n => n && n !== name && n !== "__construct" && n !== "constructor");
if (methods.length > 0 && methods.length <= 5)
return cap(`${name}: ${methods.join(", ")}`);
if (methods.length > 5)
return cap(`${name}: ${methods.slice(0, 3).join(", ")} + ${methods.length - 3} more`);
return `Declares ${name}`;
}
return "";
}
export function estimateTokens(text, type = "mixed") {
const ratio = type === "code" ? 3.5 : type === "prose" ? 4.0 : 3.75;
return Math.ceil(text.length / ratio);
}
export function timestamp() {
return new Date().toISOString();
}
export function timeShort() {
const d = new Date();
return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
}
export function readStdin() {
return new Promise((resolve) => {
const chunks = [];
process.stdin.on("data", (chunk) => chunks.push(chunk));
process.stdin.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
// If no stdin data after 4s, resolve with whatever we have so far.
// On Windows, stdin delivery from Claude Code hooks can be slow.
setTimeout(() => resolve(chunks.length ? Buffer.concat(chunks).toString("utf-8") : "{}"), 4000);
});
}
export function normalizePath(p) {
return p.replace(/\\/g, "/");
}
//# sourceMappingURL=shared.js.map

147
.wolf/hooks/stop.js Normal file
View file

@ -0,0 +1,147 @@
import * as fs from "node:fs";
import * as path from "node:path";
import { getWolfDir, ensureWolfDir, readJSON, writeJSON, appendMarkdown, timeShort } from "./shared.js";
async function main() {
ensureWolfDir();
const wolfDir = getWolfDir();
const hooksDir = path.join(wolfDir, "hooks");
const sessionFile = path.join(hooksDir, "_session.json");
const session = readJSON(sessionFile, {
session_id: "",
started: "",
files_read: {},
files_written: [],
edit_counts: {},
anatomy_hits: 0,
anatomy_misses: 0,
repeated_reads_warned: 0,
cerebrum_warnings: 0,
stop_count: 0,
});
session.stop_count++;
// Only write to ledger if there's been activity
const readCount = Object.keys(session.files_read).length;
const writeCount = session.files_written.length;
if (readCount === 0 && writeCount === 0) {
writeJSON(sessionFile, session);
process.exit(0);
return;
}
// Check for files edited many times without a buglog entry
checkForMissingBugLogs(wolfDir, session);
// Check if cerebrum was updated this session (it should be if there were edits)
checkCerebrumFreshness(wolfDir, session);
// Build session entry for ledger
const reads = Object.entries(session.files_read).map(([file, data]) => ({
file,
tokens_estimated: data.tokens,
was_repeated: data.count > 1,
anatomy_had_description: false, // simplified
}));
const writes = session.files_written.map((w) => ({
file: w.file,
tokens_estimated: w.tokens,
action: w.action,
}));
const inputTokens = reads.reduce((sum, r) => sum + r.tokens_estimated, 0);
const outputTokens = writes.reduce((sum, w) => sum + w.tokens_estimated, 0);
const sessionEntry = {
id: session.session_id,
started: session.started,
ended: new Date().toISOString(),
reads,
writes,
totals: {
input_tokens_estimated: inputTokens,
output_tokens_estimated: outputTokens,
reads_count: readCount,
writes_count: writeCount,
repeated_reads_blocked: session.repeated_reads_warned,
anatomy_lookups: session.anatomy_hits,
},
};
// Update token-ledger.json
const ledgerPath = path.join(wolfDir, "token-ledger.json");
const ledger = readJSON(ledgerPath, {
version: 1,
created_at: "",
lifetime: {
total_tokens_estimated: 0,
total_reads: 0,
total_writes: 0,
total_sessions: 0,
anatomy_hits: 0,
anatomy_misses: 0,
repeated_reads_blocked: 0,
estimated_savings_vs_bare_cli: 0,
},
sessions: [],
daemon_usage: [],
waste_flags: [],
optimization_report: { last_generated: null, patterns: [] },
});
ledger.sessions.push(sessionEntry);
ledger.lifetime.total_reads += readCount;
ledger.lifetime.total_writes += writeCount;
ledger.lifetime.total_tokens_estimated += inputTokens + outputTokens;
ledger.lifetime.anatomy_hits += session.anatomy_hits;
ledger.lifetime.anatomy_misses += session.anatomy_misses;
ledger.lifetime.repeated_reads_blocked += session.repeated_reads_warned;
// Estimate savings: anatomy hits save ~200 tokens each, repeated reads blocked save their token count
const savedFromAnatomy = session.anatomy_hits * 200;
const savedFromRepeats = Object.values(session.files_read)
.filter((r) => r.count > 1)
.reduce((sum, r) => sum + r.tokens * (r.count - 1), 0);
ledger.lifetime.estimated_savings_vs_bare_cli += savedFromAnatomy + savedFromRepeats;
writeJSON(ledgerPath, ledger);
// Write a session summary line to memory.md if there was meaningful activity
if (writeCount > 0) {
try {
const uniqueFiles = new Set(session.files_written.map(w => path.basename(w.file)));
const fileList = [...uniqueFiles].slice(0, 5).join(", ");
const memoryPath = path.join(wolfDir, "memory.md");
appendMarkdown(memoryPath, `| ${timeShort()} | Session end: ${writeCount} writes across ${uniqueFiles.size} files (${fileList}) | ${readCount} reads | ~${inputTokens + outputTokens} tok |\n`);
}
catch { }
}
writeJSON(sessionFile, session);
process.exit(0);
}
/**
* Check if files were edited multiple times but buglog.json wasn't updated.
* Emit a stderr reminder so Claude sees it in the next turn.
*/
function checkForMissingBugLogs(wolfDir, session) {
if (!session.edit_counts)
return;
const multiEditFiles = Object.entries(session.edit_counts)
.filter(([, count]) => count >= 3)
.map(([file]) => path.basename(file));
if (multiEditFiles.length === 0)
return;
// Check if buglog was written to this session
const buglogWritten = session.files_written.some(w => w.file.includes("buglog.json"));
if (!buglogWritten) {
process.stderr.write(`⚠️ OpenWolf: Files edited 3+ times this session (${multiEditFiles.join(", ")}) but buglog.json was not updated. If you fixed bugs, please log them.\n`);
}
}
/**
* Check if cerebrum.md was updated recently. If it hasn't been updated in
* a while and there was significant activity, emit a gentle reminder.
*/
function checkCerebrumFreshness(wolfDir, session) {
const cerebrumPath = path.join(wolfDir, "cerebrum.md");
try {
const stat = fs.statSync(cerebrumPath);
const hoursSinceUpdate = (Date.now() - stat.mtimeMs) / (1000 * 60 * 60);
// If cerebrum hasn't been updated in 24h+ and there were significant writes
if (hoursSinceUpdate > 24 && session.files_written.length >= 3) {
process.stderr.write(`💡 OpenWolf: cerebrum.md hasn't been updated in ${Math.floor(hoursSinceUpdate)}h. Did you learn any user preferences, conventions, or gotchas this session? Consider updating .wolf/cerebrum.md.\n`);
}
}
catch {
// cerebrum.md doesn't exist, that's ok
}
}
main().catch(() => process.exit(0));
//# sourceMappingURL=stop.js.map

9
.wolf/identity.md Normal file
View file

@ -0,0 +1,9 @@
# Identity
- **Name:** persistent_ha
- **Role:** AI development assistant for persistent_ha
- **Tone:** Direct, concise, technically precise
- **Constraints:**
- Never modify .env or secret files without explicit user confirmation
- Never delete files without explicit user confirmation
- Always explain why before making architectural changes

1609
.wolf/memory.md Normal file

File diff suppressed because it is too large Load diff

597
.wolf/reframe-frameworks.md Normal file
View file

@ -0,0 +1,597 @@
# OpenWolf Reframe — UI Framework Knowledge Base
When the user asks to change, pick, migrate, or "reframe" their UI framework, use this file to guide the conversation and generate the right migration prompt.
## Decision Questions
Ask these in order. Stop early if the answer narrows to 1-2 frameworks.
1. **What framework/library does your project currently use?** (React, Vue, Svelte, plain HTML, etc.)
2. **What's your priority?** (a) Stunning animations, (b) Fast setup / ship quickly, (c) Full design control, (d) Accessibility-first, (e) Business/enterprise look
3. **Do you use Tailwind CSS already?** (Yes → most options work. No → Chakra UI or DaisyUI are easier without it)
4. **Light mode, dark mode, or both?**
5. **Is this a landing page, a dashboard/app, or both?**
## Quick Selection Guide
| Priority | Recommended |
|----------|-------------|
| Fastest setup | DaisyUI, Preline UI |
| Stunning animations | Aceternity UI, Magic UI |
| Maximum control | shadcn/ui, Headless UI |
| Most free components | Origin UI (400+) |
| Multi-framework (React + Vue + Svelte) | Park UI, DaisyUI, Flowbite |
| AI product aesthetic | Cult UI |
| Polished defaults + accessibility | HeroUI, Chakra UI |
| Business/enterprise | Flowbite, shadcn/ui |
## Comparison Matrix
| Framework | Styling | Animation | Setup | Best For | Cost |
|-----------|---------|-----------|-------|----------|------|
| shadcn/ui | Tailwind + Radix | Minimal | Medium | Full apps, dashboards | Free |
| Aceternity UI | Tailwind + Framer Motion | Heavy, cinematic | Easy (copy-paste) | Animated landing pages | Free / Pro |
| Magic UI | Tailwind + Motion | Purposeful, polished | Easy (shadcn CLI) | SaaS marketing | Free / Pro |
| DaisyUI | Tailwind plugin | CSS transitions | Very easy | Rapid prototyping | Free |
| HeroUI | Tailwind + React Aria | Smooth built-in | Easy | React apps, accessible | Free |
| Chakra UI | CSS-in-JS (Emotion) | Built-in transitions | Easy | Themed React apps | Free |
| Flowbite | Tailwind plugin | CSS transitions | Very easy | Multi-framework, business | Free / Pro |
| Preline UI | Tailwind plugin | CSS + minimal JS | Very easy | Speed-focused builds | Free / Pro |
| Park UI | Tailwind + Ark UI | Minimal | Medium | Multi-framework, design systems | Free |
| Origin UI | Tailwind + shadcn | Minimal | Easy (copy-paste) | Max component variety | Free |
| Headless UI | Custom Tailwind | Custom (Transition) | Medium-Hard | Full design control | Free |
| Cult UI | Tailwind + shadcn | Modern, subtle | Medium | AI apps, full-stack | Free |
---
## Framework Prompts
After the user selects a framework, use the corresponding prompt below. **Adapt it to the user's actual project** — replace generic references with their real file structure, existing routes, and components from `.wolf/anatomy.md`.
---
### shadcn/ui
**Stack:** React, Tailwind CSS, Radix UI
**Install:** `npx shadcn@latest init`
**Site:** ui.shadcn.com
```
Build a modern, minimalist landing page using Next.js 14+ (App Router), TypeScript, Tailwind CSS, and shadcn/ui components.
ARCHITECTURE:
- Use Next.js App Router with server components by default, client components only when interactivity is needed
- Organize: /app for routes, /components/ui for shadcn primitives, /components/sections for page sections, /lib for utilities
- Use CSS variables for theming via shadcn's built-in system
DESIGN PRINCIPLES:
- Minimalist with generous whitespace (py-24 to py-32 section spacing)
- Max-width container (max-w-6xl mx-auto) with responsive padding
- Typography hierarchy: text-5xl/6xl hero → text-3xl section titles → text-lg body
- Muted color palette with ONE bold accent color for CTAs
- Subtle micro-interactions on hover (scale, opacity transitions)
- Dark mode support via shadcn's theme provider
SECTIONS TO BUILD:
1. Navbar — sticky, transparent-to-solid on scroll, logo left, nav center, CTA button right
2. Hero — large headline (max 8 words), subtext (max 2 lines), primary CTA button + secondary ghost button, optional hero image or abstract illustration
3. Social proof — logo cloud of 5-6 partner/client logos in grayscale
4. Features — 3-column bento grid with icon, title, description per card using shadcn Card component
5. How it works — 3-step numbered process with connecting line
6. Testimonials — carousel or grid of 3 testimonial cards with avatar, quote, name, role
7. Pricing — 3-tier pricing table using shadcn Card, highlight the recommended plan with ring/border accent
8. FAQ — accordion using shadcn Accordion component
9. CTA — full-width section with headline, subtext, and primary action button
10. Footer — 4-column grid (brand, product links, company links, legal), copyright at bottom
COMPONENT USAGE:
- Button (primary, secondary, ghost, outline variants)
- Card, CardHeader, CardContent, CardFooter
- Accordion, AccordionItem, AccordionTrigger, AccordionContent
- Badge for labels/tags
- Separator for dividers
- NavigationMenu for navbar
CODE QUALITY:
- Fully responsive (mobile-first)
- Semantic HTML (section, nav, main, footer)
- Accessible (proper aria labels, keyboard navigation)
- Performant (lazy load images, optimize fonts via next/font)
- Clean component separation (each section is its own component)
```
---
### Aceternity UI
**Stack:** React, Next.js, Tailwind CSS, Framer Motion
**Install:** Copy-paste or `npx shadcn@latest add [component]`
**Site:** ui.aceternity.com
```
Build a visually stunning, animation-rich landing page using Next.js 14+, TypeScript, Tailwind CSS, and Aceternity UI components with Framer Motion.
ARCHITECTURE:
- Next.js App Router with client components for animated sections
- Organize: /app, /components/aceternity (copied components), /components/sections, /lib
- Install Framer Motion for animation engine
DESIGN PRINCIPLES:
- Dark theme as primary (bg-black/bg-slate-950 backgrounds)
- Dramatic contrast with glowing accents (cyan, purple, or emerald)
- Layered depth via gradients, glows, and glassmorphism
- Cinematic scroll-triggered animations with staggered reveals
- Generous padding (py-32+), large typography (text-6xl/7xl hero)
- Noise/grain texture overlays for visual richness
SECTIONS TO BUILD WITH ACETERNITY COMPONENTS:
1. Navbar — floating navbar with backdrop blur, animated on scroll
2. Hero — use Aceternity's "Spotlight" or "Lamp" effect as background, massive headline with TextGenerateEffect or TypewriterEffect, gradient text on key words, two CTAs with HoverBorderGradient buttons
3. Logo cloud — use InfiniteMovingCards for auto-scrolling partner logos
4. Features — BentoGrid layout with animated cards, each card has BackgroundGradient or CardSpotlight hover effect
5. Product showcase — use Aceternity's 3DCard or ParallaxScroll for product screenshots/mockups
6. Testimonials — AnimatedTestimonials or InfiniteMovingCards with quote, avatar, name
7. How it works — use TracingBeam component for a connected step-by-step flow
8. Pricing — dark cards with GlowingStarsBackground, highlighted tier has animated border (MovingBorder)
9. CTA — full-width with BackgroundBeams or Meteors effect behind headline and button
10. Footer — clean 4-column layout with subtle separator
ANIMATION GUIDELINES:
- Page load: stagger hero elements (title → subtitle → buttons) with 0.15s delay each
- Scroll: use Framer Motion's whileInView for reveal animations (fade up + slight scale)
- Hover: cards lift (translateY -4px) with glow intensification
- Keep animations performant (use transform/opacity only, will-change where needed)
- Respect prefers-reduced-motion
CODE QUALITY:
- Mobile-first responsive design
- Lazy load heavy animation components
- Each section is a standalone component file
- Accessible despite heavy animation (aria labels, focus states)
```
---
### Magic UI
**Stack:** React, TypeScript, Tailwind CSS, Motion (Framer Motion)
**Install:** `npx shadcn@latest add [component]` (shadcn CLI compatible)
**Site:** magicui.design
```
Build a polished, trend-forward SaaS landing page using Next.js 14+, TypeScript, Tailwind CSS, and Magic UI animated components.
ARCHITECTURE:
- Next.js App Router, server components where possible
- Magic UI components installed via shadcn CLI into /components/magicui
- Section components in /components/sections
- Shared config in /lib (cn utility, site config)
DESIGN PRINCIPLES:
- Clean and modern, inspired by Linear/Vercel aesthetic
- Light or dark mode with smooth toggle transition
- Restrained animation — purposeful motion that guides attention, not distracts
- High contrast text, muted backgrounds (slate-50 light / slate-950 dark)
- Monospace or geometric sans-serif for headings, clean sans for body
- ONE signature color (blue-500, violet-500, or emerald-500) for accents
SECTIONS TO BUILD WITH MAGIC UI COMPONENTS:
1. Navbar — clean horizontal nav, use MagicUI ShimmerButton for primary CTA
2. Hero — large headline with AnimatedGradientText on key phrase, NumberTicker for a live stat ("10,000+ users"), DotPattern or GridPattern as subtle background, two buttons (ShimmerButton primary + outline secondary)
3. Social proof — Marquee component for auto-scrolling logos horizontally
4. Features — use MagicCard components in a 3-column grid, each with icon + title + description, subtle hover glow effect
5. Bento showcase — BentoGrid layout showing product capabilities, mix of large and small tiles with different content types (text, mini-demos, stats)
6. Metrics — AnimatedList showing live activity feed OR NumberTicker showing key stats (users, uptime, speed)
7. Testimonials — Marquee of testimonial cards (vertical or horizontal scroll)
8. Pricing — 3 tiers with clean card design, NeonGradientCard for featured plan, BorderBeam on hover
9. CTA — centered layout with AnimatedShinyText headline, Particles or Meteors background effect
10. Footer — minimal 3-4 column grid, muted text, social icons
ANIMATION STRATEGY:
- Use BlurIn / FadeIn for section entrance animations
- NumberTicker for any statistics or metrics
- Marquee for any horizontally scrolling content
- Keep transitions under 600ms, use ease-out curves
- Mobile: reduce or disable particle/meteor effects for performance
CODE QUALITY:
- Fully responsive with mobile breakpoints
- Semantic HTML throughout
- next/font for optimized typography
- Image optimization via next/image
- Component-per-section architecture
```
---
### DaisyUI
**Stack:** Tailwind CSS plugin (framework-agnostic)
**Install:** `npm i -D daisyui` + add to tailwind.config
**Site:** daisyui.com
```
Build a clean, professional landing page using Tailwind CSS and DaisyUI component classes.
ARCHITECTURE:
- Framework of choice with Tailwind CSS + DaisyUI plugin
- Use DaisyUI's semantic class system (btn, card, hero, navbar, etc.)
- Select a DaisyUI theme (or create custom) in tailwind.config.js
- Organize by sections in /components/sections
DESIGN PRINCIPLES:
- Pick ONE DaisyUI theme as base (e.g., "winter" for clean light, "night" for dark, "corporate" for professional)
- Customize theme colors in tailwind.config to match brand
- Minimalist layout with clean spacing (p-8 to p-16 sections)
- Let DaisyUI's built-in design system handle consistency
- Typography via DaisyUI's prose class for content sections
SECTIONS TO BUILD WITH DAISYUI CLASSES:
1. Navbar — "navbar" component with "dropdown" for mobile menu, "btn btn-primary" for CTA
2. Hero — "hero" component with "hero-content", large headline, subtext, "btn btn-primary" + "btn btn-ghost"
3. Social proof — logo row using "flex" with "opacity-50 grayscale" on images
4. Features — "card" components in a responsive grid, each with "card-body", uses "bg-base-200" for contrast
5. Stats — "stats" component with "stat" items showing key metrics
6. Steps — "steps" component (horizontal on desktop, vertical on mobile)
7. Testimonials — "chat" component styled as testimonials OR "card" grid with "avatar"
8. Pricing — "card" components with "badge badge-primary" for recommended plan
9. FAQ — "collapse" or "collapse-plus" components for expandable Q&A
10. Footer — "footer" component with 3-4 columns, "footer-title" for headings
CODE QUALITY:
- Fully responsive using Tailwind breakpoints
- Zero custom CSS needed (DaisyUI handles it)
- Accessible by default (DaisyUI follows ARIA patterns)
- Fast load times (CSS-only, no JS shipped)
- Works with any framework or plain HTML
```
---
### HeroUI (formerly NextUI)
**Stack:** React, Tailwind CSS, React Aria
**Install:** `npm i @heroui/react`
**Site:** heroui.com
```
Build an elegant, accessible landing page using Next.js 14+, TypeScript, Tailwind CSS, and HeroUI components.
ARCHITECTURE:
- Next.js App Router with HeroUI Provider wrapping the app
- Import only needed components from @heroui/react (tree-shakeable)
- Organize: /app, /components/sections, /lib
- Use HeroUI's built-in theme system for consistent styling
DESIGN PRINCIPLES:
- Soft, modern aesthetic with rounded corners and smooth surfaces
- Light mode primary with dark mode support via HeroUI's theme toggle
- Subtle shadows and blur effects (HeroUI's built-in elevation system)
- Clean sans-serif typography, generous line heights
- Smooth transitions on all interactive elements (HeroUI provides these by default)
SECTIONS TO BUILD WITH HEROUI COMPONENTS:
1. Navbar — HeroUI Navbar with NavbarBrand, NavbarContent, NavbarItem, NavbarMenuToggle for mobile
2. Hero — large headline, descriptive subtext, Button (color="primary" size="lg") + Button (variant="bordered")
3. Social proof — Avatar group for client logos or Chip components for trust badges
4. Features — Card components in grid, each with CardHeader, CardBody, use Divider between sections
5. Product demo — Tabs component to show different product views/features interactively
6. Testimonials — Card grid with User component (avatar + name + role) inside each card
7. Pricing — Card components with Chip for "Popular" badge, Listbox for feature lists
8. FAQ — Accordion component with AccordionItem for each question
9. CTA — centered section with Input + Button combo for email capture
10. Footer — clean grid with Link components, Divider above copyright
CODE QUALITY:
- Fully responsive with HeroUI's responsive props
- Accessible by default (React Aria foundation)
- Server-side rendering compatible
- Optimized bundle (import individual components)
- TypeScript strict mode
```
---
### Chakra UI
**Stack:** React, Emotion (CSS-in-JS)
**Install:** `npm i @chakra-ui/react`
**Site:** chakra-ui.com
```
Build a clean, accessible landing page using Next.js 14+, TypeScript, and Chakra UI v3.
ARCHITECTURE:
- Next.js App Router with ChakraProvider at root
- Use Chakra's design tokens and theme extension for brand customization
- Organize: /app, /components/sections, /theme (custom theme config)
DESIGN PRINCIPLES:
- Clean and warm aesthetic using Chakra's polished defaults
- Consistent spacing using Chakra's space scale (4, 8, 12, 16, 20, 24)
- Typography: use Chakra's Heading and Text components
- Color mode support (light/dark) via useColorMode hook
- Accessible-first: every component follows WAI-ARIA by default
SECTIONS TO BUILD WITH CHAKRA COMPONENTS:
1. Navbar — Flex with HStack, IconButton for mobile menu, Button for CTA
2. Hero — VStack centered, Heading size="4xl", Text, HStack with two Buttons
3. Social proof — HStack/Wrap of grayscale Image components
4. Features — SimpleGrid columns={{base:1, md:3}} of VStack cards with Icon, Heading, Text
5. Stats — Stat component group with StatLabel, StatNumber, StatHelpText
6. Testimonials — SimpleGrid of Card with Avatar, Text (quote), Heading (name)
7. Pricing — SimpleGrid of Card, Badge for "Popular", List for features
8. FAQ — Accordion with AccordionItem, AccordionButton, AccordionPanel
9. CTA — Box with bg gradient, VStack centered, Heading, Text, Button
10. Footer — SimpleGrid columns={{base:2, md:4}} with VStack, Heading, Link
THEME EXTENSION:
const theme = extendTheme({
colors: { brand: { 50: '...', 500: '...', 900: '...' } },
fonts: { heading: 'Your Font', body: 'Your Font' },
components: { Button: { defaultProps: { colorScheme: 'brand' } } }
})
CODE QUALITY:
- Fully responsive using Chakra's responsive array/object syntax
- Dark mode ready with useColorModeValue
- Accessible by default
- TypeScript throughout with proper prop types
```
---
### Flowbite
**Stack:** Tailwind CSS (React, Vue, Svelte, Angular)
**Install:** `npm i flowbite flowbite-react`
**Site:** flowbite.com
```
Build a professional, conversion-optimized landing page using Next.js 14+, Tailwind CSS, and Flowbite React components.
ARCHITECTURE:
- Next.js App Router with Flowbite React components
- Import from flowbite-react (Button, Card, Navbar, Footer, etc.)
- Flowbite Tailwind plugin in tailwind.config for interactive styles
DESIGN PRINCIPLES:
- Business-professional aesthetic (clean, trustworthy, conversion-focused)
- Cool neutral palette (slate/gray) with strong brand accent
- Focus on readability and clear information hierarchy
SECTIONS TO BUILD WITH FLOWBITE COMPONENTS:
1. Navbar — Flowbite Navbar with Navbar.Brand, Navbar.Toggle, Navbar.Collapse
2. Hero — Jumbotron-style, large Heading, Button group
3. Social proof — row of client logos
4. Features — Card components in responsive grid with icon badge, title, description
5. Content — alternating image + text rows
6. Testimonials — Blockquote or Card with Rating component for stars
7. Pricing — Card with List for features, Badge for highlighted plan
8. FAQ — Accordion component
9. Newsletter CTA — TextInput + Button in a styled Banner
10. Footer — Flowbite Footer with Footer.LinkGroup, Footer.Brand, Footer.Copyright
CODE QUALITY:
- Fully responsive (components are responsive by default)
- Accessible (follows WAI-ARIA)
- Works server-side with Next.js
- Production-ready component patterns
```
---
### Preline UI
**Stack:** Tailwind CSS (HTML-first, React/Vue plugins)
**Install:** `npm i preline` + add plugin to tailwind.config
**Site:** preline.co
```
Build a sleek, modern landing page using Next.js 14+, Tailwind CSS, and Preline UI components.
ARCHITECTURE:
- Next.js App Router with Preline's Tailwind plugin
- Use Preline's HTML component patterns (class-based, minimal JS)
- Import preline JS for interactive components (dropdowns, modals, accordions)
DESIGN PRINCIPLES:
- Modern SaaS aesthetic (clean lines, airy spacing, professional)
- Neutral base (white/slate) with vibrant accent for CTAs
- Large section padding (py-16 to py-24)
SECTIONS TO BUILD WITH PRELINE PATTERNS:
1. Navbar — header component with mega menu support, sticky on scroll
2. Hero — centered or split layout, large headline with gradient text, two CTA buttons
3. Social proof — logo ticker or static row with grayscale filter
4. Features — icon + text cards in 3-column grid
5. Tabs showcase — tab component showing different product features per tab
6. Testimonials — grid or slider of testimonial cards
7. Pricing — pricing table pattern with toggle for monthly/annual
8. Stats — counter section with large numbers + labels
9. FAQ — accordion component with smooth expand/collapse
10. CTA + Footer — email input + button CTA above multi-column footer
CODE QUALITY:
- Fully responsive with Tailwind breakpoints
- Accessible (follows ARIA standards)
- Lightweight (CSS + minimal JS)
- Works with any framework via HTML classes
```
---
### Park UI
**Stack:** React/Vue/Solid, Ark UI (headless), Tailwind CSS
**Install:** Copy-paste components (shadcn-style)
**Site:** park-ui.com
```
Build a refined, design-system-driven landing page using Next.js 14+, TypeScript, Tailwind CSS, and Park UI components.
ARCHITECTURE:
- Next.js App Router with Park UI components (Ark UI + Tailwind CSS)
- Copy-paste component installation (like shadcn)
- Organize: /app, /components/ui (Park UI primitives), /components/sections
DESIGN PRINCIPLES:
- Polished and systematic (design-system quality)
- Neutral, sophisticated palette with deliberate accent usage
- Consistent spacing and sizing via Park UI's token scale
- Light/dark mode via Park UI's built-in theme tokens
SECTIONS TO BUILD WITH PARK UI COMPONENTS:
1. Navbar — navigation components, clean horizontal layout, mobile drawer
2. Hero — Heading + Text with proper token sizing, Button pair (solid + outline)
3. Social proof — subtle logo row with muted styling
4. Features — Card components in grid with Icon, Heading, Text
5. Tabs showcase — Tabs component for interactive product feature display
6. Testimonials — Card-based layout with Avatar + quote
7. Pricing — Card grid with Badge for recommended tier
8. FAQ — Accordion component (Ark UI primitive, styled via Park UI)
9. CTA — centered section with Input + Button
10. Footer — grid layout with Link groups and Separator
CODE QUALITY:
- Fully responsive and accessible
- Ark UI headless primitives for rock-solid accessibility
- TypeScript strict mode
- Component-per-section architecture
```
---
### Origin UI
**Stack:** React, Tailwind CSS, shadcn/ui foundation
**Install:** Copy-paste (400+ free components)
**Site:** originui.com
```
Build a polished, component-rich landing page using Next.js 14+, TypeScript, Tailwind CSS, and Origin UI components (built on shadcn/ui).
ARCHITECTURE:
- Next.js App Router with shadcn/ui base + Origin UI component variants
- Copy-paste Origin UI components into /components/ui
- Origin UI extends shadcn patterns with 400+ variant options
DESIGN PRINCIPLES:
- Professional and versatile (many style variants per component)
- Pick a cohesive subset of variants that match your brand
- Consistent spacing and typography aligned with shadcn's token system
- Focus on component quality and consistency over flashy animation
SECTIONS TO BUILD:
1. Navbar — choose from Origin UI's multiple navbar variants
2. Hero — select a hero variant (centered, split, with background pattern)
3. Social proof — logo cloud variant
4. Features — pick feature section variant (grid, list, bento, with images)
5. Content — alternating image/text sections
6. Testimonials — choose testimonial variant (cards, quotes, carousel)
7. Pricing — select pricing table variant (simple, comparison, with toggle)
8. FAQ — accordion variant
9. CTA — pick CTA variant (centered, with input, split layout)
10. Footer — choose footer variant (simple, multi-column, with newsletter)
CODE QUALITY:
- Fully responsive (all components are mobile-first)
- Accessible (shadcn/ui + Radix UI foundation)
- TypeScript throughout
- Minimal dependencies
```
---
### Headless UI
**Stack:** React or Vue, Tailwind CSS (by Tailwind Labs)
**Install:** `npm i @headlessui/react`
**Site:** headlessui.com
```
Build a custom-designed, fully accessible landing page using Next.js 14+, TypeScript, Tailwind CSS, and Headless UI for interactive components.
ARCHITECTURE:
- Next.js App Router with Tailwind CSS for all styling
- Headless UI only for interactive behavior (menus, dialogs, tabs, disclosure, transitions)
- All visual design is custom Tailwind — Headless UI provides zero styling
- Organize: /app, /components/ui (custom-styled wrappers), /components/sections
DESIGN PRINCIPLES:
- Completely custom aesthetic — you control every pixel
- Minimalist, editorial-quality design (think Stripe, Linear, Vercel)
- Monochrome base with ONE signature accent color
- Large whitespace, precise typography, meticulous alignment
- Every interactive element is keyboard-navigable and screen-reader friendly
SECTIONS TO BUILD:
1. Navbar — custom with Headless UI Popover for dropdowns, Disclosure for mobile menu
2. Hero — pure Tailwind, dramatic typography, clean CTAs
3. Social proof — pure Tailwind logo row
4. Features — custom card grid, optional Tab component for tabbed showcase
5. Product showcase — TabGroup for interactive product views
6. Testimonials — pure Tailwind card grid
7. Pricing — RadioGroup for plan selection, Switch for monthly/annual toggle
8. FAQ — Disclosure component for accessible expand/collapse
9. CTA — pure Tailwind centered section
10. Footer — pure Tailwind grid layout
HEADLESS UI COMPONENTS TO LEVERAGE:
- Popover — navbar dropdowns
- Disclosure — mobile menu, FAQ items
- Dialog — modals/overlays
- Transition — smooth enter/leave animations
- RadioGroup — plan selection
- Switch — toggles
- TabGroup — tabbed content
CODE QUALITY:
- 100% accessible (Headless UI's core guarantee)
- Fully responsive custom Tailwind design
- Keyboard navigation on all interactive elements
- TypeScript with proper generics
- Zero design dependencies — total creative control
```
---
### Cult UI
**Stack:** React, Next.js, Tailwind CSS, shadcn/ui foundation
**Install:** Copy-paste or CLI
**Site:** cult-ui.com
```
Build a modern, AI-forward landing page using Next.js 14+, TypeScript, Tailwind CSS, and Cult UI components.
ARCHITECTURE:
- Next.js App Router with Cult UI components (extends shadcn/ui)
- Full-stack ready (Cult UI includes backend-aware patterns)
- Use Cult UI's AI-specific blocks if building an AI product
DESIGN PRINCIPLES:
- Tech-forward, developer-centric aesthetic
- Dark mode primary with high-contrast accents
- Modern gradients and subtle glow effects
- Clean code-like typography (mono fonts for accents)
- Purposeful animation that communicates AI/tech sophistication
SECTIONS TO BUILD:
1. Navbar — modern floating nav with glass effect
2. Hero — bold headline with animated text effect, live demo or interactive element, gradient mesh background
3. Features — AI blocks for feature cards, animated icons or micro-interactions
4. Live demo — interactive component showing product in action
5. Code showcase — syntax-highlighted code examples using Cult UI's code block components
6. Testimonials — modern card layout with subtle animation
7. Pricing — clean tier comparison with feature matrix
8. Open source / community — GitHub stars counter, contributor avatars
9. CTA — compelling action section with email input or waitlist signup
10. Footer — developer-friendly footer with docs links, API reference, status page
CODE QUALITY:
- Full-stack patterns (API routes + client components)
- TypeScript strict mode throughout
- Accessible interactive components
- Production-ready architecture patterns
```

4
.wolf/suggestions.json Normal file
View file

@ -0,0 +1,4 @@
{
"suggestions": [],
"generated_at": null
}

107369
.wolf/token-ledger.json Normal file

File diff suppressed because it is too large Load diff