@cohortapp/agent-sdk 2.11.15 → 2.13.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/.env.example +37 -22
- package/README.md +2 -0
- package/bin/maestro.mjs +117 -39
- package/bin/maestro.test.mjs +175 -5
- package/docs/guides/front-door-session.md +313 -0
- package/docs/guides/mac-mini.md +100 -28
- package/docs/guides/org-onboarding.md +1 -1
- package/docs/guides/setup-wizard.md +9 -5
- package/docs/runbooks/cohort-cutover.md +11 -1
- package/docs/runbooks/mac-mini-bootstrap.md +38 -63
- package/lib/cadence-bus-requeue.test.mjs +83 -0
- package/lib/cadence-bus.mjs +43 -7
- package/lib/channels/inbox-item.mjs +59 -2
- package/lib/cli/board.mjs +285 -0
- package/lib/cli/board.test.mjs +227 -0
- package/lib/cli/design.mjs +185 -0
- package/lib/cli/design.test.mjs +270 -0
- package/lib/cli/doctor-checks.mjs +441 -0
- package/lib/cli/doctor-checks.test.mjs +336 -0
- package/lib/cli/global-setup-extras.mjs +454 -0
- package/lib/cli/global-setup-extras.test.mjs +462 -0
- package/lib/cli/inbox.mjs +304 -0
- package/lib/cli/inbox.test.mjs +230 -0
- package/lib/cli/session-ack.mjs +63 -0
- package/lib/cli/session-ack.test.mjs +63 -0
- package/lib/cli/session.mjs +760 -0
- package/lib/cli/session.test.mjs +613 -0
- package/lib/collective/global-config.mjs +209 -6
- package/lib/collective/global-config.test.mjs +145 -0
- package/lib/collective/global-skills.mjs +145 -0
- package/lib/collective/global-skills.test.mjs +126 -0
- package/lib/collective/presence.mjs +4 -3
- package/lib/collective/vendor-skills.mjs +305 -0
- package/lib/collective/vendor-skills.test.mjs +306 -0
- package/lib/comms/send-gate.mjs +115 -0
- package/lib/comms/send-gate.test.mjs +113 -0
- package/lib/design/design-md.mjs +793 -0
- package/lib/design/design-md.test.mjs +318 -0
- package/lib/design/fixtures/DESIGN.golden.md +238 -0
- package/lib/design/fixtures/PRODUCT.golden.md +67 -0
- package/lib/design/fixtures/foundation.json +133 -0
- package/lib/design/refresh-gate.mjs +154 -0
- package/lib/design/refresh-gate.test.mjs +144 -0
- package/lib/design/write.mjs +275 -0
- package/lib/design/write.test.mjs +241 -0
- package/lib/feature-init.mjs +2 -2
- package/lib/mcp/server.test.mjs +9 -4
- package/lib/model-router/spawn.test.mjs +21 -0
- package/lib/org/board-mine-cache.mjs +99 -0
- package/lib/org/board-mine-cache.test.mjs +53 -0
- package/lib/org/board.mjs +11 -0
- package/lib/org/board.test.mjs +11 -1
- package/lib/org/client.mjs +36 -0
- package/lib/org/client.test.mjs +46 -0
- package/lib/org/inbound/directedness.mjs +18 -2
- package/lib/org/inbound/directedness.test.mjs +58 -0
- package/lib/org/inbound/index.mjs +8 -1
- package/lib/org/inbound/index.test.mjs +22 -0
- package/lib/org/mesh-directives.test.mjs +110 -0
- package/lib/org/mesh.mjs +61 -1
- package/lib/org/protocol.checksum +1 -1
- package/lib/org/protocol.mjs +52 -0
- package/lib/org/protocol.test.mjs +12 -1
- package/lib/org/registry.mjs +3 -2
- package/lib/org/tool-surface.mjs +120 -0
- package/lib/org/tool-surface.test.mjs +118 -5
- package/lib/prompts/parallelism.mjs +79 -0
- package/lib/prompts/parallelism.test.mjs +177 -0
- package/lib/security/external-content.mjs +1 -1
- package/lib/security/external-content.test.mjs +17 -0
- package/lib/session/config.mjs +137 -0
- package/lib/session/config.test.mjs +92 -0
- package/lib/session/feed-core.mjs +229 -0
- package/lib/session/feed-core.test.mjs +198 -0
- package/lib/session/first-run.mjs +126 -0
- package/lib/session/first-run.test.mjs +121 -0
- package/lib/session/frontdoor.mjs +266 -0
- package/lib/session/frontdoor.test.mjs +205 -0
- package/lib/session/handoffs.mjs +295 -0
- package/lib/session/handoffs.test.mjs +183 -0
- package/lib/session/identity.mjs +220 -0
- package/lib/session/identity.test.mjs +180 -0
- package/lib/session/inbox-claims.mjs +434 -0
- package/lib/session/inbox-claims.test.mjs +286 -0
- package/lib/session/launch-args.mjs +161 -0
- package/lib/session/launch-args.test.mjs +157 -0
- package/lib/session/liveness.mjs +174 -0
- package/lib/session/liveness.test.mjs +100 -0
- package/lib/session/status-summary.mjs +172 -0
- package/lib/session/status-summary.test.mjs +118 -0
- package/lib/session-permissions.mjs +39 -3
- package/lib/session-permissions.test.mjs +20 -0
- package/lib/setup/claude-probe.mjs +161 -24
- package/lib/setup/claude-probe.test.mjs +187 -0
- package/lib/setup/sections/learning.mjs +2 -1
- package/lib/setup/sections/model.mjs +104 -24
- package/lib/setup/sections/model.test.mjs +240 -0
- package/lib/setup/sections/org.mjs +27 -2
- package/lib/setup/sections/org.test.mjs +35 -2
- package/lib/setup/sections/verify.mjs +5 -0
- package/lib/setup/state.mjs +30 -10
- package/lib/setup/state.test.mjs +24 -1
- package/lib/singleton.js +11 -3
- package/lib/singleton.test.mjs +16 -0
- package/lib/subagents/lock.mjs +1 -1
- package/lib/telemetry/collect.mjs +270 -6
- package/lib/telemetry/collect.test.mjs +196 -1
- package/lib/upgrade/global-refresh.mjs +108 -0
- package/lib/upgrade/global-refresh.test.mjs +65 -0
- package/lib/upgrade/launchd-reconcile.mjs +327 -0
- package/lib/upgrade/launchd-reconcile.test.mjs +272 -0
- package/lib/upgrade/post-steps.mjs +151 -0
- package/lib/upgrade/post-steps.test.mjs +200 -0
- package/lib/upgrade/verify.mjs +215 -0
- package/lib/upgrade/verify.test.mjs +164 -0
- package/lib/voice/outbound.mjs +3 -2
- package/lib/voice/post-call-brief.mjs +2 -1
- package/lib/voice/session-rotation.mjs +6 -1
- package/lib/voice/session-rotation.test.mjs +114 -0
- package/package.json +3 -3
- package/plugins/maestro-skills/plugin.json +25 -1
- package/plugins/maestro-skills/skills/board-work.md +63 -0
- package/plugins/maestro-skills/skills/cohort-design.md +153 -0
- package/plugins/maestro-skills/skills/inbound-triage.md +80 -0
- package/plugins/maestro-skills/skills/main-session.md +102 -0
- package/plugins/maestro-skills/skills/peer-sessions.md +65 -0
- package/plugins/maestro-skills/skills/persona-discipline.md +75 -0
- package/plugins/maestro-skills/vendor/emilkowalski/LICENSE +21 -0
- package/plugins/maestro-skills/vendor/emilkowalski/UPSTREAM.json +70 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/animate/RECIPES.md +324 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/animate/SKILL.md +199 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/animation-vocabulary/SKILL.md +173 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/apple-design/SKILL.md +282 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/emil-design-eng/SKILL.md +674 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/find-animation-opportunities/SKILL.md +132 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/AUDIT.md +115 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/PLAN-TEMPLATE.md +73 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/improve-animations/SKILL.md +101 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/prototype/PICKER.md +197 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/prototype/SKILL.md +90 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/review-animations/SKILL.md +112 -0
- package/plugins/maestro-skills/vendor/emilkowalski/skills/review-animations/STANDARDS.md +187 -0
- package/plugins/maestro-skills/vendor/impeccable/LICENSE +191 -0
- package/plugins/maestro-skills/vendor/impeccable/NOTICE.md +11 -0
- package/plugins/maestro-skills/vendor/impeccable/SKILL.md +86 -0
- package/plugins/maestro-skills/vendor/impeccable/UPSTREAM.json +201 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-asset-producer.md +42 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-documenter.md +29 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-finish-reviewer.md +43 -0
- package/plugins/maestro-skills/vendor/impeccable/agents/impeccable-manual-edit-applier.md +97 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/adapt.md +312 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/adapt.native.md +58 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/android.md +46 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/animate.md +89 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/audit.md +136 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/audit.native.md +139 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/bolder.md +33 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/clarify.md +94 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/colorize.md +86 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/craft-floor.md +44 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/craft.md +5 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/critique.md +806 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/asset-producer.md +37 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/documenter.md +24 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/finish-reviewer.md +38 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/degraded/manual-edit-applier.md +92 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/delight.md +70 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/distill.md +111 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/doctor.md +54 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/document.md +416 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/extract.md +69 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/harden.md +336 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/hooks.md +111 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/init.md +131 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/ios.md +51 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/layout.md +84 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/live-setup.md +104 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/live.md +325 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/new-work.md +147 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/onboard.md +234 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/operate.md +61 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/optimize.md +258 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/overdrive.md +127 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/polish.md +105 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/quieter.md +99 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/routing.md +24 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/shape.md +59 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/typeset.md +80 -0
- package/plugins/maestro-skills/vendor/impeccable/reference/visualize.md +46 -0
- package/plugins/maestro-skills/vendor/taste-skill/LICENSE +21 -0
- package/plugins/maestro-skills/vendor/taste-skill/UPSTREAM.json +37 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/minimalist-skill/SKILL.md +85 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/redesign-skill/SKILL.md +178 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/soft-skill/SKILL.md +98 -0
- package/plugins/maestro-skills/vendor/taste-skill/skills/taste-skill/SKILL.md +1206 -0
- package/plugins/maestro-skills/vendor/unlazy/LICENSE +21 -0
- package/plugins/maestro-skills/vendor/unlazy/SECURITY.md +72 -0
- package/plugins/maestro-skills/vendor/unlazy/SKILL.md +104 -0
- package/plugins/maestro-skills/vendor/unlazy/UPSTREAM.json +94 -0
- package/plugins/maestro-skills/vendor/unlazy/references/dispatch.md +82 -0
- package/plugins/maestro-skills/vendor/unlazy/references/gates.md +149 -0
- package/plugins/maestro-skills/vendor/unlazy/references/method.md +49 -0
- package/plugins/maestro-skills/vendor/unlazy/references/orchestration.md +107 -0
- package/plugins/maestro-skills/vendor/unlazy/references/parallel.md +133 -0
- package/plugins/maestro-skills/vendor/unlazy/references/token-economy.md +48 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/dispatch-check.mjs +139 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/gate-check.mjs +960 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/gate-lint.mjs +245 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/check-supervisor.mjs +46 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/dispatch.mjs +293 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/gates.mjs +953 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/process-tree.mjs +161 -0
- package/plugins/maestro-skills/vendor/unlazy/scripts/lib/regex-worker.mjs +9 -0
- package/plugins/maestro-skills/vendor/unlazy/templates/PLAN.md +116 -0
- package/plugins/maestro-skills/vendor/unlazy/templates/gates-leaf.md +51 -0
- package/plugins/maestro-skills/vendor/unlazy/templates/gates-node.md +51 -0
- package/scaffold/CLAUDE.md +24 -0
- package/scripts/ci/check-durable-write-seam.mjs +147 -0
- package/scripts/ci/check-durable-write-seam.test.mjs +90 -0
- package/scripts/ci/check-skill-packs.mjs +388 -0
- package/scripts/ci/check-skill-packs.test.mjs +495 -0
- package/scripts/ci/check.mjs +6 -0
- package/scripts/collective/hook-runner.mjs +39 -4
- package/scripts/collective/hook-runner.test.mjs +85 -2
- package/scripts/daemon/agent-daemon-board-mine.test.mjs +96 -0
- package/scripts/daemon/agent-daemon-design.test.mjs +238 -0
- package/scripts/daemon/agent-daemon-frontdoor.test.mjs +60 -0
- package/scripts/daemon/agent-daemon.mjs +249 -10
- package/scripts/daemon/agent-daemon.test.mjs +73 -0
- package/scripts/daemon/assurance-e2e.test.mjs +141 -6
- package/scripts/daemon/assurance.mjs +461 -37
- package/scripts/daemon/assurance.test.mjs +408 -43
- package/scripts/daemon/cadence-consumer-frontdoor.test.mjs +393 -0
- package/scripts/daemon/cadence-consumer.mjs +289 -89
- package/scripts/daemon/cadence-handlers.mjs +53 -0
- package/scripts/daemon/classifier.mjs +1 -1
- package/scripts/daemon/dispatcher-resume.test.mjs +166 -0
- package/scripts/daemon/dispatcher.mjs +127 -19
- package/scripts/daemon/health.mjs +12 -1
- package/scripts/daemon/inbox-deferral-session.test.mjs +49 -0
- package/scripts/daemon/inbox-deferral.mjs +6 -0
- package/scripts/daemon/lib/self-echo.mjs +201 -0
- package/scripts/daemon/lib/self-echo.test.mjs +153 -0
- package/scripts/daemon/maestro-daemon.mjs +3 -0
- package/scripts/daemon/prompt-builder.mjs +19 -3
- package/scripts/daemon/responder.mjs +51 -40
- package/scripts/daemon/sdk-version.mjs +51 -0
- package/scripts/daemon/sdk-version.test.mjs +31 -0
- package/scripts/hooks/pre-send-audit.sh +97 -4
- package/scripts/hooks/pre-send-audit.test.mjs +140 -1
- package/scripts/local-triggers/autoupdate.sh +243 -19
- package/scripts/local-triggers/autoupdate.test.mjs +518 -0
- package/scripts/local-triggers/generate-plists.sh +24 -1
- package/scripts/local-triggers/generate-plists.test.mjs +49 -11
- package/scripts/org/send-orgmail.first-contact.test.mjs +102 -0
- package/scripts/org/send-orgmail.mjs +27 -3
- package/scripts/poller/inbox-privilege-injection.test.mjs +167 -0
- package/scripts/poller/slack-poller.mjs +13 -1
- package/scripts/poller/utils.mjs +46 -1
- package/scripts/poller-launchd/install.sh +19 -11
- package/scripts/poller-launchd/install.test.mjs +243 -0
- package/scripts/poller-launchd/launchd-poller-wrapper.sh +92 -0
- package/scripts/poller-launchd/migrate.sh +66 -0
- package/scripts/poller-launchd/poller.plist.template +4 -2
- package/scripts/session/feed.mjs +237 -0
- package/scripts/session/feed.test.mjs +196 -0
- package/scripts/session/supervisor-sh.test.mjs +218 -0
- package/scripts/session/supervisor.mjs +328 -0
- package/scripts/session/supervisor.sh +141 -0
- package/scripts/session/supervisor.test.mjs +482 -0
- package/scripts/setup/configure-macos.sh +250 -55
- package/scripts/setup/configure-macos.test.mjs +306 -0
- package/scripts/setup/init-agent.sh +112 -7
- package/scripts/setup/init-agent.test.mjs +220 -1
- package/scripts/vendor/skill-packs.mjs +354 -0
- package/scripts/vendor/sync-skill-packs.mjs +242 -0
- package/scripts/vendor/sync-skill-packs.test.mjs +103 -0
- package/scripts/watchdog/memory-watchdog.sh +37 -1
- package/scripts/watchdog/memory-watchdog.test.mjs +64 -0
- package/scripts/setup/boot-claude-session.sh +0 -94
|
@@ -0,0 +1,454 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/cli/global-setup-extras.mjs — the front-door half of `maestro global-setup`.
|
|
3
|
+
*
|
|
4
|
+
* `bin/maestro.mjs#globalSetup` wires the collective hooks, the collective
|
|
5
|
+
* CLAUDE.md block and the machine pointer. This module adds what §3.7 of the
|
|
6
|
+
* 2026-09-08 design asks for, so ANY Claude Code session on the seat machine
|
|
7
|
+
* knows who the agent is and has its tools:
|
|
8
|
+
*
|
|
9
|
+
* 1. ~/.claude/CLAUDE.md the delimited `maestro:identity` block
|
|
10
|
+
* 2. ~/.claude.json user-scope MCP server `cohort` → cohort-mcp
|
|
11
|
+
* (via `claude mcp add-json --scope user` when
|
|
12
|
+
* the CLI is available, else a JSON merge)
|
|
13
|
+
* 3. ~/.claude/settings.json crossSessionInbound, permissions.allow,
|
|
14
|
+
* the mcp__cohort__messaging_send PreToolUse
|
|
15
|
+
* persona/audit hook
|
|
16
|
+
* 4. ~/.claude/skills/maestro-<name>/ every shipped skill (symlink for a global
|
|
17
|
+
* install, copy otherwise)
|
|
18
|
+
* 5. ~/.claude/skills/<pack skill>/ the vendored design skill packs (WP-M7),
|
|
19
|
+
* copied under their own frontmatter names;
|
|
20
|
+
* a directory that is not ours is never touched,
|
|
21
|
+
* local edits inside one that IS ours are
|
|
22
|
+
* copied aside first, and the whole step is
|
|
23
|
+
* opt-out via MAESTRO_SKIP_VENDOR_SKILLS=1
|
|
24
|
+
*
|
|
25
|
+
* Additive, idempotent, backed up: JSON files are copied to
|
|
26
|
+
* `<file>.backup.<stamp>` before the first write of a run, and a re-run that
|
|
27
|
+
* changes nothing writes nothing (and backs up nothing). Every step is
|
|
28
|
+
* fail-open and reported; the merges themselves are the pure functions in
|
|
29
|
+
* lib/collective/global-config.mjs. All I/O is injectable for tests.
|
|
30
|
+
*
|
|
31
|
+
* Two rules for the two files Claude Code itself owns (~/.claude.json and
|
|
32
|
+
* ~/.claude/settings.json), because a wrong write there costs the seat its
|
|
33
|
+
* OAuth account, onboarding state and per-project trust:
|
|
34
|
+
* - A file that is PRESENT but UNPARSEABLE (mid-rewrite by a running claude,
|
|
35
|
+
* a BOM, trailing garbage) is never "merged onto {}": the step warns and
|
|
36
|
+
* skips; nothing is written.
|
|
37
|
+
* - A write is a read → merge → verify-unchanged → rename cycle
|
|
38
|
+
* ({@link mergeJsonFileAtomic}) that keeps the file's mode (0600 stays
|
|
39
|
+
* 0600) and re-merges when another process rewrote the file in between —
|
|
40
|
+
* the closest thing to a lock a file Claude Code rewrites at will allows.
|
|
41
|
+
*
|
|
42
|
+
* @module lib/cli/global-setup-extras
|
|
43
|
+
*/
|
|
44
|
+
|
|
45
|
+
"use strict";
|
|
46
|
+
|
|
47
|
+
import { existsSync, readFileSync, writeFileSync, copyFileSync, mkdirSync, statSync, openSync, writeSync, fsyncSync, closeSync, renameSync, unlinkSync } from "node:fs";
|
|
48
|
+
import { execFileSync } from "node:child_process";
|
|
49
|
+
import { homedir } from "node:os";
|
|
50
|
+
import { join, dirname } from "node:path";
|
|
51
|
+
import { fileURLToPath } from "node:url";
|
|
52
|
+
import {
|
|
53
|
+
buildIdentityClaudeMd, upsertDelimitedBlock, IDENTITY_BEGIN, IDENTITY_END,
|
|
54
|
+
cohortMcpEntry, mergeMcpServers, mergeSessionSettings,
|
|
55
|
+
} from "../collective/global-config.mjs";
|
|
56
|
+
import { installSkills, isGlobalInstall } from "../collective/global-skills.mjs";
|
|
57
|
+
import { buildContext } from "../render.mjs";
|
|
58
|
+
|
|
59
|
+
/** The SDK package root (two levels up from lib/cli/). */
|
|
60
|
+
export const SDK_ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
|
61
|
+
|
|
62
|
+
function stamp(now) {
|
|
63
|
+
return new Date(Number.isFinite(now) ? now : Date.now()).toISOString().replace(/[:.]/g, "-");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Read a JSON file. `{present:false}` when absent; `{present:true, value}` when
|
|
68
|
+
* parsed; `{present:true, value:null, unparseable:true}` when the bytes are
|
|
69
|
+
* there but do not parse — the caller must NOT write over that.
|
|
70
|
+
*/
|
|
71
|
+
function readJsonFile(p) {
|
|
72
|
+
if (!existsSync(p)) return { present: false, value: null };
|
|
73
|
+
try {
|
|
74
|
+
return { present: true, value: JSON.parse(readFileSync(p, "utf8")) };
|
|
75
|
+
} catch {
|
|
76
|
+
return { present: true, value: null, unparseable: true };
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* First `<dir>/<name>` on a PATH string that exists as a file. Pure over the
|
|
82
|
+
* injected `existsSync`. Null when nothing matches or the PATH is empty.
|
|
83
|
+
* @param {string} name @param {string} pathEnv @param {{existsSync?:Function}} [deps]
|
|
84
|
+
*/
|
|
85
|
+
export function findOnPath(name, pathEnv, deps = {}) {
|
|
86
|
+
const ex = deps.existsSync || existsSync;
|
|
87
|
+
const n = String(name || "");
|
|
88
|
+
if (!n) return null;
|
|
89
|
+
for (const dir of String(pathEnv || "").split(":")) {
|
|
90
|
+
if (!dir) continue;
|
|
91
|
+
const candidate = join(dir, n);
|
|
92
|
+
try { if (ex(candidate)) return candidate; } catch { /* an unreadable PATH entry is not a hit */ }
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Turn the resolver's answer into a usable binary path or null. lib/claude-bin
|
|
99
|
+
* returns the bare name "claude" when none of its fixed locations exist (an
|
|
100
|
+
* nvm / npm-prefix install); that is a PATH lookup, not a miss.
|
|
101
|
+
* @param {string} resolved @param {{pathEnv?:string, existsSync?:Function}} [o]
|
|
102
|
+
*/
|
|
103
|
+
export function pickClaudeBin(resolved, o = {}) {
|
|
104
|
+
const ex = o.existsSync || existsSync;
|
|
105
|
+
const r = String(resolved || "");
|
|
106
|
+
if (!r) return null;
|
|
107
|
+
if (r === "claude" || !r.includes("/")) return findOnPath(r, o.pathEnv === undefined ? process.env.PATH : o.pathEnv, { existsSync: ex });
|
|
108
|
+
try { return ex(r) ? r : null; } catch { return null; }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Read → merge → publish, refusing to clobber. `mergeFn(parsedOrNull)` returns
|
|
113
|
+
* `{config, changed}` (the shape of the pure mergers). The file's mode is kept
|
|
114
|
+
* (a new file gets `o.mode`, default 0o600). Before the rename the file is
|
|
115
|
+
* re-read and compared with what was merged from: a difference means another
|
|
116
|
+
* process wrote in between, so the merge is redone (up to `o.attempts`, 3).
|
|
117
|
+
*
|
|
118
|
+
* @returns {{ok:boolean, changed:boolean, attempts:number, reason?:string}}
|
|
119
|
+
*/
|
|
120
|
+
export function mergeJsonFileAtomic(path, mergeFn, o = {}) {
|
|
121
|
+
const maxAttempts = Number.isFinite(o.attempts) && o.attempts > 0 ? o.attempts : 3;
|
|
122
|
+
let attempts = 0;
|
|
123
|
+
while (attempts < maxAttempts) {
|
|
124
|
+
attempts += 1;
|
|
125
|
+
let raw = null;
|
|
126
|
+
let mode = Number.isFinite(o.mode) ? o.mode : 0o600;
|
|
127
|
+
if (existsSync(path)) {
|
|
128
|
+
try {
|
|
129
|
+
raw = readFileSync(path, "utf8");
|
|
130
|
+
mode = statSync(path).mode & 0o777;
|
|
131
|
+
} catch (err) {
|
|
132
|
+
return { ok: false, changed: false, attempts, reason: `unreadable: ${err && err.message}` };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
let parsed = null;
|
|
136
|
+
if (raw !== null) {
|
|
137
|
+
try { parsed = JSON.parse(raw); } catch { return { ok: false, changed: false, attempts, reason: "unparseable" }; }
|
|
138
|
+
}
|
|
139
|
+
const merged = mergeFn(parsed);
|
|
140
|
+
if (!merged || !merged.changed) return { ok: true, changed: false, attempts };
|
|
141
|
+
const out = JSON.stringify(merged.config, null, 2) + "\n";
|
|
142
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
143
|
+
const tmp = `${path}.tmp.${process.pid}.${Date.now()}`;
|
|
144
|
+
const fd = openSync(tmp, "w", mode);
|
|
145
|
+
try { writeSync(fd, out); fsyncSync(fd); } finally { closeSync(fd); }
|
|
146
|
+
// Verify nobody wrote in between; a change means our merge is stale.
|
|
147
|
+
let now = null;
|
|
148
|
+
try { now = existsSync(path) ? readFileSync(path, "utf8") : null; } catch { now = undefined; }
|
|
149
|
+
if (now !== raw) {
|
|
150
|
+
try { unlinkSync(tmp); } catch { /* orphan tmp is harmless */ }
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
try {
|
|
154
|
+
renameSync(tmp, path);
|
|
155
|
+
} catch (err) {
|
|
156
|
+
try { unlinkSync(tmp); } catch { /* ignore */ }
|
|
157
|
+
return { ok: false, changed: false, attempts, reason: `rename failed: ${err && err.message}` };
|
|
158
|
+
}
|
|
159
|
+
return { ok: true, changed: true, attempts };
|
|
160
|
+
}
|
|
161
|
+
return { ok: false, changed: false, attempts, reason: "concurrent writer: gave up after retries" };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/** `npm root -g`, "" when npm is unavailable (→ copy mode). */
|
|
165
|
+
function defaultGlobalRoot() {
|
|
166
|
+
try {
|
|
167
|
+
return execFileSync("npm", ["root", "-g"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"], timeout: 15_000 }).trim();
|
|
168
|
+
} catch {
|
|
169
|
+
return ""; // no npm on PATH: skills are copied rather than linked
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** The claude CLI, or null when it is not installed (→ JSON merge). */
|
|
174
|
+
async function defaultClaudeBin(pathEnv) {
|
|
175
|
+
try {
|
|
176
|
+
const mod = await import("../claude-bin.mjs");
|
|
177
|
+
return pickClaudeBin(mod.resolveClaudeBin(), { pathEnv });
|
|
178
|
+
} catch {
|
|
179
|
+
return null; // resolver unavailable: the JSON merge path is the fallback
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function defaultExec(bin, args) {
|
|
184
|
+
try {
|
|
185
|
+
const stdout = execFileSync(bin, args, { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 30_000 });
|
|
186
|
+
return { status: 0, stdout };
|
|
187
|
+
} catch (err) {
|
|
188
|
+
return { status: typeof err.status === "number" ? err.status : 1, stderr: String((err && err.stderr) || (err && err.message) || "") };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/** Marker every persona-gated pre-send-audit.sh carries (section a2). */
|
|
193
|
+
export const PERSONA_GATE_MARKER = "PERSONA_PATTERNS";
|
|
194
|
+
|
|
195
|
+
/** Does `<agentRoot>/scripts/hooks/pre-send-audit.sh` exist and carry the persona gate? */
|
|
196
|
+
export function checkPersonaHookScript(agentRoot) {
|
|
197
|
+
const path = join(String(agentRoot || ""), "scripts", "hooks", "pre-send-audit.sh");
|
|
198
|
+
try {
|
|
199
|
+
if (!existsSync(path)) return { path, present: false, personaGate: false };
|
|
200
|
+
return { path, present: true, personaGate: readFileSync(path, "utf8").includes(PERSONA_GATE_MARKER) };
|
|
201
|
+
} catch {
|
|
202
|
+
return { path, present: false, personaGate: false };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Apply the four steps. Returns a summary; never throws.
|
|
208
|
+
*
|
|
209
|
+
* @param {object} o
|
|
210
|
+
* @param {string} o.agentRoot the agent repo being wired
|
|
211
|
+
* @param {string} [o.claudeDir] ~/.claude
|
|
212
|
+
* @param {string} [o.claudeJsonPath] ~/.claude.json
|
|
213
|
+
* @param {string} [o.sdkRoot] where plugins/maestro-skills lives (default: this package)
|
|
214
|
+
* @param {boolean} [o.dryRun]
|
|
215
|
+
* @param {string|null} [o.claudeBin] claude CLI path; null = not available
|
|
216
|
+
* @param {(bin:string,args:string[])=>{status:number}} [o.execImpl]
|
|
217
|
+
* @param {string} [o.globalRoot] `npm root -g`
|
|
218
|
+
* @param {string} [o.pathEnv] PATH to resolve `claude` / `cohort-mcp` on (default process.env.PATH)
|
|
219
|
+
* @param {number} [o.now]
|
|
220
|
+
* @param {(m:string)=>void} [o.log] [o.ok] [o.warn]
|
|
221
|
+
*/
|
|
222
|
+
export async function applyFrontDoorSetup(o = {}) {
|
|
223
|
+
const log = o.log || (() => {});
|
|
224
|
+
const ok = o.ok || log;
|
|
225
|
+
const warn = o.warn || log;
|
|
226
|
+
const dryRun = !!o.dryRun;
|
|
227
|
+
const pre = dryRun ? "[dry-run] " : "";
|
|
228
|
+
const agentRoot = String(o.agentRoot || "");
|
|
229
|
+
const claudeDir = o.claudeDir || join(homedir(), ".claude");
|
|
230
|
+
const claudeJsonPath = o.claudeJsonPath || join(homedir(), ".claude.json");
|
|
231
|
+
const sdkRoot = o.sdkRoot || SDK_ROOT;
|
|
232
|
+
const now = Number.isFinite(o.now) ? o.now : Date.now();
|
|
233
|
+
const ts = stamp(now);
|
|
234
|
+
const pathEnv = o.pathEnv === undefined ? (process.env.PATH || "") : String(o.pathEnv || "");
|
|
235
|
+
const summary = {
|
|
236
|
+
ok: true,
|
|
237
|
+
identity: { changed: false, skipped: false },
|
|
238
|
+
mcp: { changed: false, via: null, skipped: null },
|
|
239
|
+
settings: { changes: [], skipped: null, hookScript: null },
|
|
240
|
+
skills: { mode: null, installed: [], unchanged: [] },
|
|
241
|
+
vendorSkills: { installed: [], unchanged: [], skipped: [], errors: [] },
|
|
242
|
+
};
|
|
243
|
+
let agentJson = null; // config/agent.json, shared by the identity block and the skill renderer
|
|
244
|
+
|
|
245
|
+
const backupOnce = (p) => {
|
|
246
|
+
if (dryRun || !existsSync(p)) return;
|
|
247
|
+
try { copyFileSync(p, `${p}.backup.${ts}`); } catch { /* a missing backup must not block the write; the merge is additive */ }
|
|
248
|
+
};
|
|
249
|
+
|
|
250
|
+
// 1) Identity block.
|
|
251
|
+
try {
|
|
252
|
+
const aj = join(agentRoot, "config", "agent.json");
|
|
253
|
+
if (existsSync(aj)) { try { agentJson = JSON.parse(readFileSync(aj, "utf8")); } catch { agentJson = null; } }
|
|
254
|
+
if (!agentJson || !(agentJson.fullName || agentJson.firstName)) {
|
|
255
|
+
summary.identity.skipped = true;
|
|
256
|
+
warn("identity block skipped: config/agent.json has no name yet (run `maestro setup`, then re-run global-setup)");
|
|
257
|
+
} else {
|
|
258
|
+
const mdPath = join(claudeDir, "CLAUDE.md");
|
|
259
|
+
let cur = "";
|
|
260
|
+
if (existsSync(mdPath)) { try { cur = readFileSync(mdPath, "utf8"); } catch { cur = ""; } }
|
|
261
|
+
const r = upsertDelimitedBlock(cur, buildIdentityClaudeMd(agentJson, { agentRoot }), { begin: IDENTITY_BEGIN, end: IDENTITY_END });
|
|
262
|
+
summary.identity.changed = r.changed;
|
|
263
|
+
if (r.changed && !dryRun) { mkdirSync(claudeDir, { recursive: true }); writeFileSync(mdPath, r.content); }
|
|
264
|
+
ok(r.changed ? `${pre}global CLAUDE.md identity block written` : "global CLAUDE.md identity block up to date");
|
|
265
|
+
}
|
|
266
|
+
} catch (err) {
|
|
267
|
+
summary.ok = false;
|
|
268
|
+
warn(`identity block failed: ${err && err.message}`);
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// 2) MCP server entry (user scope).
|
|
272
|
+
try {
|
|
273
|
+
// Bare `cohort-mcp` needs the global npm install. Until that lands on a
|
|
274
|
+
// seat (WP-M6 refreshes it on upgrade), point the entry at this package's
|
|
275
|
+
// own bin under the current node so every session's `cohort` server still
|
|
276
|
+
// starts; a later re-run flips it back to the bare command once it is on PATH.
|
|
277
|
+
let entry;
|
|
278
|
+
if (findOnPath("cohort-mcp", pathEnv)) {
|
|
279
|
+
entry = cohortMcpEntry(agentRoot);
|
|
280
|
+
} else {
|
|
281
|
+
const localBin = join(sdkRoot, "bin", "cohort-mcp.mjs");
|
|
282
|
+
if (existsSync(localBin)) {
|
|
283
|
+
entry = cohortMcpEntry(agentRoot, { command: process.execPath, args: [localBin] });
|
|
284
|
+
log(`cohort-mcp is not on PATH — registering ${localBin} under ${process.execPath} (re-run global-setup after \`npm i -g @cohortapp/agent-sdk\`)`);
|
|
285
|
+
} else {
|
|
286
|
+
entry = cohortMcpEntry(agentRoot);
|
|
287
|
+
warn("cohort-mcp is not on PATH and this package has no bin/cohort-mcp.mjs; the entry is registered as the bare command and will fail to start until the global install exists");
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
const cur = readJsonFile(claudeJsonPath);
|
|
291
|
+
if (cur.unparseable) {
|
|
292
|
+
summary.ok = false;
|
|
293
|
+
summary.mcp.skipped = "unparseable";
|
|
294
|
+
warn(`${claudeJsonPath} is present but does not parse as JSON — leaving it untouched (a running claude may be mid-rewrite; re-run global-setup in a minute, or fix the file). cohort MCP server NOT registered.`);
|
|
295
|
+
} else {
|
|
296
|
+
const merged = mergeMcpServers(cur.value, entry);
|
|
297
|
+
if (!merged.changed) {
|
|
298
|
+
ok("cohort MCP server already registered in ~/.claude.json");
|
|
299
|
+
} else if (dryRun) {
|
|
300
|
+
summary.mcp.changed = true;
|
|
301
|
+
summary.mcp.via = "json";
|
|
302
|
+
ok(`${pre}cohort MCP server → ~/.claude.json (user scope)`);
|
|
303
|
+
} else {
|
|
304
|
+
summary.mcp.changed = true;
|
|
305
|
+
const claudeBin = o.claudeBin === undefined ? await defaultClaudeBin(pathEnv) : o.claudeBin;
|
|
306
|
+
const exec = o.execImpl || defaultExec;
|
|
307
|
+
let done = false;
|
|
308
|
+
if (claudeBin) {
|
|
309
|
+
try {
|
|
310
|
+
const r = exec(claudeBin, ["mcp", "add-json", "cohort", JSON.stringify(entry), "--scope", "user"]);
|
|
311
|
+
if (r && r.status === 0) {
|
|
312
|
+
// Trust but verify: the CLI owns the file format; confirm the entry landed.
|
|
313
|
+
const after = readJsonFile(claudeJsonPath);
|
|
314
|
+
done = !after.unparseable && !mergeMcpServers(after.value, entry).changed;
|
|
315
|
+
}
|
|
316
|
+
} catch {
|
|
317
|
+
done = false; // spawn failure → JSON merge below
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
if (done) {
|
|
321
|
+
summary.mcp.via = "cli";
|
|
322
|
+
ok("cohort MCP server registered via `claude mcp add-json --scope user`");
|
|
323
|
+
} else {
|
|
324
|
+
backupOnce(claudeJsonPath);
|
|
325
|
+
const w = mergeJsonFileAtomic(claudeJsonPath, (doc) => mergeMcpServers(doc, entry), { mode: 0o600 });
|
|
326
|
+
if (!w.ok) {
|
|
327
|
+
summary.ok = false;
|
|
328
|
+
summary.mcp.skipped = w.reason || "write failed";
|
|
329
|
+
warn(`cohort MCP server NOT written to ${claudeJsonPath}: ${w.reason}`);
|
|
330
|
+
} else {
|
|
331
|
+
summary.mcp.via = "json";
|
|
332
|
+
ok(`cohort MCP server merged into ${claudeJsonPath}${claudeBin ? " (CLI registration failed; JSON merge used)" : ""}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
} catch (err) {
|
|
338
|
+
summary.ok = false;
|
|
339
|
+
warn(`MCP registration failed: ${err && err.message}`);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// 3) settings.json.
|
|
343
|
+
try {
|
|
344
|
+
const settingsPath = join(claudeDir, "settings.json");
|
|
345
|
+
const cur = readJsonFile(settingsPath);
|
|
346
|
+
if (cur.unparseable) {
|
|
347
|
+
summary.ok = false;
|
|
348
|
+
summary.settings.skipped = "unparseable";
|
|
349
|
+
warn(`${settingsPath} is present but does not parse as JSON — leaving it untouched (its hooks and permissions would be lost). Front-door settings NOT applied.`);
|
|
350
|
+
} else {
|
|
351
|
+
const r = mergeSessionSettings(cur.value, { agentRoot });
|
|
352
|
+
summary.settings.changes = r.changes;
|
|
353
|
+
if (r.changes.length === 0) {
|
|
354
|
+
ok("global settings already carry the front-door additions (no change)");
|
|
355
|
+
} else if (dryRun) {
|
|
356
|
+
ok(`${pre}global settings updated: ${r.changes.join(", ")}`);
|
|
357
|
+
} else {
|
|
358
|
+
backupOnce(settingsPath);
|
|
359
|
+
mkdirSync(claudeDir, { recursive: true });
|
|
360
|
+
const w = mergeJsonFileAtomic(settingsPath, (doc) => {
|
|
361
|
+
const m = mergeSessionSettings(doc, { agentRoot });
|
|
362
|
+
return { config: m.settings, changed: m.changes.length > 0 };
|
|
363
|
+
}, { mode: 0o644 });
|
|
364
|
+
if (!w.ok) {
|
|
365
|
+
summary.ok = false;
|
|
366
|
+
summary.settings.skipped = w.reason || "write failed";
|
|
367
|
+
warn(`global settings NOT written to ${settingsPath}: ${w.reason}`);
|
|
368
|
+
} else {
|
|
369
|
+
ok(`global settings updated: ${r.changes.join(", ")}`);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
// The PreToolUse persona hook points at the AGENT's copy of pre-send-audit.sh.
|
|
374
|
+
// `upgrade` keeps a locally modified copy, so a seat can have the matcher
|
|
375
|
+
// wired to a script that predates the persona gate. Say so; nothing else
|
|
376
|
+
// in this WP can (doctor/verify are M5/M6).
|
|
377
|
+
summary.settings.hookScript = checkPersonaHookScript(agentRoot);
|
|
378
|
+
if (summary.settings.hookScript.present && !summary.settings.hookScript.personaGate) {
|
|
379
|
+
warn(`${summary.settings.hookScript.path} has no persona gate (PERSONA_PATTERNS) — the file is older than this SDK or locally modified; the mcp__cohort outbound hook will not screen persona leaks until it is refreshed (\`maestro upgrade --force-overwrite\`, or merge by hand)`);
|
|
380
|
+
} else if (!summary.settings.hookScript.present) {
|
|
381
|
+
warn(`${summary.settings.hookScript.path} is missing — the global PreToolUse persona hook will fail closed on every mcp__cohort outbound call until \`maestro upgrade\` ships it`);
|
|
382
|
+
}
|
|
383
|
+
} catch (err) {
|
|
384
|
+
summary.ok = false;
|
|
385
|
+
warn(`settings merge failed: ${err && err.message}`);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// 4) Skills — rendered to this agent's identity where they carry tokens
|
|
389
|
+
// (Claude Code loads SKILL.md verbatim; only the cadence consumer used
|
|
390
|
+
// to render). No identity yet → templates verbatim, as before.
|
|
391
|
+
try {
|
|
392
|
+
const globalRoot = o.globalRoot === undefined ? defaultGlobalRoot() : o.globalRoot;
|
|
393
|
+
const mode = isGlobalInstall(sdkRoot, { globalRoot }) ? "symlink" : "copy";
|
|
394
|
+
let context = null;
|
|
395
|
+
if (agentJson) {
|
|
396
|
+
let companyJson = null;
|
|
397
|
+
const cj = join(agentRoot, "config", "company.json");
|
|
398
|
+
if (existsSync(cj)) { try { companyJson = JSON.parse(readFileSync(cj, "utf8")); } catch { companyJson = null; } }
|
|
399
|
+
context = buildContext(agentJson, null, companyJson);
|
|
400
|
+
}
|
|
401
|
+
const r = installSkills({ sdkRoot, skillsDir: join(claudeDir, "skills"), mode, dryRun, context });
|
|
402
|
+
summary.skills = { mode, installed: r.installed, unchanged: r.unchanged, rendered: r.rendered || [], errors: r.errors };
|
|
403
|
+
const renderedNote = r.rendered && r.rendered.length ? `; ${r.rendered.length} rendered to ${agentJson.firstName || agentJson.fullName}` : "";
|
|
404
|
+
if (!r.ok && r.error) warn(`skills not installed: ${r.error}`);
|
|
405
|
+
else if (r.installed.length === 0) ok(`global skills up to date (${r.unchanged.length} ${mode === "symlink" ? "linked" : "copied"}${renderedNote})`);
|
|
406
|
+
else ok(`${pre}global skills ${mode === "symlink" ? "linked" : "copied"}: ${r.installed.join(", ")}${r.unchanged.length ? ` (+${r.unchanged.length} unchanged)` : ""}${renderedNote}`);
|
|
407
|
+
if (!agentJson && r.plan && r.plan.length) warn("skills installed as templates ({{agent.*}} unrendered): config/agent.json has no name yet — re-run global-setup after `maestro setup`");
|
|
408
|
+
for (const e of r.errors || []) warn(`skill ${e.name}: ${e.error}`);
|
|
409
|
+
} catch (err) {
|
|
410
|
+
summary.ok = false;
|
|
411
|
+
warn(`skills install failed: ${err && err.message}`);
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// 5) Vendored design skill packs (WP-M7) — impeccable, emilkowalski, taste-skill,
|
|
415
|
+
// unlazy, installed under their OWN frontmatter names because that is what
|
|
416
|
+
// their prose and the cohort-design harmoniser refer to. Copies, never
|
|
417
|
+
// links: the point of vendoring is that the seat holds reviewed bytes. A
|
|
418
|
+
// directory that is not ours (the human installed the upstream pack
|
|
419
|
+
// themselves, launcher and hooks and all) is left exactly as it is.
|
|
420
|
+
try {
|
|
421
|
+
// An opt-out, because this step writes into the GLOBAL ~/.claude/skills and
|
|
422
|
+
// so reaches every piece of work done on the machine, including repos that
|
|
423
|
+
// have nothing to do with Cohort design. A seat that does not want the
|
|
424
|
+
// packs sets MAESTRO_SKIP_VENDOR_SKILLS=1 and keeps every other step.
|
|
425
|
+
const skip = String(process.env.MAESTRO_SKIP_VENDOR_SKILLS || "").trim();
|
|
426
|
+
if (skip && skip !== "0" && skip.toLowerCase() !== "false") {
|
|
427
|
+
summary.vendorSkills = { skippedByEnv: true };
|
|
428
|
+
ok("vendored design skills skipped (MAESTRO_SKIP_VENDOR_SKILLS)");
|
|
429
|
+
return summary;
|
|
430
|
+
}
|
|
431
|
+
const [{ PACKS }, vendor] = await Promise.all([
|
|
432
|
+
import("../../scripts/vendor/skill-packs.mjs"),
|
|
433
|
+
import("../collective/vendor-skills.mjs"),
|
|
434
|
+
]);
|
|
435
|
+
const r = vendor.installVendorSkills({ sdkRoot, skillsDir: join(claudeDir, "skills"), packs: PACKS, dryRun });
|
|
436
|
+
summary.vendorSkills = { installed: r.installed, unchanged: r.unchanged, skipped: r.skipped, backups: r.backups || [], errors: r.errors };
|
|
437
|
+
if (r.installed.length === 0 && r.skipped.length === 0 && r.errors.length === 0) {
|
|
438
|
+
ok(`vendored design skills up to date (${r.unchanged.length} pack skill(s))`);
|
|
439
|
+
} else if (r.installed.length) {
|
|
440
|
+
ok(`${pre}vendored design skills copied: ${r.installed.join(", ")}${r.unchanged.length ? ` (+${r.unchanged.length} unchanged)` : ""}`);
|
|
441
|
+
}
|
|
442
|
+
for (const b of r.backups || []) warn(`design skill ${b.name}: local edits found (${b.files.join(", ")}) — copied to ${b.path} before rewriting`);
|
|
443
|
+
for (const sk of r.skipped) warn(`design skill ${sk.name} left alone: ${sk.reason}`);
|
|
444
|
+
for (const e of r.errors || []) warn(`design pack ${e.pack}: ${e.error}`);
|
|
445
|
+
} catch (err) {
|
|
446
|
+
// Fail-open: an SDK copy that predates WP-M7 has no vendor tree, and a seat
|
|
447
|
+
// that cannot install design skills must still get its identity and tools.
|
|
448
|
+
warn(`vendored design skills not installed: ${err && err.message}`);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
return summary;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export default { applyFrontDoorSetup, mergeJsonFileAtomic, findOnPath, pickClaudeBin, checkPersonaHookScript, SDK_ROOT };
|