@agentic-survey/core 0.1.0 → 0.1.1

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/index.d.ts CHANGED
@@ -5,3 +5,4 @@ export { createSurvey, updateSurvey, publishSurvey, closeSurvey, listSurveys, ge
5
5
  export { addQuestion, updateQuestion, removeQuestion, reorderQuestions, } from './questions.js';
6
6
  export { listResponses, type ResponseWithAnswers } from './responses.js';
7
7
  export { getResults } from './results.js';
8
+ export { validateSurvey, type SurveyLintIssue } from './lint.js';
package/dist/index.js CHANGED
@@ -5,3 +5,4 @@ export { createSurvey, updateSurvey, publishSurvey, closeSurvey, listSurveys, ge
5
5
  export { addQuestion, updateQuestion, removeQuestion, reorderQuestions, } from './questions.js';
6
6
  export { listResponses } from './responses.js';
7
7
  export { getResults } from './results.js';
8
+ export { validateSurvey } from './lint.js';
package/dist/lint.d.ts ADDED
@@ -0,0 +1,15 @@
1
+ import type { Question } from '@agentic-survey/schema';
2
+ /**
3
+ * Static checks an agent should run before publishing — especially once skip
4
+ * logic is involved. Pure and DB-free: operates on the survey's questions.
5
+ *
6
+ * Errors block a sensible publish (broken/cyclic logic); warnings flag likely
7
+ * mistakes (e.g. a comparison op on a non-numeric question).
8
+ */
9
+ export interface SurveyLintIssue {
10
+ level: 'error' | 'warning';
11
+ code: string;
12
+ questionId?: string;
13
+ message: string;
14
+ }
15
+ export declare function validateSurvey(questions: Question[]): SurveyLintIssue[];
package/dist/lint.js ADDED
@@ -0,0 +1,69 @@
1
+ const CHOICE_TYPES = new Set(['single_choice', 'multi_choice']);
2
+ const NUMERIC_TYPES = new Set(['rating', 'number', 'slider']);
3
+ const NUMERIC_OPS = new Set(['gt', 'gte', 'lt', 'lte']);
4
+ const CHOICE_OPS = new Set(['equals', 'not_equals', 'includes']);
5
+ function optionIds(q) {
6
+ const opts = q.config.options ?? [];
7
+ return opts.map((o) => o.id);
8
+ }
9
+ export function validateSurvey(questions) {
10
+ const issues = [];
11
+ const byId = new Map(questions.map((q) => [q.id, q]));
12
+ // Structural: choice questions need options; option ids must be unique.
13
+ for (const q of questions) {
14
+ if (CHOICE_TYPES.has(q.type)) {
15
+ const ids = optionIds(q);
16
+ if (ids.length === 0)
17
+ issues.push({ level: 'error', code: 'no_options', questionId: q.id, message: `${q.type} question "${q.prompt}" has no options.` });
18
+ const dupes = ids.filter((id, i) => ids.indexOf(id) !== i);
19
+ if (dupes.length)
20
+ issues.push({ level: 'error', code: 'duplicate_option_id', questionId: q.id, message: `Duplicate option id(s): ${[...new Set(dupes)].join(', ')}.` });
21
+ }
22
+ }
23
+ // Logic checks.
24
+ for (const q of questions) {
25
+ const logic = q.logic;
26
+ if (!logic)
27
+ continue;
28
+ if (!logic.conditions || logic.conditions.length === 0) {
29
+ issues.push({ level: 'warning', code: 'empty_logic', questionId: q.id, message: `Question "${q.prompt}" has a logic rule with no conditions (it has no effect).` });
30
+ continue;
31
+ }
32
+ for (const c of logic.conditions) {
33
+ const target = byId.get(c.questionId);
34
+ if (!target) {
35
+ issues.push({ level: 'error', code: 'dangling_reference', questionId: q.id, message: `Condition references unknown question id "${c.questionId}".` });
36
+ continue;
37
+ }
38
+ if (target.id === q.id) {
39
+ issues.push({ level: 'error', code: 'self_reference', questionId: q.id, message: `Question "${q.prompt}" references itself in its logic.` });
40
+ continue;
41
+ }
42
+ if (target.position >= q.position) {
43
+ issues.push({ level: 'error', code: 'forward_reference', questionId: q.id, message: `Condition references a later question ("${target.prompt}"). Conditions may only reference earlier questions.` });
44
+ }
45
+ checkOpCompatibility(q, target, c, issues);
46
+ }
47
+ }
48
+ return issues;
49
+ }
50
+ function checkOpCompatibility(q, target, c, issues) {
51
+ if (c.op === 'answered' || c.op === 'not_answered')
52
+ return;
53
+ if (NUMERIC_OPS.has(c.op)) {
54
+ if (!NUMERIC_TYPES.has(target.type))
55
+ issues.push({ level: 'warning', code: 'op_type_mismatch', questionId: q.id, message: `Op "${c.op}" targets a ${target.type} question; expected rating/number.` });
56
+ if (typeof c.value !== 'number')
57
+ issues.push({ level: 'error', code: 'bad_operand', questionId: q.id, message: `Op "${c.op}" needs a numeric value.` });
58
+ return;
59
+ }
60
+ // Choice equality against a choice question: operand must be a real option id.
61
+ if (CHOICE_OPS.has(c.op) && CHOICE_TYPES.has(target.type)) {
62
+ const ids = optionIds(target);
63
+ if (typeof c.value !== 'string' || !ids.includes(c.value))
64
+ issues.push({ level: 'error', code: 'unknown_option', questionId: q.id, message: `Condition value "${String(c.value)}" is not an option of "${target.prompt}" — this question can never become visible as written.` });
65
+ return;
66
+ }
67
+ if (target.type === 'yes_no' && (c.op === 'equals' || c.op === 'not_equals') && typeof c.value !== 'boolean')
68
+ issues.push({ level: 'warning', code: 'bad_operand', questionId: q.id, message: `yes_no comparison expects a boolean value.` });
69
+ }
@@ -1,4 +1,4 @@
1
- import type { Question, QuestionType, QuestionConfig } from '@agentic-survey/schema';
1
+ import type { Question, QuestionType, QuestionConfig, QuestionLogic } from '@agentic-survey/schema';
2
2
  import type { Db } from './client.js';
3
3
  import { type Result } from './result.js';
4
4
  export declare function addQuestion(db: Db, surveyId: string, input: {
@@ -6,6 +6,7 @@ export declare function addQuestion(db: Db, surveyId: string, input: {
6
6
  prompt: string;
7
7
  required?: boolean;
8
8
  config?: QuestionConfig;
9
+ logic?: QuestionLogic | null;
9
10
  position?: number;
10
11
  }): Promise<Result<Question>>;
11
12
  export declare function updateQuestion(db: Db, questionId: string, patch: {
@@ -13,6 +14,7 @@ export declare function updateQuestion(db: Db, questionId: string, patch: {
13
14
  prompt?: string;
14
15
  required?: boolean;
15
16
  config?: QuestionConfig;
17
+ logic?: QuestionLogic | null;
16
18
  position?: number;
17
19
  }): Promise<Result<Question>>;
18
20
  export declare function removeQuestion(db: Db, questionId: string): Promise<Result<void>>;
package/dist/questions.js CHANGED
@@ -23,6 +23,7 @@ export async function addQuestion(db, surveyId, input) {
23
23
  prompt: input.prompt,
24
24
  required: input.required ?? false,
25
25
  config: input.config ?? {},
26
+ logic: input.logic ?? null,
26
27
  position,
27
28
  })
28
29
  .select()
@@ -46,6 +47,8 @@ export async function updateQuestion(db, questionId, patch) {
46
47
  row.required = patch.required;
47
48
  if (patch.config !== undefined)
48
49
  row.config = patch.config;
50
+ if (patch.logic !== undefined)
51
+ row.logic = patch.logic;
49
52
  if (patch.position !== undefined)
50
53
  row.position = patch.position;
51
54
  const { data, error } = await db
package/dist/results.js CHANGED
@@ -60,7 +60,7 @@ function yesNoAggregate(values) {
60
60
  function numericAggregate(values) {
61
61
  const nums = [];
62
62
  for (const v of values)
63
- if (v.kind === 'rating' || v.kind === 'number')
63
+ if (v.kind === 'rating' || v.kind === 'number' || v.kind === 'slider')
64
64
  nums.push(v.value);
65
65
  const totalAnswered = nums.length;
66
66
  if (!totalAnswered) {
@@ -88,9 +88,12 @@ function numericAggregate(values) {
88
88
  }
89
89
  function textAggregate(values) {
90
90
  const responses = [];
91
- for (const v of values)
91
+ for (const v of values) {
92
92
  if (v.kind === 'short_text' || v.kind === 'long_text')
93
93
  responses.push(v.text);
94
+ else if (v.kind === 'date' || v.kind === 'time')
95
+ responses.push(v.value);
96
+ }
94
97
  return { kind: 'text', totalAnswered: responses.length, responses };
95
98
  }
96
99
  function aggregateQuestion(q, values) {
@@ -102,9 +105,12 @@ function aggregateQuestion(q, values) {
102
105
  return yesNoAggregate(values);
103
106
  case 'rating':
104
107
  case 'number':
108
+ case 'slider':
105
109
  return numericAggregate(values);
106
110
  case 'short_text':
107
111
  case 'long_text':
112
+ case 'date':
113
+ case 'time':
108
114
  return textAggregate(values);
109
115
  }
110
116
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentic-survey/core",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Pure TypeScript service layer for survey logic. No MCP, no HTTP, no framework types.",
5
5
  "license": "MIT",
6
6
  "author": "Monty Daley <monty@daley.org.nz>",