arkaos 4.0.2 → 4.1.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.
Files changed (167) hide show
  1. package/README.md +2 -2
  2. package/VERSION +1 -1
  3. package/arka/SKILL.md +16 -22
  4. package/arka/skills/flow/SKILL.md +108 -234
  5. package/config/claude-agents/eduardo-copy.md +53 -0
  6. package/config/claude-agents/francisca-tech.md +52 -0
  7. package/config/claude-agents/marta-cqo.md +52 -0
  8. package/config/claude-agents/paulo-tech-lead.md +50 -0
  9. package/config/cognition/schedules.yaml +15 -0
  10. package/config/constitution.yaml +17 -21
  11. package/config/hooks/_lib/workflow-classifier.sh +1 -1
  12. package/config/hooks/post-tool-use.sh +40 -539
  13. package/config/hooks/pre-tool-use.sh +32 -281
  14. package/config/hooks/session-start.sh +8 -10
  15. package/config/hooks/stop.sh +30 -425
  16. package/config/hooks/user-prompt-submit.sh +42 -459
  17. package/core/cognition/__pycache__/dreaming.cpython-313.pyc +0 -0
  18. package/core/cognition/dreaming.py +5 -0
  19. package/core/cognition/scheduler/__pycache__/daemon.cpython-313.pyc +0 -0
  20. package/core/cognition/scheduler/daemon.py +10 -0
  21. package/core/forge/__init__.py +2 -0
  22. package/core/forge/__pycache__/__init__.cpython-313.pyc +0 -0
  23. package/core/forge/__pycache__/orchestrator.cpython-313.pyc +0 -0
  24. package/core/forge/__pycache__/persistence.cpython-313.pyc +0 -0
  25. package/core/forge/__pycache__/renderer.cpython-313.pyc +0 -0
  26. package/core/forge/__pycache__/runtime_dispatcher.cpython-313.pyc +0 -0
  27. package/core/forge/__pycache__/schema.cpython-313.pyc +0 -0
  28. package/core/forge/orchestrator.py +50 -27
  29. package/core/forge/persistence.py +16 -0
  30. package/core/forge/renderer.py +16 -0
  31. package/core/forge/runtime_dispatcher.py +34 -19
  32. package/core/forge/schema.py +12 -0
  33. package/core/governance/__pycache__/__init__.cpython-312.pyc +0 -0
  34. package/core/governance/__pycache__/closing_marker_check.cpython-313.pyc +0 -0
  35. package/core/governance/__pycache__/compliance_telemetry.cpython-313.pyc +0 -0
  36. package/core/governance/__pycache__/constitution.cpython-312.pyc +0 -0
  37. package/core/governance/__pycache__/evidence_checks.cpython-313.pyc +0 -0
  38. package/core/governance/__pycache__/meta_tag_check.cpython-313.pyc +0 -0
  39. package/core/governance/__pycache__/qg_verdict.cpython-313.pyc +0 -0
  40. package/core/governance/__pycache__/review_workflow.cpython-313.pyc +0 -0
  41. package/core/governance/__pycache__/skill_proposer.cpython-313.pyc +0 -0
  42. package/core/governance/closing_marker_check.py +9 -5
  43. package/core/governance/compliance_telemetry.py +1 -1
  44. package/core/governance/compliance_telemetry_cli.py +1 -1
  45. package/core/governance/evidence_checks.py +421 -0
  46. package/core/governance/meta_tag_check.py +36 -0
  47. package/core/governance/qg_verdict.py +78 -0
  48. package/core/governance/review_workflow.py +34 -1
  49. package/core/governance/skill_proposer.py +2 -1
  50. package/core/hooks/__init__.py +12 -0
  51. package/core/hooks/__pycache__/__init__.cpython-312.pyc +0 -0
  52. package/core/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
  53. package/core/hooks/__pycache__/_shared.cpython-312.pyc +0 -0
  54. package/core/hooks/__pycache__/_shared.cpython-313.pyc +0 -0
  55. package/core/hooks/__pycache__/post_tool_use.cpython-312.pyc +0 -0
  56. package/core/hooks/__pycache__/post_tool_use.cpython-313.pyc +0 -0
  57. package/core/hooks/__pycache__/pre_tool_use.cpython-312.pyc +0 -0
  58. package/core/hooks/__pycache__/pre_tool_use.cpython-313.pyc +0 -0
  59. package/core/hooks/__pycache__/stop.cpython-312.pyc +0 -0
  60. package/core/hooks/__pycache__/stop.cpython-313.pyc +0 -0
  61. package/core/hooks/__pycache__/user_prompt_submit.cpython-312.pyc +0 -0
  62. package/core/hooks/__pycache__/user_prompt_submit.cpython-313.pyc +0 -0
  63. package/core/hooks/_shared.py +110 -0
  64. package/core/hooks/post_tool_use.py +689 -0
  65. package/core/hooks/pre_tool_use.py +267 -0
  66. package/core/hooks/stop.py +395 -0
  67. package/core/hooks/user_prompt_submit.py +600 -0
  68. package/core/jobs/__pycache__/__init__.cpython-312.pyc +0 -0
  69. package/core/jobs/__pycache__/auto_doc_worker.cpython-312.pyc +0 -0
  70. package/core/jobs/__pycache__/manager.cpython-312.pyc +0 -0
  71. package/core/knowledge/__pycache__/embedder.cpython-312.pyc +0 -0
  72. package/core/knowledge/__pycache__/embedder.cpython-313.pyc +0 -0
  73. package/core/knowledge/__pycache__/vector_store.cpython-312.pyc +0 -0
  74. package/core/knowledge/__pycache__/vector_store.cpython-313.pyc +0 -0
  75. package/core/knowledge/embedder.py +118 -11
  76. package/core/knowledge/vector_store.py +111 -14
  77. package/core/runtime/__pycache__/__init__.cpython-312.pyc +0 -0
  78. package/core/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
  79. package/core/runtime/__pycache__/base.cpython-312.pyc +0 -0
  80. package/core/runtime/__pycache__/base.cpython-313.pyc +0 -0
  81. package/core/runtime/__pycache__/capabilities_cli.cpython-312.pyc +0 -0
  82. package/core/runtime/__pycache__/capabilities_cli.cpython-313.pyc +0 -0
  83. package/core/runtime/__pycache__/claude_code.cpython-312.pyc +0 -0
  84. package/core/runtime/__pycache__/claude_code.cpython-313.pyc +0 -0
  85. package/core/runtime/__pycache__/codex_cli.cpython-312.pyc +0 -0
  86. package/core/runtime/__pycache__/codex_cli.cpython-313.pyc +0 -0
  87. package/core/runtime/__pycache__/cost_governor.cpython-312.pyc +0 -0
  88. package/core/runtime/__pycache__/cost_governor.cpython-313.pyc +0 -0
  89. package/core/runtime/__pycache__/cursor.cpython-312.pyc +0 -0
  90. package/core/runtime/__pycache__/cursor.cpython-313.pyc +0 -0
  91. package/core/runtime/__pycache__/gemini_cli.cpython-312.pyc +0 -0
  92. package/core/runtime/__pycache__/gemini_cli.cpython-313.pyc +0 -0
  93. package/core/runtime/__pycache__/llm_provider.cpython-312.pyc +0 -0
  94. package/core/runtime/__pycache__/llm_provider.cpython-313.pyc +0 -0
  95. package/core/runtime/__pycache__/llm_retry.cpython-312.pyc +0 -0
  96. package/core/runtime/__pycache__/llm_retry.cpython-313.pyc +0 -0
  97. package/core/runtime/__pycache__/native_usage.cpython-312.pyc +0 -0
  98. package/core/runtime/__pycache__/native_usage.cpython-313.pyc +0 -0
  99. package/core/runtime/__pycache__/path_resolver.cpython-312.pyc +0 -0
  100. package/core/runtime/__pycache__/pricing.cpython-312.pyc +0 -0
  101. package/core/runtime/__pycache__/pricing.cpython-313.pyc +0 -0
  102. package/core/runtime/__pycache__/registry.cpython-312.pyc +0 -0
  103. package/core/runtime/__pycache__/registry.cpython-313.pyc +0 -0
  104. package/core/runtime/__pycache__/squad_orchestrator.cpython-312.pyc +0 -0
  105. package/core/runtime/base.py +29 -0
  106. package/core/runtime/capabilities_cli.py +74 -0
  107. package/core/runtime/claude_code.py +8 -0
  108. package/core/runtime/codex_cli.py +205 -37
  109. package/core/runtime/cost_governor.py +184 -0
  110. package/core/runtime/cursor.py +8 -0
  111. package/core/runtime/gemini_cli.py +8 -0
  112. package/core/runtime/llm_provider.py +32 -13
  113. package/core/runtime/llm_retry.py +233 -0
  114. package/core/runtime/native_usage.py +186 -0
  115. package/core/runtime/pricing.py +9 -0
  116. package/core/runtime/registry.py +3 -0
  117. package/core/shared/__pycache__/test_evidence.cpython-313-pytest-9.1.1.pyc +0 -0
  118. package/core/shared/__pycache__/test_evidence.cpython-313.pyc +0 -0
  119. package/core/shared/test_evidence.py +34 -0
  120. package/core/synapse/__pycache__/agent_experiences_layer.cpython-312.pyc +0 -0
  121. package/core/synapse/__pycache__/engine.cpython-312.pyc +0 -0
  122. package/core/synapse/__pycache__/engine.cpython-313.pyc +0 -0
  123. package/core/synapse/__pycache__/graph_context_layer.cpython-313.pyc +0 -0
  124. package/core/synapse/__pycache__/kb_cache.cpython-312.pyc +0 -0
  125. package/core/synapse/__pycache__/kb_cache.cpython-313.pyc +0 -0
  126. package/core/synapse/__pycache__/layers.cpython-312.pyc +0 -0
  127. package/core/synapse/__pycache__/layers.cpython-313.pyc +0 -0
  128. package/core/synapse/engine.py +7 -0
  129. package/core/synapse/graph_context_layer.py +212 -0
  130. package/core/synapse/kb_cache.py +3 -0
  131. package/core/synapse/layers.py +104 -20
  132. package/core/workflow/__pycache__/enforcer.cpython-313.pyc +0 -0
  133. package/core/workflow/__pycache__/flow_enforcer.cpython-312.pyc +0 -0
  134. package/core/workflow/__pycache__/flow_enforcer.cpython-313.pyc +0 -0
  135. package/core/workflow/__pycache__/gate_checkpoint.cpython-312.pyc +0 -0
  136. package/core/workflow/__pycache__/gate_checkpoint.cpython-313.pyc +0 -0
  137. package/core/workflow/__pycache__/research_gate.cpython-313.pyc +0 -0
  138. package/core/workflow/__pycache__/rules_registry.cpython-313.pyc +0 -0
  139. package/core/workflow/__pycache__/specialist_enforcer.cpython-312.pyc +0 -0
  140. package/core/workflow/__pycache__/specialist_enforcer.cpython-313.pyc +0 -0
  141. package/core/workflow/enforcer.py +5 -16
  142. package/core/workflow/flow_enforcer.py +36 -11
  143. package/core/workflow/gate_checkpoint.py +149 -0
  144. package/core/workflow/research_gate.py +15 -3
  145. package/core/workflow/rules_registry.py +137 -389
  146. package/core/workflow/specialist_enforcer.py +21 -4
  147. package/dashboard/app/pages/knowledge/index.vue +4 -1
  148. package/departments/ops/skills/n8n-flow/SKILL.md +13 -0
  149. package/departments/ops/skills/workflow-automate/SKILL.md +16 -0
  150. package/departments/quality/SKILL.md +45 -5
  151. package/departments/quality/agents/copy-director.yaml +3 -1
  152. package/departments/quality/agents/tech-director.yaml +3 -1
  153. package/installer/adapters/claude-code.js +46 -2
  154. package/installer/config-seed.js +39 -14
  155. package/installer/doctor.js +8 -0
  156. package/installer/graphify.js +153 -0
  157. package/installer/index.js +20 -0
  158. package/installer/init.js +11 -0
  159. package/installer/update.js +19 -0
  160. package/package.json +1 -1
  161. package/pyproject.toml +1 -1
  162. package/scripts/__pycache__/dashboard-api.cpython-313.pyc +0 -0
  163. package/scripts/__pycache__/knowledge-index.cpython-313.pyc +0 -0
  164. package/scripts/__pycache__/synapse-bridge.cpython-312.pyc +0 -0
  165. package/scripts/__pycache__/synapse-bridge.cpython-313.pyc +0 -0
  166. package/scripts/knowledge-index.py +8 -2
  167. package/scripts/synapse-bridge.py +102 -26
@@ -594,7 +594,10 @@ async function handleSearch() {
594
594
  }
595
595
  }
596
596
 
597
- function formatScore(score: number): string {
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. Marta receives ALL output from execution phases
23
- 2. Marta dispatches Eduardo (text) + Francisca (tech) in parallel
24
- 3. Each reviewer returns APPROVED or REJECTED with specific issues
25
- 4. If ANY reviewer rejects work loops back with issue list
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 dispatch (Marta/Eduardo/Francisca) ALWAYS uses `model: opus` NON-NEGOTIABLE
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
- model: opus
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
- model: opus
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.
@@ -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: writes `hooks.hardEnforcement = true` only when the key
5
- // is unset. Explicit user choice (true OR false) is preserved.
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, { hooks: { hardEnforcement: true } });
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, { hooks: { hardEnforcement: true } });
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
- if (current === true) {
44
- return { action: "noop", path: cfgPath };
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 (current === false) {
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) {
@@ -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
+ }
@@ -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
 
@@ -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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "4.0.2",
3
+ "version": "4.1.0",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "arkaos-core"
3
- version = "4.0.2"
3
+ version = "4.1.0"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}
@@ -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"VSS: {'enabled' if stats['vss_available'] else 'disabled (keyword fallback)'}")
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
- print(f"\n[{i}] Score: {r['score']:.3f} | {src}")
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]}...")