brainiac-cli 0.16.0__py3-none-any.whl
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.
- brain/__init__.py +39 -0
- brain/__main__.py +14 -0
- brain/_assets/AGENTS.md +564 -0
- brain/_assets/overlay/template/brand/brand-guide.md +24 -0
- brain/_assets/overlay/template/keywords/glossary.md +15 -0
- brain/_assets/overlay/template/people/roster.md +14 -0
- brain/_assets/overlay/template/voice/voice-profile.md +34 -0
- brain/_assets/routines/manifest.json +257 -0
- brain/_assets/scripts/brain-brief-mac.plist +59 -0
- brain/_assets/scripts/brain-brief.sh +74 -0
- brain/_assets/scripts/brain-synthesis-mac.plist +48 -0
- brain/_assets/scripts/brain-synthesis.sh +214 -0
- brain/_assets/scripts/install-brief-mac.sh +152 -0
- brain/_assets/scripts/install-brief-windows.ps1 +97 -0
- brain/_assets/scripts/register_tasks.py +386 -0
- brain/_assets/templates/company.md +21 -0
- brain/_assets/templates/concept.md +27 -0
- brain/_assets/templates/daily.md +20 -0
- brain/_assets/templates/decision.md +42 -0
- brain/_assets/templates/meeting.md +33 -0
- brain/_assets/templates/person.md +20 -0
- brain/_assets/templates/project.md +23 -0
- brain/_assets/templates/state-moc.md +40 -0
- brain/_version.py +12 -0
- brain/anchor.py +121 -0
- brain/audit.py +422 -0
- brain/backup.py +210 -0
- brain/brief.py +417 -0
- brain/capture.py +117 -0
- brain/chunk.py +249 -0
- brain/classification.py +134 -0
- brain/cli.py +1906 -0
- brain/config.py +368 -0
- brain/connect.py +362 -0
- brain/context.py +108 -0
- brain/core.py +3018 -0
- brain/doctor.py +1161 -0
- brain/egress.py +148 -0
- brain/embed.py +857 -0
- brain/encryption.py +217 -0
- brain/frontmatter.py +102 -0
- brain/golden_probe.py +678 -0
- brain/graph.py +369 -0
- brain/graphify.py +352 -0
- brain/index.py +1576 -0
- brain/ingest/__init__.py +19 -0
- brain/ingest/handlers/__init__.py +43 -0
- brain/ingest/handlers/base.py +95 -0
- brain/ingest/handlers/docx.py +78 -0
- brain/ingest/handlers/email.py +228 -0
- brain/ingest/handlers/html.py +142 -0
- brain/ingest/handlers/image.py +91 -0
- brain/ingest/handlers/pdf.py +99 -0
- brain/ingest/handlers/pptx.py +69 -0
- brain/ingest/handlers/tables.py +41 -0
- brain/ingest/handlers/text.py +43 -0
- brain/ingest/handlers/xlsx.py +100 -0
- brain/ingest/handlers/zip.py +163 -0
- brain/ingest/pipeline.py +839 -0
- brain/ingest/transcript.py +158 -0
- brain/init.py +870 -0
- brain/maintenance.py +2266 -0
- brain/mcp_adapter.py +217 -0
- brain/multihop.py +232 -0
- brain/notes.py +195 -0
- brain/overlay.py +183 -0
- brain/projection.py +79 -0
- brain/rerank.py +425 -0
- brain/snapshot.py +231 -0
- brain/update.py +743 -0
- brain/vectors.py +225 -0
- brainiac_cli-0.16.0.dist-info/METADATA +306 -0
- brainiac_cli-0.16.0.dist-info/RECORD +77 -0
- brainiac_cli-0.16.0.dist-info/WHEEL +5 -0
- brainiac_cli-0.16.0.dist-info/entry_points.txt +4 -0
- brainiac_cli-0.16.0.dist-info/licenses/LICENSE +202 -0
- brainiac_cli-0.16.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""register_tasks.py — idempotent cross-client scheduled-task registrar (CUT-04 / s07).
|
|
3
|
+
|
|
4
|
+
Reads routines/manifest.json and emits per-client registration steps for the
|
|
5
|
+
two task classes the persistence budget (routines/manifest.json locked_counts)
|
|
6
|
+
allows:
|
|
7
|
+
|
|
8
|
+
HOST leg — the ONE locked OS-scheduled task (`brain-nightly`, manifest id
|
|
9
|
+
"brain-nightly", command `brain maintain --json`). Registers via
|
|
10
|
+
macOS launchd or Windows Task Scheduler, reusing the existing
|
|
11
|
+
installer scripts. list-then-create-or-update: never blindly
|
|
12
|
+
re-registers without first checking what's there.
|
|
13
|
+
|
|
14
|
+
COWORK leg — the manifest's vm_eligible ON-INVOKE tasks (promotion-scan,
|
|
15
|
+
ingestion-digest-weekly's on-demand form). autoresearch-cascade
|
|
16
|
+
moved to host-only in AUT-04/s11 (needs the dev repo's eval/ +
|
|
17
|
+
src/, not something a Cowork VM session can run).
|
|
18
|
+
These are NEVER auto-firing: persistence-budget.md locks the
|
|
19
|
+
Cowork/VM OS-scheduled-task count at exactly 0, so this leg
|
|
20
|
+
never emits a cron_expression. It emits ONE paste-ready prompt
|
|
21
|
+
a human pastes into a Cowork chat session; the prompt itself
|
|
22
|
+
instructs the agent inside Cowork to register POKE-ONLY
|
|
23
|
+
triggers (list_scheduled_tasks -> create_scheduled_task only if
|
|
24
|
+
absent, else update_scheduled_task to adopt -- never
|
|
25
|
+
delete_scheduled_task, retire via enabled:false).
|
|
26
|
+
|
|
27
|
+
Default mode is --dry-run: read-only on the host (a `launchctl list` /
|
|
28
|
+
`schtasks` probe is harmless) and the Cowork leg is ALWAYS just printed text
|
|
29
|
+
-- this script has no way to reach Cowork's MCP tools from the Mac host, so
|
|
30
|
+
"apply" for that leg is "paste the printed prompt into a Cowork chat
|
|
31
|
+
yourself". --apply only changes behaviour for the HOST leg (it invokes the
|
|
32
|
+
existing idempotent installer script).
|
|
33
|
+
|
|
34
|
+
Usage:
|
|
35
|
+
python3 scripts/register_tasks.py --dry-run # default-safe report
|
|
36
|
+
python3 scripts/register_tasks.py --apply --client host # actually install
|
|
37
|
+
python3 scripts/register_tasks.py --dry-run --client cowork # just print the prompt
|
|
38
|
+
"""
|
|
39
|
+
from __future__ import annotations
|
|
40
|
+
|
|
41
|
+
import argparse
|
|
42
|
+
import json
|
|
43
|
+
import platform
|
|
44
|
+
import shutil
|
|
45
|
+
import subprocess
|
|
46
|
+
import sys
|
|
47
|
+
from pathlib import Path
|
|
48
|
+
|
|
49
|
+
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
50
|
+
DEFAULT_MANIFEST = REPO_ROOT / "routines" / "manifest.json"
|
|
51
|
+
|
|
52
|
+
LEGACY_MAC_LABEL = "com.profile-a-brain.daily-brief" # pre-per-vault SHARED label; install-brief-mac.sh migrates away from it
|
|
53
|
+
WIN_TASK_NAME = "brain-daily-brief"
|
|
54
|
+
SYNTHESIS_MAC_LABEL = "com.brainiac.synthesis" # ONE per host (not per vault) — brain-synthesis.sh iterates every registered vault
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _vault_slug8(vault: str | None) -> str | None:
|
|
58
|
+
"""8-hex per-vault id. Prefers brain.config.vault_slug8 (source of truth);
|
|
59
|
+
falls back to the same sha256(realpath)[:8] if brain isn't importable."""
|
|
60
|
+
if not vault:
|
|
61
|
+
return None
|
|
62
|
+
try:
|
|
63
|
+
from brain.config import vault_slug8
|
|
64
|
+
return vault_slug8(vault)
|
|
65
|
+
except Exception:
|
|
66
|
+
import hashlib
|
|
67
|
+
import os
|
|
68
|
+
return hashlib.sha256(
|
|
69
|
+
os.path.realpath(os.path.expanduser(vault)).encode()
|
|
70
|
+
).hexdigest()[:8]
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def mac_label(vault: str | None) -> str:
|
|
74
|
+
"""Per-vault launchd label; matches brain.config.nightly_label."""
|
|
75
|
+
slug = _vault_slug8(vault)
|
|
76
|
+
return f"com.brainiac.nightly.{slug}" if slug else "com.brainiac.nightly.<BRAIN_VAULT-unset>"
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def win_task_name(vault: str | None) -> str:
|
|
80
|
+
"""Per-vault Windows task name (same clobber fix as the macOS label)."""
|
|
81
|
+
slug = _vault_slug8(vault)
|
|
82
|
+
return f"{WIN_TASK_NAME}-{slug}" if slug else WIN_TASK_NAME
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def load_manifest(path: Path) -> dict:
|
|
86
|
+
with path.open() as f:
|
|
87
|
+
d = json.load(f)
|
|
88
|
+
locked = d.get("locked_counts", {})
|
|
89
|
+
# THE LOCK, amended 2026-07-11 (owner decision): host budget is 2 --
|
|
90
|
+
# brain-nightly (maintenance umbrella) + brain-synthesis (weekly
|
|
91
|
+
# model-backed kb-curator session). VM stays 0.
|
|
92
|
+
if locked.get("host_os_scheduled") != 2 or locked.get("vm_os_scheduled") != 0:
|
|
93
|
+
raise SystemExit(
|
|
94
|
+
"manifest locked_counts do not match persistence-budget.md THE LOCK "
|
|
95
|
+
f"(host=2, vm=0) -- got {locked}. Refusing to register against an "
|
|
96
|
+
"unratified budget. Amend routines/manifest.json locked_counts first."
|
|
97
|
+
)
|
|
98
|
+
return d
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def find_task(manifest: dict, task_id: str) -> dict:
|
|
102
|
+
for t in manifest["tasks"]:
|
|
103
|
+
if t["id"] == task_id:
|
|
104
|
+
return t
|
|
105
|
+
raise SystemExit(f"manifest has no task id={task_id!r}")
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# --------------------------------------------------------------------------
|
|
109
|
+
# HOST leg
|
|
110
|
+
# --------------------------------------------------------------------------
|
|
111
|
+
|
|
112
|
+
def host_probe_mac(label: str) -> tuple[bool, str]:
|
|
113
|
+
"""Return (already_registered, detail) by listing this vault's per-vault
|
|
114
|
+
launchd label, read-only."""
|
|
115
|
+
if not shutil.which("launchctl"):
|
|
116
|
+
return False, "launchctl not found (not on macOS, or PATH issue)"
|
|
117
|
+
try:
|
|
118
|
+
out = subprocess.run(
|
|
119
|
+
["launchctl", "list", label],
|
|
120
|
+
capture_output=True, text=True, timeout=10,
|
|
121
|
+
)
|
|
122
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
123
|
+
return False, f"launchctl probe failed: {exc}"
|
|
124
|
+
if out.returncode == 0:
|
|
125
|
+
return True, out.stdout.strip() or "(label found, no detail)"
|
|
126
|
+
return False, f"label {label!r} not currently loaded"
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def host_probe_windows(task_name: str) -> tuple[bool, str]:
|
|
130
|
+
"""Return (already_registered, detail) by listing this vault's per-vault
|
|
131
|
+
Task Scheduler task, read-only."""
|
|
132
|
+
if not shutil.which("schtasks") and not shutil.which("powershell"):
|
|
133
|
+
return False, "schtasks/powershell not found (not on Windows, or PATH issue)"
|
|
134
|
+
cmd = ["schtasks", "/Query", "/TN", task_name] if shutil.which("schtasks") else [
|
|
135
|
+
"powershell", "-NonInteractive", "-Command",
|
|
136
|
+
f"Get-ScheduledTask -TaskName '{task_name}' -ErrorAction SilentlyContinue",
|
|
137
|
+
]
|
|
138
|
+
try:
|
|
139
|
+
out = subprocess.run(cmd, capture_output=True, text=True, timeout=10)
|
|
140
|
+
except Exception as exc: # pragma: no cover - defensive
|
|
141
|
+
return False, f"Task Scheduler probe failed: {exc}"
|
|
142
|
+
if out.returncode == 0 and out.stdout.strip():
|
|
143
|
+
return True, out.stdout.strip().splitlines()[0]
|
|
144
|
+
return False, f"task {task_name!r} not currently registered"
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
def synthesis_host_report(manifest: dict, system: str) -> dict:
|
|
148
|
+
"""Probe/report the SECOND locked host task (brain-synthesis-weekly,
|
|
149
|
+
launchd label com.brainiac.synthesis). install-brief-mac.sh installs BOTH
|
|
150
|
+
tasks in one run, so there is no separate apply step here — this block
|
|
151
|
+
exists so a dry-run visibly accounts for the full host budget (THE LOCK,
|
|
152
|
+
host=2), never just the nightly."""
|
|
153
|
+
synthesis = find_task(manifest, "brain-synthesis-weekly")
|
|
154
|
+
rep: dict = {
|
|
155
|
+
"task_id": synthesis["id"],
|
|
156
|
+
"command": synthesis["command"],
|
|
157
|
+
}
|
|
158
|
+
if system == "Darwin":
|
|
159
|
+
rep["label"] = SYNTHESIS_MAC_LABEL
|
|
160
|
+
already, detail = host_probe_mac(SYNTHESIS_MAC_LABEL)
|
|
161
|
+
rep["already_registered"] = already
|
|
162
|
+
rep["probe_detail"] = detail
|
|
163
|
+
rep["install_script"] = str(REPO_ROOT / "scripts" / "install-brief-mac.sh")
|
|
164
|
+
rep["runtime_script"] = str(REPO_ROOT / "scripts" / "brain-synthesis.sh")
|
|
165
|
+
rep["note"] = "installed by the same install-brief-mac.sh run as brain-nightly"
|
|
166
|
+
else:
|
|
167
|
+
rep["already_registered"] = None
|
|
168
|
+
rep["probe_detail"] = (
|
|
169
|
+
f"no synthesis registration leg for host OS {system!r} yet "
|
|
170
|
+
"(macOS launchd only; run scripts/brain-synthesis.sh via your scheduler by hand)"
|
|
171
|
+
)
|
|
172
|
+
return rep
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def register_host_leg(manifest: dict, *, apply: bool) -> dict:
|
|
176
|
+
nightly = find_task(manifest, "brain-nightly")
|
|
177
|
+
system = platform.system() # "Darwin" | "Windows" | "Linux"
|
|
178
|
+
report: dict = {
|
|
179
|
+
"task_id": nightly["id"],
|
|
180
|
+
"command": nightly["command"],
|
|
181
|
+
"detected_os": system,
|
|
182
|
+
"apply": apply,
|
|
183
|
+
# BOTH locked host tasks (host_os_scheduled=2): the nightly fields
|
|
184
|
+
# below keep their original flat shape; the weekly synthesis task is
|
|
185
|
+
# reported alongside so a dry-run accounts for the whole budget.
|
|
186
|
+
"synthesis": synthesis_host_report(manifest, system),
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
if system == "Darwin":
|
|
190
|
+
vault = subprocess_env_brain_vault()
|
|
191
|
+
label = mac_label(vault)
|
|
192
|
+
report["label"] = label
|
|
193
|
+
already, detail = host_probe_mac(label)
|
|
194
|
+
report["already_registered"] = already
|
|
195
|
+
report["probe_detail"] = detail
|
|
196
|
+
report["action"] = "update (re-install, picks up the new `brain maintain` body)" if already else "create"
|
|
197
|
+
report["install_script"] = str(REPO_ROOT / "scripts" / "install-brief-mac.sh")
|
|
198
|
+
if apply:
|
|
199
|
+
if not vault:
|
|
200
|
+
report["apply_result"] = "SKIPPED — BRAIN_VAULT not set; export BRAIN_VAULT=<path> and re-run --apply"
|
|
201
|
+
else:
|
|
202
|
+
proc = subprocess.run(
|
|
203
|
+
["bash", report["install_script"]],
|
|
204
|
+
cwd=REPO_ROOT, env={**_os_environ(), "BRAIN_VAULT": vault},
|
|
205
|
+
capture_output=True, text=True, timeout=60,
|
|
206
|
+
)
|
|
207
|
+
report["apply_result"] = {
|
|
208
|
+
"exit_code": proc.returncode,
|
|
209
|
+
"stdout": proc.stdout.strip(),
|
|
210
|
+
"stderr": proc.stderr.strip(),
|
|
211
|
+
}
|
|
212
|
+
else:
|
|
213
|
+
report["apply_result"] = "DRY-RUN — would run: bash " + report["install_script"]
|
|
214
|
+
|
|
215
|
+
elif system == "Windows":
|
|
216
|
+
vault = subprocess_env_brain_vault()
|
|
217
|
+
task_name = win_task_name(vault)
|
|
218
|
+
report["label"] = task_name
|
|
219
|
+
already, detail = host_probe_windows(task_name)
|
|
220
|
+
report["already_registered"] = already
|
|
221
|
+
report["probe_detail"] = detail
|
|
222
|
+
report["action"] = "update (Register-ScheduledTask -Force is idempotent — re-running re-points the task at the new `brain maintain` body)" if already else "create"
|
|
223
|
+
report["install_script"] = str(REPO_ROOT / "scripts" / "install-brief-windows.ps1")
|
|
224
|
+
report["apply_result"] = (
|
|
225
|
+
"Run from an elevated-NOT-required PowerShell prompt (Windows leg cannot be "
|
|
226
|
+
"driven from this Mac host): "
|
|
227
|
+
f"powershell -File {report['install_script']} -VaultPath <path-to-vault>"
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
else:
|
|
231
|
+
report["already_registered"] = None
|
|
232
|
+
report["probe_detail"] = f"unsupported/undetected host OS for this leg: {system}"
|
|
233
|
+
report["action"] = "n/a"
|
|
234
|
+
report["apply_result"] = "SKIPPED — host leg targets macOS (launchd) or Windows (Task Scheduler) only"
|
|
235
|
+
|
|
236
|
+
return report
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def subprocess_env_brain_vault() -> str | None:
|
|
240
|
+
import os
|
|
241
|
+
return os.environ.get("BRAIN_VAULT")
|
|
242
|
+
|
|
243
|
+
|
|
244
|
+
def _os_environ() -> dict:
|
|
245
|
+
import os
|
|
246
|
+
return dict(os.environ)
|
|
247
|
+
|
|
248
|
+
|
|
249
|
+
# --------------------------------------------------------------------------
|
|
250
|
+
# COWORK leg — paste-ready, idempotent, POKE-ONLY (never cron — budget=0)
|
|
251
|
+
# --------------------------------------------------------------------------
|
|
252
|
+
|
|
253
|
+
def cowork_task_block(task: dict) -> str:
|
|
254
|
+
trigger_name = f"brain-{task['id']}"
|
|
255
|
+
cmd = task["command"].strip()
|
|
256
|
+
return f"""### `{trigger_name}` — {task['id']}
|
|
257
|
+
|
|
258
|
+
**Manifest source:** routines/manifest.json id `{task['id']}` (disposition: {task['disposition']}, cadence: {task['cadence']})
|
|
259
|
+
|
|
260
|
+
Idempotent registration steps for this trigger:
|
|
261
|
+
1. Call `list_scheduled_tasks` and search for a task/trigger named `{trigger_name}`.
|
|
262
|
+
2. If ABSENT: call `create_scheduled_task` with name=`{trigger_name}`. Do **NOT**
|
|
263
|
+
set a cron/schedule expression — leave it poke-only / fire-on-demand. The
|
|
264
|
+
locked persistence budget (routines/manifest.json locked_counts, THE LOCK) caps
|
|
265
|
+
Cowork/VM OS-scheduled-task count at exactly **0**; this trigger exists only
|
|
266
|
+
so the prompt below can be re-fired by name instead of retyped.
|
|
267
|
+
3. If PRESENT: call `update_scheduled_task` on the existing entry to adopt /
|
|
268
|
+
refresh its prompt body to the block below (do not create a duplicate).
|
|
269
|
+
4. **Never call `delete_scheduled_task`.** To retire this trigger later, call
|
|
270
|
+
`update_scheduled_task(enabled=false)` instead.
|
|
271
|
+
5. **#29022 caveat:** `create_scheduled_task` is sometimes not injected by the
|
|
272
|
+
MCP layer (silently no-ops). After step 2, re-run `list_scheduled_tasks`
|
|
273
|
+
and confirm `{trigger_name}` now appears. If it does not, fall back to the
|
|
274
|
+
Cowork Schedule UI and register the same prompt body manually.
|
|
275
|
+
|
|
276
|
+
**Trigger prompt body (what `{trigger_name}` runs when manually fired):**
|
|
277
|
+
|
|
278
|
+
```
|
|
279
|
+
{cmd}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
PF-02 export-egress gate (docs/operations/egress-provider-posture.md) — already satisfied
|
|
283
|
+
by this block: every `brain` call above carries `--max-tier Internal`; no
|
|
284
|
+
personal names appear (role-title only); this is a read/draft operation, not a
|
|
285
|
+
`brain project`-style export, so no `brain snapshot` step is required before it
|
|
286
|
+
— but if you extend this trigger to ship results outside the Cowork session
|
|
287
|
+
(an email, a doc, a paste to another tool), run `brain snapshot` first and
|
|
288
|
+
record the gate evidence per export-egress-gate.md Step D before doing so.
|
|
289
|
+
"""
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def build_cowork_prompt(manifest: dict) -> str:
|
|
293
|
+
vm_tasks = [t for t in manifest["tasks"] if t.get("vm_eligible")]
|
|
294
|
+
blocks = "\n".join(cowork_task_block(t) for t in vm_tasks)
|
|
295
|
+
return f"""# Paste-ready Cowork registrar prompt — brain on-invoke VM tasks (s07 / TSK-04)
|
|
296
|
+
|
|
297
|
+
Paste this entire block into a Cowork chat session that has the
|
|
298
|
+
scheduled-tasks MCP tools available (`list_scheduled_tasks`,
|
|
299
|
+
`create_scheduled_task`, `update_scheduled_task`). It registers
|
|
300
|
+
{len(vm_tasks)} POKE-ONLY triggers — none of them auto-fire on a cron, so
|
|
301
|
+
this paste never increases the Cowork/VM OS-scheduled-task count above the
|
|
302
|
+
locked **0** (routines/manifest.json locked_counts). They exist purely so the
|
|
303
|
+
analyst can re-fire a named, idempotently-registered prompt instead of
|
|
304
|
+
retyping it.
|
|
305
|
+
|
|
306
|
+
Before registering anything, confirm `BRAIN_VAULT` (or the projected
|
|
307
|
+
`brain --vault` path the Cowork sandbox uses) is set in the trigger's own
|
|
308
|
+
command lines, not assumed from the session environment — Cowork sessions are
|
|
309
|
+
ephemeral and do not inherit a persistent shell profile.
|
|
310
|
+
|
|
311
|
+
For EACH of the {len(vm_tasks)} tasks below, run the 5-step idempotent
|
|
312
|
+
sequence (list -> create-if-absent / update-if-present -> never delete):
|
|
313
|
+
|
|
314
|
+
{blocks}
|
|
315
|
+
---
|
|
316
|
+
**Summary you should report back after running this:** which of the
|
|
317
|
+
{len(vm_tasks)} triggers were CREATED vs UPDATED (adopted), and whether the
|
|
318
|
+
#29022 verify-after-create check passed for each, or required the Schedule UI
|
|
319
|
+
fallback.
|
|
320
|
+
"""
|
|
321
|
+
|
|
322
|
+
|
|
323
|
+
# --------------------------------------------------------------------------
|
|
324
|
+
# main
|
|
325
|
+
# --------------------------------------------------------------------------
|
|
326
|
+
|
|
327
|
+
def main(argv: list[str] | None = None) -> int:
|
|
328
|
+
p = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter)
|
|
329
|
+
p.add_argument("--manifest", default=str(DEFAULT_MANIFEST))
|
|
330
|
+
p.add_argument("--client", choices=["host", "cowork", "all"], default="all")
|
|
331
|
+
mode = p.add_mutually_exclusive_group()
|
|
332
|
+
mode.add_argument("--dry-run", action="store_true", help="default: report-only, no mutation (host leg probe is read-only; Cowork leg is always just printed text)")
|
|
333
|
+
mode.add_argument("--apply", action="store_true", help="HOST leg only: actually invoke the installer script. Cowork leg is unaffected (this script cannot reach Cowork's MCP tools)")
|
|
334
|
+
p.add_argument("--json", action="store_true", help="emit machine-readable JSON instead of human text")
|
|
335
|
+
p.add_argument("--save-cowork-prompt", default=None, help="also write the Cowork paste-prompt to this file path")
|
|
336
|
+
args = p.parse_args(argv)
|
|
337
|
+
|
|
338
|
+
apply = bool(args.apply) # dry-run is the default whenever --apply is absent
|
|
339
|
+
|
|
340
|
+
manifest = load_manifest(Path(args.manifest))
|
|
341
|
+
|
|
342
|
+
out: dict = {"manifest": args.manifest, "apply": apply, "client": args.client}
|
|
343
|
+
|
|
344
|
+
if args.client in ("host", "all"):
|
|
345
|
+
out["host"] = register_host_leg(manifest, apply=apply)
|
|
346
|
+
|
|
347
|
+
if args.client in ("cowork", "all"):
|
|
348
|
+
prompt = build_cowork_prompt(manifest)
|
|
349
|
+
out["cowork"] = {
|
|
350
|
+
"vm_eligible_tasks": [t["id"] for t in manifest["tasks"] if t.get("vm_eligible")],
|
|
351
|
+
"prompt": prompt,
|
|
352
|
+
}
|
|
353
|
+
if args.save_cowork_prompt:
|
|
354
|
+
dest = Path(args.save_cowork_prompt)
|
|
355
|
+
dest.parent.mkdir(parents=True, exist_ok=True)
|
|
356
|
+
dest.write_text(prompt)
|
|
357
|
+
out["cowork"]["saved_to"] = str(dest)
|
|
358
|
+
|
|
359
|
+
if args.json:
|
|
360
|
+
print(json.dumps(out, indent=2, default=str))
|
|
361
|
+
else:
|
|
362
|
+
print(f"=== register_tasks.py — mode={'APPLY' if apply else 'DRY-RUN'} client={args.client} ===\n")
|
|
363
|
+
if "host" in out:
|
|
364
|
+
h = out["host"]
|
|
365
|
+
print(f"-- HOST leg ({h['detected_os']}) --")
|
|
366
|
+
print(f" task: {h['task_id']} command: {h['command']}")
|
|
367
|
+
print(f" already_registered: {h.get('already_registered')} ({h.get('probe_detail')})")
|
|
368
|
+
print(f" action: {h.get('action')}")
|
|
369
|
+
print(f" apply_result: {h.get('apply_result')}")
|
|
370
|
+
s = h.get("synthesis")
|
|
371
|
+
if s:
|
|
372
|
+
print(f" task: {s['task_id']} command: {s['command']}")
|
|
373
|
+
print(f" already_registered: {s.get('already_registered')} ({s.get('probe_detail')})")
|
|
374
|
+
print()
|
|
375
|
+
if "cowork" in out:
|
|
376
|
+
c = out["cowork"]
|
|
377
|
+
print(f"-- COWORK leg — {len(c['vm_eligible_tasks'])} on-invoke task(s): {', '.join(c['vm_eligible_tasks'])} --")
|
|
378
|
+
print(c["prompt"])
|
|
379
|
+
if c.get("saved_to"):
|
|
380
|
+
print(f"(prompt also saved to {c['saved_to']})")
|
|
381
|
+
|
|
382
|
+
return 0
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
if __name__ == "__main__":
|
|
386
|
+
raise SystemExit(main())
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: "{{id}}"
|
|
3
|
+
title: "{{title}}"
|
|
4
|
+
type: company
|
|
5
|
+
classification: Internal
|
|
6
|
+
created: "{{date}}"
|
|
7
|
+
updated: "{{date}}"
|
|
8
|
+
tags: []
|
|
9
|
+
industry: ""
|
|
10
|
+
related: []
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# {{title}}
|
|
14
|
+
|
|
15
|
+
## Overview
|
|
16
|
+
|
|
17
|
+
## Key People
|
|
18
|
+
|
|
19
|
+
## Projects & Engagements
|
|
20
|
+
|
|
21
|
+
## Notes
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: "{{id}}"
|
|
3
|
+
title: "{{title}}"
|
|
4
|
+
type: concept
|
|
5
|
+
classification: Internal
|
|
6
|
+
created: "{{date}}"
|
|
7
|
+
updated: "{{date}}"
|
|
8
|
+
tags: []
|
|
9
|
+
domain: ""
|
|
10
|
+
related: []
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# {{title}}
|
|
14
|
+
|
|
15
|
+
## Definition
|
|
16
|
+
|
|
17
|
+
## Context & Application
|
|
18
|
+
|
|
19
|
+
## Counter-Arguments
|
|
20
|
+
|
|
21
|
+
Reasons this concept might be wrong, incomplete, or context-dependent. A
|
|
22
|
+
concept note without this section is warn-flagged (TMP-05) — trustworthy
|
|
23
|
+
notes name their own limits.
|
|
24
|
+
|
|
25
|
+
## Related Concepts
|
|
26
|
+
|
|
27
|
+
## Sources
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: "{{id}}"
|
|
3
|
+
title: "{{date}}"
|
|
4
|
+
type: daily
|
|
5
|
+
classification: Internal
|
|
6
|
+
created: "{{date}}"
|
|
7
|
+
updated: "{{date}}"
|
|
8
|
+
tags: []
|
|
9
|
+
related: []
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# {{date}}
|
|
13
|
+
|
|
14
|
+
## Session Summary
|
|
15
|
+
|
|
16
|
+
## Work Done
|
|
17
|
+
|
|
18
|
+
## Open Threads
|
|
19
|
+
|
|
20
|
+
## Next Session
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: "{{id}}"
|
|
3
|
+
title: "Decision - {{title}}"
|
|
4
|
+
type: decision
|
|
5
|
+
classification: Internal
|
|
6
|
+
created: "{{date}}"
|
|
7
|
+
updated: "{{date}}"
|
|
8
|
+
tags: []
|
|
9
|
+
# Bitemporal frontmatter (ADR-0003 ruling 2, optional but recommended for
|
|
10
|
+
# type:decision): two clocks — document_date is when the decision was
|
|
11
|
+
# recorded, effective_date is when it applies in the world.
|
|
12
|
+
document_date: "{{date}}"
|
|
13
|
+
effective_date: "{{date}}"
|
|
14
|
+
context: ""
|
|
15
|
+
project: ""
|
|
16
|
+
stakeholders: []
|
|
17
|
+
# Lint (TMP-05, warn-only): a decision note must anchor its claim to a
|
|
18
|
+
# source — set `source:` to a raw/ wikilink, or cite one inline below.
|
|
19
|
+
source: ""
|
|
20
|
+
related: []
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# Decision - {{title}}
|
|
24
|
+
|
|
25
|
+
## Context
|
|
26
|
+
|
|
27
|
+
Why this decision was needed.
|
|
28
|
+
|
|
29
|
+
## Decision
|
|
30
|
+
|
|
31
|
+
What was decided.
|
|
32
|
+
|
|
33
|
+
## Rationale
|
|
34
|
+
|
|
35
|
+
Why this option was chosen over alternatives. Anchor claims to sources, e.g.
|
|
36
|
+
`[[raw/2026-07-05-example-source]]`.
|
|
37
|
+
|
|
38
|
+
## Consequences
|
|
39
|
+
|
|
40
|
+
What follows from this decision.
|
|
41
|
+
|
|
42
|
+
## Alternatives Considered
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: "{{id}}"
|
|
3
|
+
title: "{{date}} - {{title}}"
|
|
4
|
+
type: meeting
|
|
5
|
+
classification: Internal
|
|
6
|
+
created: "{{date}}"
|
|
7
|
+
updated: "{{date}}"
|
|
8
|
+
tags: []
|
|
9
|
+
# Bitemporal frontmatter (ADR-0003 ruling 2, optional): meetings rarely
|
|
10
|
+
# supersede each other, but document_date/is_latest_version anchor the note
|
|
11
|
+
# when they do.
|
|
12
|
+
document_date: "{{date}}"
|
|
13
|
+
is_latest_version: true
|
|
14
|
+
people: []
|
|
15
|
+
companies: []
|
|
16
|
+
projects: []
|
|
17
|
+
source: ""
|
|
18
|
+
related: []
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
# {{date}} - {{title}}
|
|
22
|
+
|
|
23
|
+
## Attendees
|
|
24
|
+
|
|
25
|
+
## Key Discussion Points
|
|
26
|
+
|
|
27
|
+
## Decisions Made
|
|
28
|
+
|
|
29
|
+
## Action Items
|
|
30
|
+
|
|
31
|
+
- [ ]
|
|
32
|
+
|
|
33
|
+
## Notes
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: "{{id}}"
|
|
3
|
+
title: "{{title}}"
|
|
4
|
+
type: person
|
|
5
|
+
classification: Internal
|
|
6
|
+
created: "{{date}}"
|
|
7
|
+
updated: "{{date}}"
|
|
8
|
+
tags: []
|
|
9
|
+
organization: ""
|
|
10
|
+
role: ""
|
|
11
|
+
related: []
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
# {{title}}
|
|
15
|
+
|
|
16
|
+
## Role & Context
|
|
17
|
+
|
|
18
|
+
## Key Interactions
|
|
19
|
+
|
|
20
|
+
## Notes
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: "{{id}}"
|
|
3
|
+
title: "{{title}}"
|
|
4
|
+
type: project
|
|
5
|
+
classification: Internal
|
|
6
|
+
created: "{{date}}"
|
|
7
|
+
updated: "{{date}}"
|
|
8
|
+
tags: []
|
|
9
|
+
owner: ""
|
|
10
|
+
related: []
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
# {{title}}
|
|
14
|
+
|
|
15
|
+
## Objective
|
|
16
|
+
|
|
17
|
+
## Current Status
|
|
18
|
+
|
|
19
|
+
## Key Stakeholders
|
|
20
|
+
|
|
21
|
+
## Decisions
|
|
22
|
+
|
|
23
|
+
## Notes
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: "{{id}}"
|
|
3
|
+
title: "{{title}}"
|
|
4
|
+
type: moc
|
|
5
|
+
classification: Internal
|
|
6
|
+
created: "{{date}}"
|
|
7
|
+
updated: "{{date}}"
|
|
8
|
+
tags: []
|
|
9
|
+
related: []
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
# {{title}}
|
|
13
|
+
|
|
14
|
+
One live "state of play" note — the navigable spine for this vault/project
|
|
15
|
+
(AGENTS.md §3, HYG-03). Each `## Section:` heading below carries its own
|
|
16
|
+
`Updated: YYYY-MM-DD` stamp on the very next line, so a reader (or
|
|
17
|
+
`tools/validate.py`'s warn-only staleness lint) can see which parts are
|
|
18
|
+
current without touching this note's own top-level `updated:`. A section
|
|
19
|
+
whose stamp is older than the staleness threshold is warn-flagged, never
|
|
20
|
+
blocked — a quality nudge, not a gate.
|
|
21
|
+
|
|
22
|
+
## Section: Current Priorities
|
|
23
|
+
Updated: {{date}}
|
|
24
|
+
|
|
25
|
+
- What matters right now.
|
|
26
|
+
|
|
27
|
+
## Section: Open Threads
|
|
28
|
+
Updated: {{date}}
|
|
29
|
+
|
|
30
|
+
- Unresolved questions, in-flight decisions.
|
|
31
|
+
|
|
32
|
+
## Section: Key Decisions
|
|
33
|
+
Updated: {{date}}
|
|
34
|
+
|
|
35
|
+
- Link to `[[decision-notes]]` rather than re-stating them here.
|
|
36
|
+
|
|
37
|
+
## Section: Watch List
|
|
38
|
+
Updated: {{date}}
|
|
39
|
+
|
|
40
|
+
- Things to revisit; stale items surface via the lint above.
|
brain/_version.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"""Committed version stamp — GENERATED by tools/package_clients.py; do not hand-edit.
|
|
2
|
+
|
|
3
|
+
ADR-0005 Ruling 1: the clean-room export ships only git-tracked files, so the
|
|
4
|
+
zero-install VM (staged source, no package metadata) can only report a real
|
|
5
|
+
version if this stamp is COMMITTED. tools/release.py rewrites it (via
|
|
6
|
+
tools/package_clients.py) in the same act as the pyproject.toml bump, so the
|
|
7
|
+
tagged release commit always carries the exact released version. On the
|
|
8
|
+
pip-installed host, importlib.metadata stays primary; brain/__init__.py reads
|
|
9
|
+
this file only as the fallback before 0.0.0+unknown.
|
|
10
|
+
"""
|
|
11
|
+
|
|
12
|
+
__version__ = "0.16.0"
|