@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,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* lib/cli/design.mjs — `maestro design sync`.
|
|
3
|
+
*
|
|
4
|
+
* maestro design sync [--out <dir>] [--json]
|
|
5
|
+
*
|
|
6
|
+
* Pulls the workspace's SAVED brand foundation from hq (`branding.getFoundation`
|
|
7
|
+
* — the same read the `design_foundation` tool makes; drafts are never visible
|
|
8
|
+
* to a seat) and lands it as `DESIGN.md` + `PRODUCT.md` in the agent dir, plus
|
|
9
|
+
* the raw snapshot at `state/design/foundation.json`. Design skills read those
|
|
10
|
+
* files; this command is how they stop being fiction.
|
|
11
|
+
*
|
|
12
|
+
* FAIL POSTURE. A fetch that fails writes NOTHING and exits non-zero with the
|
|
13
|
+
* server's own message. That is deliberate and is the opposite of the daemon's
|
|
14
|
+
* posture: a human who typed `design sync` is owed the error, whereas a
|
|
15
|
+
* background poll must never overwrite a good DESIGN.md with a half-answer from
|
|
16
|
+
* an hq that was briefly down. Nothing here throws — expected failures are
|
|
17
|
+
* printed and become exit 1.
|
|
18
|
+
*
|
|
19
|
+
* IDEMPOTENT. Re-running against an unchanged foundation rewrites no file (the
|
|
20
|
+
* comparison is on rendered bytes — `lib/design/write.mjs#syncDesign`), so a
|
|
21
|
+
* cron-y re-sync leaves mtimes and the git tree alone.
|
|
22
|
+
*
|
|
23
|
+
* `runDesign(argv, deps)` is the testable core (every I/O injectable);
|
|
24
|
+
* `designCmd(argv)` is the bin entry.
|
|
25
|
+
*
|
|
26
|
+
* @module lib/cli/design
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
"use strict";
|
|
30
|
+
|
|
31
|
+
import { loadOrgConfig, configFromAgent, isEnabled, brandingGetFoundation } from "../org/client.mjs";
|
|
32
|
+
import { syncDesign, workspaceName } from "../design/write.mjs";
|
|
33
|
+
import { DESIGN_FILES, DESIGN_STATE_REL, isUnversionedFoundation } from "../design/refresh-gate.mjs";
|
|
34
|
+
|
|
35
|
+
export function usage() {
|
|
36
|
+
return [
|
|
37
|
+
"Usage:",
|
|
38
|
+
" maestro design sync [--out <dir>] [--json] Pull the brand foundation and write DESIGN.md + PRODUCT.md",
|
|
39
|
+
"",
|
|
40
|
+
`Writes ${DESIGN_FILES.join(" + ")} into the agent dir (or --out) and the raw`,
|
|
41
|
+
`snapshot to ${DESIGN_STATE_REL}. Unchanged files are left untouched.`,
|
|
42
|
+
"Foundation changes are human-gated: propose one with the design_propose_change tool.",
|
|
43
|
+
].join("\n");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Parse argv → {sub, out, json, help, error}. Pure.
|
|
48
|
+
* @param {string[]} argv
|
|
49
|
+
*/
|
|
50
|
+
export function parseDesignArgs(argv) {
|
|
51
|
+
const args = Array.isArray(argv) ? argv.map(String) : [];
|
|
52
|
+
const out = { sub: null, out: null, json: false, help: false, error: null };
|
|
53
|
+
if (args.length === 0 || args.includes("--help") || args.includes("-h")) { out.help = true; return out; }
|
|
54
|
+
out.sub = args[0];
|
|
55
|
+
const rest = args.slice(1);
|
|
56
|
+
for (let i = 0; i < rest.length; i++) {
|
|
57
|
+
const a = rest[i];
|
|
58
|
+
const eq = a.indexOf("=");
|
|
59
|
+
const key = a.startsWith("--") && eq > 0 ? a.slice(0, eq) : a;
|
|
60
|
+
const inline = a.startsWith("--") && eq > 0 ? a.slice(eq + 1) : undefined;
|
|
61
|
+
const take = () => (inline !== undefined ? inline : rest[++i]);
|
|
62
|
+
if (key === "--json") out.json = true;
|
|
63
|
+
else if (key === "--out") {
|
|
64
|
+
// A MISSING value is an error, never a silent fallback. `--out` with the
|
|
65
|
+
// path forgotten (or eaten by the shell) used to leave `out` null and
|
|
66
|
+
// write DESIGN.md into the agent dir — exactly where the operator was
|
|
67
|
+
// trying not to write — and `--out --json` used to create a directory
|
|
68
|
+
// called "--json". Both exit 1 now and write nothing.
|
|
69
|
+
const v = take();
|
|
70
|
+
if (v === undefined) { out.error = "--out needs a directory"; return out; }
|
|
71
|
+
if (typeof v === "string" && v.startsWith("--")) { out.error = `--out needs a directory, not ${v}`; return out; }
|
|
72
|
+
out.out = v;
|
|
73
|
+
}
|
|
74
|
+
else { out.error = `unknown argument ${a}`; return out; }
|
|
75
|
+
}
|
|
76
|
+
if (out.sub !== "sync") out.error = `unknown design command: ${out.sub}`;
|
|
77
|
+
else if (out.out !== null && !String(out.out).trim()) out.error = "--out needs a directory";
|
|
78
|
+
return out;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/** A protocol error frame → one readable line. */
|
|
82
|
+
export function describeFrame(frame) {
|
|
83
|
+
const e = frame && frame.error ? frame.error : null;
|
|
84
|
+
if (e) return `${e.code || "error"}: ${e.message || ""}`.trim();
|
|
85
|
+
return "no response";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Re-exported so `maestro design sync` and the daemon's poller name the
|
|
90
|
+
* workspace identically — the heading is part of the rendered bytes, so two
|
|
91
|
+
* definitions would make the two lanes rewrite each other's files forever.
|
|
92
|
+
* The definition lives in `lib/design/write.mjs`.
|
|
93
|
+
*/
|
|
94
|
+
export { workspaceName };
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The testable core. deps: {agentRoot, cfg, now, write, fetchImpl,
|
|
98
|
+
* getFoundationImpl, syncImpl, io}.
|
|
99
|
+
* @param {string[]} argv
|
|
100
|
+
* @param {object} [deps]
|
|
101
|
+
* @returns {Promise<{code:number, data?:object}>}
|
|
102
|
+
*/
|
|
103
|
+
export async function runDesign(argv, deps = {}) {
|
|
104
|
+
const write = typeof deps.write === "function" ? deps.write : (s) => process.stdout.write(`${s}\n`);
|
|
105
|
+
const p = parseDesignArgs(argv);
|
|
106
|
+
if (p.help) { write(usage()); return { code: 0 }; }
|
|
107
|
+
if (p.error) { write(`error: ${p.error}\n\n${usage()}`); return { code: 1 }; }
|
|
108
|
+
|
|
109
|
+
const agentRoot = deps.agentRoot || process.cwd();
|
|
110
|
+
const cfg = deps.cfg || loadOrgConfig(agentRoot);
|
|
111
|
+
if (!isEnabled(cfg)) {
|
|
112
|
+
write("error: the org (Cohort) is not enabled for this agent — run `maestro setup --only org`.");
|
|
113
|
+
return { code: 1 };
|
|
114
|
+
}
|
|
115
|
+
const conn = configFromAgent(cfg) || {};
|
|
116
|
+
const now = Number.isFinite(deps.now) ? deps.now : Date.now();
|
|
117
|
+
const outDir = p.out ? String(p.out) : agentRoot;
|
|
118
|
+
|
|
119
|
+
let frame;
|
|
120
|
+
try {
|
|
121
|
+
frame = await (deps.getFoundationImpl || brandingGetFoundation)(
|
|
122
|
+
{ historyLimit: 0 },
|
|
123
|
+
{ base: conn.base, token: conn.token, orgId: conn.orgId, fetchImpl: deps.fetchImpl },
|
|
124
|
+
);
|
|
125
|
+
} catch (err) {
|
|
126
|
+
// A transport throw is the same class of failure as an error frame: say so
|
|
127
|
+
// and write nothing. The last-good DESIGN.md on disk stays authoritative.
|
|
128
|
+
write(`error: could not read the brand foundation — ${err && err.message ? err.message : String(err)}. Nothing was written.`);
|
|
129
|
+
return { code: 1 };
|
|
130
|
+
}
|
|
131
|
+
if (!frame || !frame.ok || !frame.result || typeof frame.result !== "object") {
|
|
132
|
+
write(`error: could not read the brand foundation — ${describeFrame(frame)}. Nothing was written.`);
|
|
133
|
+
return { code: 1 };
|
|
134
|
+
}
|
|
135
|
+
if (!frame.result.foundation || typeof frame.result.foundation !== "object") {
|
|
136
|
+
write("error: the workspace has no brand foundation yet — set one up in Design first. Nothing was written.");
|
|
137
|
+
return { code: 1 };
|
|
138
|
+
}
|
|
139
|
+
// AN UNVERSIONED WORKSPACE IS NOT A BRAND. hq answers a workspace that has
|
|
140
|
+
// never saved a foundation with its own product defaults and `unversioned:
|
|
141
|
+
// true` — a complete, plausible palette that is nobody's brand. Writing it
|
|
142
|
+
// would hand every design skill on the seat a wrong palette wearing the
|
|
143
|
+
// authority of a generated file. See refresh-gate.mjs#isUnversionedFoundation.
|
|
144
|
+
if (isUnversionedFoundation(frame.result)) {
|
|
145
|
+
write("error: this workspace has never saved a brand foundation — hq answered with stock defaults, which are not your brand. Set one up in Design (or propose one with design_propose_change) and re-run. Nothing was written.");
|
|
146
|
+
return { code: 1 };
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
let result;
|
|
150
|
+
try {
|
|
151
|
+
result = (deps.syncImpl || syncDesign)(
|
|
152
|
+
{ agentRoot, outDir, result: frame.result, now, name: deps.name || workspaceName(cfg), source: "cohort · branding.getFoundation" },
|
|
153
|
+
deps.io || {},
|
|
154
|
+
);
|
|
155
|
+
} catch (err) {
|
|
156
|
+
write(`error: could not write the design files — ${err && err.message ? err.message : String(err)}`);
|
|
157
|
+
return { code: 1 };
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
if (p.json) {
|
|
161
|
+
write(JSON.stringify(result, null, 2));
|
|
162
|
+
return { code: 0, data: result };
|
|
163
|
+
}
|
|
164
|
+
const wrote = result.written.length
|
|
165
|
+
? `wrote ${result.written.join(", ")}`
|
|
166
|
+
: `unchanged (${result.unchanged.join(", ")})`;
|
|
167
|
+
write(`design sync: ${wrote} in ${result.outDir}${result.version ? ` — foundation ${result.version}` : ""}`);
|
|
168
|
+
return { code: 0, data: result };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** bin entry: resolve the agent root (env → walk → machine pointer) and run. */
|
|
172
|
+
export async function designCmd(argv) {
|
|
173
|
+
let agentRoot = null;
|
|
174
|
+
try {
|
|
175
|
+
const { resolveAgentRoot } = await import("../collective/config.mjs");
|
|
176
|
+
agentRoot = resolveAgentRoot(process.cwd());
|
|
177
|
+
} catch {
|
|
178
|
+
agentRoot = null; // fall through to cwd
|
|
179
|
+
}
|
|
180
|
+
const r = await runDesign(argv, { agentRoot: agentRoot || process.cwd() });
|
|
181
|
+
process.exitCode = r.code;
|
|
182
|
+
return r;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
export default { runDesign, designCmd, parseDesignArgs, workspaceName, describeFrame, usage };
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* design.test.mjs — `maestro design sync` (WP-M7 mechanic 4).
|
|
3
|
+
*
|
|
4
|
+
* Drives the REAL `runDesign` against a temp agent dir with an injected
|
|
5
|
+
* `branding.getFoundation`. Two claims carry the command and both are load
|
|
6
|
+
* bearing:
|
|
7
|
+
*
|
|
8
|
+
* · FAIL-OPEN MEANS "WRITE NOTHING". A seat's DESIGN.md is what every design
|
|
9
|
+
* skill grounds on. A failed read must leave the last-good file exactly
|
|
10
|
+
* where it is and exit non-zero — the opposite of half-writing a file that
|
|
11
|
+
* then looks authoritative.
|
|
12
|
+
* · IDEMPOTENT BY BYTES. A second run against an unchanged foundation must
|
|
13
|
+
* not touch a single mtime, so the 15-minute daemon poll never churns the
|
|
14
|
+
* tree or wakes an editor's file watcher.
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { test } from "node:test";
|
|
18
|
+
import assert from "node:assert/strict";
|
|
19
|
+
import { mkdtempSync, readFileSync, writeFileSync, statSync, utimesSync, existsSync, rmSync, readdirSync } from "node:fs";
|
|
20
|
+
import { join } from "node:path";
|
|
21
|
+
import { tmpdir } from "node:os";
|
|
22
|
+
import { execFileSync } from "node:child_process";
|
|
23
|
+
import { fileURLToPath } from "node:url";
|
|
24
|
+
|
|
25
|
+
import { runDesign, parseDesignArgs, describeFrame, workspaceName, usage } from "./design.mjs";
|
|
26
|
+
|
|
27
|
+
const NOW = Date.parse("2026-09-08T12:00:00Z");
|
|
28
|
+
const FIXTURE = JSON.parse(readFileSync(new URL("../design/fixtures/foundation.json", import.meta.url), "utf8"));
|
|
29
|
+
const CFG = { org: { cohort: { enabled: true, base: "https://os.example.test", token: "nlk_t", orgId: "org_1", orgName: "Northwind" } } };
|
|
30
|
+
|
|
31
|
+
const okFrame = (over = {}) => ({
|
|
32
|
+
ok: true,
|
|
33
|
+
result: {
|
|
34
|
+
foundation: FIXTURE,
|
|
35
|
+
version: { id: "bv_01HQZ", seq: 7, label: "V2.7" },
|
|
36
|
+
headVersionId: "bv_01HQZ",
|
|
37
|
+
isHead: true,
|
|
38
|
+
unversioned: false,
|
|
39
|
+
history: [],
|
|
40
|
+
...over,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function harness(t, opts = {}) {
|
|
45
|
+
const root = mkdtempSync(join(tmpdir(), "maestro-design-cli-"));
|
|
46
|
+
t.after(() => rmSync(root, { recursive: true, force: true }));
|
|
47
|
+
const lines = [];
|
|
48
|
+
const calls = [];
|
|
49
|
+
const deps = {
|
|
50
|
+
agentRoot: root,
|
|
51
|
+
cfg: opts.cfg === undefined ? CFG : opts.cfg,
|
|
52
|
+
now: opts.now === undefined ? NOW : opts.now,
|
|
53
|
+
write: (s) => lines.push(s),
|
|
54
|
+
getFoundationImpl: opts.getFoundationImpl || (async (params, o) => { calls.push({ params, o }); return okFrame(); }),
|
|
55
|
+
};
|
|
56
|
+
return { root, lines, calls, deps, out: () => lines.join("\n") };
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// ── argument parsing ────────────────────────────────────────────────────────
|
|
60
|
+
|
|
61
|
+
test("parseDesignArgs handles the flags, both spellings, and rejects the rest", () => {
|
|
62
|
+
assert.deepEqual(parseDesignArgs(["sync"]), { sub: "sync", out: null, json: false, help: false, error: null });
|
|
63
|
+
assert.equal(parseDesignArgs(["sync", "--json"]).json, true);
|
|
64
|
+
assert.equal(parseDesignArgs(["sync", "--out", "docs"]).out, "docs");
|
|
65
|
+
assert.equal(parseDesignArgs(["sync", "--out=docs"]).out, "docs");
|
|
66
|
+
assert.equal(parseDesignArgs([]).help, true);
|
|
67
|
+
assert.equal(parseDesignArgs(["--help"]).help, true);
|
|
68
|
+
assert.equal(parseDesignArgs(["-h"]).help, true);
|
|
69
|
+
assert.match(parseDesignArgs(["shove"]).error, /unknown design command: shove/);
|
|
70
|
+
assert.match(parseDesignArgs(["sync", "--wat"]).error, /unknown argument --wat/);
|
|
71
|
+
assert.match(parseDesignArgs(["sync", "--out", ""]).error, /--out needs a directory/);
|
|
72
|
+
|
|
73
|
+
// A MISSING value is an error, not a silent fallback to the agent dir —
|
|
74
|
+
// which is precisely the directory the operator was trying not to write to.
|
|
75
|
+
assert.match(parseDesignArgs(["sync", "--out"]).error, /--out needs a directory/);
|
|
76
|
+
assert.equal(parseDesignArgs(["sync", "--out"]).out, null);
|
|
77
|
+
assert.match(parseDesignArgs(["sync", "--out", "--json"]).error, /--out needs a directory, not --json/);
|
|
78
|
+
assert.equal(parseDesignArgs(["sync", "--out", "--json"]).json, false, "and --json was not eaten as a path");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
test("`--out` with no path exits 1 and writes nothing at all", async (t) => {
|
|
82
|
+
const h = harness(t);
|
|
83
|
+
const r = await runDesign(["sync", "--out"], h.deps);
|
|
84
|
+
assert.equal(r.code, 1);
|
|
85
|
+
assert.match(h.out(), /--out needs a directory/);
|
|
86
|
+
assert.equal(h.calls.length, 0, "not even a read is spent");
|
|
87
|
+
assert.deepEqual(readdirSync(h.root), [], "and no DESIGN.md lands in the agent dir by accident");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("--help and a bad argument both print the usage; only the bad argument is an error", async (t) => {
|
|
91
|
+
const h = harness(t);
|
|
92
|
+
assert.deepEqual(await runDesign(["--help"], h.deps), { code: 0 });
|
|
93
|
+
assert.match(h.out(), /maestro design sync/);
|
|
94
|
+
|
|
95
|
+
const h2 = harness(t);
|
|
96
|
+
const r = await runDesign(["sync", "--nope"], h2.deps);
|
|
97
|
+
assert.equal(r.code, 1);
|
|
98
|
+
assert.match(h2.out(), /error: unknown argument --nope/);
|
|
99
|
+
assert.match(h2.out(), /maestro design sync/, "the usage follows the error");
|
|
100
|
+
assert.ok(usage().includes("design_propose_change"), "the usage says how a change is actually made");
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// ── the happy path ──────────────────────────────────────────────────────────
|
|
104
|
+
|
|
105
|
+
test("sync writes DESIGN.md, PRODUCT.md and the snapshot, and reads the SAVED foundation", async (t) => {
|
|
106
|
+
const h = harness(t);
|
|
107
|
+
const r = await runDesign(["sync"], h.deps);
|
|
108
|
+
|
|
109
|
+
assert.equal(r.code, 0);
|
|
110
|
+
assert.deepEqual(r.data.written, ["DESIGN.md", "PRODUCT.md"]);
|
|
111
|
+
assert.ok(existsSync(join(h.root, "DESIGN.md")));
|
|
112
|
+
assert.ok(existsSync(join(h.root, "PRODUCT.md")));
|
|
113
|
+
assert.ok(existsSync(join(h.root, "state", "design", "foundation.json")));
|
|
114
|
+
assert.match(h.out(), /design sync: wrote DESIGN\.md, PRODUCT\.md/);
|
|
115
|
+
assert.match(h.out(), /foundation bv_01HQZ/);
|
|
116
|
+
|
|
117
|
+
assert.equal(h.calls.length, 1);
|
|
118
|
+
assert.deepEqual(h.calls[0].params, { historyLimit: 0 }, "no history is fetched — the seat only needs the head");
|
|
119
|
+
assert.equal(h.calls[0].o.base, "https://os.example.test", "the agent's own connection is used");
|
|
120
|
+
assert.equal(h.calls[0].o.orgId, "org_1");
|
|
121
|
+
|
|
122
|
+
const design = readFileSync(join(h.root, "DESIGN.md"), "utf8");
|
|
123
|
+
assert.match(design, /^---\nname: "Northwind"\n/, "the heading comes from the org config, not the foundation prose");
|
|
124
|
+
assert.match(design, /\{colors\.primary\}/);
|
|
125
|
+
assert.equal(statSync(join(h.root, "DESIGN.md")).mode & 0o777, 0o644);
|
|
126
|
+
assert.equal(statSync(join(h.root, "PRODUCT.md")).mode & 0o777, 0o644);
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("--json prints the machine result and still writes the files", async (t) => {
|
|
130
|
+
const h = harness(t);
|
|
131
|
+
const r = await runDesign(["sync", "--json"], h.deps);
|
|
132
|
+
assert.equal(r.code, 0);
|
|
133
|
+
const doc = JSON.parse(h.out());
|
|
134
|
+
assert.deepEqual(doc.written, ["DESIGN.md", "PRODUCT.md"]);
|
|
135
|
+
assert.equal(doc.version, "bv_01HQZ");
|
|
136
|
+
assert.equal(doc.outDir, h.root);
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("--out puts the docs elsewhere and keeps the snapshot under the agent root", async (t) => {
|
|
140
|
+
const h = harness(t);
|
|
141
|
+
const outDir = join(h.root, "brand");
|
|
142
|
+
const r = await runDesign(["sync", "--out", outDir], h.deps);
|
|
143
|
+
assert.equal(r.code, 0);
|
|
144
|
+
assert.ok(existsSync(join(outDir, "DESIGN.md")));
|
|
145
|
+
assert.ok(!existsSync(join(h.root, "DESIGN.md")));
|
|
146
|
+
assert.ok(existsSync(join(h.root, "state", "design", "foundation.json")));
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
test("re-running against an unchanged foundation rewrites nothing", async (t) => {
|
|
150
|
+
const h = harness(t);
|
|
151
|
+
await runDesign(["sync"], h.deps);
|
|
152
|
+
const old = new Date(NOW - 86_400_000);
|
|
153
|
+
for (const rel of ["DESIGN.md", "PRODUCT.md"]) utimesSync(join(h.root, rel), old, old);
|
|
154
|
+
const before = ["DESIGN.md", "PRODUCT.md"].map((rel) => statSync(join(h.root, rel)).mtimeMs);
|
|
155
|
+
|
|
156
|
+
const r = await runDesign(["sync"], { ...h.deps, now: NOW + 3_600_000 });
|
|
157
|
+
assert.equal(r.code, 0);
|
|
158
|
+
assert.deepEqual(r.data.written, []);
|
|
159
|
+
assert.deepEqual(r.data.unchanged, ["DESIGN.md", "PRODUCT.md"]);
|
|
160
|
+
assert.match(h.out(), /design sync: unchanged \(DESIGN\.md, PRODUCT\.md\)/);
|
|
161
|
+
assert.deepEqual(["DESIGN.md", "PRODUCT.md"].map((rel) => statSync(join(h.root, rel)).mtimeMs), before);
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// ── the failure postures ────────────────────────────────────────────────────
|
|
165
|
+
|
|
166
|
+
test("an error frame exits 1 and writes NOTHING", async (t) => {
|
|
167
|
+
const h = harness(t, { getFoundationImpl: async () => ({ ok: false, error: { code: "FORBIDDEN", message: "design.read is not granted to this seat." } }) });
|
|
168
|
+
const r = await runDesign(["sync"], h.deps);
|
|
169
|
+
assert.equal(r.code, 1);
|
|
170
|
+
assert.match(h.out(), /FORBIDDEN: design\.read is not granted to this seat\./);
|
|
171
|
+
assert.match(h.out(), /Nothing was written\./);
|
|
172
|
+
assert.deepEqual(readdirSync(h.root), [], "not one file, not even the state dir");
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
test("a transport throw exits 1, says so, and leaves the last-good files exactly as they were", async (t) => {
|
|
176
|
+
const h = harness(t);
|
|
177
|
+
await runDesign(["sync"], h.deps); // establish a good DESIGN.md first
|
|
178
|
+
const good = readFileSync(join(h.root, "DESIGN.md"), "utf8");
|
|
179
|
+
const old = new Date(NOW - 86_400_000);
|
|
180
|
+
utimesSync(join(h.root, "DESIGN.md"), old, old);
|
|
181
|
+
const mtime = statSync(join(h.root, "DESIGN.md")).mtimeMs;
|
|
182
|
+
|
|
183
|
+
const r = await runDesign(["sync"], { ...h.deps, getFoundationImpl: async () => { throw new Error("fetch failed: ECONNREFUSED"); } });
|
|
184
|
+
assert.equal(r.code, 1);
|
|
185
|
+
assert.match(h.out(), /could not read the brand foundation — fetch failed: ECONNREFUSED/);
|
|
186
|
+
assert.match(h.out(), /Nothing was written\./);
|
|
187
|
+
assert.equal(readFileSync(join(h.root, "DESIGN.md"), "utf8"), good, "the last-good file is untouched");
|
|
188
|
+
assert.equal(statSync(join(h.root, "DESIGN.md")).mtimeMs, mtime);
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
test("a workspace with no foundation yet is named as such, not rendered as an empty brand", async (t) => {
|
|
192
|
+
const h = harness(t, { getFoundationImpl: async () => ({ ok: true, result: { foundation: null, headVersionId: null, unversioned: true, history: [] } }) });
|
|
193
|
+
const r = await runDesign(["sync"], h.deps);
|
|
194
|
+
assert.equal(r.code, 1);
|
|
195
|
+
assert.match(h.out(), /no brand foundation yet — set one up in Design first/);
|
|
196
|
+
assert.deepEqual(readdirSync(h.root), []);
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
test("hq's stock defaults are refused: an unversioned workspace writes nothing and is told why", async (t) => {
|
|
200
|
+
// The FRAME HQ ACTUALLY SENDS for a workspace that never saved a foundation:
|
|
201
|
+
// a complete `dsDefaults()` palette, `unversioned: true`, `headVersionId:
|
|
202
|
+
// null`. The `foundation: null` case above cannot occur in production, so
|
|
203
|
+
// this is the guard that has to hold.
|
|
204
|
+
const h = harness(t, {
|
|
205
|
+
getFoundationImpl: async () => ({
|
|
206
|
+
ok: true,
|
|
207
|
+
result: {
|
|
208
|
+
foundation: { ...FIXTURE, colors: [{ name: "Ink", value: "#0d0d0d" }] },
|
|
209
|
+
version: null,
|
|
210
|
+
headVersionId: null,
|
|
211
|
+
isHead: false,
|
|
212
|
+
unversioned: true,
|
|
213
|
+
history: [],
|
|
214
|
+
},
|
|
215
|
+
}),
|
|
216
|
+
});
|
|
217
|
+
const r = await runDesign(["sync"], h.deps);
|
|
218
|
+
assert.equal(r.code, 1);
|
|
219
|
+
assert.match(h.out(), /never saved a brand foundation/);
|
|
220
|
+
assert.match(h.out(), /stock defaults, which are not your brand/);
|
|
221
|
+
assert.match(h.out(), /Nothing was written\./);
|
|
222
|
+
assert.deepEqual(readdirSync(h.root), [], "no DESIGN.md wearing another product's palette");
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
test("an older hq that omits `unversioned` is judged by the version identity instead", async (t) => {
|
|
226
|
+
const h = harness(t, {
|
|
227
|
+
getFoundationImpl: async () => ({ ok: true, result: { foundation: FIXTURE, version: null, headVersionId: null, history: [] } }),
|
|
228
|
+
});
|
|
229
|
+
const r = await runDesign(["sync"], h.deps);
|
|
230
|
+
assert.equal(r.code, 1);
|
|
231
|
+
assert.match(h.out(), /never saved a brand foundation/);
|
|
232
|
+
assert.deepEqual(readdirSync(h.root), []);
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
test("a seat that is not enrolled in the org exits 1 and points at setup", async (t) => {
|
|
236
|
+
const h = harness(t, { cfg: { org: { cohort: { enabled: false } } } });
|
|
237
|
+
const r = await runDesign(["sync"], h.deps);
|
|
238
|
+
assert.equal(r.code, 1);
|
|
239
|
+
assert.match(h.out(), /not enabled for this agent — run `maestro setup --only org`/);
|
|
240
|
+
assert.equal(h.calls.length, 0, "no hq call is attempted");
|
|
241
|
+
assert.deepEqual(readdirSync(h.root), []);
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
test("a write failure is reported, not thrown", async (t) => {
|
|
245
|
+
const h = harness(t);
|
|
246
|
+
const r = await runDesign(["sync"], { ...h.deps, syncImpl: () => { throw new Error("EROFS: read-only file system"); } });
|
|
247
|
+
assert.equal(r.code, 1);
|
|
248
|
+
assert.match(h.out(), /could not write the design files — EROFS/);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("describeFrame turns any frame into one readable line", () => {
|
|
252
|
+
assert.equal(describeFrame({ ok: false, error: { code: "RATE_LIMITED", message: "Slow down." } }), "RATE_LIMITED: Slow down.");
|
|
253
|
+
assert.equal(describeFrame({ ok: false, error: { message: "no code" } }), "error: no code");
|
|
254
|
+
assert.equal(describeFrame(null), "no response");
|
|
255
|
+
assert.equal(describeFrame({ ok: true }), "no response");
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
test("workspaceName is the one shared with the daemon", async () => {
|
|
259
|
+
const { workspaceName: fromWriter } = await import("../design/write.mjs");
|
|
260
|
+
assert.equal(workspaceName, fromWriter, "one definition, so the CLI and the daemon cannot render different headings");
|
|
261
|
+
});
|
|
262
|
+
|
|
263
|
+
// ── the bin seam ────────────────────────────────────────────────────────────
|
|
264
|
+
|
|
265
|
+
test("`maestro design` is wired into bin/maestro.mjs", () => {
|
|
266
|
+
const bin = fileURLToPath(new URL("../../bin/maestro.mjs", import.meta.url));
|
|
267
|
+
const out = execFileSync(process.execPath, [bin, "design", "--help"], { encoding: "utf8" });
|
|
268
|
+
assert.match(out, /maestro design sync \[--out <dir>\] \[--json\]/);
|
|
269
|
+
assert.match(execFileSync(process.execPath, [bin, "--help"], { encoding: "utf8" }), /design sync/, "and listed in the top-level help");
|
|
270
|
+
});
|