arkaos 4.43.0 → 4.45.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 (50) 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/config/output-styles/arkaos.md +1 -1
  5. package/core/governance/aggregate_guard.py +32 -6
  6. package/core/governance/quality_api.py +13 -17
  7. package/core/governance/review_workflow.py +26 -12
  8. package/core/hooks/session_start.py +17 -0
  9. package/core/hooks/stop.py +67 -21
  10. package/core/runtime/opencode_hooks.py +13 -10
  11. package/core/synapse/layers.py +85 -12
  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/installer/adapters/claude-code.js +88 -3
  44. package/installer/doctor.js +121 -3
  45. package/installer/index.js +17 -0
  46. package/installer/skill-deploy.js +43 -2
  47. package/installer/update.js +12 -0
  48. package/knowledge/skills-manifest.json +1 -1
  49. package/package.json +1 -1
  50. package/pyproject.toml +1 -1
@@ -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.45.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.45.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.45.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"}