@mison/ag-kit-cn 2.0.1
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/.agent/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agent/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agent/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agent/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agent/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agent/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agent/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agent/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agent/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agent/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agent/.shared/ui-ux-pro-max/scripts/search.py +106 -0
- package/.agent/ARCHITECTURE.md +285 -0
- package/.agent/agents/backend-specialist.md +268 -0
- package/.agent/agents/code-archaeologist.md +106 -0
- package/.agent/agents/database-architect.md +225 -0
- package/.agent/agents/debugger.md +225 -0
- package/.agent/agents/devops-engineer.md +242 -0
- package/.agent/agents/documentation-writer.md +104 -0
- package/.agent/agents/explorer-agent.md +73 -0
- package/.agent/agents/frontend-specialist.md +618 -0
- package/.agent/agents/game-developer.md +162 -0
- package/.agent/agents/mobile-developer.md +382 -0
- package/.agent/agents/orchestrator.md +438 -0
- package/.agent/agents/penetration-tester.md +188 -0
- package/.agent/agents/performance-optimizer.md +187 -0
- package/.agent/agents/product-manager.md +112 -0
- package/.agent/agents/product-owner.md +95 -0
- package/.agent/agents/project-planner.md +405 -0
- package/.agent/agents/qa-automation-engineer.md +103 -0
- package/.agent/agents/security-auditor.md +170 -0
- package/.agent/agents/seo-specialist.md +111 -0
- package/.agent/agents/test-engineer.md +158 -0
- package/.agent/mcp_config.json +12 -0
- package/.agent/rules/GEMINI.md +273 -0
- package/.agent/scripts/auto_preview.py +148 -0
- package/.agent/scripts/checklist.py +217 -0
- package/.agent/scripts/session_manager.py +120 -0
- package/.agent/scripts/verify_all.py +327 -0
- package/.agent/skills/api-patterns/SKILL.md +84 -0
- package/.agent/skills/api-patterns/api-style.md +42 -0
- package/.agent/skills/api-patterns/auth.md +24 -0
- package/.agent/skills/api-patterns/documentation.md +26 -0
- package/.agent/skills/api-patterns/graphql.md +41 -0
- package/.agent/skills/api-patterns/rate-limiting.md +31 -0
- package/.agent/skills/api-patterns/response.md +37 -0
- package/.agent/skills/api-patterns/rest.md +40 -0
- package/.agent/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agent/skills/api-patterns/security-testing.md +122 -0
- package/.agent/skills/api-patterns/trpc.md +41 -0
- package/.agent/skills/api-patterns/versioning.md +22 -0
- package/.agent/skills/app-builder/SKILL.md +75 -0
- package/.agent/skills/app-builder/agent-coordination.md +74 -0
- package/.agent/skills/app-builder/feature-building.md +53 -0
- package/.agent/skills/app-builder/project-detection.md +34 -0
- package/.agent/skills/app-builder/scaffolding.md +118 -0
- package/.agent/skills/app-builder/tech-stack.md +40 -0
- package/.agent/skills/app-builder/templates/SKILL.md +39 -0
- package/.agent/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agent/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agent/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agent/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agent/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
- package/.agent/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
- package/.agent/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
- package/.agent/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agent/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
- package/.agent/skills/architecture/SKILL.md +57 -0
- package/.agent/skills/architecture/context-discovery.md +43 -0
- package/.agent/skills/architecture/examples.md +94 -0
- package/.agent/skills/architecture/pattern-selection.md +68 -0
- package/.agent/skills/architecture/patterns-reference.md +50 -0
- package/.agent/skills/architecture/trade-off-analysis.md +77 -0
- package/.agent/skills/bash-linux/SKILL.md +201 -0
- package/.agent/skills/behavioral-modes/SKILL.md +264 -0
- package/.agent/skills/brainstorming/SKILL.md +164 -0
- package/.agent/skills/brainstorming/dynamic-questioning.md +359 -0
- package/.agent/skills/clean-code/SKILL.md +200 -0
- package/.agent/skills/code-review-checklist/SKILL.md +125 -0
- package/.agent/skills/database-design/SKILL.md +54 -0
- package/.agent/skills/database-design/database-selection.md +43 -0
- package/.agent/skills/database-design/indexing.md +39 -0
- package/.agent/skills/database-design/migrations.md +50 -0
- package/.agent/skills/database-design/optimization.md +36 -0
- package/.agent/skills/database-design/orm-selection.md +30 -0
- package/.agent/skills/database-design/schema-design.md +56 -0
- package/.agent/skills/database-design/scripts/schema_validator.py +172 -0
- package/.agent/skills/deployment-procedures/SKILL.md +241 -0
- package/.agent/skills/doc.md +177 -0
- package/.agent/skills/documentation-templates/SKILL.md +194 -0
- package/.agent/skills/frontend-design/SKILL.md +418 -0
- package/.agent/skills/frontend-design/animation-guide.md +331 -0
- package/.agent/skills/frontend-design/color-system.md +307 -0
- package/.agent/skills/frontend-design/decision-trees.md +418 -0
- package/.agent/skills/frontend-design/motion-graphics.md +306 -0
- package/.agent/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agent/skills/frontend-design/scripts/ux_audit.py +727 -0
- package/.agent/skills/frontend-design/typography-system.md +345 -0
- package/.agent/skills/frontend-design/ux-psychology.md +1118 -0
- package/.agent/skills/frontend-design/visual-effects.md +383 -0
- package/.agent/skills/game-development/2d-games/SKILL.md +119 -0
- package/.agent/skills/game-development/3d-games/SKILL.md +135 -0
- package/.agent/skills/game-development/SKILL.md +167 -0
- package/.agent/skills/game-development/game-art/SKILL.md +185 -0
- package/.agent/skills/game-development/game-audio/SKILL.md +190 -0
- package/.agent/skills/game-development/game-design/SKILL.md +129 -0
- package/.agent/skills/game-development/mobile-games/SKILL.md +108 -0
- package/.agent/skills/game-development/multiplayer/SKILL.md +132 -0
- package/.agent/skills/game-development/pc-games/SKILL.md +144 -0
- package/.agent/skills/game-development/vr-ar/SKILL.md +123 -0
- package/.agent/skills/game-development/web-games/SKILL.md +150 -0
- package/.agent/skills/geo-fundamentals/SKILL.md +155 -0
- package/.agent/skills/geo-fundamentals/scripts/geo_checker.py +289 -0
- package/.agent/skills/i18n-localization/SKILL.md +154 -0
- package/.agent/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agent/skills/intelligent-routing/SKILL.md +335 -0
- package/.agent/skills/lint-and-validate/SKILL.md +44 -0
- package/.agent/skills/lint-and-validate/scripts/lint_runner.py +184 -0
- package/.agent/skills/lint-and-validate/scripts/type_coverage.py +173 -0
- package/.agent/skills/mcp-builder/SKILL.md +176 -0
- package/.agent/skills/mobile-design/SKILL.md +394 -0
- package/.agent/skills/mobile-design/decision-trees.md +516 -0
- package/.agent/skills/mobile-design/mobile-backend.md +491 -0
- package/.agent/skills/mobile-design/mobile-color-system.md +420 -0
- package/.agent/skills/mobile-design/mobile-debugging.md +122 -0
- package/.agent/skills/mobile-design/mobile-design-thinking.md +355 -0
- package/.agent/skills/mobile-design/mobile-navigation.md +458 -0
- package/.agent/skills/mobile-design/mobile-performance.md +767 -0
- package/.agent/skills/mobile-design/mobile-testing.md +356 -0
- package/.agent/skills/mobile-design/mobile-typography.md +432 -0
- package/.agent/skills/mobile-design/platform-android.md +666 -0
- package/.agent/skills/mobile-design/platform-ios.md +561 -0
- package/.agent/skills/mobile-design/scripts/mobile_audit.py +670 -0
- package/.agent/skills/mobile-design/touch-psychology.md +537 -0
- package/.agent/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +311 -0
- package/.agent/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +241 -0
- package/.agent/skills/nextjs-react-expert/3-server-server-side-performance.md +489 -0
- package/.agent/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +263 -0
- package/.agent/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +581 -0
- package/.agent/skills/nextjs-react-expert/6-rendering-rendering-performance.md +431 -0
- package/.agent/skills/nextjs-react-expert/7-js-javascript-performance.md +683 -0
- package/.agent/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +149 -0
- package/.agent/skills/nextjs-react-expert/SKILL.md +286 -0
- package/.agent/skills/nextjs-react-expert/scripts/convert_rules.py +222 -0
- package/.agent/skills/nextjs-react-expert/scripts/react_performance_checker.py +252 -0
- package/.agent/skills/nodejs-best-practices/SKILL.md +333 -0
- package/.agent/skills/parallel-agents/SKILL.md +194 -0
- package/.agent/skills/performance-profiling/SKILL.md +149 -0
- package/.agent/skills/performance-profiling/scripts/lighthouse_audit.py +76 -0
- package/.agent/skills/plan-writing/SKILL.md +152 -0
- package/.agent/skills/powershell-windows/SKILL.md +166 -0
- package/.agent/skills/python-patterns/SKILL.md +441 -0
- package/.agent/skills/red-team-tactics/SKILL.md +203 -0
- package/.agent/skills/rust-pro/SKILL.md +190 -0
- package/.agent/skills/seo-fundamentals/SKILL.md +135 -0
- package/.agent/skills/seo-fundamentals/scripts/seo_checker.py +215 -0
- package/.agent/skills/server-management/SKILL.md +161 -0
- package/.agent/skills/systematic-debugging/SKILL.md +114 -0
- package/.agent/skills/tailwind-patterns/SKILL.md +269 -0
- package/.agent/skills/tdd-workflow/SKILL.md +149 -0
- package/.agent/skills/testing-patterns/SKILL.md +178 -0
- package/.agent/skills/testing-patterns/scripts/test_runner.py +219 -0
- package/.agent/skills/vulnerability-scanner/SKILL.md +276 -0
- package/.agent/skills/vulnerability-scanner/checklists.md +131 -0
- package/.agent/skills/vulnerability-scanner/scripts/security_scan.py +459 -0
- package/.agent/skills/web-design-guidelines/SKILL.md +57 -0
- package/.agent/skills/webapp-testing/SKILL.md +187 -0
- package/.agent/skills/webapp-testing/scripts/playwright_runner.py +173 -0
- package/.agent/workflows/brainstorm.md +113 -0
- package/.agent/workflows/create.md +59 -0
- package/.agent/workflows/debug.md +103 -0
- package/.agent/workflows/deploy.md +176 -0
- package/.agent/workflows/enhance.md +63 -0
- package/.agent/workflows/orchestrate.md +242 -0
- package/.agent/workflows/plan.md +89 -0
- package/.agent/workflows/preview.md +80 -0
- package/.agent/workflows/restore-localize-compat.md +525 -0
- package/.agent/workflows/status.md +86 -0
- package/.agent/workflows/test.md +144 -0
- package/.agent/workflows/ui-ux-pro-max.md +295 -0
- package/AGENT_FLOW.md +609 -0
- package/CHANGELOG.md +68 -0
- package/LICENSE +21 -0
- package/README.md +260 -0
- package/bin/adapters/base.js +63 -0
- package/bin/adapters/codex.js +391 -0
- package/bin/adapters/gemini.js +137 -0
- package/bin/ag-kit.js +1336 -0
- package/bin/core/builder.js +80 -0
- package/bin/core/generator.js +59 -0
- package/bin/core/resource-loader.js +64 -0
- package/bin/core/transformer.js +208 -0
- package/bin/interactive.js +65 -0
- package/bin/utils/atomic-writer.js +97 -0
- package/bin/utils/git-helper.js +68 -0
- package/bin/utils/managed-block.js +65 -0
- package/bin/utils/manifest.js +241 -0
- package/bin/utils.js +82 -0
- package/docs/codex-rules-template.md +36 -0
- package/docs/mapping-spec.md +68 -0
- package/docs/multi-target-adapter.md +80 -0
- package/docs/official/README.md +53 -0
- package/docs/official/antigravity/agent-modes-settings.md +64 -0
- package/docs/official/antigravity/rules-workflows.md +96 -0
- package/docs/official/antigravity/skills.md +147 -0
- package/docs/official/codex/agents-md.md +119 -0
- package/docs/official/codex/config-advanced.md +358 -0
- package/docs/official/codex/config-basic.md +141 -0
- package/docs/official/codex/config-reference.md +223 -0
- package/docs/official/codex/config-sample.md +216 -0
- package/docs/official/codex/mcp.md +107 -0
- package/docs/official/codex/rules.md +79 -0
- package/docs/official/codex/skills.md +114 -0
- package/docs/official/sources-index.md +32 -0
- package/docs/operations.md +145 -0
- package/docs/terminology-style-guide.md +69 -0
- package/package.json +51 -0
- package/scripts/postinstall-check.js +112 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
const ResourceLoader = require("./resource-loader");
|
|
5
|
+
const ResourceTransformer = require("./transformer");
|
|
6
|
+
const RuleGenerator = require("./generator");
|
|
7
|
+
const pkg = require("../../package.json");
|
|
8
|
+
|
|
9
|
+
class CodexBuilder {
|
|
10
|
+
/**
|
|
11
|
+
* Build a Codex structure from a legacy/source repository
|
|
12
|
+
* @param {string} sourceRoot Root of the repo (containing .agent/)
|
|
13
|
+
* @param {string} outputDir Directory to output the built .agents-compatible structure
|
|
14
|
+
*/
|
|
15
|
+
static build(sourceRoot, outputDir) {
|
|
16
|
+
if (fs.existsSync(outputDir)) {
|
|
17
|
+
fs.rmSync(outputDir, { recursive: true, force: true });
|
|
18
|
+
}
|
|
19
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
20
|
+
|
|
21
|
+
// 1. Load Resources
|
|
22
|
+
const loader = new ResourceLoader(sourceRoot);
|
|
23
|
+
const resources = loader.loadAll();
|
|
24
|
+
|
|
25
|
+
// 2. Transform
|
|
26
|
+
const transformResult = ResourceTransformer.transform(resources);
|
|
27
|
+
|
|
28
|
+
// 3. Execute File/Dir Copying based on Transform Map
|
|
29
|
+
for (const map of transformResult.mappedFiles) {
|
|
30
|
+
const dest = path.join(outputDir, map.destPath);
|
|
31
|
+
const destParent = path.dirname(dest);
|
|
32
|
+
|
|
33
|
+
if (!fs.existsSync(destParent)) {
|
|
34
|
+
fs.mkdirSync(destParent, { recursive: true });
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (map.isDir) {
|
|
38
|
+
// Recursive copy
|
|
39
|
+
CodexBuilder._copyDir(map.src, dest);
|
|
40
|
+
} else if (typeof map.content === "string") {
|
|
41
|
+
fs.writeFileSync(dest, map.content);
|
|
42
|
+
} else {
|
|
43
|
+
if (fs.existsSync(map.src)) {
|
|
44
|
+
fs.copyFileSync(map.src, dest);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 4. Generate & Write Metadata
|
|
50
|
+
const { agentsMd, antigravityRules, codexJson } = RuleGenerator.generate(transformResult, pkg.version);
|
|
51
|
+
|
|
52
|
+
fs.writeFileSync(path.join(outputDir, "codex.json"), `${JSON.stringify(codexJson, null, 2)}\n`);
|
|
53
|
+
fs.writeFileSync(path.join(outputDir, "AGENTS.md"), `${agentsMd}\n`);
|
|
54
|
+
fs.writeFileSync(path.join(outputDir, "antigravity.rules"), `${antigravityRules}\n`);
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
outputDir,
|
|
58
|
+
stats: {
|
|
59
|
+
skills: resources.skills.length,
|
|
60
|
+
workflows: resources.workflows.length
|
|
61
|
+
}
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
static _copyDir(src, dest) {
|
|
66
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
67
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
68
|
+
for (const entry of entries) {
|
|
69
|
+
const srcPath = path.join(src, entry.name);
|
|
70
|
+
const destPath = path.join(dest, entry.name);
|
|
71
|
+
if (entry.isDirectory()) {
|
|
72
|
+
CodexBuilder._copyDir(srcPath, destPath);
|
|
73
|
+
} else {
|
|
74
|
+
fs.copyFileSync(srcPath, destPath);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
module.exports = CodexBuilder;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
class RuleGenerator {
|
|
2
|
+
/**
|
|
3
|
+
* Generate content for AGENTS.md, antigravity.rules and codex.json
|
|
4
|
+
* @param {object} transformResult Result from Transformer
|
|
5
|
+
* @returns {object} { agentsMd: string, antigravityRules: string, codexJson: object }
|
|
6
|
+
*/
|
|
7
|
+
static generate(transformResult, version) {
|
|
8
|
+
if (typeof version !== "string" || version.trim() === "") {
|
|
9
|
+
throw new Error("RuleGenerator.generate 需要显式传入版本号");
|
|
10
|
+
}
|
|
11
|
+
const { metadata } = transformResult;
|
|
12
|
+
|
|
13
|
+
// 1. Generate codex.json
|
|
14
|
+
const codexJson = {
|
|
15
|
+
version: version,
|
|
16
|
+
updatedAt: new Date().toISOString(),
|
|
17
|
+
skills: metadata.map(m => ({
|
|
18
|
+
id: m.id,
|
|
19
|
+
originalName: m.originalName,
|
|
20
|
+
path: m.path
|
|
21
|
+
}))
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
// 2. Generate AGENTS.md managed section content
|
|
25
|
+
let agentsMd = `# AI Agent Capabilities (Codex Managed)\n\n`;
|
|
26
|
+
agentsMd += `> **Auto-generated by Antigravity Kit (Codex)**\n`;
|
|
27
|
+
agentsMd += `> Version: ${version}\n\n`;
|
|
28
|
+
|
|
29
|
+
agentsMd += `## Available Skills\n\n`;
|
|
30
|
+
|
|
31
|
+
for (const meta of metadata) {
|
|
32
|
+
agentsMd += `- **${meta.id}** (${meta.type})\n`;
|
|
33
|
+
agentsMd += ` - Path: \`${meta.path}\`\n`;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
agentsMd += `\n## Usage Rules\n\n`;
|
|
37
|
+
agentsMd += `1. Managed resources are synchronized under \`.agents/skills\`.\n`;
|
|
38
|
+
agentsMd += `2. Do not rename managed skill folders manually.\n`;
|
|
39
|
+
agentsMd += `3. Use \`ag-kit doctor --target codex --fix\` to recover missing managed artifacts.\n`;
|
|
40
|
+
|
|
41
|
+
// 3. Generate risk controls
|
|
42
|
+
let antigravityRules = `# Antigravity Risk Controls (Codex Managed)\n\n`;
|
|
43
|
+
antigravityRules += `version: ${version}\n`;
|
|
44
|
+
antigravityRules += `generated_at: ${new Date().toISOString()}\n\n`;
|
|
45
|
+
antigravityRules += `## Controls\n`;
|
|
46
|
+
antigravityRules += `- Managed skills are stored under \`.agents/skills\`.\n`;
|
|
47
|
+
antigravityRules += `- Keep generated skill IDs stable to avoid selector drift.\n`;
|
|
48
|
+
antigravityRules += `- If integrity checks fail, run \`ag-kit doctor --target codex --fix\`.\n`;
|
|
49
|
+
antigravityRules += `- Before destructive updates, preserve manual edits in source control.\n`;
|
|
50
|
+
|
|
51
|
+
return {
|
|
52
|
+
agentsMd,
|
|
53
|
+
antigravityRules,
|
|
54
|
+
codexJson
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
module.exports = RuleGenerator;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
class ResourceLoader {
|
|
5
|
+
constructor(rootDir) {
|
|
6
|
+
this.rootDir = rootDir;
|
|
7
|
+
this.skillsDir = path.join(rootDir, ".agent", "skills");
|
|
8
|
+
this.workflowsDir = path.join(rootDir, ".agent", "workflows");
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Load all skills and workflows
|
|
13
|
+
*/
|
|
14
|
+
loadAll() {
|
|
15
|
+
return {
|
|
16
|
+
skills: this.loadSkills(),
|
|
17
|
+
workflows: this.loadWorkflows()
|
|
18
|
+
};
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
loadSkills() {
|
|
22
|
+
if (!fs.existsSync(this.skillsDir)) return [];
|
|
23
|
+
|
|
24
|
+
const skills = [];
|
|
25
|
+
const entries = fs.readdirSync(this.skillsDir, { withFileTypes: true });
|
|
26
|
+
|
|
27
|
+
for (const entry of entries) {
|
|
28
|
+
if (entry.isDirectory()) {
|
|
29
|
+
const skillPath = path.join(this.skillsDir, entry.name);
|
|
30
|
+
const skillFile = path.join(skillPath, "SKILL.md");
|
|
31
|
+
|
|
32
|
+
if (fs.existsSync(skillFile)) {
|
|
33
|
+
skills.push({
|
|
34
|
+
name: entry.name,
|
|
35
|
+
path: skillPath,
|
|
36
|
+
entryFile: "SKILL.md", // Relative to skillPath
|
|
37
|
+
type: "skill"
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
return skills;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
loadWorkflows() {
|
|
46
|
+
if (!fs.existsSync(this.workflowsDir)) return [];
|
|
47
|
+
|
|
48
|
+
const workflows = [];
|
|
49
|
+
const entries = fs.readdirSync(this.workflowsDir, { withFileTypes: true });
|
|
50
|
+
|
|
51
|
+
for (const entry of entries) {
|
|
52
|
+
if (entry.isFile() && entry.name.endsWith(".md")) {
|
|
53
|
+
workflows.push({
|
|
54
|
+
name: path.basename(entry.name, ".md"),
|
|
55
|
+
path: path.join(this.workflowsDir, entry.name),
|
|
56
|
+
type: "workflow"
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return workflows;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
module.exports = ResourceLoader;
|
|
@@ -0,0 +1,208 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
class ResourceTransformer {
|
|
5
|
+
/**
|
|
6
|
+
* Transform raw resources into Codex-compatible structure
|
|
7
|
+
* @param {object} resources { skills: [], workflows: [] }
|
|
8
|
+
* @returns {object} { mappedFiles: [], metadata: [] }
|
|
9
|
+
*/
|
|
10
|
+
static transform(resources) {
|
|
11
|
+
const result = {
|
|
12
|
+
mappedFiles: [], // List of { src, destPath }
|
|
13
|
+
metadata: [] // List of desc for rules
|
|
14
|
+
};
|
|
15
|
+
const usedIds = new Set();
|
|
16
|
+
|
|
17
|
+
function allocateUniqueId(baseId) {
|
|
18
|
+
let candidate = baseId;
|
|
19
|
+
let suffix = 2;
|
|
20
|
+
while (usedIds.has(candidate)) {
|
|
21
|
+
candidate = `${baseId}-${suffix}`;
|
|
22
|
+
suffix += 1;
|
|
23
|
+
}
|
|
24
|
+
usedIds.add(candidate);
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function stripWrappingQuotes(value) {
|
|
29
|
+
const trimmed = String(value || "").trim();
|
|
30
|
+
if ((trimmed.startsWith("\"") && trimmed.endsWith("\"")) || (trimmed.startsWith("'") && trimmed.endsWith("'"))) {
|
|
31
|
+
return trimmed.slice(1, -1);
|
|
32
|
+
}
|
|
33
|
+
return trimmed;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function foldYamlBlock(lines) {
|
|
37
|
+
const paragraphs = [];
|
|
38
|
+
let current = [];
|
|
39
|
+
|
|
40
|
+
for (const line of lines) {
|
|
41
|
+
const trimmed = line.trim();
|
|
42
|
+
if (!trimmed) {
|
|
43
|
+
if (current.length > 0) {
|
|
44
|
+
paragraphs.push(current.join(" "));
|
|
45
|
+
current = [];
|
|
46
|
+
}
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
current.push(trimmed);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (current.length > 0) {
|
|
53
|
+
paragraphs.push(current.join(" "));
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return paragraphs.join("\n");
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function collectIndentedLines(lines, startIndex) {
|
|
60
|
+
const collected = [];
|
|
61
|
+
let idx = startIndex;
|
|
62
|
+
let baseIndent = -1;
|
|
63
|
+
|
|
64
|
+
while (idx < lines.length) {
|
|
65
|
+
const line = lines[idx];
|
|
66
|
+
if (line.trim() === "") {
|
|
67
|
+
if (collected.length > 0) {
|
|
68
|
+
collected.push("");
|
|
69
|
+
}
|
|
70
|
+
idx += 1;
|
|
71
|
+
continue;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const indentMatch = line.match(/^(\s+)/);
|
|
75
|
+
if (!indentMatch) {
|
|
76
|
+
break;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const indentLength = indentMatch[1].length;
|
|
80
|
+
if (baseIndent < 0) {
|
|
81
|
+
baseIndent = indentLength;
|
|
82
|
+
}
|
|
83
|
+
if (indentLength < baseIndent) {
|
|
84
|
+
break;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
collected.push(line.slice(baseIndent));
|
|
88
|
+
idx += 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
return { lines: collected, nextIndex: idx - 1 };
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function parseFrontmatter(content) {
|
|
95
|
+
const normalized = String(content || "").replace(/\r\n?/g, "\n");
|
|
96
|
+
if (!normalized.startsWith("---\n")) {
|
|
97
|
+
return { frontmatter: {}, body: normalized };
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const match = normalized.match(/^---\n([\s\S]*?)\n---(?:\n|$)/);
|
|
101
|
+
if (!match) {
|
|
102
|
+
return { frontmatter: {}, body: normalized };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const fmRaw = match[1];
|
|
106
|
+
const frontmatter = {};
|
|
107
|
+
const lines = fmRaw.split("\n");
|
|
108
|
+
|
|
109
|
+
for (let i = 0; i < lines.length; i++) {
|
|
110
|
+
const line = lines[i];
|
|
111
|
+
const keyMatch = line.match(/^([A-Za-z0-9_-]+)\s*:\s*(.*)$/);
|
|
112
|
+
if (!keyMatch) continue;
|
|
113
|
+
|
|
114
|
+
const key = keyMatch[1];
|
|
115
|
+
const inlineValue = keyMatch[2];
|
|
116
|
+
if (!key) continue;
|
|
117
|
+
|
|
118
|
+
if (inlineValue === "|" || inlineValue === ">") {
|
|
119
|
+
const block = collectIndentedLines(lines, i + 1);
|
|
120
|
+
i = Math.max(i, block.nextIndex);
|
|
121
|
+
frontmatter[key] = inlineValue === ">"
|
|
122
|
+
? foldYamlBlock(block.lines)
|
|
123
|
+
: block.lines.join("\n").trim();
|
|
124
|
+
continue;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
if (inlineValue.trim() === "") {
|
|
128
|
+
const block = collectIndentedLines(lines, i + 1);
|
|
129
|
+
if (block.lines.length > 0) {
|
|
130
|
+
i = Math.max(i, block.nextIndex);
|
|
131
|
+
frontmatter[key] = block.lines.join("\n").trim();
|
|
132
|
+
} else {
|
|
133
|
+
frontmatter[key] = "";
|
|
134
|
+
}
|
|
135
|
+
continue;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
frontmatter[key] = stripWrappingQuotes(inlineValue);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const body = normalized.slice(match[0].length);
|
|
142
|
+
return { frontmatter, body };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// 1. Process Skills
|
|
146
|
+
// Strategy: Skill folder -> skills/<name>/SKILL.md
|
|
147
|
+
for (const skill of resources.skills) {
|
|
148
|
+
const codexName = allocateUniqueId(skill.name);
|
|
149
|
+
const destDir = `skills/${codexName}`;
|
|
150
|
+
|
|
151
|
+
// Keep the full skill folder to preserve resources/scripts exactly.
|
|
152
|
+
result.mappedFiles.push({
|
|
153
|
+
src: skill.path, // The folder
|
|
154
|
+
destPath: destDir,
|
|
155
|
+
isDir: true
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
result.metadata.push({
|
|
159
|
+
id: codexName,
|
|
160
|
+
originalName: skill.name,
|
|
161
|
+
type: "skill",
|
|
162
|
+
path: `${destDir}/SKILL.md`
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// 2. Process Workflows
|
|
167
|
+
// Codex only understands skills. Convert each workflow markdown to a valid SKILL.md.
|
|
168
|
+
for (const workflow of resources.workflows) {
|
|
169
|
+
const codexName = allocateUniqueId(`workflow-${workflow.name}`);
|
|
170
|
+
const destDir = `skills/${codexName}`;
|
|
171
|
+
|
|
172
|
+
const rawWorkflow = fs.existsSync(workflow.path)
|
|
173
|
+
? fs.readFileSync(workflow.path, "utf8")
|
|
174
|
+
: "";
|
|
175
|
+
const parsed = parseFrontmatter(rawWorkflow);
|
|
176
|
+
const descriptionRaw = parsed.frontmatter.description
|
|
177
|
+
|| `Antigravity workflow bridge for /${workflow.name}`;
|
|
178
|
+
const description = JSON.stringify(String(descriptionRaw).replace(/\s+/g, " ").trim());
|
|
179
|
+
|
|
180
|
+
const generatedSkill = [
|
|
181
|
+
"---",
|
|
182
|
+
`name: ${codexName}`,
|
|
183
|
+
`description: ${description}`,
|
|
184
|
+
"---",
|
|
185
|
+
"",
|
|
186
|
+
parsed.body.trimStart(),
|
|
187
|
+
"",
|
|
188
|
+
].join("\n");
|
|
189
|
+
|
|
190
|
+
result.mappedFiles.push({
|
|
191
|
+
content: generatedSkill,
|
|
192
|
+
destPath: `${destDir}/SKILL.md`,
|
|
193
|
+
isDir: false
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
result.metadata.push({
|
|
197
|
+
id: codexName,
|
|
198
|
+
originalName: workflow.name,
|
|
199
|
+
type: "workflow-as-skill",
|
|
200
|
+
path: `${destDir}/SKILL.md`
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return result;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
module.exports = ResourceTransformer;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
const readline = require("readline");
|
|
2
|
+
|
|
3
|
+
function createInterface() {
|
|
4
|
+
return readline.createInterface({
|
|
5
|
+
input: process.stdin,
|
|
6
|
+
output: process.stdout,
|
|
7
|
+
});
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
function askQuestion(rl, query) {
|
|
11
|
+
return new Promise((resolve) => {
|
|
12
|
+
rl.question(query, (answer) => {
|
|
13
|
+
resolve(answer);
|
|
14
|
+
});
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Prompt user to select targets from a list.
|
|
20
|
+
* Supports multiple selection by comma separated numbers or names.
|
|
21
|
+
* Currently simplified for 'gemini' and 'codex'.
|
|
22
|
+
* @param {object} options CLI options
|
|
23
|
+
* @returns {Promise<string[]>} List of selected targets
|
|
24
|
+
*/
|
|
25
|
+
async function selectTargets(options) {
|
|
26
|
+
if (options.nonInteractive) {
|
|
27
|
+
throw new Error("非交互模式下必须通过 --target 或 --targets 指定目标");
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const rl = createInterface();
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
console.log("\n🎯 请选择要安装的目标 (多选请用逗号分隔):");
|
|
34
|
+
console.log(" 1. Gemini (适用于 Cursor/VSCode)");
|
|
35
|
+
console.log(" 2. Codex (兼容性增强版)");
|
|
36
|
+
|
|
37
|
+
const answer = await askQuestion(rl, "\n请输入序号或名称(必填): ");
|
|
38
|
+
const input = answer.trim();
|
|
39
|
+
|
|
40
|
+
if (!input) {
|
|
41
|
+
throw new Error("必须选择至少一个目标");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const selection = [];
|
|
45
|
+
const parts = input.split(/[,,\s]+/);
|
|
46
|
+
|
|
47
|
+
for (const part of parts) {
|
|
48
|
+
const p = part.toLowerCase();
|
|
49
|
+
if (p === "1" || p === "gemini") selection.push("gemini");
|
|
50
|
+
else if (p === "2" || p === "codex") selection.push("codex");
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (selection.length === 0) {
|
|
54
|
+
throw new Error("无效的目标选择,请输入 1 / 2 / gemini / codex(可多选)");
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return [...new Set(selection)];
|
|
58
|
+
} finally {
|
|
59
|
+
rl.close();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
module.exports = {
|
|
64
|
+
selectTargets
|
|
65
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
const os = require("os");
|
|
4
|
+
|
|
5
|
+
class AtomicWriter {
|
|
6
|
+
/**
|
|
7
|
+
* Atomically copy a directory structure.
|
|
8
|
+
* 1. Copies source to a temp directory.
|
|
9
|
+
* 2. Renames temp directory to target directory (atomic on POSIX, nearly atomic win32 move).
|
|
10
|
+
*
|
|
11
|
+
* @param {string} sourceDir Source directory path
|
|
12
|
+
* @param {string} targetDir Target directory path
|
|
13
|
+
* @param {object} options { logger: function, force: boolean }
|
|
14
|
+
*/
|
|
15
|
+
static atomicCopyDir(sourceDir, targetDir, options = {}) {
|
|
16
|
+
const log = options.logger || console.log;
|
|
17
|
+
const tempName = `.tmp-${Date.now()}-${Math.random().toString(36).substring(2, 8)}`;
|
|
18
|
+
const tempTarget = path.join(path.dirname(targetDir), tempName);
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
// 1. Prepare temp dir
|
|
22
|
+
// Use copy logic (recursive)
|
|
23
|
+
AtomicWriter._copyDirRecursive(sourceDir, tempTarget);
|
|
24
|
+
|
|
25
|
+
// 2. Backup if exists (optional strategy, but standard atomic replace usually implies overwrite)
|
|
26
|
+
// If target exists, we need to remove it OR rename it away before renaming temp in?
|
|
27
|
+
// fs.rename overwrite behavior:
|
|
28
|
+
// - Linux/Mac: Atomic replacement if target exists (directory replacement rules apply).
|
|
29
|
+
// - Windows: rename fails if target exists.
|
|
30
|
+
|
|
31
|
+
// 2. Atomic replacement logic:
|
|
32
|
+
// To be cross-platform safe and minimize the "missing target" window:
|
|
33
|
+
// 1. Rename existing targetDir to a temporary backup name.
|
|
34
|
+
// 2. Rename tempTarget to targetDir.
|
|
35
|
+
// 3. Delete the backup if 1 & 2 succeed.
|
|
36
|
+
if (fs.existsSync(targetDir)) {
|
|
37
|
+
const backupPath = `${targetDir}.bak-${Date.now()}-${Math.random().toString(36).substring(2, 5)}`;
|
|
38
|
+
try {
|
|
39
|
+
fs.renameSync(targetDir, backupPath);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
// In some cases (e.g. Windows EPERM/EBUSY), rename might fail.
|
|
42
|
+
// If we can't rename, the only fallback is direct deletion, but we prefer failing
|
|
43
|
+
// and asking user to stop processes than being non-atomic.
|
|
44
|
+
throw new Error(`无法先行重命名旧目标目录 (${targetDir}): ${err.message}`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
try {
|
|
48
|
+
fs.renameSync(tempTarget, targetDir);
|
|
49
|
+
} catch (err) {
|
|
50
|
+
// Failed to move new content into place after moving old one out.
|
|
51
|
+
// Attempt to restore the backup.
|
|
52
|
+
try {
|
|
53
|
+
fs.renameSync(backupPath, targetDir);
|
|
54
|
+
} catch (restoreErr) {
|
|
55
|
+
throw new Error(`临界失败: 无法将新版本移入目标且无法恢复旧版本。旧版本位于 ${backupPath}。错误: ${err.message}`);
|
|
56
|
+
}
|
|
57
|
+
throw err;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Step 3: Success! Cleanup backup.
|
|
61
|
+
try {
|
|
62
|
+
fs.rmSync(backupPath, { recursive: true, force: true });
|
|
63
|
+
} catch (cleanupErr) {
|
|
64
|
+
// Not a critical failure for the operation itself, just messy.
|
|
65
|
+
if (options.logger) options.logger(`[warn] 无法清理备份目录 ${backupPath}: ${cleanupErr.message}`);
|
|
66
|
+
}
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// If target didn't exist or was removed (Win32 path)
|
|
71
|
+
fs.renameSync(tempTarget, targetDir);
|
|
72
|
+
|
|
73
|
+
} catch (err) {
|
|
74
|
+
// Cleanup temp
|
|
75
|
+
if (fs.existsSync(tempTarget)) {
|
|
76
|
+
fs.rmSync(tempTarget, { recursive: true, force: true });
|
|
77
|
+
}
|
|
78
|
+
throw new Error(`原子写入失败: ${err.message}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
static _copyDirRecursive(src, dest) {
|
|
83
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
84
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
const srcPath = path.join(src, entry.name);
|
|
87
|
+
const destPath = path.join(dest, entry.name);
|
|
88
|
+
if (entry.isDirectory()) {
|
|
89
|
+
AtomicWriter._copyDirRecursive(srcPath, destPath);
|
|
90
|
+
} else {
|
|
91
|
+
fs.copyFileSync(srcPath, destPath);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
module.exports = AtomicWriter;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
const fs = require("fs");
|
|
2
|
+
const path = require("path");
|
|
3
|
+
|
|
4
|
+
class GitHelper {
|
|
5
|
+
/**
|
|
6
|
+
* Remove ignore rules matching specific patterns from .gitignore
|
|
7
|
+
* @param {string} workspaceRoot Directory containing .gitignore
|
|
8
|
+
* @param {string[]} targetPatterns List of patterns to remove (e.g. ['.agent', '.codex'])
|
|
9
|
+
* @param {object} options { dryRun: boolean }
|
|
10
|
+
* @returns {object} { fileExists, removedCount, dryRun }
|
|
11
|
+
*/
|
|
12
|
+
static removeIgnoreRules(workspaceRoot, targetPatterns, options) {
|
|
13
|
+
const gitIgnorePath = path.join(workspaceRoot, ".gitignore");
|
|
14
|
+
if (!fs.existsSync(gitIgnorePath)) {
|
|
15
|
+
return { fileExists: false, removedCount: 0, dryRun: options.dryRun };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const original = fs.readFileSync(gitIgnorePath, "utf8");
|
|
19
|
+
const lineEnding = original.includes("\r\n") ? "\r\n" : "\n";
|
|
20
|
+
const hadTrailingNewline = /\r?\n$/.test(original);
|
|
21
|
+
const lines = original.split(/\r?\n/);
|
|
22
|
+
|
|
23
|
+
const kept = [];
|
|
24
|
+
let removedCount = 0;
|
|
25
|
+
|
|
26
|
+
function isTargetRule(line) {
|
|
27
|
+
const trimmed = line.trim();
|
|
28
|
+
if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("!")) return false;
|
|
29
|
+
let pattern = trimmed;
|
|
30
|
+
// Remove leading/trailing patterns
|
|
31
|
+
while (pattern.startsWith("**/")) pattern = pattern.slice(3);
|
|
32
|
+
while (pattern.startsWith("/")) pattern = pattern.slice(1);
|
|
33
|
+
while (pattern.endsWith("/")) pattern = pattern.slice(0, -1);
|
|
34
|
+
while (pattern.endsWith("/**")) pattern = pattern.slice(0, -3);
|
|
35
|
+
|
|
36
|
+
if (!pattern) return false;
|
|
37
|
+
const segments = pattern.split("/");
|
|
38
|
+
|
|
39
|
+
// Check if any segment matches our target patterns
|
|
40
|
+
return segments.some((segment) => targetPatterns.includes(segment));
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
if (isTargetRule(line)) {
|
|
45
|
+
removedCount += 1;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
kept.push(line);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (removedCount === 0) {
|
|
52
|
+
return { fileExists: true, removedCount: 0, dryRun: options.dryRun };
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
let updated = kept.join(lineEnding);
|
|
56
|
+
if (hadTrailingNewline) {
|
|
57
|
+
updated += lineEnding;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!options.dryRun) {
|
|
61
|
+
fs.writeFileSync(gitIgnorePath, updated, "utf8");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return { fileExists: true, removedCount, dryRun: options.dryRun };
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = GitHelper;
|