@hegemonart/get-design-done 1.30.6 → 1.31.5
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/.claude-plugin/marketplace.json +6 -3
- package/.claude-plugin/plugin.json +5 -2
- package/CHANGELOG.md +105 -0
- package/NOTICE +224 -0
- package/README.md +22 -1
- package/SKILL.md +1 -0
- package/agents/design-authority-watcher.md +1 -1
- package/agents/perf-analyzer.md +2 -2
- package/bin/gdd-mcp +78 -0
- package/bin/gdd-sdk +34 -24
- package/bin/gdd-state-mcp +78 -0
- package/{README.de.md → docs/i18n/README.de.md} +1 -1
- package/{README.fr.md → docs/i18n/README.fr.md} +1 -1
- package/{README.it.md → docs/i18n/README.it.md} +1 -1
- package/{README.ja.md → docs/i18n/README.ja.md} +1 -1
- package/{README.ko.md → docs/i18n/README.ko.md} +1 -1
- package/{README.zh-CN.md → docs/i18n/README.zh-CN.md} +1 -1
- package/hooks/_hook-emit.js +1 -1
- package/hooks/budget-enforcer.ts +5 -5
- package/hooks/context-exhaustion.ts +2 -2
- package/hooks/gdd-precompact-snapshot.js +3 -3
- package/hooks/gdd-read-injection-scanner.ts +2 -2
- package/hooks/gdd-sessionstart-recap.js +1 -1
- package/hooks/gdd-turn-closeout.js +1 -1
- package/package.json +24 -10
- package/recipes/.gitkeep +0 -0
- package/reference/schemas/recipe.schema.json +33 -0
- package/scripts/cli/gdd-events.mjs +5 -5
- package/scripts/lib/cache/gdd-cache-manager.cjs +1 -1
- package/scripts/lib/cli/index.ts +22 -160
- package/scripts/lib/connection-probe/index.cjs +1 -1
- package/scripts/lib/discuss-parallel-runner/aggregator.ts +1 -1
- package/scripts/lib/discuss-parallel-runner/index.ts +1 -1
- package/scripts/lib/error-classifier.cjs +24 -227
- package/scripts/lib/event-stream/index.ts +25 -193
- package/scripts/lib/figma-extract/digest.cjs +430 -0
- package/scripts/lib/figma-extract/parse-url.cjs +87 -0
- package/scripts/lib/figma-extract/payload-schema.json +108 -0
- package/scripts/lib/figma-extract/pull.cjs +394 -0
- package/scripts/lib/figma-extract/receiver.cjs +273 -0
- package/scripts/lib/figma-extract/render-md.cjs +143 -0
- package/scripts/lib/figma-extract/styles-resolver.cjs +147 -0
- package/scripts/lib/figma-extract/walk.cjs +100 -0
- package/scripts/lib/gdd-errors/index.ts +24 -213
- package/scripts/lib/gdd-state/index.ts +23 -161
- package/scripts/lib/health-mirror/index.cjs +88 -1
- package/scripts/lib/iteration-budget.cjs +23 -199
- package/scripts/lib/jittered-backoff.cjs +24 -107
- package/scripts/lib/lockfile.cjs +23 -195
- package/scripts/lib/logger/index.ts +1 -1
- package/scripts/lib/parallelism-engine/concurrency-tuner.cjs +1 -1
- package/scripts/lib/perf-analyzer/index.cjs +1 -1
- package/scripts/lib/pipeline-runner/index.ts +4 -4
- package/scripts/lib/pipeline-runner/state-machine.ts +1 -1
- package/scripts/lib/prompt-dedup/index.cjs +1 -1
- package/scripts/lib/rate-guard.cjs +2 -2
- package/scripts/lib/recipe-loader.cjs +142 -0
- package/scripts/lib/session-runner/errors.ts +3 -3
- package/scripts/lib/session-runner/index.ts +3 -3
- package/scripts/lib/session-runner/transcript.ts +1 -1
- package/scripts/lib/tool-scoping/index.ts +1 -1
- package/scripts/mcp-servers/gdd-mcp/server.ts +29 -311
- package/scripts/mcp-servers/gdd-state/server.ts +28 -282
- package/sdk/README.md +45 -0
- package/{scripts/lib → sdk}/cli/commands/audit.ts +3 -3
- package/{scripts/lib → sdk}/cli/commands/init.ts +3 -3
- package/{scripts/lib → sdk}/cli/commands/query.ts +4 -4
- package/{scripts/lib → sdk}/cli/commands/run.ts +5 -5
- package/{scripts/lib → sdk}/cli/commands/stage.ts +5 -5
- package/sdk/cli/index.js +8091 -0
- package/sdk/cli/index.ts +172 -0
- package/{scripts/lib → sdk}/cli/parse-args.ts +2 -2
- package/{scripts/lib/gdd-errors → sdk/errors}/classification.ts +1 -1
- package/sdk/errors/index.ts +218 -0
- package/{scripts/lib → sdk}/event-stream/emitter.ts +1 -1
- package/sdk/event-stream/index.ts +197 -0
- package/{scripts/lib → sdk}/event-stream/reader.ts +1 -1
- package/{scripts/lib → sdk}/event-stream/types.ts +2 -2
- package/{scripts/lib → sdk}/event-stream/writer.ts +1 -1
- package/sdk/index.ts +19 -0
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/README.md +3 -3
- package/sdk/mcp/gdd-mcp/server.js +1924 -0
- package/sdk/mcp/gdd-mcp/server.ts +325 -0
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_cycle_recap.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_decisions_list.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_events_tail.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_health.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_intel_get.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_learnings_digest.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phase_current.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_phases_list.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_plans_list.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_reflections_latest.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_status.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/gdd_telemetry_query.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/index.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/tools/shared.ts +3 -3
- package/sdk/mcp/gdd-state/server.js +2790 -0
- package/sdk/mcp/gdd-state/server.ts +294 -0
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_blocker.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_decision.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/add_must_have.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/checkpoint.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/frontmatter_update.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/get.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/index.ts +1 -1
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/probe_connections.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/resolve_blocker.ts +3 -3
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/set_status.ts +2 -2
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/shared.ts +8 -8
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/transition_stage.ts +4 -4
- package/{scripts/mcp-servers → sdk/mcp}/gdd-state/tools/update_progress.ts +2 -2
- package/sdk/primitives/error-classifier.cjs +232 -0
- package/sdk/primitives/iteration-budget.cjs +205 -0
- package/sdk/primitives/jittered-backoff.cjs +112 -0
- package/sdk/primitives/lockfile.cjs +201 -0
- package/{scripts/lib/gdd-state → sdk/state}/gates.ts +1 -1
- package/sdk/state/index.ts +167 -0
- package/{scripts/lib/gdd-state → sdk/state}/lockfile.ts +1 -1
- package/{scripts/lib/gdd-state → sdk/state}/mutator.ts +1 -1
- package/{scripts/lib/gdd-state → sdk/state}/parser.ts +1 -1
- package/{scripts/lib/gdd-state → sdk/state}/types.ts +4 -4
- package/skills/figma-extract/SKILL.md +64 -0
- package/skills/health/SKILL.md +10 -0
- package/skills/quality-gate/SKILL.md +2 -2
- package/scripts/aggregate-agent-metrics.ts +0 -282
- package/scripts/bootstrap-manifest.txt +0 -3
- package/scripts/bootstrap.sh +0 -80
- package/scripts/build-distribution-bundles.cjs +0 -549
- package/scripts/build-intel.cjs +0 -486
- package/scripts/codegen-schema-types.ts +0 -149
- package/scripts/detect-stale-refs.cjs +0 -107
- package/scripts/e2e/run-headless.ts +0 -514
- package/scripts/extract-changelog-section.cjs +0 -58
- package/scripts/gsd-cleanup-incubator.cjs +0 -367
- package/scripts/injection-patterns.cjs +0 -58
- package/scripts/lint-agentskills-spec.cjs +0 -457
- package/scripts/release-smoke-test.cjs +0 -200
- package/scripts/rollback-release.sh +0 -42
- package/scripts/run-injection-scanner-ci.cjs +0 -83
- package/scripts/tests/test-authority-rejected-kinds.sh +0 -58
- package/scripts/tests/test-authority-watcher-diff.sh +0 -113
- package/scripts/tests/test-motion-provenance.sh +0 -64
- package/scripts/validate-frontmatter.ts +0 -409
- package/scripts/validate-incubator-scope.cjs +0 -133
- package/scripts/validate-schemas.ts +0 -401
- package/scripts/validate-skill-length.cjs +0 -283
- package/scripts/verify-version-sync.cjs +0 -30
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_cycle_recap.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_decisions_list.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_events_tail.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_health.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_intel_get.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_learnings_digest.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phase_current.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_phases_list.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_plans_list.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_reflections_latest.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_status.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-mcp/schemas/gdd_telemetry_query.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_blocker.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_decision.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/add_must_have.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/checkpoint.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/frontmatter_update.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/get.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/probe_connections.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/resolve_blocker.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/set_status.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/transition_stage.schema.json +0 -0
- /package/{scripts/mcp-servers → sdk/mcp}/gdd-state/schemas/update_progress.schema.json +0 -0
- /package/{scripts/lib → sdk/primitives}/error-classifier.d.cts +0 -0
- /package/{scripts/lib → sdk/primitives}/iteration-budget.d.cts +0 -0
- /package/{scripts/lib → sdk/primitives}/jittered-backoff.d.cts +0 -0
- /package/{scripts/lib → sdk/primitives}/lockfile.d.cts +0 -0
|
@@ -1,197 +1,29 @@
|
|
|
1
|
-
// scripts/lib/event-stream/index.ts —
|
|
2
|
-
// telemetry stream (Plan 20-06, SDK-08).
|
|
1
|
+
// scripts/lib/event-stream/index.ts — GDD-DEPRECATION-SHIM (Plan 31-5-06, SDK-05, D-02).
|
|
3
2
|
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
3
|
+
// Thin deprecation shim. The real implementation moved to
|
|
4
|
+
// sdk/event-stream/index.ts in Plan 31-5-04 (SDK consolidation). This file
|
|
5
|
+
// is re-created at the OLD path so undocumented EXTERNAL importers (anyone
|
|
6
|
+
// who reached into node_modules/@hegemonart/get-design-done/scripts/lib/
|
|
7
|
+
// event-stream/index.ts directly) keep working for one minor grace window.
|
|
8
8
|
//
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
12
|
-
//
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
//
|
|
16
|
-
//
|
|
17
|
-
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
BaseEvent,
|
|
28
|
-
EventMeta,
|
|
29
|
-
KnownEvent,
|
|
30
|
-
StateMutationEvent,
|
|
31
|
-
StateTransitionEvent,
|
|
32
|
-
StageEnteredEvent,
|
|
33
|
-
StageExitedEvent,
|
|
34
|
-
HookFiredEvent,
|
|
35
|
-
ErrorEvent,
|
|
36
|
-
WaveStartedEvent,
|
|
37
|
-
WaveCompletedEvent,
|
|
38
|
-
BlockerAddedEvent,
|
|
39
|
-
DecisionAddedEvent,
|
|
40
|
-
MustHaveAddedEvent,
|
|
41
|
-
ParallelismVerdictEvent,
|
|
42
|
-
CostUpdateEvent,
|
|
43
|
-
RateLimitEvent,
|
|
44
|
-
ApiRetryEvent,
|
|
45
|
-
CompactBoundaryEvent,
|
|
46
|
-
McpProbeEvent,
|
|
47
|
-
ReflectionProposedEvent,
|
|
48
|
-
ConnectionStatusChangeEvent,
|
|
49
|
-
ToolCallStartedEvent,
|
|
50
|
-
ToolCallCompletedEvent,
|
|
51
|
-
AgentSpawnEvent,
|
|
52
|
-
AgentOutcomeEvent,
|
|
53
|
-
// Phase 27 / Plan 27-08 — peer-CLI delegation events (D-09).
|
|
54
|
-
RuntimeRole,
|
|
55
|
-
PeerCallStartedEvent,
|
|
56
|
-
PeerCallCompleteEvent,
|
|
57
|
-
PeerCallFailedEvent,
|
|
58
|
-
} from './types.ts';
|
|
59
|
-
export {
|
|
60
|
-
KNOWN_EVENT_TYPES,
|
|
61
|
-
// Phase 27 / Plan 27-08 — symbolic constants for peer-CLI event names.
|
|
62
|
-
PEER_CALL_STARTED,
|
|
63
|
-
PEER_CALL_COMPLETE,
|
|
64
|
-
PEER_CALL_FAILED,
|
|
65
|
-
PEER_CALL_EVENT_TYPES,
|
|
66
|
-
DEFAULT_RUNTIME_ROLE,
|
|
67
|
-
} from './types.ts';
|
|
68
|
-
export { EventBus } from './emitter.ts';
|
|
69
|
-
export type { EventHandler, Unsubscribe } from './emitter.ts';
|
|
70
|
-
export { EventWriter, DEFAULT_EVENTS_PATH, DEFAULT_MAX_LINE_BYTES } from './writer.ts';
|
|
71
|
-
export type { WriterOptions } from './writer.ts';
|
|
72
|
-
export { readEvents, aggregate } from './reader.ts';
|
|
73
|
-
export type { ReadEventsOptions, AggregateResult } from './reader.ts';
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Lazily-constructed module-level singletons. `getWriter()` honors the
|
|
77
|
-
* first `opts` it receives; subsequent calls with different options are
|
|
78
|
-
* ignored. Tests that need to vary options across runs should call
|
|
79
|
-
* {@link reset} between runs.
|
|
80
|
-
*/
|
|
81
|
-
let defaultWriter: EventWriter | null = null;
|
|
82
|
-
let defaultBus: EventBus | null = null;
|
|
83
|
-
/**
|
|
84
|
-
* Cached host name. `os.hostname()` is cheap but not free (syscall on
|
|
85
|
-
* some platforms) and we stamp it onto every event; compute once.
|
|
86
|
-
*/
|
|
87
|
-
let cachedHost: string | null = null;
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Return the module-level default writer, constructing it on first
|
|
91
|
-
* call. Passing `opts` on subsequent calls is a no-op (the first
|
|
92
|
-
* caller wins); that matches the "single shared file per process"
|
|
93
|
-
* intent.
|
|
94
|
-
*/
|
|
95
|
-
export function getWriter(opts?: WriterOptions): EventWriter {
|
|
96
|
-
if (defaultWriter === null) {
|
|
97
|
-
// Honor GDD_EVENTS_PATH env var as the first-choice default path
|
|
98
|
-
// when the caller doesn't pass an explicit `opts.path`. Lets test
|
|
99
|
-
// harnesses and Plan 21-11's E2E subprocess steer the on-disk
|
|
100
|
-
// stream into a fixture-specific directory without chdir'ing the
|
|
101
|
-
// entire process. Explicit `opts.path` always wins.
|
|
102
|
-
const envPath: string | undefined = process.env['GDD_EVENTS_PATH'];
|
|
103
|
-
const finalOpts: WriterOptions =
|
|
104
|
-
opts?.path === undefined && envPath !== undefined && envPath.length > 0
|
|
105
|
-
? { ...(opts ?? {}), path: envPath }
|
|
106
|
-
: (opts ?? {});
|
|
107
|
-
defaultWriter = new EventWriter(finalOpts);
|
|
108
|
-
}
|
|
109
|
-
return defaultWriter;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
/** Return the module-level default bus, constructing it on first call. */
|
|
113
|
-
export function getBus(): EventBus {
|
|
114
|
-
if (defaultBus === null) {
|
|
115
|
-
defaultBus = new EventBus();
|
|
116
|
-
}
|
|
117
|
-
return defaultBus;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* Persist `ev` to the on-disk JSONL stream AND broadcast it to the
|
|
122
|
-
* in-process bus. This is the normal emission path for every Phase 20+
|
|
123
|
-
* event producer.
|
|
124
|
-
*
|
|
125
|
-
* Ordering:
|
|
126
|
-
* 1. Stamp `_meta` (pid/host/source) if the caller didn't supply it.
|
|
127
|
-
* 2. Persist via `getWriter().append(ev)` — sync, never throws.
|
|
128
|
-
* 3. Broadcast via `getBus().emit(ev.type, ev)` AND `emit('*', ev)`
|
|
129
|
-
* so typed subscribers and `subscribeAll` observers both see it.
|
|
130
|
-
*
|
|
131
|
-
* Bus emission can still throw if a subscriber handler throws; we
|
|
132
|
-
* intentionally surface that rather than silently swallowing, since a
|
|
133
|
-
* failing handler is a programming bug, not an expected runtime
|
|
134
|
-
* condition. Plan 20-13's hooks wrap their handler bodies defensively
|
|
135
|
-
* for this reason.
|
|
136
|
-
*/
|
|
137
|
-
export function appendEvent(ev: BaseEvent): void {
|
|
138
|
-
// Stamp writer-injected metadata if absent. We don't clone the full
|
|
139
|
-
// event — callers typically build it fresh per emission — but we do
|
|
140
|
-
// need to ensure `_meta` is present by the time we persist.
|
|
141
|
-
if (ev._meta === undefined) {
|
|
142
|
-
if (cachedHost === null) {
|
|
143
|
-
try {
|
|
144
|
-
cachedHost = hostname();
|
|
145
|
-
} catch {
|
|
146
|
-
cachedHost = 'unknown';
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
const meta: EventMeta = {
|
|
150
|
-
pid: process.pid,
|
|
151
|
-
host: cachedHost,
|
|
152
|
-
source: 'event-stream',
|
|
153
|
-
};
|
|
154
|
-
ev._meta = meta;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
// Persist first. Bus emission is synchronous; if a subscriber throws
|
|
158
|
-
// after we've persisted, the durable record is already safe.
|
|
159
|
-
getWriter().append(ev);
|
|
160
|
-
|
|
161
|
-
const bus = getBus();
|
|
162
|
-
bus.emit(ev.type, ev);
|
|
163
|
-
bus.emit('*', ev);
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Reset module-level singletons. Intended for tests that want a fresh
|
|
168
|
-
* writer (e.g. pointed at a new temp directory) or a fresh bus (e.g.
|
|
169
|
-
* to assert isolation between test cases).
|
|
170
|
-
*
|
|
171
|
-
* Safe to call from production code but the intended caller is a test.
|
|
172
|
-
* `appendEvent()` will lazily reconstruct both singletons on the next
|
|
173
|
-
* emission.
|
|
174
|
-
*/
|
|
175
|
-
export function reset(): void {
|
|
176
|
-
if (defaultBus !== null) {
|
|
177
|
-
defaultBus.removeAllListeners();
|
|
178
|
-
}
|
|
179
|
-
defaultWriter = null;
|
|
180
|
-
defaultBus = null;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
// Re-export `subscribe`/`subscribeAll` convenience: some callers only
|
|
184
|
-
// need to subscribe, not emit, and `getBus().subscribe(…)` reads fine
|
|
185
|
-
// but the shorter form keeps consumer code terse.
|
|
186
|
-
/** Convenience: subscribe to one event type on the default bus. */
|
|
187
|
-
export function subscribe<T extends BaseEvent = BaseEvent>(
|
|
188
|
-
type: T['type'],
|
|
189
|
-
handler: EventHandler<T>,
|
|
190
|
-
): Unsubscribe {
|
|
191
|
-
return getBus().subscribe<T>(type, handler);
|
|
9
|
+
// REMOVED IN v1.33.0 (D-02). Grace window: 1.31.5 ships with shims →
|
|
10
|
+
// 1.32.0 still has them → 1.33.0 removes them. Internal callers already use
|
|
11
|
+
// the sdk/ path (Plan 31-5-04/05) — this shim is external-only; 31-5-10's
|
|
12
|
+
// no-stale-internal-refs guard excludes files carrying the
|
|
13
|
+
// GDD-DEPRECATION-SHIM marker above.
|
|
14
|
+
//
|
|
15
|
+
// Runs under --experimental-strip-types, so `export *` re-export is
|
|
16
|
+
// strip-types-clean.
|
|
17
|
+
|
|
18
|
+
import { emitWarning } from 'node:process';
|
|
19
|
+
|
|
20
|
+
let warned = false;
|
|
21
|
+
if (!warned) {
|
|
22
|
+
warned = true;
|
|
23
|
+
emitWarning(
|
|
24
|
+
'scripts/lib/event-stream/index.ts is deprecated; import sdk/event-stream instead. Removed in v1.33.0.',
|
|
25
|
+
'DeprecationWarning',
|
|
26
|
+
);
|
|
192
27
|
}
|
|
193
28
|
|
|
194
|
-
|
|
195
|
-
export function subscribeAll(handler: EventHandler<BaseEvent>): Unsubscribe {
|
|
196
|
-
return getBus().subscribeAll(handler);
|
|
197
|
-
}
|
|
29
|
+
export * from '../../../sdk/event-stream/index.ts';
|
|
@@ -0,0 +1,430 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
/**
|
|
3
|
+
* Plan 31-02 — productionized from spike 001 digest.mjs (orchestration + token extraction).
|
|
4
|
+
*
|
|
5
|
+
* DIGEST stage of the two-stage pipeline (decision D-01: extract → digest stay
|
|
6
|
+
* separated). This module reads ONLY the raw/ cache that pull.cjs (31-01) wrote;
|
|
7
|
+
* it performs ZERO network calls, so it can re-run against an existing cache
|
|
8
|
+
* without re-pulling (idempotent / off-line).
|
|
9
|
+
*
|
|
10
|
+
* Three-path token assembly (decision D-04):
|
|
11
|
+
* Path A — Variables API body (rawDir/variables.json without the plugin marker)
|
|
12
|
+
* Path B — styles resolver (rawDir/styles.json) — pluggable seam; 31-03 ships
|
|
13
|
+
* the real two-step /styles + /nodes?ids= resolver
|
|
14
|
+
* Path C — plugin sync (rawDir/variables.json WITH the receiver marker, written
|
|
15
|
+
* by 31-06's localhost receiver)
|
|
16
|
+
* Resolution priority on name collision: Variables > plugin sync > styles.
|
|
17
|
+
* The --prefer-styles escape inverts the chain to prefer styles.
|
|
18
|
+
*
|
|
19
|
+
* Pure CommonJS, no external deps, no network.
|
|
20
|
+
*
|
|
21
|
+
* Per-component slicing (decision D-08):
|
|
22
|
+
* digest({..., component}) — when `component` is a name or glob (e.g.
|
|
23
|
+
* 'Sample/Button', 'Sample/*', 'Form/Butt?'), the digest renders ONLY
|
|
24
|
+
* the matching component(s) — a ~500-token slice instead of the full
|
|
25
|
+
* ~16K digest. The filter is strictly ADDITIVE: omitting `component`
|
|
26
|
+
* reproduces 31-02's full-digest behavior unchanged. Glob support is a
|
|
27
|
+
* few lines of in-file string work (no external glob dependency).
|
|
28
|
+
*
|
|
29
|
+
* Exports:
|
|
30
|
+
* digest(opts) — async orchestrator (reads raw/, writes digest/)
|
|
31
|
+
* assembleTokens(opts) — pure three-path merge by priority
|
|
32
|
+
* DEFAULT_TOKEN_PRIORITY — ['variables','plugin','styles'] (D-04)
|
|
33
|
+
* globToRegExp(pattern) — minimal glob→RegExp (D-08 component filter)
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
const fs = require('node:fs/promises');
|
|
37
|
+
const path = require('node:path');
|
|
38
|
+
const { collectComponents } = require('./walk.cjs');
|
|
39
|
+
const { renderDesignMd } = require('./render-md.cjs');
|
|
40
|
+
|
|
41
|
+
// D-04: Variables > plugin sync > styles.
|
|
42
|
+
const DEFAULT_TOKEN_PRIORITY = ['variables', 'plugin', 'styles'];
|
|
43
|
+
|
|
44
|
+
// Receiver-written payload marker (31-06 contract). A rawDir/variables.json that
|
|
45
|
+
// carries this top-level field is the plugin's Path-C payload, NOT the Figma
|
|
46
|
+
// Variables API body.
|
|
47
|
+
const PLUGIN_PAYLOAD_MARKER = 'gdd-plugin';
|
|
48
|
+
|
|
49
|
+
// ── component filter (D-08) ────────────────────────────────────────────────────
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Translate a minimal glob pattern into an anchored, case-sensitive RegExp.
|
|
53
|
+
*
|
|
54
|
+
* Supported wildcards (the only two the acceptance criterion needs):
|
|
55
|
+
* `*` → `.*` (zero or more of any char)
|
|
56
|
+
* `?` → `.` (exactly one char)
|
|
57
|
+
* Every other character — including regex metacharacters like `.`/`/`/`(`/`+`
|
|
58
|
+
* and the Figma `Sample/Path/Name` separators — is treated LITERALLY. We escape
|
|
59
|
+
* the whole pattern first, then re-activate the escaped `\*`/`\?` placeholders,
|
|
60
|
+
* so e.g. the `.` in 'Sample.Icon' is literal and does NOT over-match 'Sample/Icon'.
|
|
61
|
+
*
|
|
62
|
+
* Matching is exact (anchored `^...$`) and case-sensitive — Figma component
|
|
63
|
+
* names are case-significant, and an exact name with no wildcard must match only
|
|
64
|
+
* that one component.
|
|
65
|
+
*
|
|
66
|
+
* @param {string} pattern a component name or glob (e.g. 'Button*', 'Form/*')
|
|
67
|
+
* @returns {RegExp}
|
|
68
|
+
*/
|
|
69
|
+
function globToRegExp(pattern) {
|
|
70
|
+
// Escape ALL regex metacharacters (incl. * and ? for now).
|
|
71
|
+
const escaped = String(pattern).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
72
|
+
// Re-activate the wildcards: escaped '\*' → '.*', escaped '\?' → '.'.
|
|
73
|
+
const body = escaped.replace(/\\\*/g, '.*').replace(/\\\?/g, '.');
|
|
74
|
+
return new RegExp(`^${body}$`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Collect the set of token names that are RELEVANT to a set of components, so a
|
|
79
|
+
* slice can carry just those tokens instead of the full ~hundreds-of-tokens
|
|
80
|
+
* catalog (which would blow past the ~500-token budget). Relevance = a token
|
|
81
|
+
* whose name is referenced by a component's prop default/options, variant name,
|
|
82
|
+
* or the component name itself. When nothing is determinable we return an empty
|
|
83
|
+
* set and the slice renders component shape only — keeping the slice bounded
|
|
84
|
+
* regardless of catalog size (D-08 size guarantee).
|
|
85
|
+
*
|
|
86
|
+
* @param {Array} matched matched component entries (from collectComponents)
|
|
87
|
+
* @param {Array} tokens the full assembled token list
|
|
88
|
+
* @returns {Array} the subset of `tokens` referenced by the matched components
|
|
89
|
+
*/
|
|
90
|
+
function tokensForComponents(matched, tokens) {
|
|
91
|
+
if (!Array.isArray(tokens) || tokens.length === 0) return [];
|
|
92
|
+
// Build a haystack of strings drawn from the matched components.
|
|
93
|
+
const haystack = [];
|
|
94
|
+
for (const c of matched) {
|
|
95
|
+
if (c.name) haystack.push(c.name);
|
|
96
|
+
for (const v of c.variants || []) haystack.push(v);
|
|
97
|
+
for (const p of c.props || []) {
|
|
98
|
+
if (p.name) haystack.push(p.name);
|
|
99
|
+
if (p.default !== undefined) haystack.push(String(p.default));
|
|
100
|
+
for (const o of p.options || []) haystack.push(String(o));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
const blob = haystack.join('\n');
|
|
104
|
+
return tokens.filter((t) => t && t.name !== undefined && blob.includes(t.name));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// ── helpers (Path A) ─────────────────────────────────────────────────────────
|
|
108
|
+
|
|
109
|
+
/** Convert a Figma {r,g,b,a?} (0..1 floats) colour to a hex string. */
|
|
110
|
+
function rgbToHex({ r, g, b, a }) {
|
|
111
|
+
const to = (v) => Math.round(v * 255).toString(16).padStart(2, '0');
|
|
112
|
+
const hex = `#${to(r)}${to(g)}${to(b)}`;
|
|
113
|
+
return a !== undefined && a < 1 ? `${hex}${to(a)}` : hex;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Path A — extract tokens from a Figma Variables API body
|
|
118
|
+
* (`/v1/files/:key/variables/local`). Mirrors the spike's extractTokensFromVariables.
|
|
119
|
+
* @param {object|null} vars the Variables API response (has .meta.{variables,variableCollections})
|
|
120
|
+
* @returns {Array<{name,type,collection?,modes?}>}
|
|
121
|
+
*/
|
|
122
|
+
function extractTokensFromVariables(vars) {
|
|
123
|
+
if (!vars || !vars.meta) return [];
|
|
124
|
+
const collections = vars.meta.variableCollections || {};
|
|
125
|
+
const variables = vars.meta.variables || {};
|
|
126
|
+
const tokens = [];
|
|
127
|
+
for (const v of Object.values(variables)) {
|
|
128
|
+
const collection = collections[v.variableCollectionId];
|
|
129
|
+
const modes = collection?.modes || [];
|
|
130
|
+
const valuesByMode = {};
|
|
131
|
+
for (const mode of modes) {
|
|
132
|
+
const raw = v.valuesByMode?.[mode.modeId];
|
|
133
|
+
if (raw && typeof raw === 'object' && 'r' in raw) {
|
|
134
|
+
valuesByMode[mode.name] = rgbToHex(raw);
|
|
135
|
+
} else if (raw && raw.type === 'VARIABLE_ALIAS') {
|
|
136
|
+
valuesByMode[mode.name] = `{${variables[raw.id]?.name || raw.id}}`;
|
|
137
|
+
} else {
|
|
138
|
+
valuesByMode[mode.name] = raw;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
tokens.push({
|
|
142
|
+
name: v.name,
|
|
143
|
+
type: v.resolvedType,
|
|
144
|
+
collection: collection?.name,
|
|
145
|
+
modes: valuesByMode,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
return tokens;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Path C — normalize a receiver-written plugin payload into the common token
|
|
153
|
+
* shape. The plugin (D-13) emits ALL local variables; we accept either a
|
|
154
|
+
* pre-shaped `tokens[]` array or the raw `variables`/`meta` form and pass it
|
|
155
|
+
* through extractTokensFromVariables when needed.
|
|
156
|
+
* @param {object|null} payload variables.json carrying source:'gdd-plugin'
|
|
157
|
+
* @returns {Array}
|
|
158
|
+
*/
|
|
159
|
+
function normalizePluginPayload(payload) {
|
|
160
|
+
if (!payload) return [];
|
|
161
|
+
// Preferred shape: the plugin already emits a flat tokens[] array.
|
|
162
|
+
if (Array.isArray(payload.tokens)) return payload.tokens;
|
|
163
|
+
// Fallback: it carries a Variables-API-like body — reuse Path A extraction.
|
|
164
|
+
if (payload.meta) return extractTokensFromVariables(payload);
|
|
165
|
+
return [];
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// ── three-path merge (D-04) ──────────────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Merge the three token sources by priority. On a NAME collision the
|
|
172
|
+
* higher-priority source wins.
|
|
173
|
+
*
|
|
174
|
+
* Implementation note: we iterate the priority chain HIGHEST-first and only set
|
|
175
|
+
* a name the first time we see it (skip-if-present), so the highest-priority
|
|
176
|
+
* source's entry is the one that survives.
|
|
177
|
+
*
|
|
178
|
+
* @param {object} opts
|
|
179
|
+
* @param {Array} [opts.variables] Path A tokens
|
|
180
|
+
* @param {Array} [opts.pluginVariables] Path C tokens
|
|
181
|
+
* @param {Array} [opts.styleTokens] Path B tokens
|
|
182
|
+
* @param {boolean} [opts.preferStyles] D-04 escape — move styles to the front
|
|
183
|
+
* @returns {Array} merged tokens (insertion order follows the priority chain)
|
|
184
|
+
*/
|
|
185
|
+
function assembleTokens({ variables, pluginVariables, styleTokens, preferStyles } = {}) {
|
|
186
|
+
const bySource = {
|
|
187
|
+
variables: Array.isArray(variables) ? variables : [],
|
|
188
|
+
plugin: Array.isArray(pluginVariables) ? pluginVariables : [],
|
|
189
|
+
styles: Array.isArray(styleTokens) ? styleTokens : [],
|
|
190
|
+
};
|
|
191
|
+
const priority = preferStyles
|
|
192
|
+
? ['styles', 'variables', 'plugin']
|
|
193
|
+
: DEFAULT_TOKEN_PRIORITY;
|
|
194
|
+
|
|
195
|
+
const merged = new Map();
|
|
196
|
+
for (const source of priority) {
|
|
197
|
+
for (const tok of bySource[source]) {
|
|
198
|
+
if (!tok || tok.name === undefined) continue;
|
|
199
|
+
if (!merged.has(tok.name)) merged.set(tok.name, tok);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return [...merged.values()];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ── orchestrator ─────────────────────────────────────────────────────────────
|
|
206
|
+
|
|
207
|
+
/** Read+parse a JSON file from the raw cache; return null if absent/unreadable. */
|
|
208
|
+
async function readJson(rawDir, name) {
|
|
209
|
+
try {
|
|
210
|
+
const body = await fs.readFile(path.join(rawDir, `${name}.json`), 'utf8');
|
|
211
|
+
return JSON.parse(body);
|
|
212
|
+
} catch {
|
|
213
|
+
return null;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/**
|
|
218
|
+
* Run the digest: read raw/ cache → walk (variant rollup) → 3-path token
|
|
219
|
+
* assembly → render DESIGN.md + write tokens.json + components.json.
|
|
220
|
+
*
|
|
221
|
+
* @param {object} opts
|
|
222
|
+
* @param {string} opts.rawDir raw/ cache dir produced by pull.cjs (31-01) — REQUIRED
|
|
223
|
+
* @param {string} opts.outDir dir to write DESIGN.md/tokens.json/components.json — REQUIRED for writes
|
|
224
|
+
* @param {Function} [opts.stylesResolver] fn(file, styles) → styleTokens[] (Path B; 31-03 provides real impl)
|
|
225
|
+
* @param {boolean} [opts.preferStyles] D-04 escape hatch
|
|
226
|
+
* @param {string} [opts.fetchedAtOverride] deterministic provenance header for tests
|
|
227
|
+
* @param {string} [opts.component] D-08 — name or glob; when set, render a
|
|
228
|
+
* per-component SLICE (~500 tokens) of only
|
|
229
|
+
* the matching component(s). Additive: when
|
|
230
|
+
* absent, the full-digest path is unchanged.
|
|
231
|
+
* @returns {Promise<object>}
|
|
232
|
+
* full: { ok:true, counts, bytes, outDir }
|
|
233
|
+
* sliced: { ok:true, sliced:true, matched:[names], counts:{components,tokens}, bytes, outDir, note? }
|
|
234
|
+
* error: { ok:false, error }
|
|
235
|
+
*/
|
|
236
|
+
async function digest({ rawDir, outDir, stylesResolver, preferStyles, fetchedAtOverride, component } = {}) {
|
|
237
|
+
if (!rawDir) {
|
|
238
|
+
return { ok: false, error: 'rawDir is required — run pull.cjs first' };
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// (1) Required input — graceful guard (mirrors spike). NEVER throws.
|
|
242
|
+
const file = await readJson(rawDir, 'file');
|
|
243
|
+
if (!file) {
|
|
244
|
+
return { ok: false, error: 'raw/file.json not found — run pull.cjs first' };
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// (2) Optional inputs.
|
|
248
|
+
const variablesRaw = await readJson(rawDir, 'variables');
|
|
249
|
+
const styles = await readJson(rawDir, 'styles');
|
|
250
|
+
const meta = await readJson(rawDir, '_meta');
|
|
251
|
+
|
|
252
|
+
// Distinguish Path A (Variables API body) from Path C (receiver plugin payload)
|
|
253
|
+
// by the receiver marker. Only one of the two is populated from variables.json.
|
|
254
|
+
let apiVariables = null;
|
|
255
|
+
let pluginPayload = null;
|
|
256
|
+
if (variablesRaw && variablesRaw.source === PLUGIN_PAYLOAD_MARKER) {
|
|
257
|
+
pluginPayload = variablesRaw; // Path C
|
|
258
|
+
} else {
|
|
259
|
+
apiVariables = variablesRaw; // Path A (may be null)
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// (3) Components + widgets — variant rollup is default-on (D-02).
|
|
263
|
+
const { components, widgets } = collectComponents(file.document);
|
|
264
|
+
|
|
265
|
+
// (4) Three token paths.
|
|
266
|
+
const pathATokens = extractTokensFromVariables(apiVariables);
|
|
267
|
+
const styleTokens = stylesResolver ? await stylesResolver(file, styles) : [];
|
|
268
|
+
const pluginVariables = normalizePluginPayload(pluginPayload);
|
|
269
|
+
|
|
270
|
+
// (5) Merge by priority (D-04).
|
|
271
|
+
const tokens = assembleTokens({
|
|
272
|
+
variables: pathATokens,
|
|
273
|
+
pluginVariables,
|
|
274
|
+
styleTokens,
|
|
275
|
+
preferStyles,
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
// (6) Provenance — fetched_at is injectable for deterministic output.
|
|
279
|
+
const fileMeta = {
|
|
280
|
+
file_key: meta?.file_key,
|
|
281
|
+
fetched_at: fetchedAtOverride !== undefined ? fetchedAtOverride : meta?.fetched_at,
|
|
282
|
+
name: file.name,
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// (6b) D-08 — per-component SLICE. When `component` is provided we short-circuit
|
|
286
|
+
// the full digest and render only the matching component(s) + their relevant
|
|
287
|
+
// tokens. This is additive: the block below is skipped entirely when `component`
|
|
288
|
+
// is undefined, so the full-digest path (step 7) stays byte-identical.
|
|
289
|
+
if (component !== undefined && component !== null && component !== '') {
|
|
290
|
+
const rx = globToRegExp(component);
|
|
291
|
+
const matched = components.filter((c) => rx.test(c.name));
|
|
292
|
+
// Only tokens referenced by the matched components — keeps the slice ~500
|
|
293
|
+
// tokens instead of dumping the whole catalog.
|
|
294
|
+
const sliceTokens = tokensForComponents(matched, tokens);
|
|
295
|
+
const sliceMd = renderDesignMd({
|
|
296
|
+
tokens: sliceTokens,
|
|
297
|
+
components: matched,
|
|
298
|
+
widgets: [], // a per-component slice omits page/widget noise
|
|
299
|
+
fileMeta,
|
|
300
|
+
});
|
|
301
|
+
if (outDir) {
|
|
302
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
303
|
+
// Write the slice to DESIGN.md (the SKILL/e2e read whatever digest writes).
|
|
304
|
+
await fs.writeFile(path.join(outDir, 'DESIGN.md'), sliceMd);
|
|
305
|
+
}
|
|
306
|
+
const result = {
|
|
307
|
+
ok: true,
|
|
308
|
+
sliced: true,
|
|
309
|
+
matched: matched.map((c) => c.name),
|
|
310
|
+
counts: { components: matched.length, tokens: sliceTokens.length },
|
|
311
|
+
bytes: { designMd: Buffer.byteLength(sliceMd, 'utf8') },
|
|
312
|
+
outDir,
|
|
313
|
+
};
|
|
314
|
+
if (matched.length === 0) result.note = `no component matched ${component}`;
|
|
315
|
+
return result;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
// (7) Render + write artifacts (D-09: digest/ is commit-able).
|
|
319
|
+
const designMd = renderDesignMd({ tokens, components, widgets, fileMeta });
|
|
320
|
+
const tokensJson = JSON.stringify(tokens, null, 2);
|
|
321
|
+
const componentsJson = JSON.stringify(components, null, 2);
|
|
322
|
+
|
|
323
|
+
if (outDir) {
|
|
324
|
+
await fs.mkdir(outDir, { recursive: true });
|
|
325
|
+
await fs.writeFile(path.join(outDir, 'DESIGN.md'), designMd);
|
|
326
|
+
await fs.writeFile(path.join(outDir, 'tokens.json'), tokensJson);
|
|
327
|
+
await fs.writeFile(path.join(outDir, 'components.json'), componentsJson);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
ok: true,
|
|
332
|
+
counts: {
|
|
333
|
+
tokens: tokens.length,
|
|
334
|
+
components: components.length,
|
|
335
|
+
widgets: widgets.length,
|
|
336
|
+
},
|
|
337
|
+
bytes: {
|
|
338
|
+
designMd: Buffer.byteLength(designMd, 'utf8'),
|
|
339
|
+
tokensJson: Buffer.byteLength(tokensJson, 'utf8'),
|
|
340
|
+
componentsJson: Buffer.byteLength(componentsJson, 'utf8'),
|
|
341
|
+
},
|
|
342
|
+
outDir,
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
module.exports = {
|
|
347
|
+
digest,
|
|
348
|
+
assembleTokens,
|
|
349
|
+
DEFAULT_TOKEN_PRIORITY,
|
|
350
|
+
// exported for unit reuse / downstream (31-08 --component, 31-03 normalization parity)
|
|
351
|
+
extractTokensFromVariables,
|
|
352
|
+
normalizePluginPayload,
|
|
353
|
+
PLUGIN_PAYLOAD_MARKER,
|
|
354
|
+
// D-08 component filter — exported for unit reuse / downstream slicing tools.
|
|
355
|
+
globToRegExp,
|
|
356
|
+
tokensForComponents,
|
|
357
|
+
};
|
|
358
|
+
|
|
359
|
+
// ── CLI entry (31-08) ──────────────────────────────────────────────────────────
|
|
360
|
+
//
|
|
361
|
+
// Thin argv wrapper invoked by the figma-extract SKILL (31-07) and the e2e test
|
|
362
|
+
// (31-10). The callable API (digest/assembleTokens/…) above is the import surface;
|
|
363
|
+
// this block runs ONLY when the file is executed directly. Flag → option map
|
|
364
|
+
// (contract from 31-02 SUMMARY, extended with --component for D-08):
|
|
365
|
+
//
|
|
366
|
+
// --raw <dir> rawDir (required) raw cache dir written by pull.cjs
|
|
367
|
+
// --out <dir> outDir (required) digest artifact output dir
|
|
368
|
+
// --prefer-styles preferStyles:true D-04 escape — styles-first priority
|
|
369
|
+
// --component <name> component D-08 per-component slice (name or glob)
|
|
370
|
+
//
|
|
371
|
+
// D-10: this block NEVER reads, logs, or persists FIGMA_TOKEN — digest is offline
|
|
372
|
+
// and token-free by construction; the CLI only echoes counts/paths.
|
|
373
|
+
|
|
374
|
+
/** Minimal flag parser for the four supported options (no external dep). */
|
|
375
|
+
function parseArgv(argv) {
|
|
376
|
+
const opts = {};
|
|
377
|
+
for (let i = 0; i < argv.length; i++) {
|
|
378
|
+
const a = argv[i];
|
|
379
|
+
if (a === '--raw') opts.rawDir = argv[++i];
|
|
380
|
+
else if (a === '--out') opts.outDir = argv[++i];
|
|
381
|
+
else if (a === '--prefer-styles') opts.preferStyles = true;
|
|
382
|
+
else if (a === '--component') opts.component = argv[++i];
|
|
383
|
+
else if (a === '--help' || a === '-h') opts.help = true;
|
|
384
|
+
}
|
|
385
|
+
return opts;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
const CLI_USAGE =
|
|
389
|
+
'Usage: node digest.cjs --raw <dir> --out <dir> [--prefer-styles] [--component <name|glob>]';
|
|
390
|
+
|
|
391
|
+
if (require.main === module) {
|
|
392
|
+
(async () => {
|
|
393
|
+
const opts = parseArgv(process.argv.slice(2));
|
|
394
|
+
if (opts.help) {
|
|
395
|
+
process.stdout.write(`${CLI_USAGE}\n`);
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
if (!opts.rawDir || !opts.outDir) {
|
|
399
|
+
process.stderr.write(`${CLI_USAGE}\n--raw and --out are required.\n`);
|
|
400
|
+
process.exitCode = 2;
|
|
401
|
+
return;
|
|
402
|
+
}
|
|
403
|
+
const res = await digest({
|
|
404
|
+
rawDir: opts.rawDir,
|
|
405
|
+
outDir: opts.outDir,
|
|
406
|
+
preferStyles: opts.preferStyles,
|
|
407
|
+
component: opts.component,
|
|
408
|
+
});
|
|
409
|
+
if (!res.ok) {
|
|
410
|
+
process.stderr.write(`digest failed: ${res.error}\n`);
|
|
411
|
+
process.exitCode = 1;
|
|
412
|
+
return;
|
|
413
|
+
}
|
|
414
|
+
if (res.sliced) {
|
|
415
|
+
const summary =
|
|
416
|
+
res.matched.length > 0
|
|
417
|
+
? `sliced ${res.counts.components} component(s): ${res.matched.join(', ')} (${res.counts.tokens} tokens) → ${res.outDir}`
|
|
418
|
+
: `${res.note} → ${res.outDir} (empty slice)`;
|
|
419
|
+
process.stdout.write(`${summary}\n`);
|
|
420
|
+
} else {
|
|
421
|
+
process.stdout.write(
|
|
422
|
+
`digest ok: ${res.counts.components} components, ${res.counts.tokens} tokens, ${res.counts.widgets} widgets → ${res.outDir}\n`
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
})().catch((err) => {
|
|
426
|
+
// Never leak a token; surface only the message.
|
|
427
|
+
process.stderr.write(`digest error: ${err && err.message ? err.message : err}\n`);
|
|
428
|
+
process.exitCode = 1;
|
|
429
|
+
});
|
|
430
|
+
}
|