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.
- package/THE-ARKAOS-GUIDE.md +1 -1
- package/VERSION +1 -1
- package/config/hooks/agent-provision.sh +35 -14
- package/config/output-styles/arkaos.md +1 -1
- package/core/governance/aggregate_guard.py +32 -6
- package/core/governance/quality_api.py +13 -17
- package/core/governance/review_workflow.py +26 -12
- package/core/hooks/session_start.py +17 -0
- package/core/hooks/stop.py +67 -21
- package/core/runtime/opencode_hooks.py +13 -10
- package/core/synapse/layers.py +85 -12
- package/harness/codex/AGENTS.md +1 -1
- package/harness/copilot/copilot-instructions.md +1 -1
- package/harness/cursor/rules/arkaos.mdc +2 -2
- package/harness/gemini/GEMINI.md +1 -1
- package/harness/opencode/AGENTS.md +1 -1
- package/harness/opencode/agents/arka-architect-gabriel.md +1 -1
- package/harness/opencode/agents/arka-brand-director-valentina.md +1 -1
- package/harness/opencode/agents/arka-cfo-helena.md +1 -1
- package/harness/opencode/agents/arka-chief-of-staff-afonso.md +1 -1
- package/harness/opencode/agents/arka-community-strategist-beatriz.md +1 -1
- package/harness/opencode/agents/arka-content-strategist-rafael.md +1 -1
- package/harness/opencode/agents/arka-conversion-strategist-ines.md +1 -1
- package/harness/opencode/agents/arka-coo-sofia.md +1 -1
- package/harness/opencode/agents/arka-copy-director-eduardo.md +1 -1
- package/harness/opencode/agents/arka-cqo-marta.md +1 -1
- package/harness/opencode/agents/arka-cto-marco.md +1 -1
- package/harness/opencode/agents/arka-design-ops-lead-iris.md +1 -1
- package/harness/opencode/agents/arka-ecom-director-ricardo.md +1 -1
- package/harness/opencode/agents/arka-knowledge-director-clara.md +1 -1
- package/harness/opencode/agents/arka-leadership-director-rodrigo.md +1 -1
- package/harness/opencode/agents/arka-marketing-director-luna.md +1 -1
- package/harness/opencode/agents/arka-ops-lead-daniel.md +1 -1
- package/harness/opencode/agents/arka-pm-director-carolina.md +1 -1
- package/harness/opencode/agents/arka-revops-lead-vicente.md +1 -1
- package/harness/opencode/agents/arka-saas-strategist-tiago.md +1 -1
- package/harness/opencode/agents/arka-sales-director-miguel.md +1 -1
- package/harness/opencode/agents/arka-strategy-director-tomas.md +1 -1
- package/harness/opencode/agents/arka-tech-director-francisca.md +1 -1
- package/harness/opencode/agents/arka-tech-lead-paulo.md +1 -1
- package/harness/opencode/agents/arka-video-producer-simao.md +1 -1
- package/harness/zed/.rules +1 -1
- package/installer/adapters/claude-code.js +88 -3
- package/installer/doctor.js +121 -3
- package/installer/index.js +17 -0
- package/installer/skill-deploy.js +43 -2
- package/installer/update.js +12 -0
- package/knowledge/skills-manifest.json +1 -1
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/installer/doctor.js
CHANGED
|
@@ -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)
|
|
40
|
-
|
|
41
|
-
|
|
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",
|
package/installer/index.js
CHANGED
|
@@ -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-
|
|
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;
|
package/installer/update.js
CHANGED
|
@@ -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
|
package/package.json
CHANGED