@cleocode/contracts 2026.8.9 → 2026.9.2

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.
Files changed (41) hide show
  1. package/dist/acceptance-gate.d.ts +18 -1
  2. package/dist/acceptance-gate.d.ts.map +1 -1
  3. package/dist/branch-lock.d.ts +24 -0
  4. package/dist/branch-lock.d.ts.map +1 -1
  5. package/dist/branch-lock.js.map +1 -1
  6. package/dist/conduit.d.ts +36 -3
  7. package/dist/conduit.d.ts.map +1 -1
  8. package/dist/data-accessor.d.ts +22 -1
  9. package/dist/data-accessor.d.ts.map +1 -1
  10. package/dist/data-accessor.js.map +1 -1
  11. package/dist/dispatch/operations-registry.d.ts.map +1 -1
  12. package/dist/dispatch/operations-registry.js +14 -2
  13. package/dist/dispatch/operations-registry.js.map +1 -1
  14. package/dist/index.d.ts +1 -0
  15. package/dist/index.d.ts.map +1 -1
  16. package/dist/index.js +1 -0
  17. package/dist/index.js.map +1 -1
  18. package/dist/operations/output-contracts-data.d.ts.map +1 -1
  19. package/dist/operations/output-contracts-data.js +18 -3
  20. package/dist/operations/output-contracts-data.js.map +1 -1
  21. package/dist/operations/session.d.ts +13 -1
  22. package/dist/operations/session.d.ts.map +1 -1
  23. package/dist/operations/tasks.d.ts +39 -1
  24. package/dist/operations/tasks.d.ts.map +1 -1
  25. package/dist/operations/tasks.js.map +1 -1
  26. package/dist/task-id.d.ts +86 -0
  27. package/dist/task-id.d.ts.map +1 -0
  28. package/dist/task-id.js +118 -0
  29. package/dist/task-id.js.map +1 -0
  30. package/package.json +2 -2
  31. package/src/__tests__/__snapshots__/operations-registry.test.ts.snap +1 -1
  32. package/src/acceptance-gate.ts +18 -1
  33. package/src/branch-lock.ts +24 -0
  34. package/src/conduit.ts +36 -3
  35. package/src/data-accessor.ts +30 -1
  36. package/src/dispatch/operations-registry.ts +17 -2
  37. package/src/index.ts +2 -0
  38. package/src/operations/output-contracts-data.ts +18 -3
  39. package/src/operations/session.ts +13 -1
  40. package/src/operations/tasks.ts +39 -1
  41. package/src/task-id.ts +127 -0
@@ -335,7 +335,24 @@ export interface AcceptanceGateResult {
335
335
  req?: string;
336
336
  /** Gate variant. */
337
337
  kind: AcceptanceGateKind;
338
- /** Outcome of the gate execution. */
338
+ /**
339
+ * Outcome of the gate execution.
340
+ *
341
+ * `fail` and `error` are NOT interchangeable, and the difference is the whole
342
+ * point of having both (gh#1270):
343
+ *
344
+ * - `pass` — ran to completion, satisfied.
345
+ * - `fail` — ran to completion, **found a problem**. A verdict.
346
+ * - `warn` — a `fail` on a gate marked `advisory`.
347
+ * - `skipped` — deliberately not run.
348
+ * - `error` — **did not finish**: killed by timeout or signal, or could not
349
+ * be executed. This is *not a verdict*. Recording it as `fail` produces a
350
+ * false red, which costs an agent work it had actually completed and writes
351
+ * an assertion into the evidence record that a check found a problem when
352
+ * none was ever made. Consumers must treat `error` as "unknown, re-run",
353
+ * never as "failed" — see the mapping in `lifecycle/index.ts`, which
354
+ * resolves it to `pending`.
355
+ */
339
356
  result: 'pass' | 'fail' | 'warn' | 'skipped' | 'error';
340
357
  /** Wall-clock duration of the gate execution in milliseconds. */
341
358
  durationMs: number;
@@ -89,6 +89,30 @@ export interface WorktreeMergeResult {
89
89
  branchDeleted: boolean;
90
90
  /** Error message if any step failed (non-fatal — caller decides). */
91
91
  error?: string;
92
+ /**
93
+ * True when there was **nothing to integrate** — no `task/<taskId>` branch and
94
+ * no worktree directory ever existed for this task.
95
+ *
96
+ * @remarks
97
+ * T12153 (GH #1223): this is NOT a failure, and conflating it with one made
98
+ * `cleo complete` log
99
+ *
100
+ * ```
101
+ * WARN … mergeError: "task branch 'task/T1655' does not exist"
102
+ * ```
103
+ *
104
+ * for every task worked on a feature branch and merged by PR — which is most
105
+ * of them. `merged: false` plus an `error` was the only shape available, so a
106
+ * routine, correct outcome was reported at WARN as an integration failure.
107
+ *
108
+ * Noise at WARN is worse than silence: it trains a reader to ignore a channel
109
+ * that should mean something, so the one genuine rebase conflict is filtered
110
+ * out along with the hundred non-events.
111
+ *
112
+ * Callers should treat `nothingToIntegrate: true` as success-with-no-work,
113
+ * and reserve failure handling for `merged: false` WITHOUT this flag.
114
+ */
115
+ nothingToIntegrate?: boolean;
92
116
  }
93
117
 
94
118
  /**
package/src/conduit.ts CHANGED
@@ -119,12 +119,45 @@ export interface ConduitSendOptions {
119
119
  metadata?: Record<string, unknown>;
120
120
  }
121
121
 
122
- /** Result of sending a message. */
122
+ /**
123
+ * Result of sending a message.
124
+ *
125
+ * ## Why `deliveredAt` is nullable (gh#1316)
126
+ *
127
+ * It was `deliveredAt: string`, and `ConduitClient.send` filled it with
128
+ * `new Date().toISOString()` for a row the transport had just written with
129
+ * `status = 'pending'`. That is not an approximation of a delivery time — the
130
+ * transport returns none, the row says the message is undelivered, and the
131
+ * schema has a SEPARATE real `delivered_at` that a different code path fills in
132
+ * later. The system models the distinction correctly everywhere except at the
133
+ * boundary where it is reported.
134
+ *
135
+ * Because the field was REQUIRED, "accepted but not yet delivered" — the normal
136
+ * case, and the state every message passes through — was **unrepresentable at
137
+ * the type level**. So the only way to satisfy the contract was to invent a
138
+ * value, which is what the code did.
139
+ *
140
+ * The two facts are now separate: `acceptedAt` is what a send genuinely knows,
141
+ * and `deliveredAt` is `null` until delivery actually happens.
142
+ */
123
143
  export interface ConduitSendResult {
124
144
  /** The assigned message ID. */
125
145
  messageId: string;
126
- /** ISO 8601 timestamp of delivery. */
127
- deliveredAt: string;
146
+ /**
147
+ * ISO 8601 timestamp at which the transport ACCEPTED the message.
148
+ *
149
+ * Always known at send time. This is what the old `deliveredAt` actually
150
+ * contained.
151
+ */
152
+ acceptedAt: string;
153
+ /**
154
+ * ISO 8601 timestamp of actual delivery, or `null` when not yet delivered.
155
+ *
156
+ * `null` is the normal result of a `send`: the row is written `pending` and a
157
+ * later path records real delivery. A non-null value here means delivery is
158
+ * confirmed — never that it is assumed.
159
+ */
160
+ deliveredAt: string | null;
128
161
  }
129
162
 
130
163
  /** Unsubscribe function returned by event subscriptions. */
@@ -15,7 +15,15 @@
15
15
 
16
16
  import type { ArchivedTask } from './archive.js';
17
17
  import type { Session } from './session.js';
18
- import type { Task, TaskPriority, TaskSize, TaskStatus, TaskType } from './task.js';
18
+ import type {
19
+ Task,
20
+ TaskKind,
21
+ TaskPriority,
22
+ TaskSeverity,
23
+ TaskSize,
24
+ TaskStatus,
25
+ TaskType,
26
+ } from './task.js';
19
27
 
20
28
  /**
21
29
  * Agent instance row shape for DataAccessor methods.
@@ -59,6 +67,27 @@ export interface TaskQueryFilters {
59
67
  status?: TaskStatus | TaskStatus[];
60
68
  priority?: TaskPriority;
61
69
  type?: TaskType;
70
+ /**
71
+ * Severity axis filter (`P0`-`P3`). Orthogonal to {@link priority}.
72
+ *
73
+ * @remarks
74
+ * T12120 (GH #1245): `severity` is a first-class ADR-066 axis that
75
+ * `cleo add --severity` persists, but it had no read path at any layer —
76
+ * so `cleo list --severity P0` returned every task with no signal that the
77
+ * constraint had been dropped. A filter that is accepted and not applied
78
+ * fails OPEN, which is the most dangerous possible answer to "show me only
79
+ * the critical items".
80
+ */
81
+ severity?: TaskSeverity | TaskSeverity[];
82
+ /**
83
+ * Kind axis filter (`work`/`research`/`experiment`/`bug`/`spike`/`release`).
84
+ * Orthogonal to {@link type}. Stored in the `role` DB column.
85
+ *
86
+ * @remarks
87
+ * T12120 (GH #1245): missing from the read path for the same reason as
88
+ * {@link severity}.
89
+ */
90
+ kind?: TaskKind | TaskKind[];
62
91
  parentId?: string | null; // null = root tasks only
63
92
  phase?: string;
64
93
  label?: string;
@@ -292,19 +292,34 @@ export const OPERATIONS: OperationDef[] = [
292
292
  { name: 'status', type: 'string', required: false, description: 'Filter by task status' },
293
293
  { name: 'priority', type: 'string', required: false, description: 'Filter by task priority' },
294
294
  { name: 'type', type: 'string', required: false, description: 'Filter by task type' },
295
+ {
296
+ name: 'severity',
297
+ type: 'string',
298
+ required: false,
299
+ description: 'Filter by severity axis (P0|P1|P2|P3); invalid values are rejected',
300
+ },
301
+ {
302
+ name: 'kind',
303
+ type: 'string',
304
+ required: false,
305
+ description:
306
+ 'Filter by kind axis (work|research|experiment|bug|spike|release); invalid values are rejected',
307
+ },
295
308
  { name: 'phase', type: 'string', required: false, description: 'Filter by task phase' },
296
309
  { name: 'label', type: 'string', required: false, description: 'Filter by task label' },
297
310
  {
298
311
  name: 'children',
299
312
  type: 'boolean',
300
313
  required: false,
301
- description: 'Limit parent queries to direct children',
314
+ description:
315
+ 'DEPRECATED (no effect; slated for removal in a future major): --parent already returns DIRECT children only, for every parent type including sagas. Do not use.',
302
316
  },
303
317
  {
304
318
  name: 'limit',
305
319
  type: 'number',
306
320
  required: false,
307
- description: 'Maximum number of tasks to return',
321
+ description:
322
+ 'Maximum number of tasks to return (default 10). Use --all to return every match. (--limit 0 also returns every match, but only on this command — prefer --all.)',
308
323
  },
309
324
  {
310
325
  name: 'offset',
package/src/index.ts CHANGED
@@ -2669,3 +2669,5 @@ export {
2669
2669
  } from './caamp-markers.js';
2670
2670
  // T12077: SSoT for the keys under which payloads carry their rows.
2671
2671
  export { COLLECTION_KEYS, type CollectionKey } from './collection-keys.js';
2672
+
2673
+ export { isStorableTaskId, isTaskId, TASK_ID_REGEX, type TaskId } from './task-id.js';
@@ -66,10 +66,15 @@ const tasksShowOutputContract: OperationOutputContract = {
66
66
  operation: 'tasks.show',
67
67
  shapeNote:
68
68
  'The task record is nested under `task` — use /data/task/<field>, not /data/<field>. ' +
69
+ 'Mutation envelopes are FLAT by contrast (/data/created/0, /data/updated/0), which is ' +
70
+ 'the asymmetry that produced GH #1225/#1239/#1231. ' +
69
71
  '`view` may be null. `acRows` and `relations` are conditional. ' +
70
- 'The listed pointers are the default (MVI) projection — `verification`, `acceptance`, ' +
71
- '`description`, and `evidence` live only in the full record (`cleo show <id> --full`); ' +
72
- '`--field` resolves them transparently when they exist (T12108).',
72
+ '`description`, `acceptance` and `verification` are withheld by the default (MVI) ' +
73
+ 'projection but `--field` resolves them transparently (T12108) — no `--full` needed. ' +
74
+ 'Evidence is NOT a task field: it lives at /data/task/verification/evidence ' +
75
+ '(keyed by gate), alongside /data/task/verification/gates. T12127 (GH #1231) — the ' +
76
+ 'previous wording advertised `/data/task/evidence`, which does not exist, in the very ' +
77
+ 'text shown to an agent whose pointer had just failed.',
73
78
  dataSchema: {
74
79
  type: 'object',
75
80
  required: ['task', 'view', 'attachments'],
@@ -114,6 +119,16 @@ const tasksShowOutputContract: OperationOutputContract = {
114
119
  '/data/task/priority',
115
120
  '/data/task/type',
116
121
  '/data/task/parentId',
122
+ // T12127 (GH #1231) — withheld by the MVI projection but resolvable
123
+ // through `--field` since T12108. They were absent from this list, so the
124
+ // remediation an agent is shown on a failed pointer never mentioned the
125
+ // fields it most often wanted, and `cleo show --field /data/task` came
126
+ // back looking like a write that had silently done nothing.
127
+ '/data/task/description',
128
+ '/data/task/acceptance',
129
+ '/data/task/verification',
130
+ '/data/task/verification/gates',
131
+ '/data/task/verification/evidence',
117
132
  '/data/view/id',
118
133
  '/data/view/title',
119
134
  ],
@@ -242,6 +242,17 @@ export interface BriefingFieldRule {
242
242
  * Violations are emitted when an item carries one of these provenance values.
243
243
  */
244
244
  excludeProvenance?: BriefingExcludeProvenance[];
245
+ /**
246
+ * Keys that MUST be present on this field's object, even when the value is
247
+ * `null`. Applies to object-valued fields (not lists) and exists to stop an
248
+ * absent key from being indistinguishable from a recorded-empty one.
249
+ *
250
+ * The motivating case (GH #1277) is `lastSession.handoff.note`: a handoff
251
+ * that recorded no note omitted the key entirely, so a lost handoff and a
252
+ * note-free handoff were byte-identical to a consumer. `null` satisfies the
253
+ * rule; a missing key does not.
254
+ */
255
+ requireKeys?: readonly string[];
245
256
  }
246
257
 
247
258
  /**
@@ -284,8 +295,9 @@ export interface ContractViolation {
284
295
  * - `stale` — field data exceeds `maxAgeDays`.
285
296
  * - `duplicate` — two items share the same `dedupBy` key value.
286
297
  * - `excluded-provenance` — item carries a banned provenance tag.
298
+ * - `missing-key` — an object field omits a key named in `requireKeys`.
287
299
  */
288
- kind: 'stale' | 'duplicate' | 'excluded-provenance';
300
+ kind: 'stale' | 'duplicate' | 'excluded-provenance' | 'missing-key';
289
301
  /** Severity — P0 violations block `cleo briefing --strict`. */
290
302
  severity: 'P0' | 'P1';
291
303
  }
@@ -24,7 +24,7 @@ import type { TaskAnalysisResult, TaskRef } from '../results.js';
24
24
  * Common task types (API contract — matches CLI src/types/task.ts)
25
25
  */
26
26
  import type { TaskStatus } from '../status-registry.js';
27
- import type { TaskPriority, TaskType } from '../task.js';
27
+ import type { TaskKind, TaskPriority, TaskSeverity, TaskType } from '../task.js';
28
28
  import type { TaskRecord } from '../task-record.js';
29
29
  import type { ExternalTask, ExternalTaskLink, ReconcileResult } from '../task-sync.js';
30
30
  import type {
@@ -83,8 +83,34 @@ export interface TasksListParams {
83
83
  status?: TaskStatus;
84
84
  priority?: TaskPriority;
85
85
  type?: string;
86
+ /**
87
+ * Filter by severity axis (`P0`-`P3`), orthogonal to {@link priority}.
88
+ *
89
+ * @remarks
90
+ * T12120 (GH #1245): previously absent from every layer of the read path,
91
+ * so `cleo list --severity P0` returned every task with no signal that the
92
+ * constraint had been dropped. Values outside `TASK_SEVERITIES` are rejected
93
+ * with `E_VALIDATION` — an unrecognised filter value must never widen a
94
+ * result set.
95
+ */
96
+ severity?: TaskSeverity | TaskSeverity[];
97
+ /**
98
+ * Filter by kind axis (ADR-066), orthogonal to {@link type}.
99
+ *
100
+ * @remarks
101
+ * T12120 (GH #1246): absent from the read path for the same reason as
102
+ * {@link severity}. Stored in the `role` DB column.
103
+ */
104
+ kind?: TaskKind | TaskKind[];
86
105
  phase?: string;
87
106
  label?: string;
107
+ /**
108
+ * No-op, retained for compatibility.
109
+ *
110
+ * @remarks
111
+ * T12120 (GH #1247): `--parent` already restricts to DIRECT children on
112
+ * every path, so there is no transitive mode for this flag to narrow from.
113
+ */
88
114
  children?: boolean;
89
115
  limit?: number;
90
116
  offset?: number;
@@ -1401,6 +1427,18 @@ export interface TasksAddParams {
1401
1427
  description?: string;
1402
1428
  /** Canonical wire field for parent task ID. @see ADR-057 D2 */
1403
1429
  parent?: string;
1430
+ /**
1431
+ * Where {@link parent} came from, when the caller knows.
1432
+ *
1433
+ * @remarks
1434
+ * T12136 (GH #1232/#1238): the CLI infers a parent from the session's
1435
+ * `current` pointer before dispatch, so core cannot tell an inherited parent
1436
+ * from a named one. Without that distinction
1437
+ * `E_CLEO_DEPTH_EXCEEDED` names a task the caller never mentioned and
1438
+ * suggests reparenting into a hierarchy it never intended to touch.
1439
+ * Defaults to `'explicit'`.
1440
+ */
1441
+ parentSource?: 'explicit' | 'session-inference';
1404
1442
  depends?: string[];
1405
1443
  priority?: string;
1406
1444
  labels?: string[];
package/src/task-id.ts ADDED
@@ -0,0 +1,127 @@
1
+ /**
2
+ * The canonical shape of a task identifier (T12128).
3
+ *
4
+ * ## Why this file exists
5
+ *
6
+ * The repo carried at least five different opinions about what a task ID looks
7
+ * like, and they disagreed with each other:
8
+ *
9
+ * | pattern | where | disagrees about |
10
+ * |---|---|---|
11
+ * | `/^T[0-9]{1,7}$/` | `goal.ts`, `evidence-atom-schema.ts` (8 sites) | upper bound |
12
+ * | `/^T(\d{3,})$/` | `tasks/id-generator.ts` | rejects `T12`, allows `T123456789` |
13
+ * | `/^T\d{3,}$/` | `sentient/tick.ts`, `nexus/query.ts` | same |
14
+ * | `/\bT(\d{4,})\b/` | `lifecycle/consolidate-rcasd.ts` | unanchored, 4-digit minimum |
15
+ * | `/^(T\d+(-[A-Z]\w*)?\|E-\d+...)$/` | `changesets.ts` | allows suffixes and `E-` |
16
+ *
17
+ * Disagreement between validators is survivable. What was not survivable is
18
+ * that **none of them ran on the write path**. A project path was written into
19
+ * the `id` column of `tasks_tasks` — `id='/mnt/projects/cleocode'`,
20
+ * `title='Task /mnt/projects/cleocode'`, `type=null` — and the row is now
21
+ * **immortal**: `cleo list` returns it, but `cleo show`, `cleo update` and
22
+ * `cleo delete` all reject the id as malformed before they can reach it. The
23
+ * read-side validators that would have prevented it are the ones that make it
24
+ * unfixable.
25
+ *
26
+ * {@link TASK_ID_REGEX} deliberately adopts the `{1,7}` shape already used at
27
+ * eight sites rather than inventing a sixth opinion. It is permissive by
28
+ * design: the job here is to reject values that are not task IDs at all, not to
29
+ * re-litigate how many digits a task ID should have. Narrowing it later is a
30
+ * separate decision with its own migration.
31
+ *
32
+ * @module
33
+ * @task T12128
34
+ */
35
+
36
+ /**
37
+ * Canonical task-identifier pattern: `T` followed by 1-7 digits.
38
+ *
39
+ * Anchored at both ends — an unanchored variant would accept `see T1234 for
40
+ * details` as an id, which is how prose ends up in an id column.
41
+ */
42
+ export const TASK_ID_REGEX = /^T[0-9]{1,7}$/;
43
+
44
+ /**
45
+ * A string known to be a well-formed task identifier.
46
+ *
47
+ * Branded so a validated id cannot be confused with an arbitrary string at a
48
+ * call site that matters.
49
+ */
50
+ export type TaskId = string & { readonly __brand: 'TaskId' };
51
+
52
+ /**
53
+ * Whether `value` is a well-formed task identifier.
54
+ *
55
+ * @param value - candidate identifier.
56
+ * @returns `true` when `value` matches {@link TASK_ID_REGEX}.
57
+ *
58
+ * @example
59
+ * ```ts
60
+ * isTaskId('T12128'); // true
61
+ * isTaskId('/mnt/projects/cleo'); // false — the value that got written
62
+ * isTaskId('see T1234 for details');// false — anchoring matters
63
+ * ```
64
+ */
65
+ export function isTaskId(value: unknown): value is TaskId {
66
+ return typeof value === 'string' && TASK_ID_REGEX.test(value);
67
+ }
68
+
69
+ /**
70
+ * Characters and shapes that can never be a task identifier.
71
+ *
72
+ * ## Why this exists alongside {@link TASK_ID_REGEX}
73
+ *
74
+ * `TASK_ID_REGEX` describes the *canonical* id the generator produces. It is
75
+ * NOT what the store actually contains, and enforcing it at the write path
76
+ * would break working features. Measured against a live store of 3,198 tasks:
77
+ *
78
+ * | id | origin |
79
+ * |---|---|
80
+ * | `T-RECONCILE-FOLLOWUP-v2026.5.63-6` | generated ON PURPOSE by `archive-reason-invariant.ts` |
81
+ * | `T932EP` | unknown, pre-existing |
82
+ * | `/mnt/projects/cleocode` | the corruption this guard exists to stop |
83
+ *
84
+ * So CLEO deliberately mints structured ids that its own canonical pattern
85
+ * rejects — and, separately, that `cleo show` rejects too, which makes those
86
+ * release follow-up tasks as unreachable as the corrupt row. That is a real
87
+ * defect, but it is a **design** question about the id space, and settling it
88
+ * inside a guard that stops path-shaped garbage would be scope creep with a
89
+ * broken `cleo release reconcile` as the cost.
90
+ *
91
+ * This predicate therefore rejects only what cannot be an identifier under any
92
+ * shape: empty, whitespace, path separators, control characters, or absurd
93
+ * length. It catches the actual harm without taking a position on digits.
94
+ */
95
+ const FORBIDDEN_IN_TASK_ID = /[\s/\\\x00-\x1f\x7f]/;
96
+
97
+ /** Longest plausible identifier; anything beyond this is not an id. */
98
+ const MAX_TASK_ID_LENGTH = 64;
99
+
100
+ /**
101
+ * Whether `value` is storable as a task identifier.
102
+ *
103
+ * Deliberately broader than {@link isTaskId}: it admits every id shape CLEO
104
+ * actually mints (including the structured `T-RECONCILE-FOLLOWUP-…` ids the
105
+ * release reconciler creates) while rejecting values that are not identifiers
106
+ * at all. Use this at write chokepoints; use {@link isTaskId} when you
107
+ * genuinely need the canonical generated shape.
108
+ *
109
+ * @param value - candidate identifier.
110
+ * @returns `true` when `value` can be stored as an id.
111
+ *
112
+ * @example
113
+ * ```ts
114
+ * isStorableTaskId('T12128'); // true
115
+ * isStorableTaskId('T-RECONCILE-FOLLOWUP-v2026.5.63-6'); // true — CLEO mints these
116
+ * isStorableTaskId('/mnt/projects/cleocode'); // false — a path
117
+ * isStorableTaskId('see T1234 for details'); // false — whitespace
118
+ * ```
119
+ */
120
+ export function isStorableTaskId(value: unknown): value is TaskId {
121
+ if (typeof value !== 'string') return false;
122
+ if (value.length === 0 || value.length > MAX_TASK_ID_LENGTH) return false;
123
+ if (FORBIDDEN_IN_TASK_ID.test(value)) return false;
124
+ // An identifier must start with a letter — this rejects paths beginning
125
+ // `./`, `~`, or a digit-only value that is really a count.
126
+ return /^[A-Za-z]/.test(value);
127
+ }