@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,77 @@
|
|
|
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 { parseArgs, runClean } = require("../scripts/clean");
|
|
7
|
+
|
|
8
|
+
describe("Clean Script", () => {
|
|
9
|
+
let tempRoot;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
tempRoot = fs.mkdtempSync(path.join(os.tmpdir(), "ag-kit-clean-test-"));
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
afterEach(() => {
|
|
16
|
+
fs.rmSync(tempRoot, { recursive: true, force: true });
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("parseArgs should parse dry-run and quiet options", () => {
|
|
20
|
+
const options = parseArgs(["--dry-run", "--quiet"]);
|
|
21
|
+
assert.strictEqual(options.dryRun, true);
|
|
22
|
+
assert.strictEqual(options.quiet, true);
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
test("runClean should remove existing generated directories", () => {
|
|
26
|
+
const targetA = path.join(tempRoot, "web", ".next");
|
|
27
|
+
const targetB = path.join(tempRoot, "web", "node_modules");
|
|
28
|
+
fs.mkdirSync(targetA, { recursive: true });
|
|
29
|
+
fs.mkdirSync(targetB, { recursive: true });
|
|
30
|
+
fs.writeFileSync(path.join(targetA, "cache.txt"), "x");
|
|
31
|
+
fs.writeFileSync(path.join(targetB, "dep.txt"), "x");
|
|
32
|
+
|
|
33
|
+
const result = runClean({
|
|
34
|
+
rootDir: tempRoot,
|
|
35
|
+
quiet: true,
|
|
36
|
+
targets: ["web/.next", "web/node_modules"],
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
assert.strictEqual(result.removedCount, 2);
|
|
40
|
+
assert.ok(!fs.existsSync(targetA));
|
|
41
|
+
assert.ok(!fs.existsSync(targetB));
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("runClean dry-run should not remove directories", () => {
|
|
45
|
+
const target = path.join(tempRoot, "web", ".next");
|
|
46
|
+
fs.mkdirSync(target, { recursive: true });
|
|
47
|
+
|
|
48
|
+
const result = runClean({
|
|
49
|
+
rootDir: tempRoot,
|
|
50
|
+
dryRun: true,
|
|
51
|
+
quiet: true,
|
|
52
|
+
targets: ["web/.next"],
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
assert.strictEqual(result.wouldRemoveCount, 1);
|
|
56
|
+
assert.ok(fs.existsSync(target));
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
test("runClean should skip missing paths", () => {
|
|
60
|
+
const result = runClean({
|
|
61
|
+
rootDir: tempRoot,
|
|
62
|
+
quiet: true,
|
|
63
|
+
targets: ["web/.next"],
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
assert.strictEqual(result.skippedCount, 1);
|
|
67
|
+
assert.strictEqual(result.results[0].reason, "not_found");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("package scripts should keep scoped test and clean commands", () => {
|
|
71
|
+
const packageJsonPath = path.resolve(__dirname, "..", "package.json");
|
|
72
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
|
|
73
|
+
|
|
74
|
+
assert.strictEqual(packageJson.scripts.test, "node scripts/run-tests.js");
|
|
75
|
+
assert.strictEqual(packageJson.scripts.clean, "node scripts/clean.js");
|
|
76
|
+
});
|
|
77
|
+
});
|
|
@@ -0,0 +1,479 @@
|
|
|
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("CLI Smoke", () => {
|
|
26
|
+
let tempDir;
|
|
27
|
+
let workspaceDir;
|
|
28
|
+
let indexPath;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "ling-cli-test-"));
|
|
32
|
+
workspaceDir = path.join(tempDir, "workspace");
|
|
33
|
+
indexPath = path.join(tempDir, "workspaces.json");
|
|
34
|
+
fs.mkdirSync(workspaceDir, { recursive: true });
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
afterEach(() => {
|
|
38
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
test("doctor should work after codex init", () => {
|
|
42
|
+
const initResult = runCli(
|
|
43
|
+
["init", "--target", "codex", "--path", workspaceDir, "--quiet"],
|
|
44
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
45
|
+
);
|
|
46
|
+
assert.strictEqual(initResult.status, 0, initResult.stderr || initResult.stdout);
|
|
47
|
+
|
|
48
|
+
const doctorResult = runCli(
|
|
49
|
+
["doctor", "--target", "codex", "--path", workspaceDir, "--quiet"],
|
|
50
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
51
|
+
);
|
|
52
|
+
assert.strictEqual(doctorResult.status, 0, doctorResult.stderr || doctorResult.stdout);
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
test("doctor --quiet should not print diagnostic details", () => {
|
|
56
|
+
const initResult = runCli(
|
|
57
|
+
["init", "--target", "codex", "--path", workspaceDir, "--quiet"],
|
|
58
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
59
|
+
);
|
|
60
|
+
assert.strictEqual(initResult.status, 0, initResult.stderr || initResult.stdout);
|
|
61
|
+
|
|
62
|
+
const doctorResult = runCli(
|
|
63
|
+
["doctor", "--target", "codex", "--path", workspaceDir, "--quiet"],
|
|
64
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
65
|
+
);
|
|
66
|
+
assert.strictEqual(doctorResult.status, 0, doctorResult.stderr || doctorResult.stdout);
|
|
67
|
+
assert.strictEqual((doctorResult.stdout || "").trim(), "");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
test("doctor --fix should remove stale .codex directory", () => {
|
|
71
|
+
const initResult = runCli(
|
|
72
|
+
["init", "--target", "codex", "--path", workspaceDir, "--quiet"],
|
|
73
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
74
|
+
);
|
|
75
|
+
assert.strictEqual(initResult.status, 0, initResult.stderr || initResult.stdout);
|
|
76
|
+
|
|
77
|
+
fs.mkdirSync(path.join(workspaceDir, ".codex"), { recursive: true });
|
|
78
|
+
fs.writeFileSync(path.join(workspaceDir, ".codex", "legacy.txt"), "legacy");
|
|
79
|
+
assert.ok(fs.existsSync(path.join(workspaceDir, ".codex")));
|
|
80
|
+
|
|
81
|
+
const doctorFixResult = runCli(
|
|
82
|
+
["doctor", "--target", "codex", "--path", workspaceDir, "--fix", "--quiet"],
|
|
83
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
84
|
+
);
|
|
85
|
+
assert.strictEqual(doctorFixResult.status, 0, doctorFixResult.stderr || doctorFixResult.stdout);
|
|
86
|
+
assert.ok(!fs.existsSync(path.join(workspaceDir, ".codex")));
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
test("init should not index temporary workspace by default", () => {
|
|
90
|
+
const initResult = runCli(
|
|
91
|
+
["init", "--target", "codex", "--path", workspaceDir, "--quiet"],
|
|
92
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
93
|
+
);
|
|
94
|
+
assert.strictEqual(initResult.status, 0, initResult.stderr || initResult.stdout);
|
|
95
|
+
|
|
96
|
+
if (!fs.existsSync(indexPath)) {
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const indexData = JSON.parse(fs.readFileSync(indexPath, "utf8"));
|
|
101
|
+
const hasWorkspace = (indexData.workspaces || []).some((item) => item.path === workspaceDir);
|
|
102
|
+
assert.ok(!hasWorkspace, "temp workspace should not be persisted into global index");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("codex init should create .agents managed directory", () => {
|
|
106
|
+
const initResult = runCli(
|
|
107
|
+
["init", "--target", "codex", "--path", workspaceDir, "--quiet"],
|
|
108
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
109
|
+
);
|
|
110
|
+
assert.strictEqual(initResult.status, 0, initResult.stderr || initResult.stdout);
|
|
111
|
+
assert.ok(fs.existsSync(path.join(workspaceDir, ".agents")));
|
|
112
|
+
assert.ok(!fs.existsSync(path.join(workspaceDir, ".codex")));
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
test("codex update should remove pre-existing legacy .codex directory", () => {
|
|
116
|
+
const initResult = runCli(
|
|
117
|
+
["init", "--target", "codex", "--path", workspaceDir, "--quiet"],
|
|
118
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
119
|
+
);
|
|
120
|
+
assert.strictEqual(initResult.status, 0, initResult.stderr || initResult.stdout);
|
|
121
|
+
assert.ok(fs.existsSync(path.join(workspaceDir, ".agents")));
|
|
122
|
+
fs.mkdirSync(path.join(workspaceDir, ".codex"), { recursive: true });
|
|
123
|
+
fs.writeFileSync(path.join(workspaceDir, ".codex", "legacy.txt"), "legacy");
|
|
124
|
+
assert.ok(fs.existsSync(path.join(workspaceDir, ".codex")));
|
|
125
|
+
|
|
126
|
+
const updateResult = runCli(
|
|
127
|
+
["update", "--target", "codex", "--path", workspaceDir, "--quiet"],
|
|
128
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
129
|
+
);
|
|
130
|
+
assert.strictEqual(updateResult.status, 0, updateResult.stderr || updateResult.stdout);
|
|
131
|
+
assert.ok(!fs.existsSync(path.join(workspaceDir, ".codex")));
|
|
132
|
+
assert.ok(fs.existsSync(path.join(workspaceDir, ".agents")));
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
test("update-all should auto-clean temp workspace records from index", () => {
|
|
136
|
+
const now = new Date().toISOString();
|
|
137
|
+
const tempWorkspacePath = path.join(os.tmpdir(), "ling-indexed-temp", "workspace");
|
|
138
|
+
const seedIndex = {
|
|
139
|
+
version: 2,
|
|
140
|
+
updatedAt: now,
|
|
141
|
+
workspaces: [
|
|
142
|
+
{
|
|
143
|
+
path: tempWorkspacePath,
|
|
144
|
+
targets: {
|
|
145
|
+
gemini: {
|
|
146
|
+
version: "2.0.1",
|
|
147
|
+
installedAt: now,
|
|
148
|
+
updatedAt: now,
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
],
|
|
153
|
+
excludedPaths: [],
|
|
154
|
+
};
|
|
155
|
+
fs.writeFileSync(indexPath, `${JSON.stringify(seedIndex, null, 2)}\n`, "utf8");
|
|
156
|
+
|
|
157
|
+
const result = runCli(["update-all", "--quiet"], {
|
|
158
|
+
env: { LING_INDEX_PATH: indexPath },
|
|
159
|
+
});
|
|
160
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
161
|
+
|
|
162
|
+
const indexData = JSON.parse(fs.readFileSync(indexPath, "utf8"));
|
|
163
|
+
const hasTempWorkspace = (indexData.workspaces || []).some((item) => item.path === tempWorkspacePath);
|
|
164
|
+
assert.ok(!hasTempWorkspace, "temp workspace record should be removed during update-all");
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
test("update-all should normalize legacy full target records in v2 index", () => {
|
|
168
|
+
const now = new Date().toISOString();
|
|
169
|
+
const persistentRoot = fs.mkdtempSync(path.join(REPO_ROOT, ".temp-ling-legacy-index-"));
|
|
170
|
+
const persistentWorkspace = path.join(persistentRoot, "workspace");
|
|
171
|
+
fs.mkdirSync(persistentWorkspace, { recursive: true });
|
|
172
|
+
|
|
173
|
+
try {
|
|
174
|
+
const initResult = runCli(
|
|
175
|
+
["init", "--target", "codex", "--path", persistentWorkspace, "--quiet"],
|
|
176
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
177
|
+
);
|
|
178
|
+
assert.strictEqual(initResult.status, 0, initResult.stderr || initResult.stdout);
|
|
179
|
+
|
|
180
|
+
const seedIndex = {
|
|
181
|
+
version: 2,
|
|
182
|
+
updatedAt: now,
|
|
183
|
+
workspaces: [
|
|
184
|
+
{
|
|
185
|
+
path: persistentWorkspace,
|
|
186
|
+
targets: {
|
|
187
|
+
full: {
|
|
188
|
+
version: "1.9.0",
|
|
189
|
+
installedAt: now,
|
|
190
|
+
updatedAt: now,
|
|
191
|
+
},
|
|
192
|
+
},
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
excludedPaths: [],
|
|
196
|
+
};
|
|
197
|
+
fs.writeFileSync(indexPath, `${JSON.stringify(seedIndex, null, 2)}\n`, "utf8");
|
|
198
|
+
|
|
199
|
+
const result = runCli(["update-all", "--quiet"], {
|
|
200
|
+
env: { LING_INDEX_PATH: indexPath },
|
|
201
|
+
});
|
|
202
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
203
|
+
|
|
204
|
+
const normalized = JSON.parse(fs.readFileSync(indexPath, "utf8"));
|
|
205
|
+
assert.ok(!("full" in normalized.workspaces[0].targets), "legacy full target should be removed from rewritten index");
|
|
206
|
+
assert.ok("codex" in normalized.workspaces[0].targets, "codex target should be preserved after update-all");
|
|
207
|
+
} finally {
|
|
208
|
+
fs.rmSync(persistentRoot, { recursive: true, force: true });
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
test("update-all should auto-clean macOS /private/var temp alias records", () => {
|
|
213
|
+
if (process.platform !== "darwin") {
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const tmpRoot = os.tmpdir();
|
|
218
|
+
let aliasTempPath = "";
|
|
219
|
+
if (tmpRoot.startsWith("/var/")) {
|
|
220
|
+
aliasTempPath = `/private${tmpRoot}/ling-indexed-temp/workspace`;
|
|
221
|
+
} else if (tmpRoot.startsWith("/private/var/")) {
|
|
222
|
+
aliasTempPath = `${tmpRoot.replace(/^\/private/, "")}/ling-indexed-temp/workspace`;
|
|
223
|
+
} else {
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const now = new Date().toISOString();
|
|
228
|
+
const seedIndex = {
|
|
229
|
+
version: 2,
|
|
230
|
+
updatedAt: now,
|
|
231
|
+
workspaces: [
|
|
232
|
+
{
|
|
233
|
+
path: aliasTempPath,
|
|
234
|
+
targets: {
|
|
235
|
+
gemini: {
|
|
236
|
+
version: "2.0.1",
|
|
237
|
+
installedAt: now,
|
|
238
|
+
updatedAt: now,
|
|
239
|
+
},
|
|
240
|
+
},
|
|
241
|
+
},
|
|
242
|
+
],
|
|
243
|
+
excludedPaths: [],
|
|
244
|
+
};
|
|
245
|
+
fs.writeFileSync(indexPath, `${JSON.stringify(seedIndex, null, 2)}\n`, "utf8");
|
|
246
|
+
|
|
247
|
+
const result = runCli(["update-all", "--quiet"], {
|
|
248
|
+
env: { LING_INDEX_PATH: indexPath },
|
|
249
|
+
});
|
|
250
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
251
|
+
|
|
252
|
+
const indexData = JSON.parse(fs.readFileSync(indexPath, "utf8"));
|
|
253
|
+
const hasAliasTempWorkspace = (indexData.workspaces || []).some((item) => item.path === aliasTempPath);
|
|
254
|
+
assert.ok(!hasAliasTempWorkspace, "macOS alias temp path should be removed during update-all");
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
test("init in non-interactive mode should require explicit target", () => {
|
|
258
|
+
const result = runCli(
|
|
259
|
+
["init", "--non-interactive", "--path", workspaceDir, "--quiet"],
|
|
260
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
261
|
+
);
|
|
262
|
+
assert.notStrictEqual(result.status, 0);
|
|
263
|
+
assert.match(result.stderr || result.stdout, /非交互模式下必须通过 --target 或 --targets 指定目标/);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
test("update should run in gemini dry-run mode", () => {
|
|
267
|
+
fs.mkdirSync(path.join(workspaceDir, ".agent"), { recursive: true });
|
|
268
|
+
|
|
269
|
+
const result = runCli(
|
|
270
|
+
["update", "--target", "gemini", "--path", workspaceDir, "--dry-run", "--quiet"],
|
|
271
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
272
|
+
);
|
|
273
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
test("status --quiet should report missing with exit code 2 when nothing is installed", () => {
|
|
277
|
+
const result = runCli(
|
|
278
|
+
["status", "--path", workspaceDir, "--quiet"],
|
|
279
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
280
|
+
);
|
|
281
|
+
assert.strictEqual(result.status, 2);
|
|
282
|
+
assert.strictEqual((result.stdout || "").trim(), "missing");
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("status --quiet should report broken when installation is incomplete", () => {
|
|
286
|
+
fs.mkdirSync(path.join(workspaceDir, ".agent"), { recursive: true });
|
|
287
|
+
|
|
288
|
+
const result = runCli(
|
|
289
|
+
["status", "--path", workspaceDir, "--quiet"],
|
|
290
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
291
|
+
);
|
|
292
|
+
assert.strictEqual(result.status, 1);
|
|
293
|
+
assert.strictEqual((result.stdout || "").trim(), "broken");
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
test("status --quiet should report broken for codex drift", () => {
|
|
297
|
+
const initResult = runCli(
|
|
298
|
+
["init", "--target", "codex", "--path", workspaceDir, "--quiet"],
|
|
299
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
300
|
+
);
|
|
301
|
+
assert.strictEqual(initResult.status, 0, initResult.stderr || initResult.stdout);
|
|
302
|
+
|
|
303
|
+
fs.writeFileSync(path.join(workspaceDir, ".agents", "AGENTS.md"), "drifted", "utf8");
|
|
304
|
+
|
|
305
|
+
const result = runCli(
|
|
306
|
+
["status", "--path", workspaceDir, "--quiet"],
|
|
307
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
308
|
+
);
|
|
309
|
+
assert.strictEqual(result.status, 1);
|
|
310
|
+
assert.strictEqual((result.stdout || "").trim(), "broken");
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
test("status --quiet should report installed when installation is healthy", () => {
|
|
314
|
+
const initResult = runCli(
|
|
315
|
+
["init", "--target", "codex", "--path", workspaceDir, "--quiet"],
|
|
316
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
317
|
+
);
|
|
318
|
+
assert.strictEqual(initResult.status, 0, initResult.stderr || initResult.stdout);
|
|
319
|
+
|
|
320
|
+
const result = runCli(
|
|
321
|
+
["status", "--path", workspaceDir, "--quiet"],
|
|
322
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
323
|
+
);
|
|
324
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
325
|
+
assert.strictEqual((result.stdout || "").trim(), "installed");
|
|
326
|
+
});
|
|
327
|
+
|
|
328
|
+
test("status should reject unsupported --no-index option", () => {
|
|
329
|
+
fs.mkdirSync(path.join(workspaceDir, ".agent"), { recursive: true });
|
|
330
|
+
|
|
331
|
+
const result = runCli(
|
|
332
|
+
["status", "--path", workspaceDir, "--no-index"],
|
|
333
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
334
|
+
);
|
|
335
|
+
assert.notStrictEqual(result.status, 0);
|
|
336
|
+
assert.match(result.stderr || result.stdout, /命令 status 不支持参数: --no-index/);
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
test("exclude list should reject unsupported --path option", () => {
|
|
340
|
+
const result = runCli(
|
|
341
|
+
["exclude", "list", "--path", workspaceDir],
|
|
342
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
343
|
+
);
|
|
344
|
+
assert.notStrictEqual(result.status, 0);
|
|
345
|
+
assert.match(result.stderr || result.stdout, /命令 exclude list 不支持参数: --path/);
|
|
346
|
+
});
|
|
347
|
+
|
|
348
|
+
test("update-all should run in dry-run mode with indexed workspace", () => {
|
|
349
|
+
fs.mkdirSync(path.join(workspaceDir, ".agent"), { recursive: true });
|
|
350
|
+
const now = new Date().toISOString();
|
|
351
|
+
const seedIndex = {
|
|
352
|
+
version: 2,
|
|
353
|
+
updatedAt: now,
|
|
354
|
+
workspaces: [
|
|
355
|
+
{
|
|
356
|
+
path: workspaceDir,
|
|
357
|
+
targets: {
|
|
358
|
+
gemini: {
|
|
359
|
+
version: "2.0.1",
|
|
360
|
+
installedAt: now,
|
|
361
|
+
updatedAt: now,
|
|
362
|
+
},
|
|
363
|
+
},
|
|
364
|
+
},
|
|
365
|
+
],
|
|
366
|
+
excludedPaths: [],
|
|
367
|
+
};
|
|
368
|
+
fs.writeFileSync(indexPath, `${JSON.stringify(seedIndex, null, 2)}\n`, "utf8");
|
|
369
|
+
|
|
370
|
+
const result = runCli(["update-all", "--dry-run", "--quiet"], {
|
|
371
|
+
env: { LING_INDEX_PATH: indexPath },
|
|
372
|
+
});
|
|
373
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
test("update-all should update installed target even when index target list is stale", () => {
|
|
377
|
+
const localWorkspace = fs.mkdtempSync(path.join(REPO_ROOT, ".tmp-ling-update-all-stale-target-"));
|
|
378
|
+
try {
|
|
379
|
+
fs.mkdirSync(path.join(localWorkspace, ".codex"), { recursive: true });
|
|
380
|
+
fs.writeFileSync(
|
|
381
|
+
path.join(localWorkspace, ".codex", "manifest.json"),
|
|
382
|
+
JSON.stringify({ version: 2, target: "codex", files: {} }),
|
|
383
|
+
"utf8",
|
|
384
|
+
);
|
|
385
|
+
|
|
386
|
+
const now = new Date().toISOString();
|
|
387
|
+
const seedIndex = {
|
|
388
|
+
version: 2,
|
|
389
|
+
updatedAt: now,
|
|
390
|
+
workspaces: [
|
|
391
|
+
{
|
|
392
|
+
path: localWorkspace,
|
|
393
|
+
targets: {
|
|
394
|
+
gemini: {
|
|
395
|
+
version: "2.0.1",
|
|
396
|
+
installedAt: now,
|
|
397
|
+
updatedAt: now,
|
|
398
|
+
},
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
],
|
|
402
|
+
excludedPaths: [],
|
|
403
|
+
};
|
|
404
|
+
fs.writeFileSync(indexPath, `${JSON.stringify(seedIndex, null, 2)}\n`, "utf8");
|
|
405
|
+
|
|
406
|
+
const result = runCli(["update-all", "--targets", "codex", "--quiet"], {
|
|
407
|
+
env: { LING_INDEX_PATH: indexPath },
|
|
408
|
+
});
|
|
409
|
+
assert.strictEqual(result.status, 0, result.stderr || result.stdout);
|
|
410
|
+
|
|
411
|
+
const indexData = JSON.parse(fs.readFileSync(indexPath, "utf8"));
|
|
412
|
+
const record = (indexData.workspaces || []).find((item) => item.path === localWorkspace);
|
|
413
|
+
assert.ok(record, "workspace should remain in index");
|
|
414
|
+
assert.ok(record.targets && record.targets.codex, "codex target should be refreshed into index");
|
|
415
|
+
} finally {
|
|
416
|
+
fs.rmSync(localWorkspace, { recursive: true, force: true });
|
|
417
|
+
}
|
|
418
|
+
});
|
|
419
|
+
|
|
420
|
+
test("init should respect --no-index on non-temp workspace", () => {
|
|
421
|
+
const localWorkspace = fs.mkdtempSync(path.join(REPO_ROOT, ".tmp-ling-no-index-"));
|
|
422
|
+
try {
|
|
423
|
+
const initResult = runCli(
|
|
424
|
+
["init", "--target", "codex", "--path", localWorkspace, "--no-index", "--quiet"],
|
|
425
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
426
|
+
);
|
|
427
|
+
assert.strictEqual(initResult.status, 0, initResult.stderr || initResult.stdout);
|
|
428
|
+
|
|
429
|
+
if (!fs.existsSync(indexPath)) {
|
|
430
|
+
return;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const indexData = JSON.parse(fs.readFileSync(indexPath, "utf8"));
|
|
434
|
+
const hasWorkspace = (indexData.workspaces || []).some((item) => item.path === localWorkspace);
|
|
435
|
+
assert.ok(!hasWorkspace, "--no-index should skip workspace registration");
|
|
436
|
+
|
|
437
|
+
const updateResult = runCli(
|
|
438
|
+
["update", "--quiet"],
|
|
439
|
+
{ cwd: localWorkspace, env: { LING_INDEX_PATH: indexPath } },
|
|
440
|
+
);
|
|
441
|
+
assert.strictEqual(updateResult.status, 0, updateResult.stderr || updateResult.stdout);
|
|
442
|
+
|
|
443
|
+
const indexAfterUpdate = JSON.parse(fs.readFileSync(indexPath, "utf8"));
|
|
444
|
+
const hasWorkspaceAfterUpdate = (indexAfterUpdate.workspaces || []).some((item) => item.path === localWorkspace);
|
|
445
|
+
assert.ok(hasWorkspaceAfterUpdate, "local update without --no-index should re-register workspace");
|
|
446
|
+
} finally {
|
|
447
|
+
fs.rmSync(localWorkspace, { recursive: true, force: true });
|
|
448
|
+
}
|
|
449
|
+
});
|
|
450
|
+
|
|
451
|
+
test("init should skip indexing toolkit source-like workspace by package name", () => {
|
|
452
|
+
const toolkitWorkspace = fs.mkdtempSync(path.join(REPO_ROOT, ".tmp-ling-toolkit-source-"));
|
|
453
|
+
try {
|
|
454
|
+
fs.mkdirSync(path.join(toolkitWorkspace, "bin"), { recursive: true });
|
|
455
|
+
fs.writeFileSync(
|
|
456
|
+
path.join(toolkitWorkspace, "package.json"),
|
|
457
|
+
JSON.stringify({ name: "@mison/ling", version: "2.0.1" }, null, 2),
|
|
458
|
+
"utf8",
|
|
459
|
+
);
|
|
460
|
+
fs.writeFileSync(path.join(toolkitWorkspace, "bin", "ling.js"), "#!/usr/bin/env node\n", "utf8");
|
|
461
|
+
|
|
462
|
+
const initResult = runCli(
|
|
463
|
+
["init", "--target", "gemini", "--path", toolkitWorkspace, "--quiet"],
|
|
464
|
+
{ env: { LING_INDEX_PATH: indexPath } },
|
|
465
|
+
);
|
|
466
|
+
assert.strictEqual(initResult.status, 0, initResult.stderr || initResult.stdout);
|
|
467
|
+
|
|
468
|
+
if (!fs.existsSync(indexPath)) {
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
const indexData = JSON.parse(fs.readFileSync(indexPath, "utf8"));
|
|
473
|
+
const hasWorkspace = (indexData.workspaces || []).some((item) => item.path === toolkitWorkspace);
|
|
474
|
+
assert.ok(!hasWorkspace, "toolkit source-like workspace should be excluded from index");
|
|
475
|
+
} finally {
|
|
476
|
+
fs.rmSync(toolkitWorkspace, { recursive: true, force: true });
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
});
|
|
@@ -0,0 +1,132 @@
|
|
|
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
|
+
|
|
8
|
+
describe("CodexAdapter", () => {
|
|
9
|
+
let workDir;
|
|
10
|
+
let installSource;
|
|
11
|
+
|
|
12
|
+
beforeEach(() => {
|
|
13
|
+
workDir = fs.mkdtempSync(path.join(os.tmpdir(), "adapter-test-"));
|
|
14
|
+
installSource = path.join(workDir, "source");
|
|
15
|
+
fs.mkdirSync(installSource);
|
|
16
|
+
fs.writeFileSync(path.join(installSource, "file.txt"), "content");
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
fs.rmSync(workDir, { recursive: true, force: true });
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
test("install should create .agents as managed codex directory", () => {
|
|
24
|
+
const adapter = new CodexAdapter(workDir, { quiet: true });
|
|
25
|
+
adapter.install(installSource);
|
|
26
|
+
|
|
27
|
+
const agentsDir = path.join(workDir, ".agents");
|
|
28
|
+
const legacyDir = path.join(workDir, ".codex");
|
|
29
|
+
const manifest = path.join(agentsDir, "manifest.json");
|
|
30
|
+
|
|
31
|
+
assert.ok(fs.existsSync(agentsDir));
|
|
32
|
+
assert.ok(!fs.existsSync(legacyDir));
|
|
33
|
+
assert.ok(fs.existsSync(manifest));
|
|
34
|
+
|
|
35
|
+
const manifestJson = JSON.parse(fs.readFileSync(manifest, "utf8"));
|
|
36
|
+
assert.strictEqual(manifestJson.target, "codex");
|
|
37
|
+
assert.ok(manifestJson.files["file.txt"]);
|
|
38
|
+
assert.strictEqual(typeof manifestJson.files["file.txt"].hash, "string");
|
|
39
|
+
assert.strictEqual(typeof manifestJson.files["file.txt"].source, "string");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
test("update should migrate legacy .codex directory to .agents", () => {
|
|
43
|
+
const legacyDir = path.join(workDir, ".codex");
|
|
44
|
+
fs.mkdirSync(legacyDir, { recursive: true });
|
|
45
|
+
fs.writeFileSync(path.join(legacyDir, "manifest.json"), JSON.stringify({ version: 2, target: "codex", files: {} }));
|
|
46
|
+
fs.writeFileSync(path.join(legacyDir, "legacy.txt"), "legacy");
|
|
47
|
+
|
|
48
|
+
const updateSource = path.join(workDir, "update-src");
|
|
49
|
+
fs.mkdirSync(updateSource);
|
|
50
|
+
fs.writeFileSync(path.join(updateSource, "new.txt"), "new");
|
|
51
|
+
|
|
52
|
+
const adapter = new CodexAdapter(workDir, { quiet: true, force: true });
|
|
53
|
+
adapter.update(updateSource);
|
|
54
|
+
|
|
55
|
+
assert.ok(fs.existsSync(path.join(workDir, ".agents")));
|
|
56
|
+
assert.ok(!fs.existsSync(path.join(workDir, ".codex")));
|
|
57
|
+
assert.ok(fs.existsSync(path.join(workDir, ".agents", "new.txt")));
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
test("update should detect drift and backup", () => {
|
|
61
|
+
const options = { quiet: true, force: true };
|
|
62
|
+
const adapter = new CodexAdapter(workDir, options);
|
|
63
|
+
|
|
64
|
+
adapter.install(installSource);
|
|
65
|
+
|
|
66
|
+
const managedFile = path.join(workDir, ".agents", "file.txt");
|
|
67
|
+
fs.writeFileSync(managedFile, "modified content");
|
|
68
|
+
|
|
69
|
+
const updateSource = path.join(workDir, "update_src");
|
|
70
|
+
fs.mkdirSync(updateSource);
|
|
71
|
+
fs.writeFileSync(path.join(updateSource, "file.txt"), "v2 content");
|
|
72
|
+
fs.writeFileSync(path.join(updateSource, "new.txt"), "new file");
|
|
73
|
+
|
|
74
|
+
adapter.update(updateSource);
|
|
75
|
+
|
|
76
|
+
const agentsDir = path.join(workDir, ".agents");
|
|
77
|
+
const backupBase = path.join(workDir, ".agents-backup");
|
|
78
|
+
|
|
79
|
+
assert.strictEqual(fs.readFileSync(path.join(agentsDir, "file.txt"), "utf8"), "v2 content");
|
|
80
|
+
assert.ok(fs.existsSync(path.join(agentsDir, "new.txt")));
|
|
81
|
+
|
|
82
|
+
assert.ok(fs.existsSync(backupBase));
|
|
83
|
+
const backups = fs.readdirSync(backupBase);
|
|
84
|
+
assert.strictEqual(backups.length, 1);
|
|
85
|
+
const latestBackup = path.join(backupBase, backups[0]);
|
|
86
|
+
assert.ok(fs.existsSync(path.join(latestBackup, "file.txt")));
|
|
87
|
+
assert.strictEqual(fs.readFileSync(path.join(latestBackup, "file.txt"), "utf8"), "modified content");
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
test("smart overwrite should skip backup when local content already equals incoming content", () => {
|
|
91
|
+
const options = { quiet: true, force: true };
|
|
92
|
+
const adapter = new CodexAdapter(workDir, options);
|
|
93
|
+
|
|
94
|
+
adapter.install(installSource);
|
|
95
|
+
|
|
96
|
+
const managedFile = path.join(workDir, ".agents", "file.txt");
|
|
97
|
+
fs.writeFileSync(managedFile, "v2 content");
|
|
98
|
+
|
|
99
|
+
const updateSource = path.join(workDir, "update_src_same");
|
|
100
|
+
fs.mkdirSync(updateSource);
|
|
101
|
+
fs.writeFileSync(path.join(updateSource, "file.txt"), "v2 content");
|
|
102
|
+
|
|
103
|
+
adapter.update(updateSource);
|
|
104
|
+
|
|
105
|
+
const backupBase = path.join(workDir, ".agents-backup");
|
|
106
|
+
assert.ok(!fs.existsSync(backupBase), "No backup should be created when file already equals incoming hash");
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("update should create full snapshot backup when manifest is invalid", () => {
|
|
110
|
+
const options = { quiet: true, force: true };
|
|
111
|
+
const adapter = new CodexAdapter(workDir, options);
|
|
112
|
+
adapter.install(installSource);
|
|
113
|
+
|
|
114
|
+
const managedFile = path.join(workDir, ".agents", "file.txt");
|
|
115
|
+
fs.writeFileSync(managedFile, "user-modified");
|
|
116
|
+
fs.writeFileSync(path.join(workDir, ".agents", "manifest.json"), "{invalid-json");
|
|
117
|
+
|
|
118
|
+
const updateSource = path.join(workDir, "update_src_invalid_manifest");
|
|
119
|
+
fs.mkdirSync(updateSource);
|
|
120
|
+
fs.writeFileSync(path.join(updateSource, "file.txt"), "next-version");
|
|
121
|
+
|
|
122
|
+
adapter.update(updateSource);
|
|
123
|
+
|
|
124
|
+
const backupBase = path.join(workDir, ".agents-backup");
|
|
125
|
+
assert.ok(fs.existsSync(backupBase));
|
|
126
|
+
const backups = fs.readdirSync(backupBase);
|
|
127
|
+
assert.strictEqual(backups.length, 1);
|
|
128
|
+
const snapshotFile = path.join(backupBase, backups[0], "full-snapshot", "file.txt");
|
|
129
|
+
assert.ok(fs.existsSync(snapshotFile));
|
|
130
|
+
assert.strictEqual(fs.readFileSync(snapshotFile, "utf8"), "user-modified");
|
|
131
|
+
});
|
|
132
|
+
});
|