robot_lab-am 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: cd50cc952ad3b357990770041acd1bcce2dd0de11ecba0074e5affb1d167f555
4
+ data.tar.gz: 5e60e4ecbd1eff84b5998d63fea8f64387f7f546f7e666268481877ccf055626
5
+ SHA512:
6
+ metadata.gz: 3e31eedae9a07189737093c35be19575d42a3307ef7523ed2221de5f08de0244144e33bdb1f8481875a360cfa6ac71f1ef4f07a6529f1978df59f7ade928def7
7
+ data.tar.gz: b512f414d2f4b1538ee11543dfdcbf64a6a4c161096feef9968b4e0359f33d56772a24da549374371e1de1fc2fb4f5aa29fc1b56365f584cc17ccc31cfc26c4c
data/.envrc ADDED
@@ -0,0 +1,6 @@
1
+ # robot_lab_project/robot_lab-am/.envrc
2
+
3
+ source_up
4
+
5
+ export RR=`pwd`
6
+ export BUNDLE_GEMFILE=Gemfile
@@ -0,0 +1,52 @@
1
+ name: Deploy Documentation to GitHub Pages
2
+ on:
3
+ push:
4
+ branches:
5
+ - main
6
+ - develop
7
+ paths:
8
+ - "docs/**"
9
+ - "mkdocs.yml"
10
+ - ".github/workflows/deploy-github-pages.yml"
11
+ workflow_dispatch:
12
+
13
+ permissions:
14
+ contents: write
15
+ pages: write
16
+ id-token: write
17
+
18
+ jobs:
19
+ deploy:
20
+ runs-on: ubuntu-latest
21
+ steps:
22
+ - name: Checkout code
23
+ uses: actions/checkout@v4
24
+ with:
25
+ fetch-depth: 0
26
+
27
+ - name: Setup Python
28
+ uses: actions/setup-python@v5
29
+ with:
30
+ python-version: 3.x
31
+
32
+ - name: Install dependencies
33
+ run: |
34
+ pip install mkdocs
35
+ pip install mkdocs-material
36
+ pip install mkdocs-macros-plugin
37
+ pip install mike
38
+
39
+ - name: Configure Git
40
+ run: |
41
+ git config --local user.email "action@github.com"
42
+ git config --local user.name "GitHub Action"
43
+
44
+ - name: Build MkDocs site
45
+ run: mkdocs build
46
+
47
+ - name: Deploy to GitHub Pages
48
+ uses: peaceiris/actions-gh-pages@v4
49
+ with:
50
+ github_token: ${{ secrets.GITHUB_TOKEN }}
51
+ publish_dir: ./site
52
+ keep_files: true
data/.loki ADDED
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+ # robot_lab-am — watches repo activity (git, terminal, Claude Code sessions) to infer intent.
3
+
4
+ import_up "repo_dev.loki"
5
+
6
+ class Tasks
7
+ @@gem_name ||= "robot_lab-am".freeze
8
+
9
+ header "robot_lab-am v#{gem_version} — activity monitor for RobotLab"
10
+ end
data/.rubocop.yml ADDED
@@ -0,0 +1 @@
1
+ inherit_from: ../.rubocop-base.yml
data/Archspec.rb ADDED
@@ -0,0 +1,58 @@
1
+ # robot_lab-am is a plain Ruby gem (RobotLab::Am), not a Rails app -- there is
2
+ # no app/ tree, no controllers/models/views, so the :rails preset doesn't
3
+ # apply. These are the actual boundaries described in ARCHITECTURE.md and
4
+ # CLAUDE.md.
5
+
6
+ component :watchers, in: "lib/robot_lab/am/watchers/**/*.rb"
7
+ component :event_log, in: "lib/robot_lab/am/event_log.rb"
8
+ component :inferrer, in: "lib/robot_lab/am/inferrer.rb"
9
+ component :intent_writer, in: "lib/robot_lab/am/intent_writer.rb"
10
+ component :cli, in: "lib/robot_lab/am/cli.rb"
11
+
12
+ # NOTE: every file here reopens `module RobotLab` (shared with the external
13
+ # robot_lab gem's own top-level module -- Inferrer calls RobotLab.build), so
14
+ # a `dependencies.forbid` rule keyed on components would treat that one bare
15
+ # `RobotLab.build` call as "depends on every component" -- every file
16
+ # "defines" the bare RobotLab constant. Naming the actual target constants
17
+ # instead of components sidesteps that ambiguity (same fix robot_lab's own
18
+ # Archspec.rb applies for the same reason).
19
+
20
+ # Watchers are pure activity collectors -- git/Claude Code/terminal in,
21
+ # Event out (see ARCHITECTURE.md "Ingestion & normalization"). They must not
22
+ # know how an Event gets stored, summarized, or reported; CLI is the only
23
+ # thing that wires collection to storage/inference.
24
+ watchers.cannot_reference_constants "RobotLab::Am::EventLog", "RobotLab::Am::Inferrer",
25
+ "RobotLab::Am::IntentWriter", "RobotLab::Am::CLI",
26
+ because: "a watcher's only job is producing Events -- what " \
27
+ "happens to an Event afterward is not its concern"
28
+
29
+ # Inferrer consumes a bounded window of Events (already collected) and
30
+ # produces an Intent; it must not reach back into how those Events were
31
+ # gathered, or which specific source produced them.
32
+ inferrer.cannot_reference_constants "RobotLab::Am::Watchers::GitWatcher",
33
+ "RobotLab::Am::Watchers::ClaudeWatcher",
34
+ "RobotLab::Am::Watchers::TerminalWatcher",
35
+ "RobotLab::Am::EventLog", "RobotLab::Am::IntentWriter",
36
+ "RobotLab::Am::CLI",
37
+ because: "Inferrer only knows the normalized Event shape, never " \
38
+ "a specific watcher, the log file, or how its own " \
39
+ "output is used"
40
+
41
+ # IntentWriter is a pure renderer: Intent in, front-matter + prose file out.
42
+ # It must not know how that Intent was produced.
43
+ intent_writer.cannot_reference_constants "RobotLab::Am::Watchers::GitWatcher",
44
+ "RobotLab::Am::Watchers::ClaudeWatcher",
45
+ "RobotLab::Am::Watchers::TerminalWatcher",
46
+ "RobotLab::Am::EventLog", "RobotLab::Am::Inferrer",
47
+ "RobotLab::Am::CLI",
48
+ because: "IntentWriter only serializes an already-built " \
49
+ "Intent -- it has no business asking how one was " \
50
+ "inferred"
51
+
52
+ # CLAUDE.md/ARCHITECTURE.md: GitWatcher subprocesses `git` and must never
53
+ # build a command via string interpolation (the same rule robot_lab-to's
54
+ # CommitManager enforces for its own git ops) -- Open3.capture3 with an
55
+ # explicit argv array only.
56
+ watchers.cannot_call :system, receiver: :none,
57
+ because: "git subprocess calls must go through Open3.capture3 with an " \
58
+ "argv array, never system()/backticks with an interpolated string"
data/CHANGELOG.md ADDED
@@ -0,0 +1,37 @@
1
+ ## [Unreleased]
2
+
3
+ - **Standalone: dropped the robot_lab dependency.** Inference is a one-shot
4
+ prompt, so `Inferrer` now calls RubyLLM directly (scoped
5
+ `RubyLLM.context`, `assume_model_exists: true`) instead of building a
6
+ `RobotLab::Robot`. Dependencies are now just `ruby_llm` + `myway_config`,
7
+ making the gem useful outside the robot_lab-to environment. New `api_key`
8
+ setting / `RLAM_API_KEY` (cascades to the provider's conventional
9
+ env var, then a placeholder for keyless local servers); the
10
+ `ROBOT_LAB_RUBY_LLM__OPENAI_API_BASE` coupling is gone. Injectable seam
11
+ renamed `robot:` -> `chat:`.
12
+
13
+ - Continuous daemon: `am start|stop|status` — forked/detached process with pid
14
+ file, per-tick heartbeat (`.robot_lab_am/heartbeat.json`), and debounced
15
+ inference (new-activity flag + `--debounce` window; failed inference warns
16
+ and retries instead of killing the daemon)
17
+ - launchd integration: `am install|uninstall` write/remove a per-repo agent
18
+ plist (`RunAtLoad` + `KeepAlive`) that supervises `am start --foreground`
19
+ - `Collector`: fingerprint-based dedupe against `events.jsonl`, so repeated
20
+ snapshots and daemon restarts never duplicate events
21
+ - `Redactor`: masks credential-shaped values (key/token/secret/password
22
+ assignments, bearer tokens, known token formats) before events are stored
23
+ or sent to the LLM
24
+ - `GitWatcher` no longer reports the gem's own `.robot_lab_am/` state
25
+ directory as uncommitted work
26
+ - `am snapshot` now reports only *new* events and infers over the accumulated
27
+ recent window of the event log
28
+ - MkDocs documentation site (Material theme) with GitHub Pages deploy workflow
29
+ - myway_config-based configuration (`RobotLab::Am::Config`, same pattern as
30
+ robot_lab-to): bundled defaults → `~/.config/robot_lab_am/robot_lab_am.yml` →
31
+ `RLAM_*` env vars → CLI flags. Tunables: provider, model, api_base,
32
+ interval, debounce, inference_window, terminal_log. `am --help` now shows
33
+ the effective interval/debounce
34
+
35
+ ## [0.1.0] - 2026-08-31
36
+
37
+ - Initial release
data/CLAUDE.md ADDED
@@ -0,0 +1,99 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What This Gem Does
6
+
7
+ `robot_lab-am` ("activity monitor") is a **standalone** gem (RobotLab-family
8
+ naming, but no robot_lab dependency — just `ruby_llm` + `myway_config`) that
9
+ watches a repo working directory — git activity, Claude Code session
10
+ transcripts, and terminal commands — and infers the current goal or
11
+ direction of work via a one-shot local-LLM call. The inferred intent seeds
12
+ [`robot_lab-to`](https://github.com/MadBomber/robot_lab-to)'s takeover runs with
13
+ real context instead of a cold objective string, but any tool can consume it.
14
+
15
+ **Status: complete v1 — one-shot pipeline and continuous daemon.**
16
+ `am snapshot` collects git/Claude Code/terminal activity, infers a goal via
17
+ a local LLM, and writes `.robot_lab_am/current_intent.md`. `am start`/`stop`/
18
+ `status` run the continuous daemon (detached, pid file + heartbeat,
19
+ debounced inference), and `am install`/`uninstall` manage a launchd agent
20
+ that supervises it. All verified end to end against a live daemon and a
21
+ real LM Studio model. See `ARCHITECTURE.md` for the component survey and
22
+ decisions.
23
+
24
+ ## Commands
25
+
26
+ ```bash
27
+ bundle exec rake test # all tests
28
+ bundle exec rake test_file[path] # single test file
29
+ asgard quality # all *_check gates in parallel (tests + coverage,
30
+ # rubocop, flog, flay, reek, fasterer, typos, ...)
31
+ asgard doc_builder # build the MkDocs site
32
+ asgard doc_server # serve docs locally
33
+ bin/console # IRB shell with gem loaded
34
+ bin/am --help # CLI help
35
+ bin/am snapshot [--repo PATH] # one-shot: collect + infer + write intent
36
+ bin/am start [--foreground] # start the daemon (detached by default)
37
+ bin/am stop|status # stop / inspect the daemon
38
+ bin/am install|uninstall # manage the launchd agent plist
39
+ ```
40
+
41
+ ## Decided so far (see ARCHITECTURE.md for detail)
42
+
43
+ - **Deployment model**: continuous background daemon, not an on-demand
44
+ snapshot — it should notice drift across sessions, not just seed one
45
+ takeover run.
46
+ - **Terminal capture**: `~/.bashrc_history` is a single global `HISTFILE`
47
+ shared across every terminal and repo, with no timestamps or cwd, so it
48
+ can't attribute a command to a repo or a point in time. Instead,
49
+ `~/.bashrc__activity_monitor` (outside this repo, in the user's dotfiles)
50
+ registers a `preexec_functions` hook that appends
51
+ `epoch.microseconds\tcwd\tcommand` to `~/.activity_monitor/terminal_activity.log`
52
+ on every command. It's tab-delimited, not JSON, because it runs in
53
+ `preexec` — *before* the command executes — so it must not fork a
54
+ subprocess or every command gets delayed. Parsing/structuring that file
55
+ is this gem's job on the read side.
56
+ - **Storage**: JSONL (`.robot_lab_am/events.jsonl` in the watched repo),
57
+ not SQLite — the only consumer is an LLM summarization pass, not ad
58
+ hoc queries.
59
+ - **Inference model**: local, not hosted. `Inferrer` defaults to
60
+ `provider: :openai` / `model: "qwen/qwen3.8-27b"` against LM Studio's
61
+ OpenAI-compatible server (`lms server start`, `localhost:1234/v1`) —
62
+ explicitly not Anthropic. Your own activity log shouldn't have to
63
+ leave the machine, or need an API key, just to be summarized.
64
+ - **RubyLLM directly, no robots** (2026-09-02): the inference is a
65
+ one-shot prompt, so `Inferrer` calls
66
+ `RubyLLM.context.chat(model:, provider:, assume_model_exists: true)`
67
+ in a scoped context (host apps' global RubyLLM config inherited,
68
+ never mutated). The gem no longer depends on `robot_lab` — deps are
69
+ `ruby_llm` + `myway_config` only, so it's usable outside the
70
+ robot_lab-to environment. Injectable seam is `chat:` (responds to
71
+ `#ask(prompt)` returning a message with `#content`).
72
+ - **Configuration**: `Config < MywayConfig::Base` (same pattern as
73
+ robot_lab-to). Cascade: `config/defaults.yml` →
74
+ `~/.config/robot_lab_am/robot_lab_am.yml` (flat keys) →
75
+ `RLAM_*` env vars → CLI flags / constructor keywords.
76
+ Settings: provider, model, api_base, interval, debounce,
77
+ inference_window, terminal_log. `Am.config` is the memoized
78
+ process-wide instance (`Am.reset_config!` in tests). Caveat: the
79
+ defaults.yml-backed ivars are assigned by `super()` — never pre-nil
80
+ them in `Config#initialize`.
81
+
82
+ - **Inference cadence**: debounced — the daemon polls watchers every
83
+ `--interval` seconds (default 15) but re-infers only when new events
84
+ have arrived since the last inference, and at most once per
85
+ `--debounce` seconds (default 300). A failed inference (LLM down) is
86
+ retried on the same debounce, and never kills the daemon.
87
+ - **Redaction**: `Redactor` masks credential-shaped values (key/token/
88
+ secret/password assignments, bearer tokens, well-known token formats)
89
+ before any event is stored or sent to the LLM.
90
+ - **Dedupe**: `Collector` fingerprints events against the existing
91
+ `events.jsonl`, so repeated snapshots/daemon restarts never duplicate
92
+ log lines. `wip` events dedupe on summary (their timestamps are
93
+ collection-time), everything else on timestamp + summary.
94
+
95
+ ## Testing
96
+
97
+ Minitest with SimpleCov (branch coverage tracked). Same conventions as the
98
+ other gems in this family — see the workspace-level `CLAUDE.md` and
99
+ `.claude/rules/rubocop.md`.
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 Dewayne VanHoozer
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,118 @@
1
+ # robot_lab-am
2
+
3
+ A standalone activity monitor that watches what's happening in a repo working
4
+ directory — git activity, Claude Code session transcripts, and terminal
5
+ commands — and infers the current goal/direction using a local LLM via
6
+ [RubyLLM](https://rubyllm.com). The inferred intent is written to a well-known
7
+ artifact (`.robot_lab_am/current_intent.md`) that any tool can read;
8
+ [`robot_lab-to`](https://github.com/MadBomber/robot_lab-to) uses it to seed a
9
+ takeover run with real context instead of a cold objective string, but nothing
10
+ here depends on the [RobotLab](https://github.com/MadBomber/robot_lab)
11
+ framework.
12
+
13
+ **Documentation: [madbomber.github.io/robot_lab-am](https://madbomber.github.io/robot_lab-am)**
14
+
15
+ ## Installation
16
+
17
+ Add to your Gemfile:
18
+
19
+ ```ruby
20
+ gem "robot_lab-am"
21
+ ```
22
+
23
+ Dependencies are just `ruby_llm` and `myway_config` — no robot framework
24
+ required.
25
+
26
+ ## Usage
27
+
28
+ ```bash
29
+ am snapshot # one-shot: collect activity, infer the goal, write
30
+ # .robot_lab_am/current_intent.md in the repo
31
+
32
+ am start # start the continuous daemon for this repo (detached)
33
+ am status # is it running? heartbeat, event count, last inference
34
+ am stop # stop it
35
+
36
+ am install # write a launchd agent so the daemon runs at login
37
+ am uninstall # remove the launchd agent
38
+
39
+ # All commands accept --repo PATH (default: current directory).
40
+ # `am start` also accepts --foreground, --interval N (poll seconds, default 15),
41
+ # and --debounce N (minimum seconds between inference runs, default 300).
42
+ ```
43
+
44
+ The daemon polls three signal sources — git commits and uncommitted changes,
45
+ your own messages in Claude Code session transcripts, and terminal commands run
46
+ inside the repo — appends new (redacted, deduplicated) events to
47
+ `.robot_lab_am/events.jsonl`, and re-infers `.robot_lab_am/current_intent.md`
48
+ on a debounced cadence whenever activity arrives.
49
+
50
+ ### Prerequisites
51
+
52
+ - **Local LLM**: inference defaults to LM Studio's OpenAI-compatible server on
53
+ `localhost:1234` (`lms server start`) so your activity log never leaves the
54
+ machine. Any RubyLLM provider/model can be passed to `Inferrer` instead.
55
+ - **Terminal capture** (optional): commands are read from
56
+ `~/.activity_monitor/terminal_activity.log`, written by a bash `preexec` hook
57
+ (`~/.bashrc__activity_monitor` in your dotfiles). Without the hook, git and
58
+ Claude Code signals still work.
59
+
60
+ ## Configuration
61
+
62
+ Settings cascade via [myway_config](https://github.com/MadBomber/myway_config),
63
+ lowest to highest precedence:
64
+
65
+ 1. Bundled defaults
66
+ 2. User config file — `~/.config/robot_lab_am/robot_lab_am.yml` (flat keys)
67
+ 3. `RLAM_*` environment variables
68
+ 4. CLI flags (`--interval`, `--debounce`)
69
+
70
+ ### Environment variables
71
+
72
+ | Variable | Default | Meaning |
73
+ |----------|---------|---------|
74
+ | `RLAM_PROVIDER` | `openai` | RubyLLM provider used for inference |
75
+ | `RLAM_MODEL` | `qwen/qwen3.8-27b` | Model name |
76
+ | `RLAM_API_BASE` | `http://localhost:1234/v1` | OpenAI-compatible server URL (applied to the `openai` provider) |
77
+ | `RLAM_API_KEY` | *(none)* | API key; unset falls back to the provider's own env var, then a placeholder (fine for LM Studio) |
78
+ | `RLAM_INTERVAL` | `15` | Seconds between daemon watcher polls |
79
+ | `RLAM_DEBOUNCE` | `300` | Minimum seconds between inference runs |
80
+ | `RLAM_INFERENCE_WINDOW` | `100` | Most recent events sent to the model |
81
+ | `RLAM_TERMINAL_LOG` | `~/.activity_monitor/terminal_activity.log` | Preexec command log path |
82
+
83
+ ```bash
84
+ # point inference at a different local model/server
85
+ export RLAM_PROVIDER=openai
86
+ export RLAM_MODEL=llama-3.3-70b
87
+ export RLAM_API_BASE=http://localhost:8080/v1
88
+ ```
89
+
90
+ Notes:
91
+
92
+ - `am --help` prints the *currently effective* interval/debounce, so you can
93
+ see what your config file and environment resolve to.
94
+ - API keys cascade: `RLAM_API_KEY` if set, else the chosen provider's
95
+ conventional env var (`OPENAI_API_KEY`, `ANTHROPIC_API_KEY`, …), else a
96
+ placeholder — which is all a local LM Studio server needs. The default
97
+ local setup requires no key; your activity log never leaves the machine.
98
+ - Inference runs in a scoped RubyLLM context, so embedding robot_lab-am in a
99
+ larger app never mutates that app's global RubyLLM configuration.
100
+
101
+ ## Development
102
+
103
+ After checking out the repo, run `bin/setup` to install dependencies. Then run
104
+ `bundle exec rake test` to run the tests. `bin/console` gives an interactive
105
+ prompt with the gem loaded.
106
+
107
+ ```bash
108
+ bundle exec rake test # all tests
109
+ asgard quality # all quality gates (tests + coverage, rubocop,
110
+ # flog, flay, reek, fasterer, typos, ...)
111
+ asgard doc_builder # build the MkDocs site
112
+ asgard doc_server # serve docs locally
113
+ bin/console # IRB shell with gem loaded
114
+ ```
115
+
116
+ ## License
117
+
118
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Quality gates (quality, rubocop_check, flog_check, flay_check, ...),
4
+ # documentation tasks (doc_builder, doc_server), and the gem lifecycle
5
+ # (build, install, release) live in asgard — see .loki and the shared
6
+ # dev/*.loki files it imports. This Rakefile keeps only the task asgard
7
+ # itself delegates to: the test suite.
8
+
9
+ require 'rake/testtask'
10
+
11
+ Rake::TestTask.new(:test) do |t|
12
+ t.libs << 'test'
13
+ t.libs << 'lib'
14
+ t.test_files = FileList['test/**/*_test.rb', 'test/**/test_*.rb'].exclude('**/*_helper.rb')
15
+ t.verbose = true
16
+ t.ruby_opts << '-rtest_helper'
17
+ end
18
+
19
+ task default: :test
20
+
21
+ desc 'Run tests with verbose output'
22
+ task :test_verbose do
23
+ ENV['TESTOPTS'] = '--verbose'
24
+ Rake::Task[:test].invoke
25
+ end
26
+
27
+ desc 'Run a single test file'
28
+ task :test_file, [:file] do |_t, args|
29
+ ruby "test/#{args[:file]}"
30
+ end
data/bin/am ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ # Activate bundler if running from the gem's own dev environment.
5
+ if File.exist?(File.expand_path("../Gemfile", __dir__))
6
+ require "bundler/setup"
7
+ end
8
+
9
+ require "robot_lab/am"
10
+
11
+ Signal.trap("INT") { exit 130 }
12
+
13
+ RobotLab::Am::CLI.run(ARGV)
@@ -0,0 +1,100 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 900 700" font-family="Helvetica, Arial, sans-serif" role="img" aria-label="robot_lab-am pipeline">
2
+ <defs>
3
+ <marker id="a" markerWidth="9" markerHeight="9" refX="7" refY="3" orient="auto" markerUnits="strokeWidth">
4
+ <path d="M0,0 L7,3 L0,6 Z" fill="#94a3b8"/>
5
+ </marker>
6
+ </defs>
7
+ <style>
8
+ .t { fill:#ffffff; font-size:13px; font-weight:600; }
9
+ .s { fill:#e2e8f0; font-size:10px; }
10
+ .hd { fill:#cbd5e1; font-size:12px; font-weight:700; letter-spacing:.5px; }
11
+ .lg { fill:#cbd5e1; font-size:11px; }
12
+ </style>
13
+
14
+ <!-- signal sources -->
15
+ <text x="40" y="14" class="hd">SIGNAL SOURCES (human activity)</text>
16
+ <rect x="40" y="24" width="250" height="46" rx="10" fill="#64748b"/>
17
+ <text x="165" y="45" text-anchor="middle" class="t">Git repo</text>
18
+ <text x="165" y="61" text-anchor="middle" class="s">commits · uncommitted changes</text>
19
+
20
+ <rect x="325" y="24" width="250" height="46" rx="10" fill="#64748b"/>
21
+ <text x="450" y="45" text-anchor="middle" class="t">Claude Code transcripts</text>
22
+ <text x="450" y="61" text-anchor="middle" class="s">~/.claude/projects/&lt;slug&gt;/*.jsonl</text>
23
+
24
+ <rect x="610" y="24" width="250" height="46" rx="10" fill="#64748b"/>
25
+ <text x="735" y="45" text-anchor="middle" class="t">Terminal preexec log</text>
26
+ <text x="735" y="61" text-anchor="middle" class="s">~/.activity_monitor/terminal_activity.log</text>
27
+
28
+ <line x1="165" y1="70" x2="165" y2="98" stroke="#94a3b8" stroke-width="2" marker-end="url(#a)"/>
29
+ <line x1="450" y1="70" x2="450" y2="98" stroke="#94a3b8" stroke-width="2" marker-end="url(#a)"/>
30
+ <line x1="735" y1="70" x2="735" y2="98" stroke="#94a3b8" stroke-width="2" marker-end="url(#a)"/>
31
+
32
+ <!-- watchers -->
33
+ <rect x="40" y="100" width="250" height="46" rx="10" fill="#0891b2"/>
34
+ <text x="165" y="121" text-anchor="middle" class="t">GitWatcher</text>
35
+ <text x="165" y="137" text-anchor="middle" class="s">recent commits + WIP (own state dir filtered)</text>
36
+
37
+ <rect x="325" y="100" width="250" height="46" rx="10" fill="#0891b2"/>
38
+ <text x="450" y="121" text-anchor="middle" class="t">ClaudeWatcher</text>
39
+ <text x="450" y="137" text-anchor="middle" class="s">your own plain-language messages</text>
40
+
41
+ <rect x="610" y="100" width="250" height="46" rx="10" fill="#0891b2"/>
42
+ <text x="735" y="121" text-anchor="middle" class="t">TerminalWatcher</text>
43
+ <text x="735" y="137" text-anchor="middle" class="s">only commands with cwd inside the repo</text>
44
+
45
+ <line x1="165" y1="146" x2="435" y2="196" stroke="#94a3b8" stroke-width="2" marker-end="url(#a)"/>
46
+ <line x1="450" y1="146" x2="450" y2="196" stroke="#94a3b8" stroke-width="2" marker-end="url(#a)"/>
47
+ <line x1="735" y1="146" x2="465" y2="196" stroke="#94a3b8" stroke-width="2" marker-end="url(#a)"/>
48
+
49
+ <!-- collector -->
50
+ <rect x="300" y="198" width="300" height="52" rx="10" fill="#f59e0b"/>
51
+ <text x="450" y="220" text-anchor="middle" class="t">Collector</text>
52
+ <text x="450" y="237" text-anchor="middle" class="s">Redactor masks secrets · fingerprint dedupe</text>
53
+ <line x1="450" y1="250" x2="450" y2="278" stroke="#94a3b8" stroke-width="2" marker-end="url(#a)"/>
54
+
55
+ <!-- event store -->
56
+ <rect x="325" y="280" width="250" height="46" rx="10" fill="#475569"/>
57
+ <text x="450" y="301" text-anchor="middle" class="t">.robot_lab_am/events.jsonl</text>
58
+ <text x="450" y="317" text-anchor="middle" class="s">append-only event log, one JSON line per event</text>
59
+ <line x1="450" y1="326" x2="450" y2="354" stroke="#94a3b8" stroke-width="2" marker-end="url(#a)"/>
60
+
61
+ <!-- inferrer + local LLM -->
62
+ <rect x="325" y="356" width="250" height="52" rx="10" fill="#a855f7"/>
63
+ <text x="450" y="378" text-anchor="middle" class="t">Inferrer</text>
64
+ <text x="450" y="395" text-anchor="middle" class="s">bounded recent window (last 100 events)</text>
65
+
66
+ <rect x="640" y="356" width="220" height="52" rx="10" fill="#10b981"/>
67
+ <text x="750" y="378" text-anchor="middle" class="t">Local LLM</text>
68
+ <text x="750" y="395" text-anchor="middle" class="s">LM Studio · localhost:1234 · no API key</text>
69
+ <line x1="575" y1="376" x2="638" y2="376" stroke="#94a3b8" stroke-width="2" marker-end="url(#a)"/>
70
+ <line x1="638" y1="392" x2="577" y2="392" stroke="#94a3b8" stroke-width="2" marker-end="url(#a)"/>
71
+
72
+ <line x1="450" y1="408" x2="450" y2="436" stroke="#94a3b8" stroke-width="2" marker-end="url(#a)"/>
73
+
74
+ <!-- intent artifact -->
75
+ <rect x="325" y="438" width="250" height="52" rx="10" fill="#6366f1"/>
76
+ <text x="450" y="460" text-anchor="middle" class="t">.robot_lab_am/current_intent.md</text>
77
+ <text x="450" y="477" text-anchor="middle" class="s">goal · confidence · evidence · open questions</text>
78
+ <line x1="450" y1="490" x2="450" y2="518" stroke="#94a3b8" stroke-width="2" marker-end="url(#a)"/>
79
+
80
+ <!-- consumer -->
81
+ <rect x="325" y="520" width="250" height="46" rx="10" fill="#0ea5e9"/>
82
+ <text x="450" y="541" text-anchor="middle" class="t">robot_lab-to</text>
83
+ <text x="450" y="557" text-anchor="middle" class="s">seeds a takeover run with real context</text>
84
+
85
+ <!-- daemon loop -->
86
+ <rect x="40" y="280" width="220" height="128" rx="10" fill="none" stroke="#22d3ee" stroke-width="2" stroke-dasharray="6 4"/>
87
+ <text x="150" y="304" text-anchor="middle" class="t">Daemon (am start)</text>
88
+ <text x="150" y="326" text-anchor="middle" class="lg">polls watchers every 15s</text>
89
+ <text x="150" y="344" text-anchor="middle" class="lg">debounced inference (≥300s apart)</text>
90
+ <text x="150" y="362" text-anchor="middle" class="lg">heartbeat.json every tick</text>
91
+ <text x="150" y="380" text-anchor="middle" class="lg">pid file · SIGTERM stop</text>
92
+ <text x="150" y="398" text-anchor="middle" class="lg">supervised by launchd (am install)</text>
93
+ <line x1="260" y1="344" x2="298" y2="240" stroke="#22d3ee" stroke-width="2" stroke-dasharray="6 4" marker-end="url(#a)"/>
94
+ <line x1="260" y1="382" x2="323" y2="382" stroke="#22d3ee" stroke-width="2" stroke-dasharray="6 4" marker-end="url(#a)"/>
95
+
96
+ <!-- legend -->
97
+ <text x="40" y="620" class="hd">ONE-SHOT PATH</text>
98
+ <text x="40" y="640" class="lg">am snapshot runs the same pipeline once — collect → infer → write — with no daemon.</text>
99
+ <text x="40" y="660" class="lg">Everything runs on your machine: the activity log is only ever summarized by a local model.</text>
100
+ </svg>