@bastani/atomic 0.8.20 → 0.8.21-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 (124) hide show
  1. package/CHANGELOG.md +6 -0
  2. package/dist/builtin/intercom/package.json +1 -1
  3. package/dist/builtin/mcp/package.json +1 -1
  4. package/dist/builtin/subagents/agents/code-simplifier.md +78 -22
  5. package/dist/builtin/subagents/agents/debugger.md +4 -3
  6. package/dist/builtin/subagents/package.json +1 -1
  7. package/dist/builtin/web-access/package.json +1 -1
  8. package/dist/builtin/workflows/CHANGELOG.md +19 -0
  9. package/dist/builtin/workflows/package.json +1 -1
  10. package/dist/builtin/workflows/skills/create-spec/SKILL.md +169 -125
  11. package/dist/builtin/workflows/skills/impeccable/SKILL.md +89 -80
  12. package/dist/builtin/workflows/skills/impeccable/agents/impeccable_asset_producer.toml +92 -0
  13. package/dist/builtin/workflows/skills/impeccable/agents/impeccable_manual_edit_applier.toml +95 -0
  14. package/dist/builtin/workflows/skills/impeccable/agents/openai.yaml +4 -0
  15. package/dist/builtin/workflows/skills/impeccable/reference/adapt.md +122 -1
  16. package/dist/builtin/workflows/skills/impeccable/reference/animate.md +38 -12
  17. package/dist/builtin/workflows/skills/impeccable/reference/audit.md +5 -5
  18. package/dist/builtin/workflows/skills/impeccable/reference/bolder.md +7 -7
  19. package/dist/builtin/workflows/skills/impeccable/reference/brand.md +4 -14
  20. package/dist/builtin/workflows/skills/impeccable/reference/clarify.md +115 -1
  21. package/dist/builtin/workflows/skills/impeccable/reference/codex.md +3 -3
  22. package/dist/builtin/workflows/skills/impeccable/reference/colorize.md +109 -6
  23. package/dist/builtin/workflows/skills/impeccable/reference/craft.md +7 -7
  24. package/dist/builtin/workflows/skills/impeccable/reference/critique.md +623 -94
  25. package/dist/builtin/workflows/skills/impeccable/reference/delight.md +2 -2
  26. package/dist/builtin/workflows/skills/impeccable/reference/distill.md +2 -2
  27. package/dist/builtin/workflows/skills/impeccable/reference/document.md +16 -14
  28. package/dist/builtin/workflows/skills/impeccable/reference/extract.md +1 -1
  29. package/dist/builtin/workflows/skills/impeccable/reference/harden.md +1 -1
  30. package/dist/builtin/workflows/skills/impeccable/reference/init.md +172 -0
  31. package/dist/builtin/workflows/skills/impeccable/reference/interaction-design.md +0 -6
  32. package/dist/builtin/workflows/skills/impeccable/reference/layout.md +33 -13
  33. package/dist/builtin/workflows/skills/impeccable/reference/live.md +96 -19
  34. package/dist/builtin/workflows/skills/impeccable/reference/onboard.md +1 -1
  35. package/dist/builtin/workflows/skills/impeccable/reference/optimize.md +1 -1
  36. package/dist/builtin/workflows/skills/impeccable/reference/overdrive.md +1 -1
  37. package/dist/builtin/workflows/skills/impeccable/reference/polish.md +3 -4
  38. package/dist/builtin/workflows/skills/impeccable/reference/product.md +1 -3
  39. package/dist/builtin/workflows/skills/impeccable/reference/quieter.md +2 -2
  40. package/dist/builtin/workflows/skills/impeccable/reference/shape.md +5 -5
  41. package/dist/builtin/workflows/skills/impeccable/reference/typeset.md +158 -3
  42. package/dist/builtin/workflows/skills/impeccable/scripts/cleanup-deprecated.mjs +1 -1
  43. package/dist/builtin/workflows/skills/impeccable/scripts/command-metadata.json +2 -2
  44. package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +225 -0
  45. package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +266 -0
  46. package/dist/builtin/workflows/skills/impeccable/scripts/critique-storage.mjs +17 -1
  47. package/dist/builtin/workflows/skills/impeccable/scripts/design-parser.mjs +16 -1
  48. package/dist/builtin/workflows/skills/impeccable/scripts/detect.mjs +21 -0
  49. package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +1725 -0
  50. package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +244 -0
  51. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +4543 -0
  52. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns.mjs +43 -0
  53. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +252 -0
  54. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +535 -0
  55. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +986 -0
  56. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +208 -0
  57. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/visual/screenshot-contrast.mjs +189 -0
  58. package/dist/builtin/workflows/skills/impeccable/scripts/detector/findings.mjs +12 -0
  59. package/dist/builtin/workflows/skills/impeccable/scripts/detector/node/file-system.mjs +198 -0
  60. package/dist/builtin/workflows/skills/impeccable/scripts/detector/profile/profiler.mjs +166 -0
  61. package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +419 -0
  62. package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +2316 -0
  63. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/color.mjs +124 -0
  64. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/constants.mjs +101 -0
  65. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/page.mjs +7 -0
  66. package/dist/builtin/workflows/skills/impeccable/scripts/impeccable-paths.mjs +17 -1
  67. package/dist/builtin/workflows/skills/impeccable/scripts/is-generated.mjs +2 -2
  68. package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +139 -96
  69. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +4491 -526
  70. package/dist/builtin/workflows/skills/impeccable/scripts/live-commit-manual-edits.mjs +1241 -0
  71. package/dist/builtin/workflows/skills/impeccable/scripts/live-copy-edit-agent.mjs +683 -0
  72. package/dist/builtin/workflows/skills/impeccable/scripts/live-discard-manual-edits.mjs +51 -0
  73. package/dist/builtin/workflows/skills/impeccable/scripts/live-event-validation.mjs +136 -0
  74. package/dist/builtin/workflows/skills/impeccable/scripts/live-inject.mjs +22 -9
  75. package/dist/builtin/workflows/skills/impeccable/scripts/live-insert-ui.mjs +458 -0
  76. package/dist/builtin/workflows/skills/impeccable/scripts/live-insert.mjs +232 -0
  77. package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edit-evidence.mjs +363 -0
  78. package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edits-buffer.mjs +152 -0
  79. package/dist/builtin/workflows/skills/impeccable/scripts/live-poll.mjs +288 -110
  80. package/dist/builtin/workflows/skills/impeccable/scripts/live-resume.mjs +47 -1
  81. package/dist/builtin/workflows/skills/impeccable/scripts/live-server.mjs +1443 -100
  82. package/dist/builtin/workflows/skills/impeccable/scripts/live-session-store.mjs +17 -0
  83. package/dist/builtin/workflows/skills/impeccable/scripts/live-status.mjs +17 -3
  84. package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +216 -6
  85. package/dist/builtin/workflows/skills/impeccable/scripts/live.mjs +2 -3
  86. package/dist/builtin/workflows/skills/impeccable/scripts/palette.mjs +633 -0
  87. package/dist/builtin/workflows/skills/impeccable/scripts/pin.mjs +1 -1
  88. package/dist/builtin/workflows/src/extension/index.ts +67 -3
  89. package/dist/builtin/workflows/src/extension/render-result.ts +26 -1
  90. package/dist/builtin/workflows/src/runs/foreground/executor.ts +227 -3
  91. package/dist/builtin/workflows/src/runs/foreground/stage-runner.ts +94 -7
  92. package/dist/builtin/workflows/src/shared/stage-prompt.ts +326 -0
  93. package/dist/builtin/workflows/src/shared/stage-ui-broker.ts +62 -7
  94. package/dist/builtin/workflows/src/shared/store-types.ts +43 -0
  95. package/dist/builtin/workflows/src/shared/store.ts +37 -0
  96. package/dist/builtin/workflows/src/tui/chat-surface-message.ts +22 -4
  97. package/dist/builtin/workflows/src/tui/graph-view.ts +47 -0
  98. package/dist/builtin/workflows/src/tui/overlay-adapter.ts +43 -1
  99. package/dist/builtin/workflows/src/tui/run-detail.ts +10 -4
  100. package/dist/builtin/workflows/src/tui/stage-chat-view.ts +117 -15
  101. package/dist/builtin/workflows/src/tui/workflow-attach-pane.ts +9 -0
  102. package/dist/core/skills.d.ts.map +1 -1
  103. package/dist/core/skills.js +2 -5
  104. package/dist/core/skills.js.map +1 -1
  105. package/dist/core/system-prompt.d.ts.map +1 -1
  106. package/dist/core/system-prompt.js +11 -29
  107. package/dist/core/system-prompt.js.map +1 -1
  108. package/dist/index.d.ts +1 -0
  109. package/dist/index.d.ts.map +1 -1
  110. package/dist/index.js +3 -0
  111. package/dist/index.js.map +1 -1
  112. package/docs/quickstart.md +1 -2
  113. package/package.json +4 -4
  114. package/dist/builtin/workflows/skills/impeccable/reference/cognitive-load.md +0 -106
  115. package/dist/builtin/workflows/skills/impeccable/reference/color-and-contrast.md +0 -105
  116. package/dist/builtin/workflows/skills/impeccable/reference/heuristics-scoring.md +0 -234
  117. package/dist/builtin/workflows/skills/impeccable/reference/motion-design.md +0 -109
  118. package/dist/builtin/workflows/skills/impeccable/reference/personas.md +0 -179
  119. package/dist/builtin/workflows/skills/impeccable/reference/responsive-design.md +0 -114
  120. package/dist/builtin/workflows/skills/impeccable/reference/spatial-design.md +0 -100
  121. package/dist/builtin/workflows/skills/impeccable/reference/teach.md +0 -156
  122. package/dist/builtin/workflows/skills/impeccable/reference/typography.md +0 -159
  123. package/dist/builtin/workflows/skills/impeccable/reference/ux-writing.md +0 -107
  124. package/dist/builtin/workflows/skills/impeccable/scripts/load-context.mjs +0 -141
@@ -0,0 +1,51 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * CLI helper: discard pending manual edits from the buffer without applying.
4
+ *
5
+ * Reads .impeccable/live/pending-manual-edits.json, drops entries, writes back.
6
+ * No source-file writes. Use this when the user wants to throw away unsaved
7
+ * manual edits.
8
+ *
9
+ * Trigger: only when the user explicitly asks the AI to discard / throw away /
10
+ * clear pending manual edits.
11
+ *
12
+ * Usage:
13
+ * node live-discard-manual-edits.mjs # discard all pending
14
+ * node live-discard-manual-edits.mjs --page-url=/ # discard only entries for "/"
15
+ *
16
+ * Output JSON: { discarded: N, entries: [...discardedEntries], totalCount: N }
17
+ */
18
+
19
+ import { readBuffer, removeEntries, truncateBuffer } from './live-manual-edits-buffer.mjs';
20
+
21
+ function argVal(args, name) {
22
+ const prefix = name + '=';
23
+ for (const a of args) {
24
+ if (a === name) return true;
25
+ if (a.startsWith(prefix)) return a.slice(prefix.length);
26
+ }
27
+ return null;
28
+ }
29
+
30
+ const args = process.argv.slice(2);
31
+ if (args.includes('--help') || args.includes('-h')) {
32
+ console.log('Usage: node live-discard-manual-edits.mjs [--page-url=<url>]');
33
+ process.exit(0);
34
+ }
35
+
36
+ const pageUrlFilter = argVal(args, '--page-url');
37
+ const cwd = process.cwd();
38
+
39
+ let discarded;
40
+ let entries;
41
+ const buffer = readBuffer(cwd);
42
+ if (pageUrlFilter) {
43
+ entries = buffer.entries.filter((entry) => entry.pageUrl === pageUrlFilter);
44
+ discarded = removeEntries(cwd, (entry) => entry.pageUrl === pageUrlFilter);
45
+ } else {
46
+ entries = buffer.entries;
47
+ discarded = truncateBuffer(cwd);
48
+ }
49
+
50
+ const remaining = readBuffer(cwd).entries.reduce((n, e) => n + e.ops.length, 0);
51
+ console.log(JSON.stringify({ discarded, entries, totalCount: remaining }));
@@ -0,0 +1,136 @@
1
+ /**
2
+ * Shared event validation for the live helper server.
3
+ * Extracted for unit testing (insert mode rules).
4
+ */
5
+
6
+ import { canCreateInsert } from './live-insert-ui.mjs';
7
+
8
+ export const VISUAL_ACTIONS = [
9
+ 'impeccable', 'bolder', 'quieter', 'distill', 'polish', 'typeset',
10
+ 'colorize', 'layout', 'adapt', 'animate', 'delight', 'overdrive',
11
+ ];
12
+
13
+ const ID_PATTERN = /^[0-9a-f]{8}$/;
14
+ const VARIANT_ID_PATTERN = /^[0-9]{1,3}$/;
15
+ const INSERT_POSITIONS = new Set(['before', 'after']);
16
+ const FORBIDDEN_MANUAL_EDIT_TEXT_CHARS = ['<', '{', '}', '`'];
17
+
18
+ function isValidId(v) { return typeof v === 'string' && ID_PATTERN.test(v); }
19
+ function isValidVariantId(v) { return typeof v === 'string' && VARIANT_ID_PATTERN.test(v); }
20
+
21
+ function validateManualEditText(newText) {
22
+ if (typeof newText !== 'string') return null;
23
+ const hits = FORBIDDEN_MANUAL_EDIT_TEXT_CHARS.filter((char) => newText.includes(char));
24
+ return hits.length > 0 ? hits : null;
25
+ }
26
+
27
+ function validateAnnotationFields(msg) {
28
+ if (msg.screenshotPath !== undefined && typeof msg.screenshotPath !== 'string') {
29
+ return 'generate: screenshotPath must be string';
30
+ }
31
+ if (msg.comments !== undefined && !Array.isArray(msg.comments)) {
32
+ return 'generate: comments must be array';
33
+ }
34
+ if (msg.strokes !== undefined && !Array.isArray(msg.strokes)) {
35
+ return 'generate: strokes must be array';
36
+ }
37
+ return null;
38
+ }
39
+
40
+ function validateInsertGenerate(msg) {
41
+ if (!msg.insert || typeof msg.insert !== 'object') return 'generate: insert mode requires insert object';
42
+ if (!INSERT_POSITIONS.has(msg.insert.position)) return 'generate: insert.position must be before or after';
43
+ const anchor = msg.insert.anchor;
44
+ if (!anchor || typeof anchor !== 'object') return 'generate: insert.anchor required';
45
+ if (!anchor.tagName && !anchor.outerHTML && !(Array.isArray(anchor.classes) && anchor.classes.length)) {
46
+ return 'generate: insert.anchor needs tagName, classes, or outerHTML';
47
+ }
48
+ if (!msg.placeholder || typeof msg.placeholder !== 'object') return 'generate: insert mode requires placeholder dimensions';
49
+ if (!Number.isFinite(msg.placeholder.width) || !Number.isFinite(msg.placeholder.height)) {
50
+ return 'generate: placeholder width and height must be numbers';
51
+ }
52
+ if (!canCreateInsert({
53
+ prompt: msg.freeformPrompt,
54
+ comments: msg.comments,
55
+ strokes: msg.strokes,
56
+ })) {
57
+ return 'generate: insert requires freeformPrompt or annotations';
58
+ }
59
+ return validateAnnotationFields(msg);
60
+ }
61
+
62
+ function validateReplaceGenerate(msg) {
63
+ if (!msg.action || !VISUAL_ACTIONS.includes(msg.action)) return 'generate: invalid action';
64
+ if (!msg.element || !msg.element.outerHTML) return 'generate: missing element context';
65
+ return validateAnnotationFields(msg);
66
+ }
67
+
68
+ function validateManualEditEvent(msg, label) {
69
+ if (!isValidId(msg.id)) return label + ': missing or malformed id';
70
+ if (!msg.pageUrl || typeof msg.pageUrl !== 'string') return label + ': missing pageUrl';
71
+ if (!msg.element || typeof msg.element !== 'object') return label + ': missing element';
72
+ if (!Array.isArray(msg.ops) || msg.ops.length === 0) return label + ': ops must be non-empty array';
73
+ if (msg.ops.length > 100) return label + ': too many ops (max 100)';
74
+ for (const op of msg.ops) {
75
+ if (typeof op.ref !== 'string') return label + ': op.ref required';
76
+ if (typeof op.tag !== 'string') return label + ': op.tag required';
77
+ if (typeof op.originalText !== 'string') return label + ': op.originalText required';
78
+ if (op.deleted !== true && typeof op.newText !== 'string') {
79
+ return label + ': text op requires newText';
80
+ }
81
+ if (typeof op.newText === 'string') {
82
+ if (op.deleted !== true && op.newText.trim().length === 0) {
83
+ return label + ': newText cannot be empty';
84
+ }
85
+ const forbidden = validateManualEditText(op.newText);
86
+ if (forbidden) {
87
+ return label + ': newText cannot contain ' + forbidden.join(' ') + ' (plain text only; ask the AI to insert markup)';
88
+ }
89
+ }
90
+ }
91
+ return null;
92
+ }
93
+
94
+ export function validateEvent(msg) {
95
+ if (!msg || typeof msg !== 'object' || !msg.type) return 'Missing or invalid message';
96
+ switch (msg.type) {
97
+ case 'generate':
98
+ if (!isValidId(msg.id)) return 'generate: missing or malformed id';
99
+ if (!Number.isInteger(msg.count) || msg.count < 1 || msg.count > 8) return 'generate: count must be 1-8';
100
+ if (msg.mode === 'insert') return validateInsertGenerate(msg);
101
+ return validateReplaceGenerate(msg);
102
+ case 'accept':
103
+ if (!isValidId(msg.id)) return 'accept: missing or malformed id';
104
+ if (!isValidVariantId(msg.variantId)) return 'accept: missing or malformed variantId';
105
+ if (msg.paramValues !== undefined) {
106
+ if (typeof msg.paramValues !== 'object' || msg.paramValues === null || Array.isArray(msg.paramValues)) {
107
+ return 'accept: paramValues must be an object';
108
+ }
109
+ }
110
+ return null;
111
+ case 'discard':
112
+ return isValidId(msg.id) ? null : 'discard: missing or malformed id';
113
+ case 'checkpoint':
114
+ if (!isValidId(msg.id)) return 'checkpoint: missing or malformed id';
115
+ if (!Number.isInteger(msg.revision) || msg.revision < 0) return 'checkpoint: revision must be a non-negative integer';
116
+ if (msg.paramValues !== undefined && (typeof msg.paramValues !== 'object' || msg.paramValues === null || Array.isArray(msg.paramValues))) {
117
+ return 'checkpoint: paramValues must be an object';
118
+ }
119
+ return null;
120
+ case 'exit':
121
+ return null;
122
+ case 'prefetch':
123
+ if (!msg.pageUrl || typeof msg.pageUrl !== 'string') return 'prefetch: missing pageUrl';
124
+ return null;
125
+ case 'manual_edits':
126
+ return validateManualEditEvent(msg, 'manual_edits');
127
+ case 'steer':
128
+ if (!isValidId(msg.id)) return 'steer: missing or malformed id';
129
+ if (typeof msg.message !== 'string' || !msg.message.trim()) return 'steer: message required';
130
+ if (msg.message.length > 4000) return 'steer: message too long';
131
+ if (msg.pageUrl !== undefined && typeof msg.pageUrl !== 'string') return 'steer: pageUrl must be string';
132
+ return null;
133
+ default:
134
+ return 'Unknown event type: ' + msg.type;
135
+ }
136
+ }
@@ -116,7 +116,7 @@ Output (JSON):
116
116
  if (!fs.existsSync(absFile)) return { file: relFile, error: 'file_not_found' };
117
117
  const content = fs.readFileSync(absFile, 'utf-8');
118
118
  const withoutOld = revertCspMeta(removeTag(content, config.commentSyntax));
119
- const withTag = insertTag(withoutOld, config, port);
119
+ const withTag = insertTag(withoutOld, config, port, relFile);
120
120
  if (withTag === withoutOld) {
121
121
  return { file: relFile, error: 'insertion_point_not_found', anchor: config.insertBefore || config.insertAfter };
122
122
  }
@@ -256,18 +256,22 @@ function validateConfig(cfg) {
256
256
  function commentOpen(syntax) { return syntax === 'jsx' ? '{/*' : '<!--'; }
257
257
  function commentClose(syntax) { return syntax === 'jsx' ? '*/}' : '-->'; }
258
258
 
259
- function buildTagBlock(syntax, port) {
259
+ function buildTagBlock(syntax, port, filePath) {
260
260
  const open = commentOpen(syntax);
261
261
  const close = commentClose(syntax);
262
+ // Astro processes <script> tags by default and rewrites src to its own
263
+ // bundled URL. is:inline opts out so the literal external src survives.
264
+ const isAstro = typeof filePath === 'string' && filePath.endsWith('.astro');
265
+ const scriptAttrs = isAstro ? 'is:inline ' : '';
262
266
  return (
263
267
  open + ' ' + MARKER_OPEN_TEXT + ' ' + close + '\n' +
264
- '<script src="http://localhost:' + port + '/live.js"></script>\n' +
268
+ '<script ' + scriptAttrs + 'src="http://localhost:' + port + '/live.js"></script>\n' +
265
269
  open + ' ' + MARKER_CLOSE_TEXT + ' ' + close + '\n'
266
270
  );
267
271
  }
268
272
 
269
- function insertTag(content, config, port) {
270
- const block = buildTagBlock(config.commentSyntax, port);
273
+ function insertTag(content, config, port, filePath) {
274
+ const block = buildTagBlock(config.commentSyntax, port, filePath);
271
275
  // insertBefore: match the LAST occurrence. Anchors like `</body>` naturally
272
276
  // belong at the end, and the same literal can appear earlier in code blocks
273
277
  // within rendered documentation pages.
@@ -299,12 +303,21 @@ function insertTag(content, config, port) {
299
303
  */
300
304
  function removeTag(content, _syntax) {
301
305
  const patterns = [
302
- /([ \t]*)<!--\s*impeccable-live-start\s*-->[\s\S]*?<!--\s*impeccable-live-end\s*-->[ \t]*\n/,
303
- /([ \t]*)\{\/\*\s*impeccable-live-start\s*\*\/\}[\s\S]*?\{\/\*\s*impeccable-live-end\s*\*\/\}[ \t]*\n/,
306
+ /([ \t]*)<!--\s*impeccable-live-start\s*-->[\s\S]*?<!--\s*impeccable-live-end\s*-->([ \t]*(?:\n|$)?)/,
307
+ /([ \t]*)\{\/\*\s*impeccable-live-start\s*\*\/\}[\s\S]*?\{\/\*\s*impeccable-live-end\s*\*\/\}([ \t]*(?:\n|$)?)/,
304
308
  ];
305
309
  for (const pat of patterns) {
306
- const next = content.replace(pat, '$1');
307
- if (next !== content) return next;
310
+ let changed = false;
311
+ let next = content;
312
+ do {
313
+ content = next;
314
+ next = content.replace(pat, (_match, leadingIndent, trailing = '') => {
315
+ if (trailing.includes('\n')) return leadingIndent;
316
+ return leadingIndent || trailing || '';
317
+ });
318
+ if (next !== content) changed = true;
319
+ } while (next !== content);
320
+ if (changed) return next;
308
321
  }
309
322
  return content;
310
323
  }