@soleri/core 9.13.0 → 9.14.4
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/dist/engine/bin/soleri-engine.js +7 -2
- package/dist/engine/bin/soleri-engine.js.map +1 -1
- package/dist/flows/types.d.ts +34 -30
- package/dist/flows/types.d.ts.map +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/dist/knowledge-packs/community/.gitkeep +0 -0
- package/dist/knowledge-packs/salvador/salvador-craft/soleri-pack.json +10 -0
- package/dist/knowledge-packs/salvador/salvador-craft/vault/accessibility.json +53 -0
- package/dist/knowledge-packs/salvador/salvador-craft/vault/design-tokens.json +26 -0
- package/dist/knowledge-packs/salvador/salvador-craft/vault/design.json +33 -0
- package/dist/knowledge-packs/salvador/salvador-craft/vault/styling.json +44 -0
- package/dist/knowledge-packs/salvador/salvador-craft/vault/ux-laws.json +36 -0
- package/dist/knowledge-packs/salvador/salvador-craft/vault/ux.json +36 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/soleri-pack.json +10 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/architecture.json +143 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/commercial.json +16 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/communication.json +33 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/component.json +16 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/express.json +34 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/leadership.json +33 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/methodology.json +33 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/monorepo.json +33 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/other.json +73 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/performance.json +35 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/prisma.json +33 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/product-strategy.json +42 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/react.json +47 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/security.json +34 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/testing.json +33 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/tooling.json +85 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/typescript.json +34 -0
- package/dist/knowledge-packs/salvador/salvador-engineering/vault/workflow.json +46 -0
- package/dist/knowledge-packs/salvador/salvador-uipro/soleri-pack.json +10 -0
- package/dist/knowledge-packs/salvador/salvador-uipro/vault/design.json +2589 -0
- package/dist/knowledge-packs/starter/api-design/soleri-pack.json +9 -0
- package/dist/knowledge-packs/starter/api-design/vault/patterns.json +137 -0
- package/dist/knowledge-packs/starter/architecture/soleri-pack.json +10 -0
- package/dist/knowledge-packs/starter/architecture/vault/patterns.json +137 -0
- package/dist/knowledge-packs/starter/design/soleri-pack.json +10 -0
- package/dist/knowledge-packs/starter/design/vault/patterns.json +137 -0
- package/dist/knowledge-packs/starter/nodejs/soleri-pack.json +9 -0
- package/dist/knowledge-packs/starter/nodejs/vault/patterns.json +137 -0
- package/dist/knowledge-packs/starter/react/soleri-pack.json +9 -0
- package/dist/knowledge-packs/starter/react/vault/patterns.json +164 -0
- package/dist/knowledge-packs/starter/security/soleri-pack.json +10 -0
- package/dist/knowledge-packs/starter/security/vault/patterns.json +137 -0
- package/dist/knowledge-packs/starter/testing/soleri-pack.json +9 -0
- package/dist/knowledge-packs/starter/testing/vault/patterns.json +128 -0
- package/dist/knowledge-packs/starter/typescript/soleri-pack.json +9 -0
- package/dist/knowledge-packs/starter/typescript/vault/patterns.json +164 -0
- package/dist/packs/index.d.ts +1 -1
- package/dist/packs/index.d.ts.map +1 -1
- package/dist/packs/index.js +1 -1
- package/dist/packs/index.js.map +1 -1
- package/dist/packs/resolver.d.ts +6 -0
- package/dist/packs/resolver.d.ts.map +1 -1
- package/dist/packs/resolver.js +20 -1
- package/dist/packs/resolver.js.map +1 -1
- package/dist/runtime/admin-setup-ops.js +1 -1
- package/dist/runtime/admin-setup-ops.js.map +1 -1
- package/dist/runtime/capture-ops.d.ts.map +1 -1
- package/dist/runtime/capture-ops.js +2 -1
- package/dist/runtime/capture-ops.js.map +1 -1
- package/dist/runtime/intake-ops.d.ts.map +1 -1
- package/dist/runtime/intake-ops.js +5 -5
- package/dist/runtime/intake-ops.js.map +1 -1
- package/dist/runtime/orchestrate-ops.d.ts.map +1 -1
- package/dist/runtime/orchestrate-ops.js +26 -2
- package/dist/runtime/orchestrate-ops.js.map +1 -1
- package/dist/runtime/planning-extra-ops.d.ts.map +1 -1
- package/dist/runtime/planning-extra-ops.js +5 -7
- package/dist/runtime/planning-extra-ops.js.map +1 -1
- package/dist/runtime/playbook-ops.d.ts.map +1 -1
- package/dist/runtime/playbook-ops.js +2 -1
- package/dist/runtime/playbook-ops.js.map +1 -1
- package/dist/runtime/schema-helpers.d.ts +7 -0
- package/dist/runtime/schema-helpers.d.ts.map +1 -0
- package/dist/runtime/schema-helpers.js +21 -0
- package/dist/runtime/schema-helpers.js.map +1 -0
- package/dist/runtime/sync-ops.d.ts.map +1 -1
- package/dist/runtime/sync-ops.js +3 -4
- package/dist/runtime/sync-ops.js.map +1 -1
- package/dist/runtime/vault-extra-ops.d.ts.map +1 -1
- package/dist/runtime/vault-extra-ops.js +5 -4
- package/dist/runtime/vault-extra-ops.js.map +1 -1
- package/dist/skills/sync-skills.d.ts +26 -7
- package/dist/skills/sync-skills.d.ts.map +1 -1
- package/dist/skills/sync-skills.js +132 -32
- package/dist/skills/sync-skills.js.map +1 -1
- package/dist/skills/validate-skill-docs.d.ts +24 -0
- package/dist/skills/validate-skill-docs.d.ts.map +1 -0
- package/dist/skills/validate-skill-docs.js +476 -0
- package/dist/skills/validate-skill-docs.js.map +1 -0
- package/package.json +2 -2
- package/src/__tests__/deviation-detection.test.ts +49 -0
- package/src/enforcement/adapters/claude-code.test.ts +9 -9
- package/src/engine/bin/soleri-engine.ts +7 -2
- package/src/flows/types.ts +4 -0
- package/src/index.ts +15 -2
- package/src/packs/index.ts +6 -1
- package/src/packs/resolver.ts +24 -1
- package/src/runtime/admin-setup-ops.test.ts +2 -0
- package/src/runtime/admin-setup-ops.ts +1 -1
- package/src/runtime/capture-ops.ts +2 -1
- package/src/runtime/intake-ops.ts +7 -7
- package/src/runtime/orchestrate-ops.ts +29 -2
- package/src/runtime/planning-extra-ops.ts +35 -37
- package/src/runtime/playbook-ops.ts +2 -1
- package/src/runtime/schema-helpers.test.ts +45 -0
- package/src/runtime/schema-helpers.ts +19 -0
- package/src/runtime/sync-ops.ts +8 -9
- package/src/runtime/vault-extra-ops.ts +5 -4
- package/src/skills/__tests__/sync-skills.test.ts +102 -29
- package/src/skills/__tests__/validate-skill-docs.test.ts +58 -0
- package/src/skills/sync-skills.ts +152 -32
- package/src/skills/validate-skill-docs.ts +562 -0
package/src/index.ts
CHANGED
|
@@ -608,6 +608,9 @@ export {
|
|
|
608
608
|
export { loadPersona } from './persona/loader.js';
|
|
609
609
|
export { generatePersonaInstructions, getRandomSignoff } from './persona/prompt-generator.js';
|
|
610
610
|
|
|
611
|
+
// ─── Schema Helpers ────────────────────────────────────────────────
|
|
612
|
+
export { coerceArray } from './runtime/schema-helpers.js';
|
|
613
|
+
|
|
611
614
|
// ─── Runtime Factory ────────────────────────────────────────────────
|
|
612
615
|
export { createAgentRuntime } from './runtime/runtime.js';
|
|
613
616
|
export { createSemanticFacades } from './runtime/facades/index.js';
|
|
@@ -688,7 +691,12 @@ export {
|
|
|
688
691
|
} from './packs/index.js';
|
|
689
692
|
export type { PackState, PackTransition } from './packs/index.js';
|
|
690
693
|
export { PackLockfile, inferPackType } from './packs/index.js';
|
|
691
|
-
export {
|
|
694
|
+
export {
|
|
695
|
+
resolvePack,
|
|
696
|
+
checkNpmVersion,
|
|
697
|
+
checkVersionCompat,
|
|
698
|
+
getBuiltinKnowledgePacksDirs,
|
|
699
|
+
} from './packs/index.js';
|
|
692
700
|
export type {
|
|
693
701
|
PackManifest,
|
|
694
702
|
PackStatus,
|
|
@@ -717,7 +725,12 @@ export {
|
|
|
717
725
|
checkSkillCompatibility,
|
|
718
726
|
ApprovalRequiredError,
|
|
719
727
|
} from './skills/sync-skills.js';
|
|
720
|
-
export type {
|
|
728
|
+
export type {
|
|
729
|
+
SkillEntry,
|
|
730
|
+
SyncResult,
|
|
731
|
+
SyncOptions,
|
|
732
|
+
ClassifySkillsOptions,
|
|
733
|
+
} from './skills/sync-skills.js';
|
|
721
734
|
|
|
722
735
|
// ─── Plugin System ──────────────────────────────────────────────────────
|
|
723
736
|
export {
|
package/src/packs/index.ts
CHANGED
|
@@ -26,5 +26,10 @@ export { PackLifecycleManager } from './pack-lifecycle.js';
|
|
|
26
26
|
export { PackLockfile, inferPackType, LOCKFILE_VERSION } from './lockfile.js';
|
|
27
27
|
export type { LockEntry, PackType, PackSource, PackTier, LockfileData } from './lockfile.js';
|
|
28
28
|
|
|
29
|
-
export {
|
|
29
|
+
export {
|
|
30
|
+
resolvePack,
|
|
31
|
+
checkNpmVersion,
|
|
32
|
+
checkVersionCompat,
|
|
33
|
+
getBuiltinKnowledgePacksDirs,
|
|
34
|
+
} from './resolver.js';
|
|
30
35
|
export type { ResolvedPack, ResolveOptions } from './resolver.js';
|
package/src/packs/resolver.ts
CHANGED
|
@@ -10,10 +10,33 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { existsSync } from 'node:fs';
|
|
13
|
-
import { resolve, join } from 'node:path';
|
|
13
|
+
import { resolve, join, dirname } from 'node:path';
|
|
14
14
|
import { execFileSync } from 'node:child_process';
|
|
15
15
|
import { mkdirSync, readdirSync } from 'node:fs';
|
|
16
16
|
import { tmpdir } from 'node:os';
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
|
|
19
|
+
// ─── Built-in knowledge-packs discovery ──────────────────────────────
|
|
20
|
+
|
|
21
|
+
const _dirname =
|
|
22
|
+
typeof __dirname !== 'undefined' ? __dirname : dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Discover the built-in `knowledge-packs/` directory shipped with @soleri/core.
|
|
26
|
+
* Tries multiple levels up from this file's compiled location to account for
|
|
27
|
+
* different installation layouts (monorepo dev, npm install, npx).
|
|
28
|
+
*/
|
|
29
|
+
export function getBuiltinKnowledgePacksDirs(): string[] {
|
|
30
|
+
const dirs: string[] = [];
|
|
31
|
+
for (let i = 1; i <= 5; i++) {
|
|
32
|
+
const candidate = resolve(_dirname, ...Array<string>(i).fill('..'), 'knowledge-packs');
|
|
33
|
+
if (existsSync(candidate)) {
|
|
34
|
+
dirs.push(candidate);
|
|
35
|
+
break;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return dirs;
|
|
39
|
+
}
|
|
17
40
|
|
|
18
41
|
// ─── Types ────────────────────────────────────────────────────────────
|
|
19
42
|
|
|
@@ -425,7 +425,7 @@ export function createAdminSetupOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
425
425
|
const agentName =
|
|
426
426
|
runtime.persona?.name ??
|
|
427
427
|
config.agentId.charAt(0).toUpperCase() + config.agentId.slice(1);
|
|
428
|
-
skillsResults = syncSkillsToClaudeCode(skillsSourceDirs, agentName);
|
|
428
|
+
skillsResults = syncSkillsToClaudeCode(skillsSourceDirs, agentName, { global: true });
|
|
429
429
|
} else {
|
|
430
430
|
// Dry run — just discover what would be synced
|
|
431
431
|
const skills = discoverSkills(skillsSourceDirs);
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
import type { OpDefinition } from '../facades/types.js';
|
|
10
10
|
import type { AgentRuntime } from './types.js';
|
|
11
|
+
import { coerceArray } from './schema-helpers.js';
|
|
11
12
|
import { detectScope } from '../vault/scope-detector.js';
|
|
12
13
|
import type { ScopeTier, ScopeDetectionResult } from '../vault/scope-detector.js';
|
|
13
14
|
import { syncEntryToMarkdown } from '../vault/vault-markdown-sync.js';
|
|
@@ -35,7 +36,7 @@ export function createCaptureOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
35
36
|
.enum(['agent', 'project', 'team'])
|
|
36
37
|
.optional()
|
|
37
38
|
.describe('Manual tier override. If omitted, tier is auto-detected from content.'),
|
|
38
|
-
entries:
|
|
39
|
+
entries: coerceArray(
|
|
39
40
|
z.object({
|
|
40
41
|
id: z.string().optional(),
|
|
41
42
|
type: z
|
|
@@ -9,6 +9,7 @@ import { z } from 'zod';
|
|
|
9
9
|
import type { OpDefinition } from '../facades/types.js';
|
|
10
10
|
import type { IntakePipeline } from '../intake/intake-pipeline.js';
|
|
11
11
|
import type { TextIngester, IngestSource } from '../intake/text-ingester.js';
|
|
12
|
+
import { coerceArray } from './schema-helpers.js';
|
|
12
13
|
|
|
13
14
|
/**
|
|
14
15
|
* Create the 7 intake operations.
|
|
@@ -191,9 +192,9 @@ export function createIntakeOps(
|
|
|
191
192
|
'Ingest multiple text items in one call. Each item has its own source metadata. Processed sequentially.',
|
|
192
193
|
auth: 'write',
|
|
193
194
|
schema: z.object({
|
|
194
|
-
items:
|
|
195
|
-
|
|
196
|
-
|
|
195
|
+
items: coerceArray(
|
|
196
|
+
z
|
|
197
|
+
.object({
|
|
197
198
|
text: z.string(),
|
|
198
199
|
title: z.string(),
|
|
199
200
|
sourceType: z.enum(['article', 'transcript', 'notes', 'documentation']).optional(),
|
|
@@ -201,10 +202,9 @@ export function createIntakeOps(
|
|
|
201
202
|
author: z.string().optional(),
|
|
202
203
|
domain: z.string().optional(),
|
|
203
204
|
tags: z.array(z.string()).optional(),
|
|
204
|
-
})
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
.describe('Array of items to ingest'),
|
|
205
|
+
})
|
|
206
|
+
.strict(),
|
|
207
|
+
).describe('Array of items to ingest (at least 1)'),
|
|
208
208
|
}),
|
|
209
209
|
handler: async (params) => {
|
|
210
210
|
if (!textIngester) return { error: 'Text ingester not configured (LLM client required)' };
|
|
@@ -136,12 +136,32 @@ export function applyWorkflowOverride(plan: OrchestrationPlan, override: Workflo
|
|
|
136
136
|
}
|
|
137
137
|
}
|
|
138
138
|
|
|
139
|
+
// Inject workflow prompt.md content if available
|
|
140
|
+
if (override.prompt) {
|
|
141
|
+
plan.workflowPrompt = override.prompt;
|
|
142
|
+
plan.workflowName = override.name;
|
|
143
|
+
}
|
|
144
|
+
|
|
139
145
|
// Add workflow info to warnings for visibility
|
|
140
146
|
plan.warnings.push(
|
|
141
147
|
`Workflow override "${override.name}" applied (${override.gates.length} gate(s), ${override.tools.length} tool(s)).`,
|
|
142
148
|
);
|
|
143
149
|
}
|
|
144
150
|
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
// Workflow prompt preamble helper
|
|
153
|
+
// ---------------------------------------------------------------------------
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Prepend workflow prompt content to a task prompt when available.
|
|
157
|
+
* Returns the original prompt unchanged if no workflow prompt is set.
|
|
158
|
+
*/
|
|
159
|
+
function withWorkflowPreamble(taskPrompt: string, plan: OrchestrationPlan | undefined): string {
|
|
160
|
+
if (!plan?.workflowPrompt) return taskPrompt;
|
|
161
|
+
const header = plan.workflowName ? `## Workflow: ${plan.workflowName}` : '## Workflow';
|
|
162
|
+
return `${header}\n${plan.workflowPrompt}\n\n## Task\n${taskPrompt}`;
|
|
163
|
+
}
|
|
164
|
+
|
|
145
165
|
// ---------------------------------------------------------------------------
|
|
146
166
|
// In-memory plan store
|
|
147
167
|
// ---------------------------------------------------------------------------
|
|
@@ -532,7 +552,7 @@ export function createOrchestrateOps(
|
|
|
532
552
|
const tasks =
|
|
533
553
|
entry?.plan.steps.map((s) => ({
|
|
534
554
|
taskId: s.id,
|
|
535
|
-
prompt: s.name,
|
|
555
|
+
prompt: withWorkflowPreamble(s.name, entry?.plan),
|
|
536
556
|
workspace: process.cwd(),
|
|
537
557
|
runtime: runtimeType,
|
|
538
558
|
timeout: 300_000,
|
|
@@ -610,7 +630,8 @@ export function createOrchestrateOps(
|
|
|
610
630
|
if (runtimeType && runtime.adapterRegistry) {
|
|
611
631
|
const adapter = runtime.adapterRegistry.get(runtimeType);
|
|
612
632
|
const entry = planStore.get(planId);
|
|
613
|
-
const
|
|
633
|
+
const rawPrompt = entry?.plan.summary ?? `Execute plan ${planId}`;
|
|
634
|
+
const prompt = withWorkflowPreamble(rawPrompt, entry?.plan);
|
|
614
635
|
|
|
615
636
|
const adapterResult = await adapter.execute({
|
|
616
637
|
runId: `${planId}-${Date.now()}`,
|
|
@@ -692,6 +713,11 @@ export function createOrchestrateOps(
|
|
|
692
713
|
const healthStatus = contextHealth.check();
|
|
693
714
|
const healthWarning = buildHealthWarning(healthStatus, vault);
|
|
694
715
|
|
|
716
|
+
// Build workflow preamble for the calling agent's context
|
|
717
|
+
const workflowPreamble = entry.plan.workflowPrompt
|
|
718
|
+
? withWorkflowPreamble(entry.plan.summary, entry.plan)
|
|
719
|
+
: undefined;
|
|
720
|
+
|
|
695
721
|
return {
|
|
696
722
|
plan: { id: planId, status: 'executing' },
|
|
697
723
|
session,
|
|
@@ -702,6 +728,7 @@ export function createOrchestrateOps(
|
|
|
702
728
|
toolsCalled: executionResult.toolsCalled,
|
|
703
729
|
durationMs: executionResult.durationMs,
|
|
704
730
|
},
|
|
731
|
+
...(workflowPreamble ? { workflowPreamble } : {}),
|
|
705
732
|
...(healthWarning ? { contextHealth: healthWarning } : {}),
|
|
706
733
|
};
|
|
707
734
|
}
|
|
@@ -13,6 +13,7 @@ import fs from 'node:fs';
|
|
|
13
13
|
import { z } from 'zod';
|
|
14
14
|
import type { OpDefinition } from '../facades/types.js';
|
|
15
15
|
import type { AgentRuntime } from './types.js';
|
|
16
|
+
import { coerceArray } from './schema-helpers.js';
|
|
16
17
|
import type { DriftItem, TaskEvidence } from '../planning/planner.js';
|
|
17
18
|
import type { PlanRunManifest } from '../flows/types.js';
|
|
18
19
|
import { getPlanRunDir } from '../flows/executor.js';
|
|
@@ -64,25 +65,24 @@ export function createPlanningExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
64
65
|
)
|
|
65
66
|
.optional()
|
|
66
67
|
.describe('Rejected alternative approaches (replaces existing)'),
|
|
67
|
-
addTasks:
|
|
68
|
-
.
|
|
69
|
-
z.
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
)
|
|
68
|
+
addTasks: coerceArray(
|
|
69
|
+
z.object({
|
|
70
|
+
title: z.string(),
|
|
71
|
+
description: z.string(),
|
|
72
|
+
phase: z
|
|
73
|
+
.string()
|
|
74
|
+
.optional()
|
|
75
|
+
.describe('Phase this task belongs to (e.g., "wave-1", "discovery")'),
|
|
76
|
+
milestone: z
|
|
77
|
+
.string()
|
|
78
|
+
.optional()
|
|
79
|
+
.describe('Milestone this task contributes to (e.g., "v1.0", "mvp")'),
|
|
80
|
+
parentTaskId: z.string().optional().describe('Parent task ID for sub-task hierarchy'),
|
|
81
|
+
}),
|
|
82
|
+
)
|
|
83
83
|
.optional()
|
|
84
84
|
.describe('Tasks to append'),
|
|
85
|
-
removeTasks:
|
|
85
|
+
removeTasks: coerceArray(z.string()).optional().describe('Task IDs to remove'),
|
|
86
86
|
}),
|
|
87
87
|
handler: async (params) => {
|
|
88
88
|
try {
|
|
@@ -116,26 +116,24 @@ export function createPlanningExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
116
116
|
auth: 'write',
|
|
117
117
|
schema: z.object({
|
|
118
118
|
planId: z.string().describe('Plan ID to split tasks for'),
|
|
119
|
-
tasks:
|
|
120
|
-
.
|
|
121
|
-
z.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
.
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
)
|
|
138
|
-
.describe('New task list with optional dependency references (task-1, task-2, etc.)'),
|
|
119
|
+
tasks: coerceArray(
|
|
120
|
+
z.object({
|
|
121
|
+
title: z.string(),
|
|
122
|
+
description: z.string(),
|
|
123
|
+
dependsOn: z.array(z.string()).optional().describe('Task IDs this task depends on'),
|
|
124
|
+
phase: z
|
|
125
|
+
.string()
|
|
126
|
+
.optional()
|
|
127
|
+
.describe(
|
|
128
|
+
'Phase this task belongs to (e.g., "wave-1", "discovery", "implementation")',
|
|
129
|
+
),
|
|
130
|
+
milestone: z
|
|
131
|
+
.string()
|
|
132
|
+
.optional()
|
|
133
|
+
.describe('Milestone this task contributes to (e.g., "v1.0", "mvp", "beta")'),
|
|
134
|
+
parentTaskId: z.string().optional().describe('Parent task ID for sub-task hierarchy'),
|
|
135
|
+
}),
|
|
136
|
+
).describe('New task list with optional dependency references (task-1, task-2, etc.)'),
|
|
139
137
|
}),
|
|
140
138
|
handler: async (params) => {
|
|
141
139
|
try {
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import { z } from 'zod';
|
|
9
9
|
import type { OpDefinition } from '../facades/types.js';
|
|
10
10
|
import type { AgentRuntime } from './types.js';
|
|
11
|
+
import { coerceArray } from './schema-helpers.js';
|
|
11
12
|
import { parsePlaybookFromEntry, validatePlaybook } from '../vault/playbook.js';
|
|
12
13
|
import {
|
|
13
14
|
matchPlaybooks,
|
|
@@ -69,7 +70,7 @@ export function createPlaybookOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
69
70
|
title: z.string(),
|
|
70
71
|
domain: z.string(),
|
|
71
72
|
description: z.string(),
|
|
72
|
-
steps:
|
|
73
|
+
steps: coerceArray(
|
|
73
74
|
z.object({
|
|
74
75
|
title: z.string(),
|
|
75
76
|
description: z.string(),
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Colocated unit tests for schema-helpers.ts — coerceArray Zod helper.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect } from 'vitest';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { coerceArray } from './schema-helpers.js';
|
|
8
|
+
|
|
9
|
+
describe('coerceArray', () => {
|
|
10
|
+
const schema = coerceArray(z.string());
|
|
11
|
+
|
|
12
|
+
it('passes through a native array unchanged', () => {
|
|
13
|
+
const result = schema.parse(['a', 'b', 'c']);
|
|
14
|
+
expect(result).toEqual(['a', 'b', 'c']);
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
it('coerces a JSON-stringified array', () => {
|
|
18
|
+
const result = schema.parse(JSON.stringify(['x', 'y']));
|
|
19
|
+
expect(result).toEqual(['x', 'y']);
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('rejects invalid JSON strings', () => {
|
|
23
|
+
expect(() => schema.parse('not-json')).toThrow();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('rejects non-array JSON (object)', () => {
|
|
27
|
+
expect(() => schema.parse(JSON.stringify({ a: 1 }))).toThrow();
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('rejects non-array JSON (number)', () => {
|
|
31
|
+
expect(() => schema.parse(JSON.stringify(42))).toThrow();
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it('works with complex item schemas', () => {
|
|
35
|
+
const complex = coerceArray(z.object({ id: z.string(), value: z.number() }));
|
|
36
|
+
const items = [{ id: 'a', value: 1 }];
|
|
37
|
+
expect(complex.parse(JSON.stringify(items))).toEqual(items);
|
|
38
|
+
expect(complex.parse(items)).toEqual(items);
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('still validates item types after coercion', () => {
|
|
42
|
+
// Array of strings schema should reject array of numbers
|
|
43
|
+
expect(() => schema.parse(JSON.stringify([1, 2, 3]))).toThrow();
|
|
44
|
+
});
|
|
45
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wraps a Zod array schema so it also accepts a JSON-stringified array.
|
|
5
|
+
* MCP transports sometimes serialize array params as strings.
|
|
6
|
+
*/
|
|
7
|
+
export function coerceArray<T extends z.ZodTypeAny>(itemSchema: T) {
|
|
8
|
+
return z.preprocess((val) => {
|
|
9
|
+
if (typeof val === 'string') {
|
|
10
|
+
try {
|
|
11
|
+
const parsed = JSON.parse(val);
|
|
12
|
+
if (Array.isArray(parsed)) return parsed;
|
|
13
|
+
} catch {
|
|
14
|
+
/* fall through to let Zod reject */
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
return val;
|
|
18
|
+
}, z.array(itemSchema));
|
|
19
|
+
}
|
package/src/runtime/sync-ops.ts
CHANGED
|
@@ -10,6 +10,7 @@
|
|
|
10
10
|
import { z } from 'zod';
|
|
11
11
|
import type { OpDefinition } from '../facades/types.js';
|
|
12
12
|
import type { AgentRuntime } from './types.js';
|
|
13
|
+
import { coerceArray } from './schema-helpers.js';
|
|
13
14
|
import { GitVaultSync, type GitVaultSyncConfig } from '../vault/git-vault-sync.js';
|
|
14
15
|
import type {
|
|
15
16
|
IntelligenceEntry,
|
|
@@ -257,15 +258,13 @@ export function createSyncOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
257
258
|
'Import an intelligence pack into the vault with content-hash dedup. Entries with duplicate content are skipped.',
|
|
258
259
|
auth: 'write' as const,
|
|
259
260
|
schema: z.object({
|
|
260
|
-
bundles:
|
|
261
|
-
.
|
|
262
|
-
z.
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
)
|
|
268
|
-
.describe('Array of IntelligenceBundle objects to import'),
|
|
261
|
+
bundles: coerceArray(
|
|
262
|
+
z.object({
|
|
263
|
+
domain: z.string(),
|
|
264
|
+
version: z.string(),
|
|
265
|
+
entries: z.array(z.record(z.unknown())),
|
|
266
|
+
}),
|
|
267
|
+
).describe('Array of IntelligenceBundle objects to import'),
|
|
269
268
|
tier: z
|
|
270
269
|
.enum(['agent', 'project', 'team'])
|
|
271
270
|
.optional()
|
|
@@ -13,6 +13,7 @@ import { join, basename } from 'node:path';
|
|
|
13
13
|
import type { OpDefinition } from '../facades/types.js';
|
|
14
14
|
import type { IntelligenceEntry } from '../intelligence/types.js';
|
|
15
15
|
import type { AgentRuntime } from './types.js';
|
|
16
|
+
import { coerceArray } from './schema-helpers.js';
|
|
16
17
|
|
|
17
18
|
const entrySchema = z.object({
|
|
18
19
|
id: z.string(),
|
|
@@ -115,7 +116,7 @@ export function createVaultExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
115
116
|
description: 'Add multiple vault entries at once. Uses upsert — existing IDs are updated.',
|
|
116
117
|
auth: 'write',
|
|
117
118
|
schema: z.object({
|
|
118
|
-
entries:
|
|
119
|
+
entries: coerceArray(entrySchema),
|
|
119
120
|
}),
|
|
120
121
|
handler: async (params) => {
|
|
121
122
|
const entries = params.entries as IntelligenceEntry[];
|
|
@@ -128,7 +129,7 @@ export function createVaultExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
128
129
|
description: 'Remove multiple vault entries by IDs in a single transaction.',
|
|
129
130
|
auth: 'admin',
|
|
130
131
|
schema: z.object({
|
|
131
|
-
ids:
|
|
132
|
+
ids: coerceArray(z.string()),
|
|
132
133
|
}),
|
|
133
134
|
handler: async (params) => {
|
|
134
135
|
const ids = params.ids as string[];
|
|
@@ -177,7 +178,7 @@ export function createVaultExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
177
178
|
'Import vault entries from a JSON bundle. Uses upsert — existing IDs are updated, new IDs are inserted.',
|
|
178
179
|
auth: 'write',
|
|
179
180
|
schema: z.object({
|
|
180
|
-
entries:
|
|
181
|
+
entries: coerceArray(entrySchema),
|
|
181
182
|
}),
|
|
182
183
|
handler: async (params) => {
|
|
183
184
|
const entries = params.entries as IntelligenceEntry[];
|
|
@@ -198,7 +199,7 @@ export function createVaultExtraOps(runtime: AgentRuntime): OpDefinition[] {
|
|
|
198
199
|
'Seed the vault from intelligence data. Idempotent — safe to call multiple times. Uses upsert.',
|
|
199
200
|
auth: 'write',
|
|
200
201
|
schema: z.object({
|
|
201
|
-
entries:
|
|
202
|
+
entries: coerceArray(entrySchema),
|
|
202
203
|
}),
|
|
203
204
|
handler: async (params) => {
|
|
204
205
|
const entries = params.entries as IntelligenceEntry[];
|