arkaos 4.0.2 → 4.1.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.
- package/README.md +2 -2
- package/VERSION +1 -1
- package/arka/SKILL.md +16 -22
- package/arka/skills/flow/SKILL.md +108 -234
- package/config/claude-agents/eduardo-copy.md +53 -0
- package/config/claude-agents/francisca-tech.md +52 -0
- package/config/claude-agents/marta-cqo.md +52 -0
- package/config/claude-agents/paulo-tech-lead.md +50 -0
- package/config/cognition/schedules.yaml +15 -0
- package/config/constitution.yaml +17 -21
- package/config/hooks/_lib/workflow-classifier.sh +1 -1
- package/config/hooks/post-tool-use.sh +52 -536
- package/config/hooks/pre-tool-use.sh +46 -280
- package/config/hooks/session-start.sh +8 -10
- package/config/hooks/stop.sh +44 -424
- package/config/hooks/user-prompt-submit.sh +55 -457
- package/core/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/cognition/__pycache__/dreaming.cpython-313.pyc +0 -0
- package/core/cognition/dreaming.py +5 -0
- package/core/cognition/scheduler/__pycache__/daemon.cpython-313.pyc +0 -0
- package/core/cognition/scheduler/daemon.py +10 -0
- package/core/forge/__init__.py +2 -0
- package/core/forge/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/orchestrator.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/persistence.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/renderer.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/runtime_dispatcher.cpython-313.pyc +0 -0
- package/core/forge/__pycache__/schema.cpython-313.pyc +0 -0
- package/core/forge/orchestrator.py +50 -27
- package/core/forge/persistence.py +16 -0
- package/core/forge/renderer.py +16 -0
- package/core/forge/runtime_dispatcher.py +34 -19
- package/core/forge/schema.py +12 -0
- package/core/governance/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/governance/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/closing_marker_check.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/closing_marker_check.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/compliance_telemetry.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/constitution.cpython-312.pyc +0 -0
- package/core/governance/__pycache__/constitution.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/evidence_checks.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/kb_cite_check.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/meta_tag_check.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/meta_tag_check.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/qg_verdict.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/review_workflow.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/skill_proposer.cpython-313.pyc +0 -0
- package/core/governance/__pycache__/skill_proposer.cpython-314.pyc +0 -0
- package/core/governance/__pycache__/sycophancy_detector.cpython-314.pyc +0 -0
- package/core/governance/closing_marker_check.py +9 -5
- package/core/governance/compliance_telemetry.py +1 -1
- package/core/governance/compliance_telemetry_cli.py +1 -1
- package/core/governance/evidence_checks.py +421 -0
- package/core/governance/meta_tag_check.py +36 -0
- package/core/governance/qg_verdict.py +78 -0
- package/core/governance/review_workflow.py +34 -1
- package/core/governance/skill_proposer.py +2 -1
- package/core/hooks/__init__.py +12 -0
- package/core/hooks/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/hooks/__pycache__/_shared.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/_shared.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/_shared.cpython-314.pyc +0 -0
- package/core/hooks/__pycache__/post_tool_use.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/post_tool_use.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/pre_tool_use.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/pre_tool_use.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/pre_tool_use.cpython-314.pyc +0 -0
- package/core/hooks/__pycache__/stop.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/stop.cpython-313.pyc +0 -0
- package/core/hooks/__pycache__/stop.cpython-314.pyc +0 -0
- package/core/hooks/__pycache__/user_prompt_submit.cpython-312.pyc +0 -0
- package/core/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
- package/core/hooks/_shared.py +110 -0
- package/core/hooks/post_tool_use.py +689 -0
- package/core/hooks/pre_tool_use.py +267 -0
- package/core/hooks/stop.py +395 -0
- package/core/hooks/user_prompt_submit.py +600 -0
- package/core/jobs/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/jobs/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/jobs/__pycache__/auto_doc_worker.cpython-312.pyc +0 -0
- package/core/jobs/__pycache__/auto_doc_worker.cpython-314.pyc +0 -0
- package/core/jobs/__pycache__/manager.cpython-312.pyc +0 -0
- package/core/jobs/__pycache__/manager.cpython-314.pyc +0 -0
- package/core/knowledge/__pycache__/embedder.cpython-312.pyc +0 -0
- package/core/knowledge/__pycache__/embedder.cpython-313.pyc +0 -0
- package/core/knowledge/__pycache__/vector_store.cpython-312.pyc +0 -0
- package/core/knowledge/__pycache__/vector_store.cpython-313.pyc +0 -0
- package/core/knowledge/embedder.py +118 -11
- package/core/knowledge/vector_store.py +111 -14
- package/core/memory/__pycache__/session_store.cpython-314.pyc +0 -0
- package/core/obsidian/__pycache__/relator.cpython-313.pyc +0 -0
- package/core/obsidian/relator.py +18 -8
- package/core/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/base.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/base.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/base.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/capabilities_cli.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/capabilities_cli.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/claude_code.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/claude_code.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/claude_code.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/codex_cli.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/codex_cli.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/codex_cli.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/cost_governor.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/cost_governor.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/cursor.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/cursor.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/cursor.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/gemini_cli.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/gemini_cli.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/gemini_cli.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/llm_cost_telemetry.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/llm_provider.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/llm_provider.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/llm_provider.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/llm_retry.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/llm_retry.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/llm_retry.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/native_usage.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/native_usage.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/native_usage.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/ollama_provider.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/path_resolver.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/pricing.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/pricing.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/pricing.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/registry.cpython-312.pyc +0 -0
- package/core/runtime/__pycache__/registry.cpython-313.pyc +0 -0
- package/core/runtime/__pycache__/registry.cpython-314.pyc +0 -0
- package/core/runtime/__pycache__/squad_orchestrator.cpython-312.pyc +0 -0
- package/core/runtime/base.py +29 -0
- package/core/runtime/capabilities_cli.py +74 -0
- package/core/runtime/claude_code.py +8 -0
- package/core/runtime/codex_cli.py +205 -37
- package/core/runtime/cost_governor.py +184 -0
- package/core/runtime/cursor.py +8 -0
- package/core/runtime/gemini_cli.py +8 -0
- package/core/runtime/llm_provider.py +32 -13
- package/core/runtime/llm_retry.py +233 -0
- package/core/runtime/native_usage.py +186 -0
- package/core/runtime/pricing.py +9 -0
- package/core/runtime/registry.py +3 -0
- package/core/shared/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/shared/__pycache__/safe_session_id.cpython-314.pyc +0 -0
- package/core/shared/__pycache__/test_evidence.cpython-313-pytest-9.1.1.pyc +0 -0
- package/core/shared/__pycache__/test_evidence.cpython-313.pyc +0 -0
- package/core/shared/test_evidence.py +34 -0
- package/core/synapse/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/agent_experiences_layer.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/cache.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/engine.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/engine.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/graph_context_layer.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/kb_cache.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/kb_cache.cpython-314.pyc +0 -0
- package/core/synapse/__pycache__/layers.cpython-312.pyc +0 -0
- package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
- package/core/synapse/__pycache__/layers.cpython-314.pyc +0 -0
- package/core/synapse/engine.py +7 -0
- package/core/synapse/graph_context_layer.py +212 -0
- package/core/synapse/kb_cache.py +3 -0
- package/core/synapse/layers.py +104 -20
- package/core/workflow/__pycache__/__init__.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/enforcer.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/engine.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/flow_enforcer.cpython-312.pyc +0 -0
- package/core/workflow/__pycache__/flow_enforcer.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/flow_enforcer.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/gate_checkpoint.cpython-312.pyc +0 -0
- package/core/workflow/__pycache__/gate_checkpoint.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/gate_checkpoint.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/loader.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/marker_cache.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/research_gate.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/research_gate.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/rules_registry.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/schema.cpython-314.pyc +0 -0
- package/core/workflow/__pycache__/specialist_enforcer.cpython-312.pyc +0 -0
- package/core/workflow/__pycache__/specialist_enforcer.cpython-313.pyc +0 -0
- package/core/workflow/__pycache__/state.cpython-314.pyc +0 -0
- package/core/workflow/enforcer.py +5 -16
- package/core/workflow/flow_enforcer.py +36 -11
- package/core/workflow/gate_checkpoint.py +149 -0
- package/core/workflow/research_gate.py +15 -3
- package/core/workflow/rules_registry.py +137 -389
- package/core/workflow/specialist_enforcer.py +21 -4
- package/dashboard/app/pages/knowledge/index.vue +4 -1
- package/departments/ops/skills/n8n-flow/SKILL.md +13 -0
- package/departments/ops/skills/workflow-automate/SKILL.md +16 -0
- package/departments/quality/SKILL.md +45 -5
- package/departments/quality/agents/copy-director.yaml +3 -1
- package/departments/quality/agents/tech-director.yaml +3 -1
- package/installer/adapters/claude-code.js +46 -2
- package/installer/config-seed.js +39 -14
- package/installer/doctor.js +8 -0
- package/installer/graphify.js +153 -0
- package/installer/index.js +20 -0
- package/installer/init.js +11 -0
- package/installer/update.js +19 -0
- package/package.json +1 -1
- package/pyproject.toml +3 -1
- package/scripts/__pycache__/dashboard-api.cpython-313.pyc +0 -0
- package/scripts/__pycache__/knowledge-index.cpython-313.pyc +0 -0
- package/scripts/__pycache__/synapse-bridge.cpython-312.pyc +0 -0
- package/scripts/__pycache__/synapse-bridge.cpython-313.pyc +0 -0
- package/scripts/knowledge-index.py +8 -2
- package/scripts/synapse-bridge.py +102 -26
|
@@ -594,7 +594,10 @@ async function handleSearch() {
|
|
|
594
594
|
}
|
|
595
595
|
}
|
|
596
596
|
|
|
597
|
-
|
|
597
|
+
// RAG honesty (PR-3 v4.1): keyword-degraded results carry score=null —
|
|
598
|
+
// there is no similarity to show, so label them instead of faking "0%".
|
|
599
|
+
function formatScore(score: number | null): string {
|
|
600
|
+
if (score === null || score === undefined) return 'keyword match'
|
|
598
601
|
return `${(score * 100).toFixed(0)}%`
|
|
599
602
|
}
|
|
600
603
|
|
|
@@ -27,6 +27,19 @@ does not replace the vault.
|
|
|
27
27
|
|
|
28
28
|
n8n workflow design: AI nodes, webhooks, branching, error handling.
|
|
29
29
|
|
|
30
|
+
## Event-driven watching (Monitor)
|
|
31
|
+
|
|
32
|
+
When validating a deployed flow, prefer the runtime's Monitor tool over
|
|
33
|
+
polling loops — zero idle tokens versus a model turn per interval.
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
Monitor: watch the n8n execution log (or webhook endpoint output) until
|
|
37
|
+
/error|failed|succeeded/ matches, then inspect that execution and report.
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
Fallback: on runtimes without Monitor, use a wide-interval `/loop` and
|
|
41
|
+
stop it as soon as the first relevant event lands.
|
|
42
|
+
|
|
30
43
|
## Output
|
|
31
44
|
|
|
32
45
|
n8n flow spec with node configuration and AI integration points
|
|
@@ -71,4 +71,20 @@ Follow the [Computer Use Availability Check](/arka) for availability checking.
|
|
|
71
71
|
/schedule hourly — monitor all active automations and report error rate
|
|
72
72
|
```
|
|
73
73
|
|
|
74
|
+
## Event-driven watching (Monitor)
|
|
75
|
+
|
|
76
|
+
Prefer the runtime's Monitor tool over polling loops — it blocks on a
|
|
77
|
+
condition and burns zero idle tokens, while `/loop` re-invokes the model
|
|
78
|
+
every interval even when nothing changed.
|
|
79
|
+
|
|
80
|
+
Pattern (watch logs, act on first match):
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
Monitor: tail the automation log until /ERROR|failed/ matches,
|
|
84
|
+
then diagnose that execution and alert the operator.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Use polling (`/loop`) only as fallback on runtimes without Monitor, and
|
|
88
|
+
pick the widest interval the SLA tolerates.
|
|
89
|
+
|
|
74
90
|
## Output → Workflow diagram + platform config + error handling spec
|
|
@@ -15,19 +15,55 @@ allowed-tools: [Read, Write, Edit, Bash, Grep, Glob, Agent]
|
|
|
15
15
|
The Quality Gate is NOT invoked by the user directly. It runs automatically as the
|
|
16
16
|
second-to-last phase of EVERY workflow in EVERY department.
|
|
17
17
|
|
|
18
|
+
The gate is EVIDENCE INTERPRETATION, not persona role-play. The verdict DERIVES
|
|
19
|
+
from executable check output; reviewers interpret the report, they never
|
|
20
|
+
override it with narrative.
|
|
21
|
+
|
|
18
22
|
```
|
|
19
23
|
Any Department Workflow:
|
|
20
24
|
...
|
|
21
25
|
Phase N-1: QUALITY GATE
|
|
22
|
-
1.
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
1. Run the evidence engine over the project/diff:
|
|
27
|
+
python -m core.governance.evidence_checks <project_dir> \
|
|
28
|
+
[--changed-files f1,f2] [--test-command '...'] --json
|
|
29
|
+
2. Marta dispatches Eduardo + Francisca to INTERPRET the report:
|
|
30
|
+
- Eduardo: spellcheck section + prose review of changed copy
|
|
31
|
+
- Francisca: lint / typecheck / tests / coverage / security-grep
|
|
32
|
+
3. Verdict rules (binary, evidence-floored):
|
|
33
|
+
- overall == "fail" → REJECTED. Always. No persona can override
|
|
34
|
+
failing evidence with narrative.
|
|
35
|
+
- overall == "pass" → APPROVED only if reviewers find no blocker
|
|
36
|
+
the checks cannot see (logic, copy, UX).
|
|
37
|
+
- overall == "insufficient-evidence" → APPROVED only with an
|
|
38
|
+
explicit justification in the verdict notes; otherwise REJECTED.
|
|
39
|
+
4. If ANY reviewer rejects → work loops back with the blockers list
|
|
26
40
|
5. If ALL approve → Marta issues final APPROVED verdict
|
|
27
41
|
Phase N: DELIVERY
|
|
28
42
|
→ Only reaches user after APPROVED from all three
|
|
29
43
|
```
|
|
30
44
|
|
|
45
|
+
## Reviewer Dispatch Contract
|
|
46
|
+
|
|
47
|
+
Reviewers are dispatched via the Agent tool with STRUCTURED OUTPUT. The output
|
|
48
|
+
schema is `QG_VERDICT_JSON_SCHEMA` from `core.governance.qg_verdict` (the JSON
|
|
49
|
+
Schema of the `QGVerdict` pydantic model):
|
|
50
|
+
|
|
51
|
+
from core.governance.qg_verdict import QG_VERDICT_JSON_SCHEMA
|
|
52
|
+
|
|
53
|
+
Agent(
|
|
54
|
+
subagent_type="francisca-tech", # .claude/agents/francisca-tech.md
|
|
55
|
+
model="sonnet", # opus ONLY for Tier 0/security scope
|
|
56
|
+
prompt="<evidence report JSON> + <diff summary> — interpret and return QGVerdict",
|
|
57
|
+
output_schema=QG_VERDICT_JSON_SCHEMA, # structured-output param
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
Each reviewer MUST return a `QGVerdict` JSON object: `verdict`
|
|
61
|
+
(APPROVED|REJECTED), `evidence_report` (embedded summary), `blockers`
|
|
62
|
+
(`[{check, detail, file}]`), `reviewer`, `model_used`, `notes`. The pydantic
|
|
63
|
+
model rejects APPROVED-with-failing-evidence at validation time, and
|
|
64
|
+
`core.governance.review_workflow` raises `ValueError` on any attempt to record
|
|
65
|
+
an approval over `evidence_overall == "fail"`.
|
|
66
|
+
|
|
31
67
|
## Squad
|
|
32
68
|
|
|
33
69
|
| Agent | Role | Tier | DISC | Scope |
|
|
@@ -70,7 +106,11 @@ There is no "APPROVED WITH CAVEATS". It's binary. Fix issues first.
|
|
|
70
106
|
When dispatching subagent work via the Task tool, include the `model` parameter from the target agent's YAML `model:` field:
|
|
71
107
|
|
|
72
108
|
- Agent YAMLs at `departments/*/agents/*.yaml` have `model: opus | sonnet | haiku`
|
|
73
|
-
- Quality Gate
|
|
109
|
+
- Quality Gate reviewers (Eduardo/Francisca) run on `sonnet` by DEFAULT.
|
|
110
|
+
`opus` is used ONLY when the diff is Tier 0 scope (constitution, security,
|
|
111
|
+
release pipeline, installer auth) or the deliverable is security-flagged.
|
|
112
|
+
- Marta keeps her veto regardless of the model tier the review ran on —
|
|
113
|
+
the verdict derives from evidence, not from model size.
|
|
74
114
|
- Default to `sonnet` if the agent YAML has no `model` field
|
|
75
115
|
- Mechanical tasks (commit messages, routing, keyword extraction) use `model: haiku`
|
|
76
116
|
|
|
@@ -3,7 +3,9 @@ name: Eduardo
|
|
|
3
3
|
role: Copy & Language Director
|
|
4
4
|
department: quality
|
|
5
5
|
tier: 0
|
|
6
|
-
|
|
6
|
+
# PR-4 evidence QG (v4.1): reviewers run sonnet by default; dispatch on
|
|
7
|
+
# opus ONLY for Tier 0/security-scope diffs (see constitution model-routing).
|
|
8
|
+
model: sonnet
|
|
7
9
|
|
|
8
10
|
behavioral_dna:
|
|
9
11
|
disc:
|
|
@@ -3,7 +3,9 @@ name: Francisca
|
|
|
3
3
|
role: Technical & UX Quality Director
|
|
4
4
|
department: quality
|
|
5
5
|
tier: 0
|
|
6
|
-
|
|
6
|
+
# PR-4 evidence QG (v4.1): reviewers run sonnet by default; dispatch on
|
|
7
|
+
# opus ONLY for Tier 0/security-scope diffs (see constitution model-routing).
|
|
8
|
+
model: sonnet
|
|
7
9
|
|
|
8
10
|
behavioral_dna:
|
|
9
11
|
disc:
|
|
@@ -1,8 +1,52 @@
|
|
|
1
|
-
import { existsSync, readFileSync, writeFileSync, mkdirSync } from "node:fs";
|
|
2
|
-
import { join, dirname } from "node:path";
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, copyFileSync, statSync } from "node:fs";
|
|
2
|
+
import { join, dirname, resolve } from "node:path";
|
|
3
3
|
import { platform } from "node:os";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
4
5
|
|
|
5
6
|
const IS_WINDOWS = platform() === "win32";
|
|
7
|
+
const ARKAOS_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Deploy the packaged Quality Gate / squad-lead subagent definitions
|
|
11
|
+
* (`config/claude-agents/*.md` in the ArkaOS package: marta-cqo,
|
|
12
|
+
* eduardo-copy, francisca-tech, paulo-tech-lead) into a project's
|
|
13
|
+
* `.claude/agents/` directory, so Claude Code can dispatch them as real
|
|
14
|
+
* subagent types with structured QGVerdict output (PR-4 evidence
|
|
15
|
+
* Quality Gate). Source lives under config/ because `.claude/` is
|
|
16
|
+
* gitignored in the ArkaOS repo itself.
|
|
17
|
+
*
|
|
18
|
+
* Same merge philosophy as configureHooks: never delete user files,
|
|
19
|
+
* overwrite only the ArkaOS-owned definitions by name. Best-effort —
|
|
20
|
+
* a missing source dir (older package) is a silent no-op.
|
|
21
|
+
*
|
|
22
|
+
* @param {string} projectDir - project root (contains or gets .claude/)
|
|
23
|
+
* @param {string} [sourceRoot] - override for tests; defaults to package root
|
|
24
|
+
* @returns {number} count of agent definitions deployed
|
|
25
|
+
*/
|
|
26
|
+
export function deployProjectAgents(projectDir, sourceRoot = ARKAOS_ROOT) {
|
|
27
|
+
const agentsSrc = join(sourceRoot, "config", "claude-agents");
|
|
28
|
+
if (!existsSync(agentsSrc)) return 0;
|
|
29
|
+
|
|
30
|
+
const agentsDest = join(projectDir, ".claude", "agents");
|
|
31
|
+
let deployed = 0;
|
|
32
|
+
try {
|
|
33
|
+
mkdirSync(agentsDest, { recursive: true });
|
|
34
|
+
for (const file of readdirSync(agentsSrc)) {
|
|
35
|
+
if (!file.endsWith(".md")) continue;
|
|
36
|
+
const srcFile = join(agentsSrc, file);
|
|
37
|
+
try {
|
|
38
|
+
if (!statSync(srcFile).isFile()) continue;
|
|
39
|
+
copyFileSync(srcFile, join(agentsDest, file));
|
|
40
|
+
deployed++;
|
|
41
|
+
} catch {
|
|
42
|
+
// Best-effort — a single unreadable agent file shouldn't break init.
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
} catch {
|
|
46
|
+
return deployed;
|
|
47
|
+
}
|
|
48
|
+
return deployed;
|
|
49
|
+
}
|
|
6
50
|
|
|
7
51
|
/**
|
|
8
52
|
* Build a complete inner hook-entry object for Claude Code's settings.json.
|
package/installer/config-seed.js
CHANGED
|
@@ -1,13 +1,19 @@
|
|
|
1
|
-
// ~/.arkaos/config.json seed/migration (PR19 v2.41.0).
|
|
1
|
+
// ~/.arkaos/config.json seed/migration (PR19 v2.41.0, extended PR-3 v4.1).
|
|
2
2
|
//
|
|
3
3
|
// Run on every `npx arkaos install` and `npx arkaos@latest update`.
|
|
4
|
-
// Idempotent:
|
|
5
|
-
//
|
|
4
|
+
// Idempotent: seeds each template key only when it is unset. Explicit
|
|
5
|
+
// user choice (true OR false) is always preserved.
|
|
6
|
+
//
|
|
7
|
+
// Seeded template keys (operator decisions):
|
|
8
|
+
// hooks.hardEnforcement = true (PR19 v2.41.0)
|
|
9
|
+
// hooks.kbFirst = true (PR-3 v4.1 — KB-first ON out of the box)
|
|
6
10
|
//
|
|
7
11
|
// Returns a status object:
|
|
8
12
|
// { action: "created" | "added-key" | "noop"
|
|
9
13
|
// | "preserved-user-false" | "rewrote-corrupt" }
|
|
10
14
|
// so the installer caller can log a human-readable line per run.
|
|
15
|
+
// Precedence when several keys differ: created/rewrote-corrupt >
|
|
16
|
+
// added-key > preserved-user-false > noop.
|
|
11
17
|
|
|
12
18
|
import {
|
|
13
19
|
existsSync, mkdirSync, readFileSync, writeFileSync, renameSync, copyFileSync,
|
|
@@ -15,11 +21,20 @@ import {
|
|
|
15
21
|
import { homedir } from "node:os";
|
|
16
22
|
import { join, dirname } from "node:path";
|
|
17
23
|
|
|
24
|
+
// Template: every hooks.* key seeded to `true` when unset.
|
|
25
|
+
const SEEDED_HOOK_KEYS = ["hardEnforcement", "kbFirst"];
|
|
26
|
+
|
|
27
|
+
function defaultConfig() {
|
|
28
|
+
const hooks = {};
|
|
29
|
+
for (const key of SEEDED_HOOK_KEYS) hooks[key] = true;
|
|
30
|
+
return { hooks };
|
|
31
|
+
}
|
|
32
|
+
|
|
18
33
|
export function seedArkaosConfig({ home = homedir() } = {}) {
|
|
19
34
|
const cfgPath = join(home, ".arkaos", "config.json");
|
|
20
35
|
|
|
21
36
|
if (!existsSync(cfgPath)) {
|
|
22
|
-
writeConfig(cfgPath,
|
|
37
|
+
writeConfig(cfgPath, defaultConfig());
|
|
23
38
|
return { action: "created", path: cfgPath };
|
|
24
39
|
}
|
|
25
40
|
|
|
@@ -33,24 +48,34 @@ export function seedArkaosConfig({ home = homedir() } = {}) {
|
|
|
33
48
|
// Corrupt JSON — keep the broken copy for recovery, write safe default.
|
|
34
49
|
const backup = `${cfgPath}.broken-${Date.now()}`;
|
|
35
50
|
try { copyFileSync(cfgPath, backup); } catch { /* best effort */ }
|
|
36
|
-
writeConfig(cfgPath,
|
|
51
|
+
writeConfig(cfgPath, defaultConfig());
|
|
37
52
|
return { action: "rewrote-corrupt", path: cfgPath, backup };
|
|
38
53
|
}
|
|
39
54
|
|
|
40
55
|
config.hooks = config.hooks && typeof config.hooks === "object" ? config.hooks : {};
|
|
41
|
-
const current = config.hooks.hardEnforcement;
|
|
42
56
|
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
let added = false;
|
|
58
|
+
let preservedFalse = false;
|
|
59
|
+
for (const key of SEEDED_HOOK_KEYS) {
|
|
60
|
+
const current = config.hooks[key];
|
|
61
|
+
if (current === true) continue;
|
|
62
|
+
if (current === false) {
|
|
63
|
+
preservedFalse = true; // explicit user choice — never overwrite
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
// Key unset (undefined, null, or any non-boolean) — set to true.
|
|
67
|
+
config.hooks[key] = true;
|
|
68
|
+
added = true;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (added) {
|
|
72
|
+
writeConfig(cfgPath, config);
|
|
73
|
+
return { action: "added-key", path: cfgPath };
|
|
45
74
|
}
|
|
46
|
-
if (
|
|
75
|
+
if (preservedFalse) {
|
|
47
76
|
return { action: "preserved-user-false", path: cfgPath };
|
|
48
77
|
}
|
|
49
|
-
|
|
50
|
-
// Key unset (undefined, null, or any non-boolean) — set to true.
|
|
51
|
-
config.hooks.hardEnforcement = true;
|
|
52
|
-
writeConfig(cfgPath, config);
|
|
53
|
-
return { action: "added-key", path: cfgPath };
|
|
78
|
+
return { action: "noop", path: cfgPath };
|
|
54
79
|
}
|
|
55
80
|
|
|
56
81
|
function writeConfig(cfgPath, payload) {
|
package/installer/doctor.js
CHANGED
|
@@ -5,6 +5,7 @@ import { execSync } from "node:child_process";
|
|
|
5
5
|
import { getArkaosPython, getVenvPython, canImportCore, getRepoRoot, diagnoseVenv, ensureVenvHealthy } from "./python-resolver.js";
|
|
6
6
|
import { IS_WINDOWS, HOOK_EXT, CMD_FINDER } from "./platform.js";
|
|
7
7
|
import { checkNode, checkObsidian, checkOllama } from "./system-tools.js";
|
|
8
|
+
import { graphifyDoctor } from "./graphify.js";
|
|
8
9
|
|
|
9
10
|
const INSTALL_DIR = join(homedir(), ".arkaos");
|
|
10
11
|
|
|
@@ -195,6 +196,13 @@ const checks = [
|
|
|
195
196
|
},
|
|
196
197
|
fix: () => "Upgrade Claude Code: npm install -g @anthropic-ai/claude-code@latest",
|
|
197
198
|
},
|
|
199
|
+
{
|
|
200
|
+
name: "graphify",
|
|
201
|
+
description: "Graphify CLI present (grounding layer — code knowledge graphs)",
|
|
202
|
+
severity: "warn",
|
|
203
|
+
check: () => graphifyDoctor().installed,
|
|
204
|
+
fix: () => graphifyDoctor().hint || "Run: uv tool install graphifyy (or pipx install graphifyy)",
|
|
205
|
+
},
|
|
198
206
|
{
|
|
199
207
|
name: "magic-api-key",
|
|
200
208
|
description: "Magic API key configured (frontend UI/UX — Magic MCP)",
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Graphify — code knowledge-graph grounding layer for `npx arkaos install`
|
|
2
|
+
// and `npx arkaos@latest update`.
|
|
3
|
+
//
|
|
4
|
+
// Graphify (PyPI package `graphifyy`, CLI `graphify`) extracts a local
|
|
5
|
+
// tree-sitter code graph into `<project>/graphify-out/graph.json` with
|
|
6
|
+
// EXTRACTED/INFERRED/AMBIGUOUS confidence tags. ArkaOS uses that graph as
|
|
7
|
+
// the official grounding layer (Synapse L2.7) so answers about a codebase
|
|
8
|
+
// cite real nodes + source locations instead of inventing structure.
|
|
9
|
+
//
|
|
10
|
+
// Install strategy (best-effort, in order):
|
|
11
|
+
// 1. `graphify` already on PATH → no-op.
|
|
12
|
+
// 2. `uv tool install graphifyy` (preferred)
|
|
13
|
+
// 3. `pipx install graphifyy` (fallback)
|
|
14
|
+
// 4. Neither manager available → print a one-line manual hint.
|
|
15
|
+
// Then `graphify install` registers the Graphify skill with AI assistants
|
|
16
|
+
// (same best-effort tolerance).
|
|
17
|
+
//
|
|
18
|
+
// Invariants (.claude/rules/node-installer.md):
|
|
19
|
+
// - ESM, os.homedir()/path.join only, never hardcoded paths.
|
|
20
|
+
// - No interactive prompts; safe in headless/CI runs.
|
|
21
|
+
// - Never throws — the installer must NEVER fail because of Graphify.
|
|
22
|
+
|
|
23
|
+
import { execSync, spawnSync } from "node:child_process";
|
|
24
|
+
import { CMD_FINDER } from "./platform.js";
|
|
25
|
+
|
|
26
|
+
const PYPI_PACKAGE = "graphifyy"; // double y — the CLI binary is `graphify`
|
|
27
|
+
const MANUAL_HINT =
|
|
28
|
+
"Graphify not installed — install manually: uv tool install graphifyy (or pipx install graphifyy)";
|
|
29
|
+
|
|
30
|
+
function findBinary(name) {
|
|
31
|
+
try {
|
|
32
|
+
const out = execSync(`${CMD_FINDER} ${name}`, {
|
|
33
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
34
|
+
}).toString().trim().split(/\r?\n/)[0];
|
|
35
|
+
return out || null;
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function readVersion(command) {
|
|
42
|
+
try {
|
|
43
|
+
const out = execSync(command, { stdio: ["ignore", "pipe", "ignore"] }).toString();
|
|
44
|
+
const match = out.match(/(\d+\.\d+(?:\.\d+)?)/);
|
|
45
|
+
return match ? match[1] : null;
|
|
46
|
+
} catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Try one package-manager install of `graphifyy`. Returns true when the
|
|
52
|
+
// child exited 0. Never throws.
|
|
53
|
+
function tryInstallVia(manager) {
|
|
54
|
+
const args = manager === "uv" ? ["tool", "install", PYPI_PACKAGE] : ["install", PYPI_PACKAGE];
|
|
55
|
+
const out = spawnSync(manager, args, {
|
|
56
|
+
timeout: 180_000,
|
|
57
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
58
|
+
encoding: "utf-8",
|
|
59
|
+
});
|
|
60
|
+
return !out.error && out.status === 0;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Run `graphify install` so the Graphify skill registers with the AI
|
|
64
|
+
// assistant runtimes it detects. Best-effort: failure is reported in the
|
|
65
|
+
// status object, never thrown.
|
|
66
|
+
function runGraphifySkillInstall() {
|
|
67
|
+
const out = spawnSync("graphify", ["install"], {
|
|
68
|
+
timeout: 60_000,
|
|
69
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
70
|
+
encoding: "utf-8",
|
|
71
|
+
});
|
|
72
|
+
if (out.error || out.status !== 0) {
|
|
73
|
+
const reason = (out.stderr || out.error?.message || "unknown").trim().slice(0, 200);
|
|
74
|
+
return { action: "failed", reason };
|
|
75
|
+
}
|
|
76
|
+
return { action: "installed" };
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Ensure the Graphify CLI is present and its skill is registered.
|
|
81
|
+
*
|
|
82
|
+
* Returns a status object — never throws, never blocks the install:
|
|
83
|
+
* { binary: { installed, location?, version?, action, hint? },
|
|
84
|
+
* skillInstall: { action, reason? } }
|
|
85
|
+
*
|
|
86
|
+
* `options.dryRun` skips installation attempts and `graphify install`.
|
|
87
|
+
*/
|
|
88
|
+
export function ensureGraphify(options = {}) {
|
|
89
|
+
const result = { binary: null, skillInstall: { action: "skipped", reason: "binary-missing" } };
|
|
90
|
+
try {
|
|
91
|
+
let location = findBinary("graphify");
|
|
92
|
+
|
|
93
|
+
if (!location && !options.dryRun) {
|
|
94
|
+
if (findBinary("uv") && tryInstallVia("uv")) {
|
|
95
|
+
location = findBinary("graphify");
|
|
96
|
+
} else if (findBinary("pipx") && tryInstallVia("pipx")) {
|
|
97
|
+
location = findBinary("graphify");
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (!location) {
|
|
102
|
+
result.binary = {
|
|
103
|
+
installed: false,
|
|
104
|
+
action: options.dryRun ? "dry-run" : "manual-install-needed",
|
|
105
|
+
hint: MANUAL_HINT,
|
|
106
|
+
};
|
|
107
|
+
return result;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
result.binary = {
|
|
111
|
+
installed: true,
|
|
112
|
+
location,
|
|
113
|
+
version: readVersion("graphify --version"),
|
|
114
|
+
action: "present",
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
if (options.dryRun) {
|
|
118
|
+
result.skillInstall = { action: "skipped", reason: "dry-run" };
|
|
119
|
+
} else {
|
|
120
|
+
result.skillInstall = runGraphifySkillInstall();
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
} catch (err) {
|
|
124
|
+
result.binary = result.binary || {
|
|
125
|
+
installed: false,
|
|
126
|
+
action: "check-failed",
|
|
127
|
+
hint: MANUAL_HINT,
|
|
128
|
+
};
|
|
129
|
+
result.skillInstall = { action: "failed", reason: err.message };
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Doctor check for installer/doctor.js — is the `graphify` binary present,
|
|
136
|
+
* and at which version? Never throws.
|
|
137
|
+
*/
|
|
138
|
+
export function graphifyDoctor() {
|
|
139
|
+
try {
|
|
140
|
+
const location = findBinary("graphify");
|
|
141
|
+
if (!location) {
|
|
142
|
+
return { installed: false, location: null, version: null, hint: MANUAL_HINT };
|
|
143
|
+
}
|
|
144
|
+
return {
|
|
145
|
+
installed: true,
|
|
146
|
+
location,
|
|
147
|
+
version: readVersion("graphify --version"),
|
|
148
|
+
hint: null,
|
|
149
|
+
};
|
|
150
|
+
} catch {
|
|
151
|
+
return { installed: false, location: null, version: null, hint: MANUAL_HINT };
|
|
152
|
+
}
|
|
153
|
+
}
|
package/installer/index.js
CHANGED
|
@@ -394,6 +394,26 @@ export async function install({ runtime, path, force, skipSystem, withOllama })
|
|
|
394
394
|
console.log(` Warning: could not set up frontend tooling (${err.message})`);
|
|
395
395
|
}
|
|
396
396
|
|
|
397
|
+
// Graphify grounding layer — code knowledge graphs (PyPI `graphifyy`,
|
|
398
|
+
// CLI `graphify`). Best-effort: a missing binary or failed install can
|
|
399
|
+
// NEVER fail `npx arkaos install`; we degrade to a one-line hint.
|
|
400
|
+
try {
|
|
401
|
+
const { ensureGraphify } = await import("./graphify.js");
|
|
402
|
+
const gf = ensureGraphify();
|
|
403
|
+
if (gf.binary?.installed) {
|
|
404
|
+
console.log(` Graphify ready${gf.binary.version ? ` (v${gf.binary.version})` : ""}.`);
|
|
405
|
+
if (gf.skillInstall?.action === "installed") {
|
|
406
|
+
console.log(" Graphify skill registered (graphify install).");
|
|
407
|
+
} else if (gf.skillInstall?.action === "failed") {
|
|
408
|
+
console.log(` Graphify skill registration failed (${gf.skillInstall.reason}).`);
|
|
409
|
+
}
|
|
410
|
+
} else if (gf.binary?.hint) {
|
|
411
|
+
console.log(` ${gf.binary.hint}`);
|
|
412
|
+
}
|
|
413
|
+
} catch (err) {
|
|
414
|
+
console.log(` Warning: could not set up Graphify (${err.message})`);
|
|
415
|
+
}
|
|
416
|
+
|
|
397
417
|
const manifest = {
|
|
398
418
|
version: VERSION,
|
|
399
419
|
runtime,
|
package/installer/init.js
CHANGED
|
@@ -56,6 +56,17 @@ export async function init({ path }) {
|
|
|
56
56
|
console.log(` Created: .claude/settings.local.json`);
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
+
// Deploy Quality Gate / squad-lead subagent definitions (.claude/agents/)
|
|
60
|
+
try {
|
|
61
|
+
const { deployProjectAgents } = await import("./adapters/claude-code.js");
|
|
62
|
+
const agentCount = deployProjectAgents(projectDir);
|
|
63
|
+
if (agentCount > 0) {
|
|
64
|
+
console.log(` Created: .claude/agents/ (${agentCount} subagent definitions)`);
|
|
65
|
+
}
|
|
66
|
+
} catch {
|
|
67
|
+
// Best-effort — init must not fail on optional agent deployment.
|
|
68
|
+
}
|
|
69
|
+
|
|
59
70
|
console.log(`
|
|
60
71
|
Project initialized for ArkaOS.
|
|
61
72
|
|
package/installer/update.js
CHANGED
|
@@ -478,6 +478,25 @@ export async function update() {
|
|
|
478
478
|
console.log(` ⚠ Could not set up frontend tooling (${err.message})`);
|
|
479
479
|
}
|
|
480
480
|
|
|
481
|
+
// Graphify grounding layer — same wiring as installer/index.js. Best
|
|
482
|
+
// effort: `npx arkaos update` never fails because of Graphify.
|
|
483
|
+
try {
|
|
484
|
+
const { ensureGraphify } = await import("./graphify.js");
|
|
485
|
+
const gf = ensureGraphify();
|
|
486
|
+
if (gf.binary?.installed) {
|
|
487
|
+
console.log(` ✓ Graphify ready${gf.binary.version ? ` (v${gf.binary.version})` : ""}`);
|
|
488
|
+
if (gf.skillInstall?.action === "installed") {
|
|
489
|
+
console.log(" ✓ Graphify skill registered (graphify install)");
|
|
490
|
+
} else if (gf.skillInstall?.action === "failed") {
|
|
491
|
+
console.log(` ⚠ Graphify skill registration failed (${gf.skillInstall.reason})`);
|
|
492
|
+
}
|
|
493
|
+
} else if (gf.binary?.hint) {
|
|
494
|
+
console.log(` ⚠ ${gf.binary.hint}`);
|
|
495
|
+
}
|
|
496
|
+
} catch (err) {
|
|
497
|
+
console.log(` ⚠ Could not set up Graphify (${err.message})`);
|
|
498
|
+
}
|
|
499
|
+
|
|
481
500
|
// ── 8. Update manifest ──
|
|
482
501
|
console.log(" [8/8] Finalizing...");
|
|
483
502
|
manifest.version = VERSION;
|
package/package.json
CHANGED
package/pyproject.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "arkaos-core"
|
|
3
|
-
version = "4.
|
|
3
|
+
version = "4.1.1"
|
|
4
4
|
description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
license = {text = "MIT"}
|
|
@@ -35,6 +35,8 @@ knowledge = [
|
|
|
35
35
|
dashboard = [
|
|
36
36
|
"fastapi>=0.115.0",
|
|
37
37
|
"uvicorn>=0.32.0",
|
|
38
|
+
"python-multipart>=0.0.9",
|
|
39
|
+
"websockets>=12.0",
|
|
38
40
|
]
|
|
39
41
|
ingest = [
|
|
40
42
|
"yt-dlp>=2024.0",
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -49,7 +49,8 @@ def main() -> int:
|
|
|
49
49
|
else:
|
|
50
50
|
print(f"Chunks: {stats['total_chunks']}")
|
|
51
51
|
print(f"Files: {stats['total_files']}")
|
|
52
|
-
print(f"
|
|
52
|
+
print(f"Retrieval: {stats.get('retrieval_mode', 'unknown')}")
|
|
53
|
+
print(f"Vec: {'enabled' if stats.get('vec_available') else 'disabled (keyword fallback)'}")
|
|
53
54
|
print(f"DB: {stats['db_path']}")
|
|
54
55
|
return 0
|
|
55
56
|
|
|
@@ -62,7 +63,12 @@ def main() -> int:
|
|
|
62
63
|
print("No results found.")
|
|
63
64
|
for i, r in enumerate(results, 1):
|
|
64
65
|
src = Path(r.get("source", "")).name if r.get("source") else "unknown"
|
|
65
|
-
|
|
66
|
+
# RAG honesty: degraded keyword matches have NO similarity
|
|
67
|
+
# score — never print one.
|
|
68
|
+
if r.get("score") is None:
|
|
69
|
+
print(f"\n[{i}] Match: keyword-degraded (no similarity) | {src}")
|
|
70
|
+
else:
|
|
71
|
+
print(f"\n[{i}] Score: {r['score']:.3f} | {src}")
|
|
66
72
|
if r.get("heading"):
|
|
67
73
|
print(f" Heading: {r['heading']}")
|
|
68
74
|
print(f" {r['text'][:200]}...")
|