arkaos 4.43.0 → 4.44.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 (46) hide show
  1. package/THE-ARKAOS-GUIDE.md +1 -1
  2. package/VERSION +1 -1
  3. package/config/hooks/agent-provision.sh +35 -14
  4. package/core/governance/aggregate_guard.py +32 -6
  5. package/core/governance/quality_api.py +13 -17
  6. package/core/governance/review_workflow.py +26 -12
  7. package/core/hooks/stop.py +67 -21
  8. package/harness/codex/AGENTS.md +1 -1
  9. package/harness/copilot/copilot-instructions.md +1 -1
  10. package/harness/cursor/rules/arkaos.mdc +2 -2
  11. package/harness/gemini/GEMINI.md +1 -1
  12. package/harness/opencode/AGENTS.md +1 -1
  13. package/harness/opencode/agents/arka-architect-gabriel.md +1 -1
  14. package/harness/opencode/agents/arka-brand-director-valentina.md +1 -1
  15. package/harness/opencode/agents/arka-cfo-helena.md +1 -1
  16. package/harness/opencode/agents/arka-chief-of-staff-afonso.md +1 -1
  17. package/harness/opencode/agents/arka-community-strategist-beatriz.md +1 -1
  18. package/harness/opencode/agents/arka-content-strategist-rafael.md +1 -1
  19. package/harness/opencode/agents/arka-conversion-strategist-ines.md +1 -1
  20. package/harness/opencode/agents/arka-coo-sofia.md +1 -1
  21. package/harness/opencode/agents/arka-copy-director-eduardo.md +1 -1
  22. package/harness/opencode/agents/arka-cqo-marta.md +1 -1
  23. package/harness/opencode/agents/arka-cto-marco.md +1 -1
  24. package/harness/opencode/agents/arka-design-ops-lead-iris.md +1 -1
  25. package/harness/opencode/agents/arka-ecom-director-ricardo.md +1 -1
  26. package/harness/opencode/agents/arka-knowledge-director-clara.md +1 -1
  27. package/harness/opencode/agents/arka-leadership-director-rodrigo.md +1 -1
  28. package/harness/opencode/agents/arka-marketing-director-luna.md +1 -1
  29. package/harness/opencode/agents/arka-ops-lead-daniel.md +1 -1
  30. package/harness/opencode/agents/arka-pm-director-carolina.md +1 -1
  31. package/harness/opencode/agents/arka-revops-lead-vicente.md +1 -1
  32. package/harness/opencode/agents/arka-saas-strategist-tiago.md +1 -1
  33. package/harness/opencode/agents/arka-sales-director-miguel.md +1 -1
  34. package/harness/opencode/agents/arka-strategy-director-tomas.md +1 -1
  35. package/harness/opencode/agents/arka-tech-director-francisca.md +1 -1
  36. package/harness/opencode/agents/arka-tech-lead-paulo.md +1 -1
  37. package/harness/opencode/agents/arka-video-producer-simao.md +1 -1
  38. package/harness/zed/.rules +1 -1
  39. package/installer/adapters/claude-code.js +88 -3
  40. package/installer/doctor.js +121 -3
  41. package/installer/index.js +17 -0
  42. package/installer/skill-deploy.js +43 -2
  43. package/installer/update.js +12 -0
  44. package/knowledge/skills-manifest.json +1 -1
  45. package/package.json +1 -1
  46. package/pyproject.toml +1 -1
@@ -1,6 +1,6 @@
1
1
  # The ArkaOS Guide
2
2
 
3
- > v4.43.0 — 89 agents, 17 departments, 332 skills, 297 commands, 18 ADRs.
3
+ > v4.44.0 — 89 agents, 17 departments, 332 skills, 297 commands, 18 ADRs.
4
4
  > One file, everything you need to start. Generated by `scripts/guide_gen.py` — never hand-edited.
5
5
 
6
6
  ## What it is
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.43.0
1
+ 4.44.0
@@ -2,11 +2,23 @@
2
2
  # ArkaOS PreToolUse hook for dynamic agent provisioning.
3
3
  # Intercepts Task tool calls: if subagent_type is not present in the
4
4
  # project's .claude/agents/, copies it from ArkaOS core when available,
5
- # or blocks with an approval-request message when the agent must be
6
- # created via `/platform-arka agent provision <name>`.
5
+ # or surfaces an approval-request message WITHOUT blocking when the
6
+ # agent is unknown. The set of valid subagent_types is not enumerable
7
+ # here — runtime built-ins (general-purpose, statusline-setup, …)
8
+ # change with Claude Code versions and plugins add their own — so any
9
+ # blocking arm on "unknown" breaks legitimate dispatches (QG PR-B5 r1
10
+ # reproduced exit 2 on general-purpose). Provision when we know the
11
+ # name; stand aside when we don't.
7
12
 
8
13
  set -euo pipefail
9
14
 
15
+ # No jq, no payload parse. .claude/rules/bash-hooks.md:11-12 prescribes
16
+ # a python3 fallback for jq; this gate is OPTIONAL by design, so it
17
+ # stands aside instead of paying a python spawn on every Task call —
18
+ # and per the same rules file ("exit code 2 = block action", last
19
+ # bullet), exiting 0 here can never break a dispatch.
20
+ command -v jq >/dev/null 2>&1 || exit 0
21
+
10
22
  # ─── Shared Python resolver (exports ARKA_PY) ──────────────────────────
11
23
  # The resolver guards its own ARKA_PY assignment with `|| true`, so sourcing
12
24
  # is set -e-safe even on the last-resort fallback. The trailing `|| true`
@@ -49,7 +61,6 @@ if [ -z "$core_root" ]; then
49
61
  fi
50
62
 
51
63
  if [ -d "$core_root/departments" ]; then
52
- mkdir -p "$project_agents_dir"
53
64
  set +e
54
65
  "$ARKA_PY" - "$core_root" "$subagent_type" "$target" <<'PY'
55
66
  import os, re, sys
@@ -105,7 +116,10 @@ if md_path is not None:
105
116
 
106
117
  content = "\n".join(parts) + "\n"
107
118
 
108
- # Atomic write: temp file in same dir, then os.replace.
119
+ # Create the parent only on the arm that actually writes (a dispatch
120
+ # of an unknown name must not scatter empty .claude/agents/ dirs), then
121
+ # atomic write: temp file in same dir, then os.replace.
122
+ target.parent.mkdir(parents=True, exist_ok=True)
109
123
  tmp = target.with_suffix(".md.tmp")
110
124
  tmp.write_text(content)
111
125
  os.replace(tmp, target)
@@ -128,15 +142,22 @@ PY
128
142
  esac
129
143
  fi
130
144
 
131
- # Agent not in project and not in core surface an approval-request.
145
+ # Agent not in project, and the core lookup either found nothing or
146
+ # never ran. Say exactly which, and NEVER block: this name may be a
147
+ # runtime built-in or a plugin agent this script cannot know about —
148
+ # if it truly doesn't exist, the Task dispatch itself fails with the
149
+ # runtime's own error.
150
+ if [ -d "$core_root/departments" ]; then
151
+ core_note="no ArkaOS core agent of that name was found (core searched at: $core_root)"
152
+ else
153
+ core_note="the ArkaOS core lookup did not run (no departments/ under: ${core_root:-<unset>})"
154
+ fi
132
155
  cat >&2 <<MSG
133
- [arka:provision-needed] Agent '$subagent_type' is not installed in this
134
- project and does not exist in ArkaOS core. To create it, run:
135
-
136
- /platform-arka agent provision $subagent_type
137
-
138
- This opens the Skill Architect flow which drafts the agent YAML with
139
- 4-framework DNA, goes through Quality Gate, and commits to core before
140
- propagating to the project. Blocking dispatch until the agent exists.
156
+ [arka:provision-needed] Agent '$subagent_type' is not in this project's
157
+ .claude/agents/, and $core_note.
158
+ If ArkaOS should provide this agent, create it at
159
+ .claude/agents/$subagent_type.md, or run 'npx arkaos update' so ArkaOS
160
+ core ships a definition this gate can copy.
161
+ Runtime built-ins and plugin agents are unaffected dispatch proceeds.
141
162
  MSG
142
- exit 2
163
+ exit 0
@@ -391,12 +391,33 @@ def _digest_issues(
391
391
  return issues, notes
392
392
 
393
393
 
394
- def _ended_issues(session_id: str) -> list[str]:
395
- """A stamped session is not a live quorum (PR-B4 session binding)."""
394
+ def _ended_issues(session_id: str, artifact_names: list[str]) -> list[str]:
395
+ """A stamped session is not a live quorum (PR-B4 session binding).
396
+
397
+ M4 hardening (PR-B5): the stamp only outlaws records that PREDATE
398
+ it. A counted record captured strictly after the stamp's mtime
399
+ proves the hook boundary came back to life under this session id —
400
+ never observed on the dev machine, where the stamp shipped one
401
+ release earlier and the ledger held a single stamped session when
402
+ this was written; the refusal must not outlive its own evidence.
403
+ Ties and unreadable mtimes keep the refusal: equality cannot
404
+ distinguish before from after, and fail-closed is the guard's
405
+ resting state.
406
+ """
396
407
  from core.governance.reviewer_ledger import ENDED_NAME
397
408
 
398
- if not (ledger_root() / session_id / ENDED_NAME).is_file():
409
+ session_dir = ledger_root() / session_id
410
+ stamp = session_dir / ENDED_NAME
411
+ if not stamp.is_file():
399
412
  return []
413
+ try:
414
+ newest = max(
415
+ (session_dir / name).stat().st_mtime for name in artifact_names
416
+ )
417
+ if newest > stamp.stat().st_mtime:
418
+ return []
419
+ except (OSError, ValueError):
420
+ pass # no readable counted record newer than the stamp
400
421
  return [
401
422
  f"session {session_id} is marked ended (SessionEnd stamped its "
402
423
  "ledger) — a past session's reviewer records are not a reusable "
@@ -568,7 +589,9 @@ def check_aggregate(aggregate: dict, session_id: str) -> GuardResult:
568
589
  reviewers, [name for name, _ in counted]
569
590
  )
570
591
  verdicts = [(str(r.get("reviewer_id")), r["verdict"]) for _, r in counted]
571
- reasons, warnings = _reasons(aggregate, verdicts, session_id)
592
+ reasons, warnings = _reasons(
593
+ aggregate, verdicts, session_id, [name for name, _ in counted]
594
+ )
572
595
  return GuardResult(
573
596
  ok=not reasons,
574
597
  reasons=reasons,
@@ -579,7 +602,10 @@ def check_aggregate(aggregate: dict, session_id: str) -> GuardResult:
579
602
 
580
603
 
581
604
  def _reasons(
582
- aggregate: dict, verdicts: list[tuple[str, dict]], session_id: str
605
+ aggregate: dict,
606
+ verdicts: list[tuple[str, dict]],
607
+ session_id: str,
608
+ artifact_names: list[str],
583
609
  ) -> tuple[list[str], list[str]]:
584
610
  """Every refusal reason and warning for one aggregate.
585
611
 
@@ -594,7 +620,7 @@ def _reasons(
594
620
  """
595
621
  approved = _norm(aggregate.get("verdict")) == "approved"
596
622
  issues, notes = _digest_issues(aggregate, verdicts)
597
- issues += _ended_issues(session_id)
623
+ issues += _ended_issues(session_id, artifact_names)
598
624
  reasons = (
599
625
  _verdict_reasons(aggregate, verdicts)
600
626
  + _blocker_reasons(aggregate, verdicts, approved)
@@ -8,26 +8,15 @@ Provides a simple programmatic interface for the Quality Gate system:
8
8
  """
9
9
 
10
10
  import json
11
+ from datetime import UTC
11
12
  from pathlib import Path
12
13
  from typing import Any
13
14
 
14
15
  from core.governance.quality_router import (
15
- QualityRouter,
16
- ReviewAssignment,
17
- ReviewerType,
18
16
  DeliverableType,
19
- ReviewPriority,
20
- route_deliverable,
21
- )
22
- from core.governance.review_workflow import (
23
- ReviewWorkflowEngine,
24
- ReviewWorkflow,
25
- ReviewPhase,
26
- Verdict,
27
- ReviewerOpinion,
17
+ QualityRouter,
28
18
  )
29
19
 
30
-
31
20
  _STATE_DIR = Path.home() / ".arkaos" / "quality-gate"
32
21
  _QUEUE_FILE = _STATE_DIR / "queue.json"
33
22
  _WORKFLOWS_FILE = _STATE_DIR / "workflows.json"
@@ -147,6 +136,14 @@ def record_verdict(
147
136
  ) -> bool:
148
137
  """Record a reviewer's verdict for a submission.
149
138
 
139
+ .. deprecated:: 4.44.0 (PR-B5)
140
+ Writes an unguarded label: no ledger artifact, no digest, no
141
+ aggregate guard. Use ``core.evals.record_cli``: ``--kind qg``
142
+ passes ``aggregate_guard.check_aggregate``, which requires at
143
+ least two hook-captured reviewer artifacts in the session
144
+ ledger; ``--kind reviewer`` remains an unguarded label path —
145
+ its ledger cross-reference is provenance, not admission.
146
+
150
147
  Args:
151
148
  submission_id: ID of the submission
152
149
  reviewer: Agent ID of reviewer
@@ -157,8 +154,6 @@ def record_verdict(
157
154
  Returns:
158
155
  True if verdict recorded successfully
159
156
  """
160
- reviewer_type = ReviewerType(reviewer) if reviewer in [r.value for r in ReviewerType] else None
161
-
162
157
  queue = _load_queue()
163
158
  for item in queue:
164
159
  if item["id"] == submission_id:
@@ -217,9 +212,9 @@ def clear_resolved(older_than_days: int = 7) -> int:
217
212
  Returns:
218
213
  Number of items removed
219
214
  """
220
- from datetime import datetime, timedelta, timezone
215
+ from datetime import datetime, timedelta
221
216
 
222
- cutoff = datetime.now(timezone.utc) - timedelta(days=older_than_days)
217
+ cutoff = datetime.now(UTC) - timedelta(days=older_than_days)
223
218
  queue = _load_queue()
224
219
  original_count = len(queue)
225
220
 
@@ -268,6 +263,7 @@ class QualityGateAPI:
268
263
  feedback: str = "",
269
264
  flagged_rules: list[str] | None = None,
270
265
  ) -> bool:
266
+ """Deprecated with the module function — see ``record_verdict``."""
271
267
  return record_verdict(submission_id, reviewer, verdict, feedback, flagged_rules)
272
268
 
273
269
  def list_pending(self, reviewer: str | None = None) -> list[dict]:
@@ -1,5 +1,15 @@
1
1
  """Review Workflow Engine — structured review phases for Quality Gate.
2
2
 
3
+ .. deprecated:: 4.44.0 (PR-B5)
4
+ This engine models review phases in memory and records verdicts
5
+ WITHOUT the reviewer ledger or the aggregate guard behind them —
6
+ nothing here proves a verdict was authored at the hook boundary.
7
+ The guarded path is ``core.evals.record_cli``: ``--kind qg`` passes
8
+ ``aggregate_guard.check_aggregate``, which requires hook-captured
9
+ reviewer artifacts in the session ledger. The module stays
10
+ importable because live tests pin its phase mechanics, but no new
11
+ caller should route verdicts through it.
12
+
3
13
  Phases:
4
14
  1. Submission — deliverable submitted for review
5
15
  2. Triage — initial assessment, assign reviewers
@@ -16,13 +26,17 @@ Verdicts:
16
26
  """
17
27
 
18
28
  import uuid
29
+ from collections.abc import Callable
19
30
  from dataclasses import dataclass, field
20
- from datetime import datetime, timezone
31
+ from datetime import UTC, datetime
21
32
  from enum import Enum
22
- from typing import Callable
23
33
 
24
34
 
25
- class ReviewPhase(str, Enum):
35
+ # UP042 suppressed on the three enums below: switching to StrEnum changes
36
+ # str(member) from "Verdict.APPROVED" to "APPROVED" — behaviour, not
37
+ # style — and this module is deprecated (see the module docstring), not
38
+ # being modernised.
39
+ class ReviewPhase(str, Enum): # noqa: UP042
26
40
  SUBMISSION = "submission"
27
41
  TRIAGE = "triage"
28
42
  REVIEW = "review"
@@ -31,14 +45,14 @@ class ReviewPhase(str, Enum):
31
45
  DELIVERY = "delivery"
32
46
 
33
47
 
34
- class Verdict(str, Enum):
48
+ class Verdict(str, Enum): # noqa: UP042
35
49
  APPROVED = "APPROVED"
36
50
  REJECTED = "REJECTED"
37
51
  REQUEST_CHANGES = "REQUEST_CHANGES"
38
52
  ESCALATE = "ESCALATE"
39
53
 
40
54
 
41
- class ReviewerOpinion(str, Enum):
55
+ class ReviewerOpinion(str, Enum): # noqa: UP042
42
56
  APPROVE = "APPROVE"
43
57
  REJECT = "REJECT"
44
58
  CHANGES = "REQUEST_CHANGES"
@@ -79,7 +93,7 @@ class ReviewWorkflow:
79
93
 
80
94
  def __post_init__(self):
81
95
  if not self.submitted_at:
82
- self.submitted_at = datetime.now(timezone.utc).isoformat()
96
+ self.submitted_at = datetime.now(UTC).isoformat()
83
97
 
84
98
 
85
99
  @dataclass
@@ -156,7 +170,7 @@ class ReviewWorkflowEngine:
156
170
  self._announce_phase_start(ReviewPhase.TRIAGE, workflow_id)
157
171
 
158
172
  workflow.phase = ReviewPhase.TRIAGE
159
- workflow.triage_at = datetime.now(timezone.utc).isoformat()
173
+ workflow.triage_at = datetime.now(UTC).isoformat()
160
174
 
161
175
  result = PhaseResult(
162
176
  phase=ReviewPhase.TRIAGE,
@@ -191,7 +205,7 @@ class ReviewWorkflowEngine:
191
205
  self._announce_phase_start(ReviewPhase.REVIEW, workflow_id)
192
206
 
193
207
  workflow.phase = ReviewPhase.REVIEW
194
- workflow.review_started_at = datetime.now(timezone.utc).isoformat()
208
+ workflow.review_started_at = datetime.now(UTC).isoformat()
195
209
 
196
210
  result = PhaseResult(
197
211
  phase=ReviewPhase.REVIEW,
@@ -248,7 +262,7 @@ class ReviewWorkflowEngine:
248
262
  opinion=opinion,
249
263
  comments=comments,
250
264
  flagged_rules=flagged_rules or [],
251
- voted_at=datetime.now(timezone.utc).isoformat(),
265
+ voted_at=datetime.now(UTC).isoformat(),
252
266
  evidence_overall=evidence_overall,
253
267
  )
254
268
  workflow.votes.append(vote)
@@ -279,7 +293,7 @@ class ReviewWorkflowEngine:
279
293
  self._announce_phase_start(ReviewPhase.DELIBERATION, workflow_id)
280
294
 
281
295
  workflow.phase = ReviewPhase.DELIBERATION
282
- workflow.deliberation_at = datetime.now(timezone.utc).isoformat()
296
+ workflow.deliberation_at = datetime.now(UTC).isoformat()
283
297
 
284
298
  result = PhaseResult(
285
299
  phase=ReviewPhase.DELIBERATION,
@@ -331,7 +345,7 @@ class ReviewWorkflowEngine:
331
345
  workflow.phase = ReviewPhase.VERDICT
332
346
  workflow.verdict = verdict
333
347
  workflow.feedback = feedback
334
- workflow.verdict_at = datetime.now(timezone.utc).isoformat()
348
+ workflow.verdict_at = datetime.now(UTC).isoformat()
335
349
 
336
350
  self._announce_verdict(verdict, workflow_id)
337
351
 
@@ -370,7 +384,7 @@ class ReviewWorkflowEngine:
370
384
  )
371
385
 
372
386
  workflow.phase = ReviewPhase.DELIVERY
373
- workflow.delivered_at = datetime.now(timezone.utc).isoformat()
387
+ workflow.delivered_at = datetime.now(UTC).isoformat()
374
388
 
375
389
  result = PhaseResult(
376
390
  phase=ReviewPhase.DELIVERY,
@@ -371,6 +371,60 @@ def _flow_checks(
371
371
  clear_flow_required(session_id)
372
372
 
373
373
 
374
+ def _qg_aggregate_verdict(session_id: str) -> bool | None:
375
+ """Verdict from the session's guard-accepted ``AGGREGATE.json``.
376
+
377
+ True/False when a readable aggregate for THIS session exists — the
378
+ aggregate only lands on disk through aggregate_guard (PR-B3), so it
379
+ is authoritative in both directions. None when absent, foreign or
380
+ unreadable: only then may the caller fall back to the telemetry log.
381
+ """
382
+ try:
383
+ from core.governance.reviewer_ledger import (
384
+ AGGREGATE_NAME,
385
+ _safe_id,
386
+ ledger_root,
387
+ )
388
+
389
+ if not _safe_id(session_id):
390
+ return None
391
+ rec = json.loads(
392
+ (ledger_root() / session_id / AGGREGATE_NAME).read_text(
393
+ encoding="utf-8"
394
+ )
395
+ )
396
+ except Exception:
397
+ return None
398
+ if not isinstance(rec, dict) or rec.get("session_id") != session_id:
399
+ return None
400
+ verdict = (rec.get("aggregate") or {}).get("verdict", "")
401
+ return str(verdict).upper() == "APPROVED"
402
+
403
+
404
+ def _qg_telemetry_approved(session_id: str) -> bool:
405
+ """Legacy fallback: newest ``qg.jsonl`` record for this session.
406
+
407
+ Pre-B3 sessions have no aggregate, so its absence proves nothing;
408
+ the unguarded telemetry log then keeps its historical role.
409
+ """
410
+ qg_log = Path.home() / ".arkaos" / "telemetry" / "qg.jsonl"
411
+ if not qg_log.exists():
412
+ return False
413
+ try:
414
+ for line in reversed(qg_log.read_text(encoding="utf-8").splitlines()):
415
+ if not line.strip():
416
+ continue
417
+ try:
418
+ rec = json.loads(line)
419
+ except Exception:
420
+ continue
421
+ if rec.get("session_id") == session_id:
422
+ return rec.get("verdict", "").upper() == "APPROVED"
423
+ except Exception:
424
+ pass
425
+ return False
426
+
427
+
374
428
  def _auto_doc_enqueue(
375
429
  session_id: str, transcript_path: str, raw: str | None
376
430
  ) -> None:
@@ -389,27 +443,19 @@ def _auto_doc_enqueue(
389
443
  except Exception:
390
444
  last = ""
391
445
 
392
- qg_approved = bool(re.search(r"\[arka:qg:approved\]", last, re.IGNORECASE))
393
- if not qg_approved:
394
- qg_log = Path.home() / ".arkaos" / "telemetry" / "qg.jsonl"
395
- if qg_log.exists():
396
- try:
397
- for line in reversed(
398
- qg_log.read_text(encoding="utf-8").splitlines()
399
- ):
400
- if not line.strip():
401
- continue
402
- try:
403
- rec = json.loads(line)
404
- except Exception:
405
- continue
406
- if rec.get("session_id") == session_id:
407
- qg_approved = (
408
- rec.get("verdict", "").upper() == "APPROVED"
409
- )
410
- break
411
- except Exception:
412
- pass
446
+ # The guard-accepted aggregate is authoritative either way (PR-B5,
447
+ # G7) — over the telemetry log AND over the assistant's own
448
+ # [arka:qg:approved] marker: narration must not outrank the record
449
+ # it narrates (the same laundering shape G7 closes one layer down).
450
+ # Marker and telemetry keep their historical roles only when no
451
+ # aggregate exists for the session.
452
+ from_aggregate = _qg_aggregate_verdict(session_id)
453
+ if from_aggregate is not None:
454
+ qg_approved = from_aggregate
455
+ else:
456
+ qg_approved = bool(
457
+ re.search(r"\[arka:qg:approved\]", last, re.IGNORECASE)
458
+ ) or _qg_telemetry_approved(session_id)
413
459
  if not qg_approved:
414
460
  return
415
461
 
@@ -1,6 +1,6 @@
1
1
  # ArkaOS — The Operating System for AI Agent Teams
2
2
 
3
- > v4.43.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
3
+ > v4.44.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
4
4
 
5
5
  You are operating within ArkaOS. Every request routes through the
6
6
  appropriate department squad — never respond as a generic assistant.
@@ -1,6 +1,6 @@
1
1
  # ArkaOS — The Operating System for AI Agent Teams
2
2
 
3
- > v4.43.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
3
+ > v4.44.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
4
4
 
5
5
  You are operating within ArkaOS. Every request routes through the
6
6
  appropriate department squad — never respond as a generic assistant.
@@ -1,11 +1,11 @@
1
1
  ---
2
- description: ArkaOS v4.43.0 agent-team contract
2
+ description: ArkaOS v4.44.0 agent-team contract
3
3
  alwaysApply: true
4
4
  ---
5
5
 
6
6
  # ArkaOS — The Operating System for AI Agent Teams
7
7
 
8
- > v4.43.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
8
+ > v4.44.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
9
9
 
10
10
  You are operating within ArkaOS. Every request routes through the
11
11
  appropriate department squad — never respond as a generic assistant.
@@ -1,6 +1,6 @@
1
1
  # ArkaOS — The Operating System for AI Agent Teams
2
2
 
3
- > v4.43.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
3
+ > v4.44.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
4
4
 
5
5
  You are operating within ArkaOS. Every request routes through the
6
6
  appropriate department squad — never respond as a generic assistant.
@@ -1,6 +1,6 @@
1
1
  # ArkaOS — The Operating System for AI Agent Teams
2
2
 
3
- > v4.43.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
3
+ > v4.44.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
4
4
 
5
5
  You are operating within ArkaOS. Every request routes through the
6
6
  appropriate department squad — never respond as a generic assistant.
@@ -3,7 +3,7 @@ description: "Software Architect — ArkaOS /dev department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Gabriel, Software Architect of the ArkaOS /dev department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Gabriel, Software Architect of the ArkaOS /dev department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: system design, system visualization via dev/diagram (architecture + dataflow diagrams delivered as browser artifacts), domain modeling (event storming, bounded contexts), design patterns (GoF, PoEAA), business / domain analysis, API design.
9
9
 
@@ -3,7 +3,7 @@ description: "Creative Director — ArkaOS /brand department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Valentina, Creative Director of the ArkaOS /brand department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Valentina, Creative Director of the ArkaOS /brand department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: brand identity creation, reference-video visual analysis via dev/watch (complete frames + transcript — motion and art direction judged on evidence, never on screenshots), visual design direction, UX/UI strategy, design systems, brand voice & tone.
9
9
 
@@ -3,7 +3,7 @@ description: "Chief Financial Officer — ArkaOS /fin department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Helena, Chief Financial Officer of the ArkaOS /fin department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Helena, Chief Financial Officer of the ArkaOS /fin department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: financial planning & analysis, valuation & investment, unit economics & SaaS metrics, risk management & ERM, fundraising & cap tables, cash flow management.
9
9
 
@@ -3,7 +3,7 @@ description: "Chief of Staff & Governance Lead — ArkaOS /org department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Afonso, Chief of Staff & Governance Lead of the ArkaOS /org department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Afonso, Chief of Staff & Governance Lead of the ArkaOS /org department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: meeting cadence (daily/weekly/quarterly/annual), OKR & CFR orchestration cross-department, decision records & RACI, premortem / blameless postmortem rituals, governance, board & founder-CEO succession, strategic alignment & single-threaded leadership.
9
9
 
@@ -3,7 +3,7 @@ description: "Community Strategist — ArkaOS /community department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Beatriz, Community Strategist of the ArkaOS /community department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Beatriz, Community Strategist of the ArkaOS /community department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: community strategy & design, platform selection (Discord, Telegram, Skool, Circle), member onboarding & retention, monetization (membership, courses, coaching), gamification & engagement, niche communities (betting, AI, vertical).
9
9
 
@@ -3,7 +3,7 @@ description: "Content Strategist — ArkaOS /content department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Rafael, Content Strategist of the ArkaOS /content department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Rafael, Content Strategist of the ArkaOS /content department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: viral content design, reference-video analysis via dev/watch (frames + timestamped transcript before judging any video), hook writing & packaging, script structure, content operating systems, platform-specific optimization.
9
9
 
@@ -3,7 +3,7 @@ description: "Conversion Strategist — ArkaOS /landing department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Ines, Conversion Strategist of the ArkaOS /landing department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Ines, Conversion Strategist of the ArkaOS /landing department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: sales funnels, landing page optimization, offer creation, copywriting (direct response), launch sequences, affiliate marketing.
9
9
 
@@ -3,7 +3,7 @@ description: "Chief Operations Officer — ArkaOS /org department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Sofia, Chief Operations Officer of the ArkaOS /org department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Sofia, Chief Operations Officer of the ArkaOS /org department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: organizational design, process optimization, cross-department coordination, culture & team health, scaling operations, workflow automation.
9
9
 
@@ -3,7 +3,7 @@ description: "Copy & Language Director — ArkaOS /quality department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Eduardo, Copy & Language Director of the ArkaOS /quality department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Eduardo, Copy & Language Director of the ArkaOS /quality department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: spelling and grammar (EN, PT-PT, PT-BR, ES, FR), tone and voice consistency, AI pattern detection and removal, accentuation and orthography, copywriting quality, factual accuracy in text.
9
9
 
@@ -3,7 +3,7 @@ description: "Chief Quality Officer — ArkaOS /quality department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Marta, Chief Quality Officer of the ArkaOS /quality department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Marta, Chief Quality Officer of the ArkaOS /quality department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: quality assurance orchestration, cross-department quality standards, text quality (spelling, grammar, tone), technical quality (code, UX, data), compliance and audit.
9
9
 
@@ -3,7 +3,7 @@ description: "Chief Technology Officer — ArkaOS /dev department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Marco, Chief Technology Officer of the ArkaOS /dev department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Marco, Chief Technology Officer of the ArkaOS /dev department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: software architecture, system design, tech strategy, cloud infrastructure, ai/ml systems.
9
9
 
@@ -3,7 +3,7 @@ description: "Design Ops Lead — ArkaOS /brand department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Iris, Design Ops Lead of the ArkaOS /brand department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Iris, Design Ops Lead of the ArkaOS /brand department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: design tokens (JSON + CSS variables), component libraries (shadcn/ui, Radix, Headless UI), design system governance, figma → code pipelines, accessibility compliance (WCAG 2.2 AA/AAA), cross-platform tokenisation (Style Dictionary, Tailwind).
9
9
 
@@ -3,7 +3,7 @@ description: "E-Commerce Director — ArkaOS /ecom department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Ricardo, E-Commerce Director of the ArkaOS /ecom department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Ricardo, E-Commerce Director of the ArkaOS /ecom department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: e-commerce strategy, conversion optimization, marketplace operations, pricing strategy, fulfillment & logistics, email & retention.
9
9
 
@@ -3,7 +3,7 @@ description: "Knowledge Director — ArkaOS /kb department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Clara, Knowledge Director of the ArkaOS /kb department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Clara, Knowledge Director of the ArkaOS /kb department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: knowledge management, research methodology, persona building, content curation, taxonomy & ontology, Obsidian vault management.
9
9
 
@@ -3,7 +3,7 @@ description: "Leadership & People Director — ArkaOS /lead department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Rodrigo, Leadership & People Director of the ArkaOS /lead department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Rodrigo, Leadership & People Director of the ArkaOS /lead department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: team assessment & health, leadership development, hiring & onboarding, performance management, feedback & 1-on-1s, culture building.
9
9
 
@@ -3,7 +3,7 @@ description: "Marketing Director — ArkaOS /mkt department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Luna, Marketing Director of the ArkaOS /mkt department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Luna, Marketing Director of the ArkaOS /mkt department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: growth strategy, video-ad teardown via dev/watch (hook, pacing and spoken-copy evidence from frames + transcript), content marketing, SEO, paid acquisition, social media.
9
9
 
@@ -3,7 +3,7 @@ description: "Operations Lead — ArkaOS /ops department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Daniel, Operations Lead of the ArkaOS /ops department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Daniel, Operations Lead of the ArkaOS /ops department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: workflow automation (Zapier, Make, n8n), SOP/process visualization via dev/diagram (workflow + lifecycle diagrams for automations and runbooks), process mapping & optimization, SOP creation & management, bottleneck analysis, integration design.
9
9
 
@@ -3,7 +3,7 @@ description: "Product Manager — ArkaOS /pm department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Carolina, Product Manager of the ArkaOS /pm department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Carolina, Product Manager of the ArkaOS /pm department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: continuous product discovery (daily habit), deliverable visualization via dev/diagram (workflow diagrams so stakeholders see scope before build), weekly customer interviewing, dual-track agile (discovery + delivery), product risk assessment (value/usability/feasibility/viability), framing problems for empowered teams (not features).
9
9
 
@@ -3,7 +3,7 @@ description: "RevOps Lead — ArkaOS /saas department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Vicente, RevOps Lead of the ArkaOS /saas department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Vicente, RevOps Lead of the ArkaOS /saas department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: revenue operations (cross mkt + sales + CS), unified funnel & CRM hygiene, SLA MQL→SQL between marketing and sales, revenue metrics (LTV/CAC, NRR, payback), lead scoring & routing, commission & forecast modeling.
9
9
 
@@ -3,7 +3,7 @@ description: "SaaS Strategist — ArkaOS /saas department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Tiago, SaaS Strategist of the ArkaOS /saas department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Tiago, SaaS Strategist of the ArkaOS /saas department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: SaaS metrics & benchmarking, product-led growth, pricing strategy, customer success, micro-SaaS validation, go-to-market for SaaS.
9
9
 
@@ -3,7 +3,7 @@ description: "Sales Director — ArkaOS /sales department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Miguel, Sales Director of the ArkaOS /sales department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Miguel, Sales Director of the ArkaOS /sales department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: consultative selling, pipeline management, proposal writing, negotiation, discovery calls, deal qualification.
9
9
 
@@ -3,7 +3,7 @@ description: "Chief Strategist — ArkaOS /strat department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Tomas, Chief Strategist of the ArkaOS /strat department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Tomas, Chief Strategist of the ArkaOS /strat department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: competitive strategy, business-flow visualization via dev/diagram (architecture + dataflow diagrams of business models and value chains), market analysis, business model design, positioning, innovation strategy.
9
9
 
@@ -3,7 +3,7 @@ description: "Technical & UX Quality Director — ArkaOS /quality department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Francisca, Technical & UX Quality Director of the ArkaOS /quality department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Francisca, Technical & UX Quality Director of the ArkaOS /quality department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: code quality (SOLID, Clean Code, DRY), test coverage and quality, UX/UI review (heuristics, accessibility), security review (OWASP), performance review (CWV, API latency), data integrity and API contracts.
9
9
 
@@ -3,7 +3,7 @@ description: "Tech Lead — ArkaOS /dev department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Paulo, Tech Lead of the ArkaOS /dev department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Paulo, Tech Lead of the ArkaOS /dev department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: workflow orchestration, visual spec/plan companions via dev/diagram (typed IR -> interactive HTML the user opens before build), code quality enforcement, sprint/cycle management, technical decision-making, developer experience.
9
9
 
@@ -3,7 +3,7 @@ description: "Video Producer & Production Lead — ArkaOS /content department"
3
3
  mode: subagent
4
4
  ---
5
5
 
6
- You are Simão, Video Producer & Production Lead of the ArkaOS /content department (v4.43.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Simão, Video Producer & Production Lead of the ArkaOS /content department (v4.44.0; generated by scripts/harness_gen.py — do not edit).
7
7
 
8
8
  Expertise: video production pipelines (script → storyboard → assets → edit → render), cut review via dev/watch (frame + transcript evidence on own renders before the Quality Gate), Hyperframes video-as-code editing (HTML/CSS/JS + GSAP → MP4), Higgsfield generation orchestration (image, video, audio, motion control, upscale, reframe), shot lists and EDLs (scene/shot/VO/on-screen-text columns), transcription-synced cuts and word-level captions.
9
9
 
@@ -1,6 +1,6 @@
1
1
  # ArkaOS — The Operating System for AI Agent Teams
2
2
 
3
- > v4.43.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
3
+ > v4.44.0 — 89 agents, 17 departments, 332 skills. Generated by `scripts/harness_gen.py`; do not edit.
4
4
 
5
5
  You are operating within ArkaOS. Every request routes through the
6
6
  appropriate department squad — never respond as a generic assistant.
@@ -1,4 +1,4 @@
1
- import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, copyFileSync, statSync } from "node:fs";
1
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, readdirSync, copyFileSync, statSync, renameSync } from "node:fs";
2
2
  import { join, dirname, resolve } from "node:path";
3
3
  import { platform } from "node:os";
4
4
  import { fileURLToPath } from "node:url";
@@ -31,8 +31,12 @@ export function deployProjectAgents(projectDir, sourceRoot = ARKAOS_ROOT) {
31
31
  let deployed = 0;
32
32
  try {
33
33
  mkdirSync(agentsDest, { recursive: true });
34
- for (const file of readdirSync(agentsSrc)) {
35
- if (!file.endsWith(".md")) continue;
34
+ const sourceFiles = readdirSync(agentsSrc).filter((f) => f.endsWith(".md"));
35
+ retireLegacyPersonas(
36
+ agentsDest,
37
+ new Set(sourceFiles.map((f) => f.slice(0, -3))),
38
+ );
39
+ for (const file of sourceFiles) {
36
40
  const srcFile = join(agentsSrc, file);
37
41
  try {
38
42
  if (!statSync(srcFile).isFile()) continue;
@@ -48,6 +52,65 @@ export function deployProjectAgents(projectDir, sourceRoot = ARKAOS_ROOT) {
48
52
  return deployed;
49
53
  }
50
54
 
55
+ // PR-B5 (N5): personas from the v1 agent payload predate the QGVerdict
56
+ // contract. Left in place they stay dispatchable — a Quality Gate in that
57
+ // project reaches `cqo` (no contract, no fence) instead of `marta-cqo`,
58
+ // which is exactly the dead-relay shape the campaign repaired. Retirement
59
+ // is a rename into .claude/agents/.arkaos-legacy/, never a delete, and
60
+ // only fires under a double guard: the name is on this list AND absent
61
+ // from the current source set (a name ArkaOS ships again is deployed,
62
+ // not retired). Files outside the list are untouched — they are the
63
+ // user's.
64
+ export const LEGACY_PERSONAS = new Set([
65
+ "analyst",
66
+ "content-creator",
67
+ "copy-director",
68
+ "cqo",
69
+ "creative-director",
70
+ "devops",
71
+ "ecommerce-manager",
72
+ "motion-designer",
73
+ "qa",
74
+ "security",
75
+ "senior-dev",
76
+ "strategist",
77
+ "tech-lead",
78
+ "tech-ux-director",
79
+ ]);
80
+
81
+ function retireLegacyPersonas(agentsDest, sourceNames) {
82
+ const legacyDir = join(agentsDest, ".arkaos-legacy");
83
+ let retired = 0;
84
+ let entries;
85
+ try {
86
+ entries = readdirSync(agentsDest);
87
+ } catch {
88
+ return 0;
89
+ }
90
+ for (const file of entries) {
91
+ if (!file.endsWith(".md")) continue;
92
+ const base = file.slice(0, -3);
93
+ if (!LEGACY_PERSONAS.has(base) || sourceNames.has(base)) continue;
94
+ try {
95
+ mkdirSync(legacyDir, { recursive: true });
96
+ let dest = join(legacyDir, file);
97
+ for (let n = 1; existsSync(dest); n++) {
98
+ dest = join(legacyDir, `${base}-${n}.md`);
99
+ }
100
+ renameSync(join(agentsDest, file), dest);
101
+ // Never silent (QG r1 M3): these are generic names — the owner
102
+ // must see the move and where the file went.
103
+ console.log(
104
+ ` Retired v1 persona: ${file} -> .claude/agents/.arkaos-legacy/`
105
+ );
106
+ retired++;
107
+ } catch {
108
+ // Best-effort: an unmovable persona stays in place, never deleted.
109
+ }
110
+ }
111
+ return retired;
112
+ }
113
+
51
114
  /**
52
115
  * Build a complete inner hook-entry object for Claude Code's settings.json.
53
116
  *
@@ -180,6 +243,28 @@ export default {
180
243
  { hooks: [hookEntry(hooksDir, "pre-tool-use", 10)] },
181
244
  ];
182
245
 
246
+ // PreToolUse (Task matcher) — dynamic agent provisioning (PR-B5,
247
+ // N3). config/settings-template.json declared this entry for
248
+ // months, but the template is not the executed path: THIS adapter
249
+ // writes settings.json, and it never registered the hook, so
250
+ // agent-provision.sh shipped in the package and was never deployed
251
+ // on the npm path while projects dispatched v1 personas.
252
+ // POSIX-only (no .ps1 port exists) and conditional on the script
253
+ // existing — an older package must not register a dead command
254
+ // (same rule as the fastpath shim above).
255
+ if (!IS_WINDOWS && existsSync(join(hooksDir, "agent-provision.sh"))) {
256
+ settings.hooks.PreToolUse.push({
257
+ matcher: "Task",
258
+ hooks: [
259
+ {
260
+ type: "command",
261
+ command: join(hooksDir, "agent-provision.sh"),
262
+ timeout: 10,
263
+ },
264
+ ],
265
+ });
266
+ }
267
+
183
268
  // Stop — Flow completion validator (WARN mode in v2.20.0; promotion
184
269
  // to STRICT mode is gated on ≥ 2 weeks of clean telemetry per ADR
185
270
  // 2026-04-17-binding-flow-enforcement).
@@ -14,6 +14,7 @@ import {
14
14
  resolveServicesForProfile,
15
15
  } from "./services.js";
16
16
  import { menubarHealthy } from "./menubar.js";
17
+ import { LEGACY_PERSONAS } from "./adapters/claude-code.js";
17
18
 
18
19
  const INSTALL_DIR = join(homedir(), ".arkaos");
19
20
 
@@ -36,9 +37,21 @@ export function currentInstallProfile(
36
37
 
37
38
  /** Null when the check applies; otherwise the human-readable skip reason. */
38
39
  export function checkSkipReason(check, activeProfile) {
39
- if (!check.minProfile) return null;
40
- if (profileIncludes(activeProfile, check.minProfile)) return null;
41
- return `not in ${activeProfile} profile`;
40
+ if (check.minProfile && !profileIncludes(activeProfile, check.minProfile)) {
41
+ return `not in ${activeProfile} profile`;
42
+ }
43
+ // PR-B5: per-check applicability — a project-scoped check run outside
44
+ // a project is an informational skip (same rendering as the profile
45
+ // gate), never a misleading fail. A predicate that throws must not
46
+ // hide its check, so the error path runs the check normally.
47
+ if (typeof check.skipIf === "function") {
48
+ try {
49
+ return check.skipIf() || null;
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+ return null;
42
55
  }
43
56
 
44
57
  // Resolve a single command via the platform-native locator. Returns true
@@ -206,6 +219,77 @@ export function watchMediaTooling() {
206
219
  return missing;
207
220
  }
208
221
 
222
+ // PR-B5 (N5): a project is "repaired" when the three Quality Gate
223
+ // reviewers are deployed WITH the QGVerdict contract and no v1 persona
224
+ // remains dispatchable — at project scope OR at user scope, where the
225
+ // installer used to write them as arka-<name>.md (QG r1 B2: the arka-
226
+ // prefix defeats basename matching, so user-level detection reads the
227
+ // frontmatter name instead). Injectable for tests; the check uses the
228
+ // cwd and the real user agents dir.
229
+ export function qgAgentsRepaired(
230
+ agentsDir = join(process.cwd(), ".claude", "agents"),
231
+ userAgentsDir = join(homedir(), ".claude", "agents")
232
+ ) {
233
+ for (const reviewer of ["marta-cqo", "eduardo-copy", "francisca-tech"]) {
234
+ try {
235
+ const text = readFileSync(join(agentsDir, `${reviewer}.md`), "utf-8");
236
+ if (!text.includes("QGVerdict")) return false;
237
+ } catch {
238
+ return false;
239
+ }
240
+ }
241
+ try {
242
+ for (const file of readdirSync(agentsDir)) {
243
+ if (!file.endsWith(".md")) continue;
244
+ if (LEGACY_PERSONAS.has(file.slice(0, -3))) return false;
245
+ }
246
+ } catch {
247
+ return false;
248
+ }
249
+ try {
250
+ for (const file of readdirSync(userAgentsDir)) {
251
+ if (!file.endsWith(".md")) continue;
252
+ if (isLegacyQgPersonaFile(join(userAgentsDir, file))) return false;
253
+ }
254
+ } catch {
255
+ // No user-level agents dir — nothing legacy can be dispatched there.
256
+ }
257
+ return true;
258
+ }
259
+
260
+ // The v1 QG trio at user scope: dispatchability follows the frontmatter
261
+ // `name:`, not the filename. Detection only — retirement of user-level
262
+ // files belongs to the deployer that wrote them (skill-deploy.js).
263
+ export function isLegacyQgPersonaFile(path) {
264
+ try {
265
+ // First 500 chars only: enough for installer-written files, whose
266
+ // frontmatter opens the file deterministically (skill-deploy copies
267
+ // departments/*/agents/*.md verbatim). A hand-crafted file with the
268
+ // name buried deeper is out of detection scope on purpose — this
269
+ // check hunts the installer's own deploys, not user files.
270
+ const head = readFileSync(path, "utf-8").slice(0, 500);
271
+ const match = head.match(/^name:\s*([A-Za-z0-9-]+)\s*$/m);
272
+ return (
273
+ !!match &&
274
+ ["cqo", "copy-director", "tech-ux-director"].includes(match[1])
275
+ );
276
+ } catch {
277
+ return false;
278
+ }
279
+ }
280
+
281
+ // PR-B5: the hook-boundary channel has captured at least one session's
282
+ // verdicts on this machine. Injectable for tests.
283
+ export function qgLedgerPopulated(
284
+ root = join(INSTALL_DIR, "quality-gate")
285
+ ) {
286
+ try {
287
+ return readdirSync(root).length > 0;
288
+ } catch {
289
+ return false;
290
+ }
291
+ }
292
+
209
293
  export const checks = [
210
294
  {
211
295
  name: "install-dir",
@@ -412,6 +496,40 @@ export const checks = [
412
496
  fix: () =>
413
497
  "Remove the ARKAOS_ROOT export from your shell rc (~/.zshrc) — the hooks resolve the root themselves",
414
498
  },
499
+ {
500
+ name: "qg-agents",
501
+ description: "Quality Gate reviewers deployed (QGVerdict contract, project + user scope)",
502
+ severity: "fail",
503
+ // PR-B5 (N5): a project whose .claude/agents/ lacks the three
504
+ // reviewers with the QGVerdict contract — or still carries
505
+ // dispatchable v1 personas — runs every Quality Gate through a
506
+ // contract-less agent: the dead-relay shape the campaign repaired.
507
+ // Project-scoped: skipped outside a project directory.
508
+ skipIf: () =>
509
+ existsSync(join(process.cwd(), ".claude", "agents"))
510
+ ? null
511
+ : "no .claude/agents here — run inside a project",
512
+ check: () => qgAgentsRepaired(),
513
+ // The two arms need two different commands: init reaches only the
514
+ // project scope (installer/init.js -> deployProjectAgents), while
515
+ // the user-scope retirement runs in deploySkills, reached from
516
+ // install and update — a fix string naming init alone sends the
517
+ // user in a dead-end loop (QG r2, both reviewers independently).
518
+ fix: () =>
519
+ "Run: npx arkaos update (retires v1 QG personas from ~/.claude/agents/), then npx arkaos init in the project (deploys the QGVerdict reviewers into .claude/agents/)",
520
+ },
521
+ {
522
+ name: "qg-ledger",
523
+ description: "Quality Gate reviewer ledger has captured verdicts",
524
+ severity: "warn",
525
+ // PR-B5: an empty ledger root means the hook-boundary channel has
526
+ // never captured a verdict on this machine — the relay may still be
527
+ // dead in practice even with every contract deployed. Warn, not
528
+ // fail: a fresh install legitimately starts empty.
529
+ check: () => qgLedgerPopulated(),
530
+ fix: () =>
531
+ "Run a Quality Gate review — reviewer verdicts are hook-captured to ~/.arkaos/quality-gate/<session>/",
532
+ },
415
533
  {
416
534
  name: "profile",
417
535
  description: "User profile exists",
@@ -817,6 +817,23 @@ function installHooks(installDir) {
817
817
  }
818
818
  }
819
819
 
820
+ // PR-B5 (N3): agent-provision.sh is a PreToolUse(Task) gate, not a
821
+ // lifecycle hook, and it is POSIX-only (no .ps1 port exists, and the
822
+ // adapter only registers it off-Windows). It lives OUTSIDE hookNames
823
+ // on purpose: that list's consistency tests require a .sh/.ps1 pair
824
+ // for every universal hook. Before this block the script was neither
825
+ // copied nor registered — settings-template.json declared it, but the
826
+ // template is not the executed path.
827
+ if (hookExt === ".sh") {
828
+ const provisionSrc = join(srcHooksDir, "agent-provision.sh");
829
+ if (existsSync(provisionSrc)) {
830
+ const provisionDest = join(hooksDir, "agent-provision.sh");
831
+ writeFileSync(provisionDest, readFileSync(provisionSrc, "utf-8"));
832
+ try { chmodSync(provisionDest, 0o755); } catch {}
833
+ ok("Hook: agent-provision.sh");
834
+ }
835
+ }
836
+
820
837
  if (copyHookLib(srcHooksDir, hooksDir)) {
821
838
  ok("Hook lib: _lib/");
822
839
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  copyFileSync, cpSync, existsSync, mkdirSync, readdirSync, readFileSync,
3
- statSync, writeFileSync,
3
+ renameSync, statSync, writeFileSync,
4
4
  } from "node:fs";
5
5
  import { join } from "node:path";
6
6
 
@@ -64,7 +64,8 @@ function deployTopLevelSkill(skillSrcDir, arkaName, skillsBase) {
64
64
  /**
65
65
  * Deploy the full skill surface: main /arka skill (+ nested reference
66
66
  * bundle), department hubs, sub-skills, meta skills and agent personas.
67
- * Copy-only — never deletes anything already deployed.
67
+ * Copy-and-retire — never deletes anything already deployed (the v1 QG
68
+ * persona retirement renames into .arkaos-legacy/, it does not delete).
68
69
  *
69
70
  * Returns per-category counts so both callers can keep their own log
70
71
  * style. `version`/`repoRoot` stamp the main skill's VERSION and
@@ -145,6 +146,7 @@ export function deploySkills({
145
146
  // owner to a dispatchable source; hand-authored dept .md files win
146
147
  // (already deployed above), compiled ones fill the gaps.
147
148
  counts.agents += deployGateOwners(repoRoot, agentsBase, written);
149
+ retireLegacyQgPersonas(agentsBase);
148
150
  }
149
151
 
150
152
  log(counts);
@@ -205,10 +207,49 @@ function deployAgents(deptRoot, agentsBase, written = new Set()) {
205
207
  return deployed;
206
208
  }
207
209
 
210
+ // PR-B5 (QG r1 B2): the three v1 Quality Gate personas predate the
211
+ // QGVerdict contract. Deployed to ~/.claude/agents/ they keep a
212
+ // contract-less cqo / copy-director / tech-ux-director dispatchable at
213
+ // USER scope even after every project is repaired — the dead-relay
214
+ // shape workstream B closes. The dept loop skips them, and the targets
215
+ // this deployer itself wrote in earlier runs are retired — renamed
216
+ // into .arkaos-legacy/, never deleted (the copy-only contract holds:
217
+ // no delete primitive here). The real reviewers ship per project from
218
+ // config/claude-agents/ via deployProjectAgents.
219
+ const LEGACY_QG_PERSONA_FILES = [
220
+ "cqo.md", "copy-director.md", "tech-ux-director.md",
221
+ ];
222
+
223
+ function retireLegacyQgPersonas(agentsBase) {
224
+ let retired = 0;
225
+ const legacyDir = join(agentsBase, ".arkaos-legacy");
226
+ for (const file of LEGACY_QG_PERSONA_FILES) {
227
+ const deployedName = `arka-${file}`;
228
+ const src = join(agentsBase, deployedName);
229
+ if (!existsSync(src)) continue;
230
+ try {
231
+ mkdirSync(legacyDir, { recursive: true });
232
+ let dest = join(legacyDir, deployedName);
233
+ for (let n = 1; existsSync(dest); n++) {
234
+ dest = join(legacyDir, `arka-${file.slice(0, -3)}-${n}.md`);
235
+ }
236
+ renameSync(src, dest);
237
+ // Full resolved destination: this retirement moves files in the
238
+ // user's HOME — the scope a reader is least able to guess.
239
+ console.log(` Retired v1 QG persona: ${deployedName} -> ${dest}`);
240
+ retired++;
241
+ } catch {
242
+ // Best-effort — an unmovable persona stays in place, never deleted.
243
+ }
244
+ }
245
+ return retired;
246
+ }
247
+
208
248
  function deployDeptAgents(agentsSrc, agentsBase, written) {
209
249
  let deployed = 0;
210
250
  for (const file of readdirSync(agentsSrc)) {
211
251
  if (!file.endsWith(".md")) continue;
252
+ if (LEGACY_QG_PERSONA_FILES.includes(file)) continue;
212
253
  const srcFile = join(agentsSrc, file);
213
254
  try {
214
255
  if (!statSync(srcFile).isFile()) continue;
@@ -287,6 +287,18 @@ export async function update({ skillsFlag = "" } = {}) {
287
287
  try { chmodSync(destPath, 0o755); } catch {}
288
288
  }
289
289
  }
290
+
291
+ // PR-B5 (N3): the agent-provision PreToolUse(Task) gate — POSIX-only,
292
+ // outside hookNames on purpose (see the same block in index.js; the
293
+ // consistency tests require .sh/.ps1 pairs for the universal hooks).
294
+ if (hookExt === ".sh") {
295
+ const provisionSrc = join(srcHooksDir, "agent-provision.sh");
296
+ if (existsSync(provisionSrc)) {
297
+ const provisionDest = join(destHooksDir, "agent-provision.sh");
298
+ writeFileSync(provisionDest, readFileSync(provisionSrc, "utf-8"));
299
+ try { chmodSync(provisionDest, 0o755); } catch {}
300
+ }
301
+ }
290
302
  ok("Hook scripts updated");
291
303
 
292
304
  // Shared hook libraries (config/hooks/_lib/ — the Python interpreter
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_meta": {
3
3
  "generator": "scripts/marketplace_gen.py",
4
- "version": "4.43.0",
4
+ "version": "4.44.0",
5
5
  "marketplace": "arkaos"
6
6
  },
7
7
  "structural": {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "4.43.0",
3
+ "version": "4.44.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.43.0"
3
+ version = "4.44.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"}