@nextsparkjs/ai-workflow 0.1.0-beta.100
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/LICENSE +21 -0
- package/README.md +115 -0
- package/claude/_docs/workflows-optimizations.md +359 -0
- package/claude/agents/api-tester.md +634 -0
- package/claude/agents/architecture-supervisor.md +1351 -0
- package/claude/agents/backend-developer.md +997 -0
- package/claude/agents/backend-validator.md +417 -0
- package/claude/agents/bdd-docs-writer.md +737 -0
- package/claude/agents/block-developer.md +677 -0
- package/claude/agents/code-reviewer.md +1432 -0
- package/claude/agents/db-developer.md +721 -0
- package/claude/agents/db-validator.md +407 -0
- package/claude/agents/demo-video-generator.md +493 -0
- package/claude/agents/documentation-writer.md +1268 -0
- package/claude/agents/frontend-developer.md +1234 -0
- package/claude/agents/frontend-validator.md +777 -0
- package/claude/agents/functional-validator.md +630 -0
- package/claude/agents/mock-analyst.md +387 -0
- package/claude/agents/product-manager.md +963 -0
- package/claude/agents/qa-automation.md +1762 -0
- package/claude/agents/release-manager.md +634 -0
- package/claude/agents/selectors-translator.md +262 -0
- package/claude/agents/unit-test-writer.md +785 -0
- package/claude/agents/visual-comparator.md +329 -0
- package/claude/agents/workflow-maintainer.md +352 -0
- package/claude/commands/do/README.md +88 -0
- package/claude/commands/do/create-api.md +64 -0
- package/claude/commands/do/create-entity.md +66 -0
- package/claude/commands/do/create-migration.md +64 -0
- package/claude/commands/do/create-plugin.md +56 -0
- package/claude/commands/do/create-theme.md +70 -0
- package/claude/commands/do/mock-data.md +67 -0
- package/claude/commands/do/reset-db.md +71 -0
- package/claude/commands/do/setup-scheduled-action.md +75 -0
- package/claude/commands/do/sync-code-review.md +117 -0
- package/claude/commands/do/update-selectors.md +112 -0
- package/claude/commands/do/use-skills.md +90 -0
- package/claude/commands/do/validate-blocks.md +69 -0
- package/claude/commands/how-to/README.md +261 -0
- package/claude/commands/how-to/add-metadata.md +692 -0
- package/claude/commands/how-to/add-taxonomies.md +806 -0
- package/claude/commands/how-to/add-translations.md +571 -0
- package/claude/commands/how-to/create-api.md +577 -0
- package/claude/commands/how-to/create-block.md +575 -0
- package/claude/commands/how-to/create-child-entities.md +771 -0
- package/claude/commands/how-to/create-entity.md +597 -0
- package/claude/commands/how-to/create-migrations.md +605 -0
- package/claude/commands/how-to/create-plugin.md +654 -0
- package/claude/commands/how-to/customize-app.md +481 -0
- package/claude/commands/how-to/customize-dashboard.md +553 -0
- package/claude/commands/how-to/customize-theme.md +438 -0
- package/claude/commands/how-to/define-features-flows.md +632 -0
- package/claude/commands/how-to/deploy.md +507 -0
- package/claude/commands/how-to/handle-file-uploads.md +746 -0
- package/claude/commands/how-to/implement-search.md +1001 -0
- package/claude/commands/how-to/install-plugins.md +352 -0
- package/claude/commands/how-to/manage-test-coverage.md +984 -0
- package/claude/commands/how-to/run-tests.md +400 -0
- package/claude/commands/how-to/set-app-languages.md +601 -0
- package/claude/commands/how-to/set-plans-and-permissions.md +575 -0
- package/claude/commands/how-to/set-scheduled-actions.md +527 -0
- package/claude/commands/how-to/set-user-roles-and-permissions.md +550 -0
- package/claude/commands/how-to/setup-authentication.md +388 -0
- package/claude/commands/how-to/setup-claude-code.md +440 -0
- package/claude/commands/how-to/setup-database.md +274 -0
- package/claude/commands/how-to/setup-email-providers.md +598 -0
- package/claude/commands/how-to/setup-mobile-dev.md +627 -0
- package/claude/commands/how-to/start.md +500 -0
- package/claude/commands/how-to/use-devtools.md +639 -0
- package/claude/commands/how-to/use-superadmin.md +622 -0
- package/claude/commands/session/README.md +193 -0
- package/claude/commands/session/block-create.md +190 -0
- package/claude/commands/session/block-list.md +203 -0
- package/claude/commands/session/block-update.md +192 -0
- package/claude/commands/session/block-validate.md +218 -0
- package/claude/commands/session/changelog.md +115 -0
- package/claude/commands/session/close.md +225 -0
- package/claude/commands/session/commit.md +174 -0
- package/claude/commands/session/db-entity.md +206 -0
- package/claude/commands/session/db-fix.md +212 -0
- package/claude/commands/session/db-sample.md +206 -0
- package/claude/commands/session/demo.md +178 -0
- package/claude/commands/session/doc-bdd.md +207 -0
- package/claude/commands/session/doc-feature.md +218 -0
- package/claude/commands/session/doc-read.md +225 -0
- package/claude/commands/session/execute.md +204 -0
- package/claude/commands/session/explain.md +202 -0
- package/claude/commands/session/fix-bug.md +210 -0
- package/claude/commands/session/fix-build.md +182 -0
- package/claude/commands/session/fix-test.md +189 -0
- package/claude/commands/session/pending.md +232 -0
- package/claude/commands/session/refine.md +188 -0
- package/claude/commands/session/resume.md +192 -0
- package/claude/commands/session/review.md +192 -0
- package/claude/commands/session/scope-change.md +181 -0
- package/claude/commands/session/start-blocks.md +347 -0
- package/claude/commands/session/start.md +604 -0
- package/claude/commands/session/status.md +169 -0
- package/claude/commands/session/test-fix.md +221 -0
- package/claude/commands/session/test-run.md +203 -0
- package/claude/commands/session/test-write.md +242 -0
- package/claude/commands/session/validate.md +162 -0
- package/claude/config/context.json +40 -0
- package/claude/config/github.json +69 -0
- package/claude/config/github.schema.json +106 -0
- package/claude/config/team.json +46 -0
- package/claude/config/team.schema.json +106 -0
- package/claude/config/workspace.json +43 -0
- package/claude/config/workspace.schema.json +75 -0
- package/claude/skills/README.md +228 -0
- package/claude/skills/accessibility/SKILL.md +573 -0
- package/claude/skills/api-bypass-layers/SKILL.md +550 -0
- package/claude/skills/asana-integration/SKILL.md +499 -0
- package/claude/skills/better-auth/SKILL.md +666 -0
- package/claude/skills/billing-subscriptions/SKILL.md +660 -0
- package/claude/skills/block-decision-matrix/SKILL.md +359 -0
- package/claude/skills/clickup-integration/SKILL.md +434 -0
- package/claude/skills/core-theme-responsibilities/SKILL.md +485 -0
- package/claude/skills/create-plugin/SKILL.md +425 -0
- package/claude/skills/create-theme/SKILL.md +331 -0
- package/claude/skills/cypress-api/SKILL.md +511 -0
- package/claude/skills/cypress-api/scripts/generate-api-controller.py +329 -0
- package/claude/skills/cypress-api/scripts/generate-api-test.py +930 -0
- package/claude/skills/cypress-e2e/SKILL.md +526 -0
- package/claude/skills/cypress-e2e/scripts/extract-selectors.py +383 -0
- package/claude/skills/cypress-e2e/scripts/generate-uat-test.py +788 -0
- package/claude/skills/cypress-selectors/SKILL.md +309 -0
- package/claude/skills/cypress-selectors/scripts/extract-missing.py +243 -0
- package/claude/skills/cypress-selectors/scripts/generate-block-selectors.py +283 -0
- package/claude/skills/cypress-selectors/scripts/validate-selectors.py +145 -0
- package/claude/skills/database-migrations/SKILL.md +335 -0
- package/claude/skills/database-migrations/scripts/generate-sample-data.py +284 -0
- package/claude/skills/database-migrations/scripts/validate-migration.py +323 -0
- package/claude/skills/design-system/SKILL.md +682 -0
- package/claude/skills/documentation/SKILL.md +540 -0
- package/claude/skills/entity-api/SKILL.md +482 -0
- package/claude/skills/entity-system/SKILL.md +635 -0
- package/claude/skills/entity-system/scripts/generate-child-migration.py +298 -0
- package/claude/skills/entity-system/scripts/generate-metas-migration.py +233 -0
- package/claude/skills/entity-system/scripts/generate-migration.py +382 -0
- package/claude/skills/entity-system/scripts/generate-sample-data.py +418 -0
- package/claude/skills/entity-system/scripts/scaffold-entity.py +661 -0
- package/claude/skills/github/SKILL.md +467 -0
- package/claude/skills/i18n-nextintl/SKILL.md +302 -0
- package/claude/skills/i18n-nextintl/scripts/add-translation.py +243 -0
- package/claude/skills/i18n-nextintl/scripts/extract-hardcoded.py +246 -0
- package/claude/skills/i18n-nextintl/scripts/validate-translations.py +260 -0
- package/claude/skills/impact-analysis/SKILL.md +203 -0
- package/claude/skills/jest-unit/SKILL.md +306 -0
- package/claude/skills/jest-unit/references/component-testing.md +371 -0
- package/claude/skills/jest-unit/references/mocking-patterns.md +380 -0
- package/claude/skills/jest-unit/references/service-hook-testing.md +454 -0
- package/claude/skills/jira-integration/SKILL.md +539 -0
- package/claude/skills/media-library/SKILL.md +743 -0
- package/claude/skills/mock-analysis/SKILL.md +276 -0
- package/claude/skills/monorepo-architecture/SKILL.md +162 -0
- package/claude/skills/nextjs-api-development/SKILL.md +364 -0
- package/claude/skills/nextjs-api-development/scripts/generate-crud-tests.py +456 -0
- package/claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py +481 -0
- package/claude/skills/nextjs-api-development/scripts/validate-api.py +283 -0
- package/claude/skills/notion-integration/SKILL.md +641 -0
- package/claude/skills/npm-development-workflow/SKILL.md +480 -0
- package/claude/skills/page-builder-blocks/SKILL.md +530 -0
- package/claude/skills/page-builder-blocks/scripts/scaffold-block.py +444 -0
- package/claude/skills/permissions-system/SKILL.md +619 -0
- package/claude/skills/plugins/SKILL.md +340 -0
- package/claude/skills/plugins/references/plugin-templates.md +414 -0
- package/claude/skills/plugins/references/plugin-testing.md +353 -0
- package/claude/skills/plugins/references/plugin-types.md +198 -0
- package/claude/skills/plugins/scripts/scaffold-plugin.py +443 -0
- package/claude/skills/pom-patterns/SKILL.md +452 -0
- package/claude/skills/pom-patterns/scripts/generate-pom.py +392 -0
- package/claude/skills/rate-limiting/SKILL.md +342 -0
- package/claude/skills/react-best-practices/AGENTS.md +2410 -0
- package/claude/skills/react-best-practices/README.md +123 -0
- package/claude/skills/react-best-practices/SKILL.md +125 -0
- package/claude/skills/react-best-practices/metadata.json +15 -0
- package/claude/skills/react-best-practices/rules/_sections.md +46 -0
- package/claude/skills/react-best-practices/rules/_template.md +28 -0
- package/claude/skills/react-best-practices/rules/advanced-event-handler-refs.md +55 -0
- package/claude/skills/react-best-practices/rules/advanced-use-latest.md +49 -0
- package/claude/skills/react-best-practices/rules/async-api-routes.md +38 -0
- package/claude/skills/react-best-practices/rules/async-defer-await.md +80 -0
- package/claude/skills/react-best-practices/rules/async-dependencies.md +36 -0
- package/claude/skills/react-best-practices/rules/async-parallel.md +28 -0
- package/claude/skills/react-best-practices/rules/async-suspense-boundaries.md +99 -0
- package/claude/skills/react-best-practices/rules/bundle-barrel-imports.md +59 -0
- package/claude/skills/react-best-practices/rules/bundle-conditional.md +31 -0
- package/claude/skills/react-best-practices/rules/bundle-defer-third-party.md +49 -0
- package/claude/skills/react-best-practices/rules/bundle-dynamic-imports.md +35 -0
- package/claude/skills/react-best-practices/rules/bundle-preload.md +50 -0
- package/claude/skills/react-best-practices/rules/client-event-listeners.md +74 -0
- package/claude/skills/react-best-practices/rules/client-localstorage-schema.md +71 -0
- package/claude/skills/react-best-practices/rules/client-passive-event-listeners.md +48 -0
- package/claude/skills/react-best-practices/rules/client-swr-dedup.md +56 -0
- package/claude/skills/react-best-practices/rules/js-batch-dom-css.md +82 -0
- package/claude/skills/react-best-practices/rules/js-cache-function-results.md +80 -0
- package/claude/skills/react-best-practices/rules/js-cache-property-access.md +28 -0
- package/claude/skills/react-best-practices/rules/js-cache-storage.md +70 -0
- package/claude/skills/react-best-practices/rules/js-combine-iterations.md +32 -0
- package/claude/skills/react-best-practices/rules/js-early-exit.md +50 -0
- package/claude/skills/react-best-practices/rules/js-hoist-regexp.md +45 -0
- package/claude/skills/react-best-practices/rules/js-index-maps.md +37 -0
- package/claude/skills/react-best-practices/rules/js-length-check-first.md +49 -0
- package/claude/skills/react-best-practices/rules/js-min-max-loop.md +82 -0
- package/claude/skills/react-best-practices/rules/js-set-map-lookups.md +24 -0
- package/claude/skills/react-best-practices/rules/js-tosorted-immutable.md +57 -0
- package/claude/skills/react-best-practices/rules/rendering-activity.md +26 -0
- package/claude/skills/react-best-practices/rules/rendering-animate-svg-wrapper.md +47 -0
- package/claude/skills/react-best-practices/rules/rendering-conditional-render.md +40 -0
- package/claude/skills/react-best-practices/rules/rendering-content-visibility.md +38 -0
- package/claude/skills/react-best-practices/rules/rendering-hoist-jsx.md +46 -0
- package/claude/skills/react-best-practices/rules/rendering-hydration-no-flicker.md +82 -0
- package/claude/skills/react-best-practices/rules/rendering-svg-precision.md +28 -0
- package/claude/skills/react-best-practices/rules/rerender-defer-reads.md +39 -0
- package/claude/skills/react-best-practices/rules/rerender-dependencies.md +45 -0
- package/claude/skills/react-best-practices/rules/rerender-derived-state.md +29 -0
- package/claude/skills/react-best-practices/rules/rerender-functional-setstate.md +74 -0
- package/claude/skills/react-best-practices/rules/rerender-lazy-state-init.md +58 -0
- package/claude/skills/react-best-practices/rules/rerender-memo.md +44 -0
- package/claude/skills/react-best-practices/rules/rerender-transitions.md +40 -0
- package/claude/skills/react-best-practices/rules/server-after-nonblocking.md +73 -0
- package/claude/skills/react-best-practices/rules/server-cache-lru.md +41 -0
- package/claude/skills/react-best-practices/rules/server-cache-react.md +76 -0
- package/claude/skills/react-best-practices/rules/server-parallel-fetching.md +83 -0
- package/claude/skills/react-best-practices/rules/server-serialization.md +38 -0
- package/claude/skills/react-patterns/SKILL.md +688 -0
- package/claude/skills/registry-system/SKILL.md +331 -0
- package/claude/skills/scheduled-actions/SKILL.md +671 -0
- package/claude/skills/scope-enforcement/SKILL.md +542 -0
- package/claude/skills/scope-enforcement/scripts/validate-scope.py +357 -0
- package/claude/skills/server-actions/SKILL.md +493 -0
- package/claude/skills/service-layer/SKILL.md +587 -0
- package/claude/skills/session-management/SKILL.md +266 -0
- package/claude/skills/session-management/scripts/create-session.py +166 -0
- package/claude/skills/session-management/scripts/iteration-close.sh +105 -0
- package/claude/skills/session-management/scripts/iteration-init.sh +180 -0
- package/claude/skills/session-management/scripts/session-archive.sh +87 -0
- package/claude/skills/session-management/scripts/session-close.sh +133 -0
- package/claude/skills/session-management/scripts/session-init.sh +225 -0
- package/claude/skills/session-management/scripts/session-list.sh +163 -0
- package/claude/skills/session-management/scripts/split-plan.sh +116 -0
- package/claude/skills/shadcn-components/SKILL.md +586 -0
- package/claude/skills/shadcn-theming/SKILL.md +446 -0
- package/claude/skills/suspense-loading/SKILL.md +280 -0
- package/claude/skills/tailwind-theming/SKILL.md +507 -0
- package/claude/skills/tanstack-query/SKILL.md +608 -0
- package/claude/skills/test-coverage/SKILL.md +239 -0
- package/claude/skills/web-design-guidelines/SKILL.md +39 -0
- package/claude/skills/zod-validation/SKILL.md +537 -0
- package/claude/templates/blocks/progress.md +86 -0
- package/claude/templates/iteration/changes.md +61 -0
- package/claude/templates/iteration/progress.md +55 -0
- package/claude/templates/log.md +31 -0
- package/claude/templates/story/context.md +77 -0
- package/claude/templates/story/pendings.md +37 -0
- package/claude/templates/story/plan.md +299 -0
- package/claude/templates/story/requirements.md +109 -0
- package/claude/templates/story/scope.json +10 -0
- package/claude/templates/story/tests.md +91 -0
- package/claude/templates/task/progress.md +58 -0
- package/claude/templates/task/requirements.md +54 -0
- package/claude/workflows/README.md +154 -0
- package/claude/workflows/blocks.md +614 -0
- package/claude/workflows/story.md +1207 -0
- package/claude/workflows/task.md +927 -0
- package/claude/workflows/tweak.md +527 -0
- package/cursor/.gitkeep +0 -0
- package/package.json +35 -0
- package/scripts/postinstall.mjs +198 -0
- package/scripts/setup.mjs +282 -0
- package/scripts/sync.mjs +209 -0
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @nextsparkjs/ai-workflow postinstall hook
|
|
4
|
+
*
|
|
5
|
+
* Automatically syncs AI workflow files (.claude/, .cursor/, etc.) when the package is updated.
|
|
6
|
+
* Only runs in NextSpark projects that already have AI workflow set up.
|
|
7
|
+
*
|
|
8
|
+
* Debug mode: Set NEXTSPARK_DEBUG=1 to see detailed logs
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { execSync } from 'child_process';
|
|
12
|
+
import { existsSync, readFileSync } from 'fs';
|
|
13
|
+
import { join, dirname, resolve, isAbsolute } from 'path';
|
|
14
|
+
import { fileURLToPath } from 'url';
|
|
15
|
+
|
|
16
|
+
const DEBUG = process.env.NEXTSPARK_DEBUG === '1';
|
|
17
|
+
|
|
18
|
+
function debug(message) {
|
|
19
|
+
if (DEBUG) {
|
|
20
|
+
console.log(` [DEBUG] ${message}`);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Find the project root by walking up from the current directory
|
|
26
|
+
* until we find a package.json that isn't inside node_modules
|
|
27
|
+
*/
|
|
28
|
+
function findProjectRoot() {
|
|
29
|
+
// When running postinstall, we're in node_modules/@nextsparkjs/ai-workflow
|
|
30
|
+
// We need to find the project root (where the user's package.json is)
|
|
31
|
+
let current = process.cwd();
|
|
32
|
+
|
|
33
|
+
// If we're inside node_modules, walk up to find the project root
|
|
34
|
+
if (current.includes('node_modules')) {
|
|
35
|
+
// Go up until we're out of node_modules
|
|
36
|
+
const parts = current.split('node_modules');
|
|
37
|
+
current = parts[0].replace(/[/\\]$/, ''); // Remove trailing slash
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return current;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Validate that the project root path is safe to use
|
|
45
|
+
*/
|
|
46
|
+
function validateProjectRoot(projectRoot) {
|
|
47
|
+
// Must be an absolute path
|
|
48
|
+
if (!isAbsolute(projectRoot)) {
|
|
49
|
+
debug(`Invalid path: not absolute - ${projectRoot}`);
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Must exist
|
|
54
|
+
if (!existsSync(projectRoot)) {
|
|
55
|
+
debug(`Invalid path: does not exist - ${projectRoot}`);
|
|
56
|
+
return false;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Resolve to canonical path and verify it doesn't escape
|
|
60
|
+
const resolved = resolve(projectRoot);
|
|
61
|
+
if (resolved !== projectRoot && !projectRoot.startsWith(resolved)) {
|
|
62
|
+
debug(`Invalid path: resolution mismatch - ${projectRoot} vs ${resolved}`);
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Detect if we're in the NextSpark development monorepo.
|
|
71
|
+
* Consumer projects may also have pnpm-workspace.yaml (for themes/plugins),
|
|
72
|
+
* so we check for packages/core/ which only exists in the dev monorepo.
|
|
73
|
+
*/
|
|
74
|
+
function isDevMonorepo(projectRoot) {
|
|
75
|
+
const isMonorepo = existsSync(join(projectRoot, 'packages', 'core', 'package.json'));
|
|
76
|
+
if (isMonorepo) {
|
|
77
|
+
debug('Skipping: NextSpark development monorepo detected');
|
|
78
|
+
}
|
|
79
|
+
return isMonorepo;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* Detect if we're in a NextSpark consumer project
|
|
84
|
+
*/
|
|
85
|
+
function isNextSparkProject(projectRoot) {
|
|
86
|
+
// If we're in the dev monorepo, skip
|
|
87
|
+
if (isDevMonorepo(projectRoot)) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Check for package.json with nextspark dependency
|
|
92
|
+
const pkgPath = join(projectRoot, 'package.json');
|
|
93
|
+
if (!existsSync(pkgPath)) {
|
|
94
|
+
debug('Skipping: no package.json found');
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
100
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
101
|
+
|
|
102
|
+
const hasNextSpark = !!(deps['@nextsparkjs/core'] || deps['@nextsparkjs/cli']);
|
|
103
|
+
if (!hasNextSpark) {
|
|
104
|
+
debug('Skipping: no NextSpark dependencies found');
|
|
105
|
+
}
|
|
106
|
+
return hasNextSpark;
|
|
107
|
+
} catch {
|
|
108
|
+
debug('Skipping: failed to parse package.json');
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Find the .claude/ folder location.
|
|
115
|
+
* In workspace setups, .claude/ may be in the parent directory (e.g., web/ is a
|
|
116
|
+
* workspace member but .claude/ lives at the monorepo root alongside web/).
|
|
117
|
+
* Returns the directory containing .claude/, or null if not found.
|
|
118
|
+
*/
|
|
119
|
+
function findClaudeRoot(projectRoot) {
|
|
120
|
+
// Check project root itself
|
|
121
|
+
if (existsSync(join(projectRoot, '.claude'))) {
|
|
122
|
+
debug(`.claude/ found at ${projectRoot}`);
|
|
123
|
+
return projectRoot;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Check parent directory (workspace pattern: my-project/web/ has package.json,
|
|
127
|
+
// but .claude/ is at my-project/)
|
|
128
|
+
const parentDir = resolve(projectRoot, '..');
|
|
129
|
+
if (existsSync(join(parentDir, '.claude'))) {
|
|
130
|
+
debug(`.claude/ found at parent: ${parentDir}`);
|
|
131
|
+
return parentDir;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
debug('Skipping: no .claude/ folder found (AI workflow not set up yet)');
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Find the setup.mjs script path relative to this postinstall script
|
|
140
|
+
*/
|
|
141
|
+
function getSetupScriptPath() {
|
|
142
|
+
// This script is at packages/ai-workflow/scripts/postinstall.mjs
|
|
143
|
+
// setup.mjs is at packages/ai-workflow/scripts/setup.mjs
|
|
144
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
145
|
+
const __dirname = dirname(__filename);
|
|
146
|
+
const setupPath = join(__dirname, 'setup.mjs');
|
|
147
|
+
|
|
148
|
+
if (existsSync(setupPath)) {
|
|
149
|
+
return setupPath;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
debug(`Setup script not found at ${setupPath}`);
|
|
153
|
+
return null;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Main execution
|
|
157
|
+
try {
|
|
158
|
+
const projectRoot = findProjectRoot();
|
|
159
|
+
debug(`Project root: ${projectRoot}`);
|
|
160
|
+
|
|
161
|
+
// Validate project root path
|
|
162
|
+
if (!validateProjectRoot(projectRoot)) {
|
|
163
|
+
debug('Skipping: invalid project root path');
|
|
164
|
+
process.exit(0);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Only run in NextSpark projects with an existing .claude/ folder
|
|
168
|
+
if (isNextSparkProject(projectRoot)) {
|
|
169
|
+
const claudeRoot = findClaudeRoot(projectRoot);
|
|
170
|
+
if (claudeRoot) {
|
|
171
|
+
console.log('\n 📦 @nextsparkjs/ai-workflow updated - syncing AI workflow files...\n');
|
|
172
|
+
|
|
173
|
+
const setupScript = getSetupScriptPath();
|
|
174
|
+
if (setupScript) {
|
|
175
|
+
try {
|
|
176
|
+
execSync(`node "${setupScript}" claude`, {
|
|
177
|
+
stdio: 'inherit',
|
|
178
|
+
cwd: claudeRoot,
|
|
179
|
+
});
|
|
180
|
+
} catch (syncError) {
|
|
181
|
+
console.warn('\n ⚠️ Auto-sync failed. Run manually: npx nextspark sync:ai\n');
|
|
182
|
+
debug(`Sync error: ${syncError.message}`);
|
|
183
|
+
}
|
|
184
|
+
} else {
|
|
185
|
+
debug('Skipping: setup.mjs script not found');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
} catch (error) {
|
|
190
|
+
// Postinstall hooks should not break installations
|
|
191
|
+
// The user can always run the sync manually
|
|
192
|
+
if (DEBUG) {
|
|
193
|
+
console.warn('\n [DEBUG] Postinstall error:', error.message);
|
|
194
|
+
if (error.stack) {
|
|
195
|
+
console.warn(error.stack);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @nextsparkjs/ai-workflow - Setup Script
|
|
5
|
+
*
|
|
6
|
+
* Copies AI workflow templates to the consumer's project directory.
|
|
7
|
+
* Uses file-by-file copy to preserve user-created files.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node node_modules/@nextsparkjs/ai-workflow/scripts/setup.mjs [editor]
|
|
11
|
+
* nextspark setup:ai --editor claude
|
|
12
|
+
*
|
|
13
|
+
* Editor options:
|
|
14
|
+
* claude Setup Claude Code (.claude/)
|
|
15
|
+
* cursor Setup Cursor (coming soon)
|
|
16
|
+
* antigravity Setup Antigravity (coming soon)
|
|
17
|
+
* all Setup all available editors
|
|
18
|
+
* (no arg) Defaults to "claude"
|
|
19
|
+
*
|
|
20
|
+
* Copy strategy:
|
|
21
|
+
* agents/, commands/, skills/, templates/, workflows/, _docs/
|
|
22
|
+
* → Overwrite matching files, preserve user-created files
|
|
23
|
+
* config/*.schema.json
|
|
24
|
+
* → Always overwrite (schema updates)
|
|
25
|
+
* config/*.json (non-schema)
|
|
26
|
+
* → Copy only if not exists (user config — never overwrite)
|
|
27
|
+
* sessions/
|
|
28
|
+
* → Create directory if not exists, never copy content
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
import fs from 'node:fs'
|
|
32
|
+
import path from 'node:path'
|
|
33
|
+
import { fileURLToPath } from 'node:url'
|
|
34
|
+
|
|
35
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
36
|
+
const __dirname = path.dirname(__filename)
|
|
37
|
+
|
|
38
|
+
// Package root = where this script lives (packages/ai-workflow/ or node_modules/@nextsparkjs/ai-workflow/)
|
|
39
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..')
|
|
40
|
+
|
|
41
|
+
// Consumer project root = where the user runs from
|
|
42
|
+
const PROJECT_ROOT = process.cwd()
|
|
43
|
+
|
|
44
|
+
// ---------------------------------------------------------------------------
|
|
45
|
+
// Editor definitions
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
|
|
48
|
+
const EDITORS = {
|
|
49
|
+
claude: {
|
|
50
|
+
enabled: true,
|
|
51
|
+
source: 'claude', // subdirectory in package
|
|
52
|
+
target: '.claude', // subdirectory in consumer project
|
|
53
|
+
},
|
|
54
|
+
cursor: {
|
|
55
|
+
enabled: false,
|
|
56
|
+
source: 'cursor',
|
|
57
|
+
target: '.cursor',
|
|
58
|
+
},
|
|
59
|
+
antigravity: {
|
|
60
|
+
enabled: false,
|
|
61
|
+
source: 'antigravity',
|
|
62
|
+
target: '.antigravity',
|
|
63
|
+
},
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Copy strategies per directory
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
|
|
70
|
+
/** Directories where matching files are overwritten */
|
|
71
|
+
const OVERWRITE_DIRS = new Set([
|
|
72
|
+
'agents',
|
|
73
|
+
'commands',
|
|
74
|
+
'skills',
|
|
75
|
+
'templates',
|
|
76
|
+
'workflows',
|
|
77
|
+
'_docs',
|
|
78
|
+
])
|
|
79
|
+
|
|
80
|
+
// ---------------------------------------------------------------------------
|
|
81
|
+
// Helpers
|
|
82
|
+
// ---------------------------------------------------------------------------
|
|
83
|
+
|
|
84
|
+
const colors = {
|
|
85
|
+
reset: '\x1b[0m',
|
|
86
|
+
cyan: '\x1b[36m',
|
|
87
|
+
green: '\x1b[32m',
|
|
88
|
+
yellow: '\x1b[33m',
|
|
89
|
+
blue: '\x1b[34m',
|
|
90
|
+
red: '\x1b[31m',
|
|
91
|
+
dim: '\x1b[2m',
|
|
92
|
+
bold: '\x1b[1m',
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function log(msg, color = 'reset') {
|
|
96
|
+
console.log(`${colors[color]}${msg}${colors.reset}`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Recursively get all files in a directory (relative paths).
|
|
101
|
+
*/
|
|
102
|
+
function getAllFiles(dir, baseDir = dir) {
|
|
103
|
+
const results = []
|
|
104
|
+
if (!fs.existsSync(dir)) return results
|
|
105
|
+
|
|
106
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
107
|
+
const fullPath = path.join(dir, entry.name)
|
|
108
|
+
if (entry.isDirectory()) {
|
|
109
|
+
results.push(...getAllFiles(fullPath, baseDir))
|
|
110
|
+
} else if (entry.name !== '.gitkeep') {
|
|
111
|
+
results.push(path.relative(baseDir, fullPath))
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return results
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Copy a single file, creating parent directories as needed.
|
|
119
|
+
*/
|
|
120
|
+
function copyFile(src, dest) {
|
|
121
|
+
const destDir = path.dirname(dest)
|
|
122
|
+
if (!fs.existsSync(destDir)) {
|
|
123
|
+
fs.mkdirSync(destDir, { recursive: true })
|
|
124
|
+
}
|
|
125
|
+
fs.copyFileSync(src, dest)
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// ---------------------------------------------------------------------------
|
|
129
|
+
// Setup logic for a single editor
|
|
130
|
+
// ---------------------------------------------------------------------------
|
|
131
|
+
|
|
132
|
+
function setupEditor(name, cfg) {
|
|
133
|
+
const sourceRoot = path.join(PACKAGE_ROOT, cfg.source)
|
|
134
|
+
const targetRoot = path.join(PROJECT_ROOT, cfg.target)
|
|
135
|
+
|
|
136
|
+
if (!fs.existsSync(sourceRoot)) {
|
|
137
|
+
log(` Source not found: ${sourceRoot}`, 'red')
|
|
138
|
+
return { overwritten: 0, created: 0, preserved: 0 }
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Ensure target root exists
|
|
142
|
+
if (!fs.existsSync(targetRoot)) {
|
|
143
|
+
fs.mkdirSync(targetRoot, { recursive: true })
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
let overwritten = 0
|
|
147
|
+
let created = 0
|
|
148
|
+
let preserved = 0
|
|
149
|
+
|
|
150
|
+
// 1. Process overwrite directories (agents, commands, skills, etc.)
|
|
151
|
+
for (const dirName of OVERWRITE_DIRS) {
|
|
152
|
+
const srcDir = path.join(sourceRoot, dirName)
|
|
153
|
+
if (!fs.existsSync(srcDir)) continue
|
|
154
|
+
|
|
155
|
+
const files = getAllFiles(srcDir)
|
|
156
|
+
for (const relPath of files) {
|
|
157
|
+
const srcFile = path.join(srcDir, relPath)
|
|
158
|
+
const destFile = path.join(targetRoot, dirName, relPath)
|
|
159
|
+
const existed = fs.existsSync(destFile)
|
|
160
|
+
|
|
161
|
+
copyFile(srcFile, destFile)
|
|
162
|
+
|
|
163
|
+
if (existed) {
|
|
164
|
+
overwritten++
|
|
165
|
+
} else {
|
|
166
|
+
created++
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if (files.length > 0) {
|
|
171
|
+
log(` ${dirName}/: ${files.length} files`, 'green')
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// 2. Process config/ directory with mixed strategy
|
|
176
|
+
const srcConfig = path.join(sourceRoot, 'config')
|
|
177
|
+
const destConfig = path.join(targetRoot, 'config')
|
|
178
|
+
|
|
179
|
+
if (fs.existsSync(srcConfig)) {
|
|
180
|
+
if (!fs.existsSync(destConfig)) {
|
|
181
|
+
fs.mkdirSync(destConfig, { recursive: true })
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
let schemasCopied = 0
|
|
185
|
+
let configsCreated = 0
|
|
186
|
+
let configsPreserved = 0
|
|
187
|
+
|
|
188
|
+
for (const file of fs.readdirSync(srcConfig)) {
|
|
189
|
+
const srcFile = path.join(srcConfig, file)
|
|
190
|
+
const destFile = path.join(destConfig, file)
|
|
191
|
+
|
|
192
|
+
// Skip non-files and .gitkeep
|
|
193
|
+
if (!fs.statSync(srcFile).isFile() || file === '.gitkeep') continue
|
|
194
|
+
|
|
195
|
+
const isSchema = file.endsWith('.schema.json')
|
|
196
|
+
|
|
197
|
+
if (isSchema) {
|
|
198
|
+
// Schema files: always overwrite
|
|
199
|
+
copyFile(srcFile, destFile)
|
|
200
|
+
schemasCopied++
|
|
201
|
+
overwritten++
|
|
202
|
+
} else {
|
|
203
|
+
// Config files: copy only if not exists
|
|
204
|
+
if (fs.existsSync(destFile)) {
|
|
205
|
+
configsPreserved++
|
|
206
|
+
preserved++
|
|
207
|
+
} else {
|
|
208
|
+
copyFile(srcFile, destFile)
|
|
209
|
+
configsCreated++
|
|
210
|
+
created++
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
if (schemasCopied > 0) {
|
|
216
|
+
log(` config/*.schema.json: ${schemasCopied} schemas updated`, 'green')
|
|
217
|
+
}
|
|
218
|
+
if (configsCreated > 0) {
|
|
219
|
+
log(` config/*.json: ${configsCreated} new configs created`, 'green')
|
|
220
|
+
}
|
|
221
|
+
if (configsPreserved > 0) {
|
|
222
|
+
log(` config/*.json: ${configsPreserved} user configs preserved`, 'blue')
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// 3. Ensure sessions/ directory exists (never copy content)
|
|
227
|
+
const sessionsDir = path.join(targetRoot, 'sessions')
|
|
228
|
+
if (!fs.existsSync(sessionsDir)) {
|
|
229
|
+
fs.mkdirSync(sessionsDir, { recursive: true })
|
|
230
|
+
log(` sessions/: created (empty)`, 'green')
|
|
231
|
+
} else {
|
|
232
|
+
log(` sessions/: exists (preserved)`, 'blue')
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
return { overwritten, created, preserved }
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// ---------------------------------------------------------------------------
|
|
239
|
+
// Entry point
|
|
240
|
+
// ---------------------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
const editorArg = process.argv[2] || 'claude'
|
|
243
|
+
|
|
244
|
+
log(`\n${colors.bold}@nextsparkjs/ai-workflow — Setup${colors.reset}\n`, 'cyan')
|
|
245
|
+
log(`Package: ${PACKAGE_ROOT}`, 'dim')
|
|
246
|
+
log(`Project: ${PROJECT_ROOT}\n`, 'dim')
|
|
247
|
+
|
|
248
|
+
if (editorArg === 'all') {
|
|
249
|
+
for (const [name, cfg] of Object.entries(EDITORS)) {
|
|
250
|
+
if (cfg.enabled) {
|
|
251
|
+
log(`Setting up ${name}:`, 'cyan')
|
|
252
|
+
const stats = setupEditor(name, cfg)
|
|
253
|
+
log(` Total: ${stats.overwritten} updated, ${stats.created} created, ${stats.preserved} preserved\n`, 'dim')
|
|
254
|
+
} else {
|
|
255
|
+
log(`${name}: coming soon\n`, 'yellow')
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
} else {
|
|
259
|
+
const cfg = EDITORS[editorArg]
|
|
260
|
+
|
|
261
|
+
if (!cfg) {
|
|
262
|
+
log(`Unknown editor: ${editorArg}`, 'red')
|
|
263
|
+
log(`Available: ${Object.keys(EDITORS).join(', ')}, all`, 'dim')
|
|
264
|
+
process.exit(1)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
if (!cfg.enabled) {
|
|
268
|
+
log(`${editorArg}: coming soon`, 'yellow')
|
|
269
|
+
log(`Currently supported: claude`, 'dim')
|
|
270
|
+
process.exit(0)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
log(`Setting up ${editorArg}:`, 'cyan')
|
|
274
|
+
const stats = setupEditor(editorArg, cfg)
|
|
275
|
+
|
|
276
|
+
log('')
|
|
277
|
+
log(`Setup complete!`, 'green')
|
|
278
|
+
log(` ${stats.overwritten} files updated`, 'dim')
|
|
279
|
+
log(` ${stats.created} files created`, 'dim')
|
|
280
|
+
log(` ${stats.preserved} files preserved`, 'dim')
|
|
281
|
+
log('')
|
|
282
|
+
}
|
package/scripts/sync.mjs
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @nextsparkjs/ai-workflow - Sync Script
|
|
5
|
+
*
|
|
6
|
+
* Syncs the monorepo's working .claude/ directory into the publishable
|
|
7
|
+
* packages/ai-workflow/claude/ directory. Run manually before npm publish.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node packages/ai-workflow/scripts/sync.mjs
|
|
11
|
+
* # or from package dir:
|
|
12
|
+
* pnpm sync
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import fs from 'node:fs'
|
|
16
|
+
import path from 'node:path'
|
|
17
|
+
import { fileURLToPath } from 'node:url'
|
|
18
|
+
|
|
19
|
+
const __filename = fileURLToPath(import.meta.url)
|
|
20
|
+
const __dirname = path.dirname(__filename)
|
|
21
|
+
|
|
22
|
+
// Package root = packages/ai-workflow/
|
|
23
|
+
const PACKAGE_ROOT = path.resolve(__dirname, '..')
|
|
24
|
+
// Monorepo root = repo/
|
|
25
|
+
const MONOREPO_ROOT = path.resolve(PACKAGE_ROOT, '../..')
|
|
26
|
+
|
|
27
|
+
// ---------------------------------------------------------------------------
|
|
28
|
+
// Editor definitions (future-proof)
|
|
29
|
+
// ---------------------------------------------------------------------------
|
|
30
|
+
|
|
31
|
+
const EDITORS = {
|
|
32
|
+
claude: {
|
|
33
|
+
sourceDir: '.claude',
|
|
34
|
+
targetDir: 'claude',
|
|
35
|
+
enabled: true,
|
|
36
|
+
},
|
|
37
|
+
cursor: {
|
|
38
|
+
sourceDir: '.cursor',
|
|
39
|
+
targetDir: 'cursor',
|
|
40
|
+
enabled: false,
|
|
41
|
+
},
|
|
42
|
+
antigravity: {
|
|
43
|
+
sourceDir: '.antigravity',
|
|
44
|
+
targetDir: 'antigravity',
|
|
45
|
+
enabled: false,
|
|
46
|
+
},
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ---------------------------------------------------------------------------
|
|
50
|
+
// Sync mapping: directories and files to copy from .claude/ to claude/
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
|
|
53
|
+
/** Directories to sync (cleaned before copy) */
|
|
54
|
+
const SYNC_DIRS = [
|
|
55
|
+
'agents',
|
|
56
|
+
'commands',
|
|
57
|
+
'skills',
|
|
58
|
+
'templates',
|
|
59
|
+
'workflows',
|
|
60
|
+
'_docs',
|
|
61
|
+
]
|
|
62
|
+
|
|
63
|
+
/** Subdirectories to exclude from sync (monorepo-only content) */
|
|
64
|
+
const EXCLUDE_DIRS = new Set(['_monorepo'])
|
|
65
|
+
|
|
66
|
+
/** File globs within config/ to sync (only schemas) */
|
|
67
|
+
const CONFIG_SYNC_PATTERN = /\.schema\.json$/
|
|
68
|
+
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
// Helpers
|
|
71
|
+
// ---------------------------------------------------------------------------
|
|
72
|
+
|
|
73
|
+
const colors = {
|
|
74
|
+
reset: '\x1b[0m',
|
|
75
|
+
cyan: '\x1b[36m',
|
|
76
|
+
green: '\x1b[32m',
|
|
77
|
+
yellow: '\x1b[33m',
|
|
78
|
+
red: '\x1b[31m',
|
|
79
|
+
dim: '\x1b[2m',
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function log(msg, color = 'reset') {
|
|
83
|
+
console.log(`${colors[color]}${msg}${colors.reset}`)
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function getAllFiles(dir, baseDir = dir) {
|
|
87
|
+
const results = []
|
|
88
|
+
if (!fs.existsSync(dir)) return results
|
|
89
|
+
|
|
90
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
91
|
+
if (EXCLUDE_DIRS.has(entry.name)) continue
|
|
92
|
+
const fullPath = path.join(dir, entry.name)
|
|
93
|
+
if (entry.isDirectory()) {
|
|
94
|
+
results.push(...getAllFiles(fullPath, baseDir))
|
|
95
|
+
} else {
|
|
96
|
+
results.push(path.relative(baseDir, fullPath))
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return results
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function removeDir(dir) {
|
|
103
|
+
if (fs.existsSync(dir)) {
|
|
104
|
+
fs.rmSync(dir, { recursive: true, force: true })
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function copyFile(src, dest) {
|
|
109
|
+
const destDir = path.dirname(dest)
|
|
110
|
+
if (!fs.existsSync(destDir)) {
|
|
111
|
+
fs.mkdirSync(destDir, { recursive: true })
|
|
112
|
+
}
|
|
113
|
+
fs.copyFileSync(src, dest)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
// Main sync logic
|
|
118
|
+
// ---------------------------------------------------------------------------
|
|
119
|
+
|
|
120
|
+
function syncEditor(name, cfg) {
|
|
121
|
+
const sourceRoot = path.join(MONOREPO_ROOT, cfg.sourceDir)
|
|
122
|
+
const targetRoot = path.join(PACKAGE_ROOT, cfg.targetDir)
|
|
123
|
+
|
|
124
|
+
if (!fs.existsSync(sourceRoot)) {
|
|
125
|
+
log(` Source not found: ${sourceRoot}`, 'red')
|
|
126
|
+
return { copied: 0, removed: 0, skipped: 0 }
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
let copied = 0
|
|
130
|
+
let removed = 0
|
|
131
|
+
let skipped = 0
|
|
132
|
+
|
|
133
|
+
// 1. Sync directories: clean target then copy
|
|
134
|
+
for (const dirName of SYNC_DIRS) {
|
|
135
|
+
const srcDir = path.join(sourceRoot, dirName)
|
|
136
|
+
const destDir = path.join(targetRoot, dirName)
|
|
137
|
+
|
|
138
|
+
if (!fs.existsSync(srcDir)) {
|
|
139
|
+
skipped++
|
|
140
|
+
continue
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Clean target directory (remove stale files)
|
|
144
|
+
if (fs.existsSync(destDir)) {
|
|
145
|
+
const oldFiles = getAllFiles(destDir)
|
|
146
|
+
removeDir(destDir)
|
|
147
|
+
removed += oldFiles.length
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Copy all files
|
|
151
|
+
const files = getAllFiles(srcDir)
|
|
152
|
+
for (const relPath of files) {
|
|
153
|
+
copyFile(
|
|
154
|
+
path.join(srcDir, relPath),
|
|
155
|
+
path.join(destDir, relPath)
|
|
156
|
+
)
|
|
157
|
+
copied++
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
log(` ${dirName}/: ${files.length} files`, 'green')
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// 2. Sync config/ schemas only
|
|
164
|
+
const srcConfig = path.join(sourceRoot, 'config')
|
|
165
|
+
const destConfig = path.join(targetRoot, 'config')
|
|
166
|
+
|
|
167
|
+
if (fs.existsSync(srcConfig)) {
|
|
168
|
+
let schemaCount = 0
|
|
169
|
+
for (const file of fs.readdirSync(srcConfig)) {
|
|
170
|
+
if (CONFIG_SYNC_PATTERN.test(file)) {
|
|
171
|
+
copyFile(
|
|
172
|
+
path.join(srcConfig, file),
|
|
173
|
+
path.join(destConfig, file)
|
|
174
|
+
)
|
|
175
|
+
schemaCount++
|
|
176
|
+
copied++
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
log(` config/*.schema.json: ${schemaCount} files`, 'green')
|
|
180
|
+
log(` config/*.json (templates): preserved`, 'dim')
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
return { copied, removed, skipped }
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
// ---------------------------------------------------------------------------
|
|
187
|
+
// Entry point
|
|
188
|
+
// ---------------------------------------------------------------------------
|
|
189
|
+
|
|
190
|
+
log('\n@nextsparkjs/ai-workflow — Sync\n', 'cyan')
|
|
191
|
+
log(`Source: ${MONOREPO_ROOT}/.claude/`, 'dim')
|
|
192
|
+
log(`Target: ${PACKAGE_ROOT}/claude/\n`, 'dim')
|
|
193
|
+
|
|
194
|
+
let totalCopied = 0
|
|
195
|
+
let totalRemoved = 0
|
|
196
|
+
|
|
197
|
+
for (const [name, cfg] of Object.entries(EDITORS)) {
|
|
198
|
+
if (!cfg.enabled) {
|
|
199
|
+
log(`${name}: coming soon (skipped)`, 'dim')
|
|
200
|
+
continue
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
log(`Syncing ${name}:`, 'cyan')
|
|
204
|
+
const stats = syncEditor(name, cfg)
|
|
205
|
+
totalCopied += stats.copied
|
|
206
|
+
totalRemoved += stats.removed
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
log(`\nDone: ${totalCopied} files copied, ${totalRemoved} stale files removed.\n`, 'green')
|