ktg-plugin-marketplace/plugins/voyage/tests/validators/plan-validator.test.mjs
Kjell Tore Guttormsen 7a90d348ad feat(voyage)!: marketplace handoff — rename plugins/ultraplan-local to plugins/voyage [skip-docs]
Session 5 of voyage-rebrand (V6). Operator-authorized cross-plugin scope.

- git mv plugins/ultraplan-local plugins/voyage (rename detected, history preserved)
- .claude-plugin/marketplace.json: voyage entry replaces ultraplan-local
- CLAUDE.md: voyage row in plugin list, voyage in design-system consumer list
- README.md: bulk rename ultra*-local commands -> trek* commands; ultraplan-local refs -> voyage; type discriminators (type: trekbrief/trekreview); session-title pattern (voyage:<command>:<slug>); v4.0.0 release-note paragraph
- plugins/voyage/.claude-plugin/plugin.json: homepage/repository URLs point to monorepo voyage path
- plugins/voyage/verify.sh: drop URL whitelist exception (no longer needed)

Closes voyage-rebrand. bash plugins/voyage/verify.sh PASS 7/7. npm test 361/361.
2026-05-05 15:37:52 +02:00

99 lines
2.4 KiB
JavaScript

import { test } from 'node:test';
import { strict as assert } from 'node:assert';
import { validatePlanContent } from '../../lib/validators/plan-validator.mjs';
const VALID_PLAN = `---
plan_version: "1.7"
---
# Plan
## Implementation Plan
### Step 1: Add foo
- Files: a.ts
- Manifest:
\`\`\`yaml
manifest:
expected_paths:
- a.ts
min_file_count: 1
commit_message_pattern: "^feat:"
bash_syntax_check: []
forbidden_paths: []
must_contain: []
\`\`\`
### Step 2: Add bar
- Files: b.ts
- Manifest:
\`\`\`yaml
manifest:
expected_paths:
- b.ts
min_file_count: 1
commit_message_pattern: "^feat:"
bash_syntax_check: []
forbidden_paths: []
must_contain: []
\`\`\`
`;
const FORBIDDEN_PLAN = `---
plan_version: "1.7"
---
## Fase 1: Drift form
content
`;
const STEP_WITHOUT_MANIFEST = `### Step 1: oops
no manifest
### Step 2: ok
- Manifest:
\`\`\`yaml
manifest:
expected_paths: [foo]
min_file_count: 1
commit_message_pattern: "^x:"
bash_syntax_check: []
forbidden_paths: []
must_contain: []
\`\`\`
`;
test('validatePlan — strict accepts canonical v1.7 plan', () => {
const r = validatePlanContent(VALID_PLAN, { strict: true });
assert.equal(r.valid, true, JSON.stringify(r.errors));
assert.equal(r.parsed.steps.length, 2);
assert.equal(r.parsed.planVersion, '1.7');
});
test('validatePlan — forbidden Fase form blocks in strict mode', () => {
const r = validatePlanContent(FORBIDDEN_PLAN, { strict: true });
assert.equal(r.valid, false);
assert.ok(r.errors.find(e => e.code === 'PLAN_FORBIDDEN_HEADING'));
});
test('validatePlan — manifest count mismatch caught', () => {
const r = validatePlanContent(STEP_WITHOUT_MANIFEST, { strict: true });
assert.equal(r.valid, false);
assert.ok(r.errors.find(e => /Step 1/.test(e.message) && /MANIFEST_MISSING/.test(e.code)));
});
test('validatePlan — version warning when missing', () => {
const noVersion = VALID_PLAN.replace(/plan_version: "1\.7"\n/, '');
const r = validatePlanContent(noVersion, { strict: true });
assert.ok(r.warnings.find(w => w.code === 'PLAN_NO_VERSION'));
});
test('validatePlan — older version triggers warning', () => {
const old = VALID_PLAN.replace('plan_version: "1.7"', 'plan_version: "1.5"');
const r = validatePlanContent(old, { strict: true });
assert.ok(r.warnings.find(w => w.code === 'PLAN_VERSION_MISMATCH'));
});