arkaos 2.22.0 → 2.25.0
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.
- package/README.md +105 -9
- package/VERSION +1 -1
- package/arka/SKILL.md +1 -0
- package/arka/skills/comfyui/SKILL.md +2 -2
- package/arka/skills/comfyui/references/workflows.md +6 -6
- package/arka/skills/costs/SKILL.md +11 -0
- package/config/cognition/prompts/dreaming.md +3 -3
- package/config/cognition/prompts/research.md +4 -4
- package/config/hooks/user-prompt-submit.sh +6 -1
- package/core/cognition/__pycache__/auto_documentor.cpython-313.pyc +0 -0
- package/core/cognition/auto_documentor.py +127 -12
- package/core/cognition/capture/__pycache__/collector.cpython-313.pyc +0 -0
- package/core/cognition/capture/collector.py +10 -3
- package/core/cognition/scheduler/__pycache__/daemon.cpython-313.pyc +0 -0
- package/core/cognition/scheduler/daemon.py +5 -0
- package/core/jobs/__pycache__/auto_doc_worker.cpython-313.pyc +0 -0
- package/core/jobs/auto_doc_worker.py +5 -3
- package/core/obsidian/__pycache__/writer.cpython-313.pyc +0 -0
- package/core/obsidian/writer.py +5 -4
- package/core/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/base.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/claude_code.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/codex_cli.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/cursor.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/gemini_cli.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/llm_cost_telemetry.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/llm_cost_telemetry_cli.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/llm_provider.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/path_resolver.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/pricing.cpython-313.pyc +0 -0
- package/core/runtime/claude_code.py +22 -16
- package/core/runtime/codex_cli.py +23 -6
- package/core/runtime/gemini_cli.py +135 -11
- package/core/runtime/llm_provider.py +14 -9
- package/core/runtime/path_resolver.py +202 -0
- package/core/shared/__init__.py +6 -0
- package/core/shared/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/shared/__pycache__/safe_session_id.cpython-313.pyc +0 -0
- package/core/shared/safe_session_id.py +41 -0
- package/core/specs/SPEC-installer-cross-os.md +227 -0
- package/core/specs/SPEC-paths-portability.md +209 -0
- package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
- package/core/synapse/kb_cache.py +7 -6
- package/core/synapse/layers.py +7 -0
- package/core/workflow/__pycache__/flow_enforcer.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/marker_cache.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/research_gate.cpython-313.pyc +0 -0
- package/core/workflow/flow_enforcer.py +6 -14
- package/core/workflow/marker_cache.py +6 -8
- package/core/workflow/research_gate.py +11 -9
- package/departments/dev/skills/scaffold/SKILL.md +4 -4
- package/departments/kb/skills/knowledge/SKILL.md +1 -1
- package/departments/ops/skills/operations/SKILL.md +1 -1
- package/installer/cli.js +2 -1
- package/installer/doctor.js +48 -0
- package/installer/index.js +26 -1
- package/installer/migrate-user-data.js +17 -1
- package/installer/migrations/v3_path_schema.js +109 -0
- package/installer/package-manager.js +191 -0
- package/installer/path-resolver.js +124 -0
- package/installer/system-tools.js +243 -0
- package/installer/update.js +24 -3
- package/knowledge/obsidian-config.json +1 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
npx arkaos install
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
[](https://www.npmjs.com/package/arkaos) [](LICENSE) [](https://www.npmjs.com/package/arkaos) [](LICENSE) []()
|
|
12
12
|
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -228,6 +228,84 @@ arkaos scheduler logs # View logs
|
|
|
228
228
|
|
|
229
229
|
---
|
|
230
230
|
|
|
231
|
+
## Intelligence Loop (v2.21.0+)
|
|
232
|
+
|
|
233
|
+
Three-layer system that makes ArkaOS consult its brain before going external, and write every session's learnings back to the vault automatically. The system compounds daily — tomorrow's ArkaOS is smarter than today's.
|
|
234
|
+
|
|
235
|
+
### Synapse L2.5 — KB Context Injection (v2.21.0)
|
|
236
|
+
|
|
237
|
+
On every user prompt, ArkaOS runs semantic search against the Obsidian vault and injects the top relevant notes (with wikilinks and excerpts) as context **before the model starts planning**. The agent sees what the vault already knows, naturally — no hard gate, no friction.
|
|
238
|
+
|
|
239
|
+
- Top 3–5 notes by similarity
|
|
240
|
+
- Jaccard fallback when vector store absent
|
|
241
|
+
- Feature flag `synapse.l25KbContext` default `true`
|
|
242
|
+
- Kill switch `ARKA_BYPASS_L25=1`
|
|
243
|
+
|
|
244
|
+
### Research Gate — Natural Nudge (v2.21.0)
|
|
245
|
+
|
|
246
|
+
When an agent tries `Context7`, `WebSearch`, `WebFetch`, or `Firecrawl` without consulting Obsidian first:
|
|
247
|
+
|
|
248
|
+
- **First attempt** → natural PT-PT nudge listing top 3 vault hits. Allows the call.
|
|
249
|
+
- **Second attempt in same turn** → deny. The nudge wasn't enough.
|
|
250
|
+
|
|
251
|
+
Feature flag `hooks.kbFirst` default `false` in v2.21.0 — dormant until operator flips. Kill switch `ARKA_BYPASS_KB_FIRST=1` (audited).
|
|
252
|
+
|
|
253
|
+
### Auto-Documentor + Cataloger + Relator (v2.21.0)
|
|
254
|
+
|
|
255
|
+
After every approved session, an async worker writes the learnings back to the vault:
|
|
256
|
+
|
|
257
|
+
1. **Extract** — parses the transcript for sources consulted, decisions made, deliverables produced
|
|
258
|
+
2. **Classify** — cataloger chooses the right taxonomic home (code pattern, persona, client strategy, marketing test, ADR, research finding, framework, or session fallback)
|
|
259
|
+
3. **Write** — creates the note at the correct vault path with frontmatter + tags
|
|
260
|
+
4. **Relate** — relator finds semantically similar existing notes, creates bidirectional `[[wikilinks]]`, updates MOCs
|
|
261
|
+
|
|
262
|
+
The vault becomes a relational graph that grows with every session.
|
|
263
|
+
|
|
264
|
+
### LLM Agnostic Auto-Documentor (v2.22.0)
|
|
265
|
+
|
|
266
|
+
The synthesis LLM call is runtime-agnostic. **Zero model hardcoding.** Whatever runtime you have configured decides the model:
|
|
267
|
+
|
|
268
|
+
| Provider | Default | Model decided by |
|
|
269
|
+
|---|---|---|
|
|
270
|
+
| `subagent` | ✅ | Active runtime's headless CLI (`claude -p`, `gemini -p …`) |
|
|
271
|
+
| `anthropic-direct` | — | `ANTHROPIC_MODEL` env var; no code default |
|
|
272
|
+
| `stub` | tests | Template fallback |
|
|
273
|
+
|
|
274
|
+
Fallback chain `subagent → anthropic-direct → stub` never raises. Prompt caching ON by default (5-min TTL `cache_control: ephemeral` on system block).
|
|
275
|
+
|
|
276
|
+
### Budget Telemetry + `/arka costs` (v2.22.0)
|
|
277
|
+
|
|
278
|
+
Every LLM call appends to `~/.arkaos/telemetry/llm-cost.jsonl` with tokens, cache hits, and estimated cost. Per [ADR-011](docs/adr/), this is **visibility only — never blocks**.
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
/arka costs # today (default)
|
|
282
|
+
/arka costs week # last 7 days
|
|
283
|
+
/arka costs month # last 30 days
|
|
284
|
+
/arka costs sessions # top 10 expensive sessions
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Soft advisory when a single session exceeds $5 equivalent. No hard caps.
|
|
288
|
+
|
|
289
|
+
### Flow Marker v2 (v2.21.0)
|
|
290
|
+
|
|
291
|
+
The constitutional 13-phase flow enforcement (binding in v2.20) got a turn-scoped marker cache, eliminating the cross-turn false positives that triggered on subagent dispatch and short user continuations. ADR-compliant: cache accelerates ALLOW decisions only; the transcript remains authoritative for DENY.
|
|
292
|
+
|
|
293
|
+
### Polish & Consolidation (v2.22.1)
|
|
294
|
+
|
|
295
|
+
- `core/shared/safe_session_id.py` — single source of truth for the path-traversal / injection allowlist, deduped across 6 modules (100% coverage, 33 security-contract tests)
|
|
296
|
+
- Gemini CLI headless live (`gemini -p "…" --output-format json`)
|
|
297
|
+
- `_MAX_FALLBACK_NOTES = 2000` bound on Synapse L2.5 degraded-mode scan
|
|
298
|
+
- Auto-documentor coverage lifted 93% → 98%
|
|
299
|
+
- Template synthesiser now mirrors LLM prompt structure (Key Facts → Decisions → Sources)
|
|
300
|
+
|
|
301
|
+
### ADRs
|
|
302
|
+
|
|
303
|
+
- [`2026-04-20-flow-marker-v2.md`](docs/adr/2026-04-20-flow-marker-v2.md) — turn-scoped cache amendment
|
|
304
|
+
- [`2026-04-20-kb-first-intelligence.md`](docs/adr/2026-04-20-kb-first-intelligence.md) — Synapse L2.5 + gate + auto-documentor
|
|
305
|
+
- [`2026-04-20-llm-agnostic.md`](docs/adr/2026-04-20-llm-agnostic.md) — runtime-agnostic LLM wiring
|
|
306
|
+
|
|
307
|
+
---
|
|
308
|
+
|
|
231
309
|
## Ecosystem Management
|
|
232
310
|
|
|
233
311
|
ArkaOS manages client projects as **ecosystems** — groups of related projects with dedicated squads.
|
|
@@ -370,21 +448,39 @@ Full documentation is available on the **[GitHub Wiki](https://github.com/andrea
|
|
|
370
448
|
|
|
371
449
|
## CLI Reference
|
|
372
450
|
|
|
451
|
+
**Installer (terminal):**
|
|
452
|
+
|
|
373
453
|
```bash
|
|
374
|
-
npx arkaos install
|
|
375
|
-
npx arkaos update
|
|
376
|
-
npx arkaos migrate
|
|
377
|
-
npx arkaos doctor
|
|
378
|
-
npx arkaos dashboard
|
|
379
|
-
npx arkaos keys
|
|
380
|
-
npx arkaos uninstall
|
|
454
|
+
npx arkaos install # Fresh install (auto-detects runtime)
|
|
455
|
+
npx arkaos@latest update # Update core + hooks to latest version
|
|
456
|
+
npx arkaos migrate # Migrate from v1
|
|
457
|
+
npx arkaos doctor # Health check
|
|
458
|
+
npx arkaos dashboard # Start monitoring dashboard
|
|
459
|
+
npx arkaos keys # Manage API keys
|
|
460
|
+
npx arkaos uninstall # Remove ArkaOS
|
|
381
461
|
```
|
|
382
462
|
|
|
463
|
+
**In-session commands (inside Claude Code / Codex / Gemini / Cursor):**
|
|
464
|
+
|
|
465
|
+
```
|
|
466
|
+
/arka update # Sync all project configs after core update
|
|
467
|
+
/arka status # System health + LLM costs (24h)
|
|
468
|
+
/arka costs [today|week|month|all|sessions] # LLM cost visibility
|
|
469
|
+
/arka index # (Re)index Obsidian vault into vector store
|
|
470
|
+
/arka search <query> # Semantic search in knowledge base
|
|
471
|
+
/arka standup # Daily standup (projects, blockers, updates)
|
|
472
|
+
/arka onboard <path> # Onboard an existing project
|
|
473
|
+
/arka conclave # Personal AI advisory board
|
|
474
|
+
/do <description> # Universal routing — natural language to department
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
Department commands: `/dev`, `/mkt`, `/brand`, `/fin`, `/strat`, `/ecom`, `/kb`, `/ops`, `/pm`, `/saas`, `/landing`, `/content`, `/community`, `/sales`, `/lead`, `/org`.
|
|
478
|
+
|
|
383
479
|
---
|
|
384
480
|
|
|
385
481
|
## Contributing
|
|
386
482
|
|
|
387
|
-
See [CONTRIBUTING.md](.github/CONTRIBUTING.md). PRs welcome — all changes require passing the full test suite (
|
|
483
|
+
See [CONTRIBUTING.md](.github/CONTRIBUTING.md). PRs welcome — all changes require passing the full test suite (3,025 tests) and Quality Gate review.
|
|
388
484
|
|
|
389
485
|
## License
|
|
390
486
|
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.25.0
|
package/arka/SKILL.md
CHANGED
|
@@ -122,6 +122,7 @@ violation (squad-routing, arka-supremacy, spec-driven, mandatory-qa).
|
|
|
122
122
|
| `/arka search <query>` | Semantic search in knowledge base |
|
|
123
123
|
| `/arka keys` | Manage API keys (OpenAI, Google, fal.ai) |
|
|
124
124
|
| `/arka personas` | Manage AI personas (create, clone to agent) |
|
|
125
|
+
| `/arka resume <PR_URL>` | Re-enter the Claude Code session that produced a PR (GitHub / GitLab / Bitbucket). Wraps the native `/resume` from Claude Code 2.1.122+. Useful with `arka-spec` and `arka-release` archaeology. |
|
|
125
126
|
| `/do <description>` | Universal routing — natural language to department command |
|
|
126
127
|
|
|
127
128
|
## Universal Orchestrator (/do)
|
|
@@ -19,8 +19,8 @@ Full-spectrum AI media production platform. From ComfyUI node engineering to Hol
|
|
|
19
19
|
|
|
20
20
|
| Project | Type | Stack | Path |
|
|
21
21
|
|---------|------|-------|------|
|
|
22
|
-
| **purz-comfyui-workflows** | Workflow Collection | ComfyUI JSON (7 workflows, 110+ nodes) |
|
|
23
|
-
| **lora_tester** | CLI Tool + Gallery | Python 3.8+ (stdlib) + Vanilla HTML/JS |
|
|
22
|
+
| **purz-comfyui-workflows** | Workflow Collection | ComfyUI JSON (7 workflows, 110+ nodes) | `${ARKA_OS_REPOS}/purz-comfyui-workflows` |
|
|
23
|
+
| **lora_tester** | CLI Tool + Gallery | Python 3.8+ (stdlib) + Vanilla HTML/JS | `${ARKA_OS_REPOS}/lora_tester` |
|
|
24
24
|
|
|
25
25
|
## Squads
|
|
26
26
|
|
|
@@ -7,8 +7,8 @@ Referenced from SKILL.md. Read only when needed.
|
|
|
7
7
|
For EVERY `/comfyui` request, follow this workflow:
|
|
8
8
|
|
|
9
9
|
### Phase 1 — Context Loading
|
|
10
|
-
1. Read ComfyUI skill reference:
|
|
11
|
-
2. Check available workflows: `ls /
|
|
10
|
+
1. Read ComfyUI skill reference: `${ARKA_OS_REPOS}/lora_tester/.claude/skills/comfy_local/skill.md`
|
|
11
|
+
2. Check available workflows: `ls ${ARKA_OS_REPOS}/purz-comfyui-workflows/`
|
|
12
12
|
3. Check ComfyUI server: `curl -s http://localhost:8188/system_stats 2>/dev/null`
|
|
13
13
|
4. Load project context for affected repos
|
|
14
14
|
5. Identify which squad(s) the request needs
|
|
@@ -320,8 +320,8 @@ while pending:
|
|
|
320
320
|
curl -s http://localhost:8188/system_stats 2>/dev/null
|
|
321
321
|
|
|
322
322
|
# Project status
|
|
323
|
-
cd /
|
|
324
|
-
cd /
|
|
323
|
+
cd ${ARKA_OS_REPOS}/purz-comfyui-workflows && git log --oneline -5
|
|
324
|
+
cd ${ARKA_OS_REPOS}/lora_tester && git log --oneline -5
|
|
325
325
|
```
|
|
326
326
|
|
|
327
327
|
Present as:
|
|
@@ -403,10 +403,10 @@ When Iris (Node Researcher) investigates custom nodes:
|
|
|
403
403
|
|
|
404
404
|
## Obsidian Output
|
|
405
405
|
|
|
406
|
-
All documentation:
|
|
406
|
+
All documentation: `${VAULT_PATH}/Projects/ComfyUI/`
|
|
407
407
|
|
|
408
408
|
```
|
|
409
|
-
|
|
409
|
+
Projects/ComfyUI/
|
|
410
410
|
├── Home.md <- Ecosystem overview
|
|
411
411
|
├── Workflows/
|
|
412
412
|
│ ├── LTX-2 T2V.md
|
|
@@ -55,6 +55,17 @@ Source:
|
|
|
55
55
|
One JSONL line per LLM call, written by every provider adapter.
|
|
56
56
|
Malformed lines are skipped and counted, never raised.
|
|
57
57
|
|
|
58
|
+
## Cost-tuning levers
|
|
59
|
+
|
|
60
|
+
Outside this skill's scope but worth surfacing alongside the summary:
|
|
61
|
+
|
|
62
|
+
| Lever | Where | Effect |
|
|
63
|
+
| --- | --- | --- |
|
|
64
|
+
| `ARKAOS_LLM_PROVIDER` | env | Switch between Anthropic, OpenAI, Gemini adapters per the multi-LLM router. |
|
|
65
|
+
| `ANTHROPIC_BEDROCK_SERVICE_TIER` | env (Claude Code 2.1.122+) | `default` / `flex` / `priority`. Only relevant when routing through AWS Bedrock — `flex` cuts cost ~50 % on non-urgent workloads at the price of higher tail latency. |
|
|
66
|
+
| Agent `model:` field | `departments/*/agents/*.yaml` | Per-agent override per the model-routing matrix in CLAUDE.md. Mechanical roles default to haiku, C-suite to opus. |
|
|
67
|
+
| Quality Gate model | constitution rule (NON-NEGOTIABLE) | Marta / Eduardo / Francisca always run on opus regardless of the cost ceiling. |
|
|
68
|
+
|
|
58
69
|
## Non-negotiables
|
|
59
70
|
|
|
60
71
|
1. Read-only. This skill never edits state.
|
|
@@ -8,7 +8,7 @@ You are ArkaOS performing your nightly Dreaming session. Your job is to review e
|
|
|
8
8
|
- Read any file from any project
|
|
9
9
|
- Read git logs and diffs
|
|
10
10
|
- Search the web (WebSearch, Firecrawl)
|
|
11
|
-
- Write to Obsidian vault at
|
|
11
|
+
- Write to Obsidian vault at ${VAULT_PATH}/Projects/ArkaOS/
|
|
12
12
|
- Write to ~/.arkaos/ (captures, insights, logs, knowledge)
|
|
13
13
|
- Use browser for research
|
|
14
14
|
- Read online documentation
|
|
@@ -81,7 +81,7 @@ from core.cognition.memory.writer import DualWriter
|
|
|
81
81
|
|
|
82
82
|
writer = DualWriter(
|
|
83
83
|
obsidian_base=os.path.expanduser(
|
|
84
|
-
"
|
|
84
|
+
"${VAULT_PATH}/Projects/ArkaOS/Knowledge Base"
|
|
85
85
|
),
|
|
86
86
|
vector_db_path=os.path.expanduser("~/.arkaos/knowledge.db"),
|
|
87
87
|
)
|
|
@@ -113,7 +113,7 @@ Use `DualWriter.write()` for each KnowledgeEntry. This automatically writes to b
|
|
|
113
113
|
## Phase 6: Report + Evolution Metrics
|
|
114
114
|
|
|
115
115
|
Write daily report to Obsidian:
|
|
116
|
-
|
|
116
|
+
`${VAULT_PATH}/Projects/ArkaOS/Dreaming/YYYY-MM-DD.md`
|
|
117
117
|
|
|
118
118
|
Format:
|
|
119
119
|
```markdown
|
|
@@ -8,7 +8,7 @@ You are ArkaOS performing your daily Research session. Your job is to stay curre
|
|
|
8
8
|
- Read any file from any project
|
|
9
9
|
- Read git logs
|
|
10
10
|
- Search the web extensively (WebSearch, Firecrawl)
|
|
11
|
-
- Write to Obsidian vault at
|
|
11
|
+
- Write to Obsidian vault at ${VAULT_PATH}/Projects/ArkaOS/
|
|
12
12
|
- Write to ~/.arkaos/ (insights, logs, knowledge, profiles)
|
|
13
13
|
- Use browser for deep research
|
|
14
14
|
- Read online documentation, blogs, changelogs, GitHub releases
|
|
@@ -94,7 +94,7 @@ from core.cognition.memory.writer import DualWriter
|
|
|
94
94
|
|
|
95
95
|
writer = DualWriter(
|
|
96
96
|
obsidian_base=os.path.expanduser(
|
|
97
|
-
"
|
|
97
|
+
"${VAULT_PATH}/Projects/ArkaOS/Knowledge Base"
|
|
98
98
|
),
|
|
99
99
|
vector_db_path=os.path.expanduser("~/.arkaos/knowledge.db"),
|
|
100
100
|
)
|
|
@@ -114,7 +114,7 @@ writer.write(entry)
|
|
|
114
114
|
## Phase 5: Cross-Reference with Dreaming
|
|
115
115
|
|
|
116
116
|
1. Read tonight's Dreaming report (if it exists):
|
|
117
|
-
|
|
117
|
+
`${VAULT_PATH}/Projects/ArkaOS/Dreaming/YYYY-MM-DD.md`
|
|
118
118
|
|
|
119
119
|
2. Read pending insights from `~/.arkaos/insights.db`:
|
|
120
120
|
```python
|
|
@@ -145,7 +145,7 @@ store.save(insight)
|
|
|
145
145
|
## Phase 6: Intelligence Briefing
|
|
146
146
|
|
|
147
147
|
Write daily briefing to Obsidian:
|
|
148
|
-
|
|
148
|
+
`${VAULT_PATH}/Projects/ArkaOS/Research/YYYY-MM-DD.md`
|
|
149
149
|
|
|
150
150
|
Format:
|
|
151
151
|
```markdown
|
|
@@ -368,7 +368,12 @@ else
|
|
|
368
368
|
fi
|
|
369
369
|
|
|
370
370
|
# ─── Metrics ─────────────────────────────────────────────────────────────
|
|
371
|
+
# Count @-mentions in the prompt for at_mention_frequency telemetry. Mirrors
|
|
372
|
+
# the claude_code.at_mention OpenTelemetry event introduced in Claude Code
|
|
373
|
+
# 2.1.122, but works on any version because we count from the same input
|
|
374
|
+
# the hook already receives.
|
|
375
|
+
at_mentions=$(printf '%s' "$user_input" | grep -oE '(^|[[:space:]])@[A-Za-z0-9_./-]+' | wc -l | tr -d ' ')
|
|
371
376
|
elapsed=$(_hook_ms)
|
|
372
377
|
if [ "$elapsed" -gt 0 ] 2>/dev/null; then
|
|
373
|
-
echo "{\"hook\":\"user-prompt-submit-v2\",\"ms\":$elapsed}" >> "$CACHE_DIR/hook-metrics.jsonl" 2>/dev/null
|
|
378
|
+
echo "{\"hook\":\"user-prompt-submit-v2\",\"ms\":$elapsed,\"at_mentions\":${at_mentions:-0}}" >> "$CACHE_DIR/hook-metrics.jsonl" 2>/dev/null
|
|
374
379
|
fi
|
|
Binary file
|
|
@@ -21,16 +21,28 @@ from __future__ import annotations
|
|
|
21
21
|
|
|
22
22
|
import json
|
|
23
23
|
import re
|
|
24
|
+
from contextlib import contextmanager
|
|
24
25
|
from dataclasses import dataclass, field
|
|
26
|
+
from datetime import datetime, timezone
|
|
25
27
|
from pathlib import Path
|
|
26
28
|
from typing import Iterable
|
|
27
29
|
|
|
28
30
|
from core.obsidian import cataloger as _cataloger
|
|
29
31
|
from core.obsidian import relator as _relator
|
|
30
32
|
from core.obsidian.writer import ObsidianWriter
|
|
33
|
+
from core.shared import safe_session_id as _safe_session_id_module
|
|
31
34
|
|
|
35
|
+
try:
|
|
36
|
+
import fcntl # POSIX only
|
|
37
|
+
_HAS_FLOCK = True
|
|
38
|
+
except ImportError:
|
|
39
|
+
_HAS_FLOCK = False
|
|
32
40
|
|
|
33
|
-
|
|
41
|
+
|
|
42
|
+
# Re-export for backward compatibility with any external importers.
|
|
43
|
+
SAFE_SESSION_ID_RE = _safe_session_id_module.SAFE_SESSION_ID_RE
|
|
44
|
+
|
|
45
|
+
AUTO_DOC_TELEMETRY_PATH = Path.home() / ".arkaos" / "telemetry" / "auto_doc.jsonl"
|
|
34
46
|
|
|
35
47
|
_URL_RE = re.compile(r"https?://[^\s\)\]\"']+")
|
|
36
48
|
_FILE_PATH_RE = re.compile(r"(?:^|[\s`'])(/[A-Za-z0-9_./\-]+\.[A-Za-z0-9]+)")
|
|
@@ -58,8 +70,8 @@ _SYSTEM_PROMPT = (
|
|
|
58
70
|
"(150-300 words) summarising the session. Structure: short intro, "
|
|
59
71
|
"then markdown sections for Key Facts, Decisions, and Sources. "
|
|
60
72
|
"Preserve every URL and file path verbatim. Use Obsidian wikilinks "
|
|
61
|
-
"([[Topic]]) for reusable concepts.
|
|
62
|
-
"meta commentary about the model or prompt. Output only markdown."
|
|
73
|
+
"([[Topic]]) for reusable concepts. Do not include preamble, sign-off, "
|
|
74
|
+
"or meta commentary about the model or prompt. Output only markdown."
|
|
63
75
|
)
|
|
64
76
|
|
|
65
77
|
|
|
@@ -329,18 +341,52 @@ def _build_synthesis_prompt(learning: Learning) -> str:
|
|
|
329
341
|
return "\n".join(lines)
|
|
330
342
|
|
|
331
343
|
|
|
344
|
+
def _extract_key_facts(learning: Learning, limit: int = 5) -> list[str]:
|
|
345
|
+
"""Pull 3-5 bullet candidates from the learning content.
|
|
346
|
+
|
|
347
|
+
Used by the template fallback so both the LLM and template paths
|
|
348
|
+
produce a ``## Key Facts`` section in the same order as
|
|
349
|
+
``_SYSTEM_PROMPT`` requires.
|
|
350
|
+
"""
|
|
351
|
+
text = (learning.content or "").strip()
|
|
352
|
+
if not text:
|
|
353
|
+
return []
|
|
354
|
+
paragraphs = [p.strip() for p in re.split(r"\n\s*\n", text) if p.strip()]
|
|
355
|
+
bullets: list[str] = []
|
|
356
|
+
for para in paragraphs:
|
|
357
|
+
for raw in para.splitlines():
|
|
358
|
+
line = raw.strip().lstrip("-*• ").strip()
|
|
359
|
+
# Skip markdown section headings; they aren't facts.
|
|
360
|
+
if not line or line.startswith("#"):
|
|
361
|
+
continue
|
|
362
|
+
if line.startswith(">") or line.startswith("`"):
|
|
363
|
+
continue
|
|
364
|
+
if len(line) < 8:
|
|
365
|
+
continue
|
|
366
|
+
bullets.append(line[:240])
|
|
367
|
+
if len(bullets) >= limit:
|
|
368
|
+
return bullets
|
|
369
|
+
if len(bullets) >= limit:
|
|
370
|
+
break
|
|
371
|
+
return bullets
|
|
372
|
+
|
|
373
|
+
|
|
332
374
|
def _template_synthesize(learning: Learning) -> str:
|
|
333
|
-
|
|
375
|
+
# Section order mirrors _SYSTEM_PROMPT: Key Facts → Decisions →
|
|
376
|
+
# Sources. Keeping both synthesis paths aligned means downstream
|
|
377
|
+
# consumers (MOC generation, relator) never branch on provider.
|
|
378
|
+
parts: list[str] = [f"# {learning.topic}", ""]
|
|
334
379
|
parts.append(f"> {_AUTO_DOC_SUFFIX}.")
|
|
335
380
|
parts.append("")
|
|
336
381
|
if learning.content.strip():
|
|
337
382
|
parts.append(learning.content.strip())
|
|
338
383
|
parts.append("")
|
|
339
|
-
|
|
340
|
-
|
|
384
|
+
key_facts = _extract_key_facts(learning)
|
|
385
|
+
if key_facts:
|
|
386
|
+
parts.append("## Key Facts")
|
|
341
387
|
parts.append("")
|
|
342
|
-
for
|
|
343
|
-
parts.append(f"- {
|
|
388
|
+
for fact in key_facts:
|
|
389
|
+
parts.append(f"- {fact}")
|
|
344
390
|
parts.append("")
|
|
345
391
|
if learning.decisions:
|
|
346
392
|
parts.append("## Decisions")
|
|
@@ -348,6 +394,12 @@ def _template_synthesize(learning: Learning) -> str:
|
|
|
348
394
|
for dec in learning.decisions[:10]:
|
|
349
395
|
parts.append(f"- {dec}")
|
|
350
396
|
parts.append("")
|
|
397
|
+
if learning.sources:
|
|
398
|
+
parts.append("## Sources")
|
|
399
|
+
parts.append("")
|
|
400
|
+
for src in learning.sources[:20]:
|
|
401
|
+
parts.append(f"- {src}")
|
|
402
|
+
parts.append("")
|
|
351
403
|
return "\n".join(parts).rstrip() + "\n"
|
|
352
404
|
|
|
353
405
|
|
|
@@ -391,13 +443,78 @@ def _document_one(
|
|
|
391
443
|
meta.setdefault("auto_documented", True)
|
|
392
444
|
try:
|
|
393
445
|
plan = _cataloger.plan(body, meta)
|
|
394
|
-
except ValueError:
|
|
446
|
+
except ValueError as exc:
|
|
447
|
+
_log_auto_doc_event(
|
|
448
|
+
session_id=session_id,
|
|
449
|
+
event="classification-failed",
|
|
450
|
+
topic=learning.topic,
|
|
451
|
+
reason=str(exc),
|
|
452
|
+
)
|
|
453
|
+
return None
|
|
454
|
+
if plan is None:
|
|
455
|
+
_log_auto_doc_event(
|
|
456
|
+
session_id=session_id,
|
|
457
|
+
event="succeeded-empty",
|
|
458
|
+
topic=learning.topic,
|
|
459
|
+
reason="cataloger returned no plan",
|
|
460
|
+
)
|
|
395
461
|
return None
|
|
396
462
|
note_path = _cataloger.execute(plan, body, writer)
|
|
397
463
|
_relate_note(note_path, body, vault_path, plan)
|
|
464
|
+
_log_auto_doc_event(
|
|
465
|
+
session_id=session_id,
|
|
466
|
+
event="succeeded-wrote-note",
|
|
467
|
+
topic=learning.topic,
|
|
468
|
+
reason=str(note_path),
|
|
469
|
+
)
|
|
398
470
|
return note_path
|
|
399
471
|
|
|
400
472
|
|
|
473
|
+
@contextmanager
|
|
474
|
+
def _locked_append(path: Path):
|
|
475
|
+
"""Append to ``path`` under an exclusive advisory lock (POSIX flock).
|
|
476
|
+
|
|
477
|
+
Mirrors the pattern in ``core/workflow/flow_enforcer._locked_append``
|
|
478
|
+
— see that module for the platform-fallback rationale.
|
|
479
|
+
"""
|
|
480
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
481
|
+
fh = path.open("a", encoding="utf-8")
|
|
482
|
+
try:
|
|
483
|
+
if _HAS_FLOCK:
|
|
484
|
+
fcntl.flock(fh.fileno(), fcntl.LOCK_EX)
|
|
485
|
+
yield fh
|
|
486
|
+
finally:
|
|
487
|
+
if _HAS_FLOCK:
|
|
488
|
+
try:
|
|
489
|
+
fcntl.flock(fh.fileno(), fcntl.LOCK_UN)
|
|
490
|
+
except OSError:
|
|
491
|
+
pass
|
|
492
|
+
fh.close()
|
|
493
|
+
|
|
494
|
+
|
|
495
|
+
def _log_auto_doc_event(
|
|
496
|
+
*,
|
|
497
|
+
session_id: str,
|
|
498
|
+
event: str,
|
|
499
|
+
topic: str,
|
|
500
|
+
reason: str,
|
|
501
|
+
) -> None:
|
|
502
|
+
"""Append a structured auto-doc telemetry entry, degrade silently."""
|
|
503
|
+
entry = {
|
|
504
|
+
"ts": datetime.now(timezone.utc).isoformat(),
|
|
505
|
+
"session_id": session_id,
|
|
506
|
+
"event": event,
|
|
507
|
+
"topic": topic[:120],
|
|
508
|
+
"reason": reason[:240],
|
|
509
|
+
}
|
|
510
|
+
try:
|
|
511
|
+
with _locked_append(AUTO_DOC_TELEMETRY_PATH) as fh:
|
|
512
|
+
fh.write(json.dumps(entry) + "\n")
|
|
513
|
+
except OSError:
|
|
514
|
+
# Telemetry failures must never break the doc job.
|
|
515
|
+
return
|
|
516
|
+
|
|
517
|
+
|
|
401
518
|
def _relate_note(note_path: Path, body: str, vault_path: Path, plan) -> None:
|
|
402
519
|
try:
|
|
403
520
|
related = _relator.find_related(
|
|
@@ -428,6 +545,4 @@ def _append_related_block(note_path: Path, related) -> None:
|
|
|
428
545
|
|
|
429
546
|
|
|
430
547
|
def _safe_session_id(session_id: str) -> bool:
|
|
431
|
-
|
|
432
|
-
return False
|
|
433
|
-
return bool(SAFE_SESSION_ID_RE.match(session_id))
|
|
548
|
+
return _safe_session_id_module.safe_session_id(session_id) is not None
|
|
Binary file
|
|
@@ -7,13 +7,20 @@ from datetime import datetime, timezone
|
|
|
7
7
|
|
|
8
8
|
from core.cognition.capture.store import CaptureStore
|
|
9
9
|
from core.cognition.memory.schemas import RawCapture
|
|
10
|
+
from core.runtime.path_resolver import ProfileMissingError, project_root_regex
|
|
11
|
+
|
|
12
|
+
_FALLBACK_REGEX = re.compile(
|
|
13
|
+
r"((?:/Users|/home)/\S+/(?:Herd|Work|AIProjects|code|repos))[/\\]([^\s/\\]+)"
|
|
14
|
+
)
|
|
10
15
|
|
|
11
16
|
|
|
12
17
|
def _detect_project(digest: str) -> tuple[str, str]:
|
|
13
18
|
"""Try to detect project name and path from digest content."""
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
19
|
+
try:
|
|
20
|
+
regex = project_root_regex()
|
|
21
|
+
except ProfileMissingError:
|
|
22
|
+
regex = _FALLBACK_REGEX
|
|
23
|
+
path_match = regex.search(digest)
|
|
17
24
|
if path_match:
|
|
18
25
|
return path_match.group(2).rstrip("/"), path_match.group(1).rstrip("/")
|
|
19
26
|
return "unknown", os.getcwd()
|
|
Binary file
|
|
@@ -144,6 +144,11 @@ class ArkaScheduler:
|
|
|
144
144
|
claude_bin = self._resolve_claude_binary()
|
|
145
145
|
prompt_path = os.path.expanduser(schedule.prompt_file)
|
|
146
146
|
prompt_content = Path(prompt_path).read_text(encoding="utf-8")
|
|
147
|
+
try:
|
|
148
|
+
from core.runtime.path_resolver import resolve
|
|
149
|
+
prompt_content = resolve(prompt_content)
|
|
150
|
+
except Exception:
|
|
151
|
+
pass # fall back to raw template if profile unavailable
|
|
147
152
|
return [claude_bin, "-p", prompt_content, "--dangerously-skip-permissions"]
|
|
148
153
|
|
|
149
154
|
# ------------------------------------------------------------------
|
|
Binary file
|
|
@@ -28,7 +28,6 @@ from __future__ import annotations
|
|
|
28
28
|
import argparse
|
|
29
29
|
import json
|
|
30
30
|
import os
|
|
31
|
-
import re
|
|
32
31
|
import sys
|
|
33
32
|
import time
|
|
34
33
|
import uuid
|
|
@@ -36,9 +35,12 @@ from datetime import datetime, timezone
|
|
|
36
35
|
from pathlib import Path
|
|
37
36
|
from typing import Optional
|
|
38
37
|
|
|
38
|
+
from core.shared import safe_session_id as _safe_session_id_module
|
|
39
|
+
|
|
39
40
|
|
|
40
41
|
MAX_ATTEMPTS = 3
|
|
41
|
-
|
|
42
|
+
# Re-export for backward compatibility with any external importers.
|
|
43
|
+
SAFE_SESSION_ID_RE = _safe_session_id_module.SAFE_SESSION_ID_RE
|
|
42
44
|
_QUEUE_SUBDIRS = ("pending", "processing", "completed", "failed")
|
|
43
45
|
|
|
44
46
|
|
|
@@ -77,7 +79,7 @@ def enqueue_job(
|
|
|
77
79
|
"""Write a pending job file. Returns the job id."""
|
|
78
80
|
root = queue_root or _queue_root()
|
|
79
81
|
_ensure_queue(root)
|
|
80
|
-
safe =
|
|
82
|
+
safe = _safe_session_id_module.safe_session_id(session_id or "") or "unknown"
|
|
81
83
|
job_id = f"{int(time.time())}-{uuid.uuid4().hex[:12]}"
|
|
82
84
|
payload = {
|
|
83
85
|
"job_id": job_id,
|
|
Binary file
|
package/core/obsidian/writer.py
CHANGED
|
@@ -130,14 +130,15 @@ class ObsidianWriter:
|
|
|
130
130
|
|
|
131
131
|
if config_path.exists():
|
|
132
132
|
try:
|
|
133
|
+
from core.runtime.path_resolver import resolve
|
|
133
134
|
config = json.loads(config_path.read_text())
|
|
134
|
-
vault = config.get("vault_path", "")
|
|
135
|
-
if vault and Path(vault).exists():
|
|
135
|
+
vault = resolve(config.get("vault_path", ""))
|
|
136
|
+
if vault and not vault.startswith("${") and Path(vault).exists():
|
|
136
137
|
return Path(vault)
|
|
137
|
-
except (json.JSONDecodeError, OSError):
|
|
138
|
+
except (json.JSONDecodeError, OSError, Exception):
|
|
138
139
|
pass
|
|
139
140
|
|
|
140
|
-
# 3. Environment variable
|
|
141
|
+
# 3. Environment variable (legacy fallback only — path_resolver handles ARKAOS_VAULT_PATH)
|
|
141
142
|
env_vault = os.environ.get("ARKAOS_VAULT", "")
|
|
142
143
|
if env_vault and Path(env_vault).exists():
|
|
143
144
|
return Path(env_vault)
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|