arkaos 5.9.0 → 5.10.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/core/governance/evidence_checks.py +166 -22
  4. package/core/sync/content_merger.py +77 -26
  5. package/core/sync/content_syncer.py +67 -29
  6. package/core/sync/engine.py +28 -20
  7. package/core/sync/manifest.py +48 -8
  8. package/core/sync/reporter.py +49 -29
  9. package/core/sync/schema.py +2 -0
  10. package/departments/ops/skills/update/references/sync-engine.md +1 -1
  11. package/departments/ops/skills/update/references/workflows.md +4 -2
  12. package/harness/codex/AGENTS.md +1 -1
  13. package/harness/copilot/copilot-instructions.md +1 -1
  14. package/harness/cursor/rules/arkaos.mdc +2 -2
  15. package/harness/gemini/GEMINI.md +1 -1
  16. package/harness/opencode/AGENTS.md +1 -1
  17. package/harness/opencode/agents/arka-architect-gabriel.md +1 -1
  18. package/harness/opencode/agents/arka-brand-director-valentina.md +1 -1
  19. package/harness/opencode/agents/arka-cfo-helena.md +1 -1
  20. package/harness/opencode/agents/arka-chief-of-staff-afonso.md +1 -1
  21. package/harness/opencode/agents/arka-community-strategist-beatriz.md +1 -1
  22. package/harness/opencode/agents/arka-content-strategist-rafael.md +1 -1
  23. package/harness/opencode/agents/arka-conversion-strategist-ines.md +1 -1
  24. package/harness/opencode/agents/arka-coo-sofia.md +1 -1
  25. package/harness/opencode/agents/arka-copy-director-eduardo.md +1 -1
  26. package/harness/opencode/agents/arka-cqo-marta.md +1 -1
  27. package/harness/opencode/agents/arka-cto-marco.md +1 -1
  28. package/harness/opencode/agents/arka-design-ops-lead-iris.md +1 -1
  29. package/harness/opencode/agents/arka-ecom-director-ricardo.md +1 -1
  30. package/harness/opencode/agents/arka-knowledge-director-clara.md +1 -1
  31. package/harness/opencode/agents/arka-leadership-director-rodrigo.md +1 -1
  32. package/harness/opencode/agents/arka-marketing-director-luna.md +1 -1
  33. package/harness/opencode/agents/arka-ops-lead-daniel.md +1 -1
  34. package/harness/opencode/agents/arka-pm-director-carolina.md +1 -1
  35. package/harness/opencode/agents/arka-revops-lead-vicente.md +1 -1
  36. package/harness/opencode/agents/arka-saas-strategist-tiago.md +1 -1
  37. package/harness/opencode/agents/arka-sales-director-miguel.md +1 -1
  38. package/harness/opencode/agents/arka-strategy-director-tomas.md +1 -1
  39. package/harness/opencode/agents/arka-tech-director-francisca.md +1 -1
  40. package/harness/opencode/agents/arka-tech-lead-paulo.md +1 -1
  41. package/harness/opencode/agents/arka-video-producer-simao.md +1 -1
  42. package/harness/zed/.rules +1 -1
  43. package/knowledge/commands-registry.json +1 -1
  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
- > v5.9.0 — 89 agents, 17 departments, 340 skills, 306 commands, 20 ADRs.
3
+ > v5.10.0 — 89 agents, 17 departments, 340 skills, 306 commands, 20 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
- 5.9.0
1
+ 5.10.0
@@ -35,7 +35,8 @@ import subprocess
35
35
  import sys
36
36
  import time
37
37
  from dataclasses import asdict, dataclass, field, replace
38
- from pathlib import Path
38
+ from pathlib import Path, PurePosixPath
39
+ from xml.etree import ElementTree
39
40
 
40
41
  from core.governance.qg_digest import evidence_digest
41
42
  from core.shared.test_evidence import coverage_percent_from_xml
@@ -553,30 +554,172 @@ def _junit_result(junit: Path) -> CheckResult:
553
554
  )
554
555
 
555
556
 
557
+ def _coverage_from_xml(
558
+ coverage_xml: Path, project_dir: Path, changed: list[str] | None,
559
+ ) -> CheckResult:
560
+ """Coverage verdict from an artefact, refusing one that cannot describe the diff."""
561
+ stale = _stale_coverage_reason(coverage_xml, project_dir, changed)
562
+ if stale is not None:
563
+ return CheckResult(
564
+ check="coverage", ran=True, passed=False,
565
+ command="parse:coverage.xml", exit_code=None,
566
+ summary=stale, details_path=str(coverage_xml),
567
+ )
568
+ percent = coverage_percent_from_xml(coverage_xml)
569
+ if percent is None:
570
+ return CheckResult(
571
+ check="coverage", ran=True, passed=None,
572
+ command="parse:coverage.xml", exit_code=None,
573
+ summary="coverage.xml present but unparseable",
574
+ details_path=str(coverage_xml),
575
+ )
576
+ return CheckResult(
577
+ check="coverage", ran=True,
578
+ passed=percent >= COVERAGE_THRESHOLD,
579
+ command="parse:coverage.xml", exit_code=None,
580
+ summary=f"coverage {percent:.1f}% (threshold {COVERAGE_THRESHOLD:.0f}%)",
581
+ details_path=str(coverage_xml),
582
+ )
583
+
584
+
585
+ def _stale_coverage_reason(
586
+ coverage_xml: Path, project_dir: Path, changed: list[str] | None,
587
+ ) -> str | None:
588
+ """Reason the artefact cannot describe this diff, or None if it can.
589
+
590
+ A coverage.xml older than the newest changed source measured a different
591
+ codebase, and a green number then vouches for code it never executed.
592
+ """
593
+ try:
594
+ artefact_mtime = coverage_xml.stat().st_mtime
595
+ except OSError:
596
+ return "coverage.xml unreadable"
597
+
598
+ newest_name = _newest_changed_after(project_dir, changed, artefact_mtime)
599
+ if newest_name is not None:
600
+ return (
601
+ f"coverage.xml predates changed source ({newest_name}) — "
602
+ "regenerate it; it cannot describe this diff"
603
+ )
604
+ return _missing_module_reason(coverage_xml, changed, project_dir)
605
+
606
+
607
+ def _missing_module_reason(
608
+ coverage_xml: Path, changed: list[str] | None, project_dir: Path,
609
+ ) -> str | None:
610
+ """Reason a changed module is absent from the artefact, or None."""
611
+ covered = _covered_paths(coverage_xml, project_dir)
612
+ missing = [
613
+ rel for rel in (changed or [])
614
+ if rel.endswith(".py")
615
+ and not rel.startswith("tests/")
616
+ and not _is_covered(rel, covered)
617
+ ]
618
+ if not missing:
619
+ return None
620
+ return (
621
+ f"coverage.xml has no entry for {len(missing)} changed module(s), "
622
+ f"first: {missing[0]}"
623
+ )
624
+
625
+
626
+ def _newest_changed_after(
627
+ project_dir: Path, changed: list[str] | None, cutoff: float,
628
+ ) -> str | None:
629
+ """Name of the newest changed .py file modified after cutoff, else None.
630
+
631
+ Only executable source can invalidate a coverage artefact — treating
632
+ CHANGELOG.md as "changed source" forced a full regeneration for edits no
633
+ test could ever execute (same filter the module-presence check applies).
634
+ """
635
+ newest = cutoff
636
+ newest_name: str | None = None
637
+ for rel in changed or []:
638
+ if not rel.endswith(".py"):
639
+ continue
640
+ try:
641
+ mtime = (project_dir / rel).stat().st_mtime
642
+ except OSError:
643
+ continue
644
+ if mtime > newest:
645
+ newest, newest_name = mtime, rel
646
+ return newest_name
647
+
648
+
649
+ def _covered_paths(coverage_xml: Path, project_dir: Path) -> set[str]:
650
+ """Covered files as paths relative to project_dir, exactly.
651
+
652
+ A ``class/@filename`` is relative to one of the ``sources/source``
653
+ roots, which are usually absolute and which the changed-file list never
654
+ carries. Each candidate is therefore rebuilt against every source and
655
+ re-expressed relative to the project, so comparison can be equality.
656
+
657
+ Suffix or stem matching is not good enough here: `core/` alone carries
658
+ 14 colliding stems, this repo's own artefact yields five bare basenames,
659
+ and a suffix rule cannot tell `core/sync/engine.py` from
660
+ `vendor/core/sync/engine.py`. Equality can.
661
+ """
662
+ try:
663
+ root = ElementTree.parse(coverage_xml).getroot()
664
+ except (ElementTree.ParseError, OSError):
665
+ return set()
666
+ sources = [(el.text or "").strip() for el in root.iterfind("sources/source")]
667
+ try:
668
+ base = project_dir.resolve()
669
+ except OSError:
670
+ base = project_dir
671
+ return {
672
+ candidate
673
+ for cls in root.iter("class")
674
+ if cls.get("filename")
675
+ for candidate in _source_candidates(cls.get("filename", ""), sources, base)
676
+ }
677
+
678
+
679
+ def _source_candidates(
680
+ filename: str, sources: list[str], base: Path,
681
+ ) -> set[str]:
682
+ """Project-relative spellings of one covered file, anchored.
683
+
684
+ With ``<source>`` roots declared, every candidate must resolve through
685
+ one of them into the project — the unanchored raw filename let another
686
+ checkout's artefact vouch for this project's files. The raw spelling is
687
+ a fallback only when no sources exist; a relative source resolves
688
+ against the project, never the CWD.
689
+ """
690
+ if not sources:
691
+ raw = PurePosixPath(filename)
692
+ return set() if raw.is_absolute() else {raw.as_posix()}
693
+
694
+ # Candidates must exist on disk; existence under more than one source
695
+ # is ambiguous — vouch for neither (fail closed).
696
+ found: set[str] = set()
697
+ for source in sources:
698
+ src = Path(source)
699
+ if not src.is_absolute():
700
+ src = base / src
701
+ try:
702
+ resolved = (src / filename).resolve()
703
+ if not resolved.is_file():
704
+ continue
705
+ found.add(PurePosixPath(resolved.relative_to(base)).as_posix())
706
+ except (OSError, ValueError):
707
+ continue
708
+ return found if len(found) == 1 else set()
709
+
710
+
711
+ def _is_covered(rel: str, covered: set[str]) -> bool:
712
+ """True only when the artefact names exactly this project-relative path."""
713
+ return PurePosixPath(rel).as_posix() in covered
714
+
715
+
556
716
  def _check_coverage(
557
717
  project_dir: Path, changed: list[str] | None,
558
718
  test_command: str | None, timeout: int,
559
719
  ) -> CheckResult:
560
720
  coverage_xml = project_dir / "coverage.xml"
561
721
  if coverage_xml.is_file():
562
- percent = coverage_percent_from_xml(coverage_xml)
563
- if percent is None:
564
- return CheckResult(
565
- check="coverage", ran=True, passed=None,
566
- command="parse:coverage.xml", exit_code=None,
567
- summary="coverage.xml present but unparseable",
568
- details_path=str(coverage_xml),
569
- )
570
- return CheckResult(
571
- check="coverage", ran=True,
572
- passed=percent >= COVERAGE_THRESHOLD,
573
- command="parse:coverage.xml", exit_code=None,
574
- summary=(
575
- f"coverage {percent:.1f}% "
576
- f"(threshold {COVERAGE_THRESHOLD:.0f}%)"
577
- ),
578
- details_path=str(coverage_xml),
579
- )
722
+ return _coverage_from_xml(coverage_xml, project_dir, changed)
580
723
  junit = project_dir / "junit.xml"
581
724
  if junit.is_file():
582
725
  return _junit_result(junit)
@@ -767,9 +910,10 @@ def _check_spellcheck(
767
910
  def _spellcheck_inspected_count(project_dir: Path, md_files: list[str]) -> int:
768
911
  """How many of ``md_files`` codespell actually reads after `skip`.
769
912
 
770
- Asks codespell itself (``--count`` on a per-file basis is too slow; a
771
- skipped file simply produces no output for any planted probe), so instead
772
- we replay its own glob semantics from the config.
913
+ Replays the config's skip globs with fnmatch rather than asking codespell
914
+ itself — per-file ``--count`` probing is too slow, and a skipped file
915
+ simply produces no output, so codespell cannot be asked which files it
916
+ ignored.
773
917
  """
774
918
  patterns = _codespell_skip_globs(project_dir)
775
919
  if not patterns:
@@ -23,7 +23,7 @@ _END_RE = re.compile(r"<!--\s*arkaos:managed:end\s*-->")
23
23
  class MergeResult:
24
24
  """Outcome of a managed-region merge operation."""
25
25
 
26
- status: Literal["updated", "unchanged", "error"]
26
+ status: Literal["updated", "restamped", "drifted", "unchanged", "error"]
27
27
  new_text: str
28
28
  error: str | None = None
29
29
 
@@ -38,52 +38,103 @@ def merge_managed_content(
38
38
  ) -> MergeResult:
39
39
  """Merge managed_content into target_text inside the managed region.
40
40
 
41
- Returns status "updated" when the file changes, "unchanged" when the
42
- new hash matches the existing one, or "error" when markers are
43
- unbalanced.
41
+ Returns "updated" when the managed content itself changes, "restamped"
42
+ when the verified-current content carries a stale stamp, "drifted" when
43
+ the block was edited in place (nothing is written), "unchanged" when
44
+ everything already matches, or "error" when markers are malformed.
44
45
  """
46
+ # Normalise once so the hash, the rendered block and the drift check all
47
+ # see the same bytes: hashing the unstripped input against a stripped
48
+ # body made the merger report `drifted` on output it wrote itself.
49
+ managed_content = managed_content.strip()
45
50
  starts = list(_START_RE.finditer(target_text))
46
51
  ends = list(_END_RE.finditer(target_text))
47
52
 
53
+ malformed = _validate_markers(starts, ends, target_text)
54
+ if malformed is not None:
55
+ return malformed
56
+
57
+ new_hash = compute_managed_hash(managed_content)
58
+ new_block = _render_block(managed_content, version, new_hash)
59
+
60
+ if not starts:
61
+ return _prepend_block(target_text, new_block)
62
+
63
+ start_match, end_match = starts[0], ends[0]
64
+ rewritten = (
65
+ target_text[: start_match.start()]
66
+ + new_block
67
+ + target_text[end_match.end() :]
68
+ )
69
+
70
+ body = target_text[start_match.end() : end_match.start()].strip()
71
+ return _decide(
72
+ stamped_hash=start_match.group("hash"),
73
+ stamped_version=start_match.group("version"),
74
+ body=body,
75
+ new_hash=new_hash,
76
+ version=version,
77
+ target_text=target_text,
78
+ rewritten=rewritten,
79
+ )
80
+
81
+
82
+ def _decide(
83
+ *,
84
+ stamped_hash: str | None,
85
+ stamped_version: str | None,
86
+ body: str,
87
+ new_hash: str,
88
+ version: str,
89
+ target_text: str,
90
+ rewritten: str,
91
+ ) -> MergeResult:
92
+ """Choose the outcome for a well-formed block."""
93
+ if stamped_hash != new_hash:
94
+ return MergeResult(status="updated", new_text=rewritten)
95
+ # The stamp says the content matches canonical — but the stamp is a
96
+ # claim, not a measurement. Hash what is ACTUALLY between the markers
97
+ # before rewriting: an operator who edited inside the block leaves the
98
+ # stamp untouched, and a rewrite would delete their work to correct a
99
+ # version number.
100
+ if compute_managed_hash(body) != new_hash:
101
+ return MergeResult(
102
+ status="drifted",
103
+ new_text=target_text,
104
+ error="managed block was edited in place; left untouched",
105
+ )
106
+ if stamped_version == version:
107
+ return MergeResult(status="unchanged", new_text=target_text)
108
+ # Content verified current; only the stamp lags. Rewriting it keeps
109
+ # `version=` an honest record of the last sync that verified this file.
110
+ return MergeResult(status="restamped", new_text=rewritten)
111
+
112
+
113
+ def _validate_markers(
114
+ starts: list[re.Match[str]],
115
+ ends: list[re.Match[str]],
116
+ target_text: str,
117
+ ) -> MergeResult | None:
118
+ """Return an error MergeResult when the marker pair is malformed."""
48
119
  if len(starts) != len(ends):
49
120
  return MergeResult(
50
121
  status="error",
51
122
  new_text=target_text,
52
123
  error=f"unbalanced markers: {len(starts)} starts, {len(ends)} ends",
53
124
  )
54
-
55
125
  if len(starts) > 1:
56
126
  return MergeResult(
57
127
  status="error",
58
128
  new_text=target_text,
59
129
  error="multiple managed blocks are not supported",
60
130
  )
61
-
62
- new_hash = compute_managed_hash(managed_content)
63
- new_block = _render_block(managed_content, version, new_hash)
64
-
65
- if not starts:
66
- return _prepend_block(target_text, new_block)
67
-
68
- start_match = starts[0]
69
- end_match = ends[0]
70
- if end_match.start() < start_match.end():
131
+ if starts and ends[0].start() < starts[0].end():
71
132
  return MergeResult(
72
133
  status="error",
73
134
  new_text=target_text,
74
135
  error="end marker appears before start marker",
75
136
  )
76
-
77
- existing_hash = start_match.group("hash")
78
- if existing_hash == new_hash:
79
- return MergeResult(status="unchanged", new_text=target_text)
80
-
81
- new_text = (
82
- target_text[: start_match.start()]
83
- + new_block
84
- + target_text[end_match.end() :]
85
- )
86
- return MergeResult(status="updated", new_text=new_text)
137
+ return None
87
138
 
88
139
 
89
140
  def _render_block(content: str, version: str, content_hash: str) -> str:
@@ -19,6 +19,7 @@ from __future__ import annotations
19
19
 
20
20
  import os
21
21
  import shutil
22
+ from dataclasses import dataclass, field
22
23
  from pathlib import Path
23
24
 
24
25
  import yaml
@@ -51,39 +52,64 @@ def _do_sync(project: Project) -> ContentSyncResult:
51
52
  project_claude = Path(project.path) / ".claude"
52
53
  project_claude.mkdir(parents=True, exist_ok=True)
53
54
 
54
- updated: list[str] = []
55
- unchanged: list[str] = []
56
- errored: list[str] = []
57
-
58
- _sync_claude_md(core, project, project_claude, version, updated, unchanged, errored)
59
- _sync_rules(core, project_claude, updated, unchanged, errored)
60
- _sync_stack_rules(core, project, project_claude, updated, unchanged, errored)
61
- _sync_hooks(core, project_claude, updated, unchanged, errored)
62
- _sync_constitution(core, project_claude, updated, unchanged, errored)
63
-
64
- if errored:
65
- status = "error"
66
- elif updated:
67
- status = "updated"
68
- else:
69
- status = "unchanged"
55
+ out = _Artefacts()
56
+ _sync_claude_md(core, project, project_claude, version, out)
57
+ _sync_rules(core, project_claude, out.updated, out.unchanged, out.errored)
58
+ _sync_stack_rules(
59
+ core, project, project_claude, out.updated, out.unchanged, out.errored
60
+ )
61
+ _sync_hooks(core, project_claude, out.updated, out.unchanged, out.errored)
62
+ _sync_constitution(core, project_claude, out.updated, out.unchanged, out.errored)
63
+
70
64
  return ContentSyncResult(
71
65
  path=project.path,
72
- status=status,
73
- artefacts_updated=updated,
74
- artefacts_unchanged=unchanged,
75
- artefacts_errored=errored,
66
+ status=out.status(),
67
+ artefacts_updated=out.updated,
68
+ artefacts_restamped=out.restamped,
69
+ artefacts_drifted=out.drifted,
70
+ artefacts_unchanged=out.unchanged,
71
+ artefacts_errored=out.errored,
76
72
  )
77
73
 
78
74
 
75
+ @dataclass
76
+ class _Artefacts:
77
+ """Per-project artefact outcomes, one list per status.
78
+
79
+ Passed as a single object rather than four parallel out-parameters —
80
+ adding a fifth status would otherwise mean editing every signature that
81
+ threads them through.
82
+ """
83
+
84
+ updated: list[str] = field(default_factory=list)
85
+ restamped: list[str] = field(default_factory=list)
86
+ drifted: list[str] = field(default_factory=list)
87
+ unchanged: list[str] = field(default_factory=list)
88
+ errored: list[str] = field(default_factory=list)
89
+
90
+ def status(self) -> str:
91
+ """Worst outcome wins: error > drift > real change > restamp > no-op.
92
+
93
+ Drift outranks a change because it needs the operator's judgement;
94
+ it is NOT an error — the merger deliberately preserved their edit.
95
+ """
96
+ if self.errored:
97
+ return "error"
98
+ if self.drifted:
99
+ return "drifted"
100
+ if self.updated:
101
+ return "updated"
102
+ if self.restamped:
103
+ return "restamped"
104
+ return "unchanged"
105
+
106
+
79
107
  def _sync_claude_md(
80
108
  core: Path,
81
109
  project: Project,
82
110
  project_claude: Path,
83
111
  version: str,
84
- updated: list[str],
85
- unchanged: list[str],
86
- errored: list[str],
112
+ out: _Artefacts,
87
113
  ) -> None:
88
114
  # Stack conventions live in path-scoped rule files (_sync_stack_rules);
89
115
  # the managed block carries only the shared base.
@@ -92,18 +118,30 @@ def _sync_claude_md(
92
118
  )
93
119
  target_file = project_claude / "CLAUDE.md"
94
120
  target_text = target_file.read_text(encoding="utf-8") if target_file.exists() else ""
95
-
96
121
  result = merge_managed_content(target_text, managed_content, version)
122
+ _record_claude_md(result, target_file, managed_content, out)
123
+
124
+
125
+ def _record_claude_md(result, target_file, managed_content: str, out) -> None:
126
+ """Apply one merge outcome: write, skip, or record without writing."""
97
127
  if result.status == "error":
98
- errored.append(f"CLAUDE.md: {result.error}")
99
- sidecar = target_file.with_suffix(".md.arkaos-new")
100
- sidecar.write_text(managed_content, encoding="utf-8")
128
+ out.errored.append(f"CLAUDE.md: {result.error}")
129
+ target_file.with_suffix(".md.arkaos-new").write_text(
130
+ managed_content, encoding="utf-8"
131
+ )
132
+ return
133
+ if result.status == "drifted":
134
+ # The operator edited inside the managed block. Overwriting to fix a
135
+ # version number would delete their work; record it as drift — not as
136
+ # an error, because nothing failed.
137
+ out.drifted.append("CLAUDE.md")
101
138
  return
102
139
  if result.status == "unchanged":
103
- unchanged.append("CLAUDE.md")
140
+ out.unchanged.append("CLAUDE.md")
104
141
  return
105
142
  target_file.write_text(result.new_text, encoding="utf-8")
106
- updated.append("CLAUDE.md")
143
+ bucket = out.restamped if result.status == "restamped" else out.updated
144
+ bucket.append("CLAUDE.md")
107
145
 
108
146
 
109
147
  # Descriptor slug -> stack-rules basename (no .md). Slugs are case-folded first.
@@ -13,19 +13,20 @@ from pathlib import Path
13
13
 
14
14
  from core.runtime.user_paths import (
15
15
  ecosystems_file as resolve_ecosystems_file,
16
+ )
17
+ from core.runtime.user_paths import (
16
18
  projects_dir as resolve_projects_dir,
17
19
  )
18
- from core.sync.manifest import build_manifest
20
+ from core.sync.agent_provisioner import sync_all_agents
21
+ from core.sync.content_syncer import sync_all_content
22
+ from core.sync.descriptor_syncer import sync_all_descriptors
19
23
  from core.sync.discovery import discover_all_projects
24
+ from core.sync.manifest import build_manifest
20
25
  from core.sync.mcp_optimizer import optimize_all_mcps
21
26
  from core.sync.mcp_syncer import sync_all_mcps
22
- from core.sync.settings_syncer import sync_all_settings
23
- from core.sync.descriptor_syncer import sync_all_descriptors
24
- from core.sync.agent_provisioner import sync_all_agents
25
- from core.sync.content_syncer import sync_all_content
26
27
  from core.sync.reporter import build_report, format_report, write_sync_state
27
28
  from core.sync.schema import SyncReport
28
-
29
+ from core.sync.settings_syncer import sync_all_settings
29
30
 
30
31
  # ---------------------------------------------------------------------------
31
32
  # Public API
@@ -42,20 +43,7 @@ def run_sync(arkaos_home: Path, skills_dir: Path, home_path: str) -> SyncReport:
42
43
 
43
44
  projects = _discover_projects(arkaos_home, skills_dir)
44
45
 
45
- registry_path = skills_dir / "arka" / "mcps" / "registry.json"
46
- mcp_results = sync_all_mcps(projects, registry_path, home_path)
47
-
48
- policy_path = Path(__file__).resolve().parents[2] / "config" / "mcp-policy.yaml"
49
- vault_path = Path.home() / ".arkaos" / "secrets.json"
50
- cache_path = Path.home() / ".arkaos" / "mcp-decisions.cache.json"
51
- if policy_path.exists():
52
- mcp_results = optimize_all_mcps(
53
- projects,
54
- mcp_results,
55
- policy_path,
56
- vault_path if vault_path.exists() else None,
57
- cache_path,
58
- )
46
+ mcp_results = _run_mcp_phase(projects, skills_dir, home_path)
59
47
 
60
48
  settings_results = sync_all_settings(mcp_results)
61
49
  descriptor_results = sync_all_descriptors(projects)
@@ -111,6 +99,24 @@ def main() -> None:
111
99
  # ---------------------------------------------------------------------------
112
100
 
113
101
 
102
+ def _run_mcp_phase(projects: list, skills_dir: Path, home_path: str) -> list:
103
+ """Sync .mcp.json for every project, then apply the policy optimizer."""
104
+ registry_path = skills_dir / "arka" / "mcps" / "registry.json"
105
+ results = sync_all_mcps(projects, registry_path, home_path)
106
+
107
+ policy_path = Path(__file__).resolve().parents[2] / "config" / "mcp-policy.yaml"
108
+ if not policy_path.exists():
109
+ return results
110
+ vault_path = Path.home() / ".arkaos" / "secrets.json"
111
+ return optimize_all_mcps(
112
+ projects,
113
+ results,
114
+ policy_path,
115
+ vault_path if vault_path.exists() else None,
116
+ Path.home() / ".arkaos" / "mcp-decisions.cache.json",
117
+ )
118
+
119
+
114
120
  def _read_previous_version(arkaos_home: Path) -> str:
115
121
  """Read version field from sync-state.json, defaulting to pending-sync."""
116
122
  state_file = arkaos_home / "sync-state.json"
@@ -161,6 +167,8 @@ def _resolve_features_dir(arkaos_home: Path) -> Path:
161
167
  return fallback
162
168
 
163
169
 
170
+
171
+
164
172
  def _parse_scan_dirs(projects_dir_str: str) -> list[Path]:
165
173
  """Parse a projectsDir string, extracting all paths starting with /."""
166
174
  segments = re.split(r",\s*", projects_dir_str.strip())
@@ -46,12 +46,47 @@ def build_manifest(
46
46
  )
47
47
 
48
48
 
49
- def _is_version_newer(version: str, baseline: str) -> bool:
50
- """Return True if version is strictly newer than baseline (semver int tuple)."""
49
+ def compare_versions(version: str, baseline: str) -> bool | None:
50
+ """True if version > baseline, False if not, None if unorderable.
51
+
52
+ ``None`` is deliberately distinct from ``False``. A baseline of
53
+ ``unknown`` — which ``engine._read_current_version`` emits on a degraded
54
+ run and ``write_sync_state`` then persists — is not the same fact as "no
55
+ feature is newer", and collapsing the two makes a broken install report
56
+ "nothing changed" forever, with no error anywhere.
57
+ """
51
58
  def parse(v: str) -> tuple[int, ...]:
52
59
  return tuple(int(part) for part in v.split("."))
53
60
 
54
- return parse(version) > parse(baseline)
61
+ try:
62
+ return parse(version) > parse(baseline)
63
+ except ValueError:
64
+ return None
65
+
66
+
67
+ def is_version_newer(version: str, baseline: str) -> bool:
68
+ """True only when the pair is orderable and version is strictly newer.
69
+
70
+ The conservative reading, for callers where doing nothing is the safe
71
+ answer. Callers that must distinguish "unknown baseline" from "nothing
72
+ is new" call :func:`compare_versions` instead.
73
+ """
74
+ return compare_versions(version, baseline) is True
75
+
76
+
77
+ # Backwards-compatible private alias (used by existing call sites and tests).
78
+ _is_version_newer = is_version_newer
79
+
80
+
81
+ def _baseline_is_unorderable(previous_version: str) -> bool:
82
+ """True when previous_version itself cannot be ordered.
83
+
84
+ Tested against a known-good literal, never against the feature versions:
85
+ comparing the *pair* meant one malformed ``added_in`` in the registry
86
+ made an otherwise fine baseline look unorderable, and deprecated
87
+ features then stopped being removed — silently.
88
+ """
89
+ return compare_versions("0.0.0", previous_version) is None
55
90
 
56
91
 
57
92
  def _find_new_features(
@@ -59,14 +94,19 @@ def _find_new_features(
59
94
  previous_version: str,
60
95
  is_first: bool,
61
96
  ) -> list[str]:
62
- """Return names of features that are new relative to previous_version."""
63
- if is_first:
97
+ """Return names of features that are new relative to previous_version.
98
+
99
+ An unorderable baseline is treated as a first sync rather than as "nothing
100
+ is new": answering "nothing" would leave a degraded install permanently
101
+ convinced it is up to date.
102
+ """
103
+ if is_first or _baseline_is_unorderable(previous_version):
64
104
  return [f.name for f in features if f.deprecated_in is None]
65
105
 
66
106
  return [
67
107
  f.name
68
108
  for f in features
69
- if _is_version_newer(f.added_in, previous_version)
109
+ if compare_versions(f.added_in, previous_version) is True
70
110
  ]
71
111
 
72
112
 
@@ -76,12 +116,12 @@ def _find_deprecated_features(
76
116
  is_first: bool,
77
117
  ) -> list[str]:
78
118
  """Return names of features deprecated after previous_version."""
79
- if is_first:
119
+ if is_first or _baseline_is_unorderable(previous_version):
80
120
  return []
81
121
 
82
122
  return [
83
123
  f.name
84
124
  for f in features
85
125
  if f.deprecated_in is not None
86
- and _is_version_newer(f.deprecated_in, previous_version)
126
+ and compare_versions(f.deprecated_in, previous_version) is True
87
127
  ]
@@ -6,7 +6,7 @@ Builds the sync report, writes sync state to disk, and formats terminal output.
6
6
  from __future__ import annotations
7
7
 
8
8
  import json
9
- from datetime import datetime, timezone
9
+ from datetime import UTC, datetime
10
10
  from pathlib import Path
11
11
 
12
12
  from core.sync.schema import (
@@ -40,14 +40,7 @@ def build_report(
40
40
  agent_results: list[AgentProvisionResult] | None = None,
41
41
  ) -> SyncReport:
42
42
  """Aggregate all sync results into a SyncReport."""
43
- errors = _collect_errors(
44
- mcp_results,
45
- settings_results,
46
- descriptor_results,
47
- skill_results,
48
- content_results=content_results,
49
- agent_results=agent_results,
50
- )
43
+ phases = (mcp_results, settings_results, descriptor_results, skill_results)
51
44
  return SyncReport(
52
45
  previous_version=previous_version,
53
46
  current_version=current_version,
@@ -59,7 +52,9 @@ def build_report(
59
52
  skill_results=skill_results,
60
53
  content_results=content_results or [],
61
54
  agent_results=agent_results or [],
62
- errors=errors,
55
+ errors=_collect_errors(
56
+ *phases, content_results=content_results, agent_results=agent_results
57
+ ),
63
58
  )
64
59
 
65
60
 
@@ -69,7 +64,7 @@ def write_sync_state(state_file: Path, report: SyncReport) -> None:
69
64
  unique_paths = {r.path for r in report.mcp_results}
70
65
  state = {
71
66
  "version": report.current_version,
72
- "last_sync": datetime.now(timezone.utc).isoformat(),
67
+ "last_sync": datetime.now(UTC).isoformat(),
73
68
  "projects_synced": len(unique_paths),
74
69
  "skills_synced": len(report.skill_results),
75
70
  "errors": report.errors,
@@ -94,22 +89,22 @@ def format_report(report: SyncReport) -> str:
94
89
 
95
90
  key_changes = _format_key_changes(report)
96
91
  if key_changes:
97
- lines += ["", " Key changes:"]
98
- lines += [f" - {c}" for c in key_changes]
99
-
100
- total_deferred = sum(len(r.mcps_deferred) for r in report.mcp_results)
101
- projects_with_deferred = sum(1 for r in report.mcp_results if r.mcps_deferred)
102
- if total_deferred > 0:
103
- lines += ["", f" Deferred MCPs: {total_deferred} across {projects_with_deferred} projects."]
92
+ lines += ["", " Key changes:", *[f" - {c}" for c in key_changes]]
104
93
 
105
- lines += [
106
- "",
107
- f" Errors: {len(report.errors)}",
108
- _SEPARATOR,
109
- ]
94
+ lines += _format_deferred_lines(report.mcp_results)
95
+ lines += ["", f" Errors: {len(report.errors)}", _SEPARATOR]
110
96
  return "\n".join(lines)
111
97
 
112
98
 
99
+ def _format_deferred_lines(results: list[McpSyncResult]) -> list[str]:
100
+ """Deferred MCPs, or nothing when none were deferred."""
101
+ total = sum(len(r.mcps_deferred) for r in results)
102
+ if total == 0:
103
+ return []
104
+ projects = sum(1 for r in results if r.mcps_deferred)
105
+ return ["", f" Deferred MCPs: {total} across {projects} projects."]
106
+
107
+
113
108
  # ---------------------------------------------------------------------------
114
109
  # Private helpers
115
110
  # ---------------------------------------------------------------------------
@@ -138,16 +133,25 @@ def _collect_errors(
138
133
  for r in skills:
139
134
  if r.error:
140
135
  errors.append(f"Skill({r.skill_name}): {r.error}")
136
+ errors += _content_and_agent_errors(content_results, agent_results)
137
+ return errors
138
+
139
+
140
+ def _content_and_agent_errors(
141
+ content_results: list[ContentSyncResult] | None,
142
+ agent_results: list[AgentProvisionResult] | None,
143
+ ) -> list[str]:
144
+ errors: list[str] = []
141
145
  for r in content_results or []:
142
146
  if r.error:
143
147
  errors.append(f"Content({r.path}): {r.error}")
144
- for artefact_error in r.artefacts_errored:
145
- errors.append(f"Content({r.path}): {artefact_error}")
148
+ errors += [f"Content({r.path}): {e}" for e in r.artefacts_errored]
146
149
  for r in agent_results or []:
147
150
  if r.error:
148
151
  errors.append(f"Agents({r.path}): {r.error}")
149
- for a in r.agents_errored:
150
- errors.append(f"Agents({r.path}): missing core file for {a}")
152
+ errors += [
153
+ f"Agents({r.path}): missing core file for {a}" for a in r.agents_errored
154
+ ]
151
155
  return errors
152
156
 
153
157
 
@@ -169,8 +173,12 @@ def _format_phase_line(label: str, results: list) -> str:
169
173
  def _format_skill_line(results: list[SkillSyncResult]) -> str:
170
174
  total = len(results)
171
175
  updated = _count_updated(results)
176
+ restamped = sum(1 for r in results if r.status == "restamped")
172
177
  unchanged = _count_unchanged(results)
173
- return f" {'Skills:':<14}{total} ecosystems synced ({updated} updated, {unchanged} unchanged)"
178
+ counts = f"{updated} updated, {unchanged} unchanged"
179
+ if restamped:
180
+ counts = f"{updated} updated, {restamped} restamped, {unchanged} unchanged"
181
+ return f" {'Skills:':<14}{total} ecosystems synced ({counts})"
174
182
 
175
183
 
176
184
  def _format_key_changes(report: SyncReport) -> list[str]:
@@ -215,8 +223,20 @@ def _add_skill_changes(results: list[SkillSyncResult], changes: list[str]) -> No
215
223
  def _format_content_line(results: list[ContentSyncResult]) -> str:
216
224
  total = len(results)
217
225
  updated = _count_updated(results)
226
+ restamped = sum(1 for r in results if r.status == "restamped")
218
227
  unchanged = _count_unchanged(results)
219
- return f" {'Content:':<14}{total} synced ({updated} updated, {unchanged} unchanged)"
228
+ drifted = sum(1 for r in results if r.status == "drifted")
229
+ errored = sum(1 for r in results if r.status == "error")
230
+ counts = f"{updated} updated, {unchanged} unchanged"
231
+ if restamped:
232
+ counts = f"{updated} updated, {restamped} restamped, {unchanged} unchanged"
233
+ if drifted:
234
+ # Drift is not failure: the merger deliberately preserved an edit the
235
+ # operator made inside a managed block. It gets its own word.
236
+ counts += f", {drifted} drifted"
237
+ if errored:
238
+ counts += f", {errored} errored"
239
+ return f" {'Content:':<14}{total} synced ({counts})"
220
240
 
221
241
 
222
242
  def _format_agents_line(results: list[AgentProvisionResult]) -> str:
@@ -88,6 +88,8 @@ class ContentSyncResult(BaseModel):
88
88
  path: str
89
89
  status: str
90
90
  artefacts_updated: list[str] = Field(default_factory=list)
91
+ artefacts_restamped: list[str] = Field(default_factory=list)
92
+ artefacts_drifted: list[str] = Field(default_factory=list)
91
93
  artefacts_unchanged: list[str] = Field(default_factory=list)
92
94
  artefacts_errored: list[str] = Field(default_factory=list)
93
95
  error: str | None = None
@@ -77,7 +77,7 @@ Returns JSON report for downstream consumption.
77
77
  YAML files under `core/sync/features/*.yaml` (or `~/.arkaos/config/sync/features/*.yaml`). Each feature has:
78
78
  - `detection_pattern` — regex searched in ecosystem SKILL.md to decide if the feature is already present. Matches any of: the `arka:feature:<name>` marker, the bare `## <section_title>` heading (legacy/customized sections), or — only where a token is unique enough to never appear in unrelated prose (e.g. `arka-forge`) — a historical keyword
79
79
  - `content` — the section to inject if missing, wrapped in `<!-- arka:feature:<name>:start -->` / `<!-- arka:feature:<name>:end -->` markers so future runs detect it and deprecation can remove it precisely
80
- - `deprecated_in` — if set, the matching section is removed (marker pair preferred; fall back to the `## <section_title>` heading block)
80
+ - `deprecated_in` — if set, the section is removed ONLY when a single, well-formed marker pair exists. An unmarked `## <section_title>` section is never deleted; it is reported and left in place.
81
81
 
82
82
  The registry is self-detecting by contract: `detection_pattern` MUST match the feature's own `content` (locked by `tests/python/test_sync_features_registry.py`), otherwise every naive sync re-injects a duplicate section.
83
83
 
@@ -34,7 +34,7 @@ AI-powered sync that updates ecosystem skills, project descriptors, MCP configs,
34
34
 
35
35
  ## Hybrid Orchestration
36
36
 
37
- Phases 1–3 + 5 run via the Python engine (see `sync-engine.md`). Phase 4 runs as ONE AI subagent to handle intelligent ecosystem-skill text updates.
37
+ The Python engine (see `sync-engine.md`) runs every deterministic phase: manifest, discovery, MCP sync, settings sync, descriptors, content sync (CLAUDE.md managed block, rules, hooks, constitution excerpt), agent provisioning, and state. Phase 4 runs as ONE AI subagent to handle intelligent ecosystem-skill text updates.
38
38
 
39
39
  ### Phase 4 — Intelligent Sync (AI Subagent)
40
40
 
@@ -50,7 +50,9 @@ After the Python engine completes, dispatch ONE subagent.
50
50
  - Apply the `detection_pattern` regex to the SKILL.md text. It matches the `arka:feature:<name>` marker, the bare `## <section_title>` heading, or a unique historical keyword — a customized section without markers still counts as present and MUST NOT be duplicated.
51
51
  - If NOT found: inject `content` (already marker-wrapped) after the last existing feature section, or after the "Commands" table if no feature sections exist (before "Orchestration Workflows").
52
52
  3. For each feature where `deprecated_in` is set:
53
- - Remove the `<!-- arka:feature:<name>:start -->` … `:end -->` block when markers exist; otherwise remove the `## <section_title>` section.
53
+ - Remove the `<!-- arka:feature:<name>:start -->` … `:end -->` block ONLY when a single, well-formed marker pair exists.
54
+ - **Never delete an unmarked `## <section_title>` section.** Without markers there is no way to tell ArkaOS's own text from the project's, and a section the operator customized looks identical to one they did not. Leave it in place and report it instead. `~/.claude/skills/` is not a git repository — a wrong deletion there is unrecoverable.
55
+ - If the markers are unbalanced, duplicated or inverted, do not edit the file at all; report the fault.
54
56
  4. PRESERVE all custom content: commands, architecture, tech stack, business descriptions, ecosystem-specific workflow details.
55
57
 
56
58
  ### Report
@@ -1,6 +1,6 @@
1
1
  # ArkaOS — The Operating System for AI Agent Teams
2
2
 
3
- > v5.9.0 — 89 agents, 17 departments, 340 skills. Generated by `scripts/harness_gen.py`; do not edit.
3
+ > v5.10.0 — 89 agents, 17 departments, 340 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
- > v5.9.0 — 89 agents, 17 departments, 340 skills. Generated by `scripts/harness_gen.py`; do not edit.
3
+ > v5.10.0 — 89 agents, 17 departments, 340 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 v5.9.0 agent-team contract
2
+ description: ArkaOS v5.10.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
- > v5.9.0 — 89 agents, 17 departments, 340 skills. Generated by `scripts/harness_gen.py`; do not edit.
8
+ > v5.10.0 — 89 agents, 17 departments, 340 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
- > v5.9.0 — 89 agents, 17 departments, 340 skills. Generated by `scripts/harness_gen.py`; do not edit.
3
+ > v5.10.0 — 89 agents, 17 departments, 340 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
- > v5.9.0 — 89 agents, 17 departments, 340 skills. Generated by `scripts/harness_gen.py`; do not edit.
3
+ > v5.10.0 — 89 agents, 17 departments, 340 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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Gabriel, Software Architect of the ArkaOS /dev department (v5.10.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, data architecture, integration patterns.
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Valentina, Creative Director of the ArkaOS /brand department (v5.10.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, design DNA extraction and replicate-vs-differentiate calls via brand/design-dna (refusal layer, SSRF rules, attestation), creative direction.
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Helena, Chief Financial Officer of the ArkaOS /fin department (v5.10.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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Afonso, Chief of Staff & Governance Lead of the ArkaOS /org department (v5.10.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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Beatriz, Community Strategist of the ArkaOS /community department (v5.10.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), community-led growth.
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Rafael, Content Strategist of the ArkaOS /content department (v5.10.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, repurposing (1→30+ pieces), AI-augmented content creation.
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Ines, Conversion Strategist of the ArkaOS /landing department (v5.10.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, A/B testing.
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Sofia, Chief Operations Officer of the ArkaOS /org department (v5.10.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, structured decision-making with clear ownership.
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Eduardo, Copy & Language Director of the ArkaOS /quality department (v5.10.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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Marta, Chief Quality Officer of the ArkaOS /quality department (v5.10.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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Marco, Chief Technology Officer of the ArkaOS /dev department (v5.10.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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Iris, Design Ops Lead of the ArkaOS /brand department (v5.10.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), design-dna JSON custody and token handoff via brand/design-dna (phase-3 token file).
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Ricardo, E-Commerce Director of the ArkaOS /ecom department (v5.10.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, Shopify & headless commerce.
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Clara, Knowledge Director of the ArkaOS /kb department (v5.10.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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Rodrigo, Leadership & People Director of the ArkaOS /lead department (v5.10.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, conflict resolution, coaching as primary leadership skill.
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Luna, Marketing Director of the ArkaOS /mkt department (v5.10.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, email marketing, analytics & attribution.
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Daniel, Operations Lead of the ArkaOS /ops department (v5.10.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, operational metrics.
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Carolina, Product Manager of the ArkaOS /pm department (v5.10.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), backlog management, sprint/cycle planning.
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Vicente, RevOps Lead of the ArkaOS /saas department (v5.10.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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Tiago, SaaS Strategist of the ArkaOS /saas department (v5.10.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, churn analysis.
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Miguel, Sales Director of the ArkaOS /sales department (v5.10.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, revenue forecasting, The Ask Method (diagnose before offering).
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Tomas, Chief Strategist of the ArkaOS /strat department (v5.10.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, scenario planning, trade-off framing (explicit choose A / NOT B).
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Francisca, Technical & UX Quality Director of the ArkaOS /quality department (v5.10.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, product data accuracy.
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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Paulo, Tech Lead of the ArkaOS /dev department (v5.10.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 (v5.9.0; generated by scripts/harness_gen.py — do not edit).
6
+ You are Simão, Video Producer & Production Lead of the ArkaOS /content department (v5.10.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, multi-format delivery (16:9, 9:16, 1:1; YouTube, Reels, TikTok), backend degradation planning (full → server-side → edit-ready package).
9
9
 
@@ -1,6 +1,6 @@
1
1
  # ArkaOS — The Operating System for AI Agent Teams
2
2
 
3
- > v5.9.0 — 89 agents, 17 departments, 340 skills. Generated by `scripts/harness_gen.py`; do not edit.
3
+ > v5.10.0 — 89 agents, 17 departments, 340 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,7 +1,7 @@
1
1
  {
2
2
  "_meta": {
3
3
  "version": "3.0.0",
4
- "generated": "2026-08-05T16:18:43Z",
4
+ "generated": "2026-08-05T23:24:53Z",
5
5
  "total_commands": 306,
6
6
  "generator": "core/registry/generator.py",
7
7
  "departments": {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "_meta": {
3
3
  "generator": "scripts/marketplace_gen.py",
4
- "version": "5.9.0",
4
+ "version": "5.10.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": "5.9.0",
3
+ "version": "5.10.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 = "5.9.0"
3
+ version = "5.10.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"}