@bastani/atomic 0.9.0-alpha.3 → 0.9.0-alpha.4

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 (84) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/dist/builtin/cursor/package.json +2 -2
  3. package/dist/builtin/intercom/package.json +1 -1
  4. package/dist/builtin/mcp/package.json +1 -1
  5. package/dist/builtin/subagents/package.json +1 -1
  6. package/dist/builtin/web-access/package.json +1 -1
  7. package/dist/builtin/workflows/CHANGELOG.md +17 -0
  8. package/dist/builtin/workflows/README.md +12 -12
  9. package/dist/builtin/workflows/builtin/goal-prompts.ts +8 -0
  10. package/dist/builtin/workflows/builtin/goal-runner.ts +96 -1
  11. package/dist/builtin/workflows/builtin/goal-types.ts +2 -0
  12. package/dist/builtin/workflows/builtin/goal.d.ts +3 -0
  13. package/dist/builtin/workflows/builtin/goal.ts +12 -1
  14. package/dist/builtin/workflows/builtin/index.d.ts +8 -8
  15. package/dist/builtin/workflows/builtin/open-claude-design-feedback.ts +359 -0
  16. package/dist/builtin/workflows/builtin/open-claude-design-phases.ts +254 -352
  17. package/dist/builtin/workflows/builtin/open-claude-design-runner.ts +256 -414
  18. package/dist/builtin/workflows/builtin/open-claude-design-setup.ts +272 -0
  19. package/dist/builtin/workflows/builtin/open-claude-design-utils.ts +58 -68
  20. package/dist/builtin/workflows/builtin/open-claude-design.d.ts +5 -9
  21. package/dist/builtin/workflows/builtin/open-claude-design.ts +14 -26
  22. package/dist/builtin/workflows/package.json +1 -1
  23. package/dist/builtin/workflows/skills/impeccable/SKILL.md +14 -23
  24. package/dist/builtin/workflows/skills/impeccable/reference/brand.md +2 -2
  25. package/dist/builtin/workflows/skills/impeccable/reference/live.md +25 -4
  26. package/dist/builtin/workflows/skills/impeccable/scripts/context-signals.mjs +1 -1
  27. package/dist/builtin/workflows/skills/impeccable/scripts/context.mjs +724 -29
  28. package/dist/builtin/workflows/skills/impeccable/scripts/critique-storage.mjs +1 -1
  29. package/dist/builtin/workflows/skills/impeccable/scripts/detector/browser/injected/index.mjs +219 -7
  30. package/dist/builtin/workflows/skills/impeccable/scripts/detector/cli/main.mjs +57 -11
  31. package/dist/builtin/workflows/skills/impeccable/scripts/detector/design-system.mjs +750 -0
  32. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns-browser.js +648 -53
  33. package/dist/builtin/workflows/skills/impeccable/scripts/detector/detect-antipatterns.mjs +7 -0
  34. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/browser/detect-url.mjs +29 -4
  35. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/regex/detect-text.mjs +44 -11
  36. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +29 -0
  37. package/dist/builtin/workflows/skills/impeccable/scripts/detector/engines/static-html/detect-html.mjs +27 -1
  38. package/dist/builtin/workflows/skills/impeccable/scripts/detector/node/file-system.mjs +1 -1
  39. package/dist/builtin/workflows/skills/impeccable/scripts/detector/registry/antipatterns.mjs +29 -0
  40. package/dist/builtin/workflows/skills/impeccable/scripts/detector/rules/checks.mjs +401 -46
  41. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
  42. package/dist/builtin/workflows/skills/impeccable/scripts/detector/shared/page.mjs +6 -6
  43. package/dist/builtin/workflows/skills/impeccable/scripts/{design-parser.mjs → lib/design-parser.mjs} +8 -1
  44. package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-config.mjs +638 -0
  45. package/dist/builtin/workflows/skills/impeccable/scripts/lib/impeccable-paths.mjs +128 -0
  46. package/dist/builtin/workflows/skills/impeccable/scripts/{is-generated.mjs → lib/is-generated.mjs} +2 -2
  47. package/dist/builtin/workflows/skills/impeccable/scripts/lib/target-args.mjs +42 -0
  48. package/dist/builtin/workflows/skills/impeccable/scripts/live/browser-script-parts.mjs +49 -0
  49. package/dist/builtin/workflows/skills/impeccable/scripts/{live-completion.mjs → live/completion.mjs} +1 -0
  50. package/dist/builtin/workflows/skills/impeccable/scripts/{live-event-validation.mjs → live/event-validation.mjs} +6 -5
  51. package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-apply.mjs +939 -0
  52. package/dist/builtin/workflows/skills/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
  53. package/dist/builtin/workflows/skills/impeccable/scripts/{live-manual-edits-buffer.mjs → live/manual-edits-buffer.mjs} +1 -1
  54. package/dist/builtin/workflows/skills/impeccable/scripts/{live-session-store.mjs → live/session-store.mjs} +21 -3
  55. package/dist/builtin/workflows/skills/impeccable/scripts/live/svelte-component.mjs +835 -0
  56. package/dist/builtin/workflows/skills/impeccable/scripts/live/sveltekit-adapter.mjs +274 -0
  57. package/dist/builtin/workflows/skills/impeccable/scripts/live/ui-core.mjs +180 -0
  58. package/dist/builtin/workflows/skills/impeccable/scripts/live/vocabulary.mjs +36 -0
  59. package/dist/builtin/workflows/skills/impeccable/scripts/live-accept.mjs +185 -60
  60. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser-dom.js +146 -0
  61. package/dist/builtin/workflows/skills/impeccable/scripts/live-browser.js +3369 -1026
  62. package/dist/builtin/workflows/skills/impeccable/scripts/live-commit-manual-edits.mjs +2 -2
  63. package/dist/builtin/workflows/skills/impeccable/scripts/live-complete.mjs +2 -2
  64. package/dist/builtin/workflows/skills/impeccable/scripts/live-discard-manual-edits.mjs +1 -1
  65. package/dist/builtin/workflows/skills/impeccable/scripts/live-inject.mjs +133 -9
  66. package/dist/builtin/workflows/skills/impeccable/scripts/live-insert.mjs +42 -2
  67. package/dist/builtin/workflows/skills/impeccable/scripts/live-manual-edit-evidence.mjs +4 -4
  68. package/dist/builtin/workflows/skills/impeccable/scripts/live-poll.mjs +21 -15
  69. package/dist/builtin/workflows/skills/impeccable/scripts/live-resume.mjs +1 -1
  70. package/dist/builtin/workflows/skills/impeccable/scripts/live-server.mjs +205 -1269
  71. package/dist/builtin/workflows/skills/impeccable/scripts/live-status.mjs +2 -2
  72. package/dist/builtin/workflows/skills/impeccable/scripts/live-target.mjs +30 -0
  73. package/dist/builtin/workflows/skills/impeccable/scripts/live-wrap.mjs +69 -26
  74. package/dist/builtin/workflows/skills/impeccable/scripts/live.mjs +73 -22
  75. package/dist/core/atomic-guide-command.d.ts.map +1 -1
  76. package/dist/core/atomic-guide-command.js +5 -5
  77. package/dist/core/atomic-guide-command.js.map +1 -1
  78. package/docs/index.md +2 -2
  79. package/docs/quickstart.md +9 -9
  80. package/docs/workflows.md +42 -23
  81. package/package.json +2 -2
  82. package/dist/builtin/workflows/skills/impeccable/scripts/cleanup-deprecated.mjs +0 -284
  83. package/dist/builtin/workflows/skills/impeccable/scripts/impeccable-paths.mjs +0 -126
  84. /package/dist/builtin/workflows/skills/impeccable/scripts/{live-insert-ui.mjs → live/insert-ui.mjs} +0 -0
@@ -1,284 +0,0 @@
1
- #!/usr/bin/env node
2
- /**
3
- * Cleans up deprecated Impeccable skill files, symlinks, and
4
- * skills-lock.json entries left over from previous versions.
5
- *
6
- * Safe to run repeatedly -- it is a no-op when nothing needs cleaning.
7
- *
8
- * Usage (from the project root):
9
- * node {{scripts_path}}/cleanup-deprecated.mjs
10
- *
11
- * What it does:
12
- * 1. Finds every harness-specific skills directory (.claude/skills,
13
- * .cursor/skills, .agents/skills, etc.).
14
- * 2. For each deprecated skill name (with and without i- prefix),
15
- * checks if the directory exists and its SKILL.md mentions
16
- * "impeccable" (to avoid deleting unrelated user skills).
17
- * 3. Deletes confirmed matches (files, directories, or symlinks).
18
- * 4. Removes the corresponding entries from skills-lock.json.
19
- */
20
-
21
- import { existsSync, readFileSync, writeFileSync, rmSync, readdirSync, statSync, lstatSync, unlinkSync } from 'node:fs';
22
- import { join, resolve } from 'node:path';
23
-
24
- // Skills that were renamed, merged, or folded in v2.0, v2.1, and v3.0.
25
- const DEPRECATED_NAMES = [
26
- // v2.0 renames
27
- 'frontend-design', // renamed to impeccable
28
- 'teach-impeccable', // folded into /impeccable init
29
- // v2.1 merges
30
- 'arrange', // renamed to layout
31
- 'normalize', // merged into polish
32
- 'onboard', // merged into harden
33
- 'extract', // merged into /impeccable extract
34
- // v3.0 consolidation: all standalone skills -> /impeccable sub-commands
35
- 'adapt',
36
- 'animate',
37
- 'audit',
38
- 'bolder',
39
- 'clarify',
40
- 'colorize',
41
- 'critique',
42
- 'delight',
43
- 'distill',
44
- 'harden',
45
- 'layout',
46
- 'optimize',
47
- 'overdrive',
48
- 'polish',
49
- 'quieter',
50
- 'shape',
51
- 'typeset',
52
- ];
53
-
54
- // All known harness directories that may contain a skills/ subfolder.
55
- const HARNESS_DIRS = [
56
- '.claude', '.cursor', '.gemini', '.codex', '.agents',
57
- '.trae', '.trae-cn', '.pi', '.opencode', '.kiro', '.rovodev',
58
- ];
59
-
60
- // Per-skill fingerprints for SKILL.md bodies that never mentioned
61
- // "impeccable" in their v2.x source. Used as a last-resort match
62
- // when no skills-lock.json exists and the word heuristic fails.
63
- // The strings are lifted verbatim from the v2.x frontmatter
64
- // descriptions, so collisions with hand-written user skills are
65
- // vanishingly unlikely.
66
- const SKILL_FINGERPRINTS = {
67
- harden: 'Make interfaces production-ready: error handling, empty states',
68
- optimize: 'Diagnoses and fixes UI performance across loading speed',
69
- };
70
-
71
- /**
72
- * Walk up from startDir until we find a directory that looks like a
73
- * project root (has package.json, .git, or skills-lock.json).
74
- */
75
- export function findProjectRoot(startDir = process.cwd()) {
76
- let dir = resolve(startDir);
77
- const { root } = { root: '/' };
78
- while (dir !== root) {
79
- if (
80
- existsSync(join(dir, 'package.json')) ||
81
- existsSync(join(dir, '.git')) ||
82
- existsSync(join(dir, 'skills-lock.json'))
83
- ) {
84
- return dir;
85
- }
86
- const parent = resolve(dir, '..');
87
- if (parent === dir) break;
88
- dir = parent;
89
- }
90
- return resolve(startDir);
91
- }
92
-
93
- /**
94
- * Load skills-lock.json from the project root, or null if missing/unreadable.
95
- */
96
- export function loadLock(projectRoot) {
97
- const lockPath = join(projectRoot, 'skills-lock.json');
98
- if (!existsSync(lockPath)) return null;
99
- try {
100
- return JSON.parse(readFileSync(lockPath, 'utf-8'));
101
- } catch {
102
- return null;
103
- }
104
- }
105
-
106
- /**
107
- * Check whether a skill directory belongs to Impeccable. Three layered
108
- * signals, in order of reliability:
109
- * 1. Lock source equals "pbakaus/impeccable" (authoritative).
110
- * 2. SKILL.md body contains the word "impeccable".
111
- * 3. SKILL.md body contains a per-skill fingerprint (for harden and
112
- * optimize, whose v2.x SKILL.md never mentioned the pack name).
113
- */
114
- export function isImpeccableSkill(skillDir, { skillName, lock } = {}) {
115
- // 1. Authoritative: the lock file claims this skill is ours.
116
- if (skillName && lock?.skills?.[skillName]?.source === 'pbakaus/impeccable') {
117
- return true;
118
- }
119
- const skillMd = join(skillDir, 'SKILL.md');
120
- if (!existsSync(skillMd)) return false;
121
- let content;
122
- try {
123
- content = readFileSync(skillMd, 'utf-8');
124
- } catch {
125
- return false;
126
- }
127
- // 2. Word-level content heuristic.
128
- if (/impeccable/i.test(content)) return true;
129
- // 3. Per-skill fingerprint for old skills that never mentioned the pack.
130
- // Strip the i- prefix so both `harden` and `i-harden` resolve to the
131
- // same fingerprint entry.
132
- const unprefixed = skillName?.startsWith('i-') ? skillName.slice(2) : skillName;
133
- const fingerprint = unprefixed && SKILL_FINGERPRINTS[unprefixed];
134
- if (fingerprint && content.includes(fingerprint)) return true;
135
- return false;
136
- }
137
-
138
- /**
139
- * Build the full list of names to check: each deprecated name, plus
140
- * its i-prefixed variant.
141
- */
142
- export function buildTargetNames() {
143
- const names = [];
144
- for (const name of DEPRECATED_NAMES) {
145
- names.push(name);
146
- names.push(`i-${name}`);
147
- }
148
- return names;
149
- }
150
-
151
- /**
152
- * Find every skills directory across all harness dirs in the project.
153
- * Returns absolute paths that exist on disk.
154
- */
155
- export function findSkillsDirs(projectRoot) {
156
- const dirs = [];
157
- for (const harness of HARNESS_DIRS) {
158
- const candidate = join(projectRoot, harness, 'skills');
159
- if (existsSync(candidate)) {
160
- dirs.push(candidate);
161
- }
162
- }
163
- return dirs;
164
- }
165
-
166
- /**
167
- * Remove deprecated skill directories/symlinks from all harness dirs.
168
- * Reads skills-lock.json so the authoritative "source" field can
169
- * drive deletion even when SKILL.md never mentions impeccable.
170
- * Returns an array of paths that were deleted.
171
- */
172
- export function removeDeprecatedSkills(projectRoot, lock) {
173
- if (lock === undefined) lock = loadLock(projectRoot);
174
- const targets = buildTargetNames();
175
- const skillsDirs = findSkillsDirs(projectRoot);
176
- const deleted = [];
177
-
178
- for (const skillsDir of skillsDirs) {
179
- for (const name of targets) {
180
- const skillPath = join(skillsDir, name);
181
-
182
- // Use lstat to detect symlinks (existsSync follows symlinks and
183
- // returns false for dangling ones).
184
- let stat;
185
- try {
186
- stat = lstatSync(skillPath);
187
- } catch {
188
- continue; // does not exist at all
189
- }
190
-
191
- if (stat.isSymbolicLink()) {
192
- // Symlink: check the target if it's alive, otherwise treat
193
- // dangling symlinks to deprecated names as safe to remove.
194
- const targetAlive = existsSync(skillPath);
195
- const isMatch = targetAlive
196
- ? isImpeccableSkill(skillPath, { skillName: name, lock })
197
- : true;
198
- if (isMatch) {
199
- unlinkSync(skillPath);
200
- deleted.push(skillPath);
201
- }
202
- continue;
203
- }
204
-
205
- // Regular directory -- verify it belongs to impeccable
206
- if (isImpeccableSkill(skillPath, { skillName: name, lock })) {
207
- rmSync(skillPath, { recursive: true, force: true });
208
- deleted.push(skillPath);
209
- }
210
- }
211
- }
212
-
213
- return deleted;
214
- }
215
-
216
- /**
217
- * Remove deprecated entries from skills-lock.json.
218
- * Only removes entries whose source is "pbakaus/impeccable".
219
- * Returns the list of removed skill names.
220
- */
221
- export function cleanSkillsLock(projectRoot) {
222
- const lockPath = join(projectRoot, 'skills-lock.json');
223
- if (!existsSync(lockPath)) return [];
224
-
225
- let lock;
226
- try {
227
- lock = JSON.parse(readFileSync(lockPath, 'utf-8'));
228
- } catch {
229
- return [];
230
- }
231
-
232
- if (!lock.skills || typeof lock.skills !== 'object') return [];
233
-
234
- const targets = buildTargetNames();
235
- const removed = [];
236
-
237
- for (const name of targets) {
238
- const entry = lock.skills[name];
239
- if (!entry) continue;
240
- // Only remove if it belongs to impeccable
241
- if (entry.source === 'pbakaus/impeccable') {
242
- delete lock.skills[name];
243
- removed.push(name);
244
- }
245
- }
246
-
247
- if (removed.length > 0) {
248
- writeFileSync(lockPath, JSON.stringify(lock, null, 2) + '\n', 'utf-8');
249
- }
250
-
251
- return removed;
252
- }
253
-
254
- /**
255
- * Run the full cleanup. Returns a summary object.
256
- *
257
- * Order matters: read the lock and delete directories first, then
258
- * strip lock entries. Otherwise the authoritative signal is gone by
259
- * the time directory deletion runs.
260
- */
261
- export function cleanup(projectRoot) {
262
- const root = projectRoot || findProjectRoot();
263
- const lock = loadLock(root);
264
- const deletedPaths = removeDeprecatedSkills(root, lock);
265
- const removedLockEntries = cleanSkillsLock(root);
266
- return { deletedPaths, removedLockEntries, projectRoot: root };
267
- }
268
-
269
- // CLI entry point
270
- if (process.argv[1] && resolve(process.argv[1]) === resolve(new URL(import.meta.url).pathname)) {
271
- const result = cleanup();
272
- if (result.deletedPaths.length === 0 && result.removedLockEntries.length === 0) {
273
- console.log('No deprecated Impeccable skills found. Nothing to clean up.');
274
- } else {
275
- if (result.deletedPaths.length > 0) {
276
- console.log(`Removed ${result.deletedPaths.length} deprecated skill(s):`);
277
- for (const p of result.deletedPaths) console.log(` - ${p}`);
278
- }
279
- if (result.removedLockEntries.length > 0) {
280
- console.log(`Cleaned ${result.removedLockEntries.length} entry/entries from skills-lock.json:`);
281
- for (const name of result.removedLockEntries) console.log(` - ${name}`);
282
- }
283
- }
284
- }
@@ -1,126 +0,0 @@
1
- import fs from 'node:fs';
2
- import path from 'node:path';
3
-
4
- export const IMPECCABLE_DIR = '.impeccable';
5
- export const LIVE_DIR = 'live';
6
- export const CRITIQUE_DIR = 'critique';
7
-
8
- export function getImpeccableDir(cwd = process.cwd()) {
9
- return path.join(cwd, IMPECCABLE_DIR);
10
- }
11
-
12
- export function getDesignSidecarPath(cwd = process.cwd()) {
13
- return path.join(getImpeccableDir(cwd), 'design.json');
14
- }
15
-
16
- export function getDesignSidecarCandidates(cwd = process.cwd(), contextDir = cwd) {
17
- const candidates = [
18
- getDesignSidecarPath(cwd),
19
- path.join(cwd, 'DESIGN.json'),
20
- ];
21
- const contextLegacy = path.join(contextDir, 'DESIGN.json');
22
- if (!candidates.includes(contextLegacy)) candidates.push(contextLegacy);
23
- return candidates;
24
- }
25
-
26
- export function resolveDesignSidecarPath(cwd = process.cwd(), contextDir = cwd) {
27
- return firstExisting(getDesignSidecarCandidates(cwd, contextDir));
28
- }
29
-
30
- export function getLiveDir(cwd = process.cwd()) {
31
- return path.join(getImpeccableDir(cwd), LIVE_DIR);
32
- }
33
-
34
- export function getLiveConfigPath(cwd = process.cwd()) {
35
- return path.join(getLiveDir(cwd), 'config.json');
36
- }
37
-
38
- export function getLegacyLiveConfigPath(scriptsDir) {
39
- return path.join(scriptsDir, 'config.json');
40
- }
41
-
42
- export function resolveLiveConfigPath({ cwd = process.cwd(), scriptsDir, env = process.env } = {}) {
43
- if (env.IMPECCABLE_LIVE_CONFIG && env.IMPECCABLE_LIVE_CONFIG.trim()) {
44
- const configured = env.IMPECCABLE_LIVE_CONFIG.trim();
45
- return path.isAbsolute(configured) ? configured : path.resolve(cwd, configured);
46
- }
47
- const primary = getLiveConfigPath(cwd);
48
- if (fs.existsSync(primary)) return primary;
49
- if (scriptsDir) {
50
- const legacy = getLegacyLiveConfigPath(scriptsDir);
51
- if (fs.existsSync(legacy)) return legacy;
52
- }
53
- return primary;
54
- }
55
-
56
- export function getLiveServerPath(cwd = process.cwd()) {
57
- return path.join(getLiveDir(cwd), 'server.json');
58
- }
59
-
60
- export function getLegacyLiveServerPath(cwd = process.cwd()) {
61
- return path.join(cwd, '.impeccable-live.json');
62
- }
63
-
64
- export function readLiveServerInfo(cwd = process.cwd()) {
65
- for (const filePath of [getLiveServerPath(cwd), getLegacyLiveServerPath(cwd)]) {
66
- try {
67
- const info = JSON.parse(fs.readFileSync(filePath, 'utf-8'));
68
- if (info && typeof info.pid === 'number' && !isLiveServerPidReachable(info.pid)) {
69
- try { fs.unlinkSync(filePath); } catch {}
70
- continue;
71
- }
72
- return { info, path: filePath };
73
- } catch {
74
- /* try next */
75
- }
76
- }
77
- return null;
78
- }
79
-
80
- export function isLiveServerPidReachable(pid) {
81
- try {
82
- process.kill(pid, 0);
83
- return true;
84
- } catch (err) {
85
- // ESRCH means "no such process". EPERM means the process exists but this
86
- // user cannot signal it, so the live server info is still valid.
87
- return err?.code !== 'ESRCH';
88
- }
89
- }
90
-
91
- export function writeLiveServerInfo(cwd = process.cwd(), info) {
92
- const filePath = getLiveServerPath(cwd);
93
- fs.mkdirSync(path.dirname(filePath), { recursive: true });
94
- fs.writeFileSync(filePath, JSON.stringify(info));
95
- return filePath;
96
- }
97
-
98
- export function removeLiveServerInfo(cwd = process.cwd()) {
99
- for (const filePath of [getLiveServerPath(cwd), getLegacyLiveServerPath(cwd)]) {
100
- try { fs.unlinkSync(filePath); } catch {}
101
- }
102
- }
103
-
104
- export function getLiveSessionsDir(cwd = process.cwd()) {
105
- return path.join(getLiveDir(cwd), 'sessions');
106
- }
107
-
108
- export function getLegacyLiveSessionsDir(cwd = process.cwd()) {
109
- return path.join(cwd, '.impeccable-live', 'sessions');
110
- }
111
-
112
- export function getLiveAnnotationsDir(cwd = process.cwd()) {
113
- return path.join(getLiveDir(cwd), 'annotations');
114
- }
115
-
116
- export function getCritiqueDir(cwd = process.cwd()) {
117
- return path.join(getImpeccableDir(cwd), CRITIQUE_DIR);
118
- }
119
-
120
- export function getLegacyLiveAnnotationsDir(cwd = process.cwd()) {
121
- return path.join(cwd, '.impeccable-live', 'annotations');
122
- }
123
-
124
- function firstExisting(paths) {
125
- return paths.find((filePath) => fs.existsSync(filePath)) || null;
126
- }