@1agh/maude 0.22.2 → 0.24.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/README.md +34 -1
- package/cli/bin/maude.mjs +3 -0
- package/cli/commands/cache.mjs +181 -0
- package/cli/commands/cache.test.mjs +166 -0
- package/cli/commands/design-link.test.mjs +84 -2
- package/cli/commands/design.mjs +95 -4
- package/cli/commands/design.test.mjs +56 -0
- package/cli/commands/help.mjs +34 -0
- package/cli/commands/hub.mjs +255 -30
- package/cli/commands/hub.test.mjs +135 -10
- package/cli/commands/init.mjs +3 -0
- package/cli/commands/preflight.mjs +11 -0
- package/cli/commands/preflight.test.mjs +46 -0
- package/cli/commands/scenario-report.mjs +45 -0
- package/cli/lib/cache.mjs +407 -0
- package/cli/lib/cache.test.mjs +303 -0
- package/cli/lib/design-link.mjs +222 -11
- package/cli/lib/flow-design-integration.test.mjs +165 -0
- package/cli/lib/gitignore-block.mjs +90 -0
- package/cli/lib/gitignore-block.test.mjs +123 -0
- package/cli/lib/hubs-config.mjs +42 -4
- package/cli/lib/plugin-cli-reachability.test.mjs +56 -0
- package/cli/lib/preflight.mjs +41 -10
- package/package.json +8 -8
- package/plugins/design/dev-server/ai-banner.tsx +2 -2
- package/plugins/design/dev-server/annotations-context-toolbar.tsx +349 -112
- package/plugins/design/dev-server/annotations-layer.tsx +906 -137
- package/plugins/design/dev-server/api.ts +109 -4
- package/plugins/design/dev-server/bin/preflight.sh +21 -10
- package/plugins/design/dev-server/bin/prep.sh +211 -0
- package/plugins/design/dev-server/bin/scenario-report.mjs +209 -0
- package/plugins/design/dev-server/bin/screenshot.sh +18 -1
- package/plugins/design/dev-server/bin/smoke.sh +94 -11
- package/plugins/design/dev-server/build.ts +69 -3
- package/plugins/design/dev-server/canvas-comment-mount.tsx +384 -0
- package/plugins/design/dev-server/canvas-cursors.ts +125 -0
- package/plugins/design/dev-server/canvas-icons.tsx +130 -0
- package/plugins/design/dev-server/canvas-lib.tsx +47 -27
- package/plugins/design/dev-server/canvas-meta.schema.json +20 -0
- package/plugins/design/dev-server/canvas-shell.tsx +358 -245
- package/plugins/design/dev-server/client/app.jsx +191 -21
- package/plugins/design/dev-server/client/styles/3-shell.css +10 -0
- package/plugins/design/dev-server/collab/awareness-bridge.ts +77 -0
- package/plugins/design/dev-server/collab/index.ts +87 -9
- package/plugins/design/dev-server/collab/persistence.ts +34 -3
- package/plugins/design/dev-server/collab/registry.ts +131 -2
- package/plugins/design/dev-server/collab/room.ts +21 -8
- package/plugins/design/dev-server/config.schema.json +20 -0
- package/plugins/design/dev-server/context-menu.tsx +167 -23
- package/plugins/design/dev-server/context.ts +31 -0
- package/plugins/design/dev-server/contextual-toolbar.tsx +7 -7
- package/plugins/design/dev-server/dist/client.bundle.js +144 -22
- package/plugins/design/dev-server/dist/comment-mount.js +1868 -0
- package/plugins/design/dev-server/dist/styles.css +16 -0
- package/plugins/design/dev-server/dom-selection.ts +156 -0
- package/plugins/design/dev-server/equal-spacing-handles.tsx +1 -1
- package/plugins/design/dev-server/export-dialog.tsx +19 -17
- package/plugins/design/dev-server/fs-watch.ts +1 -0
- package/plugins/design/dev-server/hmr-broadcast.ts +51 -20
- package/plugins/design/dev-server/http.ts +260 -20
- package/plugins/design/dev-server/input-router.tsx +125 -64
- package/plugins/design/dev-server/participants-chrome.tsx +10 -10
- package/plugins/design/dev-server/server.ts +141 -1
- package/plugins/design/dev-server/sync/agent.ts +418 -0
- package/plugins/design/dev-server/sync/atomic-write.ts +103 -0
- package/plugins/design/dev-server/sync/codec.ts +324 -0
- package/plugins/design/dev-server/sync/connection-state.ts +203 -0
- package/plugins/design/dev-server/sync/echo-guard.ts +108 -0
- package/plugins/design/dev-server/sync/fs-mirror.ts +160 -0
- package/plugins/design/dev-server/sync/hubs-config.ts +87 -0
- package/plugins/design/dev-server/sync/index.ts +918 -0
- package/plugins/design/dev-server/sync/materialize.ts +62 -0
- package/plugins/design/dev-server/sync/migrate-seed.ts +163 -0
- package/plugins/design/dev-server/sync/origins.ts +57 -0
- package/plugins/design/dev-server/sync/projection.ts +368 -0
- package/plugins/design/dev-server/sync/status.ts +115 -0
- package/plugins/design/dev-server/sync/untrusted.ts +153 -0
- package/plugins/design/dev-server/test/_helpers.ts +6 -2
- package/plugins/design/dev-server/test/annotations-layer.test.ts +231 -0
- package/plugins/design/dev-server/test/annotations-roundtrip.test.ts +276 -0
- package/plugins/design/dev-server/test/canvas-cursors.test.ts +73 -0
- package/plugins/design/dev-server/test/canvas-origin-gate.test.ts +76 -0
- package/plugins/design/dev-server/test/collab-awareness-bridge.test.ts +223 -0
- package/plugins/design/dev-server/test/collab-reseed-guard.test.ts +78 -0
- package/plugins/design/dev-server/test/collab-room.test.ts +21 -10
- package/plugins/design/dev-server/test/comment-mount.test.ts +87 -0
- package/plugins/design/dev-server/test/csp-canvas-shell.test.ts +46 -0
- package/plugins/design/dev-server/test/fixtures/phase-20-annotations.svg +1 -0
- package/plugins/design/dev-server/test/hmr-classify.test.ts +70 -0
- package/plugins/design/dev-server/test/input-router.test.ts +21 -0
- package/plugins/design/dev-server/test/sanitize-annotation-svg.test.ts +100 -0
- package/plugins/design/dev-server/test/shared-doc-convergence.test.ts +265 -0
- package/plugins/design/dev-server/test/shared-doc-foundation.test.ts +63 -0
- package/plugins/design/dev-server/test/shared-doc-migrate.test.ts +160 -0
- package/plugins/design/dev-server/test/shared-doc-projection.test.ts +238 -0
- package/plugins/design/dev-server/test/sync-agent.test.ts +278 -0
- package/plugins/design/dev-server/test/sync-atomic-write.test.ts +63 -0
- package/plugins/design/dev-server/test/sync-codec.test.ts +165 -0
- package/plugins/design/dev-server/test/sync-connection-state.test.ts +146 -0
- package/plugins/design/dev-server/test/sync-echo-guard.test.ts +96 -0
- package/plugins/design/dev-server/test/sync-fs-mirror.test.ts +182 -0
- package/plugins/design/dev-server/test/sync-hardening.test.ts +520 -0
- package/plugins/design/dev-server/test/sync-hubs-config.test.ts +130 -0
- package/plugins/design/dev-server/test/sync-meta-codec.test.ts +123 -0
- package/plugins/design/dev-server/test/sync-runtime.test.ts +812 -0
- package/plugins/design/dev-server/test/sync-status.test.ts +80 -0
- package/plugins/design/dev-server/test/sync-untrusted.test.ts +104 -0
- package/plugins/design/dev-server/test/use-collab.test.ts +0 -0
- package/plugins/design/dev-server/test/use-tool-mode.test.tsx +38 -10
- package/plugins/design/dev-server/tool-palette.tsx +18 -16
- package/plugins/design/dev-server/undo-hud.tsx +4 -4
- package/plugins/design/dev-server/undo-stack.ts +20 -4
- package/plugins/design/dev-server/use-annotation-resize.tsx +16 -5
- package/plugins/design/dev-server/use-collab.tsx +157 -13
- package/plugins/design/dev-server/use-selection-set.tsx +12 -0
- package/plugins/design/dev-server/use-tool-mode.tsx +27 -12
- package/plugins/design/dev-server/ws.ts +40 -1
- package/plugins/design/templates/_shell.html +63 -5
- package/plugins/design/templates/canvas.tsx.template +13 -0
- package/plugins/design/templates/design-system-inspiration/SUB-AGENT-PROMPTS.md +15 -7
- package/plugins/flow/.claude-plugin/config.schema.json +5 -0
- package/plugins/flow/templates/ai-skeleton/README.md +1 -1
- package/plugins/flow/templates/ai-skeleton/gitignore +10 -0
- package/plugins/flow/templates/ai-skeleton/scenarios/README.md +3 -1
- package/plugins/flow/templates/ai-skeleton/workflows.config.json +2 -1
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
// Phase 11 (flow ⇄ design integration) regression guard.
|
|
2
|
+
//
|
|
3
|
+
// The slash-command orchestration (/flow:plan canvas detection, /flow:done
|
|
4
|
+
// handoff sweep, codebase-intelligence design section, ddr-keeper canvas prompt)
|
|
5
|
+
// is LLM-driven markdown — not unit-testable. What IS deterministic, and what
|
|
6
|
+
// this test locks down, is the *contract* that wiring depends on:
|
|
7
|
+
//
|
|
8
|
+
// 1. The canvas `.meta.json` schema accepts the Phase 11 fields
|
|
9
|
+
// (status enum, handoffCommit, tags, brief_sha) and rejects a bad status.
|
|
10
|
+
// 2. The flow config schema accepts `paths.designRoot`, the real skeleton
|
|
11
|
+
// config validates, and the field stays OPTIONAL (no regression for
|
|
12
|
+
// projects without the design plugin).
|
|
13
|
+
// 3. The /flow:done status round-trip (ready-for-handoff → handed-off +
|
|
14
|
+
// handoffCommit) produces schema-valid output at both ends.
|
|
15
|
+
// 4. The markdown wiring for all four integrations is present (grep guard) —
|
|
16
|
+
// a future edit that deletes a section fails loudly here.
|
|
17
|
+
|
|
18
|
+
import assert from 'node:assert/strict';
|
|
19
|
+
import { readFileSync } from 'node:fs';
|
|
20
|
+
import { resolve } from 'node:path';
|
|
21
|
+
import { test } from 'node:test';
|
|
22
|
+
import addFormats from 'ajv-formats';
|
|
23
|
+
import Ajv2020 from 'ajv/dist/2020.js';
|
|
24
|
+
import { lintConfig } from './config-lint.mjs';
|
|
25
|
+
|
|
26
|
+
const read = (p) => readFileSync(resolve(p), 'utf8');
|
|
27
|
+
const readJson = (p) => JSON.parse(read(p));
|
|
28
|
+
|
|
29
|
+
const META_SCHEMA = readJson('plugins/design/dev-server/canvas-meta.schema.json');
|
|
30
|
+
const FLOW_SCHEMA = readJson('plugins/flow/.claude-plugin/config.schema.json');
|
|
31
|
+
const SKELETON = readJson('plugins/flow/templates/ai-skeleton/workflows.config.json');
|
|
32
|
+
|
|
33
|
+
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
34
|
+
addFormats(ajv);
|
|
35
|
+
const validateMeta = ajv.compile(META_SCHEMA);
|
|
36
|
+
|
|
37
|
+
// ── 1. canvas-meta schema accepts the Phase 11 fields ──────────────────────
|
|
38
|
+
|
|
39
|
+
test('canvas-meta: accepts status + tags (ready-for-handoff)', () => {
|
|
40
|
+
const ok = validateMeta({
|
|
41
|
+
title: 'Dark Mode Toggle',
|
|
42
|
+
sections: [{ id: 'hero', label: 'Hero' }],
|
|
43
|
+
tags: ['dark-mode'],
|
|
44
|
+
status: 'ready-for-handoff',
|
|
45
|
+
});
|
|
46
|
+
assert.equal(ok, true, JSON.stringify(validateMeta.errors));
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test('canvas-meta: accepts handed-off + handoffCommit + brief_sha', () => {
|
|
50
|
+
const ok = validateMeta({
|
|
51
|
+
title: 'Settings',
|
|
52
|
+
sections: [],
|
|
53
|
+
status: 'handed-off',
|
|
54
|
+
handoffCommit: '8dd832b',
|
|
55
|
+
brief_sha: 'abc123def456',
|
|
56
|
+
});
|
|
57
|
+
assert.equal(ok, true, JSON.stringify(validateMeta.errors));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('canvas-meta: every status enum value is accepted', () => {
|
|
61
|
+
for (const status of ['draft', 'in-review', 'ready-for-handoff', 'handed-off']) {
|
|
62
|
+
const ok = validateMeta({ title: 'X', sections: [], status });
|
|
63
|
+
assert.equal(ok, true, `status=${status}: ${JSON.stringify(validateMeta.errors)}`);
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('canvas-meta: a bogus status enum is REJECTED', () => {
|
|
68
|
+
const ok = validateMeta({ title: 'X', sections: [], status: 'shipped' });
|
|
69
|
+
assert.equal(ok, false, 'expected status="shipped" to be rejected by the enum');
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('canvas-meta: back-compat — a sidecar without any Phase 11 field still validates', () => {
|
|
73
|
+
const ok = validateMeta({ title: 'Legacy', sections: [{ id: 's', label: 'S' }] });
|
|
74
|
+
assert.equal(ok, true, JSON.stringify(validateMeta.errors));
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
test('canvas-meta: tags must be unique strings', () => {
|
|
78
|
+
assert.equal(validateMeta({ title: 'X', sections: [], tags: ['a', 'a'] }), false);
|
|
79
|
+
assert.equal(validateMeta({ title: 'X', sections: [], tags: [1, 2] }), false);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
// ── 2. flow config schema: paths.designRoot ────────────────────────────────
|
|
83
|
+
|
|
84
|
+
test('flow config: the real skeleton config (with paths.designRoot) validates', async () => {
|
|
85
|
+
const r = await lintConfig({ config: SKELETON, schema: FLOW_SCHEMA });
|
|
86
|
+
assert.equal(r.ok, true, JSON.stringify(r.errors));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test('flow config: skeleton declares paths.designRoot = .design', () => {
|
|
90
|
+
assert.equal(SKELETON.paths.designRoot, '.design');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('flow config: paths.designRoot is OPTIONAL (no-regression for non-design projects)', async () => {
|
|
94
|
+
const r = await lintConfig({
|
|
95
|
+
config: { name: 'x', paths: { prd: '.ai/x-prd.md' } },
|
|
96
|
+
schema: FLOW_SCHEMA,
|
|
97
|
+
});
|
|
98
|
+
assert.equal(r.ok, true, JSON.stringify(r.errors));
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
test('flow config: a non-string paths.designRoot is rejected', async () => {
|
|
102
|
+
const r = await lintConfig({
|
|
103
|
+
config: { name: 'x', paths: { designRoot: 42 } },
|
|
104
|
+
schema: FLOW_SCHEMA,
|
|
105
|
+
});
|
|
106
|
+
assert.equal(r.ok, false);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('flow config: paths.designRoot carries a default of .design', () => {
|
|
110
|
+
assert.equal(FLOW_SCHEMA.properties.paths.properties.designRoot.default, '.design');
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
// ── 3. /flow:done status round-trip stays schema-valid ─────────────────────
|
|
114
|
+
|
|
115
|
+
test('done sweep round-trip: ready-for-handoff → handed-off + handoffCommit both valid', () => {
|
|
116
|
+
const before = { title: 'Canvas', sections: [], status: 'ready-for-handoff', tags: ['feat'] };
|
|
117
|
+
assert.equal(validateMeta(before), true, `before: ${JSON.stringify(validateMeta.errors)}`);
|
|
118
|
+
|
|
119
|
+
// what /flow:done step 5b writes:
|
|
120
|
+
const after = { ...before, status: 'handed-off', handoffCommit: 'deadbeef0123' };
|
|
121
|
+
assert.equal(validateMeta(after), true, `after: ${JSON.stringify(validateMeta.errors)}`);
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
// ── 4. markdown wiring guards (a deleted section fails here) ────────────────
|
|
125
|
+
|
|
126
|
+
const PLAN = read('plugins/flow/commands/plan.md');
|
|
127
|
+
const DONE = read('plugins/flow/commands/done.md');
|
|
128
|
+
const MAP_CMD = read('plugins/flow/commands/setup-codebase-map.md');
|
|
129
|
+
const CI_SKILL = read('plugins/flow/skills/codebase-intelligence/SKILL.md');
|
|
130
|
+
const DDR_SKILL = read('plugins/flow/skills/ddr-keeper/SKILL.md');
|
|
131
|
+
const RECORD_DDR = read('plugins/flow/commands/record-ddr.md');
|
|
132
|
+
|
|
133
|
+
test('wiring: /flow:plan has Design Canvas Detection', () => {
|
|
134
|
+
assert.match(PLAN, /Design Canvas Detection/);
|
|
135
|
+
assert.match(PLAN, /paths\.designRoot/);
|
|
136
|
+
assert.match(PLAN, /### Design canvases/); // the Context References subsection
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test('wiring: /flow:done has the handoff sweep + soft-gate DDR-066 reference', () => {
|
|
140
|
+
assert.match(DONE, /Design handoff sweep/);
|
|
141
|
+
assert.match(DONE, /ready-for-handoff/);
|
|
142
|
+
assert.match(DONE, /handed-off/);
|
|
143
|
+
assert.match(DONE, /handoffCommit/);
|
|
144
|
+
assert.match(DONE, /DDR-066/);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test('wiring: codebase map command + skill have the Design artifacts section', () => {
|
|
148
|
+
assert.match(MAP_CMD, /Map Design Artifacts/);
|
|
149
|
+
assert.match(MAP_CMD, /## Design artifacts/);
|
|
150
|
+
assert.match(CI_SKILL, /Design Artifact Scanning/);
|
|
151
|
+
assert.match(CI_SKILL, /### Design artifacts/);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
test('wiring: ddr-keeper + record-ddr have the canvas-reference prompt', () => {
|
|
155
|
+
assert.match(DDR_SKILL, /Canvas reference for UI-affecting decisions/);
|
|
156
|
+
assert.match(DDR_SKILL, /Related canvas/);
|
|
157
|
+
assert.match(RECORD_DDR, /Related canvas/);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
test('wiring: DDR-066 exists and is indexed', () => {
|
|
161
|
+
const ddr = read('.ai/decisions/DDR-066-soft-handoff-prompt-in-flow-done.md');
|
|
162
|
+
assert.match(ddr, /soft prompt/i);
|
|
163
|
+
const index = read('.ai/decisions/README.md');
|
|
164
|
+
assert.match(index, /DDR-066/);
|
|
165
|
+
});
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
// Maude design-runtime .gitignore block — Phase 9 Task 9 (gitignore strategy).
|
|
2
|
+
//
|
|
3
|
+
// Single `full` strategy in v1.1 (DDR-056): canvases + their JSON snapshots
|
|
4
|
+
// stay in git (cold backup, PR-reviewable .html diffs, bootstrap-from-clone),
|
|
5
|
+
// while regenerable per-machine runtime state is ignored — even in linked mode.
|
|
6
|
+
//
|
|
7
|
+
// The rules live between `# maude:begin` / `# maude:end` markers so the writer
|
|
8
|
+
// is idempotent: re-running replaces the block in place (never duplicates), and
|
|
9
|
+
// anything the user authored outside the markers is untouched. `maude design
|
|
10
|
+
// unlink` deliberately leaves the block — the rules are harmless in solo mode
|
|
11
|
+
// (the ignored files just don't get created).
|
|
12
|
+
|
|
13
|
+
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
14
|
+
import { resolve } from 'node:path';
|
|
15
|
+
|
|
16
|
+
export const BEGIN_MARKER = '# maude:begin';
|
|
17
|
+
export const END_MARKER = '# maude:end';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The runtime paths Maude ignores under the design root, relative to repo root.
|
|
21
|
+
* `designRel` defaults to `.design` (the only design root in v1.1 defaults).
|
|
22
|
+
*/
|
|
23
|
+
export function buildBlock(designRel = '.design') {
|
|
24
|
+
const root = designRel.replace(/\/+$/, '');
|
|
25
|
+
const lines = [
|
|
26
|
+
BEGIN_MARKER,
|
|
27
|
+
'# Maude design plugin runtime — gitignored even in linked mode (DDR-056).',
|
|
28
|
+
`${root}/_state/`, // binary CRDT logs (regenerable from hub)
|
|
29
|
+
`${root}/_server.json`,
|
|
30
|
+
`${root}/_server.log`,
|
|
31
|
+
`${root}/_active.json`,
|
|
32
|
+
`${root}/_sync.json`, // linked-mode offline/sync status (Task 8)
|
|
33
|
+
`${root}/_history/`,
|
|
34
|
+
`${root}/_canvas-state/`, // per-machine canvas undo/redo + scratch state
|
|
35
|
+
`${root}/_chat/`, // ACP transcripts (per-machine)
|
|
36
|
+
END_MARKER,
|
|
37
|
+
];
|
|
38
|
+
return `${lines.join('\n')}\n`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/** True when the file already contains a maude-managed block. */
|
|
42
|
+
export function hasBlock(contents) {
|
|
43
|
+
return contents.includes(BEGIN_MARKER) && contents.includes(END_MARKER);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Replace the existing maude block in `contents`, or append a fresh one.
|
|
48
|
+
* Pure — returns the new file text. Preserves everything outside the markers.
|
|
49
|
+
*/
|
|
50
|
+
export function applyBlock(contents, block) {
|
|
51
|
+
if (!hasBlock(contents)) {
|
|
52
|
+
// Append with a single blank-line separator (avoid leading newline on an
|
|
53
|
+
// empty file; avoid double-blank when the file already ends in a newline).
|
|
54
|
+
if (contents.length === 0) return block;
|
|
55
|
+
const sep = contents.endsWith('\n') ? '\n' : '\n\n';
|
|
56
|
+
return `${contents}${sep}${block}`;
|
|
57
|
+
}
|
|
58
|
+
const begin = contents.indexOf(BEGIN_MARKER);
|
|
59
|
+
const endIdx = contents.indexOf(END_MARKER);
|
|
60
|
+
const after = endIdx + END_MARKER.length;
|
|
61
|
+
// Consume one trailing newline after END so we don't accumulate blank lines.
|
|
62
|
+
const tail = contents.slice(after).replace(/^\n/, '');
|
|
63
|
+
const head = contents.slice(0, begin);
|
|
64
|
+
return `${head}${block}${tail ? `\n${tail}` : ''}`;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Write (or refresh) the maude block in `<repoRoot>/.gitignore`.
|
|
69
|
+
*
|
|
70
|
+
* @param {string} repoRoot
|
|
71
|
+
* @param {{ designRel?: string, dryRun?: boolean }} [opts]
|
|
72
|
+
* @returns {{ action: 'created' | 'updated' | 'unchanged', path: string }}
|
|
73
|
+
*/
|
|
74
|
+
export function writeGitignoreBlock(repoRoot, { designRel = '.design', dryRun = false } = {}) {
|
|
75
|
+
const path = resolve(repoRoot, '.gitignore');
|
|
76
|
+
const existed = existsSync(path);
|
|
77
|
+
const current = existed ? readFileSync(path, 'utf8') : '';
|
|
78
|
+
const block = buildBlock(designRel);
|
|
79
|
+
const next = applyBlock(current, block);
|
|
80
|
+
|
|
81
|
+
let action;
|
|
82
|
+
if (!existed) action = 'created';
|
|
83
|
+
else if (next === current) action = 'unchanged';
|
|
84
|
+
else action = 'updated';
|
|
85
|
+
|
|
86
|
+
if (!dryRun && next !== current) {
|
|
87
|
+
writeFileSync(path, next, 'utf8');
|
|
88
|
+
}
|
|
89
|
+
return { action, path };
|
|
90
|
+
}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
// gitignore-block helper tests — Phase 9 Task 9 (DDR-056).
|
|
2
|
+
|
|
3
|
+
import assert from 'node:assert/strict';
|
|
4
|
+
import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { join } from 'node:path';
|
|
7
|
+
import { test } from 'node:test';
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
BEGIN_MARKER,
|
|
11
|
+
END_MARKER,
|
|
12
|
+
applyBlock,
|
|
13
|
+
buildBlock,
|
|
14
|
+
hasBlock,
|
|
15
|
+
writeGitignoreBlock,
|
|
16
|
+
} from './gitignore-block.mjs';
|
|
17
|
+
|
|
18
|
+
function withRepo(fn) {
|
|
19
|
+
const dir = mkdtempSync(join(tmpdir(), 'maude-gitignore-'));
|
|
20
|
+
try {
|
|
21
|
+
return fn(dir);
|
|
22
|
+
} finally {
|
|
23
|
+
rmSync(dir, { recursive: true, force: true });
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
test('buildBlock includes the runtime paths and is marker-wrapped', () => {
|
|
28
|
+
const block = buildBlock('.design');
|
|
29
|
+
assert.ok(block.startsWith(BEGIN_MARKER));
|
|
30
|
+
assert.ok(block.trimEnd().endsWith(END_MARKER));
|
|
31
|
+
for (const p of [
|
|
32
|
+
'.design/_state/',
|
|
33
|
+
'.design/_server.json',
|
|
34
|
+
'.design/_active.json',
|
|
35
|
+
'.design/_sync.json',
|
|
36
|
+
'.design/_history/',
|
|
37
|
+
'.design/_chat/',
|
|
38
|
+
]) {
|
|
39
|
+
assert.ok(block.includes(p), `missing ${p}`);
|
|
40
|
+
}
|
|
41
|
+
// Committed artifacts must NOT be ignored.
|
|
42
|
+
assert.ok(!block.includes('.design/config.json'));
|
|
43
|
+
assert.ok(!/\.design\/\*\.html/.test(block));
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('buildBlock honors a custom design root', () => {
|
|
47
|
+
const block = buildBlock('design');
|
|
48
|
+
assert.ok(block.includes('design/_server.json'));
|
|
49
|
+
assert.ok(!block.includes('.design/_server.json'));
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test('writeGitignoreBlock creates .gitignore when absent', () => {
|
|
53
|
+
withRepo((dir) => {
|
|
54
|
+
const { action, path } = writeGitignoreBlock(dir);
|
|
55
|
+
assert.equal(action, 'created');
|
|
56
|
+
const text = readFileSync(path, 'utf8');
|
|
57
|
+
assert.ok(hasBlock(text));
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
test('writeGitignoreBlock is idempotent — second run is unchanged', () => {
|
|
62
|
+
withRepo((dir) => {
|
|
63
|
+
writeGitignoreBlock(dir);
|
|
64
|
+
const first = readFileSync(join(dir, '.gitignore'), 'utf8');
|
|
65
|
+
const { action } = writeGitignoreBlock(dir);
|
|
66
|
+
const second = readFileSync(join(dir, '.gitignore'), 'utf8');
|
|
67
|
+
assert.equal(action, 'unchanged');
|
|
68
|
+
assert.equal(first, second);
|
|
69
|
+
// Exactly one block — no duplication.
|
|
70
|
+
assert.equal(first.split(BEGIN_MARKER).length - 1, 1);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test('writeGitignoreBlock preserves user content outside the markers', () => {
|
|
75
|
+
withRepo((dir) => {
|
|
76
|
+
const userContent = 'node_modules/\n*.log\n.env\n';
|
|
77
|
+
writeFileSync(join(dir, '.gitignore'), userContent);
|
|
78
|
+
writeGitignoreBlock(dir);
|
|
79
|
+
const text = readFileSync(join(dir, '.gitignore'), 'utf8');
|
|
80
|
+
assert.ok(text.includes('node_modules/'));
|
|
81
|
+
assert.ok(text.includes('.env'));
|
|
82
|
+
assert.ok(hasBlock(text));
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
test('writeGitignoreBlock updates an existing block in place (no duplication)', () => {
|
|
87
|
+
withRepo((dir) => {
|
|
88
|
+
// Seed a stale block with a different design root.
|
|
89
|
+
const stale = `keep-me\n${buildBlock('olddesign')}trailing\n`;
|
|
90
|
+
writeFileSync(join(dir, '.gitignore'), stale);
|
|
91
|
+
const { action } = writeGitignoreBlock(dir, { designRel: '.design' });
|
|
92
|
+
assert.equal(action, 'updated');
|
|
93
|
+
const text = readFileSync(join(dir, '.gitignore'), 'utf8');
|
|
94
|
+
assert.equal(text.split(BEGIN_MARKER).length - 1, 1, 'still exactly one block');
|
|
95
|
+
assert.ok(text.includes('.design/_server.json'));
|
|
96
|
+
assert.ok(!text.includes('olddesign/_server.json'));
|
|
97
|
+
assert.ok(text.includes('keep-me'));
|
|
98
|
+
assert.ok(text.includes('trailing'));
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test('writeGitignoreBlock --dry-run does not touch disk', () => {
|
|
103
|
+
withRepo((dir) => {
|
|
104
|
+
const { action } = writeGitignoreBlock(dir, { dryRun: true });
|
|
105
|
+
assert.equal(action, 'created');
|
|
106
|
+
// File was not actually written.
|
|
107
|
+
assert.throws(() => readFileSync(join(dir, '.gitignore'), 'utf8'));
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test('applyBlock appends to a file lacking a trailing newline cleanly', () => {
|
|
112
|
+
const out = applyBlock('a\nb', buildBlock('.design'));
|
|
113
|
+
assert.ok(out.includes('a\nb'));
|
|
114
|
+
assert.ok(hasBlock(out));
|
|
115
|
+
// No run-together: marker starts on its own line.
|
|
116
|
+
assert.ok(out.includes(`\n${BEGIN_MARKER}`));
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test('hasBlock detects both markers', () => {
|
|
120
|
+
assert.ok(hasBlock(`x\n${BEGIN_MARKER}\ny\n${END_MARKER}\n`));
|
|
121
|
+
assert.ok(!hasBlock('just user content'));
|
|
122
|
+
assert.ok(!hasBlock(BEGIN_MARKER)); // begin without end
|
|
123
|
+
});
|
package/cli/lib/hubs-config.mjs
CHANGED
|
@@ -12,8 +12,15 @@
|
|
|
12
12
|
// "linkedAt": 1716800000000
|
|
13
13
|
// },
|
|
14
14
|
// ...
|
|
15
|
-
// }
|
|
15
|
+
// },
|
|
16
|
+
// "trusted": ["https://maude-hub-foo.fly.dev", ...]
|
|
16
17
|
// }
|
|
18
|
+
//
|
|
19
|
+
// `trusted` is the per-machine trust allowlist for non-loopback hubs (the
|
|
20
|
+
// `maude design link` confirmation, DDR-054 F2). It lives here — NOT in a
|
|
21
|
+
// committable repo file — on purpose: a committable allowlist would let an
|
|
22
|
+
// attacker pre-seed trust via a PR and bypass the link-time confirmation
|
|
23
|
+
// (trust laundering). Trust is a per-machine decision, like `~/.ssh/known_hosts`.
|
|
17
24
|
|
|
18
25
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
19
26
|
import { homedir } from 'node:os';
|
|
@@ -68,11 +75,20 @@ export function saveHubsConfig(config) {
|
|
|
68
75
|
}
|
|
69
76
|
}
|
|
70
77
|
|
|
71
|
-
/**
|
|
72
|
-
|
|
78
|
+
/**
|
|
79
|
+
* Upsert a hub entry. Same URL replaces the existing record. `extra` merges
|
|
80
|
+
* additional per-machine attestations (e.g. `adoptedAt` — the timestamp this
|
|
81
|
+
* machine pushed its local state up via `--adopt`, used by the sync runtime to
|
|
82
|
+
* avoid re-adopting; DDR-054 F4).
|
|
83
|
+
*
|
|
84
|
+
* @param {string} url
|
|
85
|
+
* @param {string} token
|
|
86
|
+
* @param {{ adoptedAt?: number }} [extra]
|
|
87
|
+
*/
|
|
88
|
+
export function addHub(url, token, extra = {}) {
|
|
73
89
|
const norm = normalizeUrl(url);
|
|
74
90
|
const cfg = loadHubsConfig();
|
|
75
|
-
cfg.hubs[norm] = { token, linkedAt: Date.now() };
|
|
91
|
+
cfg.hubs[norm] = { token, linkedAt: Date.now(), ...extra };
|
|
76
92
|
saveHubsConfig(cfg);
|
|
77
93
|
return cfg.hubs[norm];
|
|
78
94
|
}
|
|
@@ -98,6 +114,28 @@ export function getHub(url) {
|
|
|
98
114
|
return cfg.hubs[norm] ?? null;
|
|
99
115
|
}
|
|
100
116
|
|
|
117
|
+
/**
|
|
118
|
+
* Per-machine trust check for a non-loopback hub (DDR-054 F2). Trust is stored
|
|
119
|
+
* here, not in a committable repo file, so a malicious PR cannot pre-seed it.
|
|
120
|
+
*/
|
|
121
|
+
export function isHubTrusted(url) {
|
|
122
|
+
const norm = normalizeUrl(url);
|
|
123
|
+
const cfg = loadHubsConfig();
|
|
124
|
+
return Array.isArray(cfg.trusted) && cfg.trusted.includes(norm);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Record a hub as trusted on THIS machine. Idempotent. */
|
|
128
|
+
export function trustHub(url) {
|
|
129
|
+
const norm = normalizeUrl(url);
|
|
130
|
+
const cfg = loadHubsConfig();
|
|
131
|
+
if (!Array.isArray(cfg.trusted)) cfg.trusted = [];
|
|
132
|
+
if (!cfg.trusted.includes(norm)) {
|
|
133
|
+
cfg.trusted.push(norm);
|
|
134
|
+
saveHubsConfig(cfg);
|
|
135
|
+
}
|
|
136
|
+
return norm;
|
|
137
|
+
}
|
|
138
|
+
|
|
101
139
|
/**
|
|
102
140
|
* Normalize a hub URL so different spellings resolve to the same key:
|
|
103
141
|
* - Trim trailing slash.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// DDR-061 + DDR-062 regression guard.
|
|
2
|
+
//
|
|
3
|
+
// DDR-061: a plugin must NEVER invoke a `cli/lib/*.mjs` module by a relative
|
|
4
|
+
// path unconditionally: in a marketplace install the plugin is copied alone into
|
|
5
|
+
// `cache/<marketplace>/<plugin>/<version>/`, so the sibling `cli/` does not
|
|
6
|
+
// exist and the call crashes with MODULE_NOT_FOUND. The reachable contract is
|
|
7
|
+
// the on-PATH `maude` binary (a declared plugin dependency).
|
|
8
|
+
//
|
|
9
|
+
// So any plugin file that runs `node … cli/lib/<x>.mjs` is only allowed when the
|
|
10
|
+
// SAME file also offers a `command -v maude` fallback (the sibling-first /
|
|
11
|
+
// maude-fallback pattern). This test fails loudly if a new straggler lands.
|
|
12
|
+
//
|
|
13
|
+
// DDR-062: plugin MARKDOWN must reach the dev-server bash helpers through
|
|
14
|
+
// `maude design <verb>`, never `bash "$CLAUDE_PLUGIN_ROOT/dev-server/bin/<x>.sh"`.
|
|
15
|
+
// The marketplace never copies the dev-server beside a plugin, and a flow
|
|
16
|
+
// command's $CLAUDE_PLUGIN_ROOT points at plugins/flow (no dev-server/ at all).
|
|
17
|
+
// Prose mentions of the path are fine; INVOCATIONS (bash/sh/exec/$( prefixed)
|
|
18
|
+
// are banned.
|
|
19
|
+
|
|
20
|
+
import assert from 'node:assert/strict';
|
|
21
|
+
import { execSync } from 'node:child_process';
|
|
22
|
+
import { readFileSync } from 'node:fs';
|
|
23
|
+
import { test } from 'node:test';
|
|
24
|
+
|
|
25
|
+
test('no plugin runs `node cli/lib/*.mjs` without an on-PATH maude fallback', () => {
|
|
26
|
+
const out = execSync(
|
|
27
|
+
String.raw`grep -rlE 'node[^\n]*cli/lib/[a-z0-9_-]+\.mjs' plugins/ || true`,
|
|
28
|
+
{
|
|
29
|
+
encoding: 'utf8',
|
|
30
|
+
}
|
|
31
|
+
);
|
|
32
|
+
const files = out.trim().split('\n').filter(Boolean);
|
|
33
|
+
const offenders = files.filter((f) => !/command -v maude/.test(readFileSync(f, 'utf8')));
|
|
34
|
+
assert.deepEqual(
|
|
35
|
+
offenders,
|
|
36
|
+
[],
|
|
37
|
+
`These plugin files invoke a relative cli/lib module with no \`command -v maude\` fallback — they will crash in a marketplace install (DDR-061). Route through \`maude <subcommand>\` or guard with a sibling-exists check + maude fallback:\n ${offenders.join('\n ')}`
|
|
38
|
+
);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('no plugin markdown invokes a $CLAUDE_PLUGIN_ROOT/dev-server/bin/*.sh helper (DDR-062)', () => {
|
|
42
|
+
// INVOCATION = the bin path preceded by bash / sh / exec, or inside a $(…)
|
|
43
|
+
// command substitution. Prose mentions of the path (backtick-wrapped, no verb)
|
|
44
|
+
// are exempt — they don't run anything. Matches both the $CLAUDE_PLUGIN_ROOT
|
|
45
|
+
// form and the repo-relative `plugins/<x>/dev-server/bin/` form.
|
|
46
|
+
const pattern = String.raw`(bash|sh|exec) +"?(\$\{?CLAUDE_PLUGIN_ROOT\}?|plugins/[a-z]+)/dev-server/bin/[A-Za-z0-9_-]+\.sh|\$\("?\$?\{?CLAUDE_PLUGIN_ROOT\}?/dev-server/bin/[A-Za-z0-9_-]+\.sh`;
|
|
47
|
+
const out = execSync(`grep -rnE '${pattern}' plugins --include='*.md' || true`, {
|
|
48
|
+
encoding: 'utf8',
|
|
49
|
+
});
|
|
50
|
+
const hits = out.trim().split('\n').filter(Boolean);
|
|
51
|
+
assert.deepEqual(
|
|
52
|
+
hits,
|
|
53
|
+
[],
|
|
54
|
+
`These plugin markdown lines INVOKE a dev-server bin helper directly — they break in marketplace installs (the dev-server is never copied beside a plugin) and in flow commands (whose $CLAUDE_PLUGIN_ROOT has no dev-server/). Route through \`maude design <verb>\` instead (DDR-062):\n ${hits.join('\n ')}`
|
|
55
|
+
);
|
|
56
|
+
});
|
package/cli/lib/preflight.mjs
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
// because every consumer wants the same table format.
|
|
13
13
|
|
|
14
14
|
import { spawnSync } from 'node:child_process';
|
|
15
|
-
import { readFile } from 'node:fs/promises';
|
|
15
|
+
import { readFile, writeFile } from 'node:fs/promises';
|
|
16
16
|
import { resolve } from 'node:path';
|
|
17
17
|
|
|
18
18
|
const SEM_RE = /(\d+)\.(\d+)\.(\d+)/;
|
|
@@ -172,10 +172,11 @@ function colorFor(g) {
|
|
|
172
172
|
// ─── CLI entry — `node cli/lib/preflight.mjs --plugin design --json` ──────
|
|
173
173
|
|
|
174
174
|
function parseFlags(args) {
|
|
175
|
-
const out = { plugin: null, mode: 'text' };
|
|
175
|
+
const out = { plugin: null, mode: 'text', cache: null };
|
|
176
176
|
for (let i = 0; i < args.length; i++) {
|
|
177
177
|
const a = args[i];
|
|
178
178
|
if (a === '--plugin') out.plugin = args[++i];
|
|
179
|
+
else if (a === '--cache') out.cache = args[++i];
|
|
179
180
|
else if (a === '--json') out.mode = 'json';
|
|
180
181
|
else if (a === '--shell-export') out.mode = 'shell-export';
|
|
181
182
|
else if (a === '--quiet') out.mode = 'quiet';
|
|
@@ -184,17 +185,46 @@ function parseFlags(args) {
|
|
|
184
185
|
return out;
|
|
185
186
|
}
|
|
186
187
|
|
|
187
|
-
|
|
188
|
-
|
|
188
|
+
// Cross-command short-circuit cache (Phase A, Task A15). Written after a
|
|
189
|
+
// successful check so sibling commands within the freshness window skip the
|
|
190
|
+
// whole preflight. Path is caller-supplied (design → <designRoot>/_preflight.json,
|
|
191
|
+
// flow → .ai/state/_preflight.json) because this lib runs with cwd = the maude
|
|
192
|
+
// package root when shelled from preflight.sh, not the target repo.
|
|
193
|
+
async function writeCache(cachePath, env) {
|
|
194
|
+
const payload = {
|
|
195
|
+
checked: new Date().toISOString(),
|
|
196
|
+
plugin: env.plugin,
|
|
197
|
+
all_hard_pass: env.summary.allHardPass,
|
|
198
|
+
soft_warnings: env.results
|
|
199
|
+
.filter((r) => r.hardness === 'soft' && r.status === 'missing')
|
|
200
|
+
.map((r) => r.id),
|
|
201
|
+
};
|
|
202
|
+
try {
|
|
203
|
+
await writeFile(resolve(cachePath), `${JSON.stringify(payload, null, 2)}\n`);
|
|
204
|
+
} catch {
|
|
205
|
+
// Cache is a best-effort optimization; a write failure (read-only FS,
|
|
206
|
+
// missing parent dir) must never break the preflight itself.
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function manifestPathForPlugin(pluginName, pkgRoot = process.cwd()) {
|
|
211
|
+
return resolve(pkgRoot, 'plugins', pluginName, 'dependencies.json');
|
|
189
212
|
}
|
|
190
213
|
|
|
191
|
-
|
|
192
|
-
|
|
214
|
+
// Shared runnable for both the `maude preflight` command and the direct
|
|
215
|
+
// `node cli/lib/preflight.mjs` entry. `pkgRoot` is where `plugins/<x>/
|
|
216
|
+
// dependencies.json` lives (the maude package root) — resolved independently
|
|
217
|
+
// of the caller's cwd, so the command works from any target repo. (DDR-061:
|
|
218
|
+
// the marketplace install has no sibling cli/, so callers reach this through
|
|
219
|
+
// the on-PATH `maude` binary, which passes its own package root as pkgRoot.)
|
|
220
|
+
export async function runPreflight({ args, pkgRoot = process.cwd() }) {
|
|
221
|
+
const flags = parseFlags(args);
|
|
193
222
|
if (!flags.plugin) {
|
|
194
|
-
process.stderr.write('preflight
|
|
223
|
+
process.stderr.write('preflight: pass --plugin <design|flow>\n');
|
|
195
224
|
process.exit(2);
|
|
196
225
|
}
|
|
197
|
-
const env = await checkAll(manifestPathForPlugin(flags.plugin));
|
|
226
|
+
const env = await checkAll(manifestPathForPlugin(flags.plugin, pkgRoot));
|
|
227
|
+
if (flags.cache) await writeCache(flags.cache, env);
|
|
198
228
|
if (flags.mode === 'json') {
|
|
199
229
|
process.stdout.write(`${JSON.stringify(env, null, 2)}\n`);
|
|
200
230
|
} else if (flags.mode === 'shell-export') {
|
|
@@ -223,9 +253,10 @@ async function main(argv) {
|
|
|
223
253
|
process.exit(env.summary.allHardPass ? 0 : 1);
|
|
224
254
|
}
|
|
225
255
|
|
|
226
|
-
// Entry guard — only run
|
|
256
|
+
// Entry guard — only run when invoked directly (`node cli/lib/preflight.mjs`).
|
|
257
|
+
// The package root is the cwd here (preflight.sh `cd`s to it before exec).
|
|
227
258
|
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
228
|
-
|
|
259
|
+
runPreflight({ args: process.argv.slice(2), pkgRoot: process.cwd() }).catch((err) => {
|
|
229
260
|
process.stderr.write(`preflight: ${err.message}\n`);
|
|
230
261
|
process.exit(1);
|
|
231
262
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@1agh/maude",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.24.0",
|
|
4
4
|
"description": "Marketplace of Claude Code plugins by Michal Dovrtěl: `design` (canvas-first design iteration) + `flow` (generic agentic workflow loop with .ai second brain). Ships the `maude` CLI (with `mdcc` legacy alias) to scaffold workspace, run the design dev server, and manage configs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -41,13 +41,13 @@
|
|
|
41
41
|
"prepublishOnly": "bash scripts/check-version-parity.sh && bash plugins/design/dev-server/bin/check-runtime-bundles.sh"
|
|
42
42
|
},
|
|
43
43
|
"optionalDependencies": {
|
|
44
|
-
"@1agh/maude-darwin-arm64": "0.
|
|
45
|
-
"@1agh/maude-darwin-x64": "0.
|
|
46
|
-
"@1agh/maude-linux-arm64": "0.
|
|
47
|
-
"@1agh/maude-linux-arm64-musl": "0.
|
|
48
|
-
"@1agh/maude-linux-x64": "0.
|
|
49
|
-
"@1agh/maude-linux-x64-musl": "0.
|
|
50
|
-
"@1agh/maude-win32-x64": "0.
|
|
44
|
+
"@1agh/maude-darwin-arm64": "0.24.0",
|
|
45
|
+
"@1agh/maude-darwin-x64": "0.24.0",
|
|
46
|
+
"@1agh/maude-linux-arm64": "0.24.0",
|
|
47
|
+
"@1agh/maude-linux-arm64-musl": "0.24.0",
|
|
48
|
+
"@1agh/maude-linux-x64": "0.24.0",
|
|
49
|
+
"@1agh/maude-linux-x64-musl": "0.24.0",
|
|
50
|
+
"@1agh/maude-win32-x64": "0.24.0"
|
|
51
51
|
},
|
|
52
52
|
"files": [
|
|
53
53
|
"cli",
|
|
@@ -41,8 +41,8 @@ const BANNER_CSS = `
|
|
|
41
41
|
align-items: center;
|
|
42
42
|
gap: 10px;
|
|
43
43
|
background: var(--maude-hud-accent-tint, oklch(92% 0.040 55));
|
|
44
|
-
color: var(--fg-0, #111);
|
|
45
|
-
border: 1px solid var(--border
|
|
44
|
+
color: var(--maude-chrome-fg-0, #111);
|
|
45
|
+
border: 1px solid var(--maude-chrome-border, rgba(0,0,0,0.16));
|
|
46
46
|
border-radius: var(--radius-md, 4px);
|
|
47
47
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.08);
|
|
48
48
|
font-family: var(--font-sans, system-ui, -apple-system, sans-serif);
|