@mison/ling 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/.shared/ui-ux-pro-max/data/charts.csv +26 -0
- package/.agents/.shared/ui-ux-pro-max/data/colors.csv +97 -0
- package/.agents/.shared/ui-ux-pro-max/data/icons.csv +101 -0
- package/.agents/.shared/ui-ux-pro-max/data/landing.csv +31 -0
- package/.agents/.shared/ui-ux-pro-max/data/products.csv +97 -0
- package/.agents/.shared/ui-ux-pro-max/data/prompts.csv +24 -0
- package/.agents/.shared/ui-ux-pro-max/data/react-performance.csv +45 -0
- package/.agents/.shared/ui-ux-pro-max/data/stacks/flutter.csv +53 -0
- package/.agents/.shared/ui-ux-pro-max/data/stacks/html-tailwind.csv +56 -0
- package/.agents/.shared/ui-ux-pro-max/data/stacks/jetpack-compose.csv +53 -0
- package/.agents/.shared/ui-ux-pro-max/data/stacks/nextjs.csv +53 -0
- package/.agents/.shared/ui-ux-pro-max/data/stacks/nuxt-ui.csv +51 -0
- package/.agents/.shared/ui-ux-pro-max/data/stacks/nuxtjs.csv +59 -0
- package/.agents/.shared/ui-ux-pro-max/data/stacks/react-native.csv +52 -0
- package/.agents/.shared/ui-ux-pro-max/data/stacks/react.csv +54 -0
- package/.agents/.shared/ui-ux-pro-max/data/stacks/shadcn.csv +61 -0
- package/.agents/.shared/ui-ux-pro-max/data/stacks/svelte.csv +54 -0
- package/.agents/.shared/ui-ux-pro-max/data/stacks/swiftui.csv +51 -0
- package/.agents/.shared/ui-ux-pro-max/data/stacks/vue.csv +50 -0
- package/.agents/.shared/ui-ux-pro-max/data/styles.csv +59 -0
- package/.agents/.shared/ui-ux-pro-max/data/typography.csv +58 -0
- package/.agents/.shared/ui-ux-pro-max/data/ui-reasoning.csv +101 -0
- package/.agents/.shared/ui-ux-pro-max/data/ux-guidelines.csv +100 -0
- package/.agents/.shared/ui-ux-pro-max/data/web-interface.csv +31 -0
- package/.agents/.shared/ui-ux-pro-max/scripts/core.py +258 -0
- package/.agents/.shared/ui-ux-pro-max/scripts/design_system.py +1067 -0
- package/.agents/.shared/ui-ux-pro-max/scripts/search.py +106 -0
- package/.agents/ARCHITECTURE.md +285 -0
- package/.agents/agents/backend-specialist.md +268 -0
- package/.agents/agents/code-archaeologist.md +106 -0
- package/.agents/agents/database-architect.md +225 -0
- package/.agents/agents/debugger.md +225 -0
- package/.agents/agents/devops-engineer.md +242 -0
- package/.agents/agents/documentation-writer.md +104 -0
- package/.agents/agents/explorer-agent.md +73 -0
- package/.agents/agents/frontend-specialist.md +618 -0
- package/.agents/agents/game-developer.md +162 -0
- package/.agents/agents/mobile-developer.md +382 -0
- package/.agents/agents/orchestrator.md +436 -0
- package/.agents/agents/penetration-tester.md +188 -0
- package/.agents/agents/performance-optimizer.md +187 -0
- package/.agents/agents/product-manager.md +112 -0
- package/.agents/agents/product-owner.md +95 -0
- package/.agents/agents/project-planner.md +405 -0
- package/.agents/agents/qa-automation-engineer.md +103 -0
- package/.agents/agents/security-auditor.md +170 -0
- package/.agents/agents/seo-specialist.md +111 -0
- package/.agents/agents/test-engineer.md +158 -0
- package/.agents/mcp_config.json +22 -0
- package/.agents/rules/GEMINI.md +273 -0
- package/.agents/scripts/auto_preview.py +148 -0
- package/.agents/scripts/checklist.py +217 -0
- package/.agents/scripts/session_manager.py +120 -0
- package/.agents/scripts/verify_all.py +327 -0
- package/.agents/skills/api-patterns/SKILL.md +84 -0
- package/.agents/skills/api-patterns/api-style.md +42 -0
- package/.agents/skills/api-patterns/auth.md +24 -0
- package/.agents/skills/api-patterns/documentation.md +26 -0
- package/.agents/skills/api-patterns/graphql.md +41 -0
- package/.agents/skills/api-patterns/rate-limiting.md +31 -0
- package/.agents/skills/api-patterns/response.md +37 -0
- package/.agents/skills/api-patterns/rest.md +40 -0
- package/.agents/skills/api-patterns/scripts/api_validator.py +211 -0
- package/.agents/skills/api-patterns/security-testing.md +122 -0
- package/.agents/skills/api-patterns/trpc.md +41 -0
- package/.agents/skills/api-patterns/versioning.md +22 -0
- package/.agents/skills/app-builder/SKILL.md +75 -0
- package/.agents/skills/app-builder/agent-coordination.md +74 -0
- package/.agents/skills/app-builder/feature-building.md +53 -0
- package/.agents/skills/app-builder/project-detection.md +34 -0
- package/.agents/skills/app-builder/scaffolding.md +118 -0
- package/.agents/skills/app-builder/tech-stack.md +40 -0
- package/.agents/skills/app-builder/templates/SKILL.md +39 -0
- package/.agents/skills/app-builder/templates/astro-static/TEMPLATE.md +76 -0
- package/.agents/skills/app-builder/templates/chrome-extension/TEMPLATE.md +92 -0
- package/.agents/skills/app-builder/templates/cli-tool/TEMPLATE.md +88 -0
- package/.agents/skills/app-builder/templates/electron-desktop/TEMPLATE.md +88 -0
- package/.agents/skills/app-builder/templates/express-api/TEMPLATE.md +83 -0
- package/.agents/skills/app-builder/templates/flutter-app/TEMPLATE.md +90 -0
- package/.agents/skills/app-builder/templates/monorepo-turborepo/TEMPLATE.md +90 -0
- package/.agents/skills/app-builder/templates/nextjs-fullstack/TEMPLATE.md +122 -0
- package/.agents/skills/app-builder/templates/nextjs-saas/TEMPLATE.md +122 -0
- package/.agents/skills/app-builder/templates/nextjs-static/TEMPLATE.md +169 -0
- package/.agents/skills/app-builder/templates/nuxt-app/TEMPLATE.md +134 -0
- package/.agents/skills/app-builder/templates/python-fastapi/TEMPLATE.md +83 -0
- package/.agents/skills/app-builder/templates/react-native-app/TEMPLATE.md +119 -0
- package/.agents/skills/architecture/SKILL.md +57 -0
- package/.agents/skills/architecture/context-discovery.md +43 -0
- package/.agents/skills/architecture/examples.md +94 -0
- package/.agents/skills/architecture/pattern-selection.md +68 -0
- package/.agents/skills/architecture/patterns-reference.md +50 -0
- package/.agents/skills/architecture/trade-off-analysis.md +77 -0
- package/.agents/skills/bash-linux/SKILL.md +201 -0
- package/.agents/skills/behavioral-modes/SKILL.md +264 -0
- package/.agents/skills/brainstorming/SKILL.md +164 -0
- package/.agents/skills/brainstorming/dynamic-questioning.md +359 -0
- package/.agents/skills/clean-code/SKILL.md +200 -0
- package/.agents/skills/code-review-checklist/SKILL.md +125 -0
- package/.agents/skills/database-design/SKILL.md +54 -0
- package/.agents/skills/database-design/database-selection.md +43 -0
- package/.agents/skills/database-design/indexing.md +39 -0
- package/.agents/skills/database-design/migrations.md +50 -0
- package/.agents/skills/database-design/optimization.md +36 -0
- package/.agents/skills/database-design/orm-selection.md +30 -0
- package/.agents/skills/database-design/schema-design.md +56 -0
- package/.agents/skills/database-design/scripts/schema_validator.py +172 -0
- package/.agents/skills/deployment-procedures/SKILL.md +241 -0
- package/.agents/skills/doc.md +177 -0
- package/.agents/skills/documentation-templates/SKILL.md +194 -0
- package/.agents/skills/frontend-design/SKILL.md +418 -0
- package/.agents/skills/frontend-design/animation-guide.md +331 -0
- package/.agents/skills/frontend-design/color-system.md +307 -0
- package/.agents/skills/frontend-design/decision-trees.md +418 -0
- package/.agents/skills/frontend-design/motion-graphics.md +306 -0
- package/.agents/skills/frontend-design/scripts/accessibility_checker.py +183 -0
- package/.agents/skills/frontend-design/scripts/ux_audit.py +727 -0
- package/.agents/skills/frontend-design/typography-system.md +345 -0
- package/.agents/skills/frontend-design/ux-psychology.md +1118 -0
- package/.agents/skills/frontend-design/visual-effects.md +383 -0
- package/.agents/skills/game-development/2d-games/SKILL.md +119 -0
- package/.agents/skills/game-development/3d-games/SKILL.md +135 -0
- package/.agents/skills/game-development/SKILL.md +167 -0
- package/.agents/skills/game-development/game-art/SKILL.md +185 -0
- package/.agents/skills/game-development/game-audio/SKILL.md +190 -0
- package/.agents/skills/game-development/game-design/SKILL.md +129 -0
- package/.agents/skills/game-development/mobile-games/SKILL.md +108 -0
- package/.agents/skills/game-development/multiplayer/SKILL.md +132 -0
- package/.agents/skills/game-development/pc-games/SKILL.md +144 -0
- package/.agents/skills/game-development/vr-ar/SKILL.md +123 -0
- package/.agents/skills/game-development/web-games/SKILL.md +150 -0
- package/.agents/skills/geo-fundamentals/SKILL.md +155 -0
- package/.agents/skills/geo-fundamentals/scripts/geo_checker.py +289 -0
- package/.agents/skills/i18n-localization/SKILL.md +154 -0
- package/.agents/skills/i18n-localization/scripts/i18n_checker.py +241 -0
- package/.agents/skills/intelligent-routing/SKILL.md +335 -0
- package/.agents/skills/lint-and-validate/SKILL.md +44 -0
- package/.agents/skills/lint-and-validate/scripts/lint_runner.py +184 -0
- package/.agents/skills/lint-and-validate/scripts/type_coverage.py +173 -0
- package/.agents/skills/mcp-builder/SKILL.md +176 -0
- package/.agents/skills/mobile-design/SKILL.md +394 -0
- package/.agents/skills/mobile-design/decision-trees.md +516 -0
- package/.agents/skills/mobile-design/mobile-backend.md +491 -0
- package/.agents/skills/mobile-design/mobile-color-system.md +420 -0
- package/.agents/skills/mobile-design/mobile-debugging.md +122 -0
- package/.agents/skills/mobile-design/mobile-design-thinking.md +355 -0
- package/.agents/skills/mobile-design/mobile-navigation.md +458 -0
- package/.agents/skills/mobile-design/mobile-performance.md +767 -0
- package/.agents/skills/mobile-design/mobile-testing.md +356 -0
- package/.agents/skills/mobile-design/mobile-typography.md +432 -0
- package/.agents/skills/mobile-design/platform-android.md +666 -0
- package/.agents/skills/mobile-design/platform-ios.md +561 -0
- package/.agents/skills/mobile-design/scripts/mobile_audit.py +670 -0
- package/.agents/skills/mobile-design/touch-psychology.md +537 -0
- package/.agents/skills/nextjs-react-expert/1-async-eliminating-waterfalls.md +311 -0
- package/.agents/skills/nextjs-react-expert/2-bundle-bundle-size-optimization.md +241 -0
- package/.agents/skills/nextjs-react-expert/3-server-server-side-performance.md +489 -0
- package/.agents/skills/nextjs-react-expert/4-client-client-side-data-fetching.md +263 -0
- package/.agents/skills/nextjs-react-expert/5-rerender-re-render-optimization.md +581 -0
- package/.agents/skills/nextjs-react-expert/6-rendering-rendering-performance.md +431 -0
- package/.agents/skills/nextjs-react-expert/7-js-javascript-performance.md +683 -0
- package/.agents/skills/nextjs-react-expert/8-advanced-advanced-patterns.md +149 -0
- package/.agents/skills/nextjs-react-expert/SKILL.md +286 -0
- package/.agents/skills/nextjs-react-expert/scripts/convert_rules.py +222 -0
- package/.agents/skills/nextjs-react-expert/scripts/react_performance_checker.py +252 -0
- package/.agents/skills/nodejs-best-practices/SKILL.md +333 -0
- package/.agents/skills/parallel-agents/SKILL.md +193 -0
- package/.agents/skills/performance-profiling/SKILL.md +149 -0
- package/.agents/skills/performance-profiling/scripts/lighthouse_audit.py +120 -0
- package/.agents/skills/plan-writing/SKILL.md +152 -0
- package/.agents/skills/powershell-windows/SKILL.md +166 -0
- package/.agents/skills/python-patterns/SKILL.md +441 -0
- package/.agents/skills/red-team-tactics/SKILL.md +203 -0
- package/.agents/skills/refactoring-patterns/SKILL.md +43 -0
- package/.agents/skills/rust-pro/SKILL.md +190 -0
- package/.agents/skills/seo-fundamentals/SKILL.md +135 -0
- package/.agents/skills/seo-fundamentals/scripts/seo_checker.py +215 -0
- package/.agents/skills/server-management/SKILL.md +161 -0
- package/.agents/skills/systematic-debugging/SKILL.md +114 -0
- package/.agents/skills/tailwind-patterns/SKILL.md +269 -0
- package/.agents/skills/tdd-workflow/SKILL.md +149 -0
- package/.agents/skills/testing-patterns/SKILL.md +178 -0
- package/.agents/skills/testing-patterns/scripts/test_runner.py +219 -0
- package/.agents/skills/vulnerability-scanner/SKILL.md +276 -0
- package/.agents/skills/vulnerability-scanner/checklists.md +131 -0
- package/.agents/skills/vulnerability-scanner/scripts/__pycache__/security_scan.cpython-310.pyc +0 -0
- package/.agents/skills/vulnerability-scanner/scripts/security_scan.py +524 -0
- package/.agents/skills/web-design-guidelines/SKILL.md +57 -0
- package/.agents/skills/webapp-testing/SKILL.md +187 -0
- package/.agents/skills/webapp-testing/scripts/playwright_runner.py +173 -0
- package/.agents/workflows/brainstorm.md +113 -0
- package/.agents/workflows/create.md +59 -0
- package/.agents/workflows/debug.md +103 -0
- package/.agents/workflows/deploy.md +176 -0
- package/.agents/workflows/enhance.md +63 -0
- package/.agents/workflows/orchestrate.md +242 -0
- package/.agents/workflows/plan.md +89 -0
- package/.agents/workflows/preview.md +80 -0
- package/.agents/workflows/restore-localize-compat.md +525 -0
- package/.agents/workflows/status.md +86 -0
- package/.agents/workflows/test.md +144 -0
- package/.agents/workflows/ui-ux-pro-max.md +295 -0
- package/.spec/profiles/codex/AGENTS.spec.md +7 -0
- package/.spec/profiles/codex/ling.spec.rules.md +4 -0
- package/.spec/profiles/gemini/GEMINI.spec.md +5 -0
- package/.spec/references/README.md +36 -0
- package/.spec/references/cse-quickstart.md +96 -0
- package/.spec/references/gda-framework.md +394 -0
- package/.spec/references/harness-engineering-digest.md +93 -0
- package/.spec/skills/cybernetic-systems-engineering/SKILL.md +792 -0
- package/.spec/skills/cybernetic-systems-engineering/agents/openai.yaml +5 -0
- package/.spec/skills/cybernetic-systems-engineering/assets/quickstart.md +96 -0
- package/.spec/skills/cybernetic-systems-engineering/references/README.md +36 -0
- package/.spec/skills/cybernetic-systems-engineering/references/gda-framework.md +394 -0
- package/.spec/skills/cybernetic-systems-engineering/scripts/issues.csv +20 -0
- package/.spec/skills/harness-engineering/SKILL.md +100 -0
- package/.spec/skills/harness-engineering/agents/openai.yaml +4 -0
- package/.spec/skills/harness-engineering/references/harness-engineering-digest.md +93 -0
- package/.spec/templates/driver-prompt.md +7 -0
- package/.spec/templates/handoff.md +9 -0
- package/.spec/templates/issues.template.csv +2 -0
- package/.spec/templates/phase-acceptance.md +9 -0
- package/.spec/templates/review-report.md +9 -0
- package/AGENT_FLOW.md +609 -0
- package/CHANGELOG.md +43 -0
- package/LICENSE +21 -0
- package/README.md +359 -0
- package/bin/adapters/base.js +63 -0
- package/bin/adapters/codex.js +421 -0
- package/bin/adapters/gemini.js +157 -0
- package/bin/ag-kit.js +2266 -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/ling.js +3 -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 +244 -0
- package/bin/utils.js +89 -0
- package/docs/PLAN.md +54 -0
- package/docs/TECH.md +191 -0
- package/package.json +56 -0
- package/scripts/ci-verify.js +110 -0
- package/scripts/clean.js +123 -0
- package/scripts/health-check.js +143 -0
- package/scripts/health-check.sh +6 -0
- package/scripts/postinstall-check.js +112 -0
- package/scripts/run-tests.js +49 -0
- package/tests/atomic-writer.test.js +47 -0
- package/tests/clean-script.test.js +77 -0
- package/tests/cli-smoke.test.js +479 -0
- package/tests/codex-adapter.test.js +132 -0
- package/tests/doctor.test.js +94 -0
- package/tests/gemini-adapter.test.js +30 -0
- package/tests/generator.test.js +48 -0
- package/tests/git-helper.test.js +53 -0
- package/tests/global-sync.test.js +133 -0
- package/tests/health-check-script.test.js +34 -0
- package/tests/managed-block.test.js +41 -0
- package/tests/manifest.test.js +97 -0
- package/tests/package-tarball.test.js +33 -0
- package/tests/phase-c.test.js +107 -0
- package/tests/spec-profile.test.js +86 -0
- package/tests/standards-compliance.test.js +303 -0
- package/tests/transformer.test.js +74 -0
- package/tests/versioning.test.js +51 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
const { test, describe, beforeEach, afterEach } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const CodexAdapter = require('../bin/adapters/codex');
|
|
7
|
+
const GeminiAdapter = require('../bin/adapters/gemini');
|
|
8
|
+
|
|
9
|
+
describe('Doctor Command', () => {
|
|
10
|
+
let workDir;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'doctor-test-'));
|
|
14
|
+
// Setup mock environment
|
|
15
|
+
// Gemini
|
|
16
|
+
fs.mkdirSync(path.join(workDir, '.agent', 'agents'), { recursive: true });
|
|
17
|
+
fs.mkdirSync(path.join(workDir, '.agent', 'skills'), { recursive: true });
|
|
18
|
+
fs.mkdirSync(path.join(workDir, '.agent', 'rules'), { recursive: true });
|
|
19
|
+
fs.mkdirSync(path.join(workDir, '.agent', 'workflows'), { recursive: true });
|
|
20
|
+
fs.writeFileSync(path.join(workDir, '.agent', 'agents', 'orchestrator.md'), '# orchestrator');
|
|
21
|
+
fs.writeFileSync(path.join(workDir, '.agent', 'skills', 'doc.md'), '# skills');
|
|
22
|
+
fs.writeFileSync(path.join(workDir, '.agent', 'rules', 'GEMINI.md'), '# rules');
|
|
23
|
+
fs.writeFileSync(path.join(workDir, '.agent', 'workflows', 'create.md'), '# workflow');
|
|
24
|
+
// Codex
|
|
25
|
+
fs.mkdirSync(path.join(workDir, '.agents'));
|
|
26
|
+
fs.writeFileSync(path.join(workDir, '.agents', 'manifest.json'), JSON.stringify({ version: 2, target: 'codex', files: {} }));
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
afterEach(() => {
|
|
30
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test('GeminiAdapter checkIntegrity ok', () => {
|
|
34
|
+
const adapter = new GeminiAdapter(workDir, { quiet: true });
|
|
35
|
+
const res = adapter.checkIntegrity();
|
|
36
|
+
assert.strictEqual(res.status, 'ok');
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test('GeminiAdapter checkIntegrity missing', () => {
|
|
40
|
+
fs.rmSync(path.join(workDir, '.agent'), { recursive: true });
|
|
41
|
+
const adapter = new GeminiAdapter(workDir, { quiet: true });
|
|
42
|
+
const res = adapter.checkIntegrity();
|
|
43
|
+
assert.strictEqual(res.status, 'missing');
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test('GeminiAdapter checkIntegrity should flag missing required files', () => {
|
|
47
|
+
fs.rmSync(path.join(workDir, '.agent', 'rules', 'GEMINI.md'));
|
|
48
|
+
const adapter = new GeminiAdapter(workDir, { quiet: true });
|
|
49
|
+
const res = adapter.checkIntegrity();
|
|
50
|
+
assert.strictEqual(res.status, 'broken');
|
|
51
|
+
assert.ok(res.issues.some((issue) => issue.includes('.agent/rules/GEMINI.md')));
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test('CodexAdapter checkIntegrity ok', () => {
|
|
55
|
+
const adapter = new CodexAdapter(workDir, { quiet: true });
|
|
56
|
+
const res = adapter.checkIntegrity();
|
|
57
|
+
assert.strictEqual(res.status, 'ok');
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test('CodexAdapter checkIntegrity missing', () => {
|
|
61
|
+
fs.rmSync(path.join(workDir, '.agents'), { recursive: true });
|
|
62
|
+
const adapter = new CodexAdapter(workDir, { quiet: true });
|
|
63
|
+
const res = adapter.checkIntegrity();
|
|
64
|
+
assert.strictEqual(res.status, 'missing');
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
test('CodexAdapter checkIntegrity should flag legacy .codex directory', () => {
|
|
68
|
+
fs.mkdirSync(path.join(workDir, '.codex'), { recursive: true });
|
|
69
|
+
const adapter = new CodexAdapter(workDir, { quiet: true });
|
|
70
|
+
const res = adapter.checkIntegrity();
|
|
71
|
+
assert.strictEqual(res.status, 'broken');
|
|
72
|
+
assert.ok(res.issues.some((issue) => issue.includes('.codex')));
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
test('CodexAdapter checkIntegrity should flag invalid manifest JSON', () => {
|
|
76
|
+
fs.writeFileSync(path.join(workDir, '.agents', 'manifest.json'), '{invalid-json');
|
|
77
|
+
const adapter = new CodexAdapter(workDir, { quiet: true });
|
|
78
|
+
const res = adapter.checkIntegrity();
|
|
79
|
+
assert.strictEqual(res.status, 'broken');
|
|
80
|
+
assert.ok(res.issues.some((issue) => issue.includes('invalid JSON')));
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test('CodexAdapter fixIntegrity should migrate legacy .codex directory', () => {
|
|
84
|
+
fs.rmSync(path.join(workDir, '.agents'), { recursive: true, force: true });
|
|
85
|
+
fs.mkdirSync(path.join(workDir, '.codex'), { recursive: true });
|
|
86
|
+
fs.writeFileSync(path.join(workDir, '.codex', 'manifest.json'), JSON.stringify({ version: 2, target: 'codex', files: {} }));
|
|
87
|
+
fs.writeFileSync(path.join(workDir, '.codex', 'legacy.txt'), 'legacy');
|
|
88
|
+
const adapter = new CodexAdapter(workDir, { quiet: true });
|
|
89
|
+
const res = adapter.fixIntegrity();
|
|
90
|
+
assert.ok(res.fixed);
|
|
91
|
+
assert.ok(fs.existsSync(path.join(workDir, '.agents')));
|
|
92
|
+
assert.ok(!fs.existsSync(path.join(workDir, '.codex')));
|
|
93
|
+
});
|
|
94
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
const { test, describe, beforeEach, afterEach } = require("node:test");
|
|
2
|
+
const assert = require("node:assert");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const GeminiAdapter = require("../bin/adapters/gemini");
|
|
7
|
+
|
|
8
|
+
describe("GeminiAdapter", () => {
|
|
9
|
+
let workDir;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
workDir = fs.mkdtempSync(path.join(os.tmpdir(), "gemini-adapter-test-"));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("update should work when source and target are the same directory", () => {
|
|
20
|
+
const agentDir = path.join(workDir, ".agent");
|
|
21
|
+
fs.mkdirSync(agentDir, { recursive: true });
|
|
22
|
+
fs.writeFileSync(path.join(agentDir, "SKILL.md"), "same-source-target", "utf8");
|
|
23
|
+
|
|
24
|
+
const adapter = new GeminiAdapter(workDir, { quiet: true });
|
|
25
|
+
adapter.update(agentDir);
|
|
26
|
+
|
|
27
|
+
assert.ok(fs.existsSync(path.join(agentDir, "SKILL.md")));
|
|
28
|
+
assert.strictEqual(fs.readFileSync(path.join(agentDir, "SKILL.md"), "utf8"), "same-source-target");
|
|
29
|
+
});
|
|
30
|
+
});
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
const { test, describe } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const RuleGenerator = require('../bin/core/generator');
|
|
4
|
+
|
|
5
|
+
describe('RuleGenerator', () => {
|
|
6
|
+
test('generate should keep codex managed guidance on .agents layout', () => {
|
|
7
|
+
const transformResult = {
|
|
8
|
+
metadata: [
|
|
9
|
+
{
|
|
10
|
+
id: 'test-skill',
|
|
11
|
+
originalName: 'test-skill',
|
|
12
|
+
path: 'skills/test-skill/SKILL.md',
|
|
13
|
+
type: 'skill',
|
|
14
|
+
},
|
|
15
|
+
],
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const { agentsMd, lingRules, codexJson } = RuleGenerator.generate(transformResult, '2.0.1');
|
|
19
|
+
|
|
20
|
+
assert.ok(agentsMd.includes('test-skill'));
|
|
21
|
+
assert.ok(agentsMd.includes('ling doctor --target codex --fix'));
|
|
22
|
+
assert.ok(agentsMd.includes('.agents/skills'));
|
|
23
|
+
|
|
24
|
+
assert.ok(lingRules.includes('Managed skills are stored under `.agents/skills`.'));
|
|
25
|
+
assert.ok(!lingRules.includes('.codex'));
|
|
26
|
+
|
|
27
|
+
assert.strictEqual(codexJson.version, '2.0.1');
|
|
28
|
+
assert.strictEqual(codexJson.skills.length, 1);
|
|
29
|
+
assert.strictEqual(codexJson.skills[0].id, 'test-skill');
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test('generate should work with empty metadata', () => {
|
|
33
|
+
const transformResult = { metadata: [] };
|
|
34
|
+
const { agentsMd, lingRules, codexJson } = RuleGenerator.generate(transformResult, '2.0.1');
|
|
35
|
+
|
|
36
|
+
assert.ok(agentsMd.includes('Codex Managed'));
|
|
37
|
+
assert.ok(lingRules.includes('Ling Risk Controls'));
|
|
38
|
+
assert.deepStrictEqual(codexJson.skills, []);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('generate should require explicit version', () => {
|
|
42
|
+
const transformResult = { metadata: [] };
|
|
43
|
+
assert.throws(
|
|
44
|
+
() => RuleGenerator.generate(transformResult),
|
|
45
|
+
/需要显式传入版本号/,
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
});
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
const { test, describe, beforeEach, afterEach } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const GitHelper = require('../bin/utils/git-helper');
|
|
7
|
+
|
|
8
|
+
describe('GitHelper', () => {
|
|
9
|
+
let workDir;
|
|
10
|
+
let gitIgnorePath;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
workDir = fs.mkdtempSync(path.join(os.tmpdir(), 'git-helper-test-'));
|
|
14
|
+
gitIgnorePath = path.join(workDir, '.gitignore');
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test('removeIgnoreRules should remove target patterns', () => {
|
|
22
|
+
const content = `
|
|
23
|
+
# Comment
|
|
24
|
+
node_modules
|
|
25
|
+
.agent
|
|
26
|
+
src/
|
|
27
|
+
.codex/
|
|
28
|
+
`.trim();
|
|
29
|
+
fs.writeFileSync(gitIgnorePath, content);
|
|
30
|
+
|
|
31
|
+
const result = GitHelper.removeIgnoreRules(workDir, ['.agent', '.codex'], { dryRun: false });
|
|
32
|
+
|
|
33
|
+
assert.strictEqual(result.removedCount, 2);
|
|
34
|
+
|
|
35
|
+
const newContent = fs.readFileSync(gitIgnorePath, 'utf8');
|
|
36
|
+
assert.ok(!newContent.includes('.agent'));
|
|
37
|
+
assert.ok(!newContent.includes('.codex'));
|
|
38
|
+
assert.ok(newContent.includes('node_modules'));
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test('removeIgnoreRules should respect dryRun', () => {
|
|
42
|
+
const content = `.agent\n.codex`;
|
|
43
|
+
fs.writeFileSync(gitIgnorePath, content);
|
|
44
|
+
|
|
45
|
+
const result = GitHelper.removeIgnoreRules(workDir, ['.agent'], { dryRun: true });
|
|
46
|
+
|
|
47
|
+
assert.strictEqual(result.removedCount, 1);
|
|
48
|
+
assert.strictEqual(result.dryRun, true);
|
|
49
|
+
|
|
50
|
+
const newContent = fs.readFileSync(gitIgnorePath, 'utf8');
|
|
51
|
+
assert.strictEqual(newContent, content); // Should not change
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
const { test, describe, beforeEach, afterEach } = require("node:test");
|
|
2
|
+
const assert = require("node:assert");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const { spawnSync } = require("node:child_process");
|
|
7
|
+
|
|
8
|
+
const REPO_ROOT = path.resolve(__dirname, "..");
|
|
9
|
+
const CLI_PATH = path.join(REPO_ROOT, "bin", "ling.js");
|
|
10
|
+
|
|
11
|
+
function runCli(args, options = {}) {
|
|
12
|
+
const env = {
|
|
13
|
+
...process.env,
|
|
14
|
+
LING_SKIP_UPSTREAM_CHECK: "1",
|
|
15
|
+
...options.env,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
return spawnSync(process.execPath, [CLI_PATH, ...args], {
|
|
19
|
+
cwd: options.cwd || REPO_ROOT,
|
|
20
|
+
env,
|
|
21
|
+
encoding: "utf8",
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
describe("Global Sync", () => {
|
|
26
|
+
let tempDir;
|
|
27
|
+
|
|
28
|
+
beforeEach(() => {
|
|
29
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ling-global-sync-test-"));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
afterEach(() => {
|
|
33
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("global status should report missing when no global skills installed", () => {
|
|
37
|
+
const result = runCli(["global", "status", "--quiet"], {
|
|
38
|
+
env: { LING_GLOBAL_ROOT: tempDir },
|
|
39
|
+
});
|
|
40
|
+
assert.strictEqual(result.status, 2);
|
|
41
|
+
assert.strictEqual((result.stdout || "").trim(), "missing");
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("global status should report broken when target root exists but skills are incomplete", () => {
|
|
45
|
+
fs.mkdirSync(path.join(tempDir, ".codex"), { recursive: true });
|
|
46
|
+
|
|
47
|
+
const result = runCli(["global", "status", "--quiet"], {
|
|
48
|
+
env: { LING_GLOBAL_ROOT: tempDir },
|
|
49
|
+
});
|
|
50
|
+
assert.strictEqual(result.status, 1);
|
|
51
|
+
assert.strictEqual((result.stdout || "").trim(), "broken");
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
test("global sync should install codex skills into $HOME/.codex/skills", () => {
|
|
55
|
+
const result = runCli(["global", "sync", "--target", "codex", "--quiet"], {
|
|
56
|
+
env: { LING_GLOBAL_ROOT: tempDir },
|
|
57
|
+
});
|
|
58
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
59
|
+
|
|
60
|
+
const skillsRoot = path.join(tempDir, ".codex", "skills");
|
|
61
|
+
assert.ok(fs.existsSync(skillsRoot), "missing global codex skills root");
|
|
62
|
+
assert.ok(fs.existsSync(path.join(skillsRoot, "clean-code", "SKILL.md")), "missing expected skill: clean-code");
|
|
63
|
+
assert.ok(fs.existsSync(path.join(skillsRoot, "workflow-plan", "SKILL.md")), "missing expected workflow skill: workflow-plan");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("global sync should default to syncing codex and gemini when no target is provided", () => {
|
|
67
|
+
const result = runCli(["global", "sync", "--quiet"], {
|
|
68
|
+
env: { LING_GLOBAL_ROOT: tempDir },
|
|
69
|
+
});
|
|
70
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
71
|
+
|
|
72
|
+
const codexRoot = path.join(tempDir, ".codex", "skills");
|
|
73
|
+
assert.ok(fs.existsSync(path.join(codexRoot, "clean-code", "SKILL.md")), "missing expected codex skill: clean-code");
|
|
74
|
+
assert.ok(fs.existsSync(path.join(codexRoot, "workflow-plan", "SKILL.md")), "missing expected codex workflow skill: workflow-plan");
|
|
75
|
+
|
|
76
|
+
const geminiCliRoot = path.join(tempDir, ".gemini", "skills");
|
|
77
|
+
assert.ok(fs.existsSync(path.join(geminiCliRoot, "clean-code", "SKILL.md")), "missing expected gemini-cli skill: clean-code");
|
|
78
|
+
|
|
79
|
+
const antigravityRoot = path.join(tempDir, ".gemini", "antigravity", "skills");
|
|
80
|
+
assert.ok(fs.existsSync(path.join(antigravityRoot, "clean-code", "SKILL.md")), "missing expected antigravity skill: clean-code");
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
test("global status should report installed after global sync", () => {
|
|
84
|
+
const syncResult = runCli(["global", "sync", "--quiet"], {
|
|
85
|
+
env: { LING_GLOBAL_ROOT: tempDir },
|
|
86
|
+
});
|
|
87
|
+
assert.strictEqual(syncResult.status, 0, syncResult.stderr || syncResult.stdout);
|
|
88
|
+
|
|
89
|
+
const statusResult = runCli(["global", "status", "--quiet"], {
|
|
90
|
+
env: { LING_GLOBAL_ROOT: tempDir },
|
|
91
|
+
});
|
|
92
|
+
assert.strictEqual(statusResult.status, 0, statusResult.stderr || statusResult.stdout);
|
|
93
|
+
assert.strictEqual((statusResult.stdout || "").trim(), "installed");
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
test("global sync should install gemini skills into both ~/.gemini/skills and ~/.gemini/antigravity/skills", () => {
|
|
97
|
+
const result = runCli(["global", "sync", "--target", "gemini", "--quiet"], {
|
|
98
|
+
env: { LING_GLOBAL_ROOT: tempDir },
|
|
99
|
+
});
|
|
100
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
101
|
+
|
|
102
|
+
const geminiCliRoot = path.join(tempDir, ".gemini", "skills");
|
|
103
|
+
assert.ok(fs.existsSync(geminiCliRoot), "missing global gemini-cli skills root");
|
|
104
|
+
assert.ok(fs.existsSync(path.join(geminiCliRoot, "clean-code", "SKILL.md")), "missing expected gemini-cli skill: clean-code");
|
|
105
|
+
|
|
106
|
+
const antigravityRoot = path.join(tempDir, ".gemini", "antigravity", "skills");
|
|
107
|
+
assert.ok(fs.existsSync(antigravityRoot), "missing global antigravity skills root");
|
|
108
|
+
assert.ok(fs.existsSync(path.join(antigravityRoot, "clean-code", "SKILL.md")), "missing expected antigravity skill: clean-code");
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
test("global sync should create backup when overwriting an existing skill", () => {
|
|
112
|
+
const skillsRoot = path.join(tempDir, ".codex", "skills", "clean-code");
|
|
113
|
+
fs.mkdirSync(skillsRoot, { recursive: true });
|
|
114
|
+
fs.writeFileSync(path.join(skillsRoot, "SKILL.md"), "modified", "utf8");
|
|
115
|
+
|
|
116
|
+
const result = runCli(["global", "sync", "--target", "codex", "--quiet"], {
|
|
117
|
+
env: { LING_GLOBAL_ROOT: tempDir },
|
|
118
|
+
});
|
|
119
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
120
|
+
|
|
121
|
+
const backupRoot = path.join(tempDir, ".ling", "backups", "global");
|
|
122
|
+
assert.ok(fs.existsSync(backupRoot), "missing backup root");
|
|
123
|
+
|
|
124
|
+
const timestamps = fs
|
|
125
|
+
.readdirSync(backupRoot, { withFileTypes: true })
|
|
126
|
+
.filter((entry) => entry.isDirectory())
|
|
127
|
+
.map((entry) => entry.name);
|
|
128
|
+
assert.ok(timestamps.length > 0, "backup timestamp directory missing");
|
|
129
|
+
|
|
130
|
+
const backupSkillPath = path.join(backupRoot, timestamps[0], "codex", "clean-code", "SKILL.md");
|
|
131
|
+
assert.ok(fs.existsSync(backupSkillPath), "backup for clean-code skill missing");
|
|
132
|
+
});
|
|
133
|
+
});
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
const { test, describe } = require("node:test");
|
|
2
|
+
const assert = require("node:assert");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { spawnSync } = require("node:child_process");
|
|
6
|
+
const HAS_BASH = (() => {
|
|
7
|
+
const probe = spawnSync("bash", ["--version"], { encoding: "utf8" });
|
|
8
|
+
return !probe.error && probe.status === 0;
|
|
9
|
+
})();
|
|
10
|
+
|
|
11
|
+
describe("Health Check Script", () => {
|
|
12
|
+
test("health-check script should exist and be executable", () => {
|
|
13
|
+
const scriptPath = path.resolve(__dirname, "..", "scripts", "health-check.sh");
|
|
14
|
+
assert.ok(fs.existsSync(scriptPath), "missing scripts/health-check.sh");
|
|
15
|
+
fs.accessSync(scriptPath, fs.constants.X_OK);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
test("health-check node entry should exist", () => {
|
|
19
|
+
const scriptPath = path.resolve(__dirname, "..", "scripts", "health-check.js");
|
|
20
|
+
assert.ok(fs.existsSync(scriptPath), "missing scripts/health-check.js");
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("health-check script should pass bash syntax check when bash is available", { skip: !HAS_BASH }, () => {
|
|
24
|
+
const scriptPath = path.resolve(__dirname, "..", "scripts", "health-check.sh");
|
|
25
|
+
const result = spawnSync("bash", ["-n", scriptPath], { encoding: "utf8" });
|
|
26
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
test("package scripts should expose health-check command", () => {
|
|
30
|
+
const packageJsonPath = path.resolve(__dirname, "..", "package.json");
|
|
31
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
32
|
+
assert.strictEqual(packageJson.scripts["health-check"], "node scripts/health-check.js");
|
|
33
|
+
});
|
|
34
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
const { test, describe, beforeEach, afterEach } = require("node:test");
|
|
2
|
+
const assert = require("node:assert");
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const os = require("os");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
const { upsertManagedBlock } = require("../bin/utils/managed-block");
|
|
7
|
+
|
|
8
|
+
describe("ManagedBlock", () => {
|
|
9
|
+
let tempDir;
|
|
10
|
+
let targetFile;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "managed-block-test-"));
|
|
14
|
+
targetFile = path.join(tempDir, "AGENTS.md");
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
afterEach(() => {
|
|
18
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
test("should append managed block without removing user content", () => {
|
|
22
|
+
fs.writeFileSync(targetFile, "# User Content\n\ncustom text\n", "utf8");
|
|
23
|
+
const result = upsertManagedBlock(targetFile, "codex-core-rules", "managed line");
|
|
24
|
+
|
|
25
|
+
assert.strictEqual(result.action, "appended");
|
|
26
|
+
const content = fs.readFileSync(targetFile, "utf8");
|
|
27
|
+
assert.ok(content.includes("User Content"));
|
|
28
|
+
assert.ok(content.includes("BEGIN AG-KIT MANAGED BLOCK: codex-core-rules"));
|
|
29
|
+
assert.ok(content.includes("managed line"));
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
test("should update existing managed block in place", () => {
|
|
33
|
+
upsertManagedBlock(targetFile, "codex-core-rules", "v1");
|
|
34
|
+
const result = upsertManagedBlock(targetFile, "codex-core-rules", "v2");
|
|
35
|
+
|
|
36
|
+
assert.strictEqual(result.action, "updated");
|
|
37
|
+
const content = fs.readFileSync(targetFile, "utf8");
|
|
38
|
+
assert.ok(content.includes("v2"));
|
|
39
|
+
assert.ok(!content.includes("v1"));
|
|
40
|
+
});
|
|
41
|
+
});
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
const { test, describe, beforeEach, afterEach } = require('node:test');
|
|
2
|
+
const assert = require('node:assert');
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const os = require('os');
|
|
6
|
+
const ManifestManager = require('../bin/utils/manifest');
|
|
7
|
+
|
|
8
|
+
describe('ManifestManager', () => {
|
|
9
|
+
let tempDir;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'manifest-test-'));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('computeHash should return correct sha256', () => {
|
|
20
|
+
const content = 'hello world';
|
|
21
|
+
// echo -n "hello world" | shasum -a 256
|
|
22
|
+
const expected = 'b94d27b9934d3e08a52e52d7da7dabfac484efe37a5380ee9088f7ace2efcde9';
|
|
23
|
+
assert.strictEqual(ManifestManager.computeHash(content), expected);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
test('generateFromDir should exclude directories and map files', () => {
|
|
27
|
+
const file1 = path.join(tempDir, 'file1.txt');
|
|
28
|
+
const dir1 = path.join(tempDir, 'dir1');
|
|
29
|
+
const file2 = path.join(dir1, 'file2.txt');
|
|
30
|
+
|
|
31
|
+
fs.writeFileSync(file1, 'content1');
|
|
32
|
+
fs.mkdirSync(dir1);
|
|
33
|
+
fs.writeFileSync(file2, 'content2');
|
|
34
|
+
|
|
35
|
+
const manifest = ManifestManager.generateFromDir(tempDir);
|
|
36
|
+
|
|
37
|
+
// Rel paths depend on OS separator, but manifest logic normalizes to /
|
|
38
|
+
assert.strictEqual(manifest['file1.txt'], ManifestManager.computeHash('content1'));
|
|
39
|
+
assert.strictEqual(manifest['dir1/file2.txt'], ManifestManager.computeHash('content2'));
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test('checkDrift should detect modifications and missing files', () => {
|
|
43
|
+
const manifestPath = path.join(tempDir, 'manifest.json');
|
|
44
|
+
const file1 = path.join(tempDir, 'file1.txt');
|
|
45
|
+
|
|
46
|
+
fs.writeFileSync(file1, 'original');
|
|
47
|
+
|
|
48
|
+
const mgr = new ManifestManager(manifestPath);
|
|
49
|
+
mgr.manifest.files = ManifestManager.generateFromDir(tempDir);
|
|
50
|
+
mgr.save();
|
|
51
|
+
|
|
52
|
+
// 1. No Drift
|
|
53
|
+
let drift = mgr.checkDrift(tempDir);
|
|
54
|
+
assert.strictEqual(drift.modified.length, 0);
|
|
55
|
+
assert.strictEqual(drift.missing.length, 0);
|
|
56
|
+
|
|
57
|
+
// 2. Modified
|
|
58
|
+
fs.writeFileSync(file1, 'modified');
|
|
59
|
+
drift = mgr.checkDrift(tempDir);
|
|
60
|
+
assert.strictEqual(drift.modified.length, 1);
|
|
61
|
+
assert.strictEqual(drift.modified[0], 'file1.txt');
|
|
62
|
+
|
|
63
|
+
// 3. Missing
|
|
64
|
+
fs.rmSync(file1);
|
|
65
|
+
drift = mgr.checkDrift(tempDir);
|
|
66
|
+
assert.strictEqual(drift.missing.length, 1);
|
|
67
|
+
assert.strictEqual(drift.missing[0], 'file1.txt');
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test('collectSmartOverwriteConflicts should only return true user conflicts', () => {
|
|
71
|
+
const manifestPath = path.join(tempDir, 'manifest.json');
|
|
72
|
+
const file1 = path.join(tempDir, 'file1.txt');
|
|
73
|
+
|
|
74
|
+
fs.writeFileSync(file1, 'original');
|
|
75
|
+
|
|
76
|
+
const mgr = new ManifestManager(manifestPath);
|
|
77
|
+
mgr.manifest.files = ManifestManager.generateFileEntriesFromDir(tempDir, { baseDir: tempDir });
|
|
78
|
+
mgr.save();
|
|
79
|
+
|
|
80
|
+
// user modified file
|
|
81
|
+
fs.writeFileSync(file1, 'user-modified');
|
|
82
|
+
|
|
83
|
+
const incoming = {
|
|
84
|
+
'file1.txt': {
|
|
85
|
+
hash: ManifestManager.computeHash('next-version'),
|
|
86
|
+
source: 'bundled/file1.txt',
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
const conflicts = mgr.collectSmartOverwriteConflicts(tempDir, incoming);
|
|
90
|
+
assert.deepStrictEqual(conflicts, ['file1.txt']);
|
|
91
|
+
|
|
92
|
+
// if local already equals incoming hash, no conflict backup needed
|
|
93
|
+
fs.writeFileSync(file1, 'next-version');
|
|
94
|
+
const noConflicts = mgr.collectSmartOverwriteConflicts(tempDir, incoming);
|
|
95
|
+
assert.deepStrictEqual(noConflicts, []);
|
|
96
|
+
});
|
|
97
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const { test, describe } = require("node:test");
|
|
2
|
+
const assert = require("node:assert");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const { spawnSync } = require("node:child_process");
|
|
5
|
+
|
|
6
|
+
const REPO_ROOT = path.resolve(__dirname, "..");
|
|
7
|
+
const NPM_COMMAND = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
8
|
+
|
|
9
|
+
describe("Package Tarball", () => {
|
|
10
|
+
test("npm pack --dry-run should include maintenance scripts and tests", () => {
|
|
11
|
+
const result = spawnSync(NPM_COMMAND, ["pack", "--json", "--dry-run"], {
|
|
12
|
+
cwd: REPO_ROOT,
|
|
13
|
+
encoding: "utf8",
|
|
14
|
+
shell: process.platform === "win32",
|
|
15
|
+
});
|
|
16
|
+
assert.ifError(result.error);
|
|
17
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
18
|
+
|
|
19
|
+
const payload = JSON.parse(result.stdout);
|
|
20
|
+
assert.ok(Array.isArray(payload) && payload.length > 0, "missing npm pack json payload");
|
|
21
|
+
|
|
22
|
+
const files = new Set(payload[0].files.map((item) => item.path));
|
|
23
|
+
assert.ok(files.has("scripts/clean.js"), "tarball missing scripts/clean.js");
|
|
24
|
+
assert.ok(files.has("scripts/ci-verify.js"), "tarball missing scripts/ci-verify.js");
|
|
25
|
+
assert.ok(files.has("scripts/health-check.js"), "tarball missing scripts/health-check.js");
|
|
26
|
+
assert.ok(files.has("scripts/health-check.sh"), "tarball missing scripts/health-check.sh");
|
|
27
|
+
assert.ok(files.has("scripts/run-tests.js"), "tarball missing scripts/run-tests.js");
|
|
28
|
+
assert.ok(files.has(".spec/skills/harness-engineering/SKILL.md"), "tarball missing .spec harness-engineering skill");
|
|
29
|
+
assert.ok(files.has(".spec/templates/issues.template.csv"), "tarball missing .spec issues template");
|
|
30
|
+
assert.ok(files.has("tests/cli-smoke.test.js"), "tarball missing tests/cli-smoke.test.js");
|
|
31
|
+
assert.ok(files.has("tests/global-sync.test.js"), "tarball missing tests/global-sync.test.js");
|
|
32
|
+
});
|
|
33
|
+
});
|