@bonesofspring/ai-rules 0.2.22 → 0.2.23
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/CHANGELOG.md +8 -0
- package/README.md +6 -2
- package/bin/cli.js +32 -8
- package/bin/mavis-install.js +441 -0
- package/package.json +9 -2
- package/presets/_shared/core/meta/preset-pr-checklist.md +1 -0
- package/presets/_shared/core/meta/preset-twin-sync.md +16 -0
- package/presets/claude/android-kotlin/rules/tooling-and-review/preset-pr-checklist.md +1 -0
- package/presets/claude/android-kotlin/rules/tooling-and-review/preset-twin-sync.md +16 -0
- package/presets/claude/go/rules/tooling-and-review/preset-pr-checklist.md +1 -0
- package/presets/claude/go/rules/tooling-and-review/preset-twin-sync.md +16 -0
- package/presets/claude/ios-swift/rules/tooling-and-review/preset-pr-checklist.md +1 -0
- package/presets/claude/ios-swift/rules/tooling-and-review/preset-twin-sync.md +16 -0
- package/presets/claude/java/rules/tooling-and-review/preset-pr-checklist.md +1 -0
- package/presets/claude/java/rules/tooling-and-review/preset-twin-sync.md +16 -0
- package/presets/claude/mcp-ts/rules/tooling-and-review/preset-pr-checklist.md +1 -0
- package/presets/claude/mcp-ts/rules/tooling-and-review/preset-twin-sync.md +16 -0
- package/presets/claude/next/rules/tooling-and-review/preset-pr-checklist.md +1 -0
- package/presets/claude/next/rules/tooling-and-review/preset-twin-sync.md +16 -0
- package/presets/claude/nuxt/rules/tooling-and-review/preset-pr-checklist.md +1 -0
- package/presets/claude/nuxt/rules/tooling-and-review/preset-twin-sync.md +16 -0
- package/presets/claude/php-hexagonal/rules/tooling-and-review/preset-pr-checklist.md +1 -0
- package/presets/claude/php-hexagonal/rules/tooling-and-review/preset-twin-sync.md +16 -0
- package/presets/claude/php-laravel/rules/tooling-and-review/preset-pr-checklist.md +1 -0
- package/presets/claude/php-laravel/rules/tooling-and-review/preset-twin-sync.md +16 -0
- package/presets/claude/svelte/rules/tooling-and-review/preset-pr-checklist.md +1 -0
- package/presets/claude/svelte/rules/tooling-and-review/preset-twin-sync.md +16 -0
- package/presets/cursor/android-kotlin/rules/preset-pr-checklist.mdc +1 -0
- package/presets/cursor/android-kotlin/rules/preset-twin-sync.mdc +16 -0
- package/presets/cursor/go/rules/preset-pr-checklist.mdc +1 -0
- package/presets/cursor/go/rules/preset-twin-sync.mdc +16 -0
- package/presets/cursor/ios-swift/rules/preset-pr-checklist.mdc +1 -0
- package/presets/cursor/ios-swift/rules/preset-twin-sync.mdc +16 -0
- package/presets/cursor/java/rules/preset-pr-checklist.mdc +1 -0
- package/presets/cursor/java/rules/preset-twin-sync.mdc +16 -0
- package/presets/cursor/mcp-ts/rules/preset-pr-checklist.mdc +1 -0
- package/presets/cursor/mcp-ts/rules/preset-twin-sync.mdc +16 -0
- package/presets/cursor/next/rules/preset-pr-checklist.mdc +1 -0
- package/presets/cursor/next/rules/preset-twin-sync.mdc +16 -0
- package/presets/cursor/nuxt/rules/preset-pr-checklist.mdc +1 -0
- package/presets/cursor/nuxt/rules/preset-twin-sync.mdc +16 -0
- package/presets/cursor/php-hexagonal/rules/preset-pr-checklist.mdc +1 -0
- package/presets/cursor/php-hexagonal/rules/preset-twin-sync.mdc +16 -0
- package/presets/cursor/php-laravel/rules/preset-pr-checklist.mdc +1 -0
- package/presets/cursor/php-laravel/rules/preset-twin-sync.mdc +16 -0
- package/presets/cursor/svelte/rules/preset-pr-checklist.mdc +1 -0
- package/presets/cursor/svelte/rules/preset-twin-sync.mdc +16 -0
- package/presets/mavis/README.md +60 -0
- package/presets/mavis/next/MAVIS.md +67 -0
- package/presets/mavis/next/MCP.md +36 -0
- package/presets/mavis/next/README.md +15 -0
- package/presets/mavis/next/REPO_AGENTS.md +94 -0
- package/presets/mavis/next/adapter-map.json +959 -0
- package/presets/mavis/next/agents/README.md +206 -0
- package/presets/mavis/next/agents/accessibility-reviewer.md +61 -0
- package/presets/mavis/next/agents/api-contract-reviewer.md +57 -0
- package/presets/mavis/next/agents/build-verifier.md +110 -0
- package/presets/mavis/next/agents/ci-investigator.md +63 -0
- package/presets/mavis/next/agents/code-reviewer.md +61 -0
- package/presets/mavis/next/agents/codebase-analyzer.md +21 -0
- package/presets/mavis/next/agents/debugger.md +80 -0
- package/presets/mavis/next/agents/feature-developer.md +101 -0
- package/presets/mavis/next/agents/migration-specialist.md +67 -0
- package/presets/mavis/next/agents/mockup-reviewer.md +53 -0
- package/presets/mavis/next/agents/openspec-author.md +157 -0
- package/presets/mavis/next/agents/performance-auditor.md +68 -0
- package/presets/mavis/next/agents/playwright-test-generator.md +28 -0
- package/presets/mavis/next/agents/playwright-test-healer.md +28 -0
- package/presets/mavis/next/agents/playwright-test-planner.md +21 -0
- package/presets/mavis/next/agents/qa-tester.md +52 -0
- package/presets/mavis/next/agents/security-reviewer.md +54 -0
- package/presets/mavis/next/agents/solution-architect.md +68 -0
- package/presets/mavis/next/agents/task-analyst.md +166 -0
- package/presets/mavis/next/agents/task-router.md +178 -0
- package/presets/mavis/next/agents/tech-writer.md +57 -0
- package/presets/mavis/next/agents/unit-test-generator.md +31 -0
- package/presets/mavis/next/agents/unit-test-healer.md +32 -0
- package/presets/mavis/next/agents/unit-test-planner.md +54 -0
- package/presets/mavis/next/commands/README.md +49 -0
- package/presets/mavis/next/commands/spec-start.md +24 -0
- package/presets/mavis/next/commands/task-continue.md +50 -0
- package/presets/mavis/next/commands/task.md +50 -0
- package/presets/mavis/next/commands/technical-retro.md +80 -0
- package/presets/mavis/next/hooks/README.md +22 -0
- package/presets/mavis/next/hooks/chain-team-phases.sh +845 -0
- package/presets/mavis/next/mcp.json +19 -0
- package/presets/mavis/next/rules/README.md +16 -0
- package/presets/mavis/next/rules/agent-team-intake.md +11 -0
- package/presets/mavis/next/rules/agent-team-orchestrator.md +210 -0
- package/presets/mavis/next/rules/ai-local-overlay.md +37 -0
- package/presets/mavis/next/rules/anti-sycophancy-discipline.md +23 -0
- package/presets/mavis/next/rules/api-services.md +72 -0
- package/presets/mavis/next/rules/architecture-boundaries-ui.md +9 -0
- package/presets/mavis/next/rules/architecture-boundaries.md +86 -0
- package/presets/mavis/next/rules/arrow-functions.md +25 -0
- package/presets/mavis/next/rules/code-quality-and-refactoring.md +40 -0
- package/presets/mavis/next/rules/code-review-mr.md +40 -0
- package/presets/mavis/next/rules/css-property-order-stylelint.md +5 -0
- package/presets/mavis/next/rules/design-guidance.md +99 -0
- package/presets/mavis/next/rules/feature-delivery-workflow.md +49 -0
- package/presets/mavis/next/rules/http-client.md +55 -0
- package/presets/mavis/next/rules/layer-barrel-exports.md +68 -0
- package/presets/mavis/next/rules/mcp-usage.md +86 -0
- package/presets/mavis/next/rules/mockup-review.md +107 -0
- package/presets/mavis/next/rules/navigation-router-stack.md +56 -0
- package/presets/mavis/next/rules/navigation-router-ui.md +10 -0
- package/presets/mavis/next/rules/next-app-core.md +29 -0
- package/presets/mavis/next/rules/next-app-router.md +44 -0
- package/presets/mavis/next/rules/no-props-spread.md +56 -0
- package/presets/mavis/next/rules/no-type-assertion-as-import-export.md +51 -0
- package/presets/mavis/next/rules/package-manager.md +11 -0
- package/presets/mavis/next/rules/playwright-agents.md +67 -0
- package/presets/mavis/next/rules/post-change-lint.md +35 -0
- package/presets/mavis/next/rules/preset-layering.md +27 -0
- package/presets/mavis/next/rules/preset-no-cross-stack-leakage.md +90 -0
- package/presets/mavis/next/rules/preset-pr-checklist.md +35 -0
- package/presets/mavis/next/rules/preset-token-budget.md +36 -0
- package/presets/mavis/next/rules/preset-twin-sync.md +65 -0
- package/presets/mavis/next/rules/product-specs-authoring.md +112 -0
- package/presets/mavis/next/rules/product-specs.md +56 -0
- package/presets/mavis/next/rules/public-imports.md +50 -0
- package/presets/mavis/next/rules/react-a11y-coding.md +31 -0
- package/presets/mavis/next/rules/react-next-anti-hallucination.md +60 -0
- package/presets/mavis/next/rules/react-ui.md +105 -0
- package/presets/mavis/next/rules/reference-features.md +45 -0
- package/presets/mavis/next/rules/security-next.md +45 -0
- package/presets/mavis/next/rules/store-rtk.md +78 -0
- package/presets/mavis/next/rules/technical-retro.md +11 -0
- package/presets/mavis/next/rules/tests-e2e-structure.md +72 -0
- package/presets/mavis/next/rules/tests-unit.md +72 -0
- package/presets/mavis/next/rules/types-jsdoc.md +43 -0
- package/presets/mavis/next/rules/ux-design.md +99 -0
- package/presets/mavis/next/skills/README.md +21 -0
- package/presets/mavis/next/skills/ci-investigation/SKILL.md +39 -0
- package/presets/mavis/next/skills/code-review/SKILL.md +39 -0
- package/presets/mavis/next/skills/debug-investigation/SKILL.md +26 -0
- package/presets/mavis/next/skills/feature-delivery/SKILL.md +34 -0
- package/presets/mavis/next/skills/mockup-review/SKILL.md +24 -0
- package/presets/mavis/next/skills/playwright-e2e/SKILL.md +28 -0
- package/presets/mavis/next/skills/react-performance/SKILL.md +39 -0
- package/presets/mavis/next/skills/spec-authoring/SKILL.md +21 -0
- package/presets/mavis/next/skills/spec-discovery/SKILL.md +53 -0
- package/presets/mavis/next/skills/technical-retro/SKILL.md +49 -0
- package/presets/mavis/next/skills/unit-testing/SKILL.md +29 -0
- package/presets/mavis/next/skills/ux-design/SKILL.md +31 -0
- package/presets/mavis/next/skills/write-adr/SKILL.md +36 -0
- package/presets/mavis/next/team/README.md +148 -0
- package/presets/mavis/next/team/conventions.md +21 -0
- package/presets/mavis/next/team/fixtures/artifact-manifest.example.json +79 -0
- package/presets/mavis/next/team/fixtures/bugfix-standard.json +25 -0
- package/presets/mavis/next/team/fixtures/feature-full.json +29 -0
- package/presets/mavis/next/team/fixtures/feature-light.json +27 -0
- package/presets/mavis/next/team/fixtures/feature-with-figma-url.json +17 -0
- package/presets/mavis/next/team/fixtures/feature-with-ux-no-figma.json +17 -0
- package/presets/mavis/next/team/fixtures/memlog.example.md +9 -0
- package/presets/mavis/next/team/fixtures/metrics-scenarios/tasks/human-gate/metrics.json +69 -0
- package/presets/mavis/next/team/fixtures/metrics-scenarios/tasks/missing-ledger/status.json +5 -0
- package/presets/mavis/next/team/fixtures/metrics-scenarios/tasks/parallel-receipts/metrics.json +79 -0
- package/presets/mavis/next/team/fixtures/metrics-scenarios/tasks/serial-retry/metrics.json +124 -0
- package/presets/mavis/next/team/fixtures/metrics-scenarios/tasks/validation-failed/metrics.json +124 -0
- package/presets/mavis/next/team/fixtures/metrics.example.json +54 -0
- package/presets/mavis/next/team/fixtures/mockup-review-fail-embedded-status.json +8 -0
- package/presets/mavis/next/team/fixtures/mockup-review-standalone.json +14 -0
- package/presets/mavis/next/team/fixtures/ux-design-standalone.json +15 -0
- package/presets/mavis/next/team/tasks/.gitkeep +0 -0
- package/presets/mavis/platform-contract.json +131 -0
- package/scripts/capture-cli-golden.mjs +44 -0
- package/scripts/check-chain-hook-coverage.sh +1 -0
- package/scripts/check-mavis-adapter-map.mjs +294 -0
- package/scripts/check-mavis-local-overlay.mjs +56 -0
- package/scripts/check-mavis-platform-contract.mjs +388 -0
- package/scripts/check-mavis-token-budget.mjs +48 -0
- package/scripts/check-preset-leakage.sh +35 -3
- package/scripts/check-preset-structure.sh +63 -0
- package/scripts/check-shared-core-drift.sh +18 -0
- package/scripts/check-task-router-intents.sh +15 -2
- package/scripts/fixtures/cli-golden/claude/next.json +130 -0
- package/scripts/fixtures/cli-golden/cursor/next.json +132 -0
- package/scripts/fixtures/cli-golden/mavis/next.json +125 -0
- package/scripts/fixtures/mavis-adapter-map/invalid-unmapped/cursor/AGENTS.md +1 -0
- package/scripts/fixtures/mavis-adapter-map/invalid-unmapped/cursor/rules/extra.mdc +1 -0
- package/scripts/fixtures/mavis-adapter-map/invalid-unmapped/cursor/rules/sample.mdc +2 -0
- package/scripts/fixtures/mavis-adapter-map/invalid-unmapped/mavis/MAVIS.md +1 -0
- package/scripts/fixtures/mavis-adapter-map/invalid-unmapped/mavis/adapter-map.json +26 -0
- package/scripts/fixtures/mavis-adapter-map/invalid-unmapped/mavis/rules/sample.md +2 -0
- package/scripts/fixtures/mavis-adapter-map/valid/cursor/AGENTS.md +1 -0
- package/scripts/fixtures/mavis-adapter-map/valid/cursor/rules/sample.mdc +2 -0
- package/scripts/fixtures/mavis-adapter-map/valid/mavis/MAVIS.md +1 -0
- package/scripts/fixtures/mavis-adapter-map/valid/mavis/adapter-map.json +26 -0
- package/scripts/fixtures/mavis-adapter-map/valid/mavis/rules/sample.md +2 -0
- package/scripts/fixtures/mavis-platform-contract/invalid-minimax-destination.json +131 -0
- package/scripts/fixtures/mavis-platform-contract/invalid-missing-capability.json +126 -0
- package/scripts/fixtures/mavis-platform-contract/invalid-released-unknown-stack.json +133 -0
- package/scripts/fixtures/mavis-platform-contract/invalid-unknown-native-path.json +131 -0
- package/scripts/fixtures/mavis-platform-contract/invalid-version-pin.json +132 -0
- package/scripts/fixtures/mavis-platform-contract/valid.json +131 -0
- package/scripts/fixtures/mavis-probe/AGENTS.md +6 -0
- package/scripts/fixtures/mavis-probe/agents/probe-agent.md +6 -0
- package/scripts/fixtures/mavis-probe/fake-mcode.mjs +98 -0
- package/scripts/fixtures/mavis-probe/output.schema.json +28 -0
- package/scripts/fixtures/mavis-probe/rules/probe.md +5 -0
- package/scripts/fixtures/mavis-probe/skills/probe/SKILL.md +11 -0
- package/scripts/generate-mavis-next.mjs +704 -0
- package/scripts/lib/cli-golden.mjs +50 -0
- package/scripts/lib/discover-hooks.mjs +72 -0
- package/scripts/probe-mavis-capabilities.mjs +480 -0
- package/scripts/test-agent-task-metrics-hooks.mjs +22 -16
- package/scripts/test-chain-team-phases-coverage.mjs +10 -7
- package/scripts/test-cli-init-clean.mjs +256 -0
- package/scripts/test-cli-mcp-merge.mjs +23 -0
- package/scripts/test-mavis-context-budget.mjs +55 -0
- package/scripts/test-mavis-orchestration.mjs +649 -0
- package/scripts/test-probe-mavis-capabilities.mjs +205 -0
|
@@ -9,12 +9,15 @@ import { fileURLToPath } from 'node:url';
|
|
|
9
9
|
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
10
10
|
const packageDirectory = path.resolve(scriptDirectory, '..');
|
|
11
11
|
const presetsDirectory = path.join(packageDirectory, 'presets');
|
|
12
|
-
|
|
13
|
-
...fs.readdirSync(path.join(presetsDirectory, 'cursor')).map((stack) => path.join(presetsDirectory, 'cursor', stack, 'hooks', 'chain-team-phases.sh')),
|
|
14
|
-
...fs.readdirSync(path.join(presetsDirectory, 'claude')).map((stack) => path.join(presetsDirectory, 'claude', stack, 'hooks', 'chain-team-phases.sh')),
|
|
15
|
-
];
|
|
12
|
+
import { discoverChainHooks, expectedHookCount, loadReleasedMavisStacks, platformFromHook } from './lib/discover-hooks.mjs';
|
|
16
13
|
|
|
17
|
-
|
|
14
|
+
const hooks = discoverChainHooks(presetsDirectory);
|
|
15
|
+
|
|
16
|
+
assert.equal(
|
|
17
|
+
hooks.length,
|
|
18
|
+
expectedHookCount(presetsDirectory),
|
|
19
|
+
`expected ${expectedHookCount(presetsDirectory)} packaged hooks`,
|
|
20
|
+
);
|
|
18
21
|
|
|
19
22
|
function readJson(filePath) {
|
|
20
23
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
@@ -55,7 +58,7 @@ function invokeHookAsync(hook, workingDirectory, attemptId) {
|
|
|
55
58
|
}
|
|
56
59
|
|
|
57
60
|
function writeTask(root, hook, slug, pipeline, status, metrics, artifacts = {}) {
|
|
58
|
-
const platform = hook
|
|
61
|
+
const platform = platformFromHook(hook);
|
|
59
62
|
const teamRoot = path.join(root, `.${platform}`, 'team');
|
|
60
63
|
const taskDirectory = path.join(teamRoot, 'tasks', slug);
|
|
61
64
|
fs.mkdirSync(taskDirectory, { recursive: true });
|
|
@@ -70,7 +73,7 @@ function writeTask(root, hook, slug, pipeline, status, metrics, artifacts = {})
|
|
|
70
73
|
}
|
|
71
74
|
|
|
72
75
|
function prepareTask(root, hook) {
|
|
73
|
-
const platform = hook
|
|
76
|
+
const platform = platformFromHook(hook);
|
|
74
77
|
const teamRoot = path.join(root, `.${platform}`, 'team');
|
|
75
78
|
const taskDirectory = path.join(teamRoot, 'tasks', 'reverse-parallel');
|
|
76
79
|
fs.mkdirSync(taskDirectory, { recursive: true });
|
|
@@ -162,7 +165,7 @@ for (const hook of hooks) {
|
|
|
162
165
|
for (const hook of hooks) {
|
|
163
166
|
const root = fs.mkdtempSync(path.join(packageDirectory, '.agent-task-metrics-retry-'));
|
|
164
167
|
try {
|
|
165
|
-
const platform = hook
|
|
168
|
+
const platform = platformFromHook(hook);
|
|
166
169
|
const runId = 'serial-retry-run';
|
|
167
170
|
const reviewerAttemptId = `${runId}:step-5:security-reviewer:attempt-1`;
|
|
168
171
|
const taskDirectory = writeTask(
|
|
@@ -242,7 +245,7 @@ for (const hook of hooks) {
|
|
|
242
245
|
for (const hook of hooks) {
|
|
243
246
|
const root = fs.mkdtempSync(path.join(packageDirectory, '.agent-task-metrics-no-chain-'));
|
|
244
247
|
try {
|
|
245
|
-
const platform = hook
|
|
248
|
+
const platform = platformFromHook(hook);
|
|
246
249
|
const runId = 'no-chain-run';
|
|
247
250
|
const attemptId = `${runId}:step-0:feature-developer:attempt-1`;
|
|
248
251
|
const taskDirectory = writeTask(
|
|
@@ -293,7 +296,7 @@ for (const hook of hooks) {
|
|
|
293
296
|
for (const hook of hooks) {
|
|
294
297
|
const root = fs.mkdtempSync(path.join(packageDirectory, '.agent-task-metrics-mixed-parallel-'));
|
|
295
298
|
try {
|
|
296
|
-
const platform = hook
|
|
299
|
+
const platform = platformFromHook(hook);
|
|
297
300
|
const runId = 'mixed-parallel-run';
|
|
298
301
|
const alphaAttemptId = `${runId}:step-1:code-reviewer:attempt-1`;
|
|
299
302
|
const betaAttemptId = `${runId}:step-1:security-reviewer:attempt-1`;
|
|
@@ -360,7 +363,7 @@ for (const hook of hooks) {
|
|
|
360
363
|
for (const hook of hooks) {
|
|
361
364
|
const root = fs.mkdtempSync(path.join(packageDirectory, '.agent-task-metrics-skip-'));
|
|
362
365
|
try {
|
|
363
|
-
const platform = hook
|
|
366
|
+
const platform = platformFromHook(hook);
|
|
364
367
|
const runId = 'skip-run';
|
|
365
368
|
const starterAttemptId = `${runId}:step-0:starter:attempt-1`;
|
|
366
369
|
const taskDirectory = writeTask(
|
|
@@ -418,6 +421,9 @@ for (const hook of hooks) {
|
|
|
418
421
|
for (const command of [
|
|
419
422
|
...fs.readdirSync(path.join(presetsDirectory, 'cursor')).map((stack) => path.join(presetsDirectory, 'cursor', stack, 'commands', 'task-continue.md')),
|
|
420
423
|
...fs.readdirSync(path.join(presetsDirectory, 'claude')).map((stack) => path.join(presetsDirectory, 'claude', stack, 'commands', 'task-continue.md')),
|
|
424
|
+
...loadReleasedMavisStacks(presetsDirectory).map((stack) =>
|
|
425
|
+
path.join(presetsDirectory, 'mavis', stack, 'commands', 'task-continue.md'),
|
|
426
|
+
),
|
|
421
427
|
]) {
|
|
422
428
|
const source = fs.readFileSync(command, 'utf8');
|
|
423
429
|
assert.match(source, /action":"end_human_gate"/, `${command}: must invoke installed hook end_human_gate action`);
|
|
@@ -436,7 +442,7 @@ for (const hook of hooks) {
|
|
|
436
442
|
// pre-resume status → hook end_human_gate (no manual pipelineIndex++) → invoke from status.currentAgent.
|
|
437
443
|
const root = fs.mkdtempSync(path.join(packageDirectory, '.agent-task-metrics-gate-'));
|
|
438
444
|
try {
|
|
439
|
-
const platform = hook
|
|
445
|
+
const platform = platformFromHook(hook);
|
|
440
446
|
const stack = path.basename(path.dirname(path.dirname(hook)));
|
|
441
447
|
const presetCommand = path.join(path.dirname(path.dirname(hook)), 'commands', 'task-continue.md');
|
|
442
448
|
const installedHook = path.join(root, `.${platform}`, 'hooks', 'chain-team-phases.sh');
|
|
@@ -510,7 +516,7 @@ for (const hook of hooks) {
|
|
|
510
516
|
for (const hook of hooks) {
|
|
511
517
|
const root = fs.mkdtempSync(path.join(packageDirectory, '.agent-task-metrics-manifest-reconcile-'));
|
|
512
518
|
try {
|
|
513
|
-
const platform = hook
|
|
519
|
+
const platform = platformFromHook(hook);
|
|
514
520
|
const runId = 'manifest-reconcile-run';
|
|
515
521
|
const reviewerAttemptId = `${runId}:step-1:code-reviewer:attempt-1`;
|
|
516
522
|
const nowIso = '2026-01-01T00:00:00.000Z';
|
|
@@ -617,7 +623,7 @@ for (const hook of hooks) {
|
|
|
617
623
|
for (const hook of hooks) {
|
|
618
624
|
const root = fs.mkdtempSync(path.join(packageDirectory, '.agent-task-metrics-blocked-receipt-'));
|
|
619
625
|
try {
|
|
620
|
-
const platform = hook
|
|
626
|
+
const platform = platformFromHook(hook);
|
|
621
627
|
const runId = 'blocked-receipt-run';
|
|
622
628
|
const attemptId = `${runId}:step-0:feature-developer:attempt-1`;
|
|
623
629
|
const startedAt = '2026-02-01T00:00:00.000Z';
|
|
@@ -708,7 +714,7 @@ for (const hook of hooks) {
|
|
|
708
714
|
for (const hook of hooks) {
|
|
709
715
|
const root = fs.mkdtempSync(path.join(packageDirectory, '.agent-task-metrics-stale-receipt-'));
|
|
710
716
|
try {
|
|
711
|
-
const platform = hook
|
|
717
|
+
const platform = platformFromHook(hook);
|
|
712
718
|
const runId = 'stale-receipt-run';
|
|
713
719
|
const attemptId = `${runId}:step-2:code-reviewer:attempt-2`;
|
|
714
720
|
const startedAt = '2026-02-01T00:00:00.000Z';
|
|
@@ -903,7 +909,7 @@ for (const hook of hooks) {
|
|
|
903
909
|
for (const hook of hooks) {
|
|
904
910
|
const root = fs.mkdtempSync(path.join(packageDirectory, '.agent-task-metrics-lock-race-'));
|
|
905
911
|
try {
|
|
906
|
-
const platform = hook
|
|
912
|
+
const platform = platformFromHook(hook);
|
|
907
913
|
const runId = 'lock-race-run';
|
|
908
914
|
const attemptId = `${runId}:step-0:feature-developer:attempt-1`;
|
|
909
915
|
const taskDirectory = writeTask(
|
|
@@ -32,16 +32,19 @@ const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
|
32
32
|
const packageDirectory = path.resolve(scriptDirectory, '..');
|
|
33
33
|
const presetsDirectory = path.join(packageDirectory, 'presets');
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
...fs.readdirSync(path.join(presetsDirectory, 'cursor')).map((stack) => path.join(presetsDirectory, 'cursor', stack, 'hooks', 'chain-team-phases.sh')),
|
|
37
|
-
...fs.readdirSync(path.join(presetsDirectory, 'claude')).map((stack) => path.join(presetsDirectory, 'claude', stack, 'hooks', 'chain-team-phases.sh')),
|
|
38
|
-
];
|
|
35
|
+
import { discoverChainHooks, expectedHookCount, platformFromHook } from './lib/discover-hooks.mjs';
|
|
39
36
|
|
|
40
|
-
|
|
37
|
+
const hooks = discoverChainHooks(presetsDirectory);
|
|
38
|
+
|
|
39
|
+
assert.equal(
|
|
40
|
+
hooks.length,
|
|
41
|
+
expectedHookCount(presetsDirectory),
|
|
42
|
+
`expected ${expectedHookCount(presetsDirectory)} packaged hooks, got ${hooks.length}`,
|
|
43
|
+
);
|
|
41
44
|
|
|
42
45
|
// mcp-ts does not ship openspec-author (per preset-no-cross-stack-leakage).
|
|
43
46
|
const HOOKS_WITH_OPENSPEC = hooks.filter((hook) => !hook.includes(`${path.sep}mcp-ts${path.sep}`));
|
|
44
|
-
assert.equal(HOOKS_WITH_OPENSPEC.length,
|
|
47
|
+
assert.equal(HOOKS_WITH_OPENSPEC.length, 19, 'expected 19 hooks to cover openspec-author');
|
|
45
48
|
|
|
46
49
|
function readJson(filePath) {
|
|
47
50
|
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
@@ -60,7 +63,7 @@ function invokeHook(hook, workingDirectory, attemptId, payload = {}) {
|
|
|
60
63
|
}
|
|
61
64
|
|
|
62
65
|
function platformFor(hook) {
|
|
63
|
-
return hook
|
|
66
|
+
return platformFromHook(hook);
|
|
64
67
|
}
|
|
65
68
|
|
|
66
69
|
function writeTask(root, hook, slug, pipeline, status, metrics, artifacts = {}) {
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* CLI init/clean goldens and Mavis P0 scenarios.
|
|
4
|
+
*/
|
|
5
|
+
import assert from 'node:assert/strict';
|
|
6
|
+
import { execFileSync } from 'node:child_process';
|
|
7
|
+
import fs from 'node:fs';
|
|
8
|
+
import os from 'node:os';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import { fileURLToPath } from 'node:url';
|
|
11
|
+
import { runInit, snapshotInstall } from './lib/cli-golden.mjs';
|
|
12
|
+
|
|
13
|
+
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
14
|
+
const packageDirectory = path.resolve(scriptDirectory, '..');
|
|
15
|
+
const cli = path.join(packageDirectory, 'bin', 'cli.js');
|
|
16
|
+
const goldenRoot = path.join(scriptDirectory, 'fixtures', 'cli-golden');
|
|
17
|
+
|
|
18
|
+
function readJson(filePath) {
|
|
19
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function writeJson(filePath, value) {
|
|
23
|
+
fs.mkdirSync(path.dirname(filePath), { recursive: true });
|
|
24
|
+
fs.writeFileSync(filePath, `${JSON.stringify(value, null, 2)}\n`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function runClean(cwd, tool) {
|
|
28
|
+
return execFileSync(process.execPath, [cli, 'clean', tool, '--cwd', cwd], {
|
|
29
|
+
encoding: 'utf8',
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function runCli(cwd, args, env = {}) {
|
|
34
|
+
try {
|
|
35
|
+
return {
|
|
36
|
+
status: 0,
|
|
37
|
+
stdout: execFileSync(process.execPath, [cli, ...args, '--cwd', cwd], {
|
|
38
|
+
encoding: 'utf8',
|
|
39
|
+
env: { ...process.env, ...env },
|
|
40
|
+
}),
|
|
41
|
+
};
|
|
42
|
+
} catch (error) {
|
|
43
|
+
return {
|
|
44
|
+
status: error.status ?? 1,
|
|
45
|
+
stdout: `${error.stdout || ''}`,
|
|
46
|
+
stderr: `${error.stderr || ''}`,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function assertGolden(tool, preset) {
|
|
52
|
+
const tmp = fs.mkdtempSync(path.join(os.tmpdir(), `ai-rules-golden-check-${tool}-`));
|
|
53
|
+
try {
|
|
54
|
+
runInit(cli, tmp, tool, preset);
|
|
55
|
+
const expected = readJson(path.join(goldenRoot, tool, `${preset}.json`));
|
|
56
|
+
const actual = snapshotInstall(tmp);
|
|
57
|
+
if (tool === 'mavis') {
|
|
58
|
+
delete actual['.mavis/.ai-rules-install.json'];
|
|
59
|
+
delete expected.files['.mavis/.ai-rules-install.json'];
|
|
60
|
+
}
|
|
61
|
+
assert.deepEqual(actual, expected.files, `${tool}/${preset} golden drifted`);
|
|
62
|
+
} finally {
|
|
63
|
+
fs.rmSync(tmp, { recursive: true, force: true });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function listHomeMinimax(home) {
|
|
68
|
+
const root = path.join(home, '.minimax');
|
|
69
|
+
if (!fs.existsSync(root)) {
|
|
70
|
+
return [];
|
|
71
|
+
}
|
|
72
|
+
const out = [];
|
|
73
|
+
const visit = (dir) => {
|
|
74
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
75
|
+
const full = path.join(dir, entry.name);
|
|
76
|
+
out.push(path.relative(root, full));
|
|
77
|
+
if (entry.isDirectory()) {
|
|
78
|
+
visit(full);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
};
|
|
82
|
+
visit(root);
|
|
83
|
+
return out.sort();
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
assertGolden('cursor', 'next');
|
|
87
|
+
assertGolden('claude', 'next');
|
|
88
|
+
|
|
89
|
+
const isolatedHome = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-mavis-home-'));
|
|
90
|
+
fs.mkdirSync(path.join(isolatedHome, '.minimax'));
|
|
91
|
+
const homeBefore = listHomeMinimax(isolatedHome);
|
|
92
|
+
|
|
93
|
+
const fresh = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-mavis-fresh-'));
|
|
94
|
+
const initOut = execFileSync(
|
|
95
|
+
process.execPath,
|
|
96
|
+
[cli, 'init', 'mavis', '--preset', 'next', '--cwd', fresh],
|
|
97
|
+
{ encoding: 'utf8', env: { ...process.env, HOME: isolatedHome } },
|
|
98
|
+
);
|
|
99
|
+
assert.match(initOut, /\.mavis installed/);
|
|
100
|
+
assert.match(initOut, /do not write ~\/\.minimax\/mcp\.json/i);
|
|
101
|
+
assert.equal(fs.existsSync(path.join(fresh, 'AGENTS.md')), true);
|
|
102
|
+
assert.equal(fs.existsSync(path.join(fresh, '.mavis', 'AGENTS.md')), true);
|
|
103
|
+
assert.equal(fs.existsSync(path.join(fresh, '.mavis', 'rules', 'next-app-core.md')), true);
|
|
104
|
+
assert.equal(fs.existsSync(path.join(fresh, '.mavis', 'hooks', 'chain-team-phases.sh')), true);
|
|
105
|
+
assert.equal(fs.existsSync(path.join(fresh, '.mavis', 'mcp.json')), true);
|
|
106
|
+
assert.equal(fs.existsSync(path.join(fresh, '.mavis', 'adapter-map.json')), false);
|
|
107
|
+
assert.equal(fs.existsSync(path.join(fresh, 'docs', 'specs', 'INDEX.md')), true);
|
|
108
|
+
const manifest = readJson(path.join(fresh, '.mavis', '.ai-rules-install.json'));
|
|
109
|
+
assert.equal(manifest.schemaVersion, 1);
|
|
110
|
+
assert.equal(manifest.platform, 'mavis');
|
|
111
|
+
assert.equal(manifest.preset, 'next');
|
|
112
|
+
assert.ok(manifest.managedFiles['AGENTS.md']);
|
|
113
|
+
assert.ok(manifest.shippedMcpServerIds.includes('context7'));
|
|
114
|
+
assert.equal(manifest.docsSeed.seeded, true);
|
|
115
|
+
|
|
116
|
+
const existingAgents = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-mavis-agents-'));
|
|
117
|
+
fs.writeFileSync(path.join(existingAgents, 'AGENTS.md'), '# consumer agents\n');
|
|
118
|
+
runInit(cli, existingAgents, 'mavis', 'next');
|
|
119
|
+
assert.match(fs.readFileSync(path.join(existingAgents, 'AGENTS.md'), 'utf8'), /Mavis compatibility entry/);
|
|
120
|
+
|
|
121
|
+
const specsExist = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-mavis-specs-'));
|
|
122
|
+
fs.mkdirSync(path.join(specsExist, 'docs', 'specs'), { recursive: true });
|
|
123
|
+
runInit(cli, specsExist, 'mavis', 'next');
|
|
124
|
+
assert.equal(fs.existsSync(path.join(specsExist, 'docs', 'specs', 'INDEX.md')), false);
|
|
125
|
+
assert.equal(
|
|
126
|
+
readJson(path.join(specsExist, '.mavis', '.ai-rules-install.json')).docsSeed.reason,
|
|
127
|
+
'directory-exists',
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
writeJson(path.join(fresh, '.mavis', 'mcp.json'), {
|
|
131
|
+
mcpServers: {
|
|
132
|
+
context7: {
|
|
133
|
+
command: 'npx',
|
|
134
|
+
args: ['-y', '@upstash/context7-mcp@old'],
|
|
135
|
+
env: { CONTEXT7_API_KEY: 'keep-me' },
|
|
136
|
+
},
|
|
137
|
+
storybook: { url: 'http://localhost:6006/mcp' },
|
|
138
|
+
},
|
|
139
|
+
localOnly: true,
|
|
140
|
+
});
|
|
141
|
+
const repeat = runInit(cli, fresh, 'mavis', 'next');
|
|
142
|
+
assert.match(repeat, /kept extra servers: storybook/);
|
|
143
|
+
const merged = readJson(path.join(fresh, '.mavis', 'mcp.json'));
|
|
144
|
+
assert.equal(merged.localOnly, true);
|
|
145
|
+
assert.ok(merged.mcpServers.storybook);
|
|
146
|
+
assert.equal(merged.mcpServers.context7.env.CONTEXT7_API_KEY, 'keep-me');
|
|
147
|
+
|
|
148
|
+
merged.localNote = true;
|
|
149
|
+
writeJson(path.join(fresh, '.mavis', 'mcp.json'), merged);
|
|
150
|
+
const modifiedRel = path.join(fresh, '.mavis', 'rules', 'next-app-core.md');
|
|
151
|
+
fs.appendFileSync(modifiedRel, '\n# local overlay note\n');
|
|
152
|
+
runClean(fresh, 'mavis');
|
|
153
|
+
assert.equal(fs.existsSync(modifiedRel), true);
|
|
154
|
+
assert.equal(fs.existsSync(path.join(fresh, '.mavis', '.ai-rules-install.json')), false);
|
|
155
|
+
assert.equal(fs.existsSync(path.join(fresh, 'AGENTS.md')), false);
|
|
156
|
+
assert.ok(readJson(path.join(fresh, '.mavis', 'mcp.json')).mcpServers.storybook);
|
|
157
|
+
assert.equal(readJson(path.join(fresh, '.mavis', 'mcp.json')).mcpServers.context7, undefined);
|
|
158
|
+
assert.equal(readJson(path.join(fresh, '.mavis', 'mcp.json')).localNote, true);
|
|
159
|
+
|
|
160
|
+
const missing = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-mavis-missing-'));
|
|
161
|
+
fs.writeFileSync(path.join(missing, 'keep.txt'), 'safe\n');
|
|
162
|
+
const missingClean = runCli(missing, ['clean', 'mavis']);
|
|
163
|
+
assert.equal(missingClean.status, 1);
|
|
164
|
+
assert.match(`${missingClean.stderr}${missingClean.stdout}`, /nothing removed/);
|
|
165
|
+
assert.equal(fs.readFileSync(path.join(missing, 'keep.txt'), 'utf8'), 'safe\n');
|
|
166
|
+
|
|
167
|
+
const corrupt = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-mavis-corrupt-'));
|
|
168
|
+
fs.mkdirSync(path.join(corrupt, '.mavis'), { recursive: true });
|
|
169
|
+
fs.writeFileSync(path.join(corrupt, '.mavis', '.ai-rules-install.json'), '{not json');
|
|
170
|
+
fs.writeFileSync(path.join(corrupt, '.mavis', 'keep.md'), 'keep\n');
|
|
171
|
+
const corruptClean = runCli(corrupt, ['clean', 'mavis']);
|
|
172
|
+
assert.equal(corruptClean.status, 1);
|
|
173
|
+
assert.equal(fs.readFileSync(path.join(corrupt, '.mavis', 'keep.md'), 'utf8'), 'keep\n');
|
|
174
|
+
|
|
175
|
+
const twice = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-mavis-twice-'));
|
|
176
|
+
runInit(cli, twice, 'mavis', 'next');
|
|
177
|
+
runClean(twice, 'mavis');
|
|
178
|
+
const secondClean = runCli(twice, ['clean', 'mavis']);
|
|
179
|
+
assert.equal(secondClean.status, 1);
|
|
180
|
+
|
|
181
|
+
const rollback = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-mavis-rollback-'));
|
|
182
|
+
fs.writeFileSync(path.join(rollback, 'AGENTS.md'), '# prior\n');
|
|
183
|
+
const fail = runCli(rollback, ['init', 'mavis', '--preset', 'next'], {
|
|
184
|
+
AI_RULES_MAVIS_FAIL_PHASE: 'write',
|
|
185
|
+
});
|
|
186
|
+
assert.equal(fail.status, 1);
|
|
187
|
+
assert.equal(fs.readFileSync(path.join(rollback, 'AGENTS.md'), 'utf8'), '# prior\n');
|
|
188
|
+
assert.equal(fs.existsSync(path.join(rollback, '.mavis')), false);
|
|
189
|
+
|
|
190
|
+
const failMcp = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-mavis-fail-mcp-'));
|
|
191
|
+
fs.writeFileSync(path.join(failMcp, 'AGENTS.md'), '# prior mcp\n');
|
|
192
|
+
const failMcpRun = runCli(failMcp, ['init', 'mavis', '--preset', 'next'], {
|
|
193
|
+
AI_RULES_MAVIS_FAIL_PHASE: 'mcp',
|
|
194
|
+
});
|
|
195
|
+
assert.equal(failMcpRun.status, 1);
|
|
196
|
+
assert.equal(fs.readFileSync(path.join(failMcp, 'AGENTS.md'), 'utf8'), '# prior mcp\n');
|
|
197
|
+
assert.equal(fs.existsSync(path.join(failMcp, '.mavis')), false);
|
|
198
|
+
|
|
199
|
+
const failManifest = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-mavis-fail-manifest-'));
|
|
200
|
+
fs.writeFileSync(path.join(failManifest, 'AGENTS.md'), '# prior manifest\n');
|
|
201
|
+
const failManifestRun = runCli(failManifest, ['init', 'mavis', '--preset', 'next'], {
|
|
202
|
+
AI_RULES_MAVIS_FAIL_PHASE: 'manifest',
|
|
203
|
+
});
|
|
204
|
+
assert.equal(failManifestRun.status, 1);
|
|
205
|
+
assert.equal(fs.readFileSync(path.join(failManifest, 'AGENTS.md'), 'utf8'), '# prior manifest\n');
|
|
206
|
+
assert.equal(fs.existsSync(path.join(failManifest, '.mavis')), false);
|
|
207
|
+
|
|
208
|
+
const toolFirst = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-mavis-tool-first-'));
|
|
209
|
+
execFileSync(process.execPath, [cli, 'mavis', 'init', '--preset', 'next', '--cwd', toolFirst], {
|
|
210
|
+
encoding: 'utf8',
|
|
211
|
+
});
|
|
212
|
+
assert.equal(fs.existsSync(path.join(toolFirst, '.mavis', 'AGENTS.md')), true);
|
|
213
|
+
|
|
214
|
+
const unreleased = runCli(fresh, ['init', 'mavis', '--preset', 'nuxt']);
|
|
215
|
+
assert.equal(unreleased.status, 1);
|
|
216
|
+
assert.match(`${unreleased.stderr}${unreleased.stdout}`, /not released yet/i);
|
|
217
|
+
assert.match(`${unreleased.stderr}${unreleased.stdout}`, /Released stacks: next/);
|
|
218
|
+
|
|
219
|
+
assert.deepEqual(listHomeMinimax(isolatedHome), homeBefore);
|
|
220
|
+
|
|
221
|
+
const mavisGoldenDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-mavis-golden-'));
|
|
222
|
+
runInit(cli, mavisGoldenDir, 'mavis', 'next');
|
|
223
|
+
const mavisFiles = snapshotInstall(mavisGoldenDir);
|
|
224
|
+
delete mavisFiles['.mavis/.ai-rules-install.json'];
|
|
225
|
+
const mavisGoldenPath = path.join(goldenRoot, 'mavis', 'next.json');
|
|
226
|
+
if (!fs.existsSync(mavisGoldenPath)) {
|
|
227
|
+
writeJson(mavisGoldenPath, {
|
|
228
|
+
schemaVersion: 1,
|
|
229
|
+
tool: 'mavis',
|
|
230
|
+
preset: 'next',
|
|
231
|
+
files: mavisFiles,
|
|
232
|
+
});
|
|
233
|
+
} else {
|
|
234
|
+
const expectedMavis = readJson(mavisGoldenPath);
|
|
235
|
+
delete expectedMavis.files['.mavis/.ai-rules-install.json'];
|
|
236
|
+
assert.deepEqual(mavisFiles, expectedMavis.files, 'mavis/next golden drifted');
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
for (const dir of [
|
|
240
|
+
isolatedHome,
|
|
241
|
+
fresh,
|
|
242
|
+
existingAgents,
|
|
243
|
+
specsExist,
|
|
244
|
+
missing,
|
|
245
|
+
corrupt,
|
|
246
|
+
twice,
|
|
247
|
+
rollback,
|
|
248
|
+
failMcp,
|
|
249
|
+
failManifest,
|
|
250
|
+
toolFirst,
|
|
251
|
+
mavisGoldenDir,
|
|
252
|
+
]) {
|
|
253
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
console.log('test-cli-init-clean: ok');
|
|
@@ -131,6 +131,29 @@ try {
|
|
|
131
131
|
assert.ok(restored.mcpServers.playwright);
|
|
132
132
|
fs.rmSync(deletedDir, { recursive: true, force: true });
|
|
133
133
|
|
|
134
|
+
const mavisDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ai-rules-mcp-mavis-'));
|
|
135
|
+
const mavisMcp = path.join(mavisDir, '.mavis', 'mcp.json');
|
|
136
|
+
writeJson(mavisMcp, {
|
|
137
|
+
mcpServers: {
|
|
138
|
+
context7: {
|
|
139
|
+
command: 'npx',
|
|
140
|
+
args: ['-y', '@upstash/context7-mcp@old'],
|
|
141
|
+
env: { CONTEXT7_API_KEY: 'keep-mavis' },
|
|
142
|
+
},
|
|
143
|
+
storybook: { url: 'http://localhost:6006/mcp' },
|
|
144
|
+
},
|
|
145
|
+
localOnly: true,
|
|
146
|
+
});
|
|
147
|
+
const mavisOut = init(mavisDir, 'mavis', 'next');
|
|
148
|
+
assert.match(mavisOut, /kept extra servers: storybook/);
|
|
149
|
+
const mavisMerged = readJson(mavisMcp);
|
|
150
|
+
assert.equal(mavisMerged.localOnly, true);
|
|
151
|
+
assert.ok(mavisMerged.mcpServers.storybook);
|
|
152
|
+
assert.equal(mavisMerged.mcpServers.context7.env.CONTEXT7_API_KEY, 'keep-mavis');
|
|
153
|
+
assert.ok(mavisMerged.mcpServers.playwright);
|
|
154
|
+
assert.equal(fs.existsSync(path.join(mavisDir, '.minimax')), false);
|
|
155
|
+
fs.rmSync(mavisDir, { recursive: true, force: true });
|
|
156
|
+
|
|
134
157
|
console.log('test-cli-mcp-merge: ok');
|
|
135
158
|
} finally {
|
|
136
159
|
fs.rmSync(tmp, { recursive: true, force: true });
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* P1 context budget: root AGENTS + .mavis/AGENTS combined nonempty lines ≤ 170.
|
|
4
|
+
* Critical-policy sentinels must appear in the pair.
|
|
5
|
+
*/
|
|
6
|
+
import fs from 'node:fs';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { fileURLToPath } from 'node:url';
|
|
9
|
+
import { loadReleasedMavisStacks } from './lib/discover-hooks.mjs';
|
|
10
|
+
|
|
11
|
+
const scriptDirectory = path.dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
const presetsRoot = path.join(scriptDirectory, '..', 'presets');
|
|
13
|
+
const LINE_CAP = 170;
|
|
14
|
+
const SENTINELS = [
|
|
15
|
+
'next-app-core',
|
|
16
|
+
'package-manager',
|
|
17
|
+
'code-quality-and-refactoring',
|
|
18
|
+
'ai-local-overlay',
|
|
19
|
+
'.mavis/AGENTS.md',
|
|
20
|
+
'status.json',
|
|
21
|
+
'~/.minimax',
|
|
22
|
+
];
|
|
23
|
+
|
|
24
|
+
function nonemptyLines(text) {
|
|
25
|
+
return text.split(/\r?\n/).filter((line) => line.trim().length > 0).length;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const stacks = loadReleasedMavisStacks(presetsRoot);
|
|
29
|
+
if (stacks.length === 0) {
|
|
30
|
+
console.log('ok mavis context-budget: no released stacks');
|
|
31
|
+
process.exit(0);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
let failed = 0;
|
|
35
|
+
for (const stack of stacks) {
|
|
36
|
+
const root = path.join(presetsRoot, 'mavis', stack);
|
|
37
|
+
const repoAgents = fs.readFileSync(path.join(root, 'REPO_AGENTS.md'), 'utf8');
|
|
38
|
+
const mavisAgents = fs.readFileSync(path.join(root, 'MAVIS.md'), 'utf8');
|
|
39
|
+
const combined = `${repoAgents}\n${mavisAgents}`;
|
|
40
|
+
const count = nonemptyLines(repoAgents) + nonemptyLines(mavisAgents);
|
|
41
|
+
if (count > LINE_CAP) {
|
|
42
|
+
console.error(`FAIL mavis/${stack}: entry nonempty lines ${count} > ${LINE_CAP}`);
|
|
43
|
+
failed += 1;
|
|
44
|
+
} else {
|
|
45
|
+
console.log(`ok mavis/${stack}: entry nonempty lines ${count} (cap ${LINE_CAP})`);
|
|
46
|
+
}
|
|
47
|
+
for (const sentinel of SENTINELS) {
|
|
48
|
+
if (!combined.includes(sentinel)) {
|
|
49
|
+
console.error(`FAIL mavis/${stack}: missing critical sentinel ${sentinel}`);
|
|
50
|
+
failed += 1;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
process.exit(failed === 0 ? 0 : 1);
|