@event4u/agent-config 16.4.0 → 16.5.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.
Files changed (77) hide show
  1. package/.augment-plugin/marketplace.json +3 -3
  2. package/.augment-plugin/plugin.json +1 -1
  3. package/.claude-plugin/marketplace.json +1 -1
  4. package/CHANGELOG.md +40 -191
  5. package/README.md +1 -1
  6. package/dist/agent-src/commands/design.md +1 -1
  7. package/dist/agent-src/guidelines/design-antipatterns.md +1 -1
  8. package/dist/agent-src/rules/token-budget-discipline.md +2 -2
  9. package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +2 -0
  10. package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
  11. package/dist/agent-src/skills/canvas-design/SKILL.md +4 -2
  12. package/dist/agent-src/skills/design-intelligence/references/integration-mapping.md +13 -10
  13. package/dist/agent-src/skills/fe-design/references/design-patterns.md +49 -2
  14. package/dist/agent-src/skills/frontend-render-security/SKILL.md +9 -0
  15. package/dist/agent-src/skills/iconography/SKILL.md +4 -2
  16. package/dist/agent-src/skills/image-provider-routing/SKILL.md +16 -5
  17. package/dist/agent-src/skills/logo-generation/SKILL.md +12 -8
  18. package/dist/agent-src/skills/svg-authoring/SKILL.md +318 -0
  19. package/dist/agent-src/skills/wireframe/SKILL.md +5 -2
  20. package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
  21. package/dist/cli/commands/uiRender.js +201 -4
  22. package/dist/cli/commands/uiRenderAsset.js +1308 -0
  23. package/dist/cli/commands/uiRenderAssetCompare.js +752 -0
  24. package/dist/cli/commands/uiRenderAssetGrid.js +265 -0
  25. package/dist/cli/commands/uiRenderAssetGrounds.js +323 -0
  26. package/dist/cli/commands/uiRenderAssetIsolation.js +85 -0
  27. package/dist/cli/commands/uiRenderAssetTypes.js +145 -0
  28. package/dist/cli/main.js +47 -1
  29. package/dist/cli/registry.js +1 -1
  30. package/dist/cli-delegate/{chunk-MSUL5NYP.js → chunk-4DBG5K7M.js} +1 -1
  31. package/dist/cli-delegate/{chunk-AED2VQGM.js → chunk-TMWYUOK4.js} +10 -1
  32. package/dist/cli-delegate/cmd_conformance.js +395 -214
  33. package/dist/cli-delegate/cmd_handoff.js +2 -2
  34. package/dist/cli-delegate/cmd_session_recycle.js +2 -2
  35. package/dist/discovery/deprecation-report.md +1 -1
  36. package/dist/discovery/discovery-manifest.json +47 -22
  37. package/dist/discovery/discovery-manifest.json.sha256 +1 -1
  38. package/dist/discovery/discovery-manifest.summary.md +6 -6
  39. package/dist/discovery/dormancy-report.md +1 -1
  40. package/dist/discovery/orphan-report.md +1 -1
  41. package/dist/discovery/packs.json +19 -15
  42. package/dist/discovery/trust-report.md +2 -2
  43. package/dist/discovery/workspaces.json +13 -9
  44. package/dist/hooks/dispatch.js +1073 -808
  45. package/dist/hooks/dispatch.sha256 +1 -1
  46. package/dist/hooks/svg_rules.chunk.cjs +1626 -0
  47. package/dist/hooks/svg_rules.chunk.cjs.sha256 +1 -0
  48. package/dist/mcp/registry-manifest.json +3 -3
  49. package/dist/mcp/server.json +2 -2
  50. package/dist/mcp/server.mjs +931 -735
  51. package/dist/scripts/_lib/scan_scope.js +105 -0
  52. package/dist/scripts/_lib/svg_dom.js +284 -0
  53. package/dist/scripts/_lib/svg_geometry.js +477 -0
  54. package/dist/scripts/_lib/svg_motion.js +179 -0
  55. package/dist/scripts/_lib/svg_rules.js +1337 -0
  56. package/dist/scripts/_lib/svg_urls.js +67 -0
  57. package/dist/scripts/lint_svg_asset.js +324 -0
  58. package/docs/guidelines/design-antipatterns.md +1 -1
  59. package/package.json +4 -3
  60. package/src/config/gate-violation-baselines.json +3 -3
  61. package/src/config/routing-coverage-seed.json +9 -2
  62. package/src/scripts/_lib/svg_dom.ts +318 -0
  63. package/src/scripts/_lib/svg_geometry.ts +480 -0
  64. package/src/scripts/_lib/svg_motion.ts +208 -0
  65. package/src/scripts/_lib/svg_rules.ts +1453 -0
  66. package/src/scripts/_lib/svg_urls.ts +72 -0
  67. package/src/scripts/_lib/verification_command.ts +59 -0
  68. package/src/scripts/_lib/verify_clause.ts +27 -17
  69. package/src/scripts/hooks/block_speaking_inbox_dir.ts +16 -1
  70. package/src/scripts/hooks/design_pass_hook.ts +157 -3
  71. package/src/scripts/hooks/svg_obligation.ts +497 -0
  72. package/src/scripts/hooks/svg_rules_loader.ts +133 -0
  73. package/src/scripts/hooks/turn_end_gate_hook.ts +5 -3
  74. package/src/scripts/lint_consolidation_lineage.ts +6 -3
  75. package/src/scripts/lint_design_quality.ts +60 -12
  76. package/src/scripts/lint_motion_authority_drift.ts +27 -0
  77. package/src/scripts/lint_svg_asset.ts +365 -0
@@ -0,0 +1,105 @@
1
+ /**
2
+ * Scan-scope assertions — "a gate that read nothing did not pass".
3
+ *
4
+ * WHY THIS EXISTS (audit 2026-07-29, `agents/settings/contexts/gates-that-cannot-fail.md`):
5
+ * ADR-051 moved the source container and a later commit deleted `packages/`.
6
+ * Fourteen `lint_*`/`check_*` gates kept a hardcoded literal path, and every
7
+ * one of them treats a missing directory as "nothing to check" — so they exit 0
8
+ * and print a green checkmark while scanning zero files. Their own output said
9
+ * so (`0 file(s) scanned`, `0 name(s) checked`, `0 declarer(s)`); nothing ever
10
+ * asserted on the number.
11
+ *
12
+ * Two shapes cover the whole class:
13
+ *
14
+ * - **Corpus gates** walk a root and check N units → {@link assertScanned}.
15
+ * Zero units is a failure unless the gate declares a justified `allowEmpty`.
16
+ * - **Watch-list gates** (diff-based, no corpus to count) name specific files
17
+ * they guard → {@link assertWatchlistResolves}. A watch list that resolves to
18
+ * nothing on disk means the guard is watching phantoms.
19
+ *
20
+ * Both throw {@link DeadScopeError}; callers map it to a non-zero exit. The
21
+ * point is that the failure is LOUD and names the root, so the next path
22
+ * migration cannot silently disarm a gate again.
23
+ */
24
+ import * as fs from 'node:fs';
25
+ import * as path from 'node:path';
26
+ import process from 'node:process';
27
+ /** Raised when a gate's scope is empty — it cannot have checked anything. */
28
+ export class DeadScopeError extends Error {
29
+ gate;
30
+ constructor(gate, message) {
31
+ super(message);
32
+ this.name = 'DeadScopeError';
33
+ this.gate = gate;
34
+ }
35
+ }
36
+ /**
37
+ * Assert a corpus gate examined at least one unit.
38
+ *
39
+ * @throws {DeadScopeError} when `scanned === 0` and no `allowEmpty` reason is given.
40
+ */
41
+ export function assertScanned(opts) {
42
+ if (opts.scanned > 0) {
43
+ return;
44
+ }
45
+ if (opts.allowEmpty !== undefined && opts.allowEmpty.trim() !== '') {
46
+ return;
47
+ }
48
+ const roots = opts.roots.length > 0 ? opts.roots.join(', ') : '(no root declared)';
49
+ throw new DeadScopeError(opts.gate, `${opts.gate}: scanned 0 ${opts.units} under ${roots} — the scan scope is ` +
50
+ 'dead or the root moved. A gate that read nothing has not passed. ' +
51
+ 'Repoint the root (prefer the shared resolver in _lib/agent_src.ts), ' +
52
+ 'or declare a justified `allowEmpty` reason if an empty corpus is genuinely expected.');
53
+ }
54
+ /**
55
+ * Assert a corpus gate's scope AND publish the count it just validated.
56
+ *
57
+ * The two halves of scope hardening drifted apart in practice: 14 gates emit
58
+ * the machine-readable `scanned:` line the coverage guard reads, 12 assert
59
+ * their scope, and only 8 do both. A gate that publishes a number it never
60
+ * asserted can print `scanned: 0` and exit green; a gate that asserts without
61
+ * publishing is invisible to `check_gate_coverage`. Splitting the two also
62
+ * lets the published number drift from the number that was validated — the
63
+ * invented-count failure that is mechanically undetectable downstream.
64
+ *
65
+ * One call closes both: the emitted number is, by construction, the number the
66
+ * assertion just accepted. Use this for any gate that should also carry a floor
67
+ * in `src/config/gate-coverage.yml`; plain {@link assertScanned} stays correct
68
+ * for a gate that only needs its scope guarded.
69
+ *
70
+ * The line goes to stdout unconditionally — a count only visible without
71
+ * `--quiet` is not a count, since CI passes `--quiet` (the `lint_handoffs`
72
+ * lesson, preserved here so no caller has to rediscover it).
73
+ */
74
+ export function reportScanned(opts, write = process.stdout.write.bind(process.stdout)) {
75
+ assertScanned(opts);
76
+ write(`scanned: ${String(opts.scanned)}\n`);
77
+ }
78
+ /**
79
+ * Assert a watch-list gate's targets exist on disk, and return the ones that do.
80
+ *
81
+ * A diff-based guard has no corpus to count: it compares changed paths against
82
+ * a fixed list. If that list resolves to nothing, the guard reports "clean"
83
+ * forever — the exact shape of the `check_safety_floor_untouched` defect, which
84
+ * announced `4 rules guarded` while guarding none.
85
+ *
86
+ * @returns the subset of `candidates` that exist.
87
+ * @throws {DeadScopeError} when none of them exist.
88
+ */
89
+ export function assertWatchlistResolves(opts) {
90
+ const resolved = opts.candidates.filter((rel) => {
91
+ try {
92
+ return fs.statSync(path.join(opts.repoRoot, rel)).isFile();
93
+ }
94
+ catch {
95
+ return false;
96
+ }
97
+ });
98
+ if (resolved.length === 0) {
99
+ throw new DeadScopeError(opts.gate, `${opts.gate}: none of its ${opts.candidates.length} guarded path(s) exist ` +
100
+ `under ${opts.repoRoot} — the guard is watching phantoms and can never fire. ` +
101
+ `Checked: ${opts.candidates.join(', ')}`);
102
+ }
103
+ return resolved;
104
+ }
105
+ //# sourceMappingURL=scan_scope.js.map
@@ -0,0 +1,284 @@
1
+ /**
2
+ * svg_dom — a dependency-free XML well-formedness parser, and the tree it
3
+ * builds.
4
+ *
5
+ * Step 3.1 of `road-to-svg-review-followups`, amending step 1.1 of
6
+ * `road-to-svg-with-a-witness`. Review claim C11: a regex cannot prove nesting
7
+ * or resolve a reference, and the alternatives were all refused for reasons
8
+ * that still hold — `package.json`'s runtime dependencies carry no XML parser,
9
+ * `happy-dom` is dev-only, the three vendored tree-sitter grammars are php,
10
+ * typescript and javascript, and Playwright is absent in a consumer, so the
11
+ * browser cannot carry a static layer. The remaining option is to write one,
12
+ * which is what `lint_design_quality` already does for its own surface.
13
+ *
14
+ * WHAT IT IS. One scanner over the bytes, producing every node kind an SVG
15
+ * carries — elements with their attributes, comments, CDATA sections,
16
+ * processing instructions, DOCTYPEs and text — each with a line and a column,
17
+ * assembled into a tree with parent links, plus a list of well-formedness
18
+ * errors.
19
+ *
20
+ * TWO SCANNERS, NOT ONE, and this header used to claim otherwise.
21
+ * `parseElements` in `svg_rules.ts` is an independent scanner over the same
22
+ * bytes, so the two CAN disagree about what a file contains — finding 3 of the
23
+ * third completion review of this change found a case where they did, and the
24
+ * disagreement produced an error-severity false positive. That one is fixed
25
+ * where it was (the prolog), but the general claim was false and is not
26
+ * restored: a reader deciding whether a fix here also lands there must check,
27
+ * not assume. Merging them is a larger change than the finding asked for and
28
+ * is not attempted under a remediation.
29
+ *
30
+ * WHAT IT IS NOT. Not a validating parser, not namespace-aware beyond keeping
31
+ * the prefix in the name, and it does not expand entities. It reports that a
32
+ * file is not well-formed; it does not report which schema it violates.
33
+ *
34
+ * WHY A FILE THAT IS NOT WELL-FORMED IS A FINDING RATHER THAN A SKIPPED FILE.
35
+ * A skipped file reads as a clean one in every count that matters, and the
36
+ * shape this exists to catch — `<g><path></g></path>` — is exactly the shape a
37
+ * flat element list cannot see: both tags are present, both are spelled
38
+ * correctly, and the document is nonetheless not a tree.
39
+ */
40
+ /** 1-based line and column of an offset. Built once per parse. */
41
+ function positions(content) {
42
+ const starts = [0];
43
+ for (let i = 0; i < content.length; i += 1) {
44
+ if (content[i] === '\n')
45
+ starts.push(i + 1);
46
+ }
47
+ return (offset) => {
48
+ // Binary search for the last line start at or before `offset`.
49
+ let lo = 0;
50
+ let hi = starts.length - 1;
51
+ while (lo < hi) {
52
+ const mid = (lo + hi + 1) >> 1;
53
+ if (starts[mid] <= offset)
54
+ lo = mid;
55
+ else
56
+ hi = mid - 1;
57
+ }
58
+ return { line: lo + 1, column: offset - starts[lo] + 1 };
59
+ };
60
+ }
61
+ /**
62
+ * End of the tag, honouring quoted attribute values that may contain `>`.
63
+ *
64
+ * Exported because `svg_rules.parseElements` scans the same bytes and had its
65
+ * own byte-identical copy — finding 9 of the third completion review, which
66
+ * also noted that BOTH copies ship in the per-`.svg`-write hook chunk. Two
67
+ * scanners is a fact this file's header now states plainly; two copies of
68
+ * their shared primitives is not a consequence of it.
69
+ */
70
+ export function tagEnd(content, from) {
71
+ let quote = null;
72
+ for (let i = from + 1; i < content.length; i += 1) {
73
+ const ch = content[i];
74
+ if (quote !== null) {
75
+ if (ch === quote)
76
+ quote = null;
77
+ continue;
78
+ }
79
+ if (ch === '"' || ch === "'") {
80
+ quote = ch;
81
+ continue;
82
+ }
83
+ if (ch === '>')
84
+ return i;
85
+ }
86
+ return -1;
87
+ }
88
+ /** Attributes of one tag, lowercased keys. Shared with `svg_rules` — see `tagEnd`. */
89
+ export function attrsOf(raw) {
90
+ const attrs = {};
91
+ const re = /([A-Za-z_:][\w.:-]*)\s*=\s*("([^"]*)"|'([^']*)')/g;
92
+ let m;
93
+ while ((m = re.exec(raw)) !== null) {
94
+ const key = (m[1] ?? '').toLowerCase();
95
+ const value = m[3] ?? m[4] ?? '';
96
+ attrs[key] = value.replace(/&amp;/g, '&');
97
+ }
98
+ return attrs;
99
+ }
100
+ /**
101
+ * Parse one document.
102
+ *
103
+ * Never throws: a malformed file produces whatever tree could be built plus
104
+ * the errors that explain it. A parser that threw would make an unparseable
105
+ * file indistinguishable from a crash, and the caller's job is to REPORT the
106
+ * file, not to fall over on it.
107
+ */
108
+ export function parseDocument(content) {
109
+ const at = positions(content);
110
+ const roots = [];
111
+ const elements = [];
112
+ const errors = [];
113
+ const stack = [];
114
+ const make = (kind, start, name = '', value = '', attrs = {}) => {
115
+ const { line, column } = at(start);
116
+ return { kind, name, attrs, line, column, start, value, children: [], parent: null };
117
+ };
118
+ const append = (node) => {
119
+ const parent = stack[stack.length - 1];
120
+ if (parent === undefined) {
121
+ roots.push(node);
122
+ }
123
+ else {
124
+ node.parent = parent;
125
+ parent.children.push(node);
126
+ }
127
+ };
128
+ let i = 0;
129
+ while (i < content.length) {
130
+ const lt = content.indexOf('<', i);
131
+ if (lt === -1) {
132
+ const rest = content.slice(i);
133
+ if (rest.trim() !== '')
134
+ append(make('text', i, '', rest));
135
+ break;
136
+ }
137
+ if (lt > i) {
138
+ const text = content.slice(i, lt);
139
+ if (text.trim() !== '')
140
+ append(make('text', i, '', text));
141
+ }
142
+ if (content.startsWith('<!--', lt)) {
143
+ const end = content.indexOf('-->', lt + 4);
144
+ if (end === -1) {
145
+ const p = at(lt);
146
+ errors.push({ message: 'unterminated comment', line: p.line, column: p.column });
147
+ append(make('comment', lt, '', content.slice(lt + 4)));
148
+ break;
149
+ }
150
+ append(make('comment', lt, '', content.slice(lt + 4, end)));
151
+ i = end + 3;
152
+ continue;
153
+ }
154
+ if (content.startsWith('<![CDATA[', lt)) {
155
+ const end = content.indexOf(']]>', lt + 9);
156
+ if (end === -1) {
157
+ const p = at(lt);
158
+ errors.push({ message: 'unterminated CDATA section', line: p.line, column: p.column });
159
+ append(make('cdata', lt, '', content.slice(lt + 9)));
160
+ break;
161
+ }
162
+ append(make('cdata', lt, '', content.slice(lt + 9, end)));
163
+ i = end + 3;
164
+ continue;
165
+ }
166
+ if (content.startsWith('<?', lt)) {
167
+ const end = content.indexOf('?>', lt + 2);
168
+ if (end === -1) {
169
+ // Finding 11 of this change's completion review: this and the
170
+ // declaration below swallowed the rest of the document into one
171
+ // node and returned cleanly, so a file ending in
172
+ // `<?xml version="1.0"` read as WELL-FORMED — a skip, under a
173
+ // contract whose whole point is that a file which does not
174
+ // parse is a finding rather than a skipped file.
175
+ const p = at(lt);
176
+ errors.push({ message: 'unterminated processing instruction', line: p.line, column: p.column });
177
+ }
178
+ const stop = end === -1 ? content.length : end + 2;
179
+ append(make('pi', lt, '', content.slice(lt + 2, end === -1 ? content.length : end)));
180
+ i = stop;
181
+ continue;
182
+ }
183
+ if (content.startsWith('<!', lt)) {
184
+ // A DOCTYPE may carry a bracketed internal subset; it ends after it.
185
+ const bracket = content.indexOf('[', lt);
186
+ const close = content.indexOf('>', lt);
187
+ let stop;
188
+ if (bracket !== -1 && close !== -1 && bracket < close) {
189
+ const endBracket = content.indexOf(']', bracket);
190
+ const after = endBracket === -1 ? close : content.indexOf('>', endBracket);
191
+ stop = after === -1 ? content.length : after + 1;
192
+ }
193
+ else {
194
+ stop = close === -1 ? content.length : close + 1;
195
+ }
196
+ if (close === -1) {
197
+ const p = at(lt);
198
+ errors.push({ message: 'unterminated declaration', line: p.line, column: p.column });
199
+ }
200
+ append(make('doctype', lt, '', content.slice(lt + 2, Math.max(lt + 2, stop - 1))));
201
+ i = stop;
202
+ continue;
203
+ }
204
+ const gt = tagEnd(content, lt);
205
+ if (gt === -1) {
206
+ const p = at(lt);
207
+ errors.push({ message: 'unterminated tag', line: p.line, column: p.column });
208
+ break;
209
+ }
210
+ const raw = content.slice(lt, gt + 1);
211
+ const nameMatch = /^<\/?\s*([A-Za-z_][\w.:-]*)/.exec(raw);
212
+ if (!nameMatch) {
213
+ // Finding 11, third case: a `<` that opens something with no legal
214
+ // element name is malformed markup, not whitespace. Skipping it
215
+ // silently is the same skip the other two were.
216
+ const p = at(lt);
217
+ errors.push({ message: `"${raw.slice(0, 24)}" is not a tag`, line: p.line, column: p.column });
218
+ i = gt + 1;
219
+ continue;
220
+ }
221
+ const name = (nameMatch[1] ?? '').toLowerCase();
222
+ const closing = raw.startsWith('</');
223
+ const selfClosing = raw.endsWith('/>');
224
+ if (closing) {
225
+ const open = stack[stack.length - 1];
226
+ const p = at(lt);
227
+ if (open === undefined) {
228
+ errors.push({ message: `closing tag </${name}> with no open element`, line: p.line, column: p.column });
229
+ }
230
+ else if (open.name !== name) {
231
+ // The shape a flat element list cannot see. Reported against
232
+ // the CLOSING tag, which is where a reader looking at the file
233
+ // sees the problem.
234
+ errors.push({
235
+ message: `closing tag </${name}> does not match the open <${open.name}> (line ${String(open.line)})`,
236
+ line: p.line,
237
+ column: p.column,
238
+ });
239
+ // Recover by popping to the matching open element WHEN THERE
240
+ // IS ONE, so a single mistake does not cascade into an error
241
+ // per remaining tag.
242
+ //
243
+ // When there is none, pop NOTHING. Finding 2 of the fourth
244
+ // completion review: the `else stack.pop()` detached a
245
+ // perfectly good ancestor to pay for a stray tag that never
246
+ // opened anything — a file whose `</g>` has no `<g>` lost its
247
+ // `<svg>` from the stack, so the real `</svg>` at the end then
248
+ // had no open element and the file collected a SECOND error it
249
+ // had not earned. Cascading is exactly what this branch exists
250
+ // to prevent, and the unmatched case was the one where it did
251
+ // the cascading itself. A closing tag with no match is a stray
252
+ // tag: report it and carry on.
253
+ const idx = stack.map((n) => n.name).lastIndexOf(name);
254
+ if (idx !== -1)
255
+ stack.length = idx;
256
+ }
257
+ else {
258
+ stack.pop();
259
+ }
260
+ i = gt + 1;
261
+ continue;
262
+ }
263
+ const node = make('element', lt, name, '', attrsOf(raw));
264
+ append(node);
265
+ elements.push(node);
266
+ if (!selfClosing)
267
+ stack.push(node);
268
+ i = gt + 1;
269
+ }
270
+ for (const unclosed of stack) {
271
+ errors.push({
272
+ message: `<${unclosed.name}> is never closed`,
273
+ line: unclosed.line,
274
+ column: unclosed.column,
275
+ });
276
+ }
277
+ return { roots, elements, errors };
278
+ }
279
+ // `ancestors` and `documentElement` used to live here. Finding 11 of the third
280
+ // completion review: both were exported, called by nothing, and bundled into
281
+ // the per-`.svg`-write dispatch chunk — own-orphans of this same diff, which
282
+ // `minimal-safe-diff` says the diff that made them removes. `XmlNode.parent`
283
+ // is the primitive either would have been built on and it is still here.
284
+ //# sourceMappingURL=svg_dom.js.map